Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix handling of generic delegates in file type name #3485

Merged
merged 1 commit into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,56 @@ public async Task VerifyWrongFileNameAsync(string typeKeyword)
await VerifyCSharpFixAsync("WrongFileName.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType.cs", fixedCode, CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Verifies that a wrong file name is correctly reported.
/// </summary>
/// <param name="typeKeyword">The type keyword to use during the test.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[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);
}

/// <summary>
/// Verifies that a wrong file name is correctly reported.
/// </summary>
/// <param name="typeKeyword">The type keyword to use during the test.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[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);
}

/// <summary>
/// Verifies that a wrong file name with multiple extensions is correctly reported and fixed. This is a
/// regression test for DotNetAnalyzers/StyleCopAnalyzers#1829.
Expand Down Expand Up @@ -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);
}

/// <summary>
/// Verifies that the file name is not case sensitive.
/// </summary>
/// <param name="typeKeyword">The type keyword to use during the test.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[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);
}

/// <summary>
/// Verifies that the file name is not case sensitive.
/// </summary>
/// <param name="typeKeyword">The type keyword to use during the test.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[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);
}

/// <summary>
Expand Down Expand Up @@ -168,6 +254,74 @@ public enum IgnoredEnum {{ }}
await VerifyCSharpFixAsync("TestType2.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType.cs", fixedCode, CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Verifies that the file name is based on the first type.
/// </summary>
/// <param name="typeKeyword">The type keyword to use during the test.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[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);
}

/// <summary>
/// Verifies that the file name is based on the first type.
/// </summary>
/// <param name="typeKeyword">The type keyword to use during the test.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[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()
Expand All @@ -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]
Expand All @@ -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);
}

/// <summary>
Expand All @@ -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);
}

/// <summary>
Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}

/// <summary>
Expand All @@ -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)
Expand All @@ -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<SA1649FileNameMustMatchTypeName, SA1649CodeFixProvider>.CSharpTest()
{
TestSources = { (fileName, source) },
};

if (testSettings != null)
{
test.Settings = testSettings;
}

test.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync(cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,14 @@ public static IEnumerable<object[]> AllTypeDeclarationKeywords
.Concat(new[] { new[] { "delegate" } });
}
}

public static IEnumerable<object[]> GenericTypeDeclarationKeywords
{
get
{
return TypeDeclarationKeywords
.Concat(new[] { new[] { "delegate" } });
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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, T2, T3>(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, T2, T3>(T1 x, T2 y, T3 z);
(expectedName, @"public delegate void Bar<T1, T2, T3>(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]
Expand Down
Loading