From d21dbf889ad4e6be6da23a2658d9c67cc7b71bc9 Mon Sep 17 00:00:00 2001 From: ancplua Date: Tue, 12 May 2026 23:45:49 +0200 Subject: [PATCH 1/7] feat(sdk): relax test-folder analyzers + demote 25 style rules to suggestion Splits the analyzer policy into two tiers so SDK consumers get a usable default for tests + style-rule false positives: 1. New path-scoped Config/TestProjects.editorconfig auto-injected by Common.props. Covers tests/, test/, **/*.Tests/, **/*.UnitTests/, **/*.IntegrationTests/, **/examples/. Drops CA1707/CA1816/CA2000/CA2007/ CA2201/CA1031/CA1812/CA1822/CA1859/CA1852/CA5394/VSTHRD111/VSTHRD200/ IDE0051/IDE0052/IDE1006/AL0025/AL0057/AL0105/AL0112/AL0137/RS0030/ xUnit1004 to none-or-suggestion in test paths only. Opt out via true. 2. Demotes 14 NetAnalyzers + 11 AL rules from warning to suggestion in the global analyzer files. Bug-class rules (CA2000/2012/2201/1310/1820, all crypto CA53xx, AL0026 TimeProvider migration) stay at warning. ConfigFilesGenerator now skips path-scoped editorconfigs when computing GetRuleIdsConfiguredOutside so the global analyzer files stay authoritative. Adds two SdkTests: TestProjects_EditorConfig_RelaxesIDE1006InTestPath and TestProjects_EditorConfig_DisableOptOutWorks. All three .nuspec files updated to ship Config/TestProjects.editorconfig. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/ANcpLua.NET.Sdk.Test.nuspec | 1 + src/ANcpLua.NET.Sdk.Web.nuspec | 1 + src/ANcpLua.NET.Sdk.nuspec | 1 + src/Build/Common/Common.props | 12 +- .../Analyzer.ANcpLua.Analyzers.editorconfig | 48 +++- ...oft.CodeAnalysis.NetAnalyzers.editorconfig | 59 +++-- src/Config/TestProjects.editorconfig | 226 ++++++++++++++++++ tests/ANcpLua.Sdk.Tests/SdkTests.cs | 57 +++++ tools/ConfigFilesGenerator/Program.cs | 13 + 9 files changed, 390 insertions(+), 28 deletions(-) create mode 100644 src/Config/TestProjects.editorconfig diff --git a/src/ANcpLua.NET.Sdk.Test.nuspec b/src/ANcpLua.NET.Sdk.Test.nuspec index e5d9916..083258e 100644 --- a/src/ANcpLua.NET.Sdk.Test.nuspec +++ b/src/ANcpLua.NET.Sdk.Test.nuspec @@ -47,6 +47,7 @@ + diff --git a/src/ANcpLua.NET.Sdk.Web.nuspec b/src/ANcpLua.NET.Sdk.Web.nuspec index 17b054f..855b75a 100644 --- a/src/ANcpLua.NET.Sdk.Web.nuspec +++ b/src/ANcpLua.NET.Sdk.Web.nuspec @@ -47,6 +47,7 @@ + diff --git a/src/ANcpLua.NET.Sdk.nuspec b/src/ANcpLua.NET.Sdk.nuspec index e6d2722..d1cf581 100644 --- a/src/ANcpLua.NET.Sdk.nuspec +++ b/src/ANcpLua.NET.Sdk.nuspec @@ -47,6 +47,7 @@ + diff --git a/src/Build/Common/Common.props b/src/Build/Common/Common.props index 1884e0f..138ebf7 100644 --- a/src/Build/Common/Common.props +++ b/src/Build/Common/Common.props @@ -72,10 +72,15 @@ + the project cone render at root by default; Link is the documented container. + + TestProjects.editorconfig is path-glob-scoped, not is_global=true; it's the + file that relaxes test-folder rules. We exclude it from the wildcard include + and re-include it conditionally below so consumers can opt out via + true. --> + diff --git a/src/Config/Analyzer.ANcpLua.Analyzers.editorconfig b/src/Config/Analyzer.ANcpLua.Analyzers.editorconfig index 543de2e..b40f467 100644 --- a/src/Config/Analyzer.ANcpLua.Analyzers.editorconfig +++ b/src/Config/Analyzer.ANcpLua.Analyzers.editorconfig @@ -126,7 +126,10 @@ dotnet_diagnostic.AL0024.severity = error # AL0025: Anonymous function can be made static # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0025 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0025.severity = warning +# Demoted: micro-perf hint (avoids implicit `this` capture). IDE0200 covers the +# same ground at suggestion; keeping AL0025 as suggestion avoids 100+ build-errors +# on existing code while leaving the fix one click away in the IDE. +dotnet_diagnostic.AL0025.severity = suggestion # AL0026: Avoid DateTime/DateTimeOffset time accessors # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0026 @@ -191,7 +194,9 @@ dotnet_diagnostic.AL0037.severity = warning # AL0039: Use StringComparison extension # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0039 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0039.severity = warning +# Demoted: style preference toward ANcpLua.Roslyn.Utilities extensions. The real +# correctness rule is CA1310 (kept at warning). Fix is one click in the IDE. +dotnet_diagnostic.AL0039.severity = suggestion # AL0040: Use attribute argument extraction extension # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0040 @@ -349,7 +354,10 @@ dotnet_diagnostic.AL0069.severity = suggestion # AL0070: Collector endpoint should use OTLP protocol # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0070 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0070.severity = warning +# Demoted: false-positives on non-collector endpoint strings (config URLs, +# health check probes, sample-data URLs). The platform-level OTLP exporter +# configuration is what actually matters. +dotnet_diagnostic.AL0070.severity = suggestion # AL0071: [Meter] class must be partial static # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0071 @@ -405,7 +413,10 @@ dotnet_diagnostic.AL0080.severity = warning # AL0081: Missing health checks # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0081 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0081.severity = warning +# Demoted: operational guidance, not a correctness bug. Many services +# legitimately defer health check wiring to platform infrastructure (Kubernetes +# liveness/readiness probes, container orchestrator health checks). +dotnet_diagnostic.AL0081.severity = suggestion # AL0082: Consider using configuration for connection string # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0082 @@ -472,27 +483,36 @@ dotnet_diagnostic.AL0093.severity = suggestion # AL0094: Avoid 'dynamic' keyword in AOT-published code # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0094 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0094.severity = warning +# Demoted: AOT advisory. Non-AOT projects use `dynamic` legitimately (DLR scripting, +# interop). AOT projects should re-promote to warning/error in their editorconfig. +dotnet_diagnostic.AL0094.severity = suggestion # AL0095: Avoid Expression.Compile() in AOT context # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0095 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0095.severity = warning +# Demoted: AOT advisory — see AL0094/AL0101 rationale. +dotnet_diagnostic.AL0095.severity = suggestion # AL0096: Enable EventSourceSupport for AOT with telemetry # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0096 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0096.severity = warning +# Demoted: AOT advisory — see AL0094/AL0101 rationale. +dotnet_diagnostic.AL0096.severity = suggestion # AL0101: Activator.CreateInstance is not AOT-safe # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0101 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0101.severity = warning +# Demoted: not every project ships AOT. The analyzer flags Activator.CreateInstance +# regardless of , so non-AOT projects (most services + tests) get +# unconditional warnings. AOT-bound projects should bump this back to error via +# their .editorconfig along with true. +dotnet_diagnostic.AL0101.severity = suggestion # AL0102: Type.GetType with dynamic name is not AOT-safe # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0102 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0102.severity = warning +# Demoted: AOT advisory — see AL0094/AL0101 rationale. +dotnet_diagnostic.AL0102.severity = suggestion # AL0103: Closed hierarchy match is not exhaustive # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0103 @@ -552,7 +572,9 @@ dotnet_diagnostic.AL0113.severity = warning # AL0114: Prefer TryParse over Parse # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0114 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0114.severity = warning +# Demoted: style preference. Parse is correct when the caller verifies input +# upstream (route params, config) or when an exception IS the desired signal. +dotnet_diagnostic.AL0114.severity = suggestion # AL0115: Empty catch block swallows exceptions # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0115 @@ -667,7 +689,11 @@ dotnet_diagnostic.AL0136.severity = warning # AL0137: Use Guard.* helpers instead of throw helpers # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0137 # Enabled: True, Severity: warning -dotnet_diagnostic.AL0137.severity = warning +# Demoted: style migration to ANcpLua.Roslyn.Utilities Guard.* helpers. +# Codebases that haven't taken that dependency get hundreds of warnings on +# valid BCL ArgumentNullException.ThrowIfNull / ArgumentException.ThrowIfNullOrEmpty +# usage. The RS0030 banned-symbols rule still flags the BCL calls themselves. +dotnet_diagnostic.AL0137.severity = suggestion # AL0138: Use Math.Round/MathF.Round overload with explicit MidpointRounding # Help link: https://ancplua.mintlify.app/analyzers/rules/AL0138 diff --git a/src/Config/Analyzer.Microsoft.CodeAnalysis.NetAnalyzers.editorconfig b/src/Config/Analyzer.Microsoft.CodeAnalysis.NetAnalyzers.editorconfig index d2f0b71..7168dbf 100644 --- a/src/Config/Analyzer.Microsoft.CodeAnalysis.NetAnalyzers.editorconfig +++ b/src/Config/Analyzer.Microsoft.CodeAnalysis.NetAnalyzers.editorconfig @@ -18,7 +18,9 @@ dotnet_diagnostic.CA1001.severity = warning # CA1002: Do not expose generic lists # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1002 # Enabled: False, Severity: warning -dotnet_diagnostic.CA1002.severity = warning +# Demoted from warning: wire-format DTOs, protobuf, System.Text.Json and EF projections +# legitimately expose List. Per dotnet/roslyn-analyzers#1705 DTOs are exceptions. +dotnet_diagnostic.CA1002.severity = suggestion # CA1003: Use generic event handler instances # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1003 @@ -103,7 +105,9 @@ dotnet_diagnostic.CA1031.severity = none # CA1032: Implement standard exception constructors # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1032 # Enabled: False, Severity: warning -dotnet_diagnostic.CA1032.severity = warning +# Demoted: most custom exception types intentionally restrict construction +# (sealed default ctor, single-arg-only). Keeping it visible as suggestion. +dotnet_diagnostic.CA1032.severity = suggestion # CA1033: Interface methods should be callable by child types # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1033 @@ -113,7 +117,9 @@ dotnet_diagnostic.CA1033.severity = none # CA1034: Nested types should not be visible # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1034 # Enabled: False, Severity: warning -dotnet_diagnostic.CA1034.severity = warning +# Demoted: legitimate builder/options patterns (e.g. ConfigureOptions.Builder), +# protobuf and C# 14 extension blocks routinely nest types. +dotnet_diagnostic.CA1034.severity = suggestion # CA1036: Override methods on comparable types # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1036 @@ -168,22 +174,28 @@ dotnet_diagnostic.CA1051.severity = warning # CA1052: Static holder types should be Static or NotInheritable # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1052 # Enabled: False, Severity: warning -dotnet_diagnostic.CA1052.severity = warning +# Demoted: source generators and xUnit fixture base classes can't be sealed/static +# without breaking the generator contract. Style hint, not a real bug. +dotnet_diagnostic.CA1052.severity = suggestion # CA1054: URI-like parameters should not be strings # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1054 # Enabled: False, Severity: warning -dotnet_diagnostic.CA1054.severity = warning +# Demoted: JSON DTOs, config-bound options and minimal-API route handlers +# routinely pass URLs as strings. Style hint only. +dotnet_diagnostic.CA1054.severity = suggestion # CA1055: URI-like return values should not be strings # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1055 # Enabled: False, Severity: warning -dotnet_diagnostic.CA1055.severity = warning +# Demoted with CA1054/CA1056 — wire-format reality. +dotnet_diagnostic.CA1055.severity = suggestion # CA1056: URI-like properties should not be strings # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1056 # Enabled: False, Severity: warning -dotnet_diagnostic.CA1056.severity = warning +# Demoted: JSON DTOs require string URLs for serialization round-trip. +dotnet_diagnostic.CA1056.severity = suggestion # CA1058: Types should not extend certain base types # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1058 @@ -269,7 +281,9 @@ dotnet_diagnostic.CA1305.severity = none # CA1307: Specify StringComparison for clarity # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1307 # Enabled: False, Severity: warning -dotnet_diagnostic.CA1307.severity = warning +# Demoted: clarity hint only — the correctness rule is CA1310 (kept at warning). +# CA1307 is also covered by AL0039 (StringComparison ext) at suggestion. +dotnet_diagnostic.CA1307.severity = suggestion # CA1308: Normalize strings to uppercase # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1308 @@ -464,7 +478,8 @@ dotnet_diagnostic.CA1724.severity = none # CA1725: Parameter names should match base declaration # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1725 # Enabled: True, Severity: silent -dotnet_diagnostic.CA1725.severity = warning +# Demoted: pure naming style — does not affect correctness or behavior. +dotnet_diagnostic.CA1725.severity = suggestion # CA1727: Use PascalCase for named placeholders # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1727 @@ -521,7 +536,9 @@ dotnet_diagnostic.CA1816.severity = none # CA1819: Properties should not return arrays # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1819 # Enabled: False, Severity: warning -dotnet_diagnostic.CA1819.severity = warning +# Demoted: protobuf, schemas, OTLP, token validation and JSON DTOs require +# array-typed properties. Per dotnet/roslyn-analyzers#1705 wire types are exceptions. +dotnet_diagnostic.CA1819.severity = suggestion # CA1820: Test for empty strings using string length # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1820 @@ -536,12 +553,17 @@ dotnet_diagnostic.CA1821.severity = warning # CA1822: Mark members as static # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822 # Enabled: True, Severity: suggestion -dotnet_diagnostic.CA1822.severity = warning +# Demoted to suggestion (matches the analyzer default): micro-perf hint, often +# blocked by inheritance, interfaces or test instance harnesses (xUnit fixtures). +dotnet_diagnostic.CA1822.severity = suggestion # CA1823: Avoid unused private fields # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1823 # Enabled: False, Severity: warning -dotnet_diagnostic.CA1823.severity = warning +# Demoted: high false-positive rate on DI-injected fields read via reflection, +# source-generator pattern fields, and incremental-build scaffolding. Real +# dead-code detection is better served by IDE0052 (kept at warning in CodingStyle). +dotnet_diagnostic.CA1823.severity = suggestion # CA1824: Mark assemblies with NeutralResourcesLanguageAttribute # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1824 @@ -686,7 +708,9 @@ dotnet_diagnostic.CA1851.severity = suggestion # CA1852: Seal internal types # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852 # Enabled: True, Severity: silent -dotnet_diagnostic.CA1852.severity = warning +# Demoted: micro-perf hint; mocking frameworks (Moq, NSubstitute) and test +# scaffolding routinely require open internal types. The default is silent. +dotnet_diagnostic.CA1852.severity = suggestion # CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)' # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1853 @@ -721,7 +745,9 @@ dotnet_diagnostic.CA1858.severity = warning # CA1859: Use concrete types when possible for improved performance # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1859 # Enabled: True, Severity: suggestion -dotnet_diagnostic.CA1859.severity = warning +# Demoted: micro-perf hint that fights DIP (e.g. method returning IList). +# Default is suggestion. +dotnet_diagnostic.CA1859.severity = suggestion # CA1860: Avoid using 'Enumerable.Any()' extension method # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860 @@ -1547,7 +1573,10 @@ dotnet_diagnostic.CA5393.severity = warning # CA5394: Do not use insecure randomness # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5394 # Enabled: False, Severity: warning -dotnet_diagnostic.CA5394.severity = warning +# Demoted: high false-positive rate. Random is correct for shuffling, jitter, +# test data, sampling — non-crypto use cases. Real crypto sites should use +# RandomNumberGenerator and that's a separate rule (CA5390/5391). +dotnet_diagnostic.CA5394.severity = suggestion # CA5395: Miss HttpVerb attribute for action methods # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5395 diff --git a/src/Config/TestProjects.editorconfig b/src/Config/TestProjects.editorconfig new file mode 100644 index 0000000..6121886 --- /dev/null +++ b/src/Config/TestProjects.editorconfig @@ -0,0 +1,226 @@ +# SDK-injected test-folder relaxations +# ============================================================================= +# Auto-injected by Common.props alongside other Config/*.editorconfig files. +# This is a non-global editorconfig (no `is_global = true`) because it uses +# path globs — only path-scoped editorconfigs can carry [glob] sections. +# +# Path coverage strategy: +# - `tests/` — Microsoft solution convention (Sdk's tests/, qyl, ANcpLua.*) +# - `test/` — older /test/ convention (OpenTelemetry, some OSS projects) +# - `**/*.Tests/` — Visual Studio "Add new test project" convention (Paperless) +# - `**/*.UnitTests/` — explicit unit-test split +# - `**/*.IntegrationTests/` — explicit integration-test split +# - `**/examples/` — sample code adopts the same scaffolding shortcuts as tests +# +# Brace-expansion mixed with `**` is fragile across EditorConfig parsers, so we +# repeat the same severity table across explicit [glob] sections. Editing rules: +# update ALL sections together. Keep them byte-identical. +# +# Consumers can override any rule per-project via their own .editorconfig. +# To opt out of test relaxations entirely, set in your Directory.Build.props: +# true +# (gated in Common.props on the same flag). +# ============================================================================= + +# --- tests/ (Microsoft solution convention) --- +[**/tests/**/*.cs] + +# CA1707: Identifiers should not contain underscores +# Test method names like `Method_When_Scenario_Then_Expectation` are standard. +dotnet_diagnostic.CA1707.severity = none + +# CA1816: Dispose methods should call SuppressFinalize +# Test fixtures and fakes commonly implement IDisposable as a no-op or pass-through. +dotnet_diagnostic.CA1816.severity = none + +# CA2000: Dispose objects before losing scope +# Test code intentionally leaks short-lived resources for assertion clarity; the +# test runner tears the process down. +dotnet_diagnostic.CA2000.severity = none + +# CA2007: Consider calling ConfigureAwait on the awaited task +# Test hosts have no SynchronizationContext; ConfigureAwait noise hurts readability. +dotnet_diagnostic.CA2007.severity = none + +# CA2201: Do not raise reserved exception types +# Tests intentionally `throw new Exception("boom")` to drive failure paths in fakes. +dotnet_diagnostic.CA2201.severity = none + +# CA1031: Do not catch general exception types +# Test scaffolding (assertion frameworks, retry harnesses) legitimately swallows +# Exception to assert failure modes or wrap error messages. +dotnet_diagnostic.CA1031.severity = none + +# CA1812: Avoid uninstantiated internal classes +# Test data classes are loaded reflectively by [Theory]/[ClassData]. +dotnet_diagnostic.CA1812.severity = none + +# CA1822: Mark members as static +# Test method instances are required by xUnit/NUnit; static methods can't be tests. +dotnet_diagnostic.CA1822.severity = none + +# CA1859: Use concrete types when possible for improved performance +# Tests use interface-typed locals to verify polymorphism / DI contracts. +dotnet_diagnostic.CA1859.severity = none + +# CA1852: Seal internal types +# Test fixtures and helpers are routinely inherited by per-suite specializations. +dotnet_diagnostic.CA1852.severity = none + +# CA5394: Do not use insecure randomness +# Tests use Random for sample data; that's non-crypto by definition. +dotnet_diagnostic.CA5394.severity = none + +# VSTHRD111: Use ConfigureAwait(bool) — no SynchronizationContext in test hosts. +dotnet_diagnostic.VSTHRD111.severity = none + +# VSTHRD200: Use Async naming convention — test method names like +# `ReadAsync_OnEmpty_Throws` are not async-naming targets. +dotnet_diagnostic.VSTHRD200.severity = suggestion + +# IDE0051 / IDE0052: Unused private members +# [Theory]/[ClassData] generators look unused; xUnit reads them by name. +dotnet_diagnostic.IDE0051.severity = suggestion +dotnet_diagnostic.IDE0052.severity = suggestion + +# IDE1006: Naming rule violation — test methods use snake_case intentionally. +dotnet_diagnostic.IDE1006.severity = suggestion + +# AL0025: Anonymous function can be made static — tests capture `this` via fixture. +dotnet_diagnostic.AL0025.severity = none + +# AL0137: Use Guard.* helpers — test setup uses BCL ArgumentNullException.ThrowIfNull. +dotnet_diagnostic.AL0137.severity = none + +# AL0057: Avoid async void methods — required for some xUnit/NUnit harnesses. +dotnet_diagnostic.AL0057.severity = none + +# AL0105: Avoid blocking calls in async methods — tests use .Result for sync assertions. +dotnet_diagnostic.AL0105.severity = suggestion + +# AL0112: Avoid fire-and-forget task discard — background-task tests discard deliberately. +dotnet_diagnostic.AL0112.severity = suggestion + +# RS0030: Do not use banned APIs — tests use DateTime.UtcNow for assertions. +dotnet_diagnostic.RS0030.severity = suggestion + +# xUnit1004: Test methods should not be skipped — visible at suggestion is enough. +dotnet_diagnostic.xUnit1004.severity = suggestion + +# --- test/ (legacy convention) --- +[**/test/**/*.cs] +dotnet_diagnostic.CA1707.severity = none +dotnet_diagnostic.CA1816.severity = none +dotnet_diagnostic.CA2000.severity = none +dotnet_diagnostic.CA2007.severity = none +dotnet_diagnostic.CA2201.severity = none +dotnet_diagnostic.CA1031.severity = none +dotnet_diagnostic.CA1812.severity = none +dotnet_diagnostic.CA1822.severity = none +dotnet_diagnostic.CA1859.severity = none +dotnet_diagnostic.CA1852.severity = none +dotnet_diagnostic.CA5394.severity = none +dotnet_diagnostic.VSTHRD111.severity = none +dotnet_diagnostic.VSTHRD200.severity = suggestion +dotnet_diagnostic.IDE0051.severity = suggestion +dotnet_diagnostic.IDE0052.severity = suggestion +dotnet_diagnostic.IDE1006.severity = suggestion +dotnet_diagnostic.AL0025.severity = none +dotnet_diagnostic.AL0137.severity = none +dotnet_diagnostic.AL0057.severity = none +dotnet_diagnostic.AL0105.severity = suggestion +dotnet_diagnostic.AL0112.severity = suggestion +dotnet_diagnostic.RS0030.severity = suggestion +dotnet_diagnostic.xUnit1004.severity = suggestion + +# --- *.Tests/ (VS new-project convention: PaperlessREST.Tests, MyLib.Tests, ...) --- +[**/*.Tests/**/*.cs] +dotnet_diagnostic.CA1707.severity = none +dotnet_diagnostic.CA1816.severity = none +dotnet_diagnostic.CA2000.severity = none +dotnet_diagnostic.CA2007.severity = none +dotnet_diagnostic.CA2201.severity = none +dotnet_diagnostic.CA1031.severity = none +dotnet_diagnostic.CA1812.severity = none +dotnet_diagnostic.CA1822.severity = none +dotnet_diagnostic.CA1859.severity = none +dotnet_diagnostic.CA1852.severity = none +dotnet_diagnostic.CA5394.severity = none +dotnet_diagnostic.VSTHRD111.severity = none +dotnet_diagnostic.VSTHRD200.severity = suggestion +dotnet_diagnostic.IDE0051.severity = suggestion +dotnet_diagnostic.IDE0052.severity = suggestion +dotnet_diagnostic.IDE1006.severity = suggestion +dotnet_diagnostic.AL0025.severity = none +dotnet_diagnostic.AL0137.severity = none +dotnet_diagnostic.AL0057.severity = none +dotnet_diagnostic.AL0105.severity = suggestion +dotnet_diagnostic.AL0112.severity = suggestion +dotnet_diagnostic.RS0030.severity = suggestion +dotnet_diagnostic.xUnit1004.severity = suggestion + +# --- *.UnitTests/ --- +[**/*.UnitTests/**/*.cs] +dotnet_diagnostic.CA1707.severity = none +dotnet_diagnostic.CA1816.severity = none +dotnet_diagnostic.CA2000.severity = none +dotnet_diagnostic.CA2007.severity = none +dotnet_diagnostic.CA2201.severity = none +dotnet_diagnostic.CA1031.severity = none +dotnet_diagnostic.CA1812.severity = none +dotnet_diagnostic.CA1822.severity = none +dotnet_diagnostic.CA1859.severity = none +dotnet_diagnostic.CA1852.severity = none +dotnet_diagnostic.CA5394.severity = none +dotnet_diagnostic.VSTHRD111.severity = none +dotnet_diagnostic.VSTHRD200.severity = suggestion +dotnet_diagnostic.IDE0051.severity = suggestion +dotnet_diagnostic.IDE0052.severity = suggestion +dotnet_diagnostic.IDE1006.severity = suggestion +dotnet_diagnostic.AL0025.severity = none +dotnet_diagnostic.AL0137.severity = none +dotnet_diagnostic.AL0057.severity = none +dotnet_diagnostic.AL0105.severity = suggestion +dotnet_diagnostic.AL0112.severity = suggestion +dotnet_diagnostic.RS0030.severity = suggestion +dotnet_diagnostic.xUnit1004.severity = suggestion + +# --- *.IntegrationTests/ --- +[**/*.IntegrationTests/**/*.cs] +dotnet_diagnostic.CA1707.severity = none +dotnet_diagnostic.CA1816.severity = none +dotnet_diagnostic.CA2000.severity = none +dotnet_diagnostic.CA2007.severity = none +dotnet_diagnostic.CA2201.severity = none +dotnet_diagnostic.CA1031.severity = none +dotnet_diagnostic.CA1812.severity = none +dotnet_diagnostic.CA1822.severity = none +dotnet_diagnostic.CA1859.severity = none +dotnet_diagnostic.CA1852.severity = none +dotnet_diagnostic.CA5394.severity = none +dotnet_diagnostic.VSTHRD111.severity = none +dotnet_diagnostic.VSTHRD200.severity = suggestion +dotnet_diagnostic.IDE0051.severity = suggestion +dotnet_diagnostic.IDE0052.severity = suggestion +dotnet_diagnostic.IDE1006.severity = suggestion +dotnet_diagnostic.AL0025.severity = none +dotnet_diagnostic.AL0137.severity = none +dotnet_diagnostic.AL0057.severity = none +dotnet_diagnostic.AL0105.severity = suggestion +dotnet_diagnostic.AL0112.severity = suggestion +dotnet_diagnostic.RS0030.severity = suggestion +dotnet_diagnostic.xUnit1004.severity = suggestion + +# --- examples/ --- +[**/examples/**/*.cs] +dotnet_diagnostic.CA1816.severity = none +dotnet_diagnostic.CA2000.severity = none +dotnet_diagnostic.CA2007.severity = none +dotnet_diagnostic.CA1031.severity = none +dotnet_diagnostic.CA1822.severity = none +dotnet_diagnostic.VSTHRD111.severity = none +dotnet_diagnostic.IDE1006.severity = suggestion +dotnet_diagnostic.AL0025.severity = none +dotnet_diagnostic.AL0105.severity = suggestion +dotnet_diagnostic.AL0112.severity = suggestion +dotnet_diagnostic.RS0030.severity = suggestion diff --git a/tests/ANcpLua.Sdk.Tests/SdkTests.cs b/tests/ANcpLua.Sdk.Tests/SdkTests.cs index 4c32655..da4cb69 100644 --- a/tests/ANcpLua.Sdk.Tests/SdkTests.cs +++ b/tests/ANcpLua.Sdk.Tests/SdkTests.cs @@ -858,6 +858,63 @@ public class Tests static i => i.Contains("GitHubActionsTestLogger", StringComparison.OrdinalIgnoreCase)); } + [Fact] + public async Task TestProjects_EditorConfig_RelaxesIDE1006InTestPath() + { + // TestProjects.editorconfig drops IDE1006 (naming) from warning to suggestion + // for files under tests/, test/, **/*.Tests/, **/*.UnitTests/, **/*.IntegrationTests/. + // Without that relaxation, the lowercase `field` would fire IDE1006 as error + // in Release (TreatWarningsAsErrors=true). + await using var project = CreateProject(); + + var result = await project + .AddSource("tests/SampleTests.cs", """ + _ = ""; + + class Sample + { + private readonly int field; + + public Sample(int a) => field = a; + + public int A() => field; + } + """) + .BuildAsync(["--configuration", "Release"]); + + // IDE1006 must not be promoted to error or warning under tests/. + Assert.False(result.HasError("IDE1006")); + Assert.False(result.HasWarning("IDE1006")); + } + + [Fact] + public async Task TestProjects_EditorConfig_DisableOptOutWorks() + { + // Consumers can opt out of the test-relaxation editorconfig entirely by setting + // DisableSdkTestEditorConfig=true. After opting out, IDE1006 fires again in tests + // (the global CodingStyle.editorconfig has it at warning + Release promotes to error). + await using var project = CreateProject(); + + var result = await project + .WithProperty("DisableSdkTestEditorConfig", "true") + .WithProperty("TreatWarningsAsErrors", "false") + .AddSource("tests/SampleTests.cs", """ + _ = ""; + + class Sample + { + private readonly int field; + + public Sample(int a) => field = a; + + public int A() => field; + } + """) + .BuildAsync(["--configuration", "Release"]); + + Assert.True(result.HasWarning("IDE1006")); + } + [Fact] public async Task CentralPackageManagement() { diff --git a/tools/ConfigFilesGenerator/Program.cs b/tools/ConfigFilesGenerator/Program.cs index 286b675..e62d411 100644 --- a/tools/ConfigFilesGenerator/Program.cs +++ b/tools/ConfigFilesGenerator/Program.cs @@ -145,12 +145,25 @@ HashSet GetRuleIdsConfiguredOutside(FullPath configurationFilePath) if (!Directory.Exists(configRoot)) return configuredRuleIds; + // Path-scoped editorconfigs (no `is_global = true`) override rules only inside + // their [glob] sections, not project-wide. Skipping them keeps the global + // analyzer files authoritative for default severities; otherwise rules + // relaxed only in tests/generated paths would disappear from the global file. + var pathScopedConfigs = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "TestProjects.editorconfig", + "GeneratedFiles.editorconfig", + }; + foreach (var editorconfig in Directory.EnumerateFiles(configRoot, "*.editorconfig", SearchOption.AllDirectories)) { if (string.Equals(Path.GetFullPath(editorconfig), Path.GetFullPath(configurationFilePath.Value), StringComparison.OrdinalIgnoreCase)) continue; + if (pathScopedConfigs.Contains(Path.GetFileName(editorconfig))) + continue; + foreach (var rule in GetConfiguration(FullPath.FromPath(editorconfig)).Rules) configuredRuleIds.Add(rule.Id); } From e3807c97b3d2bda19c43cfbcb716beb2b6692679 Mon Sep 17 00:00:00 2001 From: ancplua Date: Tue, 12 May 2026 23:56:39 +0200 Subject: [PATCH 2/7] fix(dogfood): exclude TestProjects.editorconfig from dogfood EditorConfigFiles CS8700 ("Multiple analyzer config files cannot be in the same directory") fires because src/Config/ now has two non-global (path-scoped) editorconfigs: the pre-existing GeneratedFiles.editorconfig and the new TestProjects.editorconfig. Common.props already excludes TestProjects.editorconfig from the consumer-side wildcard and conditionally re-includes it (gated on DisableSdkTestEditorConfig). The dogfood pattern in this repo's Directory.Build.props was missed in the prior commit, so both the lint_config job (tools/ConfigFilesGenerator) and the SDK's own test build hit CS8700. Mirror the same exclude here. The dogfood doesn't need TestProjects.editorconfig active: tools/ isn't a test project and the SDK's own tests use Microsoft.NET.Sdk directly without the test-folder relaxations. Co-Authored-By: Claude Opus 4.7 (1M context) --- Directory.Build.props | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 3fa7e27..7a3d665 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -43,7 +43,16 @@ + + Exclude="$(MSBuildThisFileDirectory)src\Config\ANcpLua.NET.Sdk*.editorconfig;$(MSBuildThisFileDirectory)src\Config\TestProjects.editorconfig" /> From ba0468ddd3aa85090267141812a5ae41e428f23d Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 22:04:26 +0000 Subject: [PATCH 3/7] fix: apply CodeRabbit auto-fixes Fixed 3 file(s) based on 3 unresolved review comments. Co-authored-by: CodeRabbit --- ...oft.CodeAnalysis.NetAnalyzers.editorconfig | 5 +-- tests/ANcpLua.Sdk.Tests/SdkTests.cs | 6 ++-- tools/ConfigFilesGenerator/Program.cs | 33 ++++++++++++++++--- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/Config/Analyzer.Microsoft.CodeAnalysis.NetAnalyzers.editorconfig b/src/Config/Analyzer.Microsoft.CodeAnalysis.NetAnalyzers.editorconfig index 7168dbf..93d2b9d 100644 --- a/src/Config/Analyzer.Microsoft.CodeAnalysis.NetAnalyzers.editorconfig +++ b/src/Config/Analyzer.Microsoft.CodeAnalysis.NetAnalyzers.editorconfig @@ -1573,10 +1573,7 @@ dotnet_diagnostic.CA5393.severity = warning # CA5394: Do not use insecure randomness # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5394 # Enabled: False, Severity: warning -# Demoted: high false-positive rate. Random is correct for shuffling, jitter, -# test data, sampling — non-crypto use cases. Real crypto sites should use -# RandomNumberGenerator and that's a separate rule (CA5390/5391). -dotnet_diagnostic.CA5394.severity = suggestion +dotnet_diagnostic.CA5394.severity = warning # CA5395: Miss HttpVerb attribute for action methods # Help link: https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca5395 diff --git a/tests/ANcpLua.Sdk.Tests/SdkTests.cs b/tests/ANcpLua.Sdk.Tests/SdkTests.cs index da4cb69..452099d 100644 --- a/tests/ANcpLua.Sdk.Tests/SdkTests.cs +++ b/tests/ANcpLua.Sdk.Tests/SdkTests.cs @@ -883,8 +883,8 @@ class Sample .BuildAsync(["--configuration", "Release"]); // IDE1006 must not be promoted to error or warning under tests/. - Assert.False(result.HasError("IDE1006")); - Assert.False(result.HasWarning("IDE1006")); + result.HasError("IDE1006").Should().BeFalse(); + result.HasWarning("IDE1006").Should().BeFalse(); } [Fact] @@ -912,7 +912,7 @@ class Sample """) .BuildAsync(["--configuration", "Release"]); - Assert.True(result.HasWarning("IDE1006")); + result.HasWarning("IDE1006").Should().BeTrue(); } [Fact] diff --git a/tools/ConfigFilesGenerator/Program.cs b/tools/ConfigFilesGenerator/Program.cs index e62d411..795af22 100644 --- a/tools/ConfigFilesGenerator/Program.cs +++ b/tools/ConfigFilesGenerator/Program.cs @@ -149,11 +149,12 @@ HashSet GetRuleIdsConfiguredOutside(FullPath configurationFilePath) // their [glob] sections, not project-wide. Skipping them keeps the global // analyzer files authoritative for default severities; otherwise rules // relaxed only in tests/generated paths would disappear from the global file. - var pathScopedConfigs = new HashSet(StringComparer.OrdinalIgnoreCase) + var pathScopedConfigs = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var editorconfig in Directory.EnumerateFiles(configRoot, "*.editorconfig", SearchOption.AllDirectories)) { - "TestProjects.editorconfig", - "GeneratedFiles.editorconfig", - }; + if (IsPathScopedEditorConfig(editorconfig)) + pathScopedConfigs.Add(Path.GetFileName(editorconfig)); + } foreach (var editorconfig in Directory.EnumerateFiles(configRoot, "*.editorconfig", SearchOption.AllDirectories)) { @@ -171,6 +172,30 @@ HashSet GetRuleIdsConfiguredOutside(FullPath configurationFilePath) return configuredRuleIds; } +static bool IsPathScopedEditorConfig(string filePath) +{ + if (!File.Exists(filePath)) + return false; + + // An editorconfig is path-scoped if it lacks `is_global = true`. + // Global editorconfigs apply project-wide; path-scoped ones only + // apply within their [glob] sections. + var lines = File.ReadAllLines(filePath); + foreach (var line in lines) + { + var trimmed = line.Trim(); + if (trimmed.StartsWith("is_global", StringComparison.OrdinalIgnoreCase)) + { + // Check if the value is "true" + var parts = trimmed.Split('=', 2, StringSplitOptions.TrimEntries); + if (parts.Length == 2 && string.Equals(parts[1], "true", StringComparison.OrdinalIgnoreCase)) + return false; // It's a global editorconfig + } + } + + return true; // No `is_global = true` found, so it's path-scoped +} + static async Task GetCompilerAnalyzerReferences() { var sdkPath = await GetDotNetSdkPath().ConfigureAwait(false); From 3cb6fbf2dad0a906fd2375ab615ee1ff4baebd13 Mon Sep 17 00:00:00 2001 From: ancplua Date: Wed, 13 May 2026 00:46:51 +0200 Subject: [PATCH 4/7] revert(sdk): drop TestProjects.editorconfig path-scoped relaxations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The path-scoped editorconfig at Config/TestProjects.editorconfig cannot reach consumer source files. Roslyn evaluates non-global editorconfig [glob] sections relative to the editorconfig file's containing directory, and consumer source paths under e.g. /Users/.../Paperless/PaperlessServices.Tests/ are not descendants of ~/.nuget/packages/ancplua.net.sdk/X.Y.Z/Config/. The two new tests appeared to pass locally only because CS8700 ("Multiple analyzer config files cannot be in the same directory") aborted the build before IDE1006 could fire — a false positive. Keep the global analyzer demotions (14 CA + 11 AL rules going warning → suggestion) and the ConfigFilesGenerator path-scoped editorconfig detection: those work as intended and apply project-wide. Test-folder relaxations need a different mechanism (e.g. NoWarn property injection in ANcpLua.NET.Sdk.Test targets, conditional on $(MSBuildProjectName.EndsWith('Tests')) patterns) and belong in a follow-up PR with its own dedicated design. Removes: - src/Config/TestProjects.editorconfig (the file) - TestProjects.editorconfig references in 3 .nuspec files - Conditional EditorConfigFiles include in Common.props - TestProjects.editorconfig exclude in dogfood Directory.Build.props - SdkTests.TestProjects_EditorConfig_RelaxesIDE1006InTestPath - SdkTests.TestProjects_EditorConfig_DisableOptOutWorks 235/235 tests pass locally against packed 99.99.99. Co-Authored-By: Claude Opus 4.7 (1M context) --- Directory.Build.props | 11 +- src/ANcpLua.NET.Sdk.Test.nuspec | 1 - src/ANcpLua.NET.Sdk.Web.nuspec | 1 - src/ANcpLua.NET.Sdk.nuspec | 1 - src/Build/Common/Common.props | 12 +- src/Config/TestProjects.editorconfig | 226 --------------------------- tests/ANcpLua.Sdk.Tests/SdkTests.cs | 57 ------- 7 files changed, 3 insertions(+), 306 deletions(-) delete mode 100644 src/Config/TestProjects.editorconfig diff --git a/Directory.Build.props b/Directory.Build.props index 7a3d665..3fa7e27 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -43,16 +43,7 @@ - + Exclude="$(MSBuildThisFileDirectory)src\Config\ANcpLua.NET.Sdk*.editorconfig" /> diff --git a/src/ANcpLua.NET.Sdk.Test.nuspec b/src/ANcpLua.NET.Sdk.Test.nuspec index 083258e..e5d9916 100644 --- a/src/ANcpLua.NET.Sdk.Test.nuspec +++ b/src/ANcpLua.NET.Sdk.Test.nuspec @@ -47,7 +47,6 @@ - diff --git a/src/ANcpLua.NET.Sdk.Web.nuspec b/src/ANcpLua.NET.Sdk.Web.nuspec index 855b75a..17b054f 100644 --- a/src/ANcpLua.NET.Sdk.Web.nuspec +++ b/src/ANcpLua.NET.Sdk.Web.nuspec @@ -47,7 +47,6 @@ - diff --git a/src/ANcpLua.NET.Sdk.nuspec b/src/ANcpLua.NET.Sdk.nuspec index d1cf581..e6d2722 100644 --- a/src/ANcpLua.NET.Sdk.nuspec +++ b/src/ANcpLua.NET.Sdk.nuspec @@ -47,7 +47,6 @@ - diff --git a/src/Build/Common/Common.props b/src/Build/Common/Common.props index 138ebf7..1884e0f 100644 --- a/src/Build/Common/Common.props +++ b/src/Build/Common/Common.props @@ -72,15 +72,10 @@ + the project cone render at root by default; Link is the documented container. --> - diff --git a/src/Config/TestProjects.editorconfig b/src/Config/TestProjects.editorconfig deleted file mode 100644 index 6121886..0000000 --- a/src/Config/TestProjects.editorconfig +++ /dev/null @@ -1,226 +0,0 @@ -# SDK-injected test-folder relaxations -# ============================================================================= -# Auto-injected by Common.props alongside other Config/*.editorconfig files. -# This is a non-global editorconfig (no `is_global = true`) because it uses -# path globs — only path-scoped editorconfigs can carry [glob] sections. -# -# Path coverage strategy: -# - `tests/` — Microsoft solution convention (Sdk's tests/, qyl, ANcpLua.*) -# - `test/` — older /test/ convention (OpenTelemetry, some OSS projects) -# - `**/*.Tests/` — Visual Studio "Add new test project" convention (Paperless) -# - `**/*.UnitTests/` — explicit unit-test split -# - `**/*.IntegrationTests/` — explicit integration-test split -# - `**/examples/` — sample code adopts the same scaffolding shortcuts as tests -# -# Brace-expansion mixed with `**` is fragile across EditorConfig parsers, so we -# repeat the same severity table across explicit [glob] sections. Editing rules: -# update ALL sections together. Keep them byte-identical. -# -# Consumers can override any rule per-project via their own .editorconfig. -# To opt out of test relaxations entirely, set in your Directory.Build.props: -# true -# (gated in Common.props on the same flag). -# ============================================================================= - -# --- tests/ (Microsoft solution convention) --- -[**/tests/**/*.cs] - -# CA1707: Identifiers should not contain underscores -# Test method names like `Method_When_Scenario_Then_Expectation` are standard. -dotnet_diagnostic.CA1707.severity = none - -# CA1816: Dispose methods should call SuppressFinalize -# Test fixtures and fakes commonly implement IDisposable as a no-op or pass-through. -dotnet_diagnostic.CA1816.severity = none - -# CA2000: Dispose objects before losing scope -# Test code intentionally leaks short-lived resources for assertion clarity; the -# test runner tears the process down. -dotnet_diagnostic.CA2000.severity = none - -# CA2007: Consider calling ConfigureAwait on the awaited task -# Test hosts have no SynchronizationContext; ConfigureAwait noise hurts readability. -dotnet_diagnostic.CA2007.severity = none - -# CA2201: Do not raise reserved exception types -# Tests intentionally `throw new Exception("boom")` to drive failure paths in fakes. -dotnet_diagnostic.CA2201.severity = none - -# CA1031: Do not catch general exception types -# Test scaffolding (assertion frameworks, retry harnesses) legitimately swallows -# Exception to assert failure modes or wrap error messages. -dotnet_diagnostic.CA1031.severity = none - -# CA1812: Avoid uninstantiated internal classes -# Test data classes are loaded reflectively by [Theory]/[ClassData]. -dotnet_diagnostic.CA1812.severity = none - -# CA1822: Mark members as static -# Test method instances are required by xUnit/NUnit; static methods can't be tests. -dotnet_diagnostic.CA1822.severity = none - -# CA1859: Use concrete types when possible for improved performance -# Tests use interface-typed locals to verify polymorphism / DI contracts. -dotnet_diagnostic.CA1859.severity = none - -# CA1852: Seal internal types -# Test fixtures and helpers are routinely inherited by per-suite specializations. -dotnet_diagnostic.CA1852.severity = none - -# CA5394: Do not use insecure randomness -# Tests use Random for sample data; that's non-crypto by definition. -dotnet_diagnostic.CA5394.severity = none - -# VSTHRD111: Use ConfigureAwait(bool) — no SynchronizationContext in test hosts. -dotnet_diagnostic.VSTHRD111.severity = none - -# VSTHRD200: Use Async naming convention — test method names like -# `ReadAsync_OnEmpty_Throws` are not async-naming targets. -dotnet_diagnostic.VSTHRD200.severity = suggestion - -# IDE0051 / IDE0052: Unused private members -# [Theory]/[ClassData] generators look unused; xUnit reads them by name. -dotnet_diagnostic.IDE0051.severity = suggestion -dotnet_diagnostic.IDE0052.severity = suggestion - -# IDE1006: Naming rule violation — test methods use snake_case intentionally. -dotnet_diagnostic.IDE1006.severity = suggestion - -# AL0025: Anonymous function can be made static — tests capture `this` via fixture. -dotnet_diagnostic.AL0025.severity = none - -# AL0137: Use Guard.* helpers — test setup uses BCL ArgumentNullException.ThrowIfNull. -dotnet_diagnostic.AL0137.severity = none - -# AL0057: Avoid async void methods — required for some xUnit/NUnit harnesses. -dotnet_diagnostic.AL0057.severity = none - -# AL0105: Avoid blocking calls in async methods — tests use .Result for sync assertions. -dotnet_diagnostic.AL0105.severity = suggestion - -# AL0112: Avoid fire-and-forget task discard — background-task tests discard deliberately. -dotnet_diagnostic.AL0112.severity = suggestion - -# RS0030: Do not use banned APIs — tests use DateTime.UtcNow for assertions. -dotnet_diagnostic.RS0030.severity = suggestion - -# xUnit1004: Test methods should not be skipped — visible at suggestion is enough. -dotnet_diagnostic.xUnit1004.severity = suggestion - -# --- test/ (legacy convention) --- -[**/test/**/*.cs] -dotnet_diagnostic.CA1707.severity = none -dotnet_diagnostic.CA1816.severity = none -dotnet_diagnostic.CA2000.severity = none -dotnet_diagnostic.CA2007.severity = none -dotnet_diagnostic.CA2201.severity = none -dotnet_diagnostic.CA1031.severity = none -dotnet_diagnostic.CA1812.severity = none -dotnet_diagnostic.CA1822.severity = none -dotnet_diagnostic.CA1859.severity = none -dotnet_diagnostic.CA1852.severity = none -dotnet_diagnostic.CA5394.severity = none -dotnet_diagnostic.VSTHRD111.severity = none -dotnet_diagnostic.VSTHRD200.severity = suggestion -dotnet_diagnostic.IDE0051.severity = suggestion -dotnet_diagnostic.IDE0052.severity = suggestion -dotnet_diagnostic.IDE1006.severity = suggestion -dotnet_diagnostic.AL0025.severity = none -dotnet_diagnostic.AL0137.severity = none -dotnet_diagnostic.AL0057.severity = none -dotnet_diagnostic.AL0105.severity = suggestion -dotnet_diagnostic.AL0112.severity = suggestion -dotnet_diagnostic.RS0030.severity = suggestion -dotnet_diagnostic.xUnit1004.severity = suggestion - -# --- *.Tests/ (VS new-project convention: PaperlessREST.Tests, MyLib.Tests, ...) --- -[**/*.Tests/**/*.cs] -dotnet_diagnostic.CA1707.severity = none -dotnet_diagnostic.CA1816.severity = none -dotnet_diagnostic.CA2000.severity = none -dotnet_diagnostic.CA2007.severity = none -dotnet_diagnostic.CA2201.severity = none -dotnet_diagnostic.CA1031.severity = none -dotnet_diagnostic.CA1812.severity = none -dotnet_diagnostic.CA1822.severity = none -dotnet_diagnostic.CA1859.severity = none -dotnet_diagnostic.CA1852.severity = none -dotnet_diagnostic.CA5394.severity = none -dotnet_diagnostic.VSTHRD111.severity = none -dotnet_diagnostic.VSTHRD200.severity = suggestion -dotnet_diagnostic.IDE0051.severity = suggestion -dotnet_diagnostic.IDE0052.severity = suggestion -dotnet_diagnostic.IDE1006.severity = suggestion -dotnet_diagnostic.AL0025.severity = none -dotnet_diagnostic.AL0137.severity = none -dotnet_diagnostic.AL0057.severity = none -dotnet_diagnostic.AL0105.severity = suggestion -dotnet_diagnostic.AL0112.severity = suggestion -dotnet_diagnostic.RS0030.severity = suggestion -dotnet_diagnostic.xUnit1004.severity = suggestion - -# --- *.UnitTests/ --- -[**/*.UnitTests/**/*.cs] -dotnet_diagnostic.CA1707.severity = none -dotnet_diagnostic.CA1816.severity = none -dotnet_diagnostic.CA2000.severity = none -dotnet_diagnostic.CA2007.severity = none -dotnet_diagnostic.CA2201.severity = none -dotnet_diagnostic.CA1031.severity = none -dotnet_diagnostic.CA1812.severity = none -dotnet_diagnostic.CA1822.severity = none -dotnet_diagnostic.CA1859.severity = none -dotnet_diagnostic.CA1852.severity = none -dotnet_diagnostic.CA5394.severity = none -dotnet_diagnostic.VSTHRD111.severity = none -dotnet_diagnostic.VSTHRD200.severity = suggestion -dotnet_diagnostic.IDE0051.severity = suggestion -dotnet_diagnostic.IDE0052.severity = suggestion -dotnet_diagnostic.IDE1006.severity = suggestion -dotnet_diagnostic.AL0025.severity = none -dotnet_diagnostic.AL0137.severity = none -dotnet_diagnostic.AL0057.severity = none -dotnet_diagnostic.AL0105.severity = suggestion -dotnet_diagnostic.AL0112.severity = suggestion -dotnet_diagnostic.RS0030.severity = suggestion -dotnet_diagnostic.xUnit1004.severity = suggestion - -# --- *.IntegrationTests/ --- -[**/*.IntegrationTests/**/*.cs] -dotnet_diagnostic.CA1707.severity = none -dotnet_diagnostic.CA1816.severity = none -dotnet_diagnostic.CA2000.severity = none -dotnet_diagnostic.CA2007.severity = none -dotnet_diagnostic.CA2201.severity = none -dotnet_diagnostic.CA1031.severity = none -dotnet_diagnostic.CA1812.severity = none -dotnet_diagnostic.CA1822.severity = none -dotnet_diagnostic.CA1859.severity = none -dotnet_diagnostic.CA1852.severity = none -dotnet_diagnostic.CA5394.severity = none -dotnet_diagnostic.VSTHRD111.severity = none -dotnet_diagnostic.VSTHRD200.severity = suggestion -dotnet_diagnostic.IDE0051.severity = suggestion -dotnet_diagnostic.IDE0052.severity = suggestion -dotnet_diagnostic.IDE1006.severity = suggestion -dotnet_diagnostic.AL0025.severity = none -dotnet_diagnostic.AL0137.severity = none -dotnet_diagnostic.AL0057.severity = none -dotnet_diagnostic.AL0105.severity = suggestion -dotnet_diagnostic.AL0112.severity = suggestion -dotnet_diagnostic.RS0030.severity = suggestion -dotnet_diagnostic.xUnit1004.severity = suggestion - -# --- examples/ --- -[**/examples/**/*.cs] -dotnet_diagnostic.CA1816.severity = none -dotnet_diagnostic.CA2000.severity = none -dotnet_diagnostic.CA2007.severity = none -dotnet_diagnostic.CA1031.severity = none -dotnet_diagnostic.CA1822.severity = none -dotnet_diagnostic.VSTHRD111.severity = none -dotnet_diagnostic.IDE1006.severity = suggestion -dotnet_diagnostic.AL0025.severity = none -dotnet_diagnostic.AL0105.severity = suggestion -dotnet_diagnostic.AL0112.severity = suggestion -dotnet_diagnostic.RS0030.severity = suggestion diff --git a/tests/ANcpLua.Sdk.Tests/SdkTests.cs b/tests/ANcpLua.Sdk.Tests/SdkTests.cs index 452099d..4c32655 100644 --- a/tests/ANcpLua.Sdk.Tests/SdkTests.cs +++ b/tests/ANcpLua.Sdk.Tests/SdkTests.cs @@ -858,63 +858,6 @@ public class Tests static i => i.Contains("GitHubActionsTestLogger", StringComparison.OrdinalIgnoreCase)); } - [Fact] - public async Task TestProjects_EditorConfig_RelaxesIDE1006InTestPath() - { - // TestProjects.editorconfig drops IDE1006 (naming) from warning to suggestion - // for files under tests/, test/, **/*.Tests/, **/*.UnitTests/, **/*.IntegrationTests/. - // Without that relaxation, the lowercase `field` would fire IDE1006 as error - // in Release (TreatWarningsAsErrors=true). - await using var project = CreateProject(); - - var result = await project - .AddSource("tests/SampleTests.cs", """ - _ = ""; - - class Sample - { - private readonly int field; - - public Sample(int a) => field = a; - - public int A() => field; - } - """) - .BuildAsync(["--configuration", "Release"]); - - // IDE1006 must not be promoted to error or warning under tests/. - result.HasError("IDE1006").Should().BeFalse(); - result.HasWarning("IDE1006").Should().BeFalse(); - } - - [Fact] - public async Task TestProjects_EditorConfig_DisableOptOutWorks() - { - // Consumers can opt out of the test-relaxation editorconfig entirely by setting - // DisableSdkTestEditorConfig=true. After opting out, IDE1006 fires again in tests - // (the global CodingStyle.editorconfig has it at warning + Release promotes to error). - await using var project = CreateProject(); - - var result = await project - .WithProperty("DisableSdkTestEditorConfig", "true") - .WithProperty("TreatWarningsAsErrors", "false") - .AddSource("tests/SampleTests.cs", """ - _ = ""; - - class Sample - { - private readonly int field; - - public Sample(int a) => field = a; - - public int A() => field; - } - """) - .BuildAsync(["--configuration", "Release"]); - - result.HasWarning("IDE1006").Should().BeTrue(); - } - [Fact] public async Task CentralPackageManagement() { From 8fe9aaf11ecae966846149ba51b224c9928d03bb Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 22:52:27 +0000 Subject: [PATCH 5/7] fix: apply CodeRabbit auto-fixes Fixed 1 file(s) based on 1 unresolved review comment. Co-authored-by: CodeRabbit --- tools/ConfigFilesGenerator/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ConfigFilesGenerator/Program.cs b/tools/ConfigFilesGenerator/Program.cs index 795af22..51cb6fc 100644 --- a/tools/ConfigFilesGenerator/Program.cs +++ b/tools/ConfigFilesGenerator/Program.cs @@ -153,7 +153,7 @@ HashSet GetRuleIdsConfiguredOutside(FullPath configurationFilePath) foreach (var editorconfig in Directory.EnumerateFiles(configRoot, "*.editorconfig", SearchOption.AllDirectories)) { if (IsPathScopedEditorConfig(editorconfig)) - pathScopedConfigs.Add(Path.GetFileName(editorconfig)); + pathScopedConfigs.Add(Path.GetFullPath(editorconfig)); } foreach (var editorconfig in Directory.EnumerateFiles(configRoot, "*.editorconfig", SearchOption.AllDirectories)) @@ -162,7 +162,7 @@ HashSet GetRuleIdsConfiguredOutside(FullPath configurationFilePath) StringComparison.OrdinalIgnoreCase)) continue; - if (pathScopedConfigs.Contains(Path.GetFileName(editorconfig))) + if (pathScopedConfigs.Contains(Path.GetFullPath(editorconfig))) continue; foreach (var rule in GetConfiguration(FullPath.FromPath(editorconfig)).Rules) From d0a01063aa49e71abaf10cd4d0f203572f8f7413 Mon Sep 17 00:00:00 2001 From: ancplua Date: Wed, 13 May 2026 01:11:32 +0200 Subject: [PATCH 6/7] refactor(tools): type path-scoped editorconfig dedup as FullPath Per tools/**/*.cs coding rule (Meziantou.Framework.FullPath for path handling), replace the string-keyed Path.GetFullPath HashSet with a FullPath-keyed set. Also folds the two enumerations into a single materialized array; equality is built-in (FullPath is comparable). Co-Authored-By: Claude Opus 4.7 (1M context) --- tools/ConfigFilesGenerator/Program.cs | 39 ++++++++++++--------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/tools/ConfigFilesGenerator/Program.cs b/tools/ConfigFilesGenerator/Program.cs index 51cb6fc..e0d180b 100644 --- a/tools/ConfigFilesGenerator/Program.cs +++ b/tools/ConfigFilesGenerator/Program.cs @@ -149,30 +149,27 @@ HashSet GetRuleIdsConfiguredOutside(FullPath configurationFilePath) // their [glob] sections, not project-wide. Skipping them keeps the global // analyzer files authoritative for default severities; otherwise rules // relaxed only in tests/generated paths would disappear from the global file. - var pathScopedConfigs = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var editorconfig in Directory.EnumerateFiles(configRoot, "*.editorconfig", SearchOption.AllDirectories)) - { - if (IsPathScopedEditorConfig(editorconfig)) - pathScopedConfigs.Add(Path.GetFullPath(editorconfig)); - } + var allEditorConfigs = Directory.EnumerateFiles(configRoot, "*.editorconfig", SearchOption.AllDirectories) + .Select(FullPath.FromPath) + .ToArray(); + var pathScopedConfigs = allEditorConfigs.Where(IsPathScopedEditorConfig).ToHashSet(); - foreach (var editorconfig in Directory.EnumerateFiles(configRoot, "*.editorconfig", SearchOption.AllDirectories)) + foreach (var editorconfig in allEditorConfigs) { - if (string.Equals(Path.GetFullPath(editorconfig), Path.GetFullPath(configurationFilePath.Value), - StringComparison.OrdinalIgnoreCase)) + if (editorconfig == configurationFilePath) continue; - if (pathScopedConfigs.Contains(Path.GetFullPath(editorconfig))) + if (pathScopedConfigs.Contains(editorconfig)) continue; - foreach (var rule in GetConfiguration(FullPath.FromPath(editorconfig)).Rules) + foreach (var rule in GetConfiguration(editorconfig).Rules) configuredRuleIds.Add(rule.Id); } return configuredRuleIds; } -static bool IsPathScopedEditorConfig(string filePath) +static bool IsPathScopedEditorConfig(FullPath filePath) { if (!File.Exists(filePath)) return false; @@ -180,20 +177,18 @@ static bool IsPathScopedEditorConfig(string filePath) // An editorconfig is path-scoped if it lacks `is_global = true`. // Global editorconfigs apply project-wide; path-scoped ones only // apply within their [glob] sections. - var lines = File.ReadAllLines(filePath); - foreach (var line in lines) + foreach (var line in File.ReadLines(filePath)) { var trimmed = line.Trim(); - if (trimmed.StartsWith("is_global", StringComparison.OrdinalIgnoreCase)) - { - // Check if the value is "true" - var parts = trimmed.Split('=', 2, StringSplitOptions.TrimEntries); - if (parts.Length == 2 && string.Equals(parts[1], "true", StringComparison.OrdinalIgnoreCase)) - return false; // It's a global editorconfig - } + if (!trimmed.StartsWith("is_global", StringComparison.OrdinalIgnoreCase)) + continue; + + var parts = trimmed.Split('=', 2, StringSplitOptions.TrimEntries); + if (parts.Length == 2 && string.Equals(parts[1], "true", StringComparison.OrdinalIgnoreCase)) + return false; } - return true; // No `is_global = true` found, so it's path-scoped + return true; } static async Task GetCompilerAnalyzerReferences() From 36e7fc2b9aabb30e5447d7c6fd12c5aa757df401 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 23:14:34 +0000 Subject: [PATCH 7/7] fix: apply CodeRabbit auto-fixes Fixed 1 file(s) based on 1 unresolved review comment. Co-authored-by: CodeRabbit --- tools/ConfigFilesGenerator/Program.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/ConfigFilesGenerator/Program.cs b/tools/ConfigFilesGenerator/Program.cs index e0d180b..ac03059 100644 --- a/tools/ConfigFilesGenerator/Program.cs +++ b/tools/ConfigFilesGenerator/Program.cs @@ -180,11 +180,21 @@ static bool IsPathScopedEditorConfig(FullPath filePath) foreach (var line in File.ReadLines(filePath)) { var trimmed = line.Trim(); - if (!trimmed.StartsWith("is_global", StringComparison.OrdinalIgnoreCase)) + + // Ignore section headers + if (trimmed.StartsWith("[", StringComparison.Ordinal)) continue; var parts = trimmed.Split('=', 2, StringSplitOptions.TrimEntries); - if (parts.Length == 2 && string.Equals(parts[1], "true", StringComparison.OrdinalIgnoreCase)) + if (parts.Length != 2) + continue; + + // Check for exact key match to "is_global" + if (!string.Equals(parts[0], "is_global", StringComparison.OrdinalIgnoreCase)) + continue; + + // Only return false if value is "true" + if (string.Equals(parts[1], "true", StringComparison.OrdinalIgnoreCase)) return false; }