From 49c7160cd31e9387e0ee7c64fd078ea4a2b7d66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 10:13:46 +0200 Subject: [PATCH 01/19] Add review file for c# 8 #122 --- CSHARP8-IMPACT-REVIEW.md | 235 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 CSHARP8-IMPACT-REVIEW.md diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md new file mode 100644 index 000000000..908ab56b1 --- /dev/null +++ b/CSHARP8-IMPACT-REVIEW.md @@ -0,0 +1,235 @@ +# C# 8 impact review + +Source: [C# version 8.0](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-80) + +Every C# 8 language feature, the StyleCop rules it touches, and what the repo already has. Findings come from reading +the analyzer implementations and the existing `StyleCop.Analyzers.Test.CSharp8` tests, not just the language spec, and +are grounded with file/line references. + +New tests belong in `StyleCop.Analyzers.Test.CSharp8` (the lowest project whose language version can express the +syntax, per `CLAUDE.md`), written as `public partial class SA####CSharp8UnitTests` — the derived-test generator +supplies the other half of the partial class, and the test then re-runs in CSharp9..15 automatically. + +The goal is a regression test for every related rule, including the ones that turn out to need no code change; a test +that pins current correct behaviour is the point, not a formality. + +Work through the numbered items one at a time. Delete an item from this file once its tests are merged. Items are +ordered by priority, and each states its own; priority reflects how likely the rule is to behave wrongly today, not +how much typing the test needs. Each item links the language documentation the source page points at for that +feature. + +## What already exists + +30 hand-written files in `StyleCop.Analyzers.Test.CSharp8`, covering: + +| Feature | Rules with C# 8 tests today | +|---|---| +| Pattern matching (switch expressions, property/tuple/positional patterns) | SA1008, SA1012, SA1013, SA1024, SA1101, SA1119, SA1122, SA1413 | +| Nullable reference types (`?`, `!`) | SA1002, SA1003, SA1009, SA1011, SA1013, SA1019, SA1135, SA1514, `SymbolNameHelpers` | +| Indices and ranges | SA1003, SA1008, SA1009, SA1011, SA1119 | +| Using declarations | SA1106, SA1503 | +| Default interface members | SA1202, SA1648 | +| Unmanaged constructed types | SA1015, SA1023 | +| Null-coalescing assignment | SA1003 | +| `stackalloc` in nested expressions | SA1119 | +| Lightup wrappers | `SwitchExpressionSyntaxWrapper`, `SwitchExpressionArmSyntaxWrapper`, `CommonForEachStatementSyntaxWrapper` | + +Three files exist but add no C# 8 test of their own: `SA1600CSharp8UnitTests.cs` (only sets +`LanguageVersion.Default`), `SA1313CSharp8UnitTests.cs` and `SA1134CSharp8UnitTests.cs` (both only adjust expected +compiler diagnostics for the newer parser). + +Lightup coverage is complete for the C# 8 syntax kinds — `SwitchExpression`, `SwitchExpressionArm`, +`RecursivePattern`, `PropertyPatternClause`, `Subpattern`, `PositionalPatternClause`, `RangeExpression`, +`IndexExpression`, `CoalesceAssignmentExpression`, `SuppressNullableWarningExpression`, `NullableDirectiveTrivia`, +`DotDotToken` and `QuestionQuestionEqualsToken` are all in `SyntaxKindEx`. No new lightup work is expected. + +## Items + +### 1. Switch expressions — SA1000 is unverified, layout rules untested + +**Priority:** High. **Suspected code gap:** SA1000. **Docs:** [Pattern matching enhancements](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns) + +`SA1119` and `SA1413` are well covered (11 and 1 tests). The gap is everything around the *layout* of a switch +expression, plus the keyword itself. + +`SA1000KeywordsMustBeSpacedCorrectly` routes `SyntaxKind.SwitchKeyword` to `HandleRequiredSpaceToken` +(`StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1000KeywordsMustBeSpacedCorrectly.cs:112-118`). That rule was +written when `switch` could only start a statement, where the keyword is *followed* by `(`. In a switch expression the +keyword is *preceded* by the governing expression and followed by `{`, so `x switch{...}` and `x switch {...}` need a +decision and a test. There is no `SA1000CSharp8UnitTests.cs` at all. + +Also untested: `=>` inside switch arms (`SA1003`), the brace layout of the arm list (`SA1500`, `SA1501`, `SA1505`, +`SA1506`, `SA1508`), arm indentation (`SA1137`) and arms sharing a line (`SA1136`). + +Worth confirming while here: no analyzer references `SyntaxKindEx.SwitchExpressionArm` or `SyntaxKindEx.Subpattern`. +That may be correct — or it may be why the layout rules ignore arms. + +**Proposed:** new `SA1000CSharp8UnitTests` (switch expression keyword spacing), plus `SA1003`, `SA1136`, `SA1137`, +`SA1500`, `SA1501`, `SA1505`, `SA1506` and `SA1508` C# 8 files covering a multi-line switch expression and a +single-line one. + +### 2. Indices and ranges — SA1010 has no C# 8 tests + +**Priority:** High. **Suspected code gap:** none, but the rule is entirely unexercised for this syntax. **Docs:** [Indices and ranges](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#range-operator-) + +`SA1003`, `SA1008`, `SA1009`, `SA1011` and `SA1119` all have range tests. `SA1010OpeningSquareBracketsMustBeSpacedCorrectly` +does not — there is no `SA1010CSharp8UnitTests.cs`, even though CSharp7, CSharp11, CSharp12 and CSharp13 versions of +that file all exist. The rule has grown special cases for index initializers, list patterns and collection expressions +(`.../SpacingRules/SA1010OpeningSquareBracketsMustBeSpacedCorrectly.cs:97,115-128`) but nothing for index-from-end or +range arguments. + +**Proposed:** `SA1010CSharp8UnitTests` covering `x[^1]`, `x[1..2]`, `x[..^1]`, `x [^1]` (diagnostic) and the same +inside a nested expression. + +### 3. Static local functions — SA1206 does not see local functions + +**Priority:** High. **Suspected code gap:** SA1206. **Docs:** [Static local functions](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/local-functions) + +`SA1206DeclarationKeywordsMustFollowOrder` registers a fixed list of declaration kinds +(`.../OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs:49-66`) which does **not** include +`LocalFunctionStatement`. A local function may carry `static`, `async`, `extern` and `unsafe`, so `async static void +Local()` is not flagged today while the equivalent method declaration would be. + +Whether that is a bug or an intentional scope limit is the open question — `SA1206`'s upstream description is +declaration-focused, and local functions have no access modifiers, so only the static/async ordering is at stake. + +Other rules already handle local functions and only need a `static` regression test: `SA1502` (`.../SA1502ElementMustNotBeOnASingleLine.cs`), +`SA1300`, and the parameter-list family `SA1110`–`SA1117`, all of which reference `LocalFunctionStatement` today. + +**Proposed:** decide SA1206's scope, then `SA1206CSharp8UnitTests` (either the fix plus tests, or a test pinning that +local functions are ignored), plus `SA1502` and `SA1300` tests using `static` local functions. + +### 4. Default interface members — SA1400 deliberately skips interfaces + +**Priority:** High. **Suspected code gap:** SA1400. **Docs:** [Default interface members](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface#default-interface-members) + +`SA1400AccessModifierMustBeDeclared` bails out whenever a member's parent is an interface +(`.../MaintainabilityRules/SA1400AccessModifierMustBeDeclared.cs:90,106,122,133,162`). Before C# 8 that was +unambiguously right: interface members could not have access modifiers. From C# 8 they can, and a `private` or +`static private` helper in an interface *must* be explicit. So the rule is silent on a construct it arguably now +covers. + +I'd lean towards "current behaviour is still defensible" — the rule's job is to make implicit access explicit, and +interface members are still implicitly `public` — but it needs a test that states the choice rather than leaving it +undefined. + +Existing coverage is thin: `SA1202CSharp8UnitTests.TestPropertiesOfInterfaceAsync` and +`SA1648CSharp8UnitTests.TestIncorrectMemberInheritDocFromStaticMemberInInterfaceAsync`. `SA1600CSharp8UnitTests.cs` +exists but adds no test. + +**Proposed:** `SA1400CSharp8UnitTests` pinning the interface behaviour, plus tests for `SA1101` (`this.` inside a +default implementation), `SA1201`/`SA1204` (ordering of static, const, field and default-implemented members in an +interface), `SA1502` (single-line default body) and `SA1600`/`SA1601` (documentation of members with bodies). + +### 5. `await foreach` and async streams + +**Priority:** Medium. **Suspected code gap:** none. **Docs:** [Asynchronous streams](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/iteration-statements#await-foreach) + +`SA1316TupleElementNamesShouldUseCorrectCasing` is the only analyzer referencing +`CommonForEachStatementSyntaxWrapper`, and it has no C# 8 test. `SA1000` handles `AwaitKeyword` and `ForEachKeyword` +separately (`SA1000KeywordsMustBeSpacedCorrectly.cs:90,95`) but has never seen them adjacent. + +**Proposed:** `SA1316CSharp8UnitTests` (`await foreach (var (a, b) in ...)` with tuple element names), and +`await foreach` cases in the new `SA1000CSharp8UnitTests`; plus `SA1101` and `SA1503` regressions for the loop body. + +### 6. `await using` and using declarations + +**Priority:** Medium. **Suspected code gap:** none. **Docs:** [Using declarations](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using) + +`SA1503` and `SA1106` already cover the using *declaration* form. Not covered: the `await using` combination, and +`SA1000`'s treatment of `UsingKeyword` in a declaration (`SA1000KeywordsMustBeSpacedCorrectly.cs:113`), where the +keyword is followed by a type or `var` rather than `(`. The source page has no separate entry for asynchronous +disposal; the linked `using` documentation covers `await using` too. + +**Proposed:** `await using` and `using var` cases in `SA1000CSharp8UnitTests`; `SA1002` semicolon test for a using +declaration. + +### 7. Readonly instance members + +**Priority:** Medium. **Suspected code gap:** none. **Docs:** [Readonly members](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#readonly-instance-members) + +No C# 8 tests exist for `readonly` struct members. `SA1206` handles the relevant declaration kinds already, and +classifies `readonly` as an "other" modifier, so `readonly public int Foo()` should be flagged and `public readonly +int Foo()` should not — untested either way. + +`SA1214` orders readonly *fields* and should be unaffected by readonly *members*; that is worth a negative test so a +future change to the modifier handling cannot silently break it. + +**Proposed:** `SA1206CSharp8UnitTests` (shared with item 3), plus `SA1201`/`SA1202`/`SA1204`/`SA1214` regressions on a +struct with readonly members. + +### 8. Positional and property patterns + +**Priority:** Medium. **Suspected code gap:** none. **Docs:** [Pattern matching enhancements](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns) + +Spacing is covered (`SA1008` has three pattern tests, `SA1012`, `SA1013`, `SA1024`, `SA1101`, `SA1122`). The +parameter-list-shaped rules are not: a positional pattern `(int x, int y)` looks like a parameter list to +`SA1111`–`SA1117`, and `SA1141UseTupleSyntax` may or may not have an opinion about tuple patterns. That resemblance is +a hypothesis, not something confirmed against the code — check it before writing the tests. + +**Proposed:** C# 8 files for `SA1111`, `SA1112`, `SA1113`, `SA1115`, `SA1116`, `SA1117` with a multi-line positional +pattern, and an `SA1141` regression on a tuple pattern. + +### 9. `#nullable` directives + +**Priority:** Medium. **Suspected code gap:** none. **Docs:** [Nullable reference types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-reference-types) + +`SyntaxKindEx.NullableDirectiveTrivia` exists but no analyzer references it. Directives are plain trivia to the +layout rules, so the risk is in blank-line and whitespace handling around `#nullable enable` / `#nullable restore`. + +**Proposed:** regressions for `SA1027`/`SA1028` (tabs and trailing whitespace on the directive line), and +`SA1505`/`SA1507`/`SA1516` for a directive sitting between two members. + +### 10. `stackalloc` in nested expressions + +**Priority:** Low. **Suspected code gap:** none. **Docs:** [Stackalloc in nested expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/stackalloc) + +`SA1119CSharp8UnitTests.TestStackAllocExpressionInExpressionAsync` covers the parenthesis rule. `SA1000` routes +`StackAllocKeyword` through `HandleNewOrStackAllocKeywordToken` +(`SA1000KeywordsMustBeSpacedCorrectly.cs:174-176`) and its only test is the C# 6 statement form +(`SA1000UnitTests.cs:327`). + +**Proposed:** nested-expression `stackalloc` cases in `SA1000CSharp8UnitTests`, plus `SA1010`/`SA1011` bracket tests. + +### 11. Null-coalescing assignment + +**Priority:** Low. **Suspected code gap:** none. **Docs:** [Null-coalescing assignment](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/assignment-operator#null-coalescing-assignment) + +`SA1003CSharp8UnitTests.TestNullCoalescingAssignmentOperatorAsync` covers spacing. The parenthesis rules +`SA1407`/`SA1408` have never seen `??=` mixed with arithmetic or conditional operators. + +**Proposed:** `SA1407`/`SA1408` regressions for `a ??= b + c` and `a ??= b && c`. + +### 12. Interpolated verbatim strings (`@$"..."`) + +**Priority:** Low. **Suspected code gap:** none. **Docs:** [Enhancement of interpolated verbatim strings](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated) + +C# 8 allows `@$"` in either order. Nothing in `StyleCop.Analyzers.Test.CSharp8` mentions it. + +**Proposed:** `SA1122` (`@$""` as an empty string), and `SA1009`/`SA1013` for a null-forgiving operator inside an +interpolation hole. + +### 13. Unmanaged constructed types + +**Priority:** Low. **Suspected code gap:** none. **Docs:** [Unmanaged constructed types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint) + +Covered for the pointer-declaration case by `SA1015CSharp8UnitTests.TestGenericTypePointerAsync` and its `SA1023` +counterpart. Untouched: `stackalloc Foo[10]` and array/bracket spacing on constructed unmanaged types. + +**Proposed:** add these to the `SA1000` and `SA1010` C# 8 files rather than new ones. + +### 14. Disposable ref structs — out of scope + +**Priority:** none, nothing to do. **Docs:** [Disposable ref structs](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/ref-struct) + +`ref struct` is C# 7.2 syntax and the C# 8 change is purely semantic: a `ref struct` cannot implement an interface, +so the compiler matches a `Dispose()` method by pattern instead of through `IDisposable`. Nothing in the rule set +looks at that — the only `IDisposable`/`Dispose` references in the analyzers are infrastructure (`JsonWriter.cs`, +`PooledObject`1.cs`, `SeparatedSyntaxListWrapper`1.cs`). + +The `ref struct` declaration itself is not unhandled: `SyntaxKind.RefKeyword` is processed by +`Helpers/ModifierOrderHelper.cs:69`, `SA1000KeywordsMustBeSpacedCorrectly.cs:109` and +`SA1004DocumentationLinesMustBeginWithSingleSpace.cs:111`, so SA1206 and SA1000 do see it. That is C# 7.2 syntax +though, and `Test.CSharp13` already has `ref struct` tests (SA1001, SA1024, SA1127, SA1201, SA1202, SA1600) for the +later change. Since C# 8 alters only what the compiler accepts, not what gets parsed, there is nothing C# 8-specific +to regression-test here. From aa121019c63a0b8067a4293ec48942199beaf087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 10:33:05 +0200 Subject: [PATCH 02/19] Remove 'disposable ref structs' from the review file #122 --- CSHARP8-IMPACT-REVIEW.md | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index 908ab56b1..dfccc980b 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -2,9 +2,12 @@ Source: [C# version 8.0](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-80) -Every C# 8 language feature, the StyleCop rules it touches, and what the repo already has. Findings come from reading -the analyzer implementations and the existing `StyleCop.Analyzers.Test.CSharp8` tests, not just the language spec, and -are grounded with file/line references. +The C# 8 language features with StyleCop work still outstanding, the rules each one touches, and what the repo +already has. Findings come from reading the analyzer implementations and the existing +`StyleCop.Analyzers.Test.CSharp8` tests, not just the language spec, and are grounded with file/line references. + +The review started from all fourteen features on the source page. A feature disappears from this file once its work +is done or once it has been confirmed to need none, so an absent feature has been dealt with, not overlooked. New tests belong in `StyleCop.Analyzers.Test.CSharp8` (the lowest project whose language version can express the syntax, per `CLAUDE.md`), written as `public partial class SA####CSharp8UnitTests` — the derived-test generator @@ -13,12 +16,13 @@ supplies the other half of the partial class, and the test then re-runs in CShar The goal is a regression test for every related rule, including the ones that turn out to need no code change; a test that pins current correct behaviour is the point, not a formality. -Work through the numbered items one at a time. Delete an item from this file once its tests are merged. Items are -ordered by priority, and each states its own; priority reflects how likely the rule is to behave wrongly today, not -how much typing the test needs. Each item links the language documentation the source page points at for that -feature. +Work through the numbered items one at a time. Delete an item from this file once its tests are merged, and update +"Current coverage" below in the same change so the two never disagree. Item numbers are stable — deleting item 6 +leaves a gap rather than renumbering, so "item 6" means the same thing across conversations. Items are ordered by +priority, and each states its own; priority reflects how likely the rule is to behave wrongly today, not how much +typing the test needs. Each item links the language documentation the source page points at for that feature. -## What already exists +## Current coverage 30 hand-written files in `StyleCop.Analyzers.Test.CSharp8`, covering: @@ -217,19 +221,3 @@ Covered for the pointer-declaration case by `SA1015CSharp8UnitTests.TestGenericT counterpart. Untouched: `stackalloc Foo[10]` and array/bracket spacing on constructed unmanaged types. **Proposed:** add these to the `SA1000` and `SA1010` C# 8 files rather than new ones. - -### 14. Disposable ref structs — out of scope - -**Priority:** none, nothing to do. **Docs:** [Disposable ref structs](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/ref-struct) - -`ref struct` is C# 7.2 syntax and the C# 8 change is purely semantic: a `ref struct` cannot implement an interface, -so the compiler matches a `Dispose()` method by pattern instead of through `IDisposable`. Nothing in the rule set -looks at that — the only `IDisposable`/`Dispose` references in the analyzers are infrastructure (`JsonWriter.cs`, -`PooledObject`1.cs`, `SeparatedSyntaxListWrapper`1.cs`). - -The `ref struct` declaration itself is not unhandled: `SyntaxKind.RefKeyword` is processed by -`Helpers/ModifierOrderHelper.cs:69`, `SA1000KeywordsMustBeSpacedCorrectly.cs:109` and -`SA1004DocumentationLinesMustBeginWithSingleSpace.cs:111`, so SA1206 and SA1000 do see it. That is C# 7.2 syntax -though, and `Test.CSharp13` already has `ref struct` tests (SA1001, SA1024, SA1127, SA1201, SA1202, SA1600) for the -later change. Since C# 8 alters only what the compiler accepts, not what gets parsed, there is nothing C# 8-specific -to regression-test here. From bca510f65df0159f7954dedc4d7339cdf1dcdad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 10:53:57 +0200 Subject: [PATCH 03/19] Handle c# 8 'unmanaged constructed types' #122 --- CSHARP8-IMPACT-REVIEW.md | 37 +++--- .../SpacingRules/SA1000CSharp8UnitTests.cs | 96 +++++++++++++++ .../SpacingRules/SA1010CSharp8UnitTests.cs | 109 ++++++++++++++++++ 3 files changed, 220 insertions(+), 22 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index dfccc980b..e1e982cc2 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -24,7 +24,7 @@ typing the test needs. Each item links the language documentation the source pag ## Current coverage -30 hand-written files in `StyleCop.Analyzers.Test.CSharp8`, covering: +32 hand-written files in `StyleCop.Analyzers.Test.CSharp8`, covering: | Feature | Rules with C# 8 tests today | |---|---| @@ -33,7 +33,7 @@ typing the test needs. Each item links the language documentation the source pag | Indices and ranges | SA1003, SA1008, SA1009, SA1011, SA1119 | | Using declarations | SA1106, SA1503 | | Default interface members | SA1202, SA1648 | -| Unmanaged constructed types | SA1015, SA1023 | +| Unmanaged constructed types | SA1000, SA1010, SA1015, SA1023 | | Null-coalescing assignment | SA1003 | | `stackalloc` in nested expressions | SA1119 | | Lightup wrappers | `SwitchExpressionSyntaxWrapper`, `SwitchExpressionArmSyntaxWrapper`, `CommonForEachStatementSyntaxWrapper` | @@ -60,7 +60,8 @@ expression, plus the keyword itself. (`StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1000KeywordsMustBeSpacedCorrectly.cs:112-118`). That rule was written when `switch` could only start a statement, where the keyword is *followed* by `(`. In a switch expression the keyword is *preceded* by the governing expression and followed by `{`, so `x switch{...}` and `x switch {...}` need a -decision and a test. There is no `SA1000CSharp8UnitTests.cs` at all. +decision and a test. `SA1000CSharp8UnitTests.cs` now exists (added for unmanaged constructed types) but covers only `stackalloc` and +`sizeof` on constructed unmanaged types, nothing about switch expressions. Also untested: `=>` inside switch arms (`SA1003`), the brace layout of the arm list (`SA1500`, `SA1501`, `SA1505`, `SA1506`, `SA1508`), arm indentation (`SA1137`) and arms sharing a line (`SA1136`). @@ -68,22 +69,22 @@ Also untested: `=>` inside switch arms (`SA1003`), the brace layout of the arm l Worth confirming while here: no analyzer references `SyntaxKindEx.SwitchExpressionArm` or `SyntaxKindEx.Subpattern`. That may be correct — or it may be why the layout rules ignore arms. -**Proposed:** new `SA1000CSharp8UnitTests` (switch expression keyword spacing), plus `SA1003`, `SA1136`, `SA1137`, +**Proposed:** switch expression keyword spacing added to `SA1000CSharp8UnitTests`, plus `SA1003`, `SA1136`, `SA1137`, `SA1500`, `SA1501`, `SA1505`, `SA1506` and `SA1508` C# 8 files covering a multi-line switch expression and a single-line one. -### 2. Indices and ranges — SA1010 has no C# 8 tests +### 2. Indices and ranges — SA1010 has no range or index tests **Priority:** High. **Suspected code gap:** none, but the rule is entirely unexercised for this syntax. **Docs:** [Indices and ranges](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#range-operator-) `SA1003`, `SA1008`, `SA1009`, `SA1011` and `SA1119` all have range tests. `SA1010OpeningSquareBracketsMustBeSpacedCorrectly` -does not — there is no `SA1010CSharp8UnitTests.cs`, even though CSharp7, CSharp11, CSharp12 and CSharp13 versions of -that file all exist. The rule has grown special cases for index initializers, list patterns and collection expressions -(`.../SpacingRules/SA1010OpeningSquareBracketsMustBeSpacedCorrectly.cs:97,115-128`) but nothing for index-from-end or -range arguments. +does not: `SA1010CSharp8UnitTests.cs` exists (added for unmanaged constructed types) but covers only constructed unmanaged types, and no +test anywhere exercises index-from-end or range arguments. The rule has grown special cases for index initializers, +list patterns and collection expressions +(`.../SpacingRules/SA1010OpeningSquareBracketsMustBeSpacedCorrectly.cs:97,115-128`) but nothing for either of those. -**Proposed:** `SA1010CSharp8UnitTests` covering `x[^1]`, `x[1..2]`, `x[..^1]`, `x [^1]` (diagnostic) and the same -inside a nested expression. +**Proposed:** add to `SA1010CSharp8UnitTests`, covering `x[^1]`, `x[1..2]`, `x[..^1]`, `x [^1]` (diagnostic) and the +same inside a nested expression. ### 3. Static local functions — SA1206 does not see local functions @@ -190,8 +191,9 @@ layout rules, so the risk is in blank-line and whitespace handling around `#null `SA1119CSharp8UnitTests.TestStackAllocExpressionInExpressionAsync` covers the parenthesis rule. `SA1000` routes `StackAllocKeyword` through `HandleNewOrStackAllocKeywordToken` -(`SA1000KeywordsMustBeSpacedCorrectly.cs:174-176`) and its only test is the C# 6 statement form -(`SA1000UnitTests.cs:327`). +(`SA1000KeywordsMustBeSpacedCorrectly.cs:174-176`); it is now tested for the C# 6 statement form +(`SA1000UnitTests.cs:327`) and for a constructed unmanaged type, but not for a `stackalloc` nested inside a +larger expression, which is the C# 8 change. **Proposed:** nested-expression `stackalloc` cases in `SA1000CSharp8UnitTests`, plus `SA1010`/`SA1011` bracket tests. @@ -212,12 +214,3 @@ C# 8 allows `@$"` in either order. Nothing in `StyleCop.Analyzers.Test.CSharp8` **Proposed:** `SA1122` (`@$""` as an empty string), and `SA1009`/`SA1013` for a null-forgiving operator inside an interpolation hole. - -### 13. Unmanaged constructed types - -**Priority:** Low. **Suspected code gap:** none. **Docs:** [Unmanaged constructed types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint) - -Covered for the pointer-declaration case by `SA1015CSharp8UnitTests.TestGenericTypePointerAsync` and its `SA1023` -counterpart. Untouched: `stackalloc Foo[10]` and array/bracket spacing on constructed unmanaged types. - -**Proposed:** add these to the `SA1000` and `SA1010` C# 8 files rather than new ones. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs new file mode 100644 index 000000000..5249102bd --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.SpacingRules.SA1000KeywordsMustBeSpacedCorrectly, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1000CSharp8UnitTests + { + /// + /// Verifies the handling of the stackalloc keyword before a constructed unmanaged type, which C# 8 allows. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStackAllocOfConstructedUnmanagedTypeAsync() + { + var testCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + Foo* data1 = {|#0:stackalloc|}@Foo[3]; + } +} +"; + + var fixedCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + Foo* data1 = stackalloc @Foo[3]; + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("stackalloc", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of the sizeof keyword applied to a constructed unmanaged type, which C# 8 allows. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSizeOfConstructedUnmanagedTypeAsync() + { + var testCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + var size1 = {|#0:sizeof|} (Foo); + } +} +"; + + var fixedCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + var size1 = sizeof(Foo); + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("sizeof", " not", "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs new file mode 100644 index 000000000..291c1e42d --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs @@ -0,0 +1,109 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.SpacingRules.SA1010OpeningSquareBracketsMustBeSpacedCorrectly; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.SpacingRules.SA1010OpeningSquareBracketsMustBeSpacedCorrectly, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1010CSharp8UnitTests + { + /// + /// Verifies the handling of a stackalloc of a constructed unmanaged type, which C# 8 allows. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStackAllocOfConstructedUnmanagedTypeAsync() + { + var testCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + Foo* data1 = stackalloc Foo {|#0:[|}3]; + Foo* data2 = stackalloc Foo{|#1:[|} 3]; + } +} +"; + + var fixedCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod() + { + Foo* data1 = stackalloc Foo[3]; + Foo* data2 = stackalloc Foo[3]; + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of an array of a constructed unmanaged type accessed through a pointer. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestPointerIndexingOfConstructedUnmanagedTypeAsync() + { + var testCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod(Foo* data) + { + var value1 = data {|#0:[|}0].Value; + var value2 = data{|#1:[|} 0].Value; + } +} +"; + + var fixedCode = @"public struct Foo +{ + public T Value; +} + +public class TestClass +{ + public unsafe void TestMethod(Foo* data) + { + var value1 = data[0].Value; + var value2 = data[0].Value; + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} From 8165373103605dde169b31e7999d4d93552c6317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 11:24:54 +0200 Subject: [PATCH 04/19] Handle c# 8 'interpolated verbatim strings' #122 --- CSHARP8-IMPACT-REVIEW.md | 34 ------------------ .../SA1122CSharp8UnitTests.cs | 22 ++++++++++++ .../SpacingRules/SA1009CSharp8UnitTests.cs | 35 +++++++++++++++++++ .../SpacingRules/SA1013CSharp8UnitTests.cs | 31 ++++++++++++++++ 4 files changed, 88 insertions(+), 34 deletions(-) diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index e1e982cc2..dfdfbf593 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -22,31 +22,6 @@ leaves a gap rather than renumbering, so "item 6" means the same thing across co priority, and each states its own; priority reflects how likely the rule is to behave wrongly today, not how much typing the test needs. Each item links the language documentation the source page points at for that feature. -## Current coverage - -32 hand-written files in `StyleCop.Analyzers.Test.CSharp8`, covering: - -| Feature | Rules with C# 8 tests today | -|---|---| -| Pattern matching (switch expressions, property/tuple/positional patterns) | SA1008, SA1012, SA1013, SA1024, SA1101, SA1119, SA1122, SA1413 | -| Nullable reference types (`?`, `!`) | SA1002, SA1003, SA1009, SA1011, SA1013, SA1019, SA1135, SA1514, `SymbolNameHelpers` | -| Indices and ranges | SA1003, SA1008, SA1009, SA1011, SA1119 | -| Using declarations | SA1106, SA1503 | -| Default interface members | SA1202, SA1648 | -| Unmanaged constructed types | SA1000, SA1010, SA1015, SA1023 | -| Null-coalescing assignment | SA1003 | -| `stackalloc` in nested expressions | SA1119 | -| Lightup wrappers | `SwitchExpressionSyntaxWrapper`, `SwitchExpressionArmSyntaxWrapper`, `CommonForEachStatementSyntaxWrapper` | - -Three files exist but add no C# 8 test of their own: `SA1600CSharp8UnitTests.cs` (only sets -`LanguageVersion.Default`), `SA1313CSharp8UnitTests.cs` and `SA1134CSharp8UnitTests.cs` (both only adjust expected -compiler diagnostics for the newer parser). - -Lightup coverage is complete for the C# 8 syntax kinds — `SwitchExpression`, `SwitchExpressionArm`, -`RecursivePattern`, `PropertyPatternClause`, `Subpattern`, `PositionalPatternClause`, `RangeExpression`, -`IndexExpression`, `CoalesceAssignmentExpression`, `SuppressNullableWarningExpression`, `NullableDirectiveTrivia`, -`DotDotToken` and `QuestionQuestionEqualsToken` are all in `SyntaxKindEx`. No new lightup work is expected. - ## Items ### 1. Switch expressions — SA1000 is unverified, layout rules untested @@ -205,12 +180,3 @@ larger expression, which is the C# 8 change. `SA1407`/`SA1408` have never seen `??=` mixed with arithmetic or conditional operators. **Proposed:** `SA1407`/`SA1408` regressions for `a ??= b + c` and `a ??= b && c`. - -### 12. Interpolated verbatim strings (`@$"..."`) - -**Priority:** Low. **Suspected code gap:** none. **Docs:** [Enhancement of interpolated verbatim strings](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated) - -C# 8 allows `@$"` in either order. Nothing in `StyleCop.Analyzers.Test.CSharp8` mentions it. - -**Proposed:** `SA1122` (`@$""` as an empty string), and `SA1009`/`SA1013` for a null-forgiving operator inside an -interpolation hole. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1122CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1122CSharp8UnitTests.cs index 96b5f9e6a..a1d2affa4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1122CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1122CSharp8UnitTests.cs @@ -82,6 +82,28 @@ public bool TestMethod(KeyValuePair condition) return condition is { Key: """" }; } } +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that an empty interpolated verbatim string is not reported. C# 8 allows these strings to be + /// written as @$"..." as well as $@"...". + /// + /// A representing the asynchronous unit test. + // TODO: Should this trigger? + [Fact] + public async Task TestEmptyInterpolatedVerbatimStringAsync() + { + string testCode = @" +public class TestClass +{ + public string TestMethod() + { + return @$""""; + } +} "; await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1009CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1009CSharp8UnitTests.cs index e76c0e5b5..020721498 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1009CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1009CSharp8UnitTests.cs @@ -194,5 +194,40 @@ public string TestMethod() var expected = Diagnostic(DescriptorNotFollowed).WithLocation(0); await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of a closing parenthesis inside an interpolation of an interpolated verbatim + /// string, which C# 8 allows to be written as @$"..." as well as $@"...". + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestClosingParenthesisInInterpolatedVerbatimStringAsync() + { + const string testCode = @" +public class Foo +{ + public string TestMethod() + { + return @$""{Bar( [|)|]}""; + } + + public string Bar() => ""x""; +} +"; + + const string fixedCode = @" +public class Foo +{ + public string TestMethod() + { + return @$""{Bar()}""; + } + + public string Bar() => ""x""; +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1013CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1013CSharp8UnitTests.cs index 24df05fb0..37e08567b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1013CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1013CSharp8UnitTests.cs @@ -88,5 +88,36 @@ public void TestMethod() await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Validates the handling of the closing brace of an interpolation in an interpolated verbatim string, which + /// C# 8 allows to be written as @$"..." as well as $@"...". + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestClosingBraceInInterpolatedVerbatimStringAsync() + { + const string testCode = @" +public class Foo +{ + public void TestMethod(string value) + { + var a = @$""{value [|}|]""; + } +} +"; + + const string fixedCode = @" +public class Foo +{ + public void TestMethod(string value) + { + var a = @$""{value}""; + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } From 17854a28918c95e45652e550f4a9599be8da6459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 11:38:32 +0200 Subject: [PATCH 05/19] Handle c# 8 'null-coalescing assignment' #122 --- CSHARP8-IMPACT-REVIEW.md | 36 ++++++---------- .../SA1407CSharp8UnitTests.cs | 43 +++++++++++++++++++ .../SA1408CSharp8UnitTests.cs | 43 +++++++++++++++++++ 3 files changed, 100 insertions(+), 22 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index dfdfbf593..6b17419ba 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -16,11 +16,11 @@ supplies the other half of the partial class, and the test then re-runs in CShar The goal is a regression test for every related rule, including the ones that turn out to need no code change; a test that pins current correct behaviour is the point, not a formality. -Work through the numbered items one at a time. Delete an item from this file once its tests are merged, and update -"Current coverage" below in the same change so the two never disagree. Item numbers are stable — deleting item 6 -leaves a gap rather than renumbering, so "item 6" means the same thing across conversations. Items are ordered by -priority, and each states its own; priority reflects how likely the rule is to behave wrongly today, not how much -typing the test needs. Each item links the language documentation the source page points at for that feature. +Work through the numbered items one at a time. Delete an item from this file once its tests are merged. Item numbers +are stable — deleting item 6 leaves a gap rather than renumbering, so "item 6" means the same thing across +conversations. Items are ordered by priority, and each states its own; priority reflects how likely the rule is to +behave wrongly today, not how much typing the test needs. Each item links the language documentation the source page +points at for that feature. ## Items @@ -35,8 +35,8 @@ expression, plus the keyword itself. (`StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1000KeywordsMustBeSpacedCorrectly.cs:112-118`). That rule was written when `switch` could only start a statement, where the keyword is *followed* by `(`. In a switch expression the keyword is *preceded* by the governing expression and followed by `{`, so `x switch{...}` and `x switch {...}` need a -decision and a test. `SA1000CSharp8UnitTests.cs` now exists (added for unmanaged constructed types) but covers only `stackalloc` and -`sizeof` on constructed unmanaged types, nothing about switch expressions. +decision and a test. `SA1000CSharp8UnitTests.cs` now exists (added for unmanaged constructed types) but covers only +`stackalloc` and `sizeof` on those, nothing about switch expressions. Also untested: `=>` inside switch arms (`SA1003`), the brace layout of the arm list (`SA1500`, `SA1501`, `SA1505`, `SA1506`, `SA1508`), arm indentation (`SA1137`) and arms sharing a line (`SA1136`). @@ -52,10 +52,10 @@ single-line one. **Priority:** High. **Suspected code gap:** none, but the rule is entirely unexercised for this syntax. **Docs:** [Indices and ranges](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#range-operator-) -`SA1003`, `SA1008`, `SA1009`, `SA1011` and `SA1119` all have range tests. `SA1010OpeningSquareBracketsMustBeSpacedCorrectly` -does not: `SA1010CSharp8UnitTests.cs` exists (added for unmanaged constructed types) but covers only constructed unmanaged types, and no -test anywhere exercises index-from-end or range arguments. The rule has grown special cases for index initializers, -list patterns and collection expressions +`SA1003`, `SA1008`, `SA1009`, `SA1011` and `SA1119` all have range tests. +`SA1010OpeningSquareBracketsMustBeSpacedCorrectly` does not: `SA1010CSharp8UnitTests.cs` exists (added for unmanaged +constructed types) but covers only those, and no test anywhere exercises index-from-end or range arguments. The rule +has grown special cases for index initializers, list patterns and collection expressions (`.../SpacingRules/SA1010OpeningSquareBracketsMustBeSpacedCorrectly.cs:97,115-128`) but nothing for either of those. **Proposed:** add to `SA1010CSharp8UnitTests`, covering `x[^1]`, `x[1..2]`, `x[..^1]`, `x [^1]` (diagnostic) and the @@ -73,8 +73,9 @@ Local()` is not flagged today while the equivalent method declaration would be. Whether that is a bug or an intentional scope limit is the open question — `SA1206`'s upstream description is declaration-focused, and local functions have no access modifiers, so only the static/async ordering is at stake. -Other rules already handle local functions and only need a `static` regression test: `SA1502` (`.../SA1502ElementMustNotBeOnASingleLine.cs`), -`SA1300`, and the parameter-list family `SA1110`–`SA1117`, all of which reference `LocalFunctionStatement` today. +Other rules already handle local functions and only need a `static` regression test: `SA1502` +(`.../SA1502ElementMustNotBeOnASingleLine.cs`), `SA1300`, and the parameter-list family `SA1110`–`SA1117`, all of +which reference `LocalFunctionStatement` today. **Proposed:** decide SA1206's scope, then `SA1206CSharp8UnitTests` (either the fix plus tests, or a test pinning that local functions are ignored), plus `SA1502` and `SA1300` tests using `static` local functions. @@ -171,12 +172,3 @@ layout rules, so the risk is in blank-line and whitespace handling around `#null larger expression, which is the C# 8 change. **Proposed:** nested-expression `stackalloc` cases in `SA1000CSharp8UnitTests`, plus `SA1010`/`SA1011` bracket tests. - -### 11. Null-coalescing assignment - -**Priority:** Low. **Suspected code gap:** none. **Docs:** [Null-coalescing assignment](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/assignment-operator#null-coalescing-assignment) - -`SA1003CSharp8UnitTests.TestNullCoalescingAssignmentOperatorAsync` covers spacing. The parenthesis rules -`SA1407`/`SA1408` have never seen `??=` mixed with arithmetic or conditional operators. - -**Proposed:** `SA1407`/`SA1408` regressions for `a ??= b + c` and `a ??= b && c`. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs new file mode 100644 index 000000000..c42be52cd --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.MaintainabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.MaintainabilityRules.SA1407ArithmeticExpressionsMustDeclarePrecedence, + StyleCop.Analyzers.MaintainabilityRules.SA1407SA1408CodeFixProvider>; + + public partial class SA1407CSharp8UnitTests + { + /// + /// Verifies that arithmetic precedence is still checked on the right hand side of a null-coalescing + /// assignment, which C# 8 introduced. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestNullCoalescingAssignmentAsync() + { + var testCode = @"public class Foo +{ + public void Bar(int? x) + { + x ??= 1 + [|1 * 1|]; + } +}"; + + var fixedCode = @"public class Foo +{ + public void Bar(int? x) + { + x ??= 1 + (1 * 1); + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs new file mode 100644 index 000000000..62a359396 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.MaintainabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.MaintainabilityRules.SA1408ConditionalExpressionsMustDeclarePrecedence, + StyleCop.Analyzers.MaintainabilityRules.SA1407SA1408CodeFixProvider>; + + public partial class SA1408CSharp8UnitTests + { + /// + /// Verifies that conditional precedence is still checked on the right hand side of a null-coalescing + /// assignment, which C# 8 introduced. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestNullCoalescingAssignmentAsync() + { + var testCode = @"public class Foo +{ + public void Bar(bool? x) + { + x ??= [|true && false|] || true; + } +}"; + + var fixedCode = @"public class Foo +{ + public void Bar(bool? x) + { + x ??= (true && false) || true; + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} From 75b3431834086b318a6cfd187ed852a8efd8d6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 11:49:06 +0200 Subject: [PATCH 06/19] Handle c# 8 'stackalloc in nested expressions' #122 --- CSHARP8-IMPACT-REVIEW.md | 20 ++----- .../SpacingRules/SA1000CSharp8UnitTests.cs | 42 ++++++++++++++ .../SpacingRules/SA1010CSharp8UnitTests.cs | 48 +++++++++++++++ .../SpacingRules/SA1011CSharp8UnitTests.cs | 42 ++++++++++++++ .../SpacingRules/SA1026CSharp8UnitTests.cs | 58 +++++++++++++++++++ 5 files changed, 194 insertions(+), 16 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index 6b17419ba..278dc6c80 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -35,8 +35,8 @@ expression, plus the keyword itself. (`StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1000KeywordsMustBeSpacedCorrectly.cs:112-118`). That rule was written when `switch` could only start a statement, where the keyword is *followed* by `(`. In a switch expression the keyword is *preceded* by the governing expression and followed by `{`, so `x switch{...}` and `x switch {...}` need a -decision and a test. `SA1000CSharp8UnitTests.cs` now exists (added for unmanaged constructed types) but covers only -`stackalloc` and `sizeof` on those, nothing about switch expressions. +decision and a test. `SA1000CSharp8UnitTests.cs` now exists but covers only `stackalloc` and `sizeof`, nothing about +switch expressions. Also untested: `=>` inside switch arms (`SA1003`), the brace layout of the arm list (`SA1500`, `SA1501`, `SA1505`, `SA1506`, `SA1508`), arm indentation (`SA1137`) and arms sharing a line (`SA1136`). @@ -53,8 +53,8 @@ single-line one. **Priority:** High. **Suspected code gap:** none, but the rule is entirely unexercised for this syntax. **Docs:** [Indices and ranges](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#range-operator-) `SA1003`, `SA1008`, `SA1009`, `SA1011` and `SA1119` all have range tests. -`SA1010OpeningSquareBracketsMustBeSpacedCorrectly` does not: `SA1010CSharp8UnitTests.cs` exists (added for unmanaged -constructed types) but covers only those, and no test anywhere exercises index-from-end or range arguments. The rule +`SA1010OpeningSquareBracketsMustBeSpacedCorrectly` does not: `SA1010CSharp8UnitTests.cs` exists but covers only +`stackalloc`, and no test anywhere exercises index-from-end or range arguments. The rule has grown special cases for index initializers, list patterns and collection expressions (`.../SpacingRules/SA1010OpeningSquareBracketsMustBeSpacedCorrectly.cs:97,115-128`) but nothing for either of those. @@ -160,15 +160,3 @@ layout rules, so the risk is in blank-line and whitespace handling around `#null **Proposed:** regressions for `SA1027`/`SA1028` (tabs and trailing whitespace on the directive line), and `SA1505`/`SA1507`/`SA1516` for a directive sitting between two members. - -### 10. `stackalloc` in nested expressions - -**Priority:** Low. **Suspected code gap:** none. **Docs:** [Stackalloc in nested expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/stackalloc) - -`SA1119CSharp8UnitTests.TestStackAllocExpressionInExpressionAsync` covers the parenthesis rule. `SA1000` routes -`StackAllocKeyword` through `HandleNewOrStackAllocKeywordToken` -(`SA1000KeywordsMustBeSpacedCorrectly.cs:174-176`); it is now tested for the C# 6 statement form -(`SA1000UnitTests.cs:327`) and for a constructed unmanaged type, but not for a `stackalloc` nested inside a -larger expression, which is the C# 8 change. - -**Proposed:** nested-expression `stackalloc` cases in `SA1000CSharp8UnitTests`, plus `SA1010`/`SA1011` bracket tests. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs index 5249102bd..778c0fe11 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs @@ -92,5 +92,47 @@ public unsafe void TestMethod() await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of the stackalloc keyword in a nested expression, which C# 8 allows. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStackAllocInNestedExpressionAsync() + { + var testCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar({|#0:stackalloc|}@Int32[3]); + } + + public void Bar(Span value) + { + } +} +"; + + var fixedCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc @Int32[3]); + } + + public void Bar(Span value) + { + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("stackalloc", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs index 291c1e42d..6bcd4aa20 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs @@ -105,5 +105,53 @@ public unsafe void TestMethod(Foo* data) await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of the opening bracket of a stackalloc in a nested expression, which C# 8 allows. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStackAllocInNestedExpressionAsync() + { + var testCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc int {|#0:[|}3]); + Bar(stackalloc int{|#1:[|} 3]); + } + + public void Bar(Span value) + { + } +} +"; + + var fixedCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc int[3]); + Bar(stackalloc int[3]); + } + + public void Bar(Span value) + { + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs index 6d492c20e..4cf2d20ba 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs @@ -119,5 +119,47 @@ public void TestMethod(int[] arg) await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of the closing bracket of a stackalloc in a nested expression, which C# 8 allows. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStackAllocInNestedExpressionAsync() + { + var testCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc int[3 {|#0:]|}); + } + + public void Bar(Span value) + { + } +} +"; + + var fixedCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc int[3]); + } + + public void Bar(Span value) + { + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments(" not", "preceded"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs new file mode 100644 index 000000000..2c9146ab6 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.SpacingRules.SA1026CodeMustNotContainSpaceAfterNewKeywordInImplicitlyTypedArrayAllocation, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1026CSharp8UnitTests + { + /// + /// Verifies the handling of an implicitly typed stackalloc in a nested expression, which C# 8 allows. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestImplicitStackAllocInNestedExpressionAsync() + { + var testCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar({|#0:stackalloc|} [] { 1, 2, 3 }); + } + + public void Bar(Span value) + { + } +} +"; + + var fixedCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + Bar(stackalloc[] { 1, 2, 3 }); + } + + public void Bar(Span value) + { + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("stackalloc"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} From bfa40b215f846edcdc2f32dd9f765a8687550d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 12:10:38 +0200 Subject: [PATCH 07/19] Handle c#8 '#nullable directives' #122 --- CSHARP8-IMPACT-REVIEW.md | 14 +---- .../LayoutRules/SA1505CSharp8UnitTests.cs | 46 ++++++++++++++++ .../LayoutRules/SA1507CSharp8UnitTests.cs | 54 +++++++++++++++++++ .../LayoutRules/SA1516CSharp8UnitTests.cs | 52 ++++++++++++++++++ .../SpacingRules/SA1027CSharp8UnitTests.cs | 39 ++++++++++++++ .../SpacingRules/SA1028CSharp8UnitTests.cs | 38 +++++++++++++ 6 files changed, 231 insertions(+), 12 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index 278dc6c80..adaaeaa74 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -45,8 +45,8 @@ Worth confirming while here: no analyzer references `SyntaxKindEx.SwitchExpressi That may be correct — or it may be why the layout rules ignore arms. **Proposed:** switch expression keyword spacing added to `SA1000CSharp8UnitTests`, plus `SA1003`, `SA1136`, `SA1137`, -`SA1500`, `SA1501`, `SA1505`, `SA1506` and `SA1508` C# 8 files covering a multi-line switch expression and a -single-line one. +`SA1500`, `SA1501`, `SA1506` and `SA1508` C# 8 files covering a multi-line switch expression and a single-line one, +plus the same added to the existing `SA1505CSharp8UnitTests`. ### 2. Indices and ranges — SA1010 has no range or index tests @@ -150,13 +150,3 @@ a hypothesis, not something confirmed against the code — check it before writi **Proposed:** C# 8 files for `SA1111`, `SA1112`, `SA1113`, `SA1115`, `SA1116`, `SA1117` with a multi-line positional pattern, and an `SA1141` regression on a tuple pattern. - -### 9. `#nullable` directives - -**Priority:** Medium. **Suspected code gap:** none. **Docs:** [Nullable reference types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-reference-types) - -`SyntaxKindEx.NullableDirectiveTrivia` exists but no analyzer references it. Directives are plain trivia to the -layout rules, so the risk is in blank-line and whitespace handling around `#nullable enable` / `#nullable restore`. - -**Proposed:** regressions for `SA1027`/`SA1028` (tabs and trailing whitespace on the directive line), and -`SA1505`/`SA1507`/`SA1516` for a directive sitting between two members. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs new file mode 100644 index 000000000..d47035db9 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1505OpeningBracesMustNotBeFollowedByBlankLine, + StyleCop.Analyzers.LayoutRules.SA1505CodeFixProvider>; + + public partial class SA1505CSharp8UnitTests + { + /// + /// Verifies that a blank line between an opening brace and a nullable directive, which C# 8 introduced, is + /// reported. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestBlankLineBeforeNullableDirectiveAsync() + { + var testCode = @"public class TestClass +[|{|] + +#nullable enable + public void Method() + { + } +} +"; + + var fixedCode = @"public class TestClass +{ +#nullable enable + public void Method() + { + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs new file mode 100644 index 000000000..a334463fa --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs @@ -0,0 +1,54 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1507CodeMustNotContainMultipleBlankLinesInARow, + StyleCop.Analyzers.LayoutRules.SA1507CodeFixProvider>; + + public partial class SA1507CSharp8UnitTests + { + /// + /// Verifies that multiple blank lines before a nullable directive, which C# 8 introduced, are reported. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestMultipleBlankLinesBeforeNullableDirectiveAsync() + { + var testCode = @"public class TestClass +{ + public void First() + { + } +[| + + +|]#nullable enable + public void Second() + { + } +} +"; + + var fixedCode = @"public class TestClass +{ + public void First() + { + } + +#nullable enable + public void Second() + { + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs new file mode 100644 index 000000000..c540a89a6 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1516ElementsMustBeSeparatedByBlankLine, + StyleCop.Analyzers.LayoutRules.SA1516CodeFixProvider>; + + public partial class SA1516CSharp8UnitTests + { + /// + /// Verifies that a nullable directive, which C# 8 introduced, does not count as the blank line required + /// between two elements. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestNullableDirectiveBetweenElementsAsync() + { + var testCode = @"public class TestClass +{ + public void First() + { + } +[|#nullable enable|] + public void Second() + { + } +} +"; + + var fixedCode = @"public class TestClass +{ + public void First() + { + } + +#nullable enable + public void Second() + { + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs new file mode 100644 index 000000000..6933d8d93 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.SpacingRules.SA1027UseTabsCorrectly, + StyleCop.Analyzers.SpacingRules.SA1027CodeFixProvider>; + + public partial class SA1027CSharp8UnitTests + { + /// + /// Verifies that a tab inside a nullable directive, which C# 8 introduced, is reported. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestTabInNullableDirectiveAsync() + { + // Written with escapes rather than a verbatim string because the test code contains a tab. + var testCode = + "#nullable[|\t|]enable\r\n" + + "public class TestClass\r\n" + + "{\r\n" + + "}\r\n"; + + var fixedCode = + "#nullable enable\r\n" + + "public class TestClass\r\n" + + "{\r\n" + + "}\r\n"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs new file mode 100644 index 000000000..f233be333 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.SpacingRules.SA1028CodeMustNotContainTrailingWhitespace, + StyleCop.Analyzers.SpacingRules.SA1028CodeFixProvider>; + + public partial class SA1028CSharp8UnitTests + { + /// + /// Verifies that trailing whitespace after a nullable directive, which C# 8 introduced, is reported. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestTrailingWhitespaceAfterNullableDirectiveAsync() + { + var testCode = @"#nullable enable[| |] +public class TestClass +{ +} +"; + + var fixedCode = @"#nullable enable +public class TestClass +{ +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} From 4c6a564fa3094ca39c5cb230a3ec9f25e4c3b201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 12:27:34 +0200 Subject: [PATCH 08/19] Handle c# 8 'positional and property patterns' #122 --- CSHARP8-IMPACT-REVIEW.md | 12 ----- .../SA1111CSharp8UnitTests.cs | 48 +++++++++++++++++++ .../SA1112CSharp8UnitTests.cs | 45 +++++++++++++++++ .../SA1113CSharp8UnitTests.cs | 47 ++++++++++++++++++ .../SA1115CSharp8UnitTests.cs | 46 ++++++++++++++++++ .../SA1116CSharp8UnitTests.cs | 48 +++++++++++++++++++ .../SA1117CSharp8UnitTests.cs | 46 ++++++++++++++++++ .../SA1141CSharp8UnitTests.cs | 36 ++++++++++++++ 8 files changed, 316 insertions(+), 12 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index adaaeaa74..5ac5cea32 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -138,15 +138,3 @@ future change to the modifier handling cannot silently break it. **Proposed:** `SA1206CSharp8UnitTests` (shared with item 3), plus `SA1201`/`SA1202`/`SA1204`/`SA1214` regressions on a struct with readonly members. - -### 8. Positional and property patterns - -**Priority:** Medium. **Suspected code gap:** none. **Docs:** [Pattern matching enhancements](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns) - -Spacing is covered (`SA1008` has three pattern tests, `SA1012`, `SA1013`, `SA1024`, `SA1101`, `SA1122`). The -parameter-list-shaped rules are not: a positional pattern `(int x, int y)` looks like a parameter list to -`SA1111`–`SA1117`, and `SA1141UseTupleSyntax` may or may not have an opinion about tuple patterns. That resemblance is -a hypothesis, not something confirmed against the code — check it before writing the tests. - -**Proposed:** C# 8 files for `SA1111`, `SA1112`, `SA1113`, `SA1115`, `SA1116`, `SA1117` with a multi-line positional -pattern, and an `SA1141` regression on a tuple pattern. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs new file mode 100644 index 000000000..08647c1cc --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs @@ -0,0 +1,48 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1111ClosingParenthesisMustBeOnLineOfLastParameter, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1111CSharp8UnitTests + { + /// + /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The + /// analyzer registers no pattern syntax kinds, so the closing parenthesis of a pattern is never inspected. + /// + /// A representing the asynchronous unit test. + // TODO: Should this trigger? + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + 2 + ); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs new file mode 100644 index 000000000..58c10d692 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1112CSharp8UnitTests + { + /// + /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The + /// analyzer registers no pattern syntax kinds, so the parentheses of a pattern are never inspected. + /// + /// A representing the asynchronous unit test. + // TODO: Should this trigger? + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct() + { + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point( + ); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs new file mode 100644 index 000000000..60e673608 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1113CommaMustBeOnSameLineAsPreviousParameter, + StyleCop.Analyzers.SpacingRules.TokenSpacingCodeFixProvider>; + + public partial class SA1113CSharp8UnitTests + { + /// + /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The + /// analyzer registers no pattern syntax kinds, so the commas of a pattern are never inspected. + /// + /// A representing the asynchronous unit test. + // TODO: Should this trigger? + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1 + , 2); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs new file mode 100644 index 000000000..20e7b2d8d --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopDiagnosticVerifier; + + public partial class SA1115CSharp8UnitTests + { + /// + /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The + /// analyzer registers no pattern syntax kinds, so the subpatterns of a pattern are never inspected. + /// + /// A representing the asynchronous unit test. + // TODO: Should this trigger? + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + + 2); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs new file mode 100644 index 000000000..e47d06f91 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs @@ -0,0 +1,48 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1116SplitParametersMustStartOnLineAfterDeclaration, + StyleCop.Analyzers.ReadabilityRules.SA1116CodeFixProvider>; + + public partial class SA1116CSharp8UnitTests + { + /// + /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The + /// analyzer registers no pattern syntax kinds, so the subpatterns of a pattern are never inspected. + /// + /// A representing the asynchronous unit test. + // TODO: Should this trigger? + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + 2 + ); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs new file mode 100644 index 000000000..1002e22f7 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopDiagnosticVerifier; + + public partial class SA1117CSharp8UnitTests + { + /// + /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The + /// analyzer registers no pattern syntax kinds, so the subpatterns of a pattern are never inspected. + /// + /// A representing the asynchronous unit test. + // TODO: Should this trigger? + [Fact] + public async Task TestMultiLinePositionalPatternAsync() + { + var testCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + 2 + ); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs new file mode 100644 index 000000000..60537007b --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1141UseTupleSyntax, + StyleCop.Analyzers.ReadabilityRules.SA1141CodeFixProvider>; + + public partial class SA1141CSharp8UnitTests + { + /// + /// Verifies that a tuple pattern, which C# 8 introduced, is not reported. The analyzer inspects type + /// syntax, not patterns, so the pattern itself is never a candidate for tuple syntax. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestTuplePatternAsync() + { + var testCode = @"public class TestClass +{ + public bool TestMethod((int, int) value) + { + return value is (1, 2); + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} From 74a3ea56535ab95e03702f9c674a27d91d767c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 12:39:50 +0200 Subject: [PATCH 09/19] Handle c# 8 'readonly instance members' #122 --- CSHARP8-IMPACT-REVIEW.md | 19 ++------ .../OrderingRules/SA1201CSharp8UnitTests.cs | 43 +++++++++++++++++ .../OrderingRules/SA1202CSharp8UnitTests.cs | 29 ++++++++++++ .../OrderingRules/SA1204CSharp8UnitTests.cs | 42 +++++++++++++++++ .../OrderingRules/SA1206CSharp8UnitTests.cs | 39 +++++++++++++++ .../OrderingRules/SA1214CSharp8UnitTests.cs | 47 +++++++++++++++++++ 6 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index 5ac5cea32..1d2028208 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -77,8 +77,9 @@ Other rules already handle local functions and only need a `static` regression t (`.../SA1502ElementMustNotBeOnASingleLine.cs`), `SA1300`, and the parameter-list family `SA1110`–`SA1117`, all of which reference `LocalFunctionStatement` today. -**Proposed:** decide SA1206's scope, then `SA1206CSharp8UnitTests` (either the fix plus tests, or a test pinning that -local functions are ignored), plus `SA1502` and `SA1300` tests using `static` local functions. +**Proposed:** decide SA1206's scope, then add to the existing `SA1206CSharp8UnitTests` (either the fix plus tests, +or a test pinning that local functions are ignored), plus `SA1502` and `SA1300` tests using `static` local +functions. ### 4. Default interface members — SA1400 deliberately skips interfaces @@ -124,17 +125,3 @@ disposal; the linked `using` documentation covers `await using` too. **Proposed:** `await using` and `using var` cases in `SA1000CSharp8UnitTests`; `SA1002` semicolon test for a using declaration. - -### 7. Readonly instance members - -**Priority:** Medium. **Suspected code gap:** none. **Docs:** [Readonly members](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#readonly-instance-members) - -No C# 8 tests exist for `readonly` struct members. `SA1206` handles the relevant declaration kinds already, and -classifies `readonly` as an "other" modifier, so `readonly public int Foo()` should be flagged and `public readonly -int Foo()` should not — untested either way. - -`SA1214` orders readonly *fields* and should be unaffected by readonly *members*; that is worth a negative test so a -future change to the modifier handling cannot silently break it. - -**Proposed:** `SA1206CSharp8UnitTests` (shared with item 3), plus `SA1201`/`SA1202`/`SA1204`/`SA1214` regressions on a -struct with readonly members. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs new file mode 100644 index 000000000..8f1a8109a --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.OrderingRules.SA1201ElementsMustAppearInTheCorrectOrder, + StyleCop.Analyzers.OrderingRules.ElementOrderCodeFixProvider>; + + public partial class SA1201CSharp8UnitTests + { + /// + /// Verifies that readonly instance members, which C# 8 introduced, are ordered by element kind like any other member. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestReadonlyInstanceMemberAsync() + { + var testCode = @"public struct TestStruct +{ + public readonly int Method() => 0; + + public readonly int {|#0:Property|} => 0; +} +"; + + var fixedCode = @"public struct TestStruct +{ + public readonly int Property => 0; + + public readonly int Method() => 0; +} +"; + + var expected = Diagnostic().WithLocation(0).WithArguments("property", "method"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs index bc8b393e3..ad18edcf9 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs @@ -56,5 +56,34 @@ public async Task TestPropertiesOfInterfaceAsync() NumberOfFixAllIterations = 2, }.RunAsync(CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that readonly instance members, which C# 8 introduced, are ordered by access like any other + /// member. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestReadonlyInstanceMemberAsync() + { + var testCode = @"public struct TestStruct +{ + private readonly int Method() => 0; + + public readonly int {|#0:OtherMethod|}() => 0; +} +"; + + var fixedCode = @"public struct TestStruct +{ + public readonly int OtherMethod() => 0; + + private readonly int Method() => 0; +} +"; + + var expected = Diagnostic().WithLocation(0).WithArguments("public", "private"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs new file mode 100644 index 000000000..430b248a4 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.OrderingRules.SA1204StaticElementsMustAppearBeforeInstanceElements, + StyleCop.Analyzers.OrderingRules.ElementOrderCodeFixProvider>; + + public partial class SA1204CSharp8UnitTests + { + /// + /// Verifies that a readonly instance member, which C# 8 introduced, is ordered as an instance member. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestReadonlyInstanceMemberAsync() + { + var testCode = @"public struct TestStruct +{ + public readonly int Method() => 0; + + public static int [|StaticMethod|]() => 0; +} +"; + + var fixedCode = @"public struct TestStruct +{ + public static int StaticMethod() => 0; + + public readonly int Method() => 0; +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs new file mode 100644 index 000000000..7033c3fc6 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.OrderingRules.SA1206DeclarationKeywordsMustFollowOrder, + StyleCop.Analyzers.OrderingRules.SA1206CodeFixProvider>; + + public partial class SA1206CSharp8UnitTests + { + /// + /// Verifies that an access modifier must precede the readonly keyword of a readonly instance member, which C# 8 introduced. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestReadonlyInstanceMemberAsync() + { + var testCode = @"public struct TestStruct +{ + readonly {|#0:public|} int Method() => 0; +} +"; + + var fixedCode = @"public struct TestStruct +{ + public readonly int Method() => 0; +} +"; + + var expected = Diagnostic().WithLocation(0).WithArguments("public", "readonly"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs new file mode 100644 index 000000000..f63524882 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.OrderingRules.SA1214ReadonlyElementsMustAppearBeforeNonReadonlyElements, + StyleCop.Analyzers.OrderingRules.ElementOrderCodeFixProvider>; + + public partial class SA1214CSharp8UnitTests + { + /// + /// Verifies that the rule orders readonly fields only, and that a readonly instance member, + /// which C# 8 introduced, is not treated as a readonly element. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestReadonlyInstanceMemberAsync() + { + var testCode = @"public struct TestStruct +{ + public int Field; + + public readonly int [|ReadonlyField|]; + + public readonly int Method() => 0; +} +"; + + var fixedCode = @"public struct TestStruct +{ + public readonly int ReadonlyField; + + public int Field; + + public readonly int Method() => 0; +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} From 5ec345e25e0c88456896c37a184dcd4916f652ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 15:39:03 +0200 Subject: [PATCH 10/19] Update SA1122 to trigger on all kinds of empty string literals #122 --- .../ReadabilityRules/SA1122UnitTests.cs | 146 ++++++++++++++++++ .../SA1122CSharp8UnitTests.cs | 22 --- .../Lightup/SyntaxKindEx.cs | 1 + .../SA1122UseStringEmptyForEmptyStrings.cs | 30 +++- documentation/SA1122.md | 17 ++ 5 files changed, 190 insertions(+), 26 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp6/ReadabilityRules/SA1122UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp6/ReadabilityRules/SA1122UnitTests.cs index 08183f938..d31ceb384 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp6/ReadabilityRules/SA1122UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp6/ReadabilityRules/SA1122UnitTests.cs @@ -6,6 +6,7 @@ namespace StyleCop.Analyzers.Test.CSharp6.ReadabilityRules using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; + using StyleCop.Analyzers.Lightup; using StyleCop.Analyzers.ReadabilityRules; using Xunit; using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< @@ -16,8 +17,99 @@ namespace StyleCop.Analyzers.Test.CSharp6.ReadabilityRules /// This class contains unit tests for and /// . /// + // TODO: Check if this can be simplified, using the theory tests public class SA1122UnitTests { + public static TheoryData EmptyStringLiterals + { + get + { + var data = new TheoryData() + { + "\"\"", + "@\"\"", + "$\"\"", + "$@\"\"", + }; + + if (LightupHelpers.SupportsCSharp8) + { + data.Add("@$\"\""); + } + + if (LightupHelpers.SupportsCSharp11) + { + // Only the multi-line form of a raw string literal can be empty, written as a single blank line + // between the delimiters. + data.Add("\"\"\"\r\n\r\n \"\"\""); + } + + return data; + } + } + + public static TheoryData NotReportedStringLiterals + { + get + { + var data = new TheoryData() + { + "\"text\"", + "@\"text\"", + "$\"text\"", + "$@\"text\"", + "$\"{value}\"", + }; + + if (LightupHelpers.SupportsCSharp8) + { + data.Add("@$\"text\""); + } + + if (LightupHelpers.SupportsCSharp11) + { + data.Add("\"\"\"text\"\"\""); + + // Two blank lines is the boundary of the empty case above: the newline ending the last content + // line belongs to the closing delimiter, so this value is a single line break, not empty. + data.Add("\"\"\"\r\n\r\n\r\n \"\"\""); + + // A UTF-8 string literal is a ReadOnlySpan rather than a string, so string.Empty can never replace it. + data.Add("\"\"u8"); + data.Add("\"text\"u8"); + } + + return data; + } + } + + public static TheoryData EmptyStringLiteralsAllowedAsConstant + { + get + { + var data = new TheoryData() + { + "\"\"", + "@\"\"", + }; + + // An interpolated string is only a constant expression from C# 10 onwards. + if (LightupHelpers.SupportsCSharp10) + { + data.Add("$\"\""); + data.Add("$@\"\""); + data.Add("@$\"\""); + } + + if (LightupHelpers.SupportsCSharp11) + { + data.Add("\"\"\"\r\n\r\n \"\"\""); + } + + return data; + } + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -70,6 +162,60 @@ public void Bar() await VerifyCSharpFixAsync(oldSource, expected, newSource, CancellationToken.None).ConfigureAwait(true); } + [Theory] + [MemberData(nameof(EmptyStringLiterals))] + public async Task TestEmptyStringLiteralIsReportedAsync(string literal) + { + var testCode = $@"public class Foo +{{ + public void Bar(string value) + {{ + var test = [|{literal}|]; + }} +}}"; + var fixedCode = @"public class Foo +{ + public void Bar(string value) + { + var test = string.Empty; + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + [Theory] + [MemberData(nameof(NotReportedStringLiterals))] + public async Task TestStringLiteralIsNotReportedAsync(string literal) + { + var testCode = $@"public class Foo +{{ + public void Bar(string value) + {{ + var test = {literal}; + }} +}}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + + [Theory] + [MemberData(nameof(EmptyStringLiteralsAllowedAsConstant))] + public async Task TestEmptyStringLiteralAsConstantIsNotReportedAsync(string literal) + { + var testCode = $@"public class Foo +{{ + private const string TestField = {literal}; + + public void Bar() + {{ + const string test = {literal}; + }} +}}"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1122CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1122CSharp8UnitTests.cs index a1d2affa4..96b5f9e6a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1122CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1122CSharp8UnitTests.cs @@ -82,28 +82,6 @@ public bool TestMethod(KeyValuePair condition) return condition is { Key: """" }; } } -"; - - await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); - } - - /// - /// Verifies that an empty interpolated verbatim string is not reported. C# 8 allows these strings to be - /// written as @$"..." as well as $@"...". - /// - /// A representing the asynchronous unit test. - // TODO: Should this trigger? - [Fact] - public async Task TestEmptyInterpolatedVerbatimStringAsync() - { - string testCode = @" -public class TestClass -{ - public string TestMethod() - { - return @$""""; - } -} "; await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs index ded535a8f..402b8c75a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Lightup/SyntaxKindEx.cs @@ -27,6 +27,7 @@ internal static class SyntaxKindEx public const SyntaxKind AnnotationsKeyword = (SyntaxKind)8489; public const SyntaxKind VarKeyword = (SyntaxKind)8490; public const SyntaxKind UnderscoreToken = (SyntaxKind)8491; + public const SyntaxKind MultiLineRawStringLiteralToken = (SyntaxKind)8519; public const SyntaxKind ConflictMarkerTrivia = (SyntaxKind)8564; public const SyntaxKind IsPatternExpression = (SyntaxKind)8657; public const SyntaxKind RangeExpression = (SyntaxKind)8658; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1122UseStringEmptyForEmptyStrings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1122UseStringEmptyForEmptyStrings.cs index 3f9a1c593..0339bf286 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1122UseStringEmptyForEmptyStrings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1122UseStringEmptyForEmptyStrings.cs @@ -43,6 +43,7 @@ internal class SA1122UseStringEmptyForEmptyStrings : DiagnosticAnalyzerBase CreateDiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.ReadabilityRules, Description); private static readonly Action StringLiteralExpressionAction = HandleStringLiteralExpression; + private static readonly Action InterpolatedStringExpressionAction = HandleInterpolatedStringExpression; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -52,20 +53,23 @@ internal class SA1122UseStringEmptyForEmptyStrings : DiagnosticAnalyzerBase protected override void HandleCompilationStart(CompilationStartAnalysisContext context) { context.RegisterSyntaxNodeAction(StringLiteralExpressionAction, SyntaxKind.StringLiteralExpression); + context.RegisterSyntaxNodeAction(InterpolatedStringExpressionAction, SyntaxKind.InterpolatedStringExpression); } private static void HandleStringLiteralExpression(SyntaxNodeAnalysisContext context) { LiteralExpressionSyntax literalExpression = (LiteralExpressionSyntax)context.Node; - var token = literalExpression.Token; - if (token.IsKind(SyntaxKind.StringLiteralToken)) + + // TODO: Skip check of syntax kind? Might not be necessary. + if (token.IsKind(SyntaxKind.StringLiteralToken) || token.IsKind(SyntaxKindEx.MultiLineRawStringLiteralToken)) { if (HasToBeConstant(literalExpression)) { return; } + // TODO: Check this first instead? Should be faster. if (token.ValueText == string.Empty) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, literalExpression.GetLocation())); @@ -73,9 +77,27 @@ private static void HandleStringLiteralExpression(SyntaxNodeAnalysisContext cont } } - private static bool HasToBeConstant(LiteralExpressionSyntax literalExpression) + private static void HandleInterpolatedStringExpression(SyntaxNodeAnalysisContext context) + { + var interpolatedStringExpression = (InterpolatedStringExpressionSyntax)context.Node; + + // Only an interpolated string without any content at all is considered empty + if (interpolatedStringExpression.Contents.Count > 0) + { + return; + } + + if (HasToBeConstant(interpolatedStringExpression)) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create(Descriptor, interpolatedStringExpression.GetLocation())); + } + + private static bool HasToBeConstant(ExpressionSyntax expression) { - ExpressionSyntax outermostExpression = FindOutermostExpression(literalExpression); + ExpressionSyntax outermostExpression = FindOutermostExpression(expression); if (outermostExpression.Parent.IsKind(SyntaxKind.AttributeArgument) || outermostExpression.Parent.IsKind(SyntaxKind.CaseSwitchLabel) diff --git a/documentation/SA1122.md b/documentation/SA1122.md index 928d035bd..c9383f739 100644 --- a/documentation/SA1122.md +++ b/documentation/SA1122.md @@ -33,6 +33,23 @@ This will cause the compiler to embed an empty string into the compiled code. Ra string s = string.Empty; ``` +Every form of empty string literal is reported, including verbatim, interpolated and raw ones: + +```csharp +string s1 = @""; +string s2 = $""; +string s3 = @$""; +string s4 = """ + + """; +``` + +A UTF-8 string literal such as `""u8` is not reported, because it is a `ReadOnlySpan` rather than a string. + +A violation does not occur where the language requires a constant, whichever form it is written in, because +`string.Empty` is a static read-only field rather than a constant and cannot be used in its place. That covers +attribute arguments, `case` labels, constant patterns, default parameter values, and `const` fields and locals. + ## How to fix violations To fix a violation of this rule, replace the hard-coded empty string with string.Empty. From b5a5a2529f5dd1133b607a670e3c2d5ce435e596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 15:52:26 +0200 Subject: [PATCH 11/19] Handle c# 8 'await using and using declarations' #122 --- CSHARP8-IMPACT-REVIEW.md | 12 ---- .../SpacingRules/SA1000CSharp8UnitTests.cs | 71 +++++++++++++++++++ .../SpacingRules/SA1002CSharp8UnitTests.cs | 38 ++++++++++ 3 files changed, 109 insertions(+), 12 deletions(-) diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index 1d2028208..3dd6f9c30 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -113,15 +113,3 @@ separately (`SA1000KeywordsMustBeSpacedCorrectly.cs:90,95`) but has never seen t **Proposed:** `SA1316CSharp8UnitTests` (`await foreach (var (a, b) in ...)` with tuple element names), and `await foreach` cases in the new `SA1000CSharp8UnitTests`; plus `SA1101` and `SA1503` regressions for the loop body. - -### 6. `await using` and using declarations - -**Priority:** Medium. **Suspected code gap:** none. **Docs:** [Using declarations](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using) - -`SA1503` and `SA1106` already cover the using *declaration* form. Not covered: the `await using` combination, and -`SA1000`'s treatment of `UsingKeyword` in a declaration (`SA1000KeywordsMustBeSpacedCorrectly.cs:113`), where the -keyword is followed by a type or `var` rather than `(`. The source page has no separate entry for asynchronous -disposal; the linked `using` documentation covers `await using` too. - -**Proposed:** `await using` and `using var` cases in `SA1000CSharp8UnitTests`; `SA1002` semicolon test for a using -declaration. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs index 778c0fe11..000fe08fd 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs @@ -134,5 +134,76 @@ public void Bar(Span value) await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of the using keyword of a using declaration, which C# 8 introduced. + /// The keyword is followed by a type rather than by an opening parenthesis here. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestUsingDeclarationAsync() + { + var testCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + {|#0:using|}@IDisposable resource = null; + } +} +"; + + var fixedCode = @"using System; + +public class TestClass +{ + public void TestMethod() + { + using @IDisposable resource = null; + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("using", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of the using keyword of an await using declaration, which C# 8 introduced. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestAwaitUsingDeclarationAsync() + { + var testCode = @"using System; +using System.Threading.Tasks; + +public class TestClass +{ + public async Task TestMethodAsync() + { + await {|#0:using|}@IAsyncDisposable resource = null; + } +} +"; + + var fixedCode = @"using System; +using System.Threading.Tasks; + +public class TestClass +{ + public async Task TestMethodAsync() + { + await using @IAsyncDisposable resource = null; + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("using", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs index 094b3528a..39e41a517 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs @@ -52,5 +52,43 @@ public void TestMethod(object?[] arguments) await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of the semicolon of a using declaration, which C# 8 introduced. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestUsingDeclarationAsync() + { + var testCode = @"namespace TestNamespace +{ + using System; + + public class TestClass + { + public void TestMethod() + { + using IDisposable resource = null [|;|] + } + } +} +"; + + var fixedCode = @"namespace TestNamespace +{ + using System; + + public class TestClass + { + public void TestMethod() + { + using IDisposable resource = null; + } + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } From 0b5ee9f25a1007c5972808a451728c89de563741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Sun, 30 Aug 2026 16:09:31 +0200 Subject: [PATCH 12/19] Handle c# 8 'async foreach' #122 --- CSHARP8-IMPACT-REVIEW.md | 11 ---- .../LayoutRules/SA1503CSharp8UnitTests.cs | 38 +++++++++++++ .../NamingRules/SA1316CSharp8UnitTests.cs | 53 +++++++++++++++++++ .../SA1101CSharp8UnitTests.cs | 47 ++++++++++++++++ .../SpacingRules/SA1000CSharp8UnitTests.cs | 41 ++++++++++++++ 5 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index 3dd6f9c30..6ff7774f9 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -102,14 +102,3 @@ exists but adds no test. **Proposed:** `SA1400CSharp8UnitTests` pinning the interface behaviour, plus tests for `SA1101` (`this.` inside a default implementation), `SA1201`/`SA1204` (ordering of static, const, field and default-implemented members in an interface), `SA1502` (single-line default body) and `SA1600`/`SA1601` (documentation of members with bodies). - -### 5. `await foreach` and async streams - -**Priority:** Medium. **Suspected code gap:** none. **Docs:** [Asynchronous streams](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/iteration-statements#await-foreach) - -`SA1316TupleElementNamesShouldUseCorrectCasing` is the only analyzer referencing -`CommonForEachStatementSyntaxWrapper`, and it has no C# 8 test. `SA1000` handles `AwaitKeyword` and `ForEachKeyword` -separately (`SA1000KeywordsMustBeSpacedCorrectly.cs:90,95`) but has never seen them adjacent. - -**Proposed:** `SA1316CSharp8UnitTests` (`await foreach (var (a, b) in ...)` with tuple element names), and -`await foreach` cases in the new `SA1000CSharp8UnitTests`; plus `SA1101` and `SA1503` regressions for the loop body. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs index 59f5c451a..386d64505 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs @@ -28,5 +28,43 @@ public void Method() await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that the body of an await foreach statement, which C# 8 introduced, must be enclosed in braces. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestAwaitForEachStatementAsync() + { + var testCode = @"using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +public class Test +{ + public async Task MethodAsync(IAsyncEnumerable values) + { + await foreach (var value in values) + [|Console.WriteLine(value);|] + } +}"; + + var fixedCode = @"using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +public class Test +{ + public async Task MethodAsync(IAsyncEnumerable values) + { + await foreach (var value in values) + { + Console.WriteLine(value); + } + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs new file mode 100644 index 000000000..a6b176514 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.NamingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.NamingRules.SA1316TupleElementNamesShouldUseCorrectCasing, + StyleCop.Analyzers.NamingRules.SA1316CodeFixProvider>; + + public partial class SA1316CSharp8UnitTests + { + // TODO: Use from base class instead + private const string PascalCaseTestSettings = @" +{ + ""settings"": { + ""namingRules"": { + ""tupleElementNameCasing"": ""PascalCase"" + } + } +} +"; + + /// + /// Verifies that the names of an await foreach deconstruction, which C# 8 introduced, are exempt from the + /// configured casing just like those of an ordinary foreach deconstruction. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestAwaitForEachDeconstructionAsync() + { + var testCode = @" +using System.Collections.Generic; +using System.Threading.Tasks; + +public class TypeName +{ + public async Task MethodNameAsync(IAsyncEnumerable<(string Name, string Value)> list) + { + await foreach ((string name, string value) in list) + { + } + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, PascalCaseTestSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs index 83c8d12dd..e768d2ade 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs @@ -29,5 +29,52 @@ public bool Method(Test arg) await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that a local call in the body of an await foreach statement, which C# 8 introduced, must be + /// prefixed with this. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestAwaitForEachStatementAsync() + { + var testCode = @"using System.Collections.Generic; +using System.Threading.Tasks; + +public class Test +{ + public async Task MethodAsync(IAsyncEnumerable values) + { + await foreach (var value in values) + { + [|Handle|](value); + } + } + + public void Handle(int value) + { + } +}"; + + var fixedCode = @"using System.Collections.Generic; +using System.Threading.Tasks; + +public class Test +{ + public async Task MethodAsync(IAsyncEnumerable values) + { + await foreach (var value in values) + { + this.Handle(value); + } + } + + public void Handle(int value) + { + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs index 000fe08fd..9c5c4422f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs @@ -205,5 +205,46 @@ public async Task TestMethodAsync() await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of the foreach keyword of an await foreach statement, which C# 8 introduced. The + /// await and foreach keywords are adjacent here. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestAwaitForEachStatementAsync() + { + var testCode = @"using System.Collections.Generic; +using System.Threading.Tasks; + +public class TestClass +{ + public async Task TestMethodAsync(IAsyncEnumerable values) + { + await {|#0:foreach|}(var value in values) + { + } + } +} +"; + + var fixedCode = @"using System.Collections.Generic; +using System.Threading.Tasks; + +public class TestClass +{ + public async Task TestMethodAsync(IAsyncEnumerable values) + { + await foreach (var value in values) + { + } + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("foreach", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } From 1c65c43ed2ee056cd7736793160ccb81b5df94d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Wed, 2 Sep 2026 19:37:45 +0200 Subject: [PATCH 13/19] Handle c# 8 'default interface members' #122 --- CSHARP8-IMPACT-REVIEW.md | 22 -------- .../SA1600CSharp8UnitTests.cs | 35 +++++++++++++ .../LayoutRules/SA1502CSharp8UnitTests.cs | 41 +++++++++++++++ .../SA1400CSharp8UnitTests.cs | 50 +++++++++++++++++++ .../OrderingRules/SA1201CSharp8UnitTests.cs | 33 ++++++++++++ .../OrderingRules/SA1204CSharp8UnitTests.cs | 34 +++++++++++++ .../SA1101CSharp8UnitTests.cs | 31 ++++++++++++ .../SX1101CSharp8UnitTests.cs | 47 +++++++++++++++++ 8 files changed, 271 insertions(+), 22 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1400CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index 6ff7774f9..50042bebc 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -80,25 +80,3 @@ which reference `LocalFunctionStatement` today. **Proposed:** decide SA1206's scope, then add to the existing `SA1206CSharp8UnitTests` (either the fix plus tests, or a test pinning that local functions are ignored), plus `SA1502` and `SA1300` tests using `static` local functions. - -### 4. Default interface members — SA1400 deliberately skips interfaces - -**Priority:** High. **Suspected code gap:** SA1400. **Docs:** [Default interface members](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface#default-interface-members) - -`SA1400AccessModifierMustBeDeclared` bails out whenever a member's parent is an interface -(`.../MaintainabilityRules/SA1400AccessModifierMustBeDeclared.cs:90,106,122,133,162`). Before C# 8 that was -unambiguously right: interface members could not have access modifiers. From C# 8 they can, and a `private` or -`static private` helper in an interface *must* be explicit. So the rule is silent on a construct it arguably now -covers. - -I'd lean towards "current behaviour is still defensible" — the rule's job is to make implicit access explicit, and -interface members are still implicitly `public` — but it needs a test that states the choice rather than leaving it -undefined. - -Existing coverage is thin: `SA1202CSharp8UnitTests.TestPropertiesOfInterfaceAsync` and -`SA1648CSharp8UnitTests.TestIncorrectMemberInheritDocFromStaticMemberInInterfaceAsync`. `SA1600CSharp8UnitTests.cs` -exists but adds no test. - -**Proposed:** `SA1400CSharp8UnitTests` pinning the interface behaviour, plus tests for `SA1101` (`this.` inside a -default implementation), `SA1201`/`SA1204` (ordering of static, const, field and default-implemented members in an -interface), `SA1502` (single-line default body) and `SA1600`/`SA1601` (documentation of members with bodies). diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs index fb3dabd1d..b5878d34f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs @@ -3,11 +3,46 @@ namespace StyleCop.Analyzers.Test.CSharp8.DocumentationRules { + using System.Threading; + using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.DocumentationRules.SA1600ElementsMustBeDocumented, + StyleCop.Analyzers.DocumentationRules.SA1600CodeFixProvider>; public partial class SA1600CSharp8UnitTests { // Using 'Default' here makes sure that later test projects also run these tests with their own language version, without having to override this property protected override LanguageVersion LanguageVersion => LanguageVersion.Default; + + /// + /// Verifies that the members an interface may hold from C# 8 onwards need documentation just like any other + /// interface member. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestInterfaceMembersWithoutDocumentationAsync() + { + var testCode = @"/// +/// A summary. +/// +public interface ITest +{ + void [|DefaultMethod|]() + { + } + + static void [|StaticMethod|]() + { + } +} +"; + + // Only the diagnostic is verified, as in every other SA1600 test: undocumented members also produce + // CS1591 warnings, which the code fix verification would require to be declared here as well. + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs new file mode 100644 index 000000000..2ffc1f803 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1502ElementMustNotBeOnASingleLine, + StyleCop.Analyzers.LayoutRules.SA1502CodeFixProvider>; + + public partial class SA1502CSharp8UnitTests + { + /// + /// Verifies that the default implementation of an interface member, which C# 8 introduced, must not be on a + /// single line. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestDefaultInterfaceMethodAsync() + { + var testCode = @"public interface ITest +{ + void DefaultMethod() [|{|] } +} +"; + + var fixedCode = @"public interface ITest +{ + void DefaultMethod() + { + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1400CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1400CSharp8UnitTests.cs new file mode 100644 index 000000000..98205fb32 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1400CSharp8UnitTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.MaintainabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.MaintainabilityRules.SA1400AccessModifierMustBeDeclared, + StyleCop.Analyzers.MaintainabilityRules.SA1400CodeFixProvider>; + + public partial class SA1400CSharp8UnitTests + { + /// + /// Verifies that no access modifier is required on an interface member, including the kinds of member that + /// C# 8 added: a method with a default implementation, a static method and a static field. Interface + /// members are implicitly public, so the rule deliberately leaves all of them alone. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestInterfaceMembersAsync() + { + var testCode = @"public interface ITest +{ + static int Field; + + static ITest() { } + + int Property { get; set; } + + event System.EventHandler Event; + + void Method(); + + void DefaultMethod() + { + } + + static void StaticMethod() + { + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs index 8f1a8109a..d9bcd2d5c 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs @@ -39,5 +39,38 @@ public async Task TestReadonlyInstanceMemberAsync() await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that the members an interface may hold from C# 8 onwards are ordered by element kind like the + /// members of any other type. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestInterfaceMembersAsync() + { + var testCode = @"public interface ITest +{ + void Method() + { + } + + int {|#0:Property|} { get; set; } +} +"; + + var fixedCode = @"public interface ITest +{ + int Property { get; set; } + + void Method() + { + } +} +"; + + var expected = Diagnostic().WithLocation(0).WithArguments("property", "method"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs index 430b248a4..52f9b2617 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs @@ -34,6 +34,40 @@ public async Task TestReadonlyInstanceMemberAsync() public readonly int Method() => 0; } +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that a static interface member, which C# 8 introduced, must appear before an instance one. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestInterfaceMembersAsync() + { + var testCode = @"public interface ITest +{ + void Method() + { + } + + static void [|StaticMethod|]() + { + } +} +"; + + var fixedCode = @"public interface ITest +{ + static void StaticMethod() + { + } + + void Method() + { + } +} "; await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs index e768d2ade..2b8fa4d89 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs @@ -76,5 +76,36 @@ public void Handle(int value) await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that a local call in the default implementation of an interface member, which C# 8 introduced, + /// must be prefixed with this. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestDefaultInterfaceMethodAsync() + { + var testCode = @"public interface ITest +{ + void Method(); + + void DefaultMethod() + { + [|Method|](); + } +}"; + + var fixedCode = @"public interface ITest +{ + void Method(); + + void DefaultMethod() + { + this.Method(); + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs new file mode 100644 index 000000000..04f062f4d --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SX1101DoNotPrefixLocalMembersWithThis, + StyleCop.Analyzers.ReadabilityRules.SX1101CodeFixProvider>; + + public partial class SX1101CSharp8UnitTests + { + /// + /// Verifies that a this prefix in the default implementation of an interface member, which C# 8 introduced, + /// is detected and removed. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestDefaultInterfaceMethodAsync() + { + var testCode = @"public interface ITest +{ + void Method(); + + void DefaultMethod() + { + [|this|].Method(); + } +}"; + + var fixedCode = @"public interface ITest +{ + void Method(); + + void DefaultMethod() + { + Method(); + } +}"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} From ef22429ed1021e21216f74ebe6b3f409d7470f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Wed, 2 Sep 2026 19:57:08 +0200 Subject: [PATCH 14/19] Handle c# 8 'static local functions' #122 --- CSHARP8-IMPACT-REVIEW.md | 20 -------- .../OrderingRules/SA1206CodeFixProvider.cs | 46 +++++++++++------ .../LayoutRules/SA1502CSharp8UnitTests.cs | 31 ++++++++++++ .../NamingRules/SA1300CSharp8UnitTests.cs | 50 +++++++++++++++++++ .../OrderingRules/SA1206CSharp8UnitTests.cs | 35 +++++++++++++ .../Helpers/DeclarationModifiersHelper.cs | 9 +++- ...A1206DeclarationKeywordsMustFollowOrder.cs | 9 ++++ documentation/SA1206.md | 6 +++ 8 files changed, 169 insertions(+), 37 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index 50042bebc..1b2b6ac37 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -60,23 +60,3 @@ has grown special cases for index initializers, list patterns and collection exp **Proposed:** add to `SA1010CSharp8UnitTests`, covering `x[^1]`, `x[1..2]`, `x[..^1]`, `x [^1]` (diagnostic) and the same inside a nested expression. - -### 3. Static local functions — SA1206 does not see local functions - -**Priority:** High. **Suspected code gap:** SA1206. **Docs:** [Static local functions](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/local-functions) - -`SA1206DeclarationKeywordsMustFollowOrder` registers a fixed list of declaration kinds -(`.../OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs:49-66`) which does **not** include -`LocalFunctionStatement`. A local function may carry `static`, `async`, `extern` and `unsafe`, so `async static void -Local()` is not flagged today while the equivalent method declaration would be. - -Whether that is a bug or an intentional scope limit is the open question — `SA1206`'s upstream description is -declaration-focused, and local functions have no access modifiers, so only the static/async ordering is at stake. - -Other rules already handle local functions and only need a `static` regression test: `SA1502` -(`.../SA1502ElementMustNotBeOnASingleLine.cs`), `SA1300`, and the parameter-list family `SA1110`–`SA1117`, all of -which reference `LocalFunctionStatement` today. - -**Proposed:** decide SA1206's scope, then add to the existing `SA1206CSharp8UnitTests` (either the fix plus tests, -or a test pinning that local functions are ignored), plus `SA1502` and `SA1300` tests using `static` local -functions. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/SA1206CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/SA1206CodeFixProvider.cs index 85005dc44..772f5be76 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/SA1206CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/SA1206CodeFixProvider.cs @@ -14,6 +14,7 @@ namespace StyleCop.Analyzers.OrderingRules using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using StyleCop.Analyzers.Helpers; + using StyleCop.Analyzers.Lightup; using static StyleCop.Analyzers.OrderingRules.ModifierOrderHelper; /// @@ -53,29 +54,42 @@ private static async Task GetTransformedDocumentAsync(Document documen { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var memberDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf(); - if (memberDeclaration == null) + var declaration = FindDeclaration(syntaxRoot, diagnostic); + if (declaration == null) { return document; } - var modifierTokenToFix = memberDeclaration.FindToken(diagnostic.Location.SourceSpan.Start); + var modifierTokenToFix = declaration.FindToken(diagnostic.Location.SourceSpan.Start); if (GetModifierType(modifierTokenToFix) == ModifierType.None) { return document; } - var newModifierList = PartiallySortModifiers(memberDeclaration.GetModifiers(), modifierTokenToFix); - syntaxRoot = UpdateSyntaxRoot(memberDeclaration, newModifierList, syntaxRoot); + var newModifierList = PartiallySortModifiers(DeclarationModifiersHelper.GetModifiers(declaration), modifierTokenToFix); + syntaxRoot = UpdateSyntaxRoot(declaration, newModifierList, syntaxRoot); return document.WithSyntaxRoot(syntaxRoot); } - private static SyntaxNode UpdateSyntaxRoot(MemberDeclarationSyntax memberDeclaration, SyntaxTokenList newModifiers, SyntaxNode syntaxRoot) + /// + /// Finds the declaration a diagnostic was reported on. A local function is a statement rather than a member + /// declaration, so it cannot be found by looking for a alone. + /// + /// The root of the syntax tree. + /// The diagnostic to find the declaration for. + /// The declaration, or if none was found. + private static SyntaxNode FindDeclaration(SyntaxNode syntaxRoot, Diagnostic diagnostic) { - var newDeclaration = memberDeclaration.WithModifiers(newModifiers); + return syntaxRoot.FindNode(diagnostic.Location.SourceSpan) + .AncestorsAndSelf() + .FirstOrDefault(node => node is MemberDeclarationSyntax || LocalFunctionStatementSyntaxWrapper.IsInstance(node)); + } - return syntaxRoot.ReplaceNode(memberDeclaration, newDeclaration); + private static SyntaxNode UpdateSyntaxRoot(SyntaxNode declaration, SyntaxTokenList newModifiers, SyntaxNode syntaxRoot) + { + var newDeclaration = DeclarationModifiersHelper.WithModifiers(declaration, newModifiers); + return syntaxRoot.ReplaceNode(declaration, newDeclaration); } /// @@ -173,31 +187,31 @@ private class FixAll : DocumentBasedFixAllProvider // because all modifiers can be fixed in one run, we // only need to store each declaration once - var trackedDiagnosticMembers = new HashSet(); + var trackedDiagnosticMembers = new HashSet(); foreach (var diagnostic in diagnostics) { - var memberDeclaration = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf(); - if (memberDeclaration == null) + var declaration = FindDeclaration(syntaxRoot, diagnostic); + if (declaration == null) { continue; } - var modifierToken = memberDeclaration.FindToken(diagnostic.Location.SourceSpan.Start); + var modifierToken = declaration.FindToken(diagnostic.Location.SourceSpan.Start); if (GetModifierType(modifierToken) == ModifierType.None) { continue; } - trackedDiagnosticMembers.Add(memberDeclaration); + trackedDiagnosticMembers.Add(declaration); } syntaxRoot = syntaxRoot.TrackNodes(trackedDiagnosticMembers); foreach (var member in trackedDiagnosticMembers) { - var memberDeclaration = syntaxRoot.GetCurrentNode(member); - var newModifierList = FullySortModifiers(memberDeclaration.GetModifiers()); - syntaxRoot = UpdateSyntaxRoot(memberDeclaration, newModifierList, syntaxRoot); + var declaration = syntaxRoot.GetCurrentNode(member); + var newModifierList = FullySortModifiers(DeclarationModifiersHelper.GetModifiers(declaration)); + syntaxRoot = UpdateSyntaxRoot(declaration, newModifierList, syntaxRoot); } return syntaxRoot; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs index 2ffc1f803..d361a3d7f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs @@ -33,6 +33,37 @@ void DefaultMethod() { } } +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that a static local function, which C# 8 introduced, must not be on a single line. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStaticLocalFunctionAsync() + { + var testCode = @"public class TestClass +{ + public void TestMethod() + { + static int LocalFunction() [|{|] return 0; } + } +} +"; + + var fixedCode = @"public class TestClass +{ + public void TestMethod() + { + static int LocalFunction() + { + return 0; + } + } +} "; await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs new file mode 100644 index 000000000..6368d2c3c --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.NamingRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.NamingRules.SA1300ElementMustBeginWithUpperCaseLetter, + StyleCop.Analyzers.NamingRules.RenameToUpperCaseCodeFixProvider>; + + public partial class SA1300CSharp8UnitTests + { + /// + /// Verifies that a static local function, which C# 8 introduced, must begin with an upper-case letter. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStaticLocalFunctionAsync() + { + var testCode = @"public class TestClass +{ + public void TestMethod() + { + static int [|localFunction|]() + { + return 0; + } + } +} +"; + + var fixedCode = @"public class TestClass +{ + public void TestMethod() + { + static int LocalFunction() + { + return 0; + } + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs index 7033c3fc6..15c56119f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs @@ -35,5 +35,40 @@ public async Task TestReadonlyInstanceMemberAsync() await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that the static keyword of a static local function, which C# 8 introduced, must precede the + /// other modifiers. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestStaticLocalFunctionAsync() + { + var testCode = @"using System.Threading.Tasks; + +public class TestClass +{ + public void TestMethod() + { + async {|#0:static|} Task LocalFunction() => await Task.CompletedTask; + } +} +"; + + var fixedCode = @"using System.Threading.Tasks; + +public class TestClass +{ + public void TestMethod() + { + static async Task LocalFunction() => await Task.CompletedTask; + } +} +"; + + var expected = Diagnostic().WithLocation(0).WithArguments("static", "async"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/DeclarationModifiersHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/DeclarationModifiersHelper.cs index dcf01acb4..96825229d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/DeclarationModifiersHelper.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/DeclarationModifiersHelper.cs @@ -96,7 +96,7 @@ internal static SyntaxTokenList AddModifiers(SyntaxTokenList modifiers, ref Synt return modifiers; } - internal static SyntaxTokenList GetModifiers(this MemberDeclarationSyntax syntax) + internal static SyntaxTokenList GetModifiers(this SyntaxNode syntax) { if (syntax is BaseMethodDeclarationSyntax) { @@ -122,6 +122,10 @@ internal static SyntaxTokenList GetModifiers(this MemberDeclarationSyntax syntax { return ((IncompleteMemberSyntax)syntax).Modifiers; } + else if (LocalFunctionStatementSyntaxWrapper.IsInstance(syntax)) + { + return ((LocalFunctionStatementSyntaxWrapper)syntax).Modifiers; + } return default; } @@ -179,6 +183,9 @@ internal static SyntaxNode WithModifiers(this SyntaxNode node, SyntaxTokenList m case SyntaxKind.EventFieldDeclaration: return ((EventFieldDeclarationSyntax)node).WithModifiers(modifiers); + case SyntaxKindEx.LocalFunctionStatement: + return ((LocalFunctionStatementSyntaxWrapper)node).WithModifiers(modifiers); + default: return node; } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs b/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs index adf748b53..7bf25ae2c 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/OrderingRules/SA1206DeclarationKeywordsMustFollowOrder.cs @@ -66,6 +66,7 @@ internal class SA1206DeclarationKeywordsMustFollowOrder : DiagnosticAnalyzerBase SyntaxKind.ConstructorDeclaration); private static readonly Action DeclarationAction = HandleDeclaration; + private static readonly Action LocalFunctionStatementAction = HandleLocalFunctionStatement; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -78,6 +79,8 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c // Register UnionDeclaration separately (with a duplicate-node guard, see the helper for why it is needed). context.RegisterSyntaxNodeActionWithDuplicateNodeGuard(DeclarationAction, SyntaxKindEx.UnionDeclaration); + + context.RegisterSyntaxNodeAction(LocalFunctionStatementAction, SyntaxKindEx.LocalFunctionStatement); } private static void HandleDeclaration(SyntaxNodeAnalysisContext context) @@ -86,6 +89,12 @@ private static void HandleDeclaration(SyntaxNodeAnalysisContext context) CheckModifiersOrderAndReportDiagnostics(context, modifiers); } + private static void HandleLocalFunctionStatement(SyntaxNodeAnalysisContext context) + { + var localFunctionStatement = (LocalFunctionStatementSyntaxWrapper)context.Node; + CheckModifiersOrderAndReportDiagnostics(context, localFunctionStatement.Modifiers); + } + private static void CheckModifiersOrderAndReportDiagnostics(SyntaxNodeAnalysisContext context, SyntaxTokenList modifiers) { var previousModifierType = ModifierType.None; diff --git a/documentation/SA1206.md b/documentation/SA1206.md index fbe74c35f..e9c5d6849 100644 --- a/documentation/SA1206.md +++ b/documentation/SA1206.md @@ -35,6 +35,12 @@ Within an element declaration, keywords should appear in the following order: Using a standard ordering scheme for element declaration keywords can make the code more readable by highlighting the access level of each element. This can help prevent elements from being given a higher access level than needed. +The rule also applies to a local function: + +```csharp +static async Task LocalFunction() => await Task.CompletedTask; +``` + ## How To Fix Violations To fix an instance of this violation, order the keywords in the element's declaration as described above. From 5aeaf56e396f7248faa359944687cb24dfe34c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Thu, 3 Sep 2026 06:24:33 +0200 Subject: [PATCH 15/19] Handle c# 8 'indices and ranges' #122 --- CSHARP8-IMPACT-REVIEW.md | 13 --- .../SpacingRules/SA1010CSharp8UnitTests.cs | 81 +++++++++++++++++++ 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md index 1b2b6ac37..430c45de9 100644 --- a/CSHARP8-IMPACT-REVIEW.md +++ b/CSHARP8-IMPACT-REVIEW.md @@ -47,16 +47,3 @@ That may be correct — or it may be why the layout rules ignore arms. **Proposed:** switch expression keyword spacing added to `SA1000CSharp8UnitTests`, plus `SA1003`, `SA1136`, `SA1137`, `SA1500`, `SA1501`, `SA1506` and `SA1508` C# 8 files covering a multi-line switch expression and a single-line one, plus the same added to the existing `SA1505CSharp8UnitTests`. - -### 2. Indices and ranges — SA1010 has no range or index tests - -**Priority:** High. **Suspected code gap:** none, but the rule is entirely unexercised for this syntax. **Docs:** [Indices and ranges](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#range-operator-) - -`SA1003`, `SA1008`, `SA1009`, `SA1011` and `SA1119` all have range tests. -`SA1010OpeningSquareBracketsMustBeSpacedCorrectly` does not: `SA1010CSharp8UnitTests.cs` exists but covers only -`stackalloc`, and no test anywhere exercises index-from-end or range arguments. The rule -has grown special cases for index initializers, list patterns and collection expressions -(`.../SpacingRules/SA1010OpeningSquareBracketsMustBeSpacedCorrectly.cs:97,115-128`) but nothing for either of those. - -**Proposed:** add to `SA1010CSharp8UnitTests`, covering `x[^1]`, `x[1..2]`, `x[..^1]`, `x [^1]` (diagnostic) and the -same inside a nested expression. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs index 6bcd4aa20..47def367a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs @@ -153,5 +153,86 @@ public void Bar(Span value) await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of an index-from-end argument, which C# 8 introduced. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestIndexFromEndAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int[] values) + { + var value1 = values {|#0:[|}^1]; + var value2 = values{|#1:[|} ^1]; + return value1 + value2 + values[^1]; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int[] values) + { + var value1 = values[^1]; + var value2 = values[^1]; + return value1 + value2 + values[^1]; + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies the handling of a range argument, which C# 8 introduced. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestRangeAsync() + { + var testCode = @"public class TestClass +{ + public void TestMethod(int[] values) + { + var range1 = values {|#0:[|}1..2]; + var range2 = values{|#1:[|} ..^1]; + var range3 = TestMethod2(values {|#2:[|}..]); + } + + public int TestMethod2(int[] values) => 0; +} +"; + + var fixedCode = @"public class TestClass +{ + public void TestMethod(int[] values) + { + var range1 = values[1..2]; + var range2 = values[..^1]; + var range3 = TestMethod2(values[..]); + } + + public int TestMethod2(int[] values) => 0; +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorNotPreceded).WithLocation(0), + Diagnostic(DescriptorNotFollowed).WithLocation(1), + Diagnostic(DescriptorNotPreceded).WithLocation(2), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } From 53446794ac30c2ff2316c6532c0fe7fdf3e831cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Mon, 7 Sep 2026 06:46:55 +0200 Subject: [PATCH 16/19] Handle c# 8 'switch expressions' #122 --- CSHARP8-IMPACT-REVIEW.md | 49 ------------- .../LayoutRules/SA1500CSharp8UnitTests.cs | 70 +++++++++++++++++++ .../LayoutRules/SA1501CSharp8UnitTests.cs | 35 ++++++++++ .../LayoutRules/SA1505CSharp8UnitTests.cs | 38 ++++++++++ .../LayoutRules/SA1508CSharp8UnitTests.cs | 54 ++++++++++++++ .../SA1137CSharp8UnitTests.cs | 52 ++++++++++++++ .../SpacingRules/SA1000CSharp8UnitTests.cs | 34 +++++++++ .../SpacingRules/SA1003CSharp8UnitTests.cs | 35 ++++++++++ ...sForMultiLineStatementsMustNotShareLine.cs | 8 +++ ...eningBracesMustNotBeFollowedByBlankLine.cs | 8 +++ ...osingBracesMustNotBePrecededByBlankLine.cs | 8 +++ ...137ElementsShouldHaveTheSameIndentation.cs | 10 +++ .../SA1003SymbolsMustBeSpacedCorrectly.cs | 8 +++ 13 files changed, 360 insertions(+), 49 deletions(-) delete mode 100644 CSHARP8-IMPACT-REVIEW.md create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1137CSharp8UnitTests.cs diff --git a/CSHARP8-IMPACT-REVIEW.md b/CSHARP8-IMPACT-REVIEW.md deleted file mode 100644 index 430c45de9..000000000 --- a/CSHARP8-IMPACT-REVIEW.md +++ /dev/null @@ -1,49 +0,0 @@ -# C# 8 impact review - -Source: [C# version 8.0](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-80) - -The C# 8 language features with StyleCop work still outstanding, the rules each one touches, and what the repo -already has. Findings come from reading the analyzer implementations and the existing -`StyleCop.Analyzers.Test.CSharp8` tests, not just the language spec, and are grounded with file/line references. - -The review started from all fourteen features on the source page. A feature disappears from this file once its work -is done or once it has been confirmed to need none, so an absent feature has been dealt with, not overlooked. - -New tests belong in `StyleCop.Analyzers.Test.CSharp8` (the lowest project whose language version can express the -syntax, per `CLAUDE.md`), written as `public partial class SA####CSharp8UnitTests` — the derived-test generator -supplies the other half of the partial class, and the test then re-runs in CSharp9..15 automatically. - -The goal is a regression test for every related rule, including the ones that turn out to need no code change; a test -that pins current correct behaviour is the point, not a formality. - -Work through the numbered items one at a time. Delete an item from this file once its tests are merged. Item numbers -are stable — deleting item 6 leaves a gap rather than renumbering, so "item 6" means the same thing across -conversations. Items are ordered by priority, and each states its own; priority reflects how likely the rule is to -behave wrongly today, not how much typing the test needs. Each item links the language documentation the source page -points at for that feature. - -## Items - -### 1. Switch expressions — SA1000 is unverified, layout rules untested - -**Priority:** High. **Suspected code gap:** SA1000. **Docs:** [Pattern matching enhancements](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns) - -`SA1119` and `SA1413` are well covered (11 and 1 tests). The gap is everything around the *layout* of a switch -expression, plus the keyword itself. - -`SA1000KeywordsMustBeSpacedCorrectly` routes `SyntaxKind.SwitchKeyword` to `HandleRequiredSpaceToken` -(`StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1000KeywordsMustBeSpacedCorrectly.cs:112-118`). That rule was -written when `switch` could only start a statement, where the keyword is *followed* by `(`. In a switch expression the -keyword is *preceded* by the governing expression and followed by `{`, so `x switch{...}` and `x switch {...}` need a -decision and a test. `SA1000CSharp8UnitTests.cs` now exists but covers only `stackalloc` and `sizeof`, nothing about -switch expressions. - -Also untested: `=>` inside switch arms (`SA1003`), the brace layout of the arm list (`SA1500`, `SA1501`, `SA1505`, -`SA1506`, `SA1508`), arm indentation (`SA1137`) and arms sharing a line (`SA1136`). - -Worth confirming while here: no analyzer references `SyntaxKindEx.SwitchExpressionArm` or `SyntaxKindEx.Subpattern`. -That may be correct — or it may be why the layout rules ignore arms. - -**Proposed:** switch expression keyword spacing added to `SA1000CSharp8UnitTests`, plus `SA1003`, `SA1136`, `SA1137`, -`SA1500`, `SA1501`, `SA1506` and `SA1508` C# 8 files covering a multi-line switch expression and a single-line one, -plus the same added to the existing `SA1505CSharp8UnitTests`. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs new file mode 100644 index 000000000..b907954a0 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1500BracesForMultiLineStatementsMustNotShareLine, + StyleCop.Analyzers.LayoutRules.SA1500CodeFixProvider>; + + public partial class SA1500CSharp8UnitTests + { + /// + /// Verifies that a single-line switch expression, which C# 8 introduced, is not inspected. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionSingleLineAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch { 0 => 0, _ => 1 }; + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that diagnostics will be reported for the braces of a multi-line switch expression, which C# 8 + /// introduced, when they share a line with other code. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionInvalidAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch [|{|] + 0 => 0, + _ => 1 [|}|]; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, + _ => 1 + }; + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs new file mode 100644 index 000000000..f1f1821c7 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1501StatementMustNotBeOnASingleLine, + StyleCop.Analyzers.LayoutRules.SA1501CodeFixProvider>; + + public partial class SA1501CSharp8UnitTests + { + /// + /// Verifies that a single-line switch expression, which C# 8 introduced, is not inspected. The analyzer registers statement kinds, and a switch expression is an expression. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch { 0 => 0, _ => 1 }; + } +} +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs index d47035db9..01647da84 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs @@ -38,6 +38,44 @@ public void Method() { } } +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that the opening brace of a switch expression, which C# 8 introduced, must not be followed by a + /// blank line. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + [|{|] + + 0 => 0, + _ => 1, + }; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, + _ => 1, + }; + } +} "; await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs new file mode 100644 index 000000000..71a32927b --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs @@ -0,0 +1,54 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.LayoutRules.SA1508ClosingBracesMustNotBePrecededByBlankLine, + StyleCop.Analyzers.LayoutRules.SA1508CodeFixProvider>; + + public partial class SA1508CSharp8UnitTests + { + /// + /// Verifies that the closing brace of a switch expression, which C# 8 introduced, must not be preceded by a + /// blank line. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, + _ => 1, + + [|}|]; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, + _ => 1, + }; + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1137CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1137CSharp8UnitTests.cs new file mode 100644 index 000000000..8ad454d6f --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1137CSharp8UnitTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Contributors to the New StyleCop Analyzers project. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules +{ + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopCodeFixVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1137ElementsShouldHaveTheSameIndentation, + StyleCop.Analyzers.ReadabilityRules.IndentationCodeFixProvider>; + + public partial class SA1137CSharp8UnitTests + { + /// + /// Verifies that the arms of a switch expression, which C# 8 introduced, are not required to share indentation. The analyzer registers SwitchStatement but not SwitchExpression. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, +[| |]_ => 1, + }; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch + { + 0 => 0, + _ => 1, + }; + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs index 9c5c4422f..c903fe542 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs @@ -246,5 +246,39 @@ public async Task TestMethodAsync(IAsyncEnumerable values) await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies the handling of the switch keyword of a switch expression, which C# 8 introduced. The keyword is + /// followed by a brace here rather than by an opening parenthesis, and is preceded by the governing + /// expression. Only the side after the keyword is checked, so the second case is not reported. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionAsync() + { + var testCode = @"public class TestClass +{ + public void TestMethod(int value) + { + var result1 = value {|#0:switch|}{ _ => 0 }; + var result2 = value switch { _ => 0 }; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public void TestMethod(int value) + { + var result1 = value switch { _ => 0 }; + var result2 = value switch { _ => 0 }; + } +} +"; + + DiagnosticResult expected = Diagnostic().WithLocation(0).WithArguments("switch", string.Empty, "followed"); + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs index b41d44672..b01f613d9 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs @@ -217,5 +217,40 @@ public void TestMethod(System.Action? x) await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); } + + /// + /// Verifies that the arrow of a switch expression arm, which C# 8 introduced, must be surrounded by + /// whitespace. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestSwitchExpressionArmAsync() + { + var testCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch { _{|#0:=>|}0 }; + } +} +"; + + var fixedCode = @"public class TestClass +{ + public int TestMethod(int value) + { + return value switch { _ => 0 }; + } +} +"; + + DiagnosticResult[] expected = + { + Diagnostic(DescriptorPrecededByWhitespace).WithLocation(0).WithArguments("=>"), + Diagnostic(DescriptorFollowedByWhitespace).WithLocation(0).WithArguments("=>"), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(true); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs index 6ab64cca2..273eea98e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1500BracesForMultiLineStatementsMustNotShareLine.cs @@ -74,6 +74,7 @@ internal class SA1500BracesForMultiLineStatementsMustNotShareLine : DiagnosticAn private static readonly Action AccessorListAction = HandleAccessorList; private static readonly Action BlockAction = HandleBlock; private static readonly Action SwitchStatementAction = HandleSwitchStatement; + private static readonly Action SwitchExpressionAction = HandleSwitchExpression; private static readonly Action InitializerExpressionAction = HandleInitializerExpression; private static readonly Action AnonymousObjectCreationExpressionAction = HandleAnonymousObjectCreationExpression; @@ -94,6 +95,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(AccessorListAction, SyntaxKind.AccessorList); context.RegisterSyntaxNodeAction(BlockAction, SyntaxKind.Block); context.RegisterSyntaxNodeAction(SwitchStatementAction, SyntaxKind.SwitchStatement); + context.RegisterSyntaxNodeAction(SwitchExpressionAction, SyntaxKindEx.SwitchExpression); context.RegisterSyntaxNodeAction(InitializerExpressionAction, SyntaxKinds.InitializerExpression); context.RegisterSyntaxNodeAction(AnonymousObjectCreationExpressionAction, SyntaxKind.AnonymousObjectCreationExpression); } @@ -128,6 +130,12 @@ private static void HandleSwitchStatement(SyntaxNodeAnalysisContext context, Sty CheckBraces(context, settings, syntax.OpenBraceToken, syntax.CloseBraceToken); } + private static void HandleSwitchExpression(SyntaxNodeAnalysisContext context, StyleCopSettings settings) + { + var syntax = (SwitchExpressionSyntaxWrapper)context.Node; + CheckBraces(context, settings, syntax.OpenBraceToken, syntax.CloseBraceToken); + } + private static void HandleInitializerExpression(SyntaxNodeAnalysisContext context, StyleCopSettings settings) { var syntax = (InitializerExpressionSyntax)context.Node; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1505OpeningBracesMustNotBeFollowedByBlankLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1505OpeningBracesMustNotBeFollowedByBlankLine.cs index 0d56eb280..f98521a62 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1505OpeningBracesMustNotBeFollowedByBlankLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1505OpeningBracesMustNotBeFollowedByBlankLine.cs @@ -56,6 +56,7 @@ internal class SA1505OpeningBracesMustNotBeFollowedByBlankLine : DiagnosticAnaly private static readonly Action InitializerExpressionAction = HandleInitializerExpression; private static readonly Action AnonymousObjectCreationExpressionAction = HandleAnonymousObjectCreationExpression; private static readonly Action SwitchStatementAction = HandleSwitchStatement; + private static readonly Action SwitchExpressionAction = HandleSwitchExpression; private static readonly Action NamespaceDeclarationAction = HandleNamespaceDeclaration; private static readonly Action BaseTypeDeclarationAction = HandleBaseTypeDeclaration; private static readonly Action AccessorListAction = HandleAccessorList; @@ -71,6 +72,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(InitializerExpressionAction, SyntaxKinds.InitializerExpression); context.RegisterSyntaxNodeAction(AnonymousObjectCreationExpressionAction, SyntaxKind.AnonymousObjectCreationExpression); context.RegisterSyntaxNodeAction(SwitchStatementAction, SyntaxKind.SwitchStatement); + context.RegisterSyntaxNodeAction(SwitchExpressionAction, SyntaxKindEx.SwitchExpression); context.RegisterSyntaxNodeAction(NamespaceDeclarationAction, SyntaxKind.NamespaceDeclaration); context.RegisterSyntaxNodeAction(BaseTypeDeclarationAction, SyntaxKinds.BaseTypeDeclaration); context.RegisterSyntaxNodeAction(BaseTypeDeclarationAction, SyntaxKindEx.ExtensionBlockDeclaration); @@ -105,6 +107,12 @@ private static void HandleSwitchStatement(SyntaxNodeAnalysisContext context) AnalyzeOpenBrace(context, switchStatement.OpenBraceToken); } + private static void HandleSwitchExpression(SyntaxNodeAnalysisContext context) + { + var switchExpression = (SwitchExpressionSyntaxWrapper)context.Node; + AnalyzeOpenBrace(context, switchExpression.OpenBraceToken); + } + private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context) { var namespaceDeclaration = (NamespaceDeclarationSyntax)context.Node; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1508ClosingBracesMustNotBePrecededByBlankLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1508ClosingBracesMustNotBePrecededByBlankLine.cs index e68f697ed..51ff67ca3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1508ClosingBracesMustNotBePrecededByBlankLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1508ClosingBracesMustNotBePrecededByBlankLine.cs @@ -56,6 +56,7 @@ internal class SA1508ClosingBracesMustNotBePrecededByBlankLine : DiagnosticAnaly private static readonly Action InitializerExpressionAction = HandleInitializerExpression; private static readonly Action AnonymousObjectCreationExpressionAction = HandleAnonymousObjectCreationExpression; private static readonly Action SwitchStatementAction = HandleSwitchStatement; + private static readonly Action SwitchExpressionAction = HandleSwitchExpression; private static readonly Action NamespaceDeclarationAction = HandleNamespaceDeclaration; private static readonly Action BaseTypeDeclarationAction = HandleBaseTypeDeclaration; private static readonly Action AccessorListAction = HandleAccessorList; @@ -71,6 +72,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(InitializerExpressionAction, SyntaxKinds.InitializerExpression); context.RegisterSyntaxNodeAction(AnonymousObjectCreationExpressionAction, SyntaxKind.AnonymousObjectCreationExpression); context.RegisterSyntaxNodeAction(SwitchStatementAction, SyntaxKind.SwitchStatement); + context.RegisterSyntaxNodeAction(SwitchExpressionAction, SyntaxKindEx.SwitchExpression); context.RegisterSyntaxNodeAction(NamespaceDeclarationAction, SyntaxKind.NamespaceDeclaration); context.RegisterSyntaxNodeAction(BaseTypeDeclarationAction, SyntaxKinds.BaseTypeDeclaration); context.RegisterSyntaxNodeAction(BaseTypeDeclarationAction, SyntaxKindEx.ExtensionBlockDeclaration); @@ -105,6 +107,12 @@ private static void HandleSwitchStatement(SyntaxNodeAnalysisContext context) AnalyzeCloseBrace(context, switchStatement.CloseBraceToken); } + private static void HandleSwitchExpression(SyntaxNodeAnalysisContext context) + { + var switchExpression = (SwitchExpressionSyntaxWrapper)context.Node; + AnalyzeCloseBrace(context, switchExpression.CloseBraceToken); + } + private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context) { var namespaceDeclaration = (NamespaceDeclarationSyntax)context.Node; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1137ElementsShouldHaveTheSameIndentation.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1137ElementsShouldHaveTheSameIndentation.cs index 4cb30a678..27d5c1351 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1137ElementsShouldHaveTheSameIndentation.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1137ElementsShouldHaveTheSameIndentation.cs @@ -44,6 +44,7 @@ internal class SA1137ElementsShouldHaveTheSameIndentation : DiagnosticAnalyzerBa private static readonly Action AttributeArgumentListAction = HandleAttributeArgumentList; private static readonly Action BlockAction = HandleBlock; private static readonly Action SwitchStatementAction = HandleSwitchStatement; + private static readonly Action SwitchExpressionAction = HandleSwitchExpression; private static readonly Action InitializerExpressionAction = HandleInitializerExpression; private static readonly Action CollectionExpressionAction = HandleCollectionExpression; private static readonly Action AnonymousObjectCreationExpressionAction = HandleAnonymousObjectCreationExpression; @@ -76,6 +77,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(AttributeArgumentListAction, SyntaxKind.AttributeArgumentList); context.RegisterSyntaxNodeAction(BlockAction, SyntaxKind.Block); context.RegisterSyntaxNodeAction(SwitchStatementAction, SyntaxKind.SwitchStatement); + context.RegisterSyntaxNodeAction(SwitchExpressionAction, SyntaxKindEx.SwitchExpression); context.RegisterSyntaxNodeAction(InitializerExpressionAction, SyntaxKinds.InitializerExpression); context.RegisterSyntaxNodeAction(CollectionExpressionAction, SyntaxKindEx.CollectionExpression); context.RegisterSyntaxNodeAction(AnonymousObjectCreationExpressionAction, SyntaxKind.AnonymousObjectCreationExpression); @@ -263,6 +265,14 @@ private static void HandleInitializerExpression(SyntaxNodeAnalysisContext contex CheckElements(context, initializerExpression.Expressions); } + private static void HandleSwitchExpression(SyntaxNodeAnalysisContext context) + { + var switchExpression = (SwitchExpressionSyntaxWrapper)context.Node; + + CheckBraces(context, switchExpression.OpenBraceToken, switchExpression.CloseBraceToken); + CheckElements(context, switchExpression.Arms); + } + private static void HandleCollectionExpression(SyntaxNodeAnalysisContext context) { var collectionExpression = (CollectionExpressionSyntaxWrapper)context.Node; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1003SymbolsMustBeSpacedCorrectly.cs b/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1003SymbolsMustBeSpacedCorrectly.cs index 8ba542f56..ccfd257f4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1003SymbolsMustBeSpacedCorrectly.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1003SymbolsMustBeSpacedCorrectly.cs @@ -135,6 +135,7 @@ internal class SA1003SymbolsMustBeSpacedCorrectly : DiagnosticAnalyzerBase private static readonly Action EqualsValueClauseAction = HandleEqualsValueClause; private static readonly Action LambdaExpressionAction = HandleLambdaExpression; private static readonly Action ArrowExpressionClauseAction = HandleArrowExpressionClause; + private static readonly Action SwitchExpressionArmAction = HandleSwitchExpressionArm; /// /// Gets the descriptor for prefix unary expression that may not be followed by a comment. @@ -208,6 +209,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(EqualsValueClauseAction, SyntaxKind.EqualsValueClause); context.RegisterSyntaxNodeAction(LambdaExpressionAction, SyntaxKinds.LambdaExpression); context.RegisterSyntaxNodeAction(ArrowExpressionClauseAction, SyntaxKind.ArrowExpressionClause); + context.RegisterSyntaxNodeAction(SwitchExpressionArmAction, SyntaxKindEx.SwitchExpressionArm); } private static void HandleConstructorDeclaration(SyntaxNodeAnalysisContext context) @@ -379,6 +381,12 @@ private static void HandleArrowExpressionClause(SyntaxNodeAnalysisContext contex CheckToken(context, arrowExpressionClause.ArrowToken, true, true, true); } + private static void HandleSwitchExpressionArm(SyntaxNodeAnalysisContext context) + { + var switchExpressionArm = (SwitchExpressionArmSyntaxWrapper)context.Node; + CheckToken(context, switchExpressionArm.EqualsGreaterThanToken, true, true, true); + } + private static void CheckToken(SyntaxNodeAnalysisContext context, SyntaxToken token, bool withLeadingWhitespace, bool allowAtEndOfLine, bool withTrailingWhitespace, string? tokenText = null) { tokenText = tokenText ?? token.Text; From eef8026e49e84087cb87574ffe936551e9729403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Mon, 7 Sep 2026 21:17:40 +0200 Subject: [PATCH 17/19] Update some analyzers to also handle positional patterns #122 --- .../SA1111CSharp8UnitTests.cs | 28 ++++++++++--- .../SA1112CSharp8UnitTests.cs | 25 ++++++++--- .../SA1113CSharp8UnitTests.cs | 28 ++++++++++--- .../SA1115CSharp8UnitTests.cs | 10 ++--- .../SA1116CSharp8UnitTests.cs | 30 +++++++++++--- .../SA1117CSharp8UnitTests.cs | 41 +++++++++++++++++-- ...gParenthesisMustBeOnLineOfLastParameter.cs | 18 ++++++++ ...nthesisMustBeOnLineOfOpeningParenthesis.cs | 21 ++++++++++ ...ommaMustBeOnSameLineAsPreviousParameter.cs | 13 ++++++ .../SA1115ParameterMustFollowComma.cs | 8 ++++ ...rametersMustStartOnLineAfterDeclaration.cs | 13 ++++++ ...rametersMustBeOnSameLineOrSeparateLines.cs | 8 ++++ 12 files changed, 214 insertions(+), 29 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs index 08647c1cc..6b658f3c8 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs @@ -14,11 +14,10 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1111CSharp8UnitTests { /// - /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The - /// analyzer registers no pattern syntax kinds, so the closing parenthesis of a pattern is never inspected. + /// Verifies that the closing parenthesis of a positional pattern, which C# 8 introduced, is inspected the + /// same way as the closing parenthesis of a parameter list. /// /// A representing the asynchronous unit test. - // TODO: Should this trigger? [Fact] public async Task TestMultiLinePositionalPatternAsync() { @@ -37,12 +36,31 @@ public bool TestMethod(object value) { return value is Point(1, 2 - ); + [|)|]; } } "; - await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + var fixedCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + 2); + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs index 58c10d692..dd1ffc360 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs @@ -14,11 +14,10 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1112CSharp8UnitTests { /// - /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The - /// analyzer registers no pattern syntax kinds, so the parentheses of a pattern are never inspected. + /// Verifies that the parentheses of an empty positional pattern, which C# 8 introduced, are inspected the + /// same way as the parentheses of an empty parameter list. /// /// A representing the asynchronous unit test. - // TODO: Should this trigger? [Fact] public async Task TestMultiLinePositionalPatternAsync() { @@ -34,12 +33,28 @@ public class TestClass public bool TestMethod(object value) { return value is Point( - ); + [|)|]; } } "; - await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + var fixedCode = @"public class Point +{ + public void Deconstruct() + { + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(); + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs index 60e673608..e4abe117a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs @@ -14,11 +14,10 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1113CSharp8UnitTests { /// - /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The - /// analyzer registers no pattern syntax kinds, so the commas of a pattern are never inspected. + /// Verifies that the commas of a positional pattern, which C# 8 introduced, are inspected the same way as + /// the commas of a parameter list. /// /// A representing the asynchronous unit test. - // TODO: Should this trigger? [Fact] public async Task TestMultiLinePositionalPatternAsync() { @@ -36,12 +35,31 @@ public class TestClass public bool TestMethod(object value) { return value is Point(1 - , 2); + [|,|] 2); } } "; - await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + var fixedCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point(1, + 2); + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs index 20e7b2d8d..cc3f509f3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs @@ -7,16 +7,16 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; using Xunit; - using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopDiagnosticVerifier; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopDiagnosticVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1115ParameterMustFollowComma>; public partial class SA1115CSharp8UnitTests { /// - /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The - /// analyzer registers no pattern syntax kinds, so the subpatterns of a pattern are never inspected. + /// Verifies that the subpatterns of a positional pattern, which C# 8 introduced, are inspected the same way + /// as the parameters of a parameter list. /// /// A representing the asynchronous unit test. - // TODO: Should this trigger? [Fact] public async Task TestMultiLinePositionalPatternAsync() { @@ -35,7 +35,7 @@ public bool TestMethod(object value) { return value is Point(1, - 2); + [|2|]); } } "; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs index e47d06f91..a3e49d548 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs @@ -14,11 +14,10 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1116CSharp8UnitTests { /// - /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The - /// analyzer registers no pattern syntax kinds, so the subpatterns of a pattern are never inspected. + /// Verifies that the subpatterns of a positional pattern, which C# 8 introduced, are inspected the same way + /// as the parameters of a parameter list. /// /// A representing the asynchronous unit test. - // TODO: Should this trigger? [Fact] public async Task TestMultiLinePositionalPatternAsync() { @@ -35,14 +34,35 @@ public class TestClass { public bool TestMethod(object value) { - return value is Point(1, + return value is Point([|1|], 2 ); } } "; - await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + var fixedCode = @"public class Point +{ + public void Deconstruct(out int x, out int y) + { + x = 0; + y = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point( + 1, + 2 + ); + } +} +"; + + await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(true); } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs index 1002e22f7..e5607510f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs @@ -7,16 +7,18 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; using Xunit; - using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopDiagnosticVerifier; + using static StyleCop.Analyzers.Test.CSharp6.Verifiers.StyleCopDiagnosticVerifier< + StyleCop.Analyzers.ReadabilityRules.SA1117ParametersMustBeOnSameLineOrSeparateLines>; public partial class SA1117CSharp8UnitTests { /// - /// Verifies that a positional pattern, which C# 8 introduced, is not treated as a parameter list. The - /// analyzer registers no pattern syntax kinds, so the subpatterns of a pattern are never inspected. + /// Verifies that the subpatterns of a two-element positional pattern, which C# 8 introduced, are inspected + /// the same way as the parameters of a parameter list. With only two elements, any relative placement of + /// the first and second subpattern establishes a valid line pattern by definition, so no diagnostic is + /// produced (matching the behavior for a two-parameter parameter list). /// /// A representing the asynchronous unit test. - // TODO: Should this trigger? [Fact] public async Task TestMultiLinePositionalPatternAsync() { @@ -38,6 +40,37 @@ public bool TestMethod(object value) ); } } +"; + + await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); + } + + /// + /// Verifies that a diagnostic is produced when the subpatterns of a positional pattern, which C# 8 + /// introduced, are not all on the same line or each on a separate line. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task TestMultiLinePositionalPatternInvalidAsync() + { + var testCode = @"public class Point3 +{ + public void Deconstruct(out int x, out int y, out int z) + { + x = 0; + y = 0; + z = 0; + } +} + +public class TestClass +{ + public bool TestMethod(object value) + { + return value is Point3(1, 2, + [|3|]); + } +} "; await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(true); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs index 2d3ff9d5e..72bcd8783 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1111ClosingParenthesisMustBeOnLineOfLastParameter.cs @@ -70,6 +70,7 @@ internal class SA1111ClosingParenthesisMustBeOnLineOfLastParameter : DiagnosticA private static readonly Action AnonymousMethodExpressionAction = HandleAnonymousMethodExpression; private static readonly Action ParenthesizedLambdaExpressionAction = HandleParenthesizedLambdaExpression; private static readonly Action ArrayCreationExpressionAction = HandleArrayCreationExpression; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -96,6 +97,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(AnonymousMethodExpressionAction, SyntaxKind.AnonymousMethodExpression); context.RegisterSyntaxNodeAction(ParenthesizedLambdaExpressionAction, SyntaxKind.ParenthesizedLambdaExpression); context.RegisterSyntaxNodeAction(ArrayCreationExpressionAction, SyntaxKind.ArrayCreationExpression); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); } private static void HandleArrayCreationExpression(SyntaxNodeAnalysisContext context) @@ -231,6 +233,22 @@ private static void HandlePrimaryConstructorBaseType(SyntaxNodeAnalysisContext c CheckArgumentList(context, typeDeclarationSyntax.ArgumentList); } + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + var subpatterns = positionalPatternClause.Subpatterns; + + if (!subpatterns.Any()) + { + return; + } + + CheckIfLocationOfLastArgumentOrParameterAndCloseTokenAreTheSame( + context, + subpatterns.Last(), + positionalPatternClause.CloseParenToken); + } + private static void CheckParameterList(SyntaxNodeAnalysisContext context, ParameterListSyntax? parameterList) { if (parameterList == null || parameterList.IsMissing || !parameterList.Parameters.Any()) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis.cs index b233b3d62..740fa226f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis.cs @@ -48,6 +48,7 @@ internal class SA1112ClosingParenthesisMustBeOnLineOfOpeningParenthesis : Diagno private static readonly Action ConstructorDeclarationAction = HandleConstructorDeclaration; private static readonly Action InvocationExpressionAction = HandleInvocationExpression; private static readonly Action ObjectCreationExpressionAction = HandleObjectCreationExpression; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -61,6 +62,26 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(ConstructorDeclarationAction, SyntaxKind.ConstructorDeclaration); context.RegisterSyntaxNodeAction(InvocationExpressionAction, SyntaxKind.InvocationExpression); context.RegisterSyntaxNodeAction(ObjectCreationExpressionAction, SyntaxKind.ObjectCreationExpression); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); + } + + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + + if (positionalPatternClause.Subpatterns.Any()) + { + return; + } + + if (!positionalPatternClause.OpenParenToken.IsMissing && + !positionalPatternClause.CloseParenToken.IsMissing) + { + CheckIfLocationOfOpenAndCloseTokensAreTheSame( + context, + positionalPatternClause.OpenParenToken, + positionalPatternClause.CloseParenToken); + } } private static void HandleObjectCreationExpression(SyntaxNodeAnalysisContext context) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1113CommaMustBeOnSameLineAsPreviousParameter.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1113CommaMustBeOnSameLineAsPreviousParameter.cs index 50752b05c..87987969e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1113CommaMustBeOnSameLineAsPreviousParameter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1113CommaMustBeOnSameLineAsPreviousParameter.cs @@ -70,6 +70,7 @@ internal class SA1113CommaMustBeOnSameLineAsPreviousParameter : DiagnosticAnalyz private static readonly Action ArrayCreationExpressionAction = HandleArrayCreationExpression; private static readonly Action ConstructorInitializerAction = HandleConstructorInitializer; private static readonly Action WithElementAction = HandleWithElement; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -92,6 +93,7 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(ArrayCreationExpressionAction, SyntaxKind.ArrayCreationExpression); context.RegisterSyntaxNodeAction(ConstructorInitializerAction, SyntaxKinds.ConstructorInitializer); context.RegisterSyntaxNodeAction(WithElementAction, SyntaxKindEx.WithElement); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); } private static void HandleArrayCreationExpression(SyntaxNodeAnalysisContext context) @@ -213,6 +215,17 @@ private static void HandleWithElement(SyntaxNodeAnalysisContext context) HandleBaseArgumentListSyntax(context, withElement.ArgumentList); } + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + var subpatterns = positionalPatternClause.Subpatterns; + + if (subpatterns.Count > 1) + { + CheckIfCommasAreAtTheSameLineAsThePreviousParameter(context, subpatterns.GetWithSeparators()); + } + } + private static void HandleBaseArgumentListSyntax(SyntaxNodeAnalysisContext context, BaseArgumentListSyntax argumentList) { if (argumentList != null && !argumentList.IsMissing) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1115ParameterMustFollowComma.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1115ParameterMustFollowComma.cs index 75b2639bb..03fe352b5 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1115ParameterMustFollowComma.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1115ParameterMustFollowComma.cs @@ -73,6 +73,7 @@ internal class SA1115ParameterMustFollowComma : DiagnosticAnalyzerBase private static readonly Action ElementBindingExpressionAction = HandleElementBindingExpression; private static readonly Action ImplicitElementAccessAction = HandleImplicitElementAccess; private static readonly Action WithElementAction = HandleWithElement; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -97,6 +98,13 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(ElementBindingExpressionAction, SyntaxKind.ElementBindingExpression); context.RegisterSyntaxNodeAction(ImplicitElementAccessAction, SyntaxKind.ImplicitElementAccess); context.RegisterSyntaxNodeAction(WithElementAction, SyntaxKindEx.WithElement); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); + } + + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + AnalyzeSyntaxList(context, SyntaxFactory.SeparatedList(positionalPatternClause.Subpatterns.GetWithSeparators())); } private static void HandleWithElement(SyntaxNodeAnalysisContext context) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1116SplitParametersMustStartOnLineAfterDeclaration.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1116SplitParametersMustStartOnLineAfterDeclaration.cs index 2fb441c35..389717bab 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1116SplitParametersMustStartOnLineAfterDeclaration.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1116SplitParametersMustStartOnLineAfterDeclaration.cs @@ -69,6 +69,7 @@ internal class SA1116SplitParametersMustStartOnLineAfterDeclaration : Diagnostic private static readonly Action AnonymousMethodExpressionAction = HandleAnonymousMethodExpression; private static readonly Action ParenthesizedLambdaExpressionAction = HandleParenthesizedLambdaExpression; private static readonly Action WithElementAction = HandleWithElement; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } = @@ -93,6 +94,18 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(AnonymousMethodExpressionAction, SyntaxKind.AnonymousMethodExpression); context.RegisterSyntaxNodeAction(ParenthesizedLambdaExpressionAction, SyntaxKind.ParenthesizedLambdaExpression); context.RegisterSyntaxNodeAction(WithElementAction, SyntaxKindEx.WithElement); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); + } + + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + var subpatterns = positionalPatternClause.Subpatterns; + + if (subpatterns.Count > 1) + { + Analyze(context, positionalPatternClause.OpenParenToken, subpatterns[0], subpatterns[1]); + } } private static void HandleWithElement(SyntaxNodeAnalysisContext context) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1117ParametersMustBeOnSameLineOrSeparateLines.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1117ParametersMustBeOnSameLineOrSeparateLines.cs index 67b3f77ea..1310457db 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1117ParametersMustBeOnSameLineOrSeparateLines.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1117ParametersMustBeOnSameLineOrSeparateLines.cs @@ -79,6 +79,7 @@ internal class SA1117ParametersMustBeOnSameLineOrSeparateLines : DiagnosticAnaly private static readonly Action AnonymousMethodExpressionAction = HandleAnonymousMethodExpression; private static readonly Action ParenthesizedLambdaExpressionAction = HandleParenthesizedLambdaExpression; private static readonly Action WithElementAction = HandleWithElement; + private static readonly Action PositionalPatternClauseAction = HandlePositionalPatternClause; /// public override ImmutableArray SupportedDiagnostics { get; } @@ -102,6 +103,13 @@ protected override void HandleCompilationStart(CompilationStartAnalysisContext c context.RegisterSyntaxNodeAction(AnonymousMethodExpressionAction, SyntaxKind.AnonymousMethodExpression); context.RegisterSyntaxNodeAction(ParenthesizedLambdaExpressionAction, SyntaxKind.ParenthesizedLambdaExpression); context.RegisterSyntaxNodeAction(WithElementAction, SyntaxKindEx.WithElement); + context.RegisterSyntaxNodeAction(PositionalPatternClauseAction, SyntaxKindEx.PositionalPatternClause); + } + + private static void HandlePositionalPatternClause(SyntaxNodeAnalysisContext context) + { + var positionalPatternClause = (PositionalPatternClauseSyntaxWrapper)context.Node; + Analyze(context, SyntaxFactory.SeparatedList(positionalPatternClause.Subpatterns.GetWithSeparators())); } private static void HandleWithElement(SyntaxNodeAnalysisContext context) From 13421327f988350a3d72839701ac5870f6178465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Mon, 7 Sep 2026 21:48:36 +0200 Subject: [PATCH 18/19] Remove unnecessary text in comments #122 --- .../DocumentationRules/SA1600CSharp8UnitTests.cs | 1 + .../LayoutRules/SA1500CSharp8UnitTests.cs | 5 ++--- .../LayoutRules/SA1501CSharp8UnitTests.cs | 3 ++- .../LayoutRules/SA1502CSharp8UnitTests.cs | 5 ++--- .../LayoutRules/SA1503CSharp8UnitTests.cs | 2 +- .../LayoutRules/SA1505CSharp8UnitTests.cs | 6 ++---- .../LayoutRules/SA1507CSharp8UnitTests.cs | 2 +- .../LayoutRules/SA1508CSharp8UnitTests.cs | 3 +-- .../LayoutRules/SA1516CSharp8UnitTests.cs | 3 +-- .../MaintainabilityRules/SA1407CSharp8UnitTests.cs | 3 +-- .../MaintainabilityRules/SA1408CSharp8UnitTests.cs | 3 +-- .../NamingRules/SA1300CSharp8UnitTests.cs | 2 +- .../NamingRules/SA1316CSharp8UnitTests.cs | 2 +- .../OrderingRules/SA1201CSharp8UnitTests.cs | 2 +- .../OrderingRules/SA1202CSharp8UnitTests.cs | 3 +-- .../OrderingRules/SA1204CSharp8UnitTests.cs | 4 ++-- .../OrderingRules/SA1206CSharp8UnitTests.cs | 5 ++--- .../OrderingRules/SA1214CSharp8UnitTests.cs | 4 ++-- .../ReadabilityRules/SA1101CSharp8UnitTests.cs | 5 ++--- .../ReadabilityRules/SA1111CSharp8UnitTests.cs | 2 +- .../ReadabilityRules/SA1112CSharp8UnitTests.cs | 2 +- .../ReadabilityRules/SA1113CSharp8UnitTests.cs | 2 +- .../ReadabilityRules/SA1115CSharp8UnitTests.cs | 2 +- .../ReadabilityRules/SA1116CSharp8UnitTests.cs | 2 +- .../ReadabilityRules/SA1117CSharp8UnitTests.cs | 6 +++--- .../ReadabilityRules/SA1141CSharp8UnitTests.cs | 2 +- .../ReadabilityRules/SX1101CSharp8UnitTests.cs | 2 +- .../SpacingRules/SA1000CSharp8UnitTests.cs | 14 +++++++------- .../SpacingRules/SA1002CSharp8UnitTests.cs | 2 +- .../SpacingRules/SA1003CSharp8UnitTests.cs | 3 +-- .../SpacingRules/SA1010CSharp8UnitTests.cs | 8 ++++---- .../SpacingRules/SA1011CSharp8UnitTests.cs | 2 +- .../SpacingRules/SA1026CSharp8UnitTests.cs | 2 +- .../SpacingRules/SA1027CSharp8UnitTests.cs | 2 +- .../SpacingRules/SA1028CSharp8UnitTests.cs | 2 +- 35 files changed, 54 insertions(+), 64 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs index b5878d34f..a5b5cf326 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs @@ -22,6 +22,7 @@ public partial class SA1600CSharp8UnitTests /// interface member. /// /// A representing the asynchronous unit test. + // TODO: Check this!!! [Fact] public async Task TestInterfaceMembersWithoutDocumentationAsync() { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs index b907954a0..8b3305173 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1500CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules public partial class SA1500CSharp8UnitTests { /// - /// Verifies that a single-line switch expression, which C# 8 introduced, is not inspected. + /// Verifies that a single-line switch expression is not inspected. /// /// A representing the asynchronous unit test. [Fact] @@ -33,8 +33,7 @@ public int TestMethod(int value) } /// - /// Verifies that diagnostics will be reported for the braces of a multi-line switch expression, which C# 8 - /// introduced, when they share a line with other code. + /// Verifies that diagnostics will be reported for the braces of a multi-line switch expression when they share a line with other code. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs index f1f1821c7..7147dbae2 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1501CSharp8UnitTests.cs @@ -14,7 +14,8 @@ namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules public partial class SA1501CSharp8UnitTests { /// - /// Verifies that a single-line switch expression, which C# 8 introduced, is not inspected. The analyzer registers statement kinds, and a switch expression is an expression. + /// Verifies that a single-line switch expression is not inspected. + /// The analyzer registers statement kinds, and a switch expression is an expression. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs index d361a3d7f..1b6e8ae5c 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1502CSharp8UnitTests.cs @@ -14,8 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules public partial class SA1502CSharp8UnitTests { /// - /// Verifies that the default implementation of an interface member, which C# 8 introduced, must not be on a - /// single line. + /// Verifies that the default implementation of an interface member must not be on a single line. /// /// A representing the asynchronous unit test. [Fact] @@ -39,7 +38,7 @@ void DefaultMethod() } /// - /// Verifies that a static local function, which C# 8 introduced, must not be on a single line. + /// Verifies that a static local function must not be on a single line. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs index 386d64505..25cfc4ac4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1503CSharp8UnitTests.cs @@ -30,7 +30,7 @@ public void Method() } /// - /// Verifies that the body of an await foreach statement, which C# 8 introduced, must be enclosed in braces. + /// Verifies that the body of an await foreach statement must be enclosed in braces. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs index 01647da84..14c530378 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1505CSharp8UnitTests.cs @@ -14,8 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules public partial class SA1505CSharp8UnitTests { /// - /// Verifies that a blank line between an opening brace and a nullable directive, which C# 8 introduced, is - /// reported. + /// Verifies that a blank line between an opening brace and a nullable directive is reported. /// /// A representing the asynchronous unit test. [Fact] @@ -44,8 +43,7 @@ public void Method() } /// - /// Verifies that the opening brace of a switch expression, which C# 8 introduced, must not be followed by a - /// blank line. + /// Verifies that the opening brace of a switch expression must not be followed by a blank line. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs index a334463fa..4fc8c0e61 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1507CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules public partial class SA1507CSharp8UnitTests { /// - /// Verifies that multiple blank lines before a nullable directive, which C# 8 introduced, are reported. + /// Verifies that multiple blank lines before a nullable directive are reported. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs index 71a32927b..4c34ed757 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1508CSharp8UnitTests.cs @@ -14,8 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules public partial class SA1508CSharp8UnitTests { /// - /// Verifies that the closing brace of a switch expression, which C# 8 introduced, must not be preceded by a - /// blank line. + /// Verifies that the closing brace of a switch expression must not be preceded by a blank line. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs index c540a89a6..498cd755d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1516CSharp8UnitTests.cs @@ -14,8 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules public partial class SA1516CSharp8UnitTests { /// - /// Verifies that a nullable directive, which C# 8 introduced, does not count as the blank line required - /// between two elements. + /// Verifies that a nullable directive does not count as the blank line required between two elements. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs index c42be52cd..582629094 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1407CSharp8UnitTests.cs @@ -14,8 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.MaintainabilityRules public partial class SA1407CSharp8UnitTests { /// - /// Verifies that arithmetic precedence is still checked on the right hand side of a null-coalescing - /// assignment, which C# 8 introduced. + /// Verifies that arithmetic precedence is still checked on the right hand side of a null-coalescing assignment. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs index 62a359396..8e379a93a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1408CSharp8UnitTests.cs @@ -14,8 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.MaintainabilityRules public partial class SA1408CSharp8UnitTests { /// - /// Verifies that conditional precedence is still checked on the right hand side of a null-coalescing - /// assignment, which C# 8 introduced. + /// Verifies that conditional precedence is still checked on the right hand side of a null-coalescing assignment. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs index 6368d2c3c..301ee3732 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1300CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.NamingRules public partial class SA1300CSharp8UnitTests { /// - /// Verifies that a static local function, which C# 8 introduced, must begin with an upper-case letter. + /// Verifies that a static local function must begin with an upper-case letter. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs index a6b176514..8f4e141e1 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/NamingRules/SA1316CSharp8UnitTests.cs @@ -25,7 +25,7 @@ public partial class SA1316CSharp8UnitTests "; /// - /// Verifies that the names of an await foreach deconstruction, which C# 8 introduced, are exempt from the + /// Verifies that the names of an await foreach deconstruction are exempt from the /// configured casing just like those of an ordinary foreach deconstruction. /// /// A representing the asynchronous unit test. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs index d9bcd2d5c..fe881aaec 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1201CSharp8UnitTests.cs @@ -13,7 +13,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules public partial class SA1201CSharp8UnitTests { /// - /// Verifies that readonly instance members, which C# 8 introduced, are ordered by element kind like any other member. + /// Verifies that readonly instance members are ordered by element kind like any other member. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs index ad18edcf9..46591affe 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1202CSharp8UnitTests.cs @@ -58,8 +58,7 @@ public async Task TestPropertiesOfInterfaceAsync() } /// - /// Verifies that readonly instance members, which C# 8 introduced, are ordered by access like any other - /// member. + /// Verifies that readonly instance members are ordered by access like any other member. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs index 52f9b2617..3b6f5807e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1204CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules public partial class SA1204CSharp8UnitTests { /// - /// Verifies that a readonly instance member, which C# 8 introduced, is ordered as an instance member. + /// Verifies that a readonly instance member is ordered as an instance member. /// /// A representing the asynchronous unit test. [Fact] @@ -40,7 +40,7 @@ public async Task TestReadonlyInstanceMemberAsync() } /// - /// Verifies that a static interface member, which C# 8 introduced, must appear before an instance one. + /// Verifies that a static interface member must appear before an instance one. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs index 15c56119f..0ba002fbf 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1206CSharp8UnitTests.cs @@ -13,7 +13,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules public partial class SA1206CSharp8UnitTests { /// - /// Verifies that an access modifier must precede the readonly keyword of a readonly instance member, which C# 8 introduced. + /// Verifies that an access modifier must precede the readonly keyword of a readonly instance member. /// /// A representing the asynchronous unit test. [Fact] @@ -37,8 +37,7 @@ public async Task TestReadonlyInstanceMemberAsync() } /// - /// Verifies that the static keyword of a static local function, which C# 8 introduced, must precede the - /// other modifiers. + /// Verifies that the static keyword of a static local function must precede the other modifiers. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs index f63524882..0c6f3581f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/OrderingRules/SA1214CSharp8UnitTests.cs @@ -14,8 +14,8 @@ namespace StyleCop.Analyzers.Test.CSharp8.OrderingRules public partial class SA1214CSharp8UnitTests { /// - /// Verifies that the rule orders readonly fields only, and that a readonly instance member, - /// which C# 8 introduced, is not treated as a readonly element. + /// Verifies that the rule orders readonly fields only, and that a readonly instance member + /// is not treated as a readonly element. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs index 2b8fa4d89..3d74fb2e3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1101CSharp8UnitTests.cs @@ -31,8 +31,7 @@ public bool Method(Test arg) } /// - /// Verifies that a local call in the body of an await foreach statement, which C# 8 introduced, must be - /// prefixed with this. + /// Verifies that a local call in the body of an await foreach statement must be prefixed with this. /// /// A representing the asynchronous unit test. [Fact] @@ -78,7 +77,7 @@ public void Handle(int value) } /// - /// Verifies that a local call in the default implementation of an interface member, which C# 8 introduced, + /// Verifies that a local call in the default implementation of an interface member /// must be prefixed with this. /// /// A representing the asynchronous unit test. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs index 6b658f3c8..27387c6ad 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1111CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1111CSharp8UnitTests { /// - /// Verifies that the closing parenthesis of a positional pattern, which C# 8 introduced, is inspected the + /// Verifies that the closing parenthesis of a positional pattern is inspected the /// same way as the closing parenthesis of a parameter list. /// /// A representing the asynchronous unit test. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs index dd1ffc360..13a5438d6 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1112CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1112CSharp8UnitTests { /// - /// Verifies that the parentheses of an empty positional pattern, which C# 8 introduced, are inspected the + /// Verifies that the parentheses of an empty positional pattern are inspected the /// same way as the parentheses of an empty parameter list. /// /// A representing the asynchronous unit test. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs index e4abe117a..3f203026f 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1113CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1113CSharp8UnitTests { /// - /// Verifies that the commas of a positional pattern, which C# 8 introduced, are inspected the same way as + /// Verifies that the commas of a positional pattern are inspected the same way as /// the commas of a parameter list. /// /// A representing the asynchronous unit test. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs index cc3f509f3..0a0485a3b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1115CSharp8UnitTests.cs @@ -13,7 +13,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1115CSharp8UnitTests { /// - /// Verifies that the subpatterns of a positional pattern, which C# 8 introduced, are inspected the same way + /// Verifies that the subpatterns of a positional pattern are inspected the same way /// as the parameters of a parameter list. /// /// A representing the asynchronous unit test. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs index a3e49d548..1547574ba 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1116CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1116CSharp8UnitTests { /// - /// Verifies that the subpatterns of a positional pattern, which C# 8 introduced, are inspected the same way + /// Verifies that the subpatterns of a positional pattern are inspected the same way /// as the parameters of a parameter list. /// /// A representing the asynchronous unit test. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs index e5607510f..d9470f6d2 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1117CSharp8UnitTests.cs @@ -13,7 +13,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1117CSharp8UnitTests { /// - /// Verifies that the subpatterns of a two-element positional pattern, which C# 8 introduced, are inspected + /// Verifies that the subpatterns of a two-element positional pattern are inspected /// the same way as the parameters of a parameter list. With only two elements, any relative placement of /// the first and second subpattern establishes a valid line pattern by definition, so no diagnostic is /// produced (matching the behavior for a two-parameter parameter list). @@ -46,8 +46,8 @@ public bool TestMethod(object value) } /// - /// Verifies that a diagnostic is produced when the subpatterns of a positional pattern, which C# 8 - /// introduced, are not all on the same line or each on a separate line. + /// Verifies that a diagnostic is produced when the subpatterns of a positional pattern + /// are not all on the same line or each on a separate line. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs index 60537007b..10c705c93 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SA1141CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SA1141CSharp8UnitTests { /// - /// Verifies that a tuple pattern, which C# 8 introduced, is not reported. The analyzer inspects type + /// Verifies that a tuple pattern is not reported. The analyzer inspects type /// syntax, not patterns, so the pattern itself is never a candidate for tuple syntax. /// /// A representing the asynchronous unit test. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs index 04f062f4d..b457487e1 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/ReadabilityRules/SX1101CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.ReadabilityRules public partial class SX1101CSharp8UnitTests { /// - /// Verifies that a this prefix in the default implementation of an interface member, which C# 8 introduced, + /// Verifies that a this prefix in the default implementation of an interface member /// is detected and removed. /// /// A representing the asynchronous unit test. diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs index c903fe542..d25afc320 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1000CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules public partial class SA1000CSharp8UnitTests { /// - /// Verifies the handling of the stackalloc keyword before a constructed unmanaged type, which C# 8 allows. + /// Verifies the handling of the stackalloc keyword before a constructed unmanaged type. /// /// A representing the asynchronous unit test. [Fact] @@ -54,7 +54,7 @@ public unsafe void TestMethod() } /// - /// Verifies the handling of the sizeof keyword applied to a constructed unmanaged type, which C# 8 allows. + /// Verifies the handling of the sizeof keyword applied to a constructed unmanaged type. /// /// A representing the asynchronous unit test. [Fact] @@ -94,7 +94,7 @@ public unsafe void TestMethod() } /// - /// Verifies the handling of the stackalloc keyword in a nested expression, which C# 8 allows. + /// Verifies the handling of the stackalloc keyword in a nested expression. /// /// A representing the asynchronous unit test. [Fact] @@ -136,7 +136,7 @@ public void Bar(Span value) } /// - /// Verifies the handling of the using keyword of a using declaration, which C# 8 introduced. + /// Verifies the handling of the using keyword of a using declaration. /// The keyword is followed by a type rather than by an opening parenthesis here. /// /// A representing the asynchronous unit test. @@ -171,7 +171,7 @@ public void TestMethod() } /// - /// Verifies the handling of the using keyword of an await using declaration, which C# 8 introduced. + /// Verifies the handling of the using keyword of an await using declaration. /// /// A representing the asynchronous unit test. [Fact] @@ -207,7 +207,7 @@ public async Task TestMethodAsync() } /// - /// Verifies the handling of the foreach keyword of an await foreach statement, which C# 8 introduced. The + /// Verifies the handling of the foreach keyword of an await foreach statement. The /// await and foreach keywords are adjacent here. /// /// A representing the asynchronous unit test. @@ -248,7 +248,7 @@ public async Task TestMethodAsync(IAsyncEnumerable values) } /// - /// Verifies the handling of the switch keyword of a switch expression, which C# 8 introduced. The keyword is + /// Verifies the handling of the switch keyword of a switch expression. The keyword is /// followed by a brace here rather than by an opening parenthesis, and is preceded by the governing /// expression. Only the side after the keyword is checked, so the second case is not reported. /// diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs index 39e41a517..f66b32e23 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1002CSharp8UnitTests.cs @@ -54,7 +54,7 @@ public void TestMethod(object?[] arguments) } /// - /// Verifies the handling of the semicolon of a using declaration, which C# 8 introduced. + /// Verifies the handling of the semicolon of a using declaration. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs index b01f613d9..9f44c7bbd 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1003CSharp8UnitTests.cs @@ -219,8 +219,7 @@ public void TestMethod(System.Action? x) } /// - /// Verifies that the arrow of a switch expression arm, which C# 8 introduced, must be surrounded by - /// whitespace. + /// Verifies that the arrow of a switch expression arm must be surrounded by whitespace. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs index 47def367a..259bfdb32 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1010CSharp8UnitTests.cs @@ -15,7 +15,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules public partial class SA1010CSharp8UnitTests { /// - /// Verifies the handling of a stackalloc of a constructed unmanaged type, which C# 8 allows. + /// Verifies the handling of a stackalloc of a constructed unmanaged type. /// /// A representing the asynchronous unit test. [Fact] @@ -107,7 +107,7 @@ public unsafe void TestMethod(Foo* data) } /// - /// Verifies the handling of the opening bracket of a stackalloc in a nested expression, which C# 8 allows. + /// Verifies the handling of the opening bracket of a stackalloc in a nested expression. /// /// A representing the asynchronous unit test. [Fact] @@ -155,7 +155,7 @@ public void Bar(Span value) } /// - /// Verifies the handling of an index-from-end argument, which C# 8 introduced. + /// Verifies the handling of an index-from-end argument. /// /// A representing the asynchronous unit test. [Fact] @@ -193,7 +193,7 @@ public int TestMethod(int[] values) } /// - /// Verifies the handling of a range argument, which C# 8 introduced. + /// Verifies the handling of a range argument. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs index 4cf2d20ba..db6c777c7 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1011CSharp8UnitTests.cs @@ -121,7 +121,7 @@ public void TestMethod(int[] arg) } /// - /// Verifies the handling of the closing bracket of a stackalloc in a nested expression, which C# 8 allows. + /// Verifies the handling of the closing bracket of a stackalloc in a nested expression. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs index 2c9146ab6..635b6e9cf 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1026CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules public partial class SA1026CSharp8UnitTests { /// - /// Verifies the handling of an implicitly typed stackalloc in a nested expression, which C# 8 allows. + /// Verifies the handling of an implicitly typed stackalloc in a nested expression. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs index 6933d8d93..3537a9301 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1027CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules public partial class SA1027CSharp8UnitTests { /// - /// Verifies that a tab inside a nullable directive, which C# 8 introduced, is reported. + /// Verifies that a tab inside a nullable directive is reported. /// /// A representing the asynchronous unit test. [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs index f233be333..7e013ce2a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/SpacingRules/SA1028CSharp8UnitTests.cs @@ -14,7 +14,7 @@ namespace StyleCop.Analyzers.Test.CSharp8.SpacingRules public partial class SA1028CSharp8UnitTests { /// - /// Verifies that trailing whitespace after a nullable directive, which C# 8 introduced, is reported. + /// Verifies that trailing whitespace after a nullable directive is reported. /// /// A representing the asynchronous unit test. [Fact] From 618413f1a378543c19c9de406ea805f98d96a831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Tue, 8 Sep 2026 06:29:43 +0200 Subject: [PATCH 19/19] Improve SA1600 test #122 --- .../DocumentationRules/SA1600CSharp8UnitTests.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs index a5b5cf326..47fc4112e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/DocumentationRules/SA1600CSharp8UnitTests.cs @@ -22,7 +22,7 @@ public partial class SA1600CSharp8UnitTests /// interface member. /// /// A representing the asynchronous unit test. - // TODO: Check this!!! + // TODO: Investigate this behavior (the private members)! [Fact] public async Task TestInterfaceMembersWithoutDocumentationAsync() { @@ -31,11 +31,19 @@ public async Task TestInterfaceMembersWithoutDocumentationAsync() /// public interface ITest { - void [|DefaultMethod|]() + void [|TestMethod1|]() { } - static void [|StaticMethod|]() + private void [|TestMethod2|]() + { + } + + static void [|TestMethod3|]() + { + } + + private static void [|TestMethod4|]() { } }