Skip to content
8 changes: 6 additions & 2 deletions Documentation/Diagnostics/PH2080.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@

## Introduction

Avoid hardcoded absolute paths, as they are bound the change without notice.
Avoid hardcoded absolute paths, as they are bound the change without notice. For performance reasons, only the first 259 charcters of a string literal are inspected.

## How to solve

Use configuration instead, for example using `App.config`.
Use configuration instead, for example using `App.config`.

## Remarks

Only Windows absolute or UNC paths are reported. Formatted like: "c:\\temp\example.xml" or "\\server\share\example.xml"

## Example

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ namespace Philips.CodeAnalysis.MaintainabilityAnalyzers.Maintainability
/*
* Analyzer for hardcoded absolute path.
* Reports diagnostics if an absolute path is used,
* For example: c:\users\Bin\example.xml - Windows & /home/kt/abc.sql - Linux
* For example: c:\users\Bin\example.xml
* or: \\server\\share\example.xml
*
* This Analyzer only reports diagnostics on Windows.
*/
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class NoHardCodedPathsAnalyzer : SingleDiagnosticAnalyzer
{
private const int MaxStringLength = 259;
private const string Title = @"Avoid hardcoded absolute paths";
private const string MessageFormat = Title;
private const string Description = Title;
private readonly Regex WindowsPattern = new(@"^[a-zA-Z]:\\{1,2}(((?![<>:/\\|?*]).)+((?<![ .])\\{1,2})?)*$", RegexOptions.Singleline | RegexOptions.Compiled, TimeSpan.FromSeconds(1));
private readonly Regex _windowsPattern = new(@"^(([a-zA-Z]:)|\\)\\{1,2}(((?![<>:/\\|?*]).)+((?<![ .])\\{1,2})?)*$", RegexOptions.Singleline | RegexOptions.Compiled, TimeSpan.FromSeconds(1));

public NoHardCodedPathsAnalyzer()
: base(DiagnosticId.NoHardcodedPaths, Title, MessageFormat, Description, Categories.Maintainability)
Expand All @@ -45,14 +49,20 @@ private void Analyze(SyntaxNodeAnalysisContext context)
return;
}

//if the character of the string do not match either of the characters : for windows and / for linux; no need to run regex, simply return.
if (!pathValue[1].Equals(':') && !pathValue[0].Equals('/'))
// If the character of the string do not match either of the characters : or \\ ; no need to run regex, simply return.
if (!pathValue.Contains(":") && !pathValue.Contains("\\"))
{
return;
}

// Limit to first MAX_PATH characters, to prevent long analysis times.
if (pathValue.Length > MaxStringLength)
{
pathValue = pathValue.Substring(0, MaxStringLength);
}

// If the pattern matches the text value, report the diagnostic.
if (WindowsPattern.IsMatch(pathValue))
if (_windowsPattern.IsMatch(pathValue))
{
Location location = stringLiteralExpressionNode.GetLocation();
var diagnostic = Diagnostic.Create(Rule, location);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protected override DiagnosticAnalyzer GetDiagnosticAnalyzer()

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task CatchesHardCodedAbsoluteWindowsPathsAsync()
public async Task CatchesHardCodedAbsoluteWindowsPaths()
{
const string template = @"
using System;
Expand All @@ -33,12 +33,11 @@ public void Test()
}
";
await VerifyDiagnostic(template, DiagnosticId.NoHardcodedPaths).ConfigureAwait(false);

}

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task CatchesHardCodedAbsoluteWindowsPathWithDoubleSlashAsync()
public async Task CatchesHardCodedAbsoluteWindowsPathWithDoubleSlash()
{
const string template = @"
using System;
Expand All @@ -51,13 +50,12 @@ public void Test()
}
";
await VerifyDiagnostic(template, DiagnosticId.NoHardcodedPaths).ConfigureAwait(false);

}


[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task CatchesHardCodedPathsRegardlessOfCaseAsync()
public async Task CatchesHardCodedPathsRegardlessOfCase()
{
const string template = @"
using System;
Expand All @@ -70,12 +68,28 @@ public void Test()
}
";
await VerifyDiagnostic(template, DiagnosticId.NoHardcodedPaths).ConfigureAwait(false);
}

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task CatchesHardCodedPathsAsUnc()
{
const string template = @"
using System;
class Foo
{
public void Test()
{
string path = @""\\server\share\BIN\EXAMPLE.XML"";
}
}
";
await VerifyDiagnostic(template, DiagnosticId.NoHardcodedPaths).ConfigureAwait(false);
}

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task CatchesHardCodedPaths2Async()
public async Task CatchesHardCodedPathsAsAbsolute()
{
const string template = @"
using System;
Expand All @@ -99,12 +113,11 @@ public void Test()
}
";
await VerifyDiagnostic(template, DiagnosticId.NoHardcodedPaths).ConfigureAwait(false);

}

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task CatchesHardCodedPathsWithSpaceAsync()
public async Task CatchesHardCodedPathsWithSpace()
{
const string template = @"
using System;
Expand All @@ -118,12 +131,29 @@ public void Test()
";

await VerifyDiagnostic(template, DiagnosticId.NoHardcodedPaths).ConfigureAwait(false);
}

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task CatchesHardCodedPathsInLongStringLiterals()
{
const string template = @"
using System;
class Foo
{
public void Test()
{
string path = @""c:\users\test in a veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery long string literal"";
}
}
";

await VerifyDiagnostic(template, DiagnosticId.NoHardcodedPaths).ConfigureAwait(false);
}

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task CatchesHardCodedPathsWithSpecialCharactersAsync()
public async Task CatchesHardCodedPathsWithSpecialCharacters()
{
const string template = @"
using System;
Expand All @@ -137,13 +167,12 @@ public void Test()
";

await VerifyDiagnostic(template, DiagnosticId.NoHardcodedPaths).ConfigureAwait(false);

}


[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task DoesNotCatchNormalStringAsync()
public async Task DoesNotCatchNormalString()
{
const string template = @"
using System;
Expand All @@ -162,7 +191,7 @@ public void Test()

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task DoesNotCatchShortStringAsync()
public async Task DoesNotCatchShortString()
{
const string template = @"
using System;
Expand All @@ -180,7 +209,7 @@ public void Test()

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task DoesNotCatchEmptyStringAsync()
public async Task DoesNotCatchEmptyString()
{
const string template = @"
using System;
Expand All @@ -198,7 +227,7 @@ public void Test()

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task DoesNotCatchRelativePathAsync()
public async Task DoesNotCatchRelativePath()
{
const string template = @"
using system;
Expand All @@ -215,7 +244,7 @@ public void test()

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task DoesNotCatchPathsInCommentsAsync()
public async Task DoesNotCatchPathsInComments()
{
const string template = @"
using System;
Expand All @@ -236,7 +265,7 @@ public void Test()

[TestMethod]
[TestCategory(TestDefinitions.UnitTests)]
public async Task DoesNotCatchPathsInTestCodeAsync()
public async Task DoesNotCatchPathsInTestCode()
{
const string template = @"
using System;
Expand Down
Loading