Skip to content

Commit 0153640

Browse files
committed
Merge pull request #1526 from Noryoko/issue-81
2 parents 513f08b + 3953a6f commit 0153640

8 files changed

Lines changed: 373 additions & 3 deletions

File tree

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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.Test.NamingRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
using StyleCop.Analyzers.NamingRules;
11+
using TestHelper;
12+
using Xunit;
13+
14+
public class SA1305UnitTests : DiagnosticVerifier
15+
{
16+
private const string SettingsFileName = "stylecop.json";
17+
private const string DefaultTestSettings = @"
18+
{
19+
""settings"": {
20+
""namingRules"": {
21+
""allowCommonHungarianPrefixes"": true,
22+
""allowedHungarianPrefixes"": [ ]
23+
}
24+
}
25+
}
26+
";
27+
28+
private string customTestSettings;
29+
30+
public static IEnumerable<object[]> CommonPrefixes
31+
{
32+
get
33+
{
34+
yield return new object[] { "as" };
35+
yield return new object[] { "at" };
36+
yield return new object[] { "by" };
37+
yield return new object[] { "do" };
38+
yield return new object[] { "go" };
39+
yield return new object[] { "if" };
40+
yield return new object[] { "in" };
41+
yield return new object[] { "is" };
42+
yield return new object[] { "it" };
43+
yield return new object[] { "no" };
44+
yield return new object[] { "of" };
45+
yield return new object[] { "on" };
46+
yield return new object[] { "or" };
47+
yield return new object[] { "to" };
48+
}
49+
}
50+
51+
[Fact]
52+
public async Task TestValidFieldNamesAreNotReportedAsync()
53+
{
54+
var testCode = @" public class TestClass
55+
{
56+
string bar, Car, fooBar, x, yz;
57+
}
58+
";
59+
60+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
61+
}
62+
63+
[Fact]
64+
public async Task TestValidVariableNamesAreNotReportedAsync()
65+
{
66+
var testCode = @" public class TestClass
67+
{
68+
public void TestMethod()
69+
{
70+
string bar, Car, fooBar, x, yz;
71+
}
72+
}
73+
";
74+
75+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
76+
}
77+
78+
[Fact]
79+
public async Task TestInvalidFieldNamesAreReportedAsync()
80+
{
81+
var testCode = @" public class TestClass
82+
{
83+
string baR, caRe, daRE, fAre;
84+
}
85+
";
86+
87+
DiagnosticResult[] expected =
88+
{
89+
this.CSharpDiagnostic().WithLocation(3, 12).WithArguments("field", "baR"),
90+
this.CSharpDiagnostic().WithLocation(3, 17).WithArguments("field", "caRe"),
91+
this.CSharpDiagnostic().WithLocation(3, 23).WithArguments("field", "daRE"),
92+
this.CSharpDiagnostic().WithLocation(3, 29).WithArguments("field", "fAre")
93+
};
94+
95+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
96+
}
97+
98+
[Fact]
99+
public async Task TestInvalidVariableNamesAreReportedAsync()
100+
{
101+
var testCode = @" public class TestClass
102+
{
103+
public void TestMethod()
104+
{
105+
string baR, caRe, daRE, fAre;
106+
}
107+
}
108+
";
109+
110+
DiagnosticResult[] expected =
111+
{
112+
this.CSharpDiagnostic().WithLocation(5, 16).WithArguments("variable", "baR"),
113+
this.CSharpDiagnostic().WithLocation(5, 21).WithArguments("variable", "caRe"),
114+
this.CSharpDiagnostic().WithLocation(5, 27).WithArguments("variable", "daRE"),
115+
this.CSharpDiagnostic().WithLocation(5, 33).WithArguments("variable", "fAre")
116+
};
117+
118+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
119+
}
120+
121+
[Fact]
122+
public async Task TestEventFieldsAreNotReportedAsync()
123+
{
124+
var testCode = @" public interface ITestInterface
125+
{
126+
event System.Action abC;
127+
}
128+
";
129+
130+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
131+
}
132+
133+
[Theory]
134+
[MemberData(nameof(CommonPrefixes))]
135+
public async Task TestAllowedCommonPrefixesAsync(string prefix)
136+
{
137+
var testCode = $@" public class TestClass
138+
{{
139+
string {prefix}R;
140+
}}
141+
";
142+
143+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
144+
}
145+
146+
[Theory]
147+
[MemberData(nameof(CommonPrefixes))]
148+
public async Task TestAllowedCommonPrefixesWhenDisabledAsync(string prefix)
149+
{
150+
this.customTestSettings = @"
151+
{
152+
""settings"": {
153+
""namingRules"": {
154+
""allowCommonHungarianPrefixes"": false,
155+
""allowedHungarianPrefixes"": [ ]
156+
}
157+
}
158+
}
159+
";
160+
161+
var testCode = $@" public class TestClass
162+
{{
163+
string {prefix}R;
164+
}}
165+
";
166+
167+
var expected = this.CSharpDiagnostic().WithLocation(3, 12).WithArguments("field", $"{prefix}R");
168+
169+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
170+
}
171+
172+
[Fact]
173+
public async Task TestExcludedPrefixesAreNotReportedAsync()
174+
{
175+
this.customTestSettings = @"
176+
{
177+
""settings"": {
178+
""namingRules"": {
179+
""allowCommonHungarianPrefixes"": false,
180+
""allowedHungarianPrefixes"": [ ""ba"", ""ca"", ""da"", ""f"" ]
181+
}
182+
}
183+
}
184+
";
185+
186+
var testCode = @" public class TestClass
187+
{
188+
string baR, caRe, daRE, fAre;
189+
}
190+
";
191+
192+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
193+
}
194+
195+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
196+
{
197+
yield return new SA1305FieldNamesMustNotUseHungarianNotation();
198+
}
199+
200+
protected override string GetSettings()
201+
{
202+
return this.customTestSettings ?? DefaultTestSettings;
203+
}
204+
}
205+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public void VerifySettingsDefaults()
2525

2626
Assert.Equal("PlaceholderCompany", styleCopSettings.DocumentationRules.CompanyName);
2727
Assert.Equal("Copyright (c) PlaceholderCompany. All rights reserved.", styleCopSettings.DocumentationRules.CopyrightText);
28+
Assert.True(styleCopSettings.NamingRules.AllowCommonHungarianPrefixes);
29+
Assert.Equal(0, styleCopSettings.NamingRules.AllowedHungarianPrefixes.Length);
2830
}
2931

3032
/// <summary>
@@ -40,6 +42,10 @@ public async Task VerifySettingsAreReadCorrectlyAsync()
4042
""documentationRules"": {
4143
""companyName"": ""TestCompany"",
4244
""copyrightText"": ""Custom copyright text.""
45+
},
46+
""namingRules"": {
47+
""allowCommonHungarianPrefixes"": false,
48+
""allowedHungarianPrefixes"": [""a"", ""ab""]
4349
}
4450
}
4551
}
@@ -50,6 +56,8 @@ public async Task VerifySettingsAreReadCorrectlyAsync()
5056

5157
Assert.Equal("TestCompany", styleCopSettings.DocumentationRules.CompanyName);
5258
Assert.Equal("Custom copyright text.", styleCopSettings.DocumentationRules.CopyrightText);
59+
Assert.False(styleCopSettings.NamingRules.AllowCommonHungarianPrefixes);
60+
Assert.Equal(new[] { "a", "ab" }, styleCopSettings.NamingRules.AllowedHungarianPrefixes);
5361
}
5462

5563
/// <summary>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@
244244
<Compile Include="NamingRules\SA1302UnitTests.cs" />
245245
<Compile Include="NamingRules\SA1303UnitTests.cs" />
246246
<Compile Include="NamingRules\SA1304UnitTests.cs" />
247+
<Compile Include="NamingRules\SA1305UnitTests.cs" />
247248
<Compile Include="NamingRules\SA1306UnitTests.cs" />
248249
<Compile Include="NamingRules\SA1307UnitTests.cs" />
249250
<Compile Include="NamingRules\SA1308UnitTests.cs" />

StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1305FieldNamesMustNotUseHungarianNotation.cs

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
namespace StyleCop.Analyzers.NamingRules
55
{
66
using System.Collections.Immutable;
7+
using System.Text.RegularExpressions;
8+
using Helpers;
79
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
812
using Microsoft.CodeAnalysis.Diagnostics;
13+
using Settings.ObjectModel;
914

1015
/// <summary>
1116
/// The name of a field or variable in C# uses Hungarian notation.
@@ -48,16 +53,23 @@ internal class SA1305FieldNamesMustNotUseHungarianNotation : DiagnosticAnalyzer
4853
/// </summary>
4954
public const string DiagnosticId = "SA1305";
5055
private const string Title = "Field names must not use Hungarian notation";
51-
private const string MessageFormat = "TODO: Message format";
56+
private const string MessageFormat = "{0} '{1}' must not use Hungarian notation";
5257
private const string Description = "The name of a field or variable in C# uses Hungarian notation.";
5358
private const string HelpLink = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1305.md";
5459

5560
private static readonly DiagnosticDescriptor Descriptor =
56-
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.NamingRules, DiagnosticSeverity.Warning, AnalyzerConstants.DisabledNoTests, Description, HelpLink);
61+
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.NamingRules, DiagnosticSeverity.Warning, AnalyzerConstants.DisabledByDefault, Description, HelpLink);
5762

5863
private static readonly ImmutableArray<DiagnosticDescriptor> SupportedDiagnosticsValue =
5964
ImmutableArray.Create(Descriptor);
6065

66+
private static readonly ImmutableArray<string> CommonPrefixes =
67+
ImmutableArray.Create("as", "at", "by", "do", "go", "if", "in", "is", "it", "no", "of", "on", "or", "to");
68+
69+
private static readonly Regex HungarianRegex = new Regex(@"^(?<notation>[a-z]{1,2})[A-Z]");
70+
71+
private static NamingSettings namingSettings;
72+
6173
/// <inheritdoc/>
6274
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
6375
{
@@ -70,7 +82,69 @@ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
7082
/// <inheritdoc/>
7183
public override void Initialize(AnalysisContext context)
7284
{
73-
// TODO: Implement analysis
85+
context.RegisterCompilationStartAction(HandleCompilationStart);
86+
}
87+
88+
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
89+
{
90+
namingSettings = context.Options.GetStyleCopSettings().NamingRules;
91+
context.RegisterSyntaxNodeActionHonorExclusions(HandleVariableDeclarationSyntax, SyntaxKind.VariableDeclaration);
92+
}
93+
94+
private static void HandleVariableDeclarationSyntax(SyntaxNodeAnalysisContext context)
95+
{
96+
var syntax = (VariableDeclarationSyntax)context.Node;
97+
98+
if (syntax.Parent.IsKind(SyntaxKind.EventFieldDeclaration))
99+
{
100+
return;
101+
}
102+
103+
if (NamedTypeHelpers.IsContainedInNativeMethodsClass(syntax))
104+
{
105+
return;
106+
}
107+
108+
var fieldDeclaration = syntax.Parent.IsKind(SyntaxKind.FieldDeclaration);
109+
foreach (var variableDeclarator in syntax.Variables)
110+
{
111+
if (variableDeclarator == null)
112+
{
113+
continue;
114+
}
115+
116+
var identifier = variableDeclarator.Identifier;
117+
if (identifier.IsMissing)
118+
{
119+
continue;
120+
}
121+
122+
string name = identifier.ValueText;
123+
if (string.IsNullOrEmpty(name))
124+
{
125+
continue;
126+
}
127+
128+
var match = HungarianRegex.Match(name);
129+
if (!match.Success)
130+
{
131+
continue;
132+
}
133+
134+
var notationValue = match.Groups["notation"].Value;
135+
if (namingSettings.AllowCommonHungarianPrefixes && CommonPrefixes.Contains(notationValue))
136+
{
137+
continue;
138+
}
139+
140+
if (namingSettings.AllowedHungarianPrefixes.Contains(notationValue))
141+
{
142+
continue;
143+
}
144+
145+
// Variable names must begin with lower-case letter
146+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, identifier.GetLocation(), fieldDeclaration ? "field" : "variable", name));
147+
}
74148
}
75149
}
76150
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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.Settings.ObjectModel
5+
{
6+
using System.Collections.Immutable;
7+
using Newtonsoft.Json;
8+
9+
[JsonObject(MemberSerialization.OptIn)]
10+
internal class NamingSettings
11+
{
12+
/// <summary>
13+
/// This is the backing field for the <see cref="AllowCommonHungarianPrefixes"/> property.
14+
/// </summary>
15+
[JsonProperty("allowCommonHungarianPrefixes", DefaultValueHandling = DefaultValueHandling.Include)]
16+
private bool allowCommonHungarianPrefixes;
17+
18+
/// <summary>
19+
/// This is the backing field for the <see cref="AllowedHungarianPrefixes"/> property.
20+
/// </summary>
21+
[JsonProperty("allowedHungarianPrefixes", DefaultValueHandling = DefaultValueHandling.Ignore)]
22+
private ImmutableArray<string>.Builder allowedHungarianPrefixes;
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="NamingSettings"/> class during JSON deserialization.
26+
/// </summary>
27+
[JsonConstructor]
28+
protected internal NamingSettings()
29+
{
30+
this.allowCommonHungarianPrefixes = true;
31+
this.allowedHungarianPrefixes = ImmutableArray<string>.Empty.ToBuilder();
32+
}
33+
34+
public bool AllowCommonHungarianPrefixes =>
35+
this.allowCommonHungarianPrefixes;
36+
37+
public ImmutableArray<string> AllowedHungarianPrefixes
38+
{
39+
get
40+
{
41+
return this.allowedHungarianPrefixes.ToImmutable();
42+
}
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)