Skip to content

Commit a280645

Browse files
committed
Merge pull request #1737 from dcooke28/master
Updating SA1402 to favor a class with the same name as the file.
2 parents 6b7ba3d + 7f229f5 commit a280645

5 files changed

Lines changed: 116 additions & 78 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402UnitTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,27 @@ public partial class Bar
6060
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
6161
}
6262

63+
[Fact]
64+
public async Task TestPreferFilenameClassAsync()
65+
{
66+
var testCode = @"public class Foo
67+
{
68+
}
69+
public class Test0
70+
{
71+
}";
72+
73+
var fixedCode = @"public class Test0
74+
{
75+
}";
76+
77+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(1, 14);
78+
79+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
80+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
81+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
82+
}
83+
6384
[Fact]
6485
public async Task TestNestedClassesAsync()
6586
{

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

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace StyleCop.Analyzers.DocumentationRules
77
using System.Collections.Immutable;
88
using System.IO;
99
using System.Linq;
10+
using Helpers;
1011
using Microsoft.CodeAnalysis;
1112
using Microsoft.CodeAnalysis.CSharp;
1213
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -76,23 +77,13 @@ public static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCopS
7677
}
7778

7879
string suffix;
79-
var fileName = GetFileNameAndSuffix(context.Tree.FilePath, out suffix);
80-
string expectedFileName;
81-
switch (settings.DocumentationRules.FileNamingConvention)
82-
{
83-
case FileNamingConvention.Metadata:
84-
expectedFileName = GetMetadataFileName(firstTypeDeclaration);
85-
break;
86-
87-
default:
88-
expectedFileName = GetStyleCopFileName(firstTypeDeclaration);
89-
break;
90-
}
80+
var fileName = FileNameHelpers.GetFileNameAndSuffix(context.Tree.FilePath, out suffix);
81+
var expectedFileName = FileNameHelpers.GetConventionalFileName(firstTypeDeclaration, settings.DocumentationRules.FileNamingConvention);
9182

9283
if (string.Compare(fileName, expectedFileName, StringComparison.OrdinalIgnoreCase) != 0)
9384
{
9485
if (settings.DocumentationRules.FileNamingConvention == FileNamingConvention.StyleCop
95-
&& string.Compare(fileName, GetSimpleFileName(firstTypeDeclaration), StringComparison.OrdinalIgnoreCase) == 0)
86+
&& string.Compare(fileName, FileNameHelpers.GetSimpleFileName(firstTypeDeclaration), StringComparison.OrdinalIgnoreCase) == 0)
9687
{
9788
return;
9889
}
@@ -104,55 +95,12 @@ public static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCopS
10495
}
10596
}
10697

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-
12498
private static TypeDeclarationSyntax GetFirstTypeDeclaration(SyntaxNode root)
12599
{
126100
return root.DescendantNodes(descendIntoChildren: node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration))
127101
.OfType<TypeDeclarationSyntax>()
128102
.FirstOrDefault();
129103
}
130-
131-
private static string GetStyleCopFileName(TypeDeclarationSyntax firstTypeDeclaration)
132-
{
133-
if (firstTypeDeclaration.TypeParameterList == null)
134-
{
135-
return $"{firstTypeDeclaration.Identifier.ValueText}";
136-
}
137-
138-
var typeParameterList = string.Join(",", firstTypeDeclaration.TypeParameterList.Parameters.Select(p => p.Identifier.ValueText));
139-
return $"{firstTypeDeclaration.Identifier.ValueText}{{{typeParameterList}}}";
140-
}
141-
142-
private static string GetSimpleFileName(TypeDeclarationSyntax firstTypeDeclaration)
143-
{
144-
return $"{firstTypeDeclaration.Identifier.ValueText}";
145-
}
146-
147-
private static string GetMetadataFileName(TypeDeclarationSyntax firstTypeDeclaration)
148-
{
149-
if (firstTypeDeclaration.TypeParameterList == null)
150-
{
151-
return $"{firstTypeDeclaration.Identifier.ValueText}";
152-
}
153-
154-
return $"{firstTypeDeclaration.Identifier.ValueText}`{firstTypeDeclaration.Arity}";
155-
}
156104
}
157105
}
158106
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Helpers
5+
{
6+
using System.Linq;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
using Settings.ObjectModel;
9+
using Path = System.IO.Path;
10+
11+
internal static class FileNameHelpers
12+
{
13+
internal static string GetFileNameAndSuffix(string path, out string suffix)
14+
{
15+
string fileName = Path.GetFileName(path);
16+
int firstDot = fileName.IndexOf('.');
17+
if (firstDot >= 0)
18+
{
19+
suffix = fileName.Substring(firstDot);
20+
fileName = fileName.Substring(0, firstDot);
21+
}
22+
else
23+
{
24+
suffix = string.Empty;
25+
}
26+
27+
return fileName;
28+
}
29+
30+
internal static string GetConventionalFileName(TypeDeclarationSyntax typeDeclaration, FileNamingConvention convention)
31+
{
32+
if (typeDeclaration.TypeParameterList == null)
33+
{
34+
return GetSimpleFileName(typeDeclaration);
35+
}
36+
37+
switch (convention)
38+
{
39+
case FileNamingConvention.Metadata:
40+
return GetMetadataFileName(typeDeclaration);
41+
42+
default:
43+
return GetStyleCopFileName(typeDeclaration);
44+
}
45+
}
46+
47+
internal static string GetSimpleFileName(TypeDeclarationSyntax typeDeclaration)
48+
{
49+
return $"{typeDeclaration.Identifier.ValueText}";
50+
}
51+
52+
private static string GetMetadataFileName(TypeDeclarationSyntax typeDeclaration)
53+
{
54+
return $"{typeDeclaration.Identifier.ValueText}`{typeDeclaration.Arity}";
55+
}
56+
57+
private static string GetStyleCopFileName(TypeDeclarationSyntax typeDeclaration)
58+
{
59+
var typeParameterList = string.Join(",", typeDeclaration.TypeParameterList.Parameters.Select(p => p.Identifier.ValueText));
60+
return $"{typeDeclaration.Identifier.ValueText}{{{typeParameterList}}}";
61+
}
62+
}
63+
}

StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1402FileMayOnlyContainASingleClass.cs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ namespace StyleCop.Analyzers.MaintainabilityRules
55
{
66
using System;
77
using System.Collections.Immutable;
8+
using System.Linq;
89
using Microsoft.CodeAnalysis;
910
using Microsoft.CodeAnalysis.CSharp;
1011
using Microsoft.CodeAnalysis.CSharp.Syntax;
1112
using Microsoft.CodeAnalysis.Diagnostics;
13+
using Settings.ObjectModel;
1214
using StyleCop.Analyzers.Helpers;
1315

1416
/// <summary>
@@ -40,7 +42,7 @@ internal class SA1402FileMayOnlyContainASingleClass : DiagnosticAnalyzer
4042
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.MaintainabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
4143

4244
private static readonly Action<CompilationStartAnalysisContext> CompilationStartAction = HandleCompilationStart;
43-
private static readonly Action<SyntaxTreeAnalysisContext> SyntaxTreeAction = HandleSyntaxTree;
45+
private static readonly Action<SyntaxTreeAnalysisContext, StyleCopSettings> SyntaxTreeAction = HandleSyntaxTree;
4446

4547
/// <inheritdoc/>
4648
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
@@ -57,38 +59,41 @@ private static void HandleCompilationStart(CompilationStartAnalysisContext conte
5759
context.RegisterSyntaxTreeActionHonorExclusions(SyntaxTreeAction);
5860
}
5961

60-
private static void HandleSyntaxTree(SyntaxTreeAnalysisContext context)
62+
private static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCopSettings settings)
6163
{
6264
var syntaxRoot = context.Tree.GetRoot(context.CancellationToken);
6365

6466
var descentNodes = syntaxRoot.DescendantNodes(descendIntoChildren: node => node != null && !node.IsKind(SyntaxKind.ClassDeclaration));
67+
var classNodes = from descentNode in descentNodes
68+
where descentNode.IsKind(SyntaxKind.ClassDeclaration)
69+
select descentNode as ClassDeclarationSyntax;
70+
71+
string suffix;
72+
var fileName = FileNameHelpers.GetFileNameAndSuffix(context.Tree.FilePath, out suffix);
73+
var preferredClassNode = classNodes.FirstOrDefault(n => FileNameHelpers.GetConventionalFileName(n, settings.DocumentationRules.FileNamingConvention) == fileName) ?? classNodes.FirstOrDefault();
74+
75+
if (preferredClassNode == null)
76+
{
77+
return;
78+
}
6579

6680
string foundClassName = null;
6781
bool isPartialClass = false;
6882

69-
foreach (var node in descentNodes)
83+
foundClassName = preferredClassNode.Identifier.Text;
84+
isPartialClass = preferredClassNode.Modifiers.Any(SyntaxKind.PartialKeyword);
85+
86+
foreach (var classNode in classNodes)
7087
{
71-
if (node.IsKind(SyntaxKind.ClassDeclaration))
88+
if (classNode == preferredClassNode || (isPartialClass && foundClassName == classNode.Identifier.Text))
7289
{
73-
ClassDeclarationSyntax classDeclaration = node as ClassDeclarationSyntax;
74-
if (foundClassName != null)
75-
{
76-
if (isPartialClass && foundClassName == classDeclaration.Identifier.Text)
77-
{
78-
continue;
79-
}
90+
continue;
91+
}
8092

81-
var location = NamedTypeHelpers.GetNameOrIdentifierLocation(node);
82-
if (location != null)
83-
{
84-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
85-
}
86-
}
87-
else
88-
{
89-
foundClassName = classDeclaration.Identifier.Text;
90-
isPartialClass = classDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword);
91-
}
93+
var location = NamedTypeHelpers.GetNameOrIdentifierLocation(classNode);
94+
if (location != null)
95+
{
96+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
9297
}
9398
}
9499
}

StyleCop.Analyzers/StyleCop.Analyzers/StyleCop.Analyzers.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
<Compile Include="Helpers\DocumentationCommentExtensions.cs" />
114114
<Compile Include="Helpers\FileHeader.cs" />
115115
<Compile Include="Helpers\FileHeaderHelpers.cs" />
116+
<Compile Include="Helpers\FileNameHelpers.cs" />
116117
<Compile Include="Helpers\HelpersResources.Designer.cs">
117118
<AutoGen>True</AutoGen>
118119
<DesignTime>True</DesignTime>

0 commit comments

Comments
 (0)