diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/DocumentationRules/SA1649CSharp9UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/DocumentationRules/SA1649CSharp9UnitTests.cs index b23bb8d7d..446a490b9 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/DocumentationRules/SA1649CSharp9UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp9/DocumentationRules/SA1649CSharp9UnitTests.cs @@ -3,88 +3,9 @@ namespace StyleCop.Analyzers.Test.CSharp9.DocumentationRules { - using System.Collections.Generic; - using System.Threading.Tasks; - using Microsoft.CodeAnalysis.CSharp; using StyleCop.Analyzers.Test.CSharp8.DocumentationRules; - using Xunit; public class SA1649CSharp9UnitTests : SA1649CSharp8UnitTests { - public static IEnumerable CSharp9TypeKeywords - { - get - { - foreach (var keyword in TypeKeywords) - { - yield return keyword; - } - - yield return new object[] { "record", LanguageVersion.CSharp9 }; - } - } - - [Theory] - [MemberData(nameof(CSharp9TypeKeywords))] - public override async Task VerifyWrongFileNameAsync(string typeKeyword, LanguageVersion languageVersion) - { - await base.VerifyWrongFileNameAsync(typeKeyword, languageVersion).ConfigureAwait(false); - } - - [Theory] - [MemberData(nameof(CSharp9TypeKeywords))] - public override async Task VerifyWrongFileNameMultipleExtensionsAsync(string typeKeyword, LanguageVersion languageVersion) - { - await base.VerifyWrongFileNameMultipleExtensionsAsync(typeKeyword, languageVersion).ConfigureAwait(false); - } - - [Theory] - [MemberData(nameof(CSharp9TypeKeywords))] - public override async Task VerifyWrongFileNameNoExtensionAsync(string typeKeyword, LanguageVersion languageVersion) - { - await base.VerifyWrongFileNameNoExtensionAsync(typeKeyword, languageVersion).ConfigureAwait(false); - } - - [Theory] - [MemberData(nameof(CSharp9TypeKeywords))] - public override async Task VerifyCaseInsensitivityAsync(string typeKeyword, LanguageVersion languageVersion) - { - await base.VerifyCaseInsensitivityAsync(typeKeyword, languageVersion).ConfigureAwait(false); - } - - [Theory] - [MemberData(nameof(CSharp9TypeKeywords))] - public override async Task VerifyFirstTypeIsUsedAsync(string typeKeyword, LanguageVersion languageVersion) - { - await base.VerifyFirstTypeIsUsedAsync(typeKeyword, languageVersion).ConfigureAwait(false); - } - - [Theory] - [MemberData(nameof(CSharp9TypeKeywords))] - public override async Task VerifyThatPartialTypesAreIgnoredAsync(string typeKeyword, LanguageVersion languageVersion) - { - await base.VerifyThatPartialTypesAreIgnoredAsync(typeKeyword, languageVersion).ConfigureAwait(false); - } - - [Theory] - [MemberData(nameof(CSharp9TypeKeywords))] - public override async Task VerifyStyleCopNamingConventionForGenericTypeAsync(string typeKeyword, LanguageVersion languageVersion) - { - await base.VerifyStyleCopNamingConventionForGenericTypeAsync(typeKeyword, languageVersion).ConfigureAwait(false); - } - - [Theory] - [MemberData(nameof(CSharp9TypeKeywords))] - public override async Task VerifyMetadataNamingConventionForGenericTypeAsync(string typeKeyword, LanguageVersion languageVersion) - { - await base.VerifyMetadataNamingConventionForGenericTypeAsync(typeKeyword, languageVersion).ConfigureAwait(false); - } - - [Theory] - [MemberData(nameof(CSharp9TypeKeywords))] - public override async Task VerifyMetadataNamingConventionForGenericTypeMultipleExtensionsAsync(string typeKeyword, LanguageVersion languageVersion) - { - await base.VerifyMetadataNamingConventionForGenericTypeMultipleExtensionsAsync(typeKeyword, languageVersion).ConfigureAwait(false); - } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs index c0f9554a0..13dc67ade 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs @@ -3,12 +3,11 @@ namespace StyleCop.Analyzers.Test.DocumentationRules { - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; - using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.DocumentationRules; + using StyleCop.Analyzers.Test.Helpers; using StyleCop.Analyzers.Test.Verifiers; using Xunit; using static StyleCop.Analyzers.Test.Verifiers.CustomDiagnosticVerifier; @@ -38,44 +37,29 @@ public class SA1649UnitTests } "; - public static IEnumerable TypeKeywords - { - get - { - yield return new object[] { "class", LanguageVersion.CSharp6 }; - yield return new object[] { "struct", LanguageVersion.CSharp6 }; - yield return new object[] { "interface", LanguageVersion.CSharp6 }; - } - } - /// /// Verifies that a wrong file name is correctly reported. /// /// The type keyword to use during the test. - /// The language version to test with. /// A representing the asynchronous unit test. [Theory] - [MemberData(nameof(TypeKeywords))] - public virtual async Task VerifyWrongFileNameAsync(string typeKeyword, LanguageVersion languageVersion) + [MemberData(nameof(CommonMemberData.AllTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyWrongFileNameAsync(string typeKeyword) { var testCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType - {{ - }} + {GetTypeDeclaration(typeKeyword, "TestType", diagnosticKey: 0)} }} "; var fixedCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType - {{ - }} + {GetTypeDeclaration(typeKeyword, "TestType")} }} "; - var expectedDiagnostic = Diagnostic().WithLocation("WrongFileName.cs", 3, 13 + typeKeyword.Length); - await VerifyCSharpFixAsync(languageVersion, "WrongFileName.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("WrongFileName.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); } /// @@ -83,30 +67,25 @@ public virtual async Task VerifyWrongFileNameAsync(string typeKeyword, LanguageV /// regression test for DotNetAnalyzers/StyleCopAnalyzers#1829. /// /// The type keyword to use during the test. - /// The language version to test with. /// A representing the asynchronous unit test. [Theory] - [MemberData(nameof(TypeKeywords))] - public virtual async Task VerifyWrongFileNameMultipleExtensionsAsync(string typeKeyword, LanguageVersion languageVersion) + [MemberData(nameof(CommonMemberData.AllTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyWrongFileNameMultipleExtensionsAsync(string typeKeyword) { var testCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType - {{ - }} + {GetTypeDeclaration(typeKeyword, "TestType", diagnosticKey: 0)} }} "; var fixedCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType - {{ - }} + {GetTypeDeclaration(typeKeyword, "TestType")} }} "; - var expectedDiagnostic = Diagnostic().WithLocation("WrongFileName.svc.cs", 3, 13 + typeKeyword.Length); - await VerifyCSharpFixAsync(languageVersion, "WrongFileName.svc.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType.svc.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("WrongFileName.svc.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType.svc.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); } /// @@ -114,87 +93,122 @@ public virtual async Task VerifyWrongFileNameMultipleExtensionsAsync(string type /// for DotNetAnalyzers/StyleCopAnalyzers#1829. /// /// The type keyword to use during the test. - /// The language version to test with. /// A representing the asynchronous unit test. [Theory] - [MemberData(nameof(TypeKeywords))] - public virtual async Task VerifyWrongFileNameNoExtensionAsync(string typeKeyword, LanguageVersion languageVersion) + [MemberData(nameof(CommonMemberData.AllTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyWrongFileNameNoExtensionAsync(string typeKeyword) { var testCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType - {{ - }} + {GetTypeDeclaration(typeKeyword, "TestType", diagnosticKey: 0)} }} "; var fixedCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType - {{ - }} + {GetTypeDeclaration(typeKeyword, "TestType")} }} "; - var expectedDiagnostic = Diagnostic().WithLocation("WrongFileName", 3, 13 + typeKeyword.Length); - await VerifyCSharpFixAsync(languageVersion, "WrongFileName", testCode, StyleCopSettings, expectedDiagnostic, "TestType", fixedCode, CancellationToken.None).ConfigureAwait(false); + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("WrongFileName", testCode, StyleCopSettings, expectedDiagnostic, "TestType", fixedCode, CancellationToken.None).ConfigureAwait(false); } /// /// Verifies that the file name is not case sensitive. /// /// The type keyword to use during the test. - /// The language version to test with. /// A representing the asynchronous unit test. [Theory] - [MemberData(nameof(TypeKeywords))] - public virtual async Task VerifyCaseInsensitivityAsync(string typeKeyword, LanguageVersion languageVersion) + [MemberData(nameof(CommonMemberData.AllTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyCaseInsensitivityAsync(string typeKeyword) { var testCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType - {{ - }} + {GetTypeDeclaration(typeKeyword, "TestType")} }} "; - await VerifyCSharpDiagnosticAsync(languageVersion, "testtype.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpDiagnosticAsync("testtype.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// /// Verifies that the file name is based on the first type. /// /// The type keyword to use during the test. - /// The language version to test with. /// A representing the asynchronous unit test. [Theory] - [MemberData(nameof(TypeKeywords))] - public virtual async Task VerifyFirstTypeIsUsedAsync(string typeKeyword, LanguageVersion languageVersion) + [MemberData(nameof(CommonMemberData.TypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyFirstTypeIsUsedAsync(string typeKeyword) + { + var testCode = $@"namespace TestNamespace +{{ + public enum IgnoredEnum {{ }} + public delegate void IgnoredDelegate(); + + {GetTypeDeclaration(typeKeyword, "TestType", diagnosticKey: 0)} + + {GetTypeDeclaration(typeKeyword, "TestType2")} +}} +"; + var fixedCode = $@"namespace TestNamespace +{{ + public enum IgnoredEnum {{ }} + public delegate void IgnoredDelegate(); + + {GetTypeDeclaration(typeKeyword, "TestType")} + + {GetTypeDeclaration(typeKeyword, "TestType2")} +}} +"; + + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("TestType2.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(3234, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3234")] + public async Task VerifyMultipleEnumTypesIgnoredAsync() { var testCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType + public enum TestType {{ }} - public {typeKeyword} TestType2 + public enum TestType2 {{ }} }} "; - await VerifyCSharpDiagnosticAsync(languageVersion, "TestType.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + // File names are not checked for 'enum' if more than one is present + await VerifyCSharpDiagnosticAsync("TestType2.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + [WorkItem(3234, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3234")] + public async Task VerifyMultipleDelegateTypesIgnoredAsync() + { + var testCode = $@"namespace TestNamespace +{{ + public delegate void TestType(); + public delegate void TestType2(); +}} +"; + + // File names are not checked for 'delegate' if more than one is present + await VerifyCSharpDiagnosticAsync("TestType2.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// /// Verifies that partial types are ignored. /// /// The type keyword to use during the test. - /// The language version to test with. /// A representing the asynchronous unit test. [Theory] - [MemberData(nameof(TypeKeywords))] - public virtual async Task VerifyThatPartialTypesAreIgnoredAsync(string typeKeyword, LanguageVersion languageVersion) + [MemberData(nameof(CommonMemberData.TypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyThatPartialTypesAreIgnoredAsync(string typeKeyword) { var testCode = $@"namespace TestNamespace {{ @@ -204,55 +218,53 @@ public partial {typeKeyword} TestType }} "; - await VerifyCSharpDiagnosticAsync(languageVersion, "WrongFileName.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpDiagnosticAsync("WrongFileName.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// /// Verifies that the StyleCop file name convention for a generic type is handled correctly. /// /// The type keyword to use during the test. - /// The language version to test with. /// A representing the asynchronous unit test. [Theory] - [MemberData(nameof(TypeKeywords))] - public virtual async Task VerifyStyleCopNamingConventionForGenericTypeAsync(string typeKeyword, LanguageVersion languageVersion) + [MemberData(nameof(CommonMemberData.TypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyStyleCopNamingConventionForGenericTypeAsync(string typeKeyword) { var testCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType + public {typeKeyword} {{|#0:TestType|}} {{ }} }} "; - var expectedDiagnostic = Diagnostic().WithLocation("TestType`3.cs", 3, 13 + typeKeyword.Length); - await VerifyCSharpDiagnosticAsync(languageVersion, "TestType.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); - await VerifyCSharpFixAsync(languageVersion, "TestType`3.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType{T1,T2,T3}.cs", testCode, CancellationToken.None).ConfigureAwait(false); + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpDiagnosticAsync("TestType.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpFixAsync("TestType`3.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType{T1,T2,T3}.cs", testCode, CancellationToken.None).ConfigureAwait(false); } /// /// Verifies that the metadata file name convention for a generic type is handled correctly. /// /// The type keyword to use during the test. - /// The language version to test with. /// A representing the asynchronous unit test. [Theory] - [MemberData(nameof(TypeKeywords))] - public virtual async Task VerifyMetadataNamingConventionForGenericTypeAsync(string typeKeyword, LanguageVersion languageVersion) + [MemberData(nameof(CommonMemberData.TypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyMetadataNamingConventionForGenericTypeAsync(string typeKeyword) { var testCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType + public {typeKeyword} {{|#0:TestType|}} {{ }} }} "; - var expectedDiagnostic = Diagnostic().WithLocation("TestType{T1,T2,T3}.cs", 3, 13 + typeKeyword.Length); - await VerifyCSharpFixAsync(languageVersion, "TestType{T1,T2,T3}.cs", testCode, MetadataSettings, expectedDiagnostic, "TestType`3.cs", testCode, CancellationToken.None).ConfigureAwait(false); + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("TestType{T1,T2,T3}.cs", testCode, MetadataSettings, expectedDiagnostic, "TestType`3.cs", testCode, CancellationToken.None).ConfigureAwait(false); - expectedDiagnostic = Diagnostic().WithLocation("TestType.cs", 3, 13 + typeKeyword.Length); - await VerifyCSharpFixAsync(languageVersion, "TestType.cs", testCode, MetadataSettings, expectedDiagnostic, "TestType`3.cs", testCode, CancellationToken.None).ConfigureAwait(false); + expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("TestType.cs", testCode, MetadataSettings, expectedDiagnostic, "TestType`3.cs", testCode, CancellationToken.None).ConfigureAwait(false); } /// @@ -260,15 +272,14 @@ public virtual async Task VerifyMetadataNamingConventionForGenericTypeAsync(stri /// regression test for DotNetAnalyzers/StyleCopAnalyzers#1829. /// /// The type keyword to use during the test. - /// The language version to test with. /// A representing the asynchronous unit test. [Theory] - [MemberData(nameof(TypeKeywords))] - public virtual async Task VerifyMetadataNamingConventionForGenericTypeMultipleExtensionsAsync(string typeKeyword, LanguageVersion languageVersion) + [MemberData(nameof(CommonMemberData.TypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyMetadataNamingConventionForGenericTypeMultipleExtensionsAsync(string typeKeyword) { var testCode = $@"namespace TestNamespace {{ - public {typeKeyword} TestType + public {typeKeyword} {{|#0:TestType|}} {{ }} }} @@ -282,8 +293,8 @@ public virtual async Task VerifyMetadataNamingConventionForGenericTypeMultipleEx }} "; - var expectedDiagnostic = Diagnostic().WithLocation("TestType.svc.cs", 3, 13 + typeKeyword.Length); - await VerifyCSharpFixAsync(languageVersion, "TestType.svc.cs", testCode, MetadataSettings, expectedDiagnostic, "TestType`1.svc.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("TestType.svc.cs", testCode, MetadataSettings, expectedDiagnostic, "TestType`1.svc.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); } /// @@ -298,12 +309,26 @@ public async Task VerifyWithoutFirstTypeAsync() } "; - await VerifyCSharpDiagnosticAsync(LanguageVersion.CSharp6, "Test0.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpDiagnosticAsync("Test0.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + private static string GetTypeDeclaration(string typeKind, string typeName, int? diagnosticKey = null) + { + if (diagnosticKey is not null) + { + typeName = $"{{|#{diagnosticKey}:{typeName}|}}"; + } + + return typeKind switch + { + "delegate" => $"public delegate void {typeName}();", + _ => $"public {typeKind} {typeName} {{ }}", + }; } - internal static Task VerifyCSharpDiagnosticAsync(LanguageVersion languageVersion, string fileName, string source, DiagnosticResult[] expected, CancellationToken cancellationToken) + private static Task VerifyCSharpDiagnosticAsync(string fileName, string source, DiagnosticResult[] expected, CancellationToken cancellationToken) { - var test = new StyleCopCodeFixVerifier.CSharpTest(languageVersion) + var test = new StyleCopCodeFixVerifier.CSharpTest() { TestSources = { (fileName, source) }, }; @@ -312,12 +337,12 @@ internal static Task VerifyCSharpDiagnosticAsync(LanguageVersion languageVersion return test.RunAsync(cancellationToken); } - internal static Task VerifyCSharpFixAsync(LanguageVersion languageVersion, string oldFileName, string source, string testSettings, DiagnosticResult expected, string newFileName, string fixedSource, CancellationToken cancellationToken) - => VerifyCSharpFixAsync(languageVersion, oldFileName, source, testSettings, new[] { expected }, newFileName, fixedSource, cancellationToken); + private static Task VerifyCSharpFixAsync(string oldFileName, string source, string testSettings, DiagnosticResult expected, string newFileName, string fixedSource, CancellationToken cancellationToken) + => VerifyCSharpFixAsync(oldFileName, source, testSettings, new[] { expected }, newFileName, fixedSource, cancellationToken); - internal static Task VerifyCSharpFixAsync(LanguageVersion languageVersion, string oldFileName, string source, string testSettings, DiagnosticResult[] expected, string newFileName, string fixedSource, CancellationToken cancellationToken) + private static Task VerifyCSharpFixAsync(string oldFileName, string source, string testSettings, DiagnosticResult[] expected, string newFileName, string fixedSource, CancellationToken cancellationToken) { - var test = new StyleCopCodeFixVerifier.CSharpTest(languageVersion) + var test = new StyleCopCodeFixVerifier.CSharpTest() { TestSources = { (oldFileName, source) }, FixedSources = { (newFileName, fixedSource) }, diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs index 92f3d655d..eb74fdfc0 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs @@ -39,5 +39,14 @@ public static IEnumerable BaseTypeDeclarationKeywords .Concat(new[] { new[] { "enum" } }); } } + + public static IEnumerable AllTypeDeclarationKeywords + { + get + { + return BaseTypeDeclarationKeywords + .Concat(new[] { new[] { "delegate" } }); + } + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1649FileNameMustMatchTypeName.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1649FileNameMustMatchTypeName.cs index f4fd14256..125fc6e4d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1649FileNameMustMatchTypeName.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1649FileNameMustMatchTypeName.cs @@ -67,7 +67,8 @@ public static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCopS return; } - if (firstTypeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword)) + var modifiers = (firstTypeDeclaration as BaseTypeDeclarationSyntax)?.Modifiers ?? SyntaxFactory.TokenList(); + if (modifiers.Any(SyntaxKind.PartialKeyword)) { return; } @@ -87,15 +88,31 @@ public static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCopS var properties = ImmutableDictionary.Create() .Add(ExpectedFileNameKey, expectedFileName + suffix); - context.ReportDiagnostic(Diagnostic.Create(Descriptor, firstTypeDeclaration.Identifier.GetLocation(), properties)); + var identifier = (firstTypeDeclaration as BaseTypeDeclarationSyntax)?.Identifier + ?? ((DelegateDeclarationSyntax)firstTypeDeclaration).Identifier; + context.ReportDiagnostic(Diagnostic.Create(Descriptor, identifier.GetLocation(), properties)); } } - private static TypeDeclarationSyntax GetFirstTypeDeclaration(SyntaxNode root) + private static MemberDeclarationSyntax GetFirstTypeDeclaration(SyntaxNode root) { - return root.DescendantNodes(descendIntoChildren: node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration)) + // Prefer to find the first type which is a true TypeDeclarationSyntax + MemberDeclarationSyntax firstTypeDeclaration = root.DescendantNodes(descendIntoChildren: node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration)) .OfType() .FirstOrDefault(); + + // If no TypeDeclarationSyntax is found, expand the search to any type declaration as long as only one + // is present + var expandedTypeDeclarations = root.DescendantNodes(descendIntoChildren: node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration)) + .OfType() + .Where(node => node is BaseTypeDeclarationSyntax || node.IsKind(SyntaxKind.DelegateDeclaration)) + .ToList(); + if (expandedTypeDeclarations.Count == 1) + { + firstTypeDeclaration = expandedTypeDeclarations[0]; + } + + return firstTypeDeclaration; } } }