Skip to content

Commit 736fcd5

Browse files
committed
Merge pull request #1837 from sharwell/fix-1829
Fix handling of filenames with varying suffixes
2 parents 08bb657 + 3cb77ce commit 736fcd5

3 files changed

Lines changed: 136 additions & 14 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,71 @@ public async Task VerifyWrongFileNameAsync(string typeKeyword)
8181
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation("WrongFileName.cs", 3, 13 + typeKeyword.Length);
8282
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "WrongFileName.cs").ConfigureAwait(false);
8383
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None, "TestType.cs").ConfigureAwait(false);
84-
await this.VerifyRenameAsync(testCode, "TestType.cs", CancellationToken.None).ConfigureAwait(false);
84+
await this.VerifyRenameAsync(testCode, "WrongFileName.cs", "TestType.cs", CancellationToken.None).ConfigureAwait(false);
85+
}
86+
87+
/// <summary>
88+
/// Verifies that a wrong file name with multiple extensions is correctly reported and fixed. This is a
89+
/// regression test for DotNetAnalyzers/StyleCopAnalyzers#1829.
90+
/// </summary>
91+
/// <param name="typeKeyword">The type keyword to use during the test.</param>
92+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
93+
[Theory]
94+
[MemberData(nameof(TypeKeywords))]
95+
public async Task VerifyWrongFileNameMultipleExtensionsAsync(string typeKeyword)
96+
{
97+
var testCode = $@"namespace TestNameSpace
98+
{{
99+
public {typeKeyword} TestType
100+
{{
101+
}}
102+
}}
103+
";
104+
105+
var fixedCode = $@"namespace TestNamespace
106+
{{
107+
public {typeKeyword} TestType
108+
{{
109+
}}
110+
}}
111+
";
112+
113+
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation("WrongFileName.svc.cs", 3, 13 + typeKeyword.Length);
114+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "WrongFileName.svc.cs").ConfigureAwait(false);
115+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None, "TestType.svc.cs").ConfigureAwait(false);
116+
await this.VerifyRenameAsync(testCode, "WrongFileName.svc.cs", "TestType.svc.cs", CancellationToken.None).ConfigureAwait(false);
117+
}
118+
119+
/// <summary>
120+
/// Verifies that a wrong file name with no extension is correctly reported and fixed. This is a regression test
121+
/// for DotNetAnalyzers/StyleCopAnalyzers#1829.
122+
/// </summary>
123+
/// <param name="typeKeyword">The type keyword to use during the test.</param>
124+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
125+
[Theory]
126+
[MemberData(nameof(TypeKeywords))]
127+
public async Task VerifyWrongFileNameNoExtensionAsync(string typeKeyword)
128+
{
129+
var testCode = $@"namespace TestNameSpace
130+
{{
131+
public {typeKeyword} TestType
132+
{{
133+
}}
134+
}}
135+
";
136+
137+
var fixedCode = $@"namespace TestNamespace
138+
{{
139+
public {typeKeyword} TestType
140+
{{
141+
}}
142+
}}
143+
";
144+
145+
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation("WrongFileName", 3, 13 + typeKeyword.Length);
146+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "WrongFileName").ConfigureAwait(false);
147+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None, "TestType").ConfigureAwait(false);
148+
await this.VerifyRenameAsync(testCode, "WrongFileName", "TestType", CancellationToken.None).ConfigureAwait(false);
85149
}
86150

87151
/// <summary>
@@ -171,7 +235,7 @@ public async Task VerifyStyleCopNamingConventionForGenericTypeAsync(string typeK
171235
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "TestType`3.cs").ConfigureAwait(false);
172236
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "TestType.cs").ConfigureAwait(false);
173237
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "TestType{T1,T2,T3}.cs").ConfigureAwait(false);
174-
await this.VerifyRenameAsync(testCode, "TestType{T1,T2,T3}.cs", CancellationToken.None).ConfigureAwait(false);
238+
await this.VerifyRenameAsync(testCode, "TestType`3.cs", "TestType{T1,T2,T3}.cs", CancellationToken.None).ConfigureAwait(false);
175239
}
176240

177241
/// <summary>
@@ -199,7 +263,41 @@ public async Task VerifyMetadataNamingConventionForGenericTypeAsync(string typeK
199263
expectedDiagnostic = this.CSharpDiagnostic().WithLocation("TestType.cs", 3, 13 + typeKeyword.Length);
200264
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "TestType.cs").ConfigureAwait(false);
201265
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None, "TestType`3.cs").ConfigureAwait(false);
202-
await this.VerifyRenameAsync(testCode, "TestType`3.cs", CancellationToken.None).ConfigureAwait(false);
266+
await this.VerifyRenameAsync(testCode, "TestType.cs", "TestType`3.cs", CancellationToken.None).ConfigureAwait(false);
267+
}
268+
269+
/// <summary>
270+
/// Verifies that a wrong metadata file name with multiple extensions is correctly reported and fixed. This is a
271+
/// regression test for DotNetAnalyzers/StyleCopAnalyzers#1829.
272+
/// </summary>
273+
/// <param name="typeKeyword">The type keyword to use during the test.</param>
274+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
275+
[Theory]
276+
[MemberData(nameof(TypeKeywords))]
277+
public async Task VerifyMetadataNamingConventionForGenericTypeMultipleExtensionsAsync(string typeKeyword)
278+
{
279+
this.useMetadataSettings = true;
280+
281+
var testCode = $@"namespace TestNameSpace
282+
{{
283+
public {typeKeyword} TestType<T>
284+
{{
285+
}}
286+
}}
287+
";
288+
289+
var fixedCode = $@"namespace TestNamespace
290+
{{
291+
public {typeKeyword} TestType<T>
292+
{{
293+
}}
294+
}}
295+
";
296+
297+
var expectedDiagnostic = this.CSharpDiagnostic().WithLocation("TestType.svc.cs", 3, 13 + typeKeyword.Length);
298+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None, "TestType.svc.cs").ConfigureAwait(false);
299+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None, "TestType`1.svc.cs").ConfigureAwait(false);
300+
await this.VerifyRenameAsync(testCode, "TestType.svc.cs", "TestType`1.svc.cs", CancellationToken.None).ConfigureAwait(false);
203301
}
204302

205303
/// <summary>
@@ -235,10 +333,10 @@ protected override string GetSettings()
235333
return this.useMetadataSettings ? MetadataSettings : StyleCopSettings;
236334
}
237335

238-
private async Task VerifyRenameAsync(string source, string expectedFileName, CancellationToken cancellationToken)
336+
private async Task VerifyRenameAsync(string source, string sourceFileName, string expectedFileName, CancellationToken cancellationToken)
239337
{
240338
var analyzers = this.GetCSharpDiagnosticAnalyzers().ToImmutableArray();
241-
var document = this.CreateDocument(source, LanguageNames.CSharp);
339+
var document = this.CreateDocument(source, LanguageNames.CSharp, sourceFileName);
242340
var analyzerDiagnostics = await GetSortedDiagnosticsFromDocumentsAsync(analyzers, new[] { document }, cancellationToken).ConfigureAwait(false);
243341

244342
Assert.Equal(1, analyzerDiagnostics.Length);

StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticVerifier.Helper.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,15 @@ protected static async Task<ImmutableArray<Diagnostic>> GetSortedDiagnosticsFrom
107107
/// <param name="language">The language the source classes are in. Values may be taken from the
108108
/// <see cref="LanguageNames"/> class.</param>
109109
/// <returns>A <see cref="Document"/> created from the source string.</returns>
110-
protected Document CreateDocument(string source, string language = LanguageNames.CSharp)
110+
protected Document CreateDocument(string source, string language = LanguageNames.CSharp, string fileName = null)
111111
{
112-
return this.CreateProject(new[] { source }, language).Documents.Single();
112+
string[] filenames = null;
113+
if (fileName != null)
114+
{
115+
filenames = new[] { fileName };
116+
}
117+
118+
return this.CreateProject(new[] { source }, language, filenames).Documents.Single();
113119
}
114120

115121
/// <summary>

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1649FileNameMustMatchTypeName.cs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ public static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCopS
7575
return;
7676
}
7777

78-
var fileName = Path.GetFileName(context.Tree.FilePath);
78+
string suffix;
79+
var fileName = GetFileNameAndSuffix(context.Tree.FilePath, out suffix);
7980
string expectedFileName;
8081
switch (settings.DocumentationRules.FileNamingConvention)
8182
{
@@ -97,12 +98,29 @@ public static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCopS
9798
}
9899

99100
var properties = ImmutableDictionary.Create<string, string>()
100-
.Add(ExpectedFileNameKey, expectedFileName);
101+
.Add(ExpectedFileNameKey, expectedFileName + suffix);
101102

102103
context.ReportDiagnostic(Diagnostic.Create(Descriptor, firstTypeDeclaration.Identifier.GetLocation(), properties));
103104
}
104105
}
105106

107+
private static string GetFileNameAndSuffix(string path, out string suffix)
108+
{
109+
string fileName = Path.GetFileName(path);
110+
int firstDot = fileName.IndexOf('.');
111+
if (firstDot >= 0)
112+
{
113+
suffix = fileName.Substring(firstDot);
114+
fileName = fileName.Substring(0, firstDot);
115+
}
116+
else
117+
{
118+
suffix = string.Empty;
119+
}
120+
121+
return fileName;
122+
}
123+
106124
private static TypeDeclarationSyntax GetFirstTypeDeclaration(SyntaxNode root)
107125
{
108126
return root.DescendantNodes(descendIntoChildren: node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration))
@@ -114,26 +132,26 @@ private static string GetStyleCopFileName(TypeDeclarationSyntax firstTypeDeclara
114132
{
115133
if (firstTypeDeclaration.TypeParameterList == null)
116134
{
117-
return $"{firstTypeDeclaration.Identifier.ValueText}.cs";
135+
return $"{firstTypeDeclaration.Identifier.ValueText}";
118136
}
119137

120138
var typeParameterList = string.Join(",", firstTypeDeclaration.TypeParameterList.Parameters.Select(p => p.Identifier.ValueText));
121-
return $"{firstTypeDeclaration.Identifier.ValueText}{{{typeParameterList}}}.cs";
139+
return $"{firstTypeDeclaration.Identifier.ValueText}{{{typeParameterList}}}";
122140
}
123141

124142
private static string GetSimpleFileName(TypeDeclarationSyntax firstTypeDeclaration)
125143
{
126-
return $"{firstTypeDeclaration.Identifier.ValueText}.cs";
144+
return $"{firstTypeDeclaration.Identifier.ValueText}";
127145
}
128146

129147
private static string GetMetadataFileName(TypeDeclarationSyntax firstTypeDeclaration)
130148
{
131149
if (firstTypeDeclaration.TypeParameterList == null)
132150
{
133-
return $"{firstTypeDeclaration.Identifier.ValueText}.cs";
151+
return $"{firstTypeDeclaration.Identifier.ValueText}";
134152
}
135153

136-
return $"{firstTypeDeclaration.Identifier.ValueText}`{firstTypeDeclaration.Arity}.cs";
154+
return $"{firstTypeDeclaration.Identifier.ValueText}`{firstTypeDeclaration.Arity}";
137155
}
138156
}
139157
}

0 commit comments

Comments
 (0)