From 80ce2e3ce314b20298014db2a4972bcfaba4e4ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:31:08 +0000 Subject: [PATCH 1/7] Initial plan From 3668c72c97ab562c159139fa20359086b2995a77 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:34:51 +0000 Subject: [PATCH 2/7] Add test cases and fix for INTL0003 triggering on test methods with underscores Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com> --- .../NamingMethodPascalTests.cs | 93 +++++++++++++++++++ .../Analyzers/NamingMethodPascal.cs | 30 ++++++ 2 files changed, 123 insertions(+) diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/NamingMethodPascalTests.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/NamingMethodPascalTests.cs index 8e540af5..3051d4d1 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/NamingMethodPascalTests.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer.Test/NamingMethodPascalTests.cs @@ -442,6 +442,99 @@ void IInterface.foo() { } VerifyCSharpDiagnostic(test, expected1, expected2); } + [TestMethod] + [Description("Test method with underscores should not trigger INTL0003")] + public void TestMethodWithUnderscores_MSTest_NoDiagnosticInformationReturned() + { + string test = @" + using System; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + namespace ConsoleApplication1 + { + [TestClass] + public class TypeName + { + [TestMethod] + public void FooThing_IsFooThing_HasFooThing() + { + Assert.IsTrue(true); + } + } + }"; + + VerifyCSharpDiagnostic(test); + } + + [TestMethod] + [Description("Test method with underscores should not trigger INTL0003 - xUnit Fact")] + public void TestMethodWithUnderscores_XunitFact_NoDiagnosticInformationReturned() + { + string test = @" + using System; + using Xunit; + + namespace ConsoleApplication1 + { + public class TypeName + { + [Fact] + public void FooThing_IsFooThing_HasFooThing() + { + Assert.True(true); + } + } + }"; + + VerifyCSharpDiagnostic(test); + } + + [TestMethod] + [Description("Test method with underscores should not trigger INTL0003 - xUnit Theory")] + public void TestMethodWithUnderscores_XunitTheory_NoDiagnosticInformationReturned() + { + string test = @" + using System; + using Xunit; + + namespace ConsoleApplication1 + { + public class TypeName + { + [Theory] + public void FooThing_IsFooThing_HasFooThing() + { + Assert.True(true); + } + } + }"; + + VerifyCSharpDiagnostic(test); + } + + [TestMethod] + [Description("Test method with underscores should not trigger INTL0003 - NUnit Test")] + public void TestMethodWithUnderscores_NUnitTest_NoDiagnosticInformationReturned() + { + string test = @" + using System; + using NUnit.Framework; + + namespace ConsoleApplication1 + { + public class TypeName + { + [Test] + public void FooThing_IsFooThing_HasFooThing() + { + Assert.That(true, Is.True); + } + } + }"; + + VerifyCSharpDiagnostic(test); + } + protected override CodeFixProvider GetCSharpCodeFixProvider() { diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs index b572f48c..d9ad0bd0 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs @@ -95,9 +95,39 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) return; } + // Skip test methods - they commonly use underscores for readability (e.g., "Method_Scenario_ExpectedResult") + if (IsTestMethod(namedTypeSymbol)) + { + return; + } + Diagnostic diagnostic = Diagnostic.Create(_Rule, namedTypeSymbol.Locations[0], name); context.ReportDiagnostic(diagnostic); } + + private static bool IsTestMethod(IMethodSymbol methodSymbol) + { + // Common test framework attributes + string[] testAttributeNames = + [ + "TestMethod", // MSTest + "TestMethodAttribute", + "Fact", // xUnit + "FactAttribute", + "Theory", // xUnit + "TheoryAttribute", + "Test", // NUnit + "TestAttribute", + "TestCase", // NUnit + "TestCaseAttribute", + "TestCaseSource", // NUnit + "TestCaseSourceAttribute" + ]; + + ImmutableArray attributes = methodSymbol.GetAttributes(); + return attributes.Any(attribute => + testAttributeNames.Contains(attribute.AttributeClass?.Name)); + } } } From e773091a87e727ca8b99a4bd83149e6170bd4dcd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:35:16 +0000 Subject: [PATCH 3/7] Update documentation for INTL0003 to explain test method exception Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com> --- docs/analyzers/00XX.Naming.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/analyzers/00XX.Naming.md b/docs/analyzers/00XX.Naming.md index 2724c6be..28951937 100644 --- a/docs/analyzers/00XX.Naming.md +++ b/docs/analyzers/00XX.Naming.md @@ -49,6 +49,8 @@ class SomeClass Methods, including local functions, should be PascalCase +**Note:** Test methods decorated with test framework attributes (e.g., `[TestMethod]`, `[Fact]`, `[Theory]`, `[Test]`) are exempt from this rule, as they commonly use underscores for readability (e.g., `Method_Scenario_ExpectedResult`). + **Allowed** ```c# class SomeClass @@ -66,6 +68,19 @@ class SomeClass } ``` +**Allowed (Test Methods)** +```c# +[TestClass] +public class SomeClassTests +{ + [TestMethod] + public void GetEmpty_WhenCalled_ReturnsEmptyString() + { + // Test implementation + } +} +``` + **Disallowed** ```c# class SomeClass From 792cbe26f932d3cd35d4609636f47c21d62006ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:36:14 +0000 Subject: [PATCH 4/7] Improve null handling clarity in IsTestMethod Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com> --- .../IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs index d9ad0bd0..34387451 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs @@ -127,7 +127,7 @@ private static bool IsTestMethod(IMethodSymbol methodSymbol) ImmutableArray attributes = methodSymbol.GetAttributes(); return attributes.Any(attribute => - testAttributeNames.Contains(attribute.AttributeClass?.Name)); + attribute.AttributeClass?.Name is string name && testAttributeNames.Contains(name)); } } } From b2fe63a4babea662c875b1d35a5ee3935f0dda11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:35:49 +0000 Subject: [PATCH 5/7] Use namespace-based test detection for more flexible test framework support Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com> --- .../Analyzers/NamingMethodPascal.cs | 45 ++++++++++++------- docs/analyzers/00XX.Naming.md | 2 +- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs index 34387451..ee7eddec 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs @@ -108,26 +108,41 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) private static bool IsTestMethod(IMethodSymbol methodSymbol) { - // Common test framework attributes - string[] testAttributeNames = + // Test framework namespaces - checking namespace is more flexible than specific attribute names + string[] testFrameworkNamespaces = [ - "TestMethod", // MSTest - "TestMethodAttribute", - "Fact", // xUnit - "FactAttribute", - "Theory", // xUnit - "TheoryAttribute", - "Test", // NUnit - "TestAttribute", - "TestCase", // NUnit - "TestCaseAttribute", - "TestCaseSource", // NUnit - "TestCaseSourceAttribute" + "Xunit", // xUnit + "NUnit.Framework", // NUnit + "Microsoft.VisualStudio.TestTools.UnitTesting", // MSTest + "TUnit.Core" // TUnit ]; ImmutableArray attributes = methodSymbol.GetAttributes(); return attributes.Any(attribute => - attribute.AttributeClass?.Name is string name && testAttributeNames.Contains(name)); + { + if (attribute.AttributeClass == null) + { + return false; + } + + // Check namespace first (more robust for production code) + string containingNamespace = attribute.AttributeClass.ContainingNamespace?.ToDisplayString(); + if (containingNamespace != null && + testFrameworkNamespaces.Any(ns => containingNamespace.StartsWith(ns, StringComparison.Ordinal))) + { + return true; + } + + // Fallback: check attribute name for common test attributes + // This helps in test environments where namespace metadata may be incomplete + string attributeName = attribute.AttributeClass.Name; + return attributeName == "TestMethod" || attributeName == "TestMethodAttribute" || + attributeName == "Fact" || attributeName == "FactAttribute" || + attributeName == "Theory" || attributeName == "TheoryAttribute" || + attributeName == "Test" || attributeName == "TestAttribute" || + attributeName == "TestCase" || attributeName == "TestCaseAttribute" || + attributeName == "TestCaseSource" || attributeName == "TestCaseSourceAttribute"; + }); } } } diff --git a/docs/analyzers/00XX.Naming.md b/docs/analyzers/00XX.Naming.md index 28951937..7b822b8f 100644 --- a/docs/analyzers/00XX.Naming.md +++ b/docs/analyzers/00XX.Naming.md @@ -49,7 +49,7 @@ class SomeClass Methods, including local functions, should be PascalCase -**Note:** Test methods decorated with test framework attributes (e.g., `[TestMethod]`, `[Fact]`, `[Theory]`, `[Test]`) are exempt from this rule, as they commonly use underscores for readability (e.g., `Method_Scenario_ExpectedResult`). +**Note:** Test methods decorated with test framework attributes from xUnit, NUnit, MSTest, or TUnit are exempt from this rule, as they commonly use underscores for readability (e.g., `Method_Scenario_ExpectedResult`). Any attribute from these framework namespaces will be recognized automatically. **Allowed** ```c# From 99ba578b2aec2f1c496bb840c73ac795c60f2224 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:36:58 +0000 Subject: [PATCH 6/7] Improve code readability by extracting attribute names to array Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com> --- .../Analyzers/NamingMethodPascal.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs index ee7eddec..c13d4f5a 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs @@ -111,12 +111,23 @@ private static bool IsTestMethod(IMethodSymbol methodSymbol) // Test framework namespaces - checking namespace is more flexible than specific attribute names string[] testFrameworkNamespaces = [ - "Xunit", // xUnit + "Xunit", // xUnit (note: namespace is "Xunit", not "XUnit") "NUnit.Framework", // NUnit "Microsoft.VisualStudio.TestTools.UnitTesting", // MSTest "TUnit.Core" // TUnit ]; + // Fallback attribute names for test environments where namespace metadata may be incomplete + string[] commonTestAttributeNames = + [ + "TestMethod", "TestMethodAttribute", // MSTest + "Fact", "FactAttribute", // xUnit + "Theory", "TheoryAttribute", // xUnit + "Test", "TestAttribute", // NUnit + "TestCase", "TestCaseAttribute", // NUnit + "TestCaseSource", "TestCaseSourceAttribute" // NUnit + ]; + ImmutableArray attributes = methodSymbol.GetAttributes(); return attributes.Any(attribute => { @@ -134,14 +145,8 @@ private static bool IsTestMethod(IMethodSymbol methodSymbol) } // Fallback: check attribute name for common test attributes - // This helps in test environments where namespace metadata may be incomplete string attributeName = attribute.AttributeClass.Name; - return attributeName == "TestMethod" || attributeName == "TestMethodAttribute" || - attributeName == "Fact" || attributeName == "FactAttribute" || - attributeName == "Theory" || attributeName == "TheoryAttribute" || - attributeName == "Test" || attributeName == "TestAttribute" || - attributeName == "TestCase" || attributeName == "TestCaseAttribute" || - attributeName == "TestCaseSource" || attributeName == "TestCaseSourceAttribute"; + return commonTestAttributeNames.Contains(attributeName); }); } } From 35c324ef125b105e7aa83f9c1bba76cbbfb3d535 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:47:15 +0000 Subject: [PATCH 7/7] Co-authored-by: BenjaminMichaelis <22186029+BenjaminMichaelis@users.noreply.github.com> --- .../Analyzers/NamingMethodPascal.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs index c13d4f5a..6d976265 100644 --- a/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs +++ b/IntelliTect.Analyzer/IntelliTect.Analyzer/Analyzers/NamingMethodPascal.cs @@ -108,7 +108,8 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) private static bool IsTestMethod(IMethodSymbol methodSymbol) { - // Test framework namespaces - checking namespace is more flexible than specific attribute names + // Test framework namespaces - any method decorated with an attribute from these namespaces + // is considered a test method and exempt from PascalCase validation string[] testFrameworkNamespaces = [ "Xunit", // xUnit (note: namespace is "Xunit", not "XUnit") @@ -117,7 +118,8 @@ private static bool IsTestMethod(IMethodSymbol methodSymbol) "TUnit.Core" // TUnit ]; - // Fallback attribute names for test environments where namespace metadata may be incomplete + // Fallback attribute names - needed because our test infrastructure (DiagnosticVerifier) + // doesn't add references to test framework assemblies, so ContainingNamespace would be null string[] commonTestAttributeNames = [ "TestMethod", "TestMethodAttribute", // MSTest @@ -136,7 +138,7 @@ private static bool IsTestMethod(IMethodSymbol methodSymbol) return false; } - // Check namespace first (more robust for production code) + // Check namespace first (works in production with proper assembly references) string containingNamespace = attribute.AttributeClass.ContainingNamespace?.ToDisplayString(); if (containingNamespace != null && testFrameworkNamespaces.Any(ns => containingNamespace.StartsWith(ns, StringComparison.Ordinal))) @@ -144,7 +146,7 @@ private static bool IsTestMethod(IMethodSymbol methodSymbol) return true; } - // Fallback: check attribute name for common test attributes + // Fallback: check attribute name (needed for test environment) string attributeName = attribute.AttributeClass.Name; return commonTestAttributeNames.Contains(attributeName); });