Skip to content

Commit

Permalink
Check file name for single 'enum' and 'delegate' top-level types
Browse files Browse the repository at this point in the history
Closes #3234
  • Loading branch information
sharwell committed Dec 7, 2020
1 parent 8056de6 commit 37a0ba2
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,18 @@ public class SA1649UnitTests
/// <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))]
[MemberData(nameof(CommonMemberData.AllTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))]
public async Task VerifyWrongFileNameAsync(string typeKeyword)
{
var testCode = $@"namespace TestNamespace
{{
public {typeKeyword} {{|#0:TestType|}}
{{
}}
{GetTypeDeclaration(typeKeyword, "TestType", diagnosticKey: 0)}
}}
";

var fixedCode = $@"namespace TestNamespace
{{
public {typeKeyword} TestType
{{
}}
{GetTypeDeclaration(typeKeyword, "TestType")}
}}
";

Expand All @@ -73,22 +69,18 @@ public async Task VerifyWrongFileNameAsync(string typeKeyword)
/// <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))]
[MemberData(nameof(CommonMemberData.AllTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))]
public async Task VerifyWrongFileNameMultipleExtensionsAsync(string typeKeyword)
{
var testCode = $@"namespace TestNamespace
{{
public {typeKeyword} {{|#0:TestType|}}
{{
}}
{GetTypeDeclaration(typeKeyword, "TestType", diagnosticKey: 0)}
}}
";

var fixedCode = $@"namespace TestNamespace
{{
public {typeKeyword} TestType
{{
}}
{GetTypeDeclaration(typeKeyword, "TestType")}
}}
";

Expand All @@ -103,22 +95,18 @@ public async Task VerifyWrongFileNameMultipleExtensionsAsync(string typeKeyword)
/// <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))]
[MemberData(nameof(CommonMemberData.AllTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))]
public async Task VerifyWrongFileNameNoExtensionAsync(string typeKeyword)
{
var testCode = $@"namespace TestNamespace
{{
public {typeKeyword} {{|#0:TestType|}}
{{
}}
{GetTypeDeclaration(typeKeyword, "TestType", diagnosticKey: 0)}
}}
";

var fixedCode = $@"namespace TestNamespace
{{
public {typeKeyword} TestType
{{
}}
{GetTypeDeclaration(typeKeyword, "TestType")}
}}
";

Expand All @@ -132,14 +120,12 @@ public async Task VerifyWrongFileNameNoExtensionAsync(string typeKeyword)
/// <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))]
[MemberData(nameof(CommonMemberData.AllTypeDeclarationKeywords), MemberType = typeof(CommonMemberData))]
public async Task VerifyCaseInsensitivityAsync(string typeKeyword)
{
var testCode = $@"namespace TestNamespace
{{
public {typeKeyword} TestType
{{
}}
{GetTypeDeclaration(typeKeyword, "TestType")}
}}
";

Expand All @@ -157,17 +143,62 @@ public async Task VerifyFirstTypeIsUsedAsync(string typeKeyword)
{
var testCode = $@"namespace TestNamespace
{{
public {typeKeyword} TestType
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 enum TestType
{{
}}
public {typeKeyword} TestType2
public enum TestType2
{{
}}
}}
";

await VerifyCSharpDiagnosticAsync("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);
}

/// <summary>
Expand Down Expand Up @@ -281,6 +312,20 @@ public async Task VerifyWithoutFirstTypeAsync()
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} {{ }}",
};
}

private static Task VerifyCSharpDiagnosticAsync(string fileName, string source, DiagnosticResult[] expected, CancellationToken cancellationToken)
{
var test = new StyleCopCodeFixVerifier<SA1649FileNameMustMatchTypeName, SA1649CodeFixProvider>.CSharpTest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,14 @@ public static IEnumerable<object[]> BaseTypeDeclarationKeywords
.Concat(new[] { new[] { "enum" } });
}
}

public static IEnumerable<object[]> AllTypeDeclarationKeywords
{
get
{
return BaseTypeDeclarationKeywords
.Concat(new[] { new[] { "delegate" } });
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -87,15 +88,31 @@ public static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCopS
var properties = ImmutableDictionary.Create<string, string>()
.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<TypeDeclarationSyntax>()
.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<MemberDeclarationSyntax>()
.Where(node => node is BaseTypeDeclarationSyntax || node.IsKind(SyntaxKind.DelegateDeclaration))
.ToList();
if (expandedTypeDeclarations.Count == 1)
{
firstTypeDeclaration = expandedTypeDeclarations[0];
}

return firstTypeDeclaration;
}
}
}
Expand Down

0 comments on commit 37a0ba2

Please sign in to comment.