From a254af758b493ced091ee7157c551a6cbb23939b Mon Sep 17 00:00:00 2001 From: Benjamin Michaelis Date: Mon, 16 Mar 2026 16:42:25 -0700 Subject: [PATCH] Fix INTL0101 code fix NotImplementedException for non-class declaration types The AttributesOnSeparateLines code fix only handled ClassDeclarationSyntax, MethodDeclarationSyntax, PropertyDeclarationSyntax, and FieldDeclarationSyntax. Any other declaration type flagged by the analyzer (structs, records, interfaces, enums, constructors, enum members, indexers, etc.) would throw NotImplementedException when a user tried to apply the code fix. Replace the two switch expressions with a MemberDeclarationSyntax base class pattern match in RegisterCodeFixesAsync, so unsupported node types silently skip registering the code fix rather than crash. PutOnSeparateLine is updated to accept MemberDeclarationSyntax directly, calling AttributeLists and WithAttributeLists() without any helper switch methods. Add code fix tests for: struct, record, interface, enum type, constructor, enum member, and indexer declarations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AttributesOnSeparateLines.cs | 35 +- .../AttributesOnSeparateLinesTests.cs | 306 ++++++++++++++++++ 2 files changed, 311 insertions(+), 30 deletions(-) diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer.CodeFixes/AttributesOnSeparateLines.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer.CodeFixes/AttributesOnSeparateLines.cs index d8f6e9d..c3f7b86 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer.CodeFixes/AttributesOnSeparateLines.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer.CodeFixes/AttributesOnSeparateLines.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; @@ -48,8 +47,8 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) attributeList = attributeList.Parent; } - // Get the class, method or property adjacent to the AttributeList - if (attributeList?.Parent is not SyntaxNode parentDeclaration) + // Get the member declaration adjacent to the AttributeList + if (attributeList?.Parent is not MemberDeclarationSyntax parentDeclaration) { return; } @@ -63,12 +62,12 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) diagnostic); } - private static async Task PutOnSeparateLine(Document document, SyntaxNode parentDeclaration, CancellationToken cancellationToken) + private static async Task PutOnSeparateLine(Document document, MemberDeclarationSyntax parentDeclaration, CancellationToken cancellationToken) { var attributeLists = new SyntaxList(); // put every attribute into it's own attributelist eg.: [A,B,C] => [A][B][C] - foreach (AttributeSyntax attribute in GetAttributeListSyntaxes(parentDeclaration).SelectMany(l => l.Attributes)) + foreach (AttributeSyntax attribute in parentDeclaration.AttributeLists.SelectMany(l => l.Attributes)) { attributeLists = attributeLists.Add( SyntaxFactory.AttributeList( @@ -80,7 +79,7 @@ private static async Task PutOnSeparateLine(Document document, SyntaxN } // the formatter-annotation will wrap every attribute on a separate line - SyntaxNode newNode = BuildNodeWithAttributeLists(parentDeclaration, attributeLists) + MemberDeclarationSyntax newNode = parentDeclaration.WithAttributeLists(attributeLists) .WithAdditionalAnnotations(Formatter.Annotation); // Replace the old local declaration with the new local declaration. @@ -90,29 +89,5 @@ private static async Task PutOnSeparateLine(Document document, SyntaxN return document.WithSyntaxRoot(newRoot); } - - private static IEnumerable GetAttributeListSyntaxes(SyntaxNode node) - { - return node switch - { - ClassDeclarationSyntax c => c.AttributeLists, - MethodDeclarationSyntax m => m.AttributeLists, - PropertyDeclarationSyntax p => p.AttributeLists, - FieldDeclarationSyntax f => f.AttributeLists, - _ => throw new NotImplementedException(), - }; - } - - private static SyntaxNode BuildNodeWithAttributeLists(SyntaxNode node, SyntaxList attributeLists) - { - return node switch - { - ClassDeclarationSyntax c => c.WithAttributeLists(attributeLists), - MethodDeclarationSyntax m => m.WithAttributeLists(attributeLists), - PropertyDeclarationSyntax p => p.WithAttributeLists(attributeLists), - FieldDeclarationSyntax f => f.WithAttributeLists(attributeLists), - _ => throw new NotImplementedException(), - }; - } } } diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/AttributesOnSeparateLinesTests.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/AttributesOnSeparateLinesTests.cs index b64e6ad..5125273 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/AttributesOnSeparateLinesTests.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/AttributesOnSeparateLinesTests.cs @@ -390,6 +390,312 @@ static void Main() await VerifyCSharpFix(test, fixTest); } + [TestMethod] + public async Task StructAttributeLineViolation_CodeFix_TwoAttributesOnSameLine_TwoAttributesOnSeparateLines() + { + string test = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + [A][B] + struct MyStruct + { + } +}"; + + string fixTest = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + [A] + [B] + struct MyStruct + { + } +}"; + await VerifyCSharpFix(test, fixTest); + } + + [TestMethod] + public async Task RecordAttributeLineViolation_CodeFix_TwoAttributesOnSameLine_TwoAttributesOnSeparateLines() + { + string test = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + [A][B] + record MyRecord + { + } +}"; + + string fixTest = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + [A] + [B] + record MyRecord + { + } +}"; + await VerifyCSharpFix(test, fixTest); + } + + [TestMethod] + public async Task InterfaceAttributeLineViolation_CodeFix_TwoAttributesOnSameLine_TwoAttributesOnSeparateLines() + { + string test = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + [A][B] + interface IMyInterface + { + } +}"; + + string fixTest = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + [A] + [B] + interface IMyInterface + { + } +}"; + await VerifyCSharpFix(test, fixTest); + } + + [TestMethod] + public async Task EnumAttributeLineViolation_CodeFix_TwoAttributesOnSameLine_TwoAttributesOnSeparateLines() + { + string test = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + [A][B] + enum MyEnum + { + Foo + } +}"; + + string fixTest = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + [A] + [B] + enum MyEnum + { + Foo + } +}"; + await VerifyCSharpFix(test, fixTest); + } + + [TestMethod] + public async Task ConstructorAttributeLineViolation_CodeFix_TwoAttributesOnSameLine_TwoAttributesOnSeparateLines() + { + string test = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + class Program + { + [A][B] + Program() + { + } + } +}"; + + string fixTest = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + class Program + { + [A] + [B] + Program() + { + } + } +}"; + await VerifyCSharpFix(test, fixTest); + } + + [TestMethod] + public async Task EnumMemberAttributeLineViolation_CodeFix_TwoAttributesOnSameLine_TwoAttributesOnSeparateLines() + { + string test = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + enum MyEnum + { + [A][B] + Bar + } +}"; + + string fixTest = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + enum MyEnum + { + [A] + [B] + Bar + } +}"; + await VerifyCSharpFix(test, fixTest); + } + + [TestMethod] + public async Task IndexerAttributeLineViolation_CodeFix_TwoAttributesOnSameLine_TwoAttributesOnSeparateLines() + { + string test = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + class Program + { + [A][B] + int this[int i] { get => 0; } + } +}"; + + string fixTest = @"using System; + +namespace ConsoleApp +{ + class AAttribute : Attribute + { + } + + class BAttribute : Attribute + { + } + + class Program + { + [A] + [B] + int this[int i] { get => 0; } + } +}"; + await VerifyCSharpFix(test, fixTest); + } + [TestMethod] [Description("Analyzer should not report on generated code")] public void AttributesOnSameLine_InGeneratedCode_NoDiagnostic()