From 35e05894008bd9e1763b6c418710b98483191f4b Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 18 Apr 2022 13:16:26 -0700 Subject: [PATCH] Fix handling of generic delegates in file type name --- .../DocumentationRules/SA1649UnitTests.cs | 189 +++++++++++++++++- .../Helpers/CommonMemberData.cs | 9 + .../Helpers/JsonTestHelper.cs | 40 ++++ .../SA1402ForDelegateUnitTests.cs | 20 +- ...1402ForNonBlockDeclarationUnitTestsBase.cs | 28 ++- .../Verifiers/StyleCopCodeFixVerifier`2.cs | 33 +-- .../Helpers/FileNameHelpers.cs | 27 +++ 7 files changed, 301 insertions(+), 45 deletions(-) create mode 100644 StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/JsonTestHelper.cs diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs index ffcc4aadf..0ec463ea3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs @@ -64,6 +64,56 @@ public async Task VerifyWrongFileNameAsync(string typeKeyword) await VerifyCSharpFixAsync("WrongFileName.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); } + /// + /// Verifies that a wrong file name is correctly reported. + /// + /// The type keyword to use during the test. + /// A representing the asynchronous unit test. + [Theory] + [MemberData(nameof(CommonMemberData.GenericTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyWrongFileNameGenericStyleCopAsync(string typeKeyword) + { + var testCode = $@"namespace TestNamespace +{{ + {GetGenericTypeDeclaration(typeKeyword, "TestType", new[] { "T" }, diagnosticKey: 0)} +}} +"; + + var fixedCode = $@"namespace TestNamespace +{{ + {GetGenericTypeDeclaration(typeKeyword, "TestType", new[] { "T" })} +}} +"; + + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("WrongFileName.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType{T}.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that a wrong file name is correctly reported. + /// + /// The type keyword to use during the test. + /// A representing the asynchronous unit test. + [Theory] + [MemberData(nameof(CommonMemberData.GenericTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyWrongFileNameGenericMetadataAsync(string typeKeyword) + { + var testCode = $@"namespace TestNamespace +{{ + {GetGenericTypeDeclaration(typeKeyword, "TestType", new[] { "T" }, diagnosticKey: 0)} +}} +"; + + var fixedCode = $@"namespace TestNamespace +{{ + {GetGenericTypeDeclaration(typeKeyword, "TestType", new[] { "T" })} +}} +"; + + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("WrongFileName.cs", testCode, MetadataSettings, expectedDiagnostic, "TestType`1.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); + } + /// /// Verifies that a wrong file name with multiple extensions is correctly reported and fixed. This is a /// regression test for DotNetAnalyzers/StyleCopAnalyzers#1829. @@ -131,7 +181,43 @@ public async Task VerifyCaseInsensitivityAsync(string typeKeyword) }} "; - await VerifyCSharpDiagnosticAsync("testtype.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpDiagnosticAsync("testtype.cs", testCode, testSettings: null, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that the file name is not case sensitive. + /// + /// The type keyword to use during the test. + /// A representing the asynchronous unit test. + [Theory] + [MemberData(nameof(CommonMemberData.GenericTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyCaseInsensitivityGenericStyleCopAsync(string typeKeyword) + { + var testCode = $@"namespace TestNamespace +{{ + {GetGenericTypeDeclaration(typeKeyword, "TestType", new[] { "T" })} +}} +"; + + await VerifyCSharpDiagnosticAsync("testtype{t}.cs", testCode, StyleCopSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that the file name is not case sensitive. + /// + /// The type keyword to use during the test. + /// A representing the asynchronous unit test. + [Theory] + [MemberData(nameof(CommonMemberData.GenericTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyCaseInsensitivityGenericMetadataAsync(string typeKeyword) + { + var testCode = $@"namespace TestNamespace +{{ + {GetGenericTypeDeclaration(typeKeyword, "TestType", new[] { "T" })} +}} +"; + + await VerifyCSharpDiagnosticAsync("testtype`1.cs", testCode, MetadataSettings, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// @@ -168,6 +254,74 @@ public enum IgnoredEnum {{ }} await VerifyCSharpFixAsync("TestType2.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); } + /// + /// Verifies that the file name is based on the first type. + /// + /// The type keyword to use during the test. + /// A representing the asynchronous unit test. + [Theory] + [MemberData(nameof(CommonMemberData.TypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyFirstTypeIsUsedGenericStyleCopAsync(string typeKeyword) + { + var testCode = $@"namespace TestNamespace +{{ + public enum IgnoredEnum {{ }} + public delegate void IgnoredDelegate(); + + {GetGenericTypeDeclaration(typeKeyword, "TestType", new[] { "T" }, diagnosticKey: 0)} + + {GetGenericTypeDeclaration(typeKeyword, "TestType2", new[] { "T" })} +}} +"; + var fixedCode = $@"namespace TestNamespace +{{ + public enum IgnoredEnum {{ }} + public delegate void IgnoredDelegate(); + + {GetGenericTypeDeclaration(typeKeyword, "TestType", new[] { "T" })} + + {GetGenericTypeDeclaration(typeKeyword, "TestType2", new[] { "T" })} +}} +"; + + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("TestType2.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType{T}.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that the file name is based on the first type. + /// + /// The type keyword to use during the test. + /// A representing the asynchronous unit test. + [Theory] + [MemberData(nameof(CommonMemberData.TypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + public async Task VerifyFirstTypeIsUsedGenericMetadataAsync(string typeKeyword) + { + var testCode = $@"namespace TestNamespace +{{ + public enum IgnoredEnum {{ }} + public delegate void IgnoredDelegate(); + + {GetGenericTypeDeclaration(typeKeyword, "TestType", new[] { "T" }, diagnosticKey: 0)} + + {GetGenericTypeDeclaration(typeKeyword, "TestType2", new[] { "T" })} +}} +"; + var fixedCode = $@"namespace TestNamespace +{{ + public enum IgnoredEnum {{ }} + public delegate void IgnoredDelegate(); + + {GetGenericTypeDeclaration(typeKeyword, "TestType", new[] { "T" })} + + {GetGenericTypeDeclaration(typeKeyword, "TestType2", new[] { "T" })} +}} +"; + + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("TestType2.cs", testCode, MetadataSettings, expectedDiagnostic, "TestType`1.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); + } + [Fact] [WorkItem(3234, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3234")] public async Task VerifyMultipleEnumTypesIgnoredAsync() @@ -185,7 +339,7 @@ public enum TestType2 "; // File names are not checked for 'enum' if more than one is present - await VerifyCSharpDiagnosticAsync("TestType2.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpDiagnosticAsync("TestType2.cs", testCode, testSettings: null, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } [Fact] @@ -200,7 +354,7 @@ public async Task VerifyMultipleDelegateTypesIgnoredAsync() "; // File names are not checked for 'delegate' if more than one is present - await VerifyCSharpDiagnosticAsync("TestType2.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpDiagnosticAsync("TestType2.cs", testCode, testSettings: null, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// @@ -220,7 +374,7 @@ public partial {typeKeyword} TestType }} "; - await VerifyCSharpDiagnosticAsync("WrongFileName.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpDiagnosticAsync("WrongFileName.cs", testCode, testSettings: null, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// @@ -241,7 +395,7 @@ public async Task VerifyStyleCopNamingConventionForGenericTypeAsync(string typeK "; var expectedDiagnostic = Diagnostic().WithLocation(0); - await VerifyCSharpDiagnosticAsync("TestType.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpDiagnosticAsync("TestType.cs", testCode, testSettings: null, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); await VerifyCSharpFixAsync("TestType`3.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType{T1,T2,T3}.cs", testCode, CancellationToken.None).ConfigureAwait(false); } @@ -311,7 +465,7 @@ public async Task VerifyWithoutFirstTypeAsync() } "; - await VerifyCSharpDiagnosticAsync("Test0.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpDiagnosticAsync("Test0.cs", testCode, testSettings: null, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } /// @@ -330,7 +484,7 @@ public class Class2 } "; - await VerifyCSharpDiagnosticAsync("Class1.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpDiagnosticAsync("Class1.cs", testCode, testSettings: null, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } protected static string GetTypeDeclaration(string typeKind, string typeName, int? diagnosticKey = null) @@ -347,13 +501,32 @@ protected static string GetTypeDeclaration(string typeKind, string typeName, int }; } - protected static Task VerifyCSharpDiagnosticAsync(string fileName, string source, DiagnosticResult[] expected, CancellationToken cancellationToken) + protected static string GetGenericTypeDeclaration(string typeKind, string typeName, string[] parameters, int? diagnosticKey = null) + { + if (diagnosticKey is not null) + { + typeName = $"{{|#{diagnosticKey}:{typeName}|}}"; + } + + return typeKind switch + { + "delegate" => $"public delegate void {typeName}<{string.Join(", ", parameters)}>();", + _ => $"public {typeKind} {typeName}<{string.Join(", ", parameters)}> {{ }}", + }; + } + + protected static Task VerifyCSharpDiagnosticAsync(string fileName, string source, string testSettings, DiagnosticResult[] expected, CancellationToken cancellationToken) { var test = new StyleCopCodeFixVerifier.CSharpTest() { TestSources = { (fileName, source) }, }; + if (testSettings != null) + { + test.Settings = testSettings; + } + test.ExpectedDiagnostics.AddRange(expected); return test.RunAsync(cancellationToken); } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs index 3fb2700c3..660ac0be6 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CommonMemberData.cs @@ -74,5 +74,14 @@ public static IEnumerable AllTypeDeclarationKeywords .Concat(new[] { new[] { "delegate" } }); } } + + public static IEnumerable GenericTypeDeclarationKeywords + { + get + { + return TypeDeclarationKeywords + .Concat(new[] { new[] { "delegate" } }); + } + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/JsonTestHelper.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/JsonTestHelper.cs new file mode 100644 index 000000000..f7e05dd93 --- /dev/null +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/JsonTestHelper.cs @@ -0,0 +1,40 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace StyleCop.Analyzers.Test.Helpers +{ + using System; + using global::LightJson; + + internal class JsonTestHelper + { + public static JsonObject MergeJsonObjects(JsonObject priority, JsonObject fallback) + { + foreach (var pair in priority) + { + if (pair.Value.IsJsonObject) + { + switch (fallback[pair.Key].Type) + { + case JsonValueType.Null: + fallback[pair.Key] = pair.Value; + break; + + case JsonValueType.Object: + fallback[pair.Key] = MergeJsonObjects(pair.Value.AsJsonObject, fallback[pair.Key].AsJsonObject); + break; + + default: + throw new InvalidOperationException($"Cannot merge objects of type '{pair.Value.Type}' and '{fallback[pair.Key].Type}'."); + } + } + else + { + fallback[pair.Key] = pair.Value; + } + } + + return fallback; + } + } +} diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402ForDelegateUnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402ForDelegateUnitTests.cs index aec17d28d..dc60a3d0a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402ForDelegateUnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402ForDelegateUnitTests.cs @@ -5,10 +5,13 @@ namespace StyleCop.Analyzers.Test.MaintainabilityRules { + using System; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; + using StyleCop.Analyzers.Settings.ObjectModel; using Xunit; + using Xunit.Sdk; public class SA1402ForDelegateUnitTests : SA1402ForNonBlockDeclarationUnitTestsBase { @@ -41,23 +44,32 @@ public async Task TestTwoElementsAsync() await VerifyCSharpFixAsync(testCode, this.GetSettings(), expected, fixedCode, CancellationToken.None).ConfigureAwait(false); } - [Fact] - public async Task TestTwoGenericElementsAsync() + [Theory] + [InlineData(FileNamingConvention.StyleCop)] + [InlineData(FileNamingConvention.Metadata)] + public async Task TestTwoGenericElementsAsync(object namingConvention) { var testCode = @"public delegate void Foo(); public delegate void Bar(T1 x, T2 y, T3 z); "; + var expectedName = (FileNamingConvention)namingConvention switch + { + FileNamingConvention.StyleCop => "Bar{T1,T2,T3}.cs", + FileNamingConvention.Metadata => "Bar`3.cs", + _ => throw new NotImplementedException(), + }; + var fixedCode = new[] { ("/0/Test0.cs", @"public delegate void Foo(); "), - ("Bar.cs", @"public delegate void Bar(T1 x, T2 y, T3 z); + (expectedName, @"public delegate void Bar(T1 x, T2 y, T3 z); "), }; DiagnosticResult expected = Diagnostic().WithLocation(2, 22); - await VerifyCSharpFixAsync(testCode, this.GetSettings(), expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpFixAsync(testCode, this.GetSettings((FileNamingConvention)namingConvention), expected, fixedCode, CancellationToken.None).ConfigureAwait(false); } [Fact] diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402ForNonBlockDeclarationUnitTestsBase.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402ForNonBlockDeclarationUnitTestsBase.cs index fb508308f..14d47cf77 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402ForNonBlockDeclarationUnitTestsBase.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402ForNonBlockDeclarationUnitTestsBase.cs @@ -7,8 +7,11 @@ namespace StyleCop.Analyzers.Test.MaintainabilityRules { using System.Threading; using System.Threading.Tasks; + using global::LightJson.Serialization; using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.MaintainabilityRules; + using StyleCop.Analyzers.Settings.ObjectModel; + using StyleCop.Analyzers.Test.Helpers; using StyleCop.Analyzers.Test.Verifiers; public abstract class SA1402ForNonBlockDeclarationUnitTestsBase @@ -55,9 +58,30 @@ protected static Task VerifyCSharpFixAsync(string source, string testSettings, D return test.RunAsync(cancellationToken); } - protected virtual string GetSettings() + private protected virtual string GetSettings(FileNamingConvention namingConvention = FileNamingConvention.StyleCop) { - return this.SettingsConfiguration.GetSettings(this.Keyword); + var settings = this.SettingsConfiguration.GetSettings(this.Keyword); + if (settings is null && namingConvention == FileNamingConvention.StyleCop) + { + return null; + } + + var namingSettings = $@" +{{ + ""settings"": {{ + ""documentationRules"": {{ + ""fileNamingConvention"": ""{namingConvention.ToString().ToLowerInvariant()}"" + }} + }} +}}"; + + if (settings is null) + { + return namingSettings; + } + + var merged = JsonTestHelper.MergeJsonObjects(JsonReader.Parse(settings).AsJsonObject, JsonReader.Parse(namingSettings).AsJsonObject); + return new JsonWriter(pretty: true).Serialize(merged); } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs index d80959834..59949aecb 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/StyleCopCodeFixVerifier`2.cs @@ -5,7 +5,6 @@ namespace StyleCop.Analyzers.Test.Verifiers { - using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -24,6 +23,7 @@ namespace StyleCop.Analyzers.Test.Verifiers using Microsoft.CodeAnalysis.Testing.Verifiers; using Microsoft.CodeAnalysis.Text; using StyleCop.Analyzers.Settings.ObjectModel; + using StyleCop.Analyzers.Test.Helpers; using Xunit; internal static class StyleCopCodeFixVerifier @@ -169,7 +169,7 @@ public CSharpTest(LanguageVersion? languageVersion) { JsonObject indentationObject = JsonReader.Parse(indentationSettings).AsJsonObject; JsonObject settingsObject = JsonReader.Parse(settings).AsJsonObject; - JsonObject mergedSettings = MergeJsonObjects(settingsObject, indentationObject); + JsonObject mergedSettings = JsonTestHelper.MergeJsonObjects(settingsObject, indentationObject); using (var writer = new JsonWriter(pretty: true)) { settings = writer.Serialize(mergedSettings); @@ -274,35 +274,6 @@ protected override IEnumerable GetCodeFixProviders() Assert.NotSame(WellKnownFixAllProviders.BatchFixer, codeFixProvider.GetFixAllProvider()); return new[] { codeFixProvider }; } - - private static JsonObject MergeJsonObjects(JsonObject priority, JsonObject fallback) - { - foreach (var pair in priority) - { - if (pair.Value.IsJsonObject) - { - switch (fallback[pair.Key].Type) - { - case JsonValueType.Null: - fallback[pair.Key] = pair.Value; - break; - - case JsonValueType.Object: - fallback[pair.Key] = MergeJsonObjects(pair.Value.AsJsonObject, fallback[pair.Key].AsJsonObject); - break; - - default: - throw new InvalidOperationException($"Cannot merge objects of type '{pair.Value.Type}' and '{fallback[pair.Key].Type}'."); - } - } - else - { - fallback[pair.Key] = pair.Value; - } - } - - return fallback; - } } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/FileNameHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/FileNameHelpers.cs index 972667d7d..089b5cdde 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/FileNameHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/FileNameHelpers.cs @@ -47,6 +47,22 @@ internal static string GetConventionalFileName(MemberDeclarationSyntax declarati return GetStyleCopFileName(typeDeclaration); } } + else if (declaration is DelegateDeclarationSyntax delegateDeclaration) + { + if (delegateDeclaration.TypeParameterList == null) + { + return GetSimpleFileName(delegateDeclaration); + } + + switch (convention) + { + case FileNamingConvention.Metadata: + return GetMetadataFileName(delegateDeclaration); + + default: + return GetStyleCopFileName(delegateDeclaration); + } + } return GetSimpleFileName(declaration); } @@ -62,10 +78,21 @@ private static string GetMetadataFileName(TypeDeclarationSyntax typeDeclaration) return $"{typeDeclaration.Identifier.ValueText}`{typeDeclaration.Arity}"; } + private static string GetMetadataFileName(DelegateDeclarationSyntax delegateDeclaration) + { + return $"{delegateDeclaration.Identifier.ValueText}`{delegateDeclaration.Arity}"; + } + private static string GetStyleCopFileName(TypeDeclarationSyntax typeDeclaration) { var typeParameterList = string.Join(",", typeDeclaration.TypeParameterList.Parameters.Select(p => p.Identifier.ValueText)); return $"{typeDeclaration.Identifier.ValueText}{{{typeParameterList}}}"; } + + private static string GetStyleCopFileName(DelegateDeclarationSyntax delegateDeclaration) + { + var typeParameterList = string.Join(",", delegateDeclaration.TypeParameterList.Parameters.Select(p => p.Identifier.ValueText)); + return $"{delegateDeclaration.Identifier.ValueText}{{{typeParameterList}}}"; + } } }