diff --git a/.editorconfig b/.editorconfig index 41acd8b..48ac757 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,6 @@ # ATC coding rules - https://github.com/atc-net/atc-coding-rules -# Version: 1.0.0 -# Updated: 25-09-2023 +# Version: 1.0.1 +# Updated: 03-06-2024 # Location: Root # Distribution: DotNet8 # Inspired by: https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options @@ -464,12 +464,58 @@ dotnet_diagnostic.MA0048.severity = error # https://github.com/atc-net dotnet_diagnostic.CA1014.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA1014.md dotnet_diagnostic.CA1068.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA1068.md dotnet_diagnostic.CA1305.severity = error +dotnet_diagnostic.CA1308.severity = suggestion # Normalize strings to uppercase +dotnet_diagnostic.CA1510.severity = suggestion # Use ArgumentNullException throw helper +dotnet_diagnostic.CA1511.severity = suggestion # Use ArgumentException throw helper +dotnet_diagnostic.CA1512.severity = suggestion # Use ArgumentOutOfRangeException throw helper +dotnet_diagnostic.CA1513.severity = suggestion # Use ObjectDisposedException throw helper +dotnet_diagnostic.CA1514.severity = error # Avoid redundant length argument +dotnet_diagnostic.CA1515.severity = suggestion # Because an application's API isn't typically referenced from outside the assembly, types can be made internal (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515) dotnet_diagnostic.CA1707.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA1707.md dotnet_diagnostic.CA1812.severity = none dotnet_diagnostic.CA1822.severity = suggestion +dotnet_diagnostic.CA1849.severity = error # Call async methods when in an async method +dotnet_diagnostic.CA1854.severity = suggestion # Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method +dotnet_diagnostic.CA1855.severity = suggestion # Prefer 'Clear' over 'Fill' +dotnet_diagnostic.CA1856.severity = error # Incorrect usage of ConstantExpected attribute +dotnet_diagnostic.CA1857.severity = suggestion # A constant is expected for the parameter +dotnet_diagnostic.CA1858.severity = suggestion # Use 'StartsWith' instead of 'IndexOf' +dotnet_diagnostic.CA1859.severity = suggestion # Use concrete types when possible for improved performance +dotnet_diagnostic.CA1860.severity = suggestion # Avoid using 'Enumerable.Any()' extension method +dotnet_diagnostic.CA1861.severity = suggestion # Avoid constant arrays as arguments +dotnet_diagnostic.CA1862.severity = error # Use the 'StringComparison' method overloads to perform case-insensitive string comparisons +dotnet_diagnostic.CA1863.severity = suggestion # Use 'CompositeFormat' +dotnet_diagnostic.CA1864.severity = suggestion # Prefer the 'IDictionary.TryAdd(TKey, TValue)' method +dotnet_diagnostic.CA1865.severity = suggestion # Use char overload +dotnet_diagnostic.CA1866.severity = suggestion # Use char overload +dotnet_diagnostic.CA1867.severity = suggestion # Use char overload +dotnet_diagnostic.CA1868.severity = suggestion # Unnecessary call to 'Contains(item)' +dotnet_diagnostic.CA1869.severity = suggestion # Cache and reuse 'JsonSerializerOptions' instances +dotnet_diagnostic.CA1870.severity = suggestion # Use a cached 'SearchValues' instance dotnet_diagnostic.CA2007.severity = suggestion # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/CA2007.md +dotnet_diagnostic.CA2017.severity = error # Parameter count mismatch +dotnet_diagnostic.CA2018.severity = error # The count argument to Buffer.BlockCopy should specify the number of bytes to copy +dotnet_diagnostic.CA2019.severity = error # ThreadStatic fields should not use inline initialization +dotnet_diagnostic.CA2021.severity = error # Don't call Enumerable.Cast or Enumerable.OfType with incompatible types +dotnet_diagnostic.CA2250.severity = suggestion # Use ThrowIfCancellationRequested +dotnet_diagnostic.CA2252.severity = suggestion # Opt-in to preview features should be used with caution +dotnet_diagnostic.CA2253.severity = error # Named placeholders should not be numeric values +dotnet_diagnostic.CA2254.severity = suggestion # Template should be a static expression +dotnet_diagnostic.CA2255.severity = suggestion # The ModuleInitializer attribute should not be used in libraries +dotnet_diagnostic.CA2259.severity = error # Ensure ThreadStatic is only used with static fields +dotnet_diagnostic.CA2260.severity = error # Implement generic math interfaces correctly +dotnet_diagnostic.CA2261.severity = error # Do not use ConfigureAwaitOptions.SuppressThrowing with Task dotnet_diagnostic.IDE0005.severity = warning # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/IDE0005.md +dotnet_diagnostic.IDE0010.severity = suggestion # Populate switch +dotnet_diagnostic.IDE0028.severity = suggestion # Collection initialization can be simplified +dotnet_diagnostic.IDE0021.severity = suggestion # Use expression body for constructor +dotnet_diagnostic.IDE0055.severity = none # Fix formatting dotnet_diagnostic.IDE0058.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/MicrosoftCodeAnalysis/IDE0058.md +dotnet_diagnostic.IDE0061.severity = suggestion # Use expression body for local function +dotnet_diagnostic.IDE0130.severity = suggestion # Namespace does not match folder structure +dotnet_diagnostic.IDE0290.severity = none # Use primary constructor +dotnet_diagnostic.IDE0301.severity = suggestion # Use collection expression for empty +dotnet_diagnostic.IDE0305.severity = suggestion # Collection initialization can be simplified # Microsoft - Compiler Errors @@ -484,6 +530,7 @@ dotnet_diagnostic.CS4014.severity = error # https://github.com/atc-net/ # StyleCop # https://github.com/DotNetAnalyzers/StyleCopAnalyzers dotnet_diagnostic.SA1009.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1009.md +dotnet_diagnostic.SA1010.severity = none # False positive when using collection initializers dotnet_diagnostic.SA1101.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1101.md dotnet_diagnostic.SA1122.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1122.md dotnet_diagnostic.SA1133.severity = error # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/StyleCop/SA1133.md @@ -505,6 +552,11 @@ dotnet_diagnostic.SA1649.severity = error # https://github.com/atc-net # SonarAnalyzer.CSharp # https://rules.sonarsource.com/csharp dotnet_diagnostic.S1135.severity = suggestion # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/SonarAnalyzerCSharp/S1135.md +dotnet_diagnostic.S2629.severity = none # Don't use string interpolation in logging message templates. +dotnet_diagnostic.S3358.severity = none # Extract this nested ternary operation into an independent statement. +dotnet_diagnostic.S6602.severity = none # "Find" method should be used instead of the "FirstOrDefault" +dotnet_diagnostic.S6603.severity = none # The collection-specific "TrueForAll" method should be used instead of the "All" +dotnet_diagnostic.S6605.severity = none # Collection-specific "Exists" method should be used instead of the "Any" ########################################## @@ -529,6 +581,7 @@ dotnet_diagnostic.IDE0305.severity = suggestion # Collection initialization dotnet_diagnostic.SA1010.severity = none # dotnet_diagnostic.CA1054.severity = none # URI parameters should not be strings +dotnet_diagnostic.CA1308.severity = none # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1308 dotnet_diagnostic.CA1515.severity = suggestion # Because an application's API isn't typically referenced from outside the assembly, types can be made internal (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515) dotnet_diagnostic.CA1848.severity = none # For improved performance, use the LoggerMessage delegates instead of calling 'LoggerExtensions.LogTrace(ILogger, string, params object[])' dotnet_diagnostic.CA1859.severity = none # diff --git a/.github/workflows/atc-coding-rules-update.yml b/.github/workflows/atc-coding-rules-update.yml index 89fe71d..80fbdc6 100644 --- a/.github/workflows/atc-coding-rules-update.yml +++ b/.github/workflows/atc-coding-rules-update.yml @@ -9,7 +9,7 @@ jobs: atc_check_for_updates: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: '0' - name: โš™ set environment stuff @@ -24,7 +24,7 @@ jobs: - name: 'โš™ Install ATC Coding Rules updater' run: dotnet tool install --global atc-coding-rules-updater - name: โ™ป๏ธ Run ATC Coding Rules updater - run: atc-coding-rules-updater run --projectPath . --verbose --projectTarget DotNet8 --useTemporarySuppressions + run: atc-coding-rules-updater run --projectPath . --verbose --projectTarget DotNet10 --useTemporarySuppressions - name: ๐Ÿ› Git - show changes shell: bash run: | @@ -35,7 +35,7 @@ jobs: - run: | dotnet build -c Release - name: ๐Ÿ’พCreate PR - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v7 with: commit-message: 'Pipeline ran atc-coding-rules-updater Build: ${{ env.GITHUB_RUN_ID}}' title: ATC Coding Rules Update diff --git a/.github/workflows/post-integration.yml b/.github/workflows/post-integration.yml index 9e897e2..7701f55 100644 --- a/.github/workflows/post-integration.yml +++ b/.github/workflows/post-integration.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: ๐Ÿ›’ Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.PAT_WORKFLOWS }} @@ -30,16 +30,10 @@ jobs: with: setAllVars: true - - name: โš™๏ธ Setup dotnet 8.0.x - uses: actions/setup-dotnet@v4 + - name: โš™๏ธ Setup dotnet 10.0.x + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.x' - - - name: โš™๏ธ Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: 17 - distribution: 'zulu' + dotnet-version: '10.0.x' - name: ๐Ÿงน Clean run: dotnet clean -c Release && dotnet nuget locals all --clear @@ -51,20 +45,7 @@ jobs: run: dotnet build -c Release --no-restore /p:UseSourceLink=true - name: ๐Ÿงช Run unit tests - run: dotnet test -c Release --no-build --filter "Category!=Integration" - - - name: ๐ŸŒฉ๏ธ SonarCloud install scanner - run: dotnet tool install --global dotnet-sonarscanner - - - name: ๐ŸŒฉ๏ธ SonarCloud analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - shell: pwsh - run: | - dotnet sonarscanner begin /k:"atc-coding-rules-updater" /o:"atc-net" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" - dotnet build -c Release /p:UseSourceLink=true --no-restore - dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" + run: dotnet test -c Release --no-build --filter-query "/[category!=integration]" --ignore-exit-code 8 - name: โฉ Merge to stable-branch run: | diff --git a/.github/workflows/pre-integration.yml b/.github/workflows/pre-integration.yml index 2536549..077223c 100644 --- a/.github/workflows/pre-integration.yml +++ b/.github/workflows/pre-integration.yml @@ -15,14 +15,14 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: ๐Ÿ›’ Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - - name: โš™๏ธ Setup dotnet 8.0.x - uses: actions/setup-dotnet@v4 + - name: โš™๏ธ Setup dotnet 10.0.x + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: ๐Ÿงน Clean run: dotnet clean -c Release && dotnet nuget locals all --clear @@ -39,14 +39,14 @@ jobs: - dotnet-build steps: - name: ๐Ÿ›’ Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - - name: โš™๏ธ Setup dotnet 8.0.x - uses: actions/setup-dotnet@v4 + - name: โš™๏ธ Setup dotnet 10.0.x + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: ๐Ÿ” Restore packages run: dotnet restore @@ -55,4 +55,4 @@ jobs: run: dotnet build -c Release --no-restore /p:UseSourceLink=true - name: ๐Ÿงช Run unit tests - run: dotnet test -c Release --no-build --filter "Category!=Integration" \ No newline at end of file + run: dotnet test -c Release --no-build --filter-query "/[category!=integration]" --ignore-exit-code 8 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b6fbb5..11166b0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: ๐Ÿ›’ Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.PAT_WORKFLOWS }} @@ -27,10 +27,10 @@ jobs: with: setAllVars: true - - name: โš™๏ธ Setup dotnet 8.0.x - uses: actions/setup-dotnet@v4 + - name: โš™๏ธ Setup dotnet 10.0.x + uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: ๐Ÿงน Clean run: dotnet clean -c Release && dotnet nuget locals all --clear diff --git a/Atc.CodingRules.Updater.CLI.sln b/Atc.CodingRules.Updater.CLI.sln deleted file mode 100644 index 302a73c..0000000 --- a/Atc.CodingRules.Updater.CLI.sln +++ /dev/null @@ -1,72 +0,0 @@ -๏ปฟ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.CodingRules.Updater.CLI", "src\Atc.CodingRules.Updater.CLI\Atc.CodingRules.Updater.CLI.csproj", "{40A9E694-220D-4FF9-8D35-9D993882B5ED}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{59C878BD-582E-46ED-986F-B06418DCA895}" - ProjectSection(SolutionItems) = preProject - README.md = README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.CodingRules.AnalyzerProviders", "src\Atc.CodingRules.AnalyzerProviders\Atc.CodingRules.AnalyzerProviders.csproj", "{E210CAD4-5593-4068-8FF2-F46780E667B0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.CodingRules.AnalyzerProviders.Tests", "test\Atc.CodingRules.AnalyzerProviders.Tests\Atc.CodingRules.AnalyzerProviders.Tests.csproj", "{53A87DE0-0F63-4505-967C-C130CAB165D7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.CodingRules", "src\Atc.CodingRules\Atc.CodingRules.csproj", "{85273D86-2477-41FA-A55A-10B912334C88}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.CodingRules.Updater", "src\Atc.CodingRules.Updater\Atc.CodingRules.Updater.csproj", "{5AF6DB00-AED6-44D6-8F60-7734D118C5FB}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E9C87546-6B4C-47EC-9BAC-038DB1EDF6CD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atc.CodingRules.Updater.Tests", "test\Atc.CodingRules.Updater.Tests\Atc.CodingRules.Updater.Tests.csproj", "{16FE991D-3145-45B1-B3B6-ACA039C97DE0}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Src", "Src", "{329E2FC9-29AE-4580-959F-B25F2E252B82}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {40A9E694-220D-4FF9-8D35-9D993882B5ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {40A9E694-220D-4FF9-8D35-9D993882B5ED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {40A9E694-220D-4FF9-8D35-9D993882B5ED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {40A9E694-220D-4FF9-8D35-9D993882B5ED}.Release|Any CPU.Build.0 = Release|Any CPU - {E210CAD4-5593-4068-8FF2-F46780E667B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E210CAD4-5593-4068-8FF2-F46780E667B0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E210CAD4-5593-4068-8FF2-F46780E667B0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E210CAD4-5593-4068-8FF2-F46780E667B0}.Release|Any CPU.Build.0 = Release|Any CPU - {53A87DE0-0F63-4505-967C-C130CAB165D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {53A87DE0-0F63-4505-967C-C130CAB165D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {53A87DE0-0F63-4505-967C-C130CAB165D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {53A87DE0-0F63-4505-967C-C130CAB165D7}.Release|Any CPU.Build.0 = Release|Any CPU - {85273D86-2477-41FA-A55A-10B912334C88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {85273D86-2477-41FA-A55A-10B912334C88}.Debug|Any CPU.Build.0 = Debug|Any CPU - {85273D86-2477-41FA-A55A-10B912334C88}.Release|Any CPU.ActiveCfg = Release|Any CPU - {85273D86-2477-41FA-A55A-10B912334C88}.Release|Any CPU.Build.0 = Release|Any CPU - {5AF6DB00-AED6-44D6-8F60-7734D118C5FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5AF6DB00-AED6-44D6-8F60-7734D118C5FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5AF6DB00-AED6-44D6-8F60-7734D118C5FB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5AF6DB00-AED6-44D6-8F60-7734D118C5FB}.Release|Any CPU.Build.0 = Release|Any CPU - {16FE991D-3145-45B1-B3B6-ACA039C97DE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {16FE991D-3145-45B1-B3B6-ACA039C97DE0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {16FE991D-3145-45B1-B3B6-ACA039C97DE0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {16FE991D-3145-45B1-B3B6-ACA039C97DE0}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {40A9E694-220D-4FF9-8D35-9D993882B5ED} = {329E2FC9-29AE-4580-959F-B25F2E252B82} - {E210CAD4-5593-4068-8FF2-F46780E667B0} = {329E2FC9-29AE-4580-959F-B25F2E252B82} - {53A87DE0-0F63-4505-967C-C130CAB165D7} = {E9C87546-6B4C-47EC-9BAC-038DB1EDF6CD} - {85273D86-2477-41FA-A55A-10B912334C88} = {329E2FC9-29AE-4580-959F-B25F2E252B82} - {5AF6DB00-AED6-44D6-8F60-7734D118C5FB} = {329E2FC9-29AE-4580-959F-B25F2E252B82} - {16FE991D-3145-45B1-B3B6-ACA039C97DE0} = {E9C87546-6B4C-47EC-9BAC-038DB1EDF6CD} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {A24C1323-4C10-4EED-B1FC-19D04C3B1C08} - EndGlobalSection -EndGlobal diff --git a/Atc.CodingRules.Updater.CLI.sln.DotSettings b/Atc.CodingRules.Updater.CLI.sln.DotSettings deleted file mode 100644 index 64feff7..0000000 --- a/Atc.CodingRules.Updater.CLI.sln.DotSettings +++ /dev/null @@ -1,8 +0,0 @@ -๏ปฟ - True - True - True - True - True - True - True \ No newline at end of file diff --git a/Atc.CodingRules.Updater.CLI.slnx b/Atc.CodingRules.Updater.CLI.slnx new file mode 100644 index 0000000..b08f9e0 --- /dev/null +++ b/Atc.CodingRules.Updater.CLI.slnx @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/Directory.Build.props b/Directory.Build.props index 335b544..7ec7c42 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,9 +16,9 @@ enable - 12.0 + 14.0 enable - net8.0 + net10.0 true 1573,1591,1712,CA1014,NU1903 @@ -41,12 +41,13 @@ + - + - + \ No newline at end of file diff --git a/atc-coding-rules-updater.json b/atc-coding-rules-updater.json index c312552..47eb5c4 100644 --- a/atc-coding-rules-updater.json +++ b/atc-coding-rules-updater.json @@ -1,5 +1,5 @@ { - "projectTarget": "DotNet8", + "projectTarget": "DotNet10", "mappings": { "src": { "paths": [ "src" ] }, "test": { "paths": [ "test" ] } diff --git a/global.json b/global.json index 86298f9..7d7ff8a 100644 --- a/global.json +++ b/global.json @@ -2,5 +2,8 @@ "sdk": { "rollForward": "latestMajor", "allowPrerelease": false + }, + "test": { + "runner": "Microsoft.Testing.Platform" } } \ No newline at end of file diff --git a/sample/atc-coding-rules-updater.json b/sample/atc-coding-rules-updater.json index c0b63a2..246d96b 100644 --- a/sample/atc-coding-rules-updater.json +++ b/sample/atc-coding-rules-updater.json @@ -1,5 +1,5 @@ { - "projectTarget": "DotNet8", + "projectTarget": "DotNet10", "useLatestMinorNugetVersion": true, "useTemporarySuppressions": false, "temporarySuppressionAsExcel": false, diff --git a/src/.editorconfig b/src/.editorconfig index 7ab8fbb..f06eefd 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -12,6 +12,7 @@ # AsyncFixer # http://www.asyncfixer.com +dotnet_diagnostic.AsyncFixer02.severity = none # False positive on synchronous HtmlAgilityPack SelectNodes().ToList() # Asyncify diff --git a/src/Atc.CodingRules.AnalyzerProviders/AnalyzerProviderBaseRulesHelper.cs b/src/Atc.CodingRules.AnalyzerProviders/AnalyzerProviderBaseRulesHelper.cs index 9c47e8b..df6be66 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/AnalyzerProviderBaseRulesHelper.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/AnalyzerProviderBaseRulesHelper.cs @@ -22,8 +22,7 @@ public static async Task> GetAnalyzerPr return analyzerProviderBaseRules; } - public static void CleanupCache( - ILogger logger) + public static void CleanupCache(ILogger logger) { var analyzerProviders = new AnalyzerProviderCollector(logger); analyzerProviders.CacheCleanup(); diff --git a/src/Atc.CodingRules.AnalyzerProviders/Atc.CodingRules.AnalyzerProviders.csproj b/src/Atc.CodingRules.AnalyzerProviders/Atc.CodingRules.AnalyzerProviders.csproj index 579f0d4..2afaea1 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Atc.CodingRules.AnalyzerProviders.csproj +++ b/src/Atc.CodingRules.AnalyzerProviders/Atc.CodingRules.AnalyzerProviders.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 @@ -9,7 +9,7 @@ - + diff --git a/src/Atc.CodingRules.AnalyzerProviders/GlobalUsings.cs b/src/Atc.CodingRules.AnalyzerProviders/GlobalUsings.cs index 3ca043b..31703cb 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/GlobalUsings.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/GlobalUsings.cs @@ -5,6 +5,7 @@ global using System.Text.Encodings.Web; global using System.Text.Json; global using System.Text.Json.Serialization; +global using System.Text.RegularExpressions; global using System.Xml; global using Atc.CodingRules.AnalyzerProviders.Models; diff --git a/src/Atc.CodingRules.AnalyzerProviders/Models/AnalyzerProviderBaseRuleData.cs b/src/Atc.CodingRules.AnalyzerProviders/Models/AnalyzerProviderBaseRuleData.cs index 15072bf..30f6feb 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Models/AnalyzerProviderBaseRuleData.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Models/AnalyzerProviderBaseRuleData.cs @@ -18,5 +18,6 @@ public AnalyzerProviderBaseRuleData(string name) public string? ExceptionMessage { get; set; } - public override string ToString() => $"{nameof(Name)}: {Name}, {nameof(Rules)}.Count: {Rules.Count}"; + public override string ToString() + => $"{nameof(Name)}: {Name}, {nameof(Rules)}.Count: {Rules.Count}"; } \ No newline at end of file diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/AnalyzerProviderBase.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/AnalyzerProviderBase.cs index 68b89e9..538691e 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/AnalyzerProviderBase.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/AnalyzerProviderBase.cs @@ -102,8 +102,7 @@ public void Cleanup() return JsonSerializer.Deserialize(fileAsJson, AnalyzerProviderSerialization.JsonOptions); } - protected static Task WriteToTempFolder( - AnalyzerProviderBaseRuleData data) + protected static Task WriteToTempFolder(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/AsyncFixerProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/AsyncFixerProvider.cs index 62b14fe..ebd26ce 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/AsyncFixerProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/AsyncFixerProvider.cs @@ -19,8 +19,7 @@ protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); [SuppressMessage("Design", "MA0051:Method is too long", Justification = "OK.")] - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); @@ -40,12 +39,12 @@ protected override async Task ReCollect( continue; } - if (!tocItem.ContainsKey("text")) + if (!tocItem.TryGetValue("text", out var value)) { continue; } - var sa = tocItem["text"] + var sa = value .ToString()! .Split(':', StringSplitOptions.RemoveEmptyEntries); @@ -64,7 +63,9 @@ protected override async Task ReCollect( } } - var headers3 = htmlDoc.DocumentNode.SelectNodes("//h3").ToList(); + var headers3 = htmlDoc.DocumentNode + .SelectNodes("//h3") + .ToList(); foreach (var item in headers3) { @@ -83,8 +84,12 @@ protected override async Task ReCollect( var code = sa[0]; var title = sa[1].Trim(); - var hashTagId = - $"user-content-{code.ToLower(GlobalizationConstants.EnglishCultureInfo)}{title.ToLower(GlobalizationConstants.EnglishCultureInfo).Replace(" ", "-", StringComparison.Ordinal).Replace("/", string.Empty, StringComparison.Ordinal).Replace(".", string.Empty, StringComparison.Ordinal)}"; + var hashTagId = $"user-content-{code.ToLower(GlobalizationConstants.EnglishCultureInfo)}{title + .ToLower(GlobalizationConstants.EnglishCultureInfo) + .Replace(" ", "-", StringComparison.Ordinal) + .Replace("/", string.Empty, StringComparison.Ordinal) + .Replace(".", string.Empty, StringComparison.Ordinal)}"; + var link = $"{DocumentationLink.OriginalString}#{hashTagId}"; data.Rules.Add( diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/AsyncifyProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/AsyncifyProvider.cs index 9185e0c..d78d296 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/AsyncifyProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/AsyncifyProvider.cs @@ -21,13 +21,14 @@ public AsyncifyProvider( protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(GitRawAnalyzerProviderBaseRulesBasePath).ConfigureAwait(false); + var htmlDoc = await web + .LoadFromWebAsync(GitRawAnalyzerProviderBaseRulesBasePath) + .ConfigureAwait(false); var xml = new XmlDocument(); xml.LoadXml(htmlDoc.Text); @@ -75,7 +76,10 @@ private static void ExtractCodesAndTitlesAndDescriptionsFromDataNodes( if (nameAttribute.Value.EndsWith("Title", StringComparison.Ordinal)) { code = code.Replace("Title", string.Empty, StringComparison.Ordinal); - var title = code.Replace("Asyncify", string.Empty, StringComparison.Ordinal).NormalizePascalCase(); + var title = code + .Replace("Asyncify", string.Empty, StringComparison.Ordinal) + .NormalizePascalCase(); + titles.Add(Tuple.Create(code, title)); } else if (nameAttribute.Value.EndsWith("Description", StringComparison.Ordinal)) @@ -85,7 +89,10 @@ private static void ExtractCodesAndTitlesAndDescriptionsFromDataNodes( else if (nameAttribute.Value.EndsWith("MessageFormat", StringComparison.Ordinal)) { code = code.Replace("MessageFormat", string.Empty, StringComparison.Ordinal); - var description = xmlElement.InnerText.Replace("\n", string.Empty, StringComparison.Ordinal).Trim(); + var description = xmlElement.InnerText + .Replace("\n", string.Empty, StringComparison.Ordinal) + .Trim(); + descriptions.Add(Tuple.Create(code, description)); } diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/MeziantouProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/MeziantouProvider.cs index 3f7b30c..80e266b 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/MeziantouProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/MeziantouProvider.cs @@ -22,13 +22,14 @@ public MeziantouProvider( protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false); + var htmlDoc = await web + .LoadFromWebAsync(DocumentationLink!.AbsoluteUri) + .ConfigureAwait(false); var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']"); if (embeddedNode is not null) @@ -39,8 +40,11 @@ protected override async Task ReCollect( htmlDoc.LoadHtml(html); } - var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First(); - var articleTableRows = articleNode.SelectNodes("//*//table[1]//tr").ToList(); + var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']")[0]; + + var articleTableRows = articleNode + .SelectNodes("//*//table[1]//tr") + .ToList(); foreach (var row in articleTableRows) { @@ -49,7 +53,10 @@ protected override async Task ReCollect( continue; } - var cells = row.SelectNodes("td").ToList(); + var cells = row + .SelectNodes("td") + .ToList(); + if (cells.Count <= 0) { continue; diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftCodeAnalysisNetAnalyzersProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftCodeAnalysisNetAnalyzersProvider.cs index bf57cf0..23406a5 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftCodeAnalysisNetAnalyzersProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftCodeAnalysisNetAnalyzersProvider.cs @@ -21,20 +21,25 @@ public MicrosoftCodeAnalysisNetAnalyzersProvider( protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + [SuppressMessage("Design", "MA0051:Method is too long", Justification = "OK.")] + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false); + var htmlDoc = await web + .LoadFromWebAsync(DocumentationLink!.AbsoluteUri) + .ConfigureAwait(false); + if (htmlDoc.DocumentNode.HasTitleWithAccessDenied()) { data.ExceptionMessage = "Access Denied"; return; } - var tableRows = htmlDoc.DocumentNode.SelectNodes("//*//table[1]//tr").ToList(); + var tableRows = htmlDoc.DocumentNode + .SelectNodes("//*//table[1]//tr") + .ToList(); foreach (var row in tableRows) { @@ -43,7 +48,10 @@ protected override async Task ReCollect( continue; } - var cells = row.SelectNodes("td").ToList(); + var cells = row + .SelectNodes("td") + .ToList(); + if (cells.Count <= 0) { continue; diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftCompilerErrorsProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftCompilerErrorsProvider.cs index cbcbede..a1d2894 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftCompilerErrorsProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftCompilerErrorsProvider.cs @@ -13,104 +13,138 @@ public MicrosoftCompilerErrorsProvider( public static string Name => "Microsoft.CompilerErrors"; - public override Uri? DocumentationLink { get; set; } = new("https://learn.microsoft.com/en-us/dotnet/csharp/language-reference", UriKind.Absolute); + public override Uri? DocumentationLink { get; set; } = new("https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/", UriKind.Absolute); protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); - var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri + "/toc.json").ConfigureAwait(false); - if (htmlDoc.DocumentNode.HasTitleWithAccessDenied()) + var categoryPages = new[] + { + "compiler-messages/preprocessor-errors", + "compiler-messages/attribute-usage-errors", + "compiler-messages/feature-version-errors", + "compiler-messages/assembly-references", + "compiler-messages/constructor-errors", + "compiler-messages/overloaded-operator-errors", + "compiler-messages/parameter-argument-mismatch", + "compiler-messages/generic-type-parameters-errors", + "compiler-messages/async-await-errors", + "compiler-messages/interface-implementation-errors", + "compiler-messages/ref-modifiers-errors", + "compiler-messages/ref-safety-errors", + "compiler-messages/ref-struct-errors", + "compiler-messages/iterator-yield", + "compiler-messages/extension-declarations", + "compiler-messages/partial-declarations", + "compiler-messages/params-arrays", + "compiler-messages/nullable-warnings", + "compiler-messages/pattern-matching-warnings", + "compiler-messages/string-literal", + "compiler-messages/array-declaration-errors", + "compiler-messages/inline-array-errors", + "compiler-messages/lambda-expression-errors", + "compiler-messages/overload-resolution", + "compiler-messages/expression-tree-restrictions", + "compiler-messages/using-directive-errors", + "compiler-messages/using-statement-declaration-errors", + "compiler-messages/source-generator-errors", + "compiler-messages/static-abstract-interfaces", + "compiler-messages/lock-semantics", + "compiler-messages/dynamic-type-and-binding-errors", + "compiler-messages/unsafe-code-errors", + "compiler-messages/warning-waves", + }; + + foreach (var categoryPath in categoryPages) { - data.ExceptionMessage = "Access Denied"; - return; + var categoryUri = new Uri(DocumentationLink!, categoryPath); + var rules = await GetRulesFromCategoryPage(categoryUri.AbsoluteUri); + foreach (var rule in rules) + { + data.Rules.Add(rule); + } } + } - var jsonDoc = JsonDocument.Parse(htmlDoc.DocumentNode.InnerText); - var jsonDocItems = jsonDoc.RootElement.GetProperty("items").EnumerateArray(); + [SuppressMessage("Design", "MA0051:Method is too long", Justification = "OK.")] + [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK - graceful degradation for individual category pages.")] + private static async Task> GetRulesFromCategoryPage( + string categoryUrl) + { + var rules = new List(); + var web = new HtmlWeb(); - while (jsonDocItems.MoveNext()) + try { - var jsonElement = jsonDocItems.Current; + var htmlDoc = await web + .LoadFromWebAsync(categoryUrl) + .ConfigureAwait(false); - if (!jsonElement.GetRawText().Contains("children", StringComparison.Ordinal)) + if (htmlDoc.DocumentNode.HasTitleWithAccessDenied()) { - continue; + return rules; } - var tocTitle = jsonElement.GetProperty("toc_title").ToString(); - if (!tocTitle.Equals("C# compiler messages", StringComparison.Ordinal)) + var mainNode = htmlDoc.DocumentNode.SelectSingleNode("//main[@id='main']"); + if (mainNode is null) { - continue; + return rules; } - var jsonChildItems = jsonElement.GetProperty("children").EnumerateArray(); - while (jsonChildItems.MoveNext()) + // Find all strong elements that contain CS error codes + var strongNodes = mainNode.SelectNodes(".//strong"); + if (strongNodes is null) { - var jsonChildElement = jsonChildItems.Current; - if (jsonChildElement.ValueKind != JsonValueKind.Object || - !jsonChildElement.TryGetProperty("children", out var jsonChildElement2)) + return rules; + } + + foreach (var strongNode in strongNodes) + { + var text = strongNode.InnerText.Trim(); + if (!text.StartsWith("CS", StringComparison.Ordinal)) { continue; } - foreach (var element in jsonChildElement2.EnumerateArray()) + // Extract just the CS code (e.g., "CS1024" from "CS1024:") + var codeTrimmed = text.TrimEnd(':'); + var code = codeTrimmed.Trim(); + if (code.Length is < 5 or > 7) { - var hrefPart = element.GetProperty("href").ToString(); - var code = element.GetProperty("toc_title").ToString(); - - var link = hrefPart.StartsWith("../misc/", StringComparison.Ordinal) - ? "https://docs.microsoft.com/en-us/dotnet/csharp/" + hrefPart.Replace("../", string.Empty, StringComparison.Ordinal) - : DocumentationLink.AbsoluteUri + "/" + hrefPart; - - var rule = await GetRuleByCode(code, link); - if (rule is not null) - { - data.Rules.Add(rule); - } + continue; } - } - } - } - private static async Task GetRuleByCode( - string code, - string link) - { - var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(link).ConfigureAwait(false); - if (htmlDoc.DocumentNode.HasTitleWithAccessDenied()) - { - return null; - } - - var mainNode = htmlDoc.DocumentNode.SelectSingleNode("//main[@id='main']"); - if (mainNode is null) - { - return null; - } + // Get the description from the following text + var description = string.Empty; + var nextSibling = strongNode.NextSibling; + if (nextSibling is not null) + { + var deEntitized = HtmlEntity.DeEntitize(nextSibling.InnerText); + var trimmed = deEntitized.Trim(); + var withoutColon = trimmed.TrimStart(':'); + description = withoutColon.Trim(); + } - var header = mainNode.SelectSingleNode(".//h1"); + var link = categoryUrl + "#" + code.ToLowerInvariant(); - var paragraphs = mainNode.SelectNodes(".//p").ToList(); - if (paragraphs.Count < 2) + rules.Add( + new Rule( + code, + code, + link, + category: null, + description)); + } + } + catch { - return null; + // Ignore errors for individual category pages } - var title = header.InnerText; - var description = paragraphs[1].InnerText; - - return new Rule( - code, - title, - link, - category: null, - description); + return rules; } } \ No newline at end of file diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftVisualStudioThreadingAnalyzersProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftVisualStudioThreadingAnalyzersProvider.cs index 819f8b7..d8acd88 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftVisualStudioThreadingAnalyzersProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftVisualStudioThreadingAnalyzersProvider.cs @@ -17,30 +17,34 @@ public MicrosoftVisualStudioThreadingAnalyzersProvider( public static string Name => "Microsoft.VisualStudio.Threading.Analyzers"; - public override Uri? DocumentationLink { get; set; } = new("https://github.com/microsoft/vs-threading/blob/main/doc/analyzers/index.md", UriKind.Absolute); + public override Uri? DocumentationLink { get; set; } = new("https://microsoft.github.io/vs-threading/analyzers/index.html", UriKind.Absolute); protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false); + var htmlDoc = await web + .LoadFromWebAsync(DocumentationLink!.AbsoluteUri) + .ConfigureAwait(false); - var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']"); - if (embeddedNode is not null) + var tableNode = htmlDoc.DocumentNode.SelectSingleNode("//table"); + if (tableNode is null) { - var dynamicJson = new DynamicJson(embeddedNode.InnerText); - var html = dynamicJson.GetValue("payload.blob.richText")?.ToString(); - - htmlDoc.LoadHtml(html); + return; } - var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First(); - var articleTableRows = articleNode.SelectNodes("//*//table[1]//tr").ToList(); + var articleTableRows = tableNode + .SelectNodes(".//tr")? + .ToList(); + + if (articleTableRows is null) + { + return; + } foreach (var row in articleTableRows) { @@ -49,7 +53,10 @@ protected override async Task ReCollect( continue; } - var cells = row.SelectNodes("td").ToList(); + var cells = row + .SelectNodes("td") + .ToList(); + if (cells.Count <= 0) { continue; @@ -63,7 +70,10 @@ protected override async Task ReCollect( var code = aHrefNode.InnerText; var title = HtmlEntity.DeEntitize(cells[TableColumnTitle].InnerText); - var link = "https://github.com/" + aHrefNode.Attributes["href"].Value; + var hrefValue = aHrefNode.Attributes["href"].Value; + var link = hrefValue.StartsWith("http", StringComparison.OrdinalIgnoreCase) + ? hrefValue + : $"https://microsoft.github.io/vs-threading/analyzers/{hrefValue}"; var category = cells[TableColumnCategory].InnerText; data.Rules.Add( diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/NSubstituteAnalyzersProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/NSubstituteAnalyzersProvider.cs index 6d92f1a..d0c877a 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/NSubstituteAnalyzersProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/NSubstituteAnalyzersProvider.cs @@ -22,13 +22,14 @@ public NSubstituteAnalyzersProvider( protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false); + var htmlDoc = await web + .LoadFromWebAsync(DocumentationLink!.AbsoluteUri) + .ConfigureAwait(false); var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']"); if (embeddedNode is not null) @@ -39,8 +40,10 @@ protected override async Task ReCollect( htmlDoc.LoadHtml(html); } - var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First(); - var articleTableRows = articleNode.SelectNodes("//*//table[1]//tr").ToList(); + var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']")[0]; + var articleTableRows = articleNode + .SelectNodes("//*//table[1]//tr") + .ToList(); foreach (var row in articleTableRows) { @@ -49,7 +52,10 @@ protected override async Task ReCollect( continue; } - var cells = row.SelectNodes("td").ToList(); + var cells = row + .SelectNodes("td") + .ToList(); + if (cells.Count <= 0) { continue; diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/SecurityCodeScanVs2019Provider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/SecurityCodeScanVs2019Provider.cs index 3e38aa8..e8ed625 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/SecurityCodeScanVs2019Provider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/SecurityCodeScanVs2019Provider.cs @@ -18,14 +18,18 @@ public SecurityCodeScanVs2019Provider( protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false); - var headers3 = htmlDoc.DocumentNode.SelectNodes("//h3").ToList(); + var htmlDoc = await web + .LoadFromWebAsync(DocumentationLink!.AbsoluteUri) + .ConfigureAwait(false); + + var headers3 = htmlDoc.DocumentNode + .SelectNodes("//h3") + .ToList(); foreach (var item in headers3.Select(x => x.InnerText)) { diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/SonarAnalyzerCSharpProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/SonarAnalyzerCSharpProvider.cs index cdf8446..6da190a 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/SonarAnalyzerCSharpProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/SonarAnalyzerCSharpProvider.cs @@ -20,13 +20,14 @@ public SonarAnalyzerCSharpProvider( protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false); + var htmlDoc = await web + .LoadFromWebAsync(DocumentationLink!.AbsoluteUri) + .ConfigureAwait(false); var dynamicJson = new DynamicJson(htmlDoc.DocumentNode.InnerText); if (dynamicJson.GetValue("result.data.allFile.nodes") is not List nodes) diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/StyleCopAnalyzersProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/StyleCopAnalyzersProvider.cs index 81f7553..088b1dc 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/StyleCopAnalyzersProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/StyleCopAnalyzersProvider.cs @@ -1,12 +1,18 @@ // ReSharper disable ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator namespace Atc.CodingRules.AnalyzerProviders.Providers; -public class StyleCopAnalyzersProvider : AnalyzerProviderBase +public partial class StyleCopAnalyzersProvider : AnalyzerProviderBase { private const int TableColumnId = 0; private const int TableColumnTitle = 1; private const int TableColumnDescription = 2; + [GeneratedRegex(@"\[(?[A-Z]+\d+[A-Z]*)\]\((?[^)]+)\)", RegexOptions.ExplicitCapture, matchTimeoutMilliseconds: 1000)] + private static partial Regex RuleIdRegex(); + + [GeneratedRegex(@"###\s+(?.+)", RegexOptions.ExplicitCapture, matchTimeoutMilliseconds: 1000)] + private static partial Regex CategoryRegex(); + public StyleCopAnalyzersProvider( ILogger logger, bool logWithAnsiConsoleMarkup = false) @@ -20,32 +26,34 @@ public StyleCopAnalyzersProvider( public override Uri? DocumentationLink { get; set; } = new("https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/DOCUMENTATION.md", UriKind.Absolute); + private static Uri RawContentBaseUri { get; } = new("https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/", UriKind.Absolute); + protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); - var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false); - - var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']"); - if (embeddedNode is not null) + var ruleFiles = new[] { - var dynamicJson = new DynamicJson(embeddedNode.InnerText); - var html = dynamicJson.GetValue("payload.blob.richText")?.ToString(); - - htmlDoc.LoadHtml(html); - } - - var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First(); - var articleRuleLinks = articleNode.SelectNodes("//*//strong//a").ToList(); - - foreach (var item in articleRuleLinks.Where(x => x.Attributes.Count == 1 && x.InnerText.Contains("(S", StringComparison.Ordinal))) + "documentation/SpecialRules.md", + "documentation/SpacingRules.md", + "documentation/ReadabilityRules.md", + "documentation/OrderingRules.md", + "documentation/NamingRules.md", + "documentation/MaintainabilityRules.md", + "documentation/LayoutRules.md", + "documentation/DocumentationRules.md", + "documentation/AlternativeRules.md", + }; + + using var httpClient = new HttpClient(); + foreach (var rulePath in ruleFiles) { - var rules = await GetRules(item); + var rules = await GetRulesFromMarkdown( + rulePath, + httpClient); foreach (var rule in rules) { data.Rules.Add(rule); @@ -53,55 +61,49 @@ protected override async Task ReCollect( } } - private static async Task> GetRules( - HtmlNode item) + [SuppressMessage("Design", "MA0051:Method is too long", Justification = "OK.")] + private static async Task> GetRulesFromMarkdown( + string rulePath, + HttpClient httpClient) { - var link = $"https://github.com{item.Attributes["href"].Value}"; - var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(link).ConfigureAwait(false); + var linkUri = new Uri(RawContentBaseUri, rulePath); + var markdown = await httpClient + .GetStringAsync(linkUri) + .ConfigureAwait(false); - var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']"); - if (embeddedNode is not null) - { - var dynamicJson = new DynamicJson(embeddedNode.InnerText); - var html = dynamicJson.GetValue("payload.blob.richText")?.ToString(); - - htmlDoc.LoadHtml(html); - } + var category = ExtractCategoryFromMarkdown(markdown); + var tableRows = ExtractTableRowsFromMarkdown(markdown); - var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First(); - var articleTableRows = articleNode.SelectNodes("//*//table[1]//tr").ToList(); - var category = articleNode.Descendants("h3").First().InnerText; var i = category.IndexOf(" Rules", StringComparison.Ordinal); if (i > 0) { - category = category.Substring(0, i); + category = category[..i]; } + var baseUrl = "/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/"; + var rules = new List(); - foreach (var row in articleTableRows) + foreach (var row in tableRows) { - if (row.SelectNodes("td") is null) - { - continue; - } - - var cells = row.SelectNodes("td").ToList(); - if (cells.Count <= 0) + var columns = row.Split('|', StringSplitOptions.TrimEntries); + if (columns.Length < 3) { continue; } - var aHrefNode = cells[TableColumnId].SelectSingleNode("a"); - if (aHrefNode is null) + var idColumn = columns[TableColumnId]; + var match = RuleIdRegex().Match(idColumn); + if (!match.Success) { continue; } - var code = aHrefNode.InnerText; - var title = HtmlEntity.DeEntitize(cells[TableColumnTitle].InnerText).NormalizePascalCase(); - var helpLink = $"https://github.com{aHrefNode.Attributes["href"].Value}"; - var description = cells[TableColumnDescription].InnerText; + var code = match.Groups["code"].Value; + var relativeLink = match.Groups["link"].Value; + var titleTrimmed = columns[TableColumnTitle].Trim(); + var title = titleTrimmed.NormalizePascalCase(); + var description = columns[TableColumnDescription].Trim(); + var helpLink = $"https://github.com{baseUrl}{relativeLink}"; rules.Add( new Rule( @@ -114,4 +116,37 @@ private static async Task> GetRules( return rules; } + + private static string ExtractCategoryFromMarkdown(string markdown) + { + var match = CategoryRegex().Match(markdown); + return match.Success ? match.Groups["category"].Value.Trim() : "Unknown"; + } + + private static List ExtractTableRowsFromMarkdown(string markdown) + { + var lines = markdown.Split('\n'); + var tableRows = new List(); + var inTable = false; + + foreach (var line in lines) + { + var trimmedLine = line.Trim(); + if (trimmedLine.Contains('|', StringComparison.Ordinal)) + { + inTable = true; + if (!trimmedLine.Contains("---", StringComparison.Ordinal) && + !trimmedLine.Contains("Identifier", StringComparison.OrdinalIgnoreCase)) + { + tableRows.Add(trimmedLine); + } + } + else if (inTable && !string.IsNullOrWhiteSpace(trimmedLine)) + { + break; + } + } + + return tableRows; + } } \ No newline at end of file diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/WpfAnalyzersProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/WpfAnalyzersProvider.cs index 32cbcf9..3c88b9c 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/WpfAnalyzersProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/WpfAnalyzersProvider.cs @@ -21,15 +21,19 @@ public WpfAnalyzersProvider( protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false); - var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First(); - var articleTableRows = articleNode.SelectNodes("//*//table[1]//tr").ToList(); + var htmlDoc = await web + .LoadFromWebAsync(DocumentationLink!.AbsoluteUri) + .ConfigureAwait(false); + + var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']")[0]; + var articleTableRows = articleNode + .SelectNodes("//*//table[1]//tr") + .ToList(); foreach (var row in articleTableRows) { @@ -38,7 +42,10 @@ protected override async Task ReCollect( continue; } - var cells = row.SelectNodes("td").ToList(); + var cells = row + .SelectNodes("td") + .ToList(); + if (cells.Count <= 0) { continue; diff --git a/src/Atc.CodingRules.AnalyzerProviders/Providers/XunitProvider.cs b/src/Atc.CodingRules.AnalyzerProviders/Providers/XunitProvider.cs index 586f0c3..e581846 100644 --- a/src/Atc.CodingRules.AnalyzerProviders/Providers/XunitProvider.cs +++ b/src/Atc.CodingRules.AnalyzerProviders/Providers/XunitProvider.cs @@ -2,8 +2,8 @@ namespace Atc.CodingRules.AnalyzerProviders.Providers; public class XunitProvider : AnalyzerProviderBase { - private const int TableThColumnId = 0; - private const int TableTdColumnTitle = 0; + private const int TableColumnId = 0; + private const int TableColumnTitle = 3; public XunitProvider( ILogger logger, @@ -21,47 +21,62 @@ public XunitProvider( protected override AnalyzerProviderBaseRuleData CreateData() => new(Name); - protected override async Task ReCollect( - AnalyzerProviderBaseRuleData data) + protected override async Task ReCollect(AnalyzerProviderBaseRuleData data) { ArgumentNullException.ThrowIfNull(data); var web = new HtmlWeb(); - var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false); - var articleNode = htmlDoc.DocumentNode.SelectNodes("//table[@class='table']").First(); - var articleTableRows = articleNode.SelectNodes("//*//tr").ToList(); + var htmlDoc = await web + .LoadFromWebAsync(DocumentationLink!.AbsoluteUri) + .ConfigureAwait(false); - foreach (var row in articleTableRows) + var tables = htmlDoc.DocumentNode.SelectNodes("//table"); + if (tables is null || tables.Count == 0) { - if (row.SelectNodes("th") is null || - row.SelectNodes("td") is null) - { - continue; - } + return; + } - var cellsTh = row.SelectNodes("th").ToList(); - var cellsTd = row.SelectNodes("td").ToList(); - if (cellsTh.Count <= 0 || cellsTd.Count <= 0) + var articleTableRows = new List(); + foreach (var table in tables) + { + var rows = table.SelectNodes(".//tr"); + if (rows is not null) { - continue; + articleTableRows.AddRange(rows); } + } - var aHrefNode = cellsTh[TableThColumnId].SelectSingleNode("a"); - if (aHrefNode is null) + foreach (var row in articleTableRows) + { + var rule = TryParseRuleFromRow(row); + if (rule is not null) { - continue; + data.Rules.Add(rule); } + } + } - var code = aHrefNode.InnerText.RemoveNewLines().Trim(); - var title = HtmlEntity.DeEntitize(cellsTd[TableTdColumnTitle].InnerText); - var link = $"{DocumentationLink}/{code}"; + private Rule? TryParseRuleFromRow(HtmlNode row) + { + var cells = row.SelectNodes("td"); + if (cells is null || cells.Count <= TableColumnTitle) + { + return null; + } - data.Rules.Add( - new Rule( - code, - title, - link, - category: null)); + var cellsList = cells.ToList(); + var aHrefNode = cellsList[TableColumnId].SelectSingleNode("a"); + if (aHrefNode is null) + { + return null; } + + var code = aHrefNode.InnerText + .RemoveNewLines() + .Trim(); + var title = HtmlEntity.DeEntitize(cellsList[TableColumnTitle].InnerText); + var link = $"{DocumentationLink}/{code}"; + + return new Rule(code, title, link, category: null); } } \ No newline at end of file diff --git a/src/Atc.CodingRules.AnalyzerRulesMetaData.Generator.CLI/Atc.CodingRules.AnalyzerRulesMetaData.Generator.CLI.csproj b/src/Atc.CodingRules.AnalyzerRulesMetaData.Generator.CLI/Atc.CodingRules.AnalyzerRulesMetaData.Generator.CLI.csproj index 613535c..0800e87 100644 --- a/src/Atc.CodingRules.AnalyzerRulesMetaData.Generator.CLI/Atc.CodingRules.AnalyzerRulesMetaData.Generator.CLI.csproj +++ b/src/Atc.CodingRules.AnalyzerRulesMetaData.Generator.CLI/Atc.CodingRules.AnalyzerRulesMetaData.Generator.CLI.csproj @@ -2,11 +2,11 @@ Exe - net8.0 + net10.0 - + diff --git a/src/Atc.CodingRules.Updater.CLI/.editorconfig b/src/Atc.CodingRules.Updater.CLI/.editorconfig new file mode 100644 index 0000000..a9ab144 --- /dev/null +++ b/src/Atc.CodingRules.Updater.CLI/.editorconfig @@ -0,0 +1,25 @@ +# ATC coding rules - https://github.com/atc-net/atc-coding-rules +# Version: 1.0.0 +# Updated: 11-04-2024 +# Location: cli +# Distribution: Frameworks + +########################################## +# Code Analyzers Rules +########################################## +[*.{cs}] + +dotnet_diagnostic.CA1031.severity = none # Do not catch general exception types +dotnet_diagnostic.CA1303.severity = none # Do not pass literals as localized parameters +dotnet_diagnostic.CA1819.severity = none # Properties should not return arrays +dotnet_diagnostic.CA1848.severity = none # Use the LoggerMessage delegates +dotnet_diagnostic.CA2000.severity = none # Dispose objects before losing scope +dotnet_diagnostic.CA2254.severity = none # Template should be a static expression + +dotnet_diagnostic.MA0076.severity = none # Do not use implicit culture-sensitive ToString in interpolated strings + +dotnet_diagnostic.S2629.severity = none # Don't use string interpolation in logging message templates. + +########################################## +# Custom - Code Analyzers Rules +########################################## \ No newline at end of file diff --git a/src/Atc.CodingRules.Updater.CLI/Atc.CodingRules.Updater.CLI.csproj b/src/Atc.CodingRules.Updater.CLI/Atc.CodingRules.Updater.CLI.csproj index b3e1dfb..c1939bb 100644 --- a/src/Atc.CodingRules.Updater.CLI/Atc.CodingRules.Updater.CLI.csproj +++ b/src/Atc.CodingRules.Updater.CLI/Atc.CodingRules.Updater.CLI.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 atc-coding-rules-updater coding-rules;rules A .NET Tool that can update a project with the latest atc-coding-rules. @@ -13,11 +13,11 @@ - - - + + + - + diff --git a/src/Atc.CodingRules.Updater.CLI/Commands/AnalyzerProvidersCacheCleanupCommand.cs b/src/Atc.CodingRules.Updater.CLI/Commands/AnalyzerProvidersCacheCleanupCommand.cs index 957f892..88080e9 100644 --- a/src/Atc.CodingRules.Updater.CLI/Commands/AnalyzerProvidersCacheCleanupCommand.cs +++ b/src/Atc.CodingRules.Updater.CLI/Commands/AnalyzerProvidersCacheCleanupCommand.cs @@ -1,13 +1,10 @@ namespace Atc.CodingRules.Updater.CLI.Commands; -[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")] -public class AnalyzerProvidersCacheCleanupCommand : Command +public class AnalyzerProvidersCacheCleanupCommand(ILogger logger) : Command { - private readonly ILogger logger; - - public AnalyzerProvidersCacheCleanupCommand(ILogger logger) => this.logger = logger; - - public override int Execute(CommandContext context) + public override int Execute( + CommandContext context, + CancellationToken cancellationToken) { ConsoleHelper.WriteHeader(); diff --git a/src/Atc.CodingRules.Updater.CLI/Commands/AnalyzerProvidersCollectCommand.cs b/src/Atc.CodingRules.Updater.CLI/Commands/AnalyzerProvidersCollectCommand.cs index d3cce82..a90e367 100644 --- a/src/Atc.CodingRules.Updater.CLI/Commands/AnalyzerProvidersCollectCommand.cs +++ b/src/Atc.CodingRules.Updater.CLI/Commands/AnalyzerProvidersCollectCommand.cs @@ -1,28 +1,26 @@ namespace Atc.CodingRules.Updater.CLI.Commands; -public class AnalyzerProvidersCollectCommand : AsyncCommand +public class AnalyzerProvidersCollectCommand(ILogger logger) + : AsyncCommand { - private readonly ILogger logger; - - public AnalyzerProvidersCollectCommand(ILogger logger) => this.logger = logger; - public override Task ExecuteAsync( CommandContext context, - AnalyzerProvidersCollectCommandSettings settings) + AnalyzerProvidersCollectCommandSettings settings, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(settings); - return ExecuteInternalAsync(settings); + return ExecuteInternalAsync(settings, cancellationToken); } - [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")] private async Task ExecuteInternalAsync( - AnalyzerProvidersCollectCommandSettings settings) + AnalyzerProvidersCollectCommandSettings settings, + CancellationToken cancellationToken) { ConsoleHelper.WriteHeader(); var projectPath = new DirectoryInfo(settings.ProjectPath); - var options = await GetOptionsFromFileAndUserArguments(settings, projectPath); + var options = await GetOptionsFromFileAndUserArguments(settings, projectPath, cancellationToken); try { @@ -44,10 +42,11 @@ await AnalyzerProviderBaseRulesHelper.GetAnalyzerProviderBaseRules( private static async Task GetOptionsFromFileAndUserArguments( AnalyzerProvidersCollectCommandSettings settings, - DirectoryInfo projectPath) + DirectoryInfo projectPath, + CancellationToken cancellationToken) { var optionsPath = settings.GetOptionsPath(); - var options = await OptionsHelper.CreateDefault(projectPath, optionsPath); + var options = await OptionsHelper.CreateDefault(projectPath, optionsPath, cancellationToken); var analyzerProviderCollectingMode = GetAnalyzerProviderCollectingMode(settings); if (analyzerProviderCollectingMode is not null) diff --git a/src/Atc.CodingRules.Updater.CLI/Commands/DescriptionAttributes/SupportedProjectTargetTypeDescriptionAttribute.cs b/src/Atc.CodingRules.Updater.CLI/Commands/DescriptionAttributes/SupportedProjectTargetTypeDescriptionAttribute.cs index 3a8764d..2311d10 100644 --- a/src/Atc.CodingRules.Updater.CLI/Commands/DescriptionAttributes/SupportedProjectTargetTypeDescriptionAttribute.cs +++ b/src/Atc.CodingRules.Updater.CLI/Commands/DescriptionAttributes/SupportedProjectTargetTypeDescriptionAttribute.cs @@ -8,7 +8,8 @@ public override string Description get { var defaultValue = new OptionsFile().ProjectTarget; - var values = Enum.GetNames() + var values = Enum + .GetNames() .Select(enumValue => enumValue.Equals(defaultValue.ToString(), StringComparison.Ordinal) ? $"{enumValue} (default)" : enumValue) diff --git a/src/Atc.CodingRules.Updater.CLI/Commands/OptionsFileCreateCommand.cs b/src/Atc.CodingRules.Updater.CLI/Commands/OptionsFileCreateCommand.cs index 3350b14..309e646 100644 --- a/src/Atc.CodingRules.Updater.CLI/Commands/OptionsFileCreateCommand.cs +++ b/src/Atc.CodingRules.Updater.CLI/Commands/OptionsFileCreateCommand.cs @@ -1,24 +1,20 @@ namespace Atc.CodingRules.Updater.CLI.Commands; -public class OptionsFileCreateCommand : AsyncCommand +public class OptionsFileCreateCommand(ILogger logger) + : AsyncCommand { - private readonly ILogger logger; - - public OptionsFileCreateCommand( - ILogger logger) - => this.logger = logger; - public override Task ExecuteAsync( CommandContext context, - ProjectCommandSettings settings) + ProjectCommandSettings settings, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(settings); - return ExecuteInternalAsync(settings); + return ExecuteInternalAsync(settings, cancellationToken); } - [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")] private async Task ExecuteInternalAsync( - ProjectCommandSettings settings) + ProjectCommandSettings settings, + CancellationToken cancellationToken) { ConsoleHelper.WriteHeader(); @@ -26,7 +22,7 @@ private async Task ExecuteInternalAsync( try { - var (isSuccessful, error) = await OptionsHelper.CreateOptionsFile(projectPath, settings); + var (isSuccessful, error) = await OptionsHelper.CreateOptionsFile(projectPath, settings, cancellationToken); if (isSuccessful) { logger.LogInformation("The options file is created"); diff --git a/src/Atc.CodingRules.Updater.CLI/Commands/OptionsFileValidateCommand.cs b/src/Atc.CodingRules.Updater.CLI/Commands/OptionsFileValidateCommand.cs index 20fccc5..94ca7a8 100644 --- a/src/Atc.CodingRules.Updater.CLI/Commands/OptionsFileValidateCommand.cs +++ b/src/Atc.CodingRules.Updater.CLI/Commands/OptionsFileValidateCommand.cs @@ -1,25 +1,21 @@ namespace Atc.CodingRules.Updater.CLI.Commands; -public class OptionsFileValidateCommand : AsyncCommand +public class OptionsFileValidateCommand(ILogger logger) + : AsyncCommand { - private readonly ILogger logger; - - public OptionsFileValidateCommand( - ILogger logger) - => this.logger = logger; - public override Task ExecuteAsync( CommandContext context, - ProjectBaseCommandSettings settings) + ProjectBaseCommandSettings settings, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(settings); - return ExecuteInternalAsync(settings); + return ExecuteInternalAsync(settings, cancellationToken); } - [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")] private async Task ExecuteInternalAsync( - ProjectBaseCommandSettings settings) + ProjectBaseCommandSettings settings, + CancellationToken cancellationToken) { ConsoleHelper.WriteHeader(); @@ -28,7 +24,7 @@ private async Task ExecuteInternalAsync( try { - var (isSuccessful, error) = await OptionsHelper.ValidateOptionsFile(projectPath, optionsPath); + var (isSuccessful, error) = await OptionsHelper.ValidateOptionsFile(projectPath, optionsPath, cancellationToken); if (isSuccessful) { logger.LogInformation("The options file is valid"); diff --git a/src/Atc.CodingRules.Updater.CLI/Commands/RootCommand.cs b/src/Atc.CodingRules.Updater.CLI/Commands/RootCommand.cs index 1ec00bf..b0a6c33 100644 --- a/src/Atc.CodingRules.Updater.CLI/Commands/RootCommand.cs +++ b/src/Atc.CodingRules.Updater.CLI/Commands/RootCommand.cs @@ -1,20 +1,21 @@ namespace Atc.CodingRules.Updater.CLI.Commands; -[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")] [SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "OK.")] public class RootCommand : AsyncCommand { public override Task ExecuteAsync( CommandContext context, - RootCommandSettings settings) + RootCommandSettings settings, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(settings); - return ExecuteInternalAsync(settings); + return ExecuteInternalAsync(settings, cancellationToken); } private static async Task ExecuteInternalAsync( - RootCommandSettings settings) + RootCommandSettings settings, + CancellationToken cancellationToken) { if (!NetworkInformationHelper.HasConnection()) { @@ -34,7 +35,7 @@ private static async Task ExecuteInternalAsync( } } - await Task.Delay(1); + await Task.Delay(1, cancellationToken); return ConsoleExitStatusCodes.Success; } diff --git a/src/Atc.CodingRules.Updater.CLI/Commands/RunCommand.cs b/src/Atc.CodingRules.Updater.CLI/Commands/RunCommand.cs index 3ca0fff..84d0108 100644 --- a/src/Atc.CodingRules.Updater.CLI/Commands/RunCommand.cs +++ b/src/Atc.CodingRules.Updater.CLI/Commands/RunCommand.cs @@ -2,26 +2,21 @@ namespace Atc.CodingRules.Updater.CLI.Commands; [SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "OK.")] -public class RunCommand : AsyncCommand +public class RunCommand(ILogger logger) : AsyncCommand { - private readonly ILogger logger; - - public RunCommand( - ILogger logger) - => this.logger = logger; - public override Task ExecuteAsync( CommandContext context, - RunCommandSettings settings) + RunCommandSettings settings, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(settings); - return ExecuteInternalAsync(settings); + return ExecuteInternalAsync(settings, cancellationToken); } - [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")] private async Task ExecuteInternalAsync( - RunCommandSettings settings) + RunCommandSettings settings, + CancellationToken cancellationToken) { if (!NetworkInformationHelper.HasHttpConnection()) { @@ -32,7 +27,7 @@ private async Task ExecuteInternalAsync( ConsoleHelper.WriteHeader(); var projectPath = new DirectoryInfo(settings.ProjectPath); - var options = await GetOptionsFromFileAndUserArguments(settings, projectPath); + var options = await GetOptionsFromFileAndUserArguments(settings, projectPath, cancellationToken); try { @@ -47,7 +42,7 @@ await ProjectHelper.HandleFiles( { var organizationName = settings.OrganizationName is not null && settings.OrganizationName.IsSet ? settings.OrganizationName.Value - : AnsiConsole.Ask("What is the [green]Organization name[/]?"); + : await AnsiConsole.AskAsync("What is the [green]Organization name[/]?", cancellationToken); DirectoryBuildPropsHelper.UpdateFileInsertPlaceholderElement(logger, projectPath, "OrganizationName", "insert organization name here", organizationName); } @@ -56,7 +51,7 @@ await ProjectHelper.HandleFiles( { var repositoryName = settings.RepositoryName is not null && settings.RepositoryName.IsSet ? settings.RepositoryName.Value - : AnsiConsole.Ask("What is the [green]Repository name[/]?"); + : await AnsiConsole.AskAsync("What is the [green]Repository name[/]?", cancellationToken); DirectoryBuildPropsHelper.UpdateFileInsertPlaceholderElement(logger, projectPath, "RepositoryName", "insert repository name here", repositoryName); } @@ -73,10 +68,11 @@ await ProjectHelper.HandleFiles( private static async Task GetOptionsFromFileAndUserArguments( RunCommandSettings settings, - DirectoryInfo projectPath) + DirectoryInfo projectPath, + CancellationToken cancellationToken) { var optionsPath = settings.GetOptionsPath(); - var options = await OptionsHelper.CreateDefault(projectPath, optionsPath); + var options = await OptionsHelper.CreateDefault(projectPath, optionsPath, cancellationToken); options.Mappings.ResolvePaths(projectPath); var projectTarget = ProjectCommandSettings.GetProjectTarget(settings); diff --git a/src/Atc.CodingRules.Updater.CLI/Commands/SanityCheckCommand.cs b/src/Atc.CodingRules.Updater.CLI/Commands/SanityCheckCommand.cs index 2125356..5c41c17 100644 --- a/src/Atc.CodingRules.Updater.CLI/Commands/SanityCheckCommand.cs +++ b/src/Atc.CodingRules.Updater.CLI/Commands/SanityCheckCommand.cs @@ -1,30 +1,25 @@ namespace Atc.CodingRules.Updater.CLI.Commands; -public class SanityCheckCommand : AsyncCommand +public class SanityCheckCommand(ILogger logger) : AsyncCommand { - private readonly ILogger logger; - - public SanityCheckCommand( - ILogger logger) - => this.logger = logger; - public override Task ExecuteAsync( CommandContext context, - ProjectCommandSettings settings) + ProjectCommandSettings settings, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(settings); - return ExecuteInternalAsync(settings); + return ExecuteInternalAsync(settings, cancellationToken); } - [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")] private async Task ExecuteInternalAsync( - ProjectCommandSettings settings) + ProjectCommandSettings settings, + CancellationToken cancellationToken) { ConsoleHelper.WriteHeader(); var projectPath = new DirectoryInfo(settings.ProjectPath); - var options = await GetOptionsFromFileAndUserArguments(settings, projectPath); + var options = await GetOptionsFromFileAndUserArguments(settings, projectPath, cancellationToken); try { @@ -42,10 +37,11 @@ private async Task ExecuteInternalAsync( private static async Task GetOptionsFromFileAndUserArguments( ProjectCommandSettings settings, - DirectoryInfo projectPath) + DirectoryInfo projectPath, + CancellationToken cancellationToken) { var optionsPath = settings.GetOptionsPath(); - var options = await OptionsHelper.CreateDefault(projectPath, optionsPath); + var options = await OptionsHelper.CreateDefault(projectPath, optionsPath, cancellationToken); options.Mappings.ResolvePaths(projectPath); var projectTarget = ProjectCommandSettings.GetProjectTarget(settings); diff --git a/src/Atc.CodingRules.Updater.CLI/Extensions/CommandAppExtensions.cs b/src/Atc.CodingRules.Updater.CLI/Extensions/CommandAppExtensions.cs index 3421d08..03dd07f 100644 --- a/src/Atc.CodingRules.Updater.CLI/Extensions/CommandAppExtensions.cs +++ b/src/Atc.CodingRules.Updater.CLI/Extensions/CommandAppExtensions.cs @@ -19,7 +19,8 @@ public static void ConfigureCommands(this CommandApp app) } private static void ConfigureRunCommand(IConfigurator config) - => config.AddCommand(NameCommandConstants.Run) + => config + .AddCommand(NameCommandConstants.Run) .WithDescription("Update the project folder with ATC coding rules and configurations") .WithExample([".", CreateEquivalentToRun(8)]) .WithExample([NameCommandConstants.Run, ".", CreateEquivalentToRun(4)]) @@ -37,7 +38,8 @@ private static void ConfigureRunCommand(IConfigurator config) ]); private static void ConfigureSanityCheckCommand(IConfigurator config) - => config.AddCommand(NameCommandConstants.SanityCheck) + => config + .AddCommand(NameCommandConstants.SanityCheck) .WithDescription("Sanity check the project files") .WithExample([NameCommandConstants.SanityCheck, ".", CreateEquivalentToSanityCheck(8)]) .WithExample([NameCommandConstants.SanityCheck, CreateArgumentProjectPathWithTestFolder()]) @@ -102,10 +104,12 @@ private static string CreateArgumentProjectPathWithCurrentFolder() private static string CreateArgumentProjectPathWithTestFolder() => @$"{ArgumentCommandConstants.ShortProjectPath} c:\temp\MyProject"; - private static string CreateArgumentProjectTarget(SupportedProjectTargetType targetType) + private static string CreateArgumentProjectTarget( + SupportedProjectTargetType targetType) => @$"{ArgumentCommandConstants.ShortProjectTarget} {targetType}"; - private static string CreateArgumentFetchMode(ProviderCollectingMode collectingMode) + private static string CreateArgumentFetchMode( + ProviderCollectingMode collectingMode) => @$"{ArgumentCommandConstants.LongFetchMode} {collectingMode}"; private static string CreateArgumentCommandsAnalyzerProvidersWithCollect() @@ -129,11 +133,16 @@ private static string CreateEquivalentToSanityCheck(int indentSpaces) private static string CreateEquivalentToOptionsFileCreate(int indentSpaces) => PrefixSpaces(indentSpaces, $"(equivalent to '{CreateArgumentCommandsOptionsFileWithCreate()} {CreateArgumentProjectPathWithCurrentFolder()}')"); - private static string CreateEquivalentToOptionsFileValidate(int indentSpaces) + private static string CreateEquivalentToOptionsFileValidate( + int indentSpaces) => PrefixSpaces(indentSpaces, $"(equivalent to '{CreateArgumentCommandsOptionsFileWithValidate()} {CreateArgumentProjectPathWithCurrentFolder()}')"); - private static string CreateEquivalentToAnalyzerProvidersCollect(int indentSpaces) + private static string CreateEquivalentToAnalyzerProvidersCollect( + int indentSpaces) => PrefixSpaces(indentSpaces, $"(equivalent to '{CreateArgumentCommandsAnalyzerProvidersWithCollect()} {CreateArgumentProjectPathWithCurrentFolder()}')"); - private static string PrefixSpaces(int indentSpaces, string value) => value.PadLeft(value.Length + indentSpaces); + private static string PrefixSpaces( + int indentSpaces, + string value) + => value.PadLeft(value.Length + indentSpaces); } \ No newline at end of file diff --git a/src/Atc.CodingRules.Updater.CLI/Models/Options/OptionsFile.cs b/src/Atc.CodingRules.Updater.CLI/Models/Options/OptionsFile.cs index fa3c7b4..1512ac9 100644 --- a/src/Atc.CodingRules.Updater.CLI/Models/Options/OptionsFile.cs +++ b/src/Atc.CodingRules.Updater.CLI/Models/Options/OptionsFile.cs @@ -2,7 +2,7 @@ namespace Atc.CodingRules.Updater.CLI.Models.Options; public class OptionsFile { - public SupportedProjectTargetType ProjectTarget { get; set; } = SupportedProjectTargetType.DotNet8; + public SupportedProjectTargetType ProjectTarget { get; set; } = SupportedProjectTargetType.DotNet10; public bool UseLatestMinorNugetVersion { get; set; } = true; diff --git a/src/Atc.CodingRules.Updater.CLI/Models/Options/OptionsMappings.cs b/src/Atc.CodingRules.Updater.CLI/Models/Options/OptionsMappings.cs index e5f9147..c74871e 100644 --- a/src/Atc.CodingRules.Updater.CLI/Models/Options/OptionsMappings.cs +++ b/src/Atc.CodingRules.Updater.CLI/Models/Options/OptionsMappings.cs @@ -15,8 +15,7 @@ public bool HasMappingsPaths() => Src.Paths.Count > 0 || Test.Paths.Count > 0; - public void ResolvePaths( - DirectoryInfo projectPath) + public void ResolvePaths(DirectoryInfo projectPath) { ArgumentNullException.ThrowIfNull(projectPath); @@ -70,7 +69,10 @@ private static bool TryResolvePathIfNeeded( if (orgPath.StartsWith("./", StringComparison.Ordinal)) { - var s = orgPath.Substring(2).Replace("/", "\\", StringComparison.Ordinal); + var s = orgPath + .Substring(2) + .Replace("/", "\\", StringComparison.Ordinal); + newPath = Path.Combine(projectPath.FullName, s); return true; } @@ -85,7 +87,10 @@ private static bool TryResolvePathIfNeeded( if (orgPath.StartsWith('\\')) { - var s = orgPath.Substring(1).Replace("/", "\\", StringComparison.Ordinal); + var s = orgPath + .Substring(1) + .Replace("/", "\\", StringComparison.Ordinal); + newPath = Path.Combine(projectPath.FullName, s); return true; } diff --git a/src/Atc.CodingRules.Updater.CLI/OptionsHelper.cs b/src/Atc.CodingRules.Updater.CLI/OptionsHelper.cs index 3a82f27..5278919 100644 --- a/src/Atc.CodingRules.Updater.CLI/OptionsHelper.cs +++ b/src/Atc.CodingRules.Updater.CLI/OptionsHelper.cs @@ -5,7 +5,8 @@ public static class OptionsHelper { public static async Task CreateDefault( DirectoryInfo projectPath, - string? settingsOptionsPath) + string? settingsOptionsPath, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(projectPath); @@ -16,7 +17,7 @@ public static async Task CreateDefault( } var optionsPath = GetOptionsPath(projectPath, settingsOptionsPath); - var options = await FileHelper.ReadJsonFileAndDeserializeAsync(fileInfo); + var options = await FileHelper.ReadJsonFileToModelAsync(fileInfo, cancellationToken); if (options is null) { return CreateDefaultOptions(projectPath); @@ -32,7 +33,8 @@ public static async Task CreateDefault( public static async Task<(bool IsSuccessful, string Error)> CreateOptionsFile( DirectoryInfo projectPath, - ProjectCommandSettings settings) + ProjectCommandSettings settings, + CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(projectPath); ArgumentNullException.ThrowIfNull(settings); @@ -49,13 +51,14 @@ public static async Task CreateDefault( options.ProjectTarget = settings.ProjectTarget.Value; } - await FileHelper.WriteModelToJsonFileAsync(fileInfo, options); + await FileHelper.WriteModelToJsonFileAsync(fileInfo, options, cancellationToken); return (true, string.Empty); } public static async Task<(bool IsSuccessful, string Error)> ValidateOptionsFile( DirectoryInfo projectPath, - string? settingsOptionsPath) + string? settingsOptionsPath, + CancellationToken cancellationToken) { var fileInfo = GetOptionsFile(projectPath, settingsOptionsPath); if (!fileInfo.Exists) @@ -63,14 +66,13 @@ public static async Task CreateDefault( return (false, "File does not exist"); } - var options = await FileHelper.ReadJsonFileAndDeserializeAsync(fileInfo); + var options = await FileHelper.ReadJsonFileToModelAsync(fileInfo, cancellationToken); return options is null ? (false, "File is invalid") : (true, string.Empty); } - private static OptionsFile CreateDefaultOptions( - DirectoryInfo projectPath) + private static OptionsFile CreateDefaultOptions(DirectoryInfo projectPath) { var options = new OptionsFile(); var directories = projectPath.GetDirectories(); diff --git a/src/Atc.CodingRules.Updater.CLI/Program.cs b/src/Atc.CodingRules.Updater.CLI/Program.cs index b6cab2e..288b1d0 100644 --- a/src/Atc.CodingRules.Updater.CLI/Program.cs +++ b/src/Atc.CodingRules.Updater.CLI/Program.cs @@ -18,7 +18,9 @@ public static Task Main(string[] args) .Build(); var consoleLoggerConfiguration = new ConsoleLoggerConfiguration(); - configuration.GetRequiredSection("ConsoleLogger").Bind(consoleLoggerConfiguration); + configuration + .GetRequiredSection("ConsoleLogger") + .Bind(consoleLoggerConfiguration); ProgramCsHelper.SetMinimumLogLevelIfNeeded(args, consoleLoggerConfiguration); @@ -74,8 +76,7 @@ private static string[] SetProjectPathFromDotArgumentIfNeeded(string[] args) return [.. newArgs]; } - private static string[] SetHelpArgumentIfNeeded( - string[] args) + private static string[] SetHelpArgumentIfNeeded(string[] args) { if (args.Length == 0) { diff --git a/src/Atc.CodingRules.Updater.CLI/ProjectHelper.cs b/src/Atc.CodingRules.Updater.CLI/ProjectHelper.cs index 6c27e9e..79b28e6 100644 --- a/src/Atc.CodingRules.Updater.CLI/ProjectHelper.cs +++ b/src/Atc.CodingRules.Updater.CLI/ProjectHelper.cs @@ -143,6 +143,7 @@ private static ProjectFrameworkType DetermineProjectFrameworkType( projectFrameworkType = optionsProjectFrameworkMapping?.Type ?? projectType switch { + DotnetProjectType.AspireAppHost or DotnetProjectType.AspireServiceDefaults => ProjectFrameworkType.Aspire, DotnetProjectType.AzureFunctionApp => ProjectFrameworkType.AzureFunctions, DotnetProjectType.BlazorServerApp or DotnetProjectType.BlazorWAsmApp => ProjectFrameworkType.Blazor, DotnetProjectType.CliApp => ProjectFrameworkType.Cli, @@ -423,6 +424,7 @@ private static async Task CreateSuppressionsFileInTempPath( await Helpers.FileHelper.WriteAllTextAsync(new FileInfo(temporarySuppressionsFile), suppressionsText); } + [SuppressMessage("Design", "MA0051:Method is too long", Justification = "OK.")] private static int AddSuppressionLinesToWorksheet( ExcelWorksheet worksheet, IEnumerable>> suppressionLinesPrAnalyzer, @@ -462,8 +464,13 @@ private static int AddSuppressionLinesToWorksheet( var indexOfHttp = afterOccurrence.LastIndexOf("- http", StringComparison.Ordinal); if (indexOfHttp != -1) { - message = afterOccurrence.Substring(2, indexOfHttp - 2).Trim(); - helpLink = afterOccurrence.Substring(indexOfHttp + 2).Trim(); + message = afterOccurrence + .Substring(2, indexOfHttp - 2) + .Trim(); + + helpLink = afterOccurrence + .Substring(indexOfHttp + 2) + .Trim(); } else { @@ -516,11 +523,19 @@ private static List>> GetSuppressionLines( var groupedSuppressionLines = suppressionLines .GroupBy(x => x.Item1, StringComparer.Ordinal) - .Select(group => new { AnalyzerName = group.Key, Values = group.Select(x => x.Item2).ToList() }) + .Select(group => new + { + AnalyzerName = group.Key, + Values = group + .Select(x => x.Item2) + .ToList(), + }) .OrderBy(x => x.AnalyzerName, StringComparer.Ordinal) .ToList(); - return groupedSuppressionLines.Select(x => Tuple.Create(x.AnalyzerName, x.Values)).ToList(); + return groupedSuppressionLines + .Select(x => Tuple.Create(x.AnalyzerName, x.Values)) + .ToList(); } private static void HandleSuppressionLinesForKnownAnalyzerRules( diff --git a/src/Atc.CodingRules.Updater/AppEmojisConstants.cs b/src/Atc.CodingRules.Updater/AppEmojisConstants.cs index ad11f66..6c12d5b 100644 --- a/src/Atc.CodingRules.Updater/AppEmojisConstants.cs +++ b/src/Atc.CodingRules.Updater/AppEmojisConstants.cs @@ -1,5 +1,3 @@ -using Spectre.Console; - namespace Atc.CodingRules.Updater; public static class AppEmojisConstants @@ -9,6 +7,5 @@ public static class AppEmojisConstants public const string AreaTemporarySuppression = Emoji.Known.CardIndex; public const string PackageReference = Emoji.Known.Package; - public const string Skipped = Emoji.Known.BackArrow; public const string DuplicateKey = Emoji.Known.Key; } \ No newline at end of file diff --git a/src/Atc.CodingRules.Updater/Atc.CodingRules.Updater.csproj b/src/Atc.CodingRules.Updater/Atc.CodingRules.Updater.csproj index 288a02e..b9a2a72 100644 --- a/src/Atc.CodingRules.Updater/Atc.CodingRules.Updater.csproj +++ b/src/Atc.CodingRules.Updater/Atc.CodingRules.Updater.csproj @@ -1,13 +1,13 @@ - net8.0 + net10.0 - - - + + + diff --git a/src/Atc.CodingRules.Updater/CodingRulesUpdaterVersionHelper.cs b/src/Atc.CodingRules.Updater/CodingRulesUpdaterVersionHelper.cs index f3c8737..599cff1 100644 --- a/src/Atc.CodingRules.Updater/CodingRulesUpdaterVersionHelper.cs +++ b/src/Atc.CodingRules.Updater/CodingRulesUpdaterVersionHelper.cs @@ -17,8 +17,7 @@ public static bool IsLatestVersion() return latestVersion is null || !latestVersion.GreaterThan(currentVersion); } - public static void PrintUpdateInfoIfNeeded( - ILogger logger) + public static void PrintUpdateInfoIfNeeded(ILogger logger) { if (IsLatestVersion()) { diff --git a/src/Atc.CodingRules.Updater/EditorConfigHelper.cs b/src/Atc.CodingRules.Updater/EditorConfigHelper.cs index 1e2df2f..58fb083 100644 --- a/src/Atc.CodingRules.Updater/EditorConfigHelper.cs +++ b/src/Atc.CodingRules.Updater/EditorConfigHelper.cs @@ -152,7 +152,9 @@ public static Task UpdateRootFileAddCustomAtcAutogeneratedRuleSuppressions( var rootEditorConfigFile = new FileInfo(Path.Combine(projectPath.FullName, FileName)); var rawFileData = FileHelper.ReadAllText(rootEditorConfigFile); - var lines = rawFileData.Split(FileHelper.LineBreaks, StringSplitOptions.None).ToList(); + var lines = rawFileData + .Split(FileHelper.LineBreaks, StringSplitOptions.None) + .ToList(); lines.Add(string.Empty); lines.Add(string.Empty); @@ -203,7 +205,10 @@ private static void UpdateFile( { var gitKeyValues = contentGit.GetDotnetDiagnosticSeverityKeyValues(); var fileKeyValues = contentFile.GetDotnetDiagnosticSeverityKeyValues(); - var fileCustomKeyValues = customLines.ToArray().GetDotnetDiagnosticSeverityKeyValues(); + var fileCustomKeyValues = customLines + .ToArray() + .GetDotnetDiagnosticSeverityKeyValues(); + LogSeverityDiffs(logger, gitKeyValues, fileKeyValues, fileCustomKeyValues, contentGit, newContentFile); } } @@ -279,8 +284,7 @@ private static string BuildNewContentFile( return newContentFile; } - private static string ExtractContentBasePart( - string content) + private static string ExtractContentBasePart(string content) { var lines = content.Split(FileHelper.LineBreaks, StringSplitOptions.None); @@ -332,7 +336,10 @@ private static List>> ExtractContentCustomParts( customParts.Add(new Tuple>(workingOnCustomHeader, workingOnCustomLines)); } - workingOnCustomHeader = nextLine.Substring(CustomSectionHeaderPrefix.Length).Trim(); + workingOnCustomHeader = nextLine + .Substring(CustomSectionHeaderPrefix.Length) + .Trim(); + workingOnCustomLines = []; } } diff --git a/src/Atc.CodingRules.Updater/Extensions/ListExtensions.cs b/src/Atc.CodingRules.Updater/Extensions/ListExtensions.cs index df877f7..96abc24 100644 --- a/src/Atc.CodingRules.Updater/Extensions/ListExtensions.cs +++ b/src/Atc.CodingRules.Updater/Extensions/ListExtensions.cs @@ -3,8 +3,7 @@ namespace System.Collections; public static class ListExtensions { - public static void TrimEndForEmptyValues( - this IList values) + public static void TrimEndForEmptyValues(this IList values) { ArgumentNullException.ThrowIfNull(values); @@ -17,7 +16,10 @@ public static void TrimEndForEmptyValues( } else { - var lastLine = values.Last().Trim(); + var lastLine = values + .Last() + .Trim(); + if (lastLine.Length == 0) { values.RemoveAt(values.Count - 1); diff --git a/src/Atc.CodingRules.Updater/Extensions/StringArrayExtensions.cs b/src/Atc.CodingRules.Updater/Extensions/StringArrayExtensions.cs index ea121b9..e8f4402 100644 --- a/src/Atc.CodingRules.Updater/Extensions/StringArrayExtensions.cs +++ b/src/Atc.CodingRules.Updater/Extensions/StringArrayExtensions.cs @@ -4,8 +4,7 @@ namespace System; public static class StringArrayExtensions { - public static Collection GetKeyValues( - this string[] values) + public static Collection GetKeyValues(this string[] values) { ArgumentNullException.ThrowIfNull(values); diff --git a/src/Atc.CodingRules.Updater/Extensions/StringExtensions.cs b/src/Atc.CodingRules.Updater/Extensions/StringExtensions.cs index e7b1329..badc6c4 100644 --- a/src/Atc.CodingRules.Updater/Extensions/StringExtensions.cs +++ b/src/Atc.CodingRules.Updater/Extensions/StringExtensions.cs @@ -6,8 +6,7 @@ public static class StringExtensions { private static readonly string[] LineBreaks = ["\r\n", "\r", "\n"]; - public static string TrimEndForEmptyLines( - this string value) + public static string TrimEndForEmptyLines(this string value) { if (string.IsNullOrEmpty(value)) { @@ -22,8 +21,7 @@ public static string TrimEndForEmptyLines( return string.Join(Environment.NewLine, values); } - public static Collection GetKeyValues( - this string value) + public static Collection GetKeyValues(this string value) => string.IsNullOrEmpty(value) ? [] : value diff --git a/src/Atc.CodingRules.Updater/FileHelper.cs b/src/Atc.CodingRules.Updater/FileHelper.cs index dbd5480..65a2f18 100644 --- a/src/Atc.CodingRules.Updater/FileHelper.cs +++ b/src/Atc.CodingRules.Updater/FileHelper.cs @@ -5,9 +5,13 @@ public static class FileHelper [SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "OK.")] public static string[] LineBreaks => Helpers.FileHelper.LineBreaks; - public static string ReadAllText(FileInfo file) => Helpers.FileHelper.ReadAllText(file); + public static string ReadAllText(FileInfo file) + => Helpers.FileHelper.ReadAllText(file); - public static Task WriteAllTextAsync(FileInfo file, string content) => Helpers.FileHelper.WriteAllTextAsync(file, content); + public static Task WriteAllTextAsync( + FileInfo file, + string content) + => Helpers.FileHelper.WriteAllTextAsync(file, content); public static Collection SearchAllForElement( DirectoryInfo projectPath, @@ -67,8 +71,15 @@ public static bool AreFilesEqual( return false; } - var headerLinesA = dataA.ToLines().Take(10).ToList(); - var headerLinesB = dataB.ToLines().Take(10).ToList(); + var headerLinesA = dataA + .ToLines() + .Take(10) + .ToList(); + + var headerLinesB = dataB + .ToLines() + .Take(10) + .ToList(); if (headerLinesA.Find(x => x.StartsWith("# Version", StringComparison.CurrentCultureIgnoreCase)) != headerLinesB.Find(x => x.StartsWith("# Version", StringComparison.CurrentCultureIgnoreCase))) @@ -86,23 +97,20 @@ public static bool AreFilesEqual( headerLinesB.Find(x => x.StartsWith("# Distribution", StringComparison.CurrentCultureIgnoreCase)); } - public static bool ContainsEditorConfigFile( - DirectoryInfo? directory) + public static bool ContainsEditorConfigFile(DirectoryInfo? directory) => directory is not null && directory.Exists && Directory.GetFiles(directory.FullName) .Any(x => x.Equals(".editorconfig", StringComparison.OrdinalIgnoreCase)); - public static bool ContainsSolutionOrProjectFile( - DirectoryInfo? directory) + public static bool ContainsSolutionOrProjectFile(DirectoryInfo? directory) => directory is not null && directory.Exists && Directory.GetFiles(directory.FullName) .Any(x => x.EndsWith(".sln", StringComparison.OrdinalIgnoreCase) || x.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase)); - public static bool IsSolutionOrProjectFile( - FileInfo? file) + public static bool IsSolutionOrProjectFile(FileInfo? file) => file is not null && file.Exists && (".sln".Equals(file.Extension, StringComparison.OrdinalIgnoreCase) || diff --git a/src/Atc.CodingRules.Updater/GlobalUsings.cs b/src/Atc.CodingRules.Updater/GlobalUsings.cs index 0c406a0..d596480 100644 --- a/src/Atc.CodingRules.Updater/GlobalUsings.cs +++ b/src/Atc.CodingRules.Updater/GlobalUsings.cs @@ -10,4 +10,5 @@ global using Atc.DotNet; global using Atc.Helpers; -global using Microsoft.Extensions.Logging; \ No newline at end of file +global using Microsoft.Extensions.Logging; +global using Spectre.Console; \ No newline at end of file diff --git a/src/Atc.CodingRules.Updater/Models/DotnetNugetPackage.cs b/src/Atc.CodingRules.Updater/Models/DotnetNugetPackage.cs index 3ddecbc..0c68bf7 100644 --- a/src/Atc.CodingRules.Updater/Models/DotnetNugetPackage.cs +++ b/src/Atc.CodingRules.Updater/Models/DotnetNugetPackage.cs @@ -2,14 +2,19 @@ namespace Atc.CodingRules.Updater.Models; public class DotnetNugetPackage { - public DotnetNugetPackage(string packageId, Version currentVersion) + public DotnetNugetPackage( + string packageId, + Version currentVersion) { PackageId = packageId; Version = currentVersion; NewestVersion = currentVersion; } - public DotnetNugetPackage(string packageId, Version currentVersion, Version newestVersion) + public DotnetNugetPackage( + string packageId, + Version currentVersion, + Version newestVersion) { PackageId = packageId; Version = currentVersion; diff --git a/src/Atc.CodingRules.Updater/ProjectFrameworkType.cs b/src/Atc.CodingRules.Updater/ProjectFrameworkType.cs index efbba5f..b3b5c3d 100644 --- a/src/Atc.CodingRules.Updater/ProjectFrameworkType.cs +++ b/src/Atc.CodingRules.Updater/ProjectFrameworkType.cs @@ -3,6 +3,7 @@ namespace Atc.CodingRules.Updater; public enum ProjectFrameworkType { None, + Aspire, AzureFunctions, Blazor, Cli, diff --git a/src/Atc.CodingRules.Updater/ProjectSanityCheckHelper.cs b/src/Atc.CodingRules.Updater/ProjectSanityCheckHelper.cs index d2cbf5a..4272fb2 100644 --- a/src/Atc.CodingRules.Updater/ProjectSanityCheckHelper.cs +++ b/src/Atc.CodingRules.Updater/ProjectSanityCheckHelper.cs @@ -15,11 +15,13 @@ public static void CheckFiles( { case SupportedProjectTargetType.DotNet5: HasEnableNetAnalyzers(throwIf, logger, projectPath, projectTarget); + HasTargetFrameworkAndImplicitUsings(throwIf, logger, projectPath, "netcoreapp3.1"); break; case SupportedProjectTargetType.DotNet6: case SupportedProjectTargetType.DotNet7: case SupportedProjectTargetType.DotNet8: case SupportedProjectTargetType.DotNet9: + case SupportedProjectTargetType.DotNet10: HasTargetFrameworkAndImplicitUsings(throwIf, logger, projectPath, "netcoreapp3.1"); break; } diff --git a/src/Atc.CodingRules.Updater/SupportedProjectTargetType.cs b/src/Atc.CodingRules.Updater/SupportedProjectTargetType.cs index 9e622d5..71e5c01 100644 --- a/src/Atc.CodingRules.Updater/SupportedProjectTargetType.cs +++ b/src/Atc.CodingRules.Updater/SupportedProjectTargetType.cs @@ -3,9 +3,10 @@ namespace Atc.CodingRules.Updater; public enum SupportedProjectTargetType { DotNetCore, - DotNet5, // STS - DotNet6, // LTS - DotNet7, // STS - DotNet8, // LTS - DotNet9, // STS + DotNet5, // STS + DotNet6, // LTS + DotNet7, // STS + DotNet8, // LTS + DotNet9, // STS + DotNet10, // LTS } \ No newline at end of file diff --git a/src/Atc.CodingRules/Atc.CodingRules.csproj b/src/Atc.CodingRules/Atc.CodingRules.csproj index 77e413a..303bf5b 100644 --- a/src/Atc.CodingRules/Atc.CodingRules.csproj +++ b/src/Atc.CodingRules/Atc.CodingRules.csproj @@ -1,11 +1,11 @@ - net8.0 + net10.0 - + \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index be885ba..9a1b1a2 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -52,6 +52,6 @@ - + diff --git a/test/.editorconfig b/test/.editorconfig index 5ba7181..92d0639 100644 --- a/test/.editorconfig +++ b/test/.editorconfig @@ -22,7 +22,7 @@ # https://www.meziantou.net/enforcing-asynchronous-code-good-practices-using-a-roslyn-analyzer.htm dotnet_diagnostic.MA0004.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0004.md dotnet_diagnostic.MA0016.severity = none # https://github.com/atc-net/atc-coding-rules/blob/main/documentation/CodeAnalyzersRules/Meziantou/MA0016.md - +dotnet_diagnostic.MA0051.severity = none # Method Length # Microsoft - Code Analysis # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Atc.CodingRules.AnalyzerProviders.Tests.csproj b/test/Atc.CodingRules.AnalyzerProviders.Tests/Atc.CodingRules.AnalyzerProviders.Tests.csproj index d33be25..ccccbb5 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Atc.CodingRules.AnalyzerProviders.Tests.csproj +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Atc.CodingRules.AnalyzerProviders.Tests.csproj @@ -1,24 +1,10 @@ - net8.0 + net10.0 false - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/GlobalUsings.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/GlobalUsings.cs index 006b615..a643287 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/GlobalUsings.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/GlobalUsings.cs @@ -1,6 +1,4 @@ +global using System.Diagnostics.CodeAnalysis; +global using Atc.CodingRules.AnalyzerProviders.Models; global using Atc.CodingRules.AnalyzerProviders.Providers; -global using Atc.XUnit; - -global using Microsoft.Extensions.Logging.Abstractions; - -global using Xunit; \ No newline at end of file +global using Microsoft.Extensions.Logging.Abstractions; \ No newline at end of file diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/AsyncFixerProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/AsyncFixerProviderTests.cs index 277f218..bbcc637 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/AsyncFixerProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/AsyncFixerProviderTests.cs @@ -2,19 +2,25 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class AsyncFixerProviderTests +public sealed class AsyncFixerProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { - // Arrange - var provider = new AsyncFixerProvider(NullLogger.Instance); + AnalyzerProviderBaseRuleData? actual = null; - // Act - var actual = await provider.CollectBaseRules(providerCollectingMode); + await RetryHelper.ExecuteWithRetryAsync(async () => + { + // Arrange + var provider = new AsyncFixerProvider(NullLogger.Instance); + + // Act + actual = await provider.CollectBaseRules(providerCollectingMode); + }); // Assert Assert.NotNull(actual); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/AsyncifyProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/AsyncifyProviderTests.cs index 768d663..0b511a7 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/AsyncifyProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/AsyncifyProviderTests.cs @@ -2,13 +2,14 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class AsyncifyProviderTests +public sealed class AsyncifyProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { // Arrange var provider = new AsyncifyProvider(NullLogger.Instance); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MeziantouProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MeziantouProviderTests.cs index 99b1252..f0120af 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MeziantouProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MeziantouProviderTests.cs @@ -2,13 +2,14 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class MeziantouProviderTests +public sealed class MeziantouProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { // Arrange var provider = new MeziantouProvider(NullLogger.Instance); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCodeAnalysisNetAnalyzersProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCodeAnalysisNetAnalyzersProviderTests.cs index 8b958a3..7179123 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCodeAnalysisNetAnalyzersProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCodeAnalysisNetAnalyzersProviderTests.cs @@ -8,7 +8,8 @@ public class MicrosoftCodeAnalysisNetAnalyzersProviderTests [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { // Arrange var provider = new MicrosoftCodeAnalysisNetAnalyzersProvider(NullLogger.Instance); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCompilerErrorsProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCompilerErrorsProviderTests.cs index 712937f..7cfbc78 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCompilerErrorsProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCompilerErrorsProviderTests.cs @@ -2,13 +2,14 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class MicrosoftCompilerErrorsProviderTests +public sealed class MicrosoftCompilerErrorsProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { // Arrange var provider = new MicrosoftCompilerErrorsProvider(NullLogger.Instance); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCompilerErrorsProviderUndocumentedTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCompilerErrorsProviderUndocumentedTests.cs index feac247..2ba094b 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCompilerErrorsProviderUndocumentedTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftCompilerErrorsProviderUndocumentedTests.cs @@ -2,13 +2,14 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class MicrosoftCompilerErrorsProviderUndocumentedTests +public sealed class MicrosoftCompilerErrorsProviderUndocumentedTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { // Arrange var provider = new MicrosoftCompilerErrorsProviderUndocumented(NullLogger.Instance); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftVisualStudioThreadingAnalyzersProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftVisualStudioThreadingAnalyzersProviderTests.cs index 114aa71..80a4bad 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftVisualStudioThreadingAnalyzersProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftVisualStudioThreadingAnalyzersProviderTests.cs @@ -2,19 +2,25 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class MicrosoftVisualStudioThreadingAnalyzersProviderTests +public sealed class MicrosoftVisualStudioThreadingAnalyzersProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { - // Arrange - var provider = new MicrosoftVisualStudioThreadingAnalyzersProvider(NullLogger.Instance); + AnalyzerProviderBaseRuleData? actual = null; - // Act - var actual = await provider.CollectBaseRules(providerCollectingMode); + await RetryHelper.ExecuteWithRetryAsync(async () => + { + // Arrange + var provider = new MicrosoftVisualStudioThreadingAnalyzersProvider(NullLogger.Instance); + + // Act + actual = await provider.CollectBaseRules(providerCollectingMode); + }); // Assert Assert.NotNull(actual); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/NSubstituteAnalyzersProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/NSubstituteAnalyzersProviderTests.cs index 7a06155..b2123d8 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/NSubstituteAnalyzersProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/NSubstituteAnalyzersProviderTests.cs @@ -2,13 +2,14 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class NSubstituteAnalyzersProviderTests +public sealed class NSubstituteAnalyzersProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { // Arrange var provider = new NSubstituteAnalyzersProvider(NullLogger.Instance); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/SecurityCodeScanVs2019ProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/SecurityCodeScanVs2019ProviderTests.cs index f585ccb..086ee86 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/SecurityCodeScanVs2019ProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/SecurityCodeScanVs2019ProviderTests.cs @@ -2,13 +2,14 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class SecurityCodeScanVs2019ProviderTests +public sealed class SecurityCodeScanVs2019ProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { // Arrange var provider = new SecurityCodeScanVs2019Provider(NullLogger.Instance); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/SonarAnalyzerCSharpProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/SonarAnalyzerCSharpProviderTests.cs index fd8e6c7..f36fde3 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/SonarAnalyzerCSharpProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/SonarAnalyzerCSharpProviderTests.cs @@ -2,13 +2,14 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class SonarAnalyzerCSharpProviderTests +public sealed class SonarAnalyzerCSharpProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { // Arrange var provider = new SonarAnalyzerCSharpProvider(NullLogger.Instance); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/StyleCopAnalyzersProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/StyleCopAnalyzersProviderTests.cs index 02cc462..38fb271 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/StyleCopAnalyzersProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/StyleCopAnalyzersProviderTests.cs @@ -2,19 +2,25 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class StyleCopAnalyzersProviderTests +public sealed class StyleCopAnalyzersProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { - // Arrange - var provider = new StyleCopAnalyzersProvider(NullLogger.Instance); + AnalyzerProviderBaseRuleData? actual = null; - // Act - var actual = await provider.CollectBaseRules(providerCollectingMode); + await RetryHelper.ExecuteWithRetryAsync(async () => + { + // Arrange + var provider = new StyleCopAnalyzersProvider(NullLogger.Instance); + + // Act + actual = await provider.CollectBaseRules(providerCollectingMode); + }); // Assert Assert.NotNull(actual); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/WpfAnalyzersProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/WpfAnalyzersProviderTests.cs index 487f54b..cd09819 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/WpfAnalyzersProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/WpfAnalyzersProviderTests.cs @@ -2,13 +2,14 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class WpfAnalyzersProviderTests +public sealed class WpfAnalyzersProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { // Arrange var provider = new WpfAnalyzersProvider(NullLogger.Instance); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/XunitProviderTests.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/XunitProviderTests.cs index 2642f63..85bac68 100644 --- a/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/XunitProviderTests.cs +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/XunitProviderTests.cs @@ -2,19 +2,25 @@ namespace Atc.CodingRules.AnalyzerProviders.Tests.Providers; [Trait(Traits.Category, Traits.Categories.Integration)] [Trait(Traits.Category, Traits.Categories.SkipWhenLiveUnitTesting)] -public class XunitProviderTests +public sealed class XunitProviderTests { [Theory] [InlineData(ProviderCollectingMode.LocalCache)] [InlineData(ProviderCollectingMode.GitHub)] [InlineData(ProviderCollectingMode.ReCollect)] - public async Task CollectBaseRules(ProviderCollectingMode providerCollectingMode) + public async Task CollectBaseRules( + ProviderCollectingMode providerCollectingMode) { - // Arrange - var provider = new XunitProvider(NullLogger.Instance); + AnalyzerProviderBaseRuleData? actual = null; - // Act - var actual = await provider.CollectBaseRules(providerCollectingMode); + await RetryHelper.ExecuteWithRetryAsync(async () => + { + // Arrange + var provider = new XunitProvider(NullLogger.Instance); + + // Act + actual = await provider.CollectBaseRules(providerCollectingMode); + }); // Assert Assert.NotNull(actual); diff --git a/test/Atc.CodingRules.AnalyzerProviders.Tests/RetryHelper.cs b/test/Atc.CodingRules.AnalyzerProviders.Tests/RetryHelper.cs new file mode 100644 index 0000000..f5f8290 --- /dev/null +++ b/test/Atc.CodingRules.AnalyzerProviders.Tests/RetryHelper.cs @@ -0,0 +1,38 @@ +namespace Atc.CodingRules.AnalyzerProviders.Tests; + +/// +/// Helper class for retrying flaky tests that depend on external services. +/// +public static class RetryHelper +{ + /// + /// Executes an async action with retry logic. + /// + /// The async action to execute. + /// Maximum number of retry attempts (default is 3). + /// Delay in milliseconds between retries (default is 1000ms). + /// A task representing the asynchronous operation. + [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Retry logic needs to catch all exceptions.")] + public static async Task ExecuteWithRetryAsync( + Func action, + int maxRetries = 3, + int delayBetweenRetriesMs = 1000) + { + ArgumentNullException.ThrowIfNull(action); + + var attempts = 0; + while (true) + { + try + { + attempts++; + await action(); + return; + } + catch when (attempts < maxRetries) + { + await Task.Delay(delayBetweenRetriesMs); + } + } + } +} \ No newline at end of file diff --git a/test/Atc.CodingRules.Updater.Tests/Atc.CodingRules.Updater.Tests.csproj b/test/Atc.CodingRules.Updater.Tests/Atc.CodingRules.Updater.Tests.csproj index d780f48..b2e0bcb 100644 --- a/test/Atc.CodingRules.Updater.Tests/Atc.CodingRules.Updater.Tests.csproj +++ b/test/Atc.CodingRules.Updater.Tests/Atc.CodingRules.Updater.Tests.csproj @@ -1,24 +1,10 @@ - net8.0 + net10.0 false - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - diff --git a/test/Atc.CodingRules.Updater.Tests/EditorConfigHelperTests.cs b/test/Atc.CodingRules.Updater.Tests/EditorConfigHelperTests.cs index f56f3ea..5377996 100644 --- a/test/Atc.CodingRules.Updater.Tests/EditorConfigHelperTests.cs +++ b/test/Atc.CodingRules.Updater.Tests/EditorConfigHelperTests.cs @@ -1,7 +1,7 @@ // ReSharper disable ReturnTypeCanBeEnumerable.Local namespace Atc.CodingRules.Updater.Tests; -public class EditorConfigHelperTests +public sealed class EditorConfigHelperTests { private static readonly string WorkingDirectory = Path.Combine(Path.GetTempPath(), "atc-coding-rules-updater-editorconfig-test"); private readonly FileInfo[] testFiles = CollectTestFiles(); @@ -9,9 +9,7 @@ public class EditorConfigHelperTests private readonly ITestOutputHelper testOutput; public EditorConfigHelperTests(ITestOutputHelper testOutput) - { - this.testOutput = testOutput; - } + => this.testOutput = testOutput; [Fact] public void DotNet6_Root_Update1() diff --git a/test/Atc.CodingRules.Updater.Tests/GlobalUsings.cs b/test/Atc.CodingRules.Updater.Tests/GlobalUsings.cs index 3626a99..dbea1a4 100644 --- a/test/Atc.CodingRules.Updater.Tests/GlobalUsings.cs +++ b/test/Atc.CodingRules.Updater.Tests/GlobalUsings.cs @@ -1,2 +1 @@ -global using System.Reflection; -global using Xunit.Abstractions; \ No newline at end of file +global using System.Reflection; \ No newline at end of file diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 020fd5c..9ecc39c 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -6,22 +6,36 @@ --> - - annotations + + true + true + true - - + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - + + \ No newline at end of file