Skip to content

Commit 2c7770d

Browse files
authored
Merge pull request #2996 from jedidja/namespace-casing
Support for nontraditional casing in namespaces (SA1300) #2923
2 parents dc78ed3 + 477c0fe commit 2c7770d

File tree

7 files changed

+120
-9
lines changed

7 files changed

+120
-9
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1300UnitTests.cs

+50
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,31 @@ public async Task TestLowerCaseNamespaceAsync()
4444
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
4545
}
4646

47+
[Fact]
48+
public async Task TestAllowedLowerCaseNamespaceIsNotReportedAsync()
49+
{
50+
var customTestSettings = @"
51+
{
52+
""settings"": {
53+
""namingRules"": {
54+
""allowedNamespaceComponents"": [ ""eBay"" ]
55+
}
56+
}
57+
}
58+
";
59+
60+
var testCode = @"namespace eBay
61+
{
62+
63+
}";
64+
65+
await new CSharpTest
66+
{
67+
TestCode = testCode,
68+
Settings = customTestSettings,
69+
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
70+
}
71+
4772
[Fact]
4873
public async Task TestLowerCaseComlicatedNamespaceAsync()
4974
{
@@ -67,6 +92,31 @@ public async Task TestLowerCaseComlicatedNamespaceAsync()
6792
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
6893
}
6994

95+
[Fact]
96+
public async Task TestAllowedLowerCaseComplicatedNamespaceIsNotReportedAsync()
97+
{
98+
var customTestSettings = @"
99+
{
100+
""settings"": {
101+
""namingRules"": {
102+
""allowedNamespaceComponents"": [ ""iPod"" ]
103+
}
104+
}
105+
}
106+
";
107+
108+
var testCode = @"namespace Apple.iPod.Library
109+
{
110+
111+
}";
112+
113+
await new CSharpTest
114+
{
115+
TestCode = testCode,
116+
Settings = customTestSettings,
117+
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
118+
}
119+
70120
[Fact]
71121
public async Task TestUpperCaseClassAsync()
72122
{

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

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public void VerifySettingsDefaults()
2929
Assert.Equal("Copyright (c) PlaceholderCompany. All rights reserved.", styleCopSettings.DocumentationRules.GetCopyrightText("unused"));
3030
Assert.True(styleCopSettings.NamingRules.AllowCommonHungarianPrefixes);
3131
Assert.Empty(styleCopSettings.NamingRules.AllowedHungarianPrefixes);
32+
Assert.Empty(styleCopSettings.NamingRules.AllowedNamespaceComponents);
3233

3334
Assert.NotNull(styleCopSettings.OrderingRules);
3435
Assert.Equal(UsingDirectivesPlacement.InsideNamespace, styleCopSettings.OrderingRules.UsingDirectivesPlacement);
@@ -61,6 +62,7 @@ public async Task VerifySettingsAreReadCorrectlyAsync()
6162
""namingRules"": {
6263
""allowCommonHungarianPrefixes"": false,
6364
""allowedHungarianPrefixes"": [""a"", ""ab"", ""ignoredTooLong""],
65+
""allowedNamespaceComponents"": [""eBay"", ""iMac""],
6466
""unrecognizedValue"": 3
6567
},
6668
""layoutRules"": {
@@ -93,6 +95,7 @@ public async Task VerifySettingsAreReadCorrectlyAsync()
9395
Assert.Equal("ru-RU", styleCopSettings.DocumentationRules.DocumentationCulture);
9496
Assert.False(styleCopSettings.NamingRules.AllowCommonHungarianPrefixes);
9597
Assert.Equal(new[] { "a", "ab" }, styleCopSettings.NamingRules.AllowedHungarianPrefixes);
98+
Assert.Equal(new[] { "eBay", "iMac" }, styleCopSettings.NamingRules.AllowedNamespaceComponents);
9699

97100
Assert.NotNull(styleCopSettings.LayoutRules);
98101
Assert.Equal(OptionSetting.Require, styleCopSettings.LayoutRules.NewlineAtEndOfFile);

StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1300ElementMustBeginWithUpperCaseLetter.cs

+12-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace StyleCop.Analyzers.NamingRules
1212
using Microsoft.CodeAnalysis.Diagnostics;
1313
using StyleCop.Analyzers.Helpers;
1414
using StyleCop.Analyzers.Lightup;
15+
using StyleCop.Analyzers.Settings.ObjectModel;
1516

1617
/// <summary>
1718
/// The name of a C# element does not begin with an upper-case letter.
@@ -29,6 +30,9 @@ namespace StyleCop.Analyzers.NamingRules
2930
/// class. A <c>NativeMethods</c> class is any class which contains a name ending in <c>NativeMethods</c>, and is
3031
/// intended as a placeholder for Win32 or COM wrappers. StyleCop will ignore this violation if the item is placed
3132
/// within a <c>NativeMethods</c> class.</para>
33+
///
34+
/// <para>For namespace components that begin with a small letter, due to branding issues or other reasons, add the
35+
/// term to the <c>allowedNamespaceComponents</c> list.</para>
3236
/// </remarks>
3337
[DiagnosticAnalyzer(LanguageNames.CSharp)]
3438
internal class SA1300ElementMustBeginWithUpperCaseLetter : DiagnosticAnalyzer
@@ -45,7 +49,7 @@ internal class SA1300ElementMustBeginWithUpperCaseLetter : DiagnosticAnalyzer
4549
private static readonly DiagnosticDescriptor Descriptor =
4650
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.NamingRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
4751

48-
private static readonly Action<SyntaxNodeAnalysisContext> NamespaceDeclarationAction = HandleNamespaceDeclaration;
52+
private static readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> NamespaceDeclarationAction = HandleNamespaceDeclaration;
4953
private static readonly Action<SyntaxNodeAnalysisContext> ClassDeclarationAction = HandleClassDeclaration;
5054
private static readonly Action<SyntaxNodeAnalysisContext> EnumDeclarationAction = HandleEnumDeclaration;
5155
private static readonly Action<SyntaxNodeAnalysisContext> EnumMemberDeclarationAction = HandleEnumMemberDeclaration;
@@ -82,13 +86,13 @@ public override void Initialize(AnalysisContext context)
8286
context.RegisterSyntaxNodeAction(PropertyDeclarationAction, SyntaxKind.PropertyDeclaration);
8387
}
8488

85-
private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context)
89+
private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context, StyleCopSettings settings)
8690
{
8791
NameSyntax nameSyntax = ((NamespaceDeclarationSyntax)context.Node).Name;
88-
CheckNameSyntax(context, nameSyntax);
92+
CheckNamespaceNameSyntax(context, nameSyntax, settings);
8993
}
9094

91-
private static void CheckNameSyntax(SyntaxNodeAnalysisContext context, NameSyntax nameSyntax)
95+
private static void CheckNamespaceNameSyntax(SyntaxNodeAnalysisContext context, NameSyntax nameSyntax, StyleCopSettings settings)
9296
{
9397
if (nameSyntax == null || nameSyntax.IsMissing)
9498
{
@@ -97,12 +101,13 @@ private static void CheckNameSyntax(SyntaxNodeAnalysisContext context, NameSynta
97101

98102
if (nameSyntax is QualifiedNameSyntax qualifiedNameSyntax)
99103
{
100-
CheckNameSyntax(context, qualifiedNameSyntax.Left);
101-
CheckNameSyntax(context, qualifiedNameSyntax.Right);
104+
CheckNamespaceNameSyntax(context, qualifiedNameSyntax.Left, settings);
105+
CheckNamespaceNameSyntax(context, qualifiedNameSyntax.Right, settings);
102106
return;
103107
}
104108

105-
if (nameSyntax is SimpleNameSyntax simpleNameSyntax)
109+
if (nameSyntax is SimpleNameSyntax simpleNameSyntax &&
110+
!settings.NamingRules.AllowedNamespaceComponents.Contains(simpleNameSyntax.Identifier.ValueText))
106111
{
107112
CheckElementNameToken(context, simpleNameSyntax.Identifier);
108113
return;

StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/NamingSettings.cs

+16
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
namespace StyleCop.Analyzers.Settings.ObjectModel
55
{
6+
using System.Collections.Generic;
67
using System.Collections.Immutable;
8+
using System.Linq;
79
using System.Text.RegularExpressions;
810
using LightJson;
911

@@ -14,13 +16,19 @@ internal class NamingSettings
1416
/// </summary>
1517
private readonly ImmutableArray<string>.Builder allowedHungarianPrefixes;
1618

19+
/// <summary>
20+
/// This is the backing field for the <see cref="AllowedNamespaceComponents"/> property.
21+
/// </summary>
22+
private readonly ImmutableArray<string>.Builder allowedNamespaceComponents;
23+
1724
/// <summary>
1825
/// Initializes a new instance of the <see cref="NamingSettings"/> class.
1926
/// </summary>
2027
protected internal NamingSettings()
2128
{
2229
this.AllowCommonHungarianPrefixes = true;
2330
this.allowedHungarianPrefixes = ImmutableArray.CreateBuilder<string>();
31+
this.allowedNamespaceComponents = ImmutableArray.CreateBuilder<string>();
2432

2533
this.IncludeInferredTupleElementNames = false;
2634
this.TupleElementNameCasing = TupleElementNameCase.PascalCase;
@@ -57,6 +65,11 @@ protected internal NamingSettings(JsonObject namingSettingsObject)
5765

5866
break;
5967

68+
case "allowedNamespaceComponents":
69+
kvp.AssertIsArray();
70+
this.allowedNamespaceComponents.AddRange(kvp.Value.AsJsonArray.Select(x => x.ToStringValue(kvp.Key)));
71+
break;
72+
6073
case "includeInferredTupleElementNames":
6174
this.IncludeInferredTupleElementNames = kvp.ToBooleanValue();
6275
break;
@@ -76,6 +89,9 @@ protected internal NamingSettings(JsonObject namingSettingsObject)
7689
public ImmutableArray<string> AllowedHungarianPrefixes
7790
=> this.allowedHungarianPrefixes.ToImmutable();
7891

92+
public ImmutableArray<string> AllowedNamespaceComponents
93+
=> this.allowedNamespaceComponents.ToImmutable();
94+
7995
public bool IncludeInferredTupleElementNames { get; }
8096

8197
public TupleElementNameCase TupleElementNameCasing { get; }

StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json

+9
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@
128128
"uniqueItems": true
129129
}
130130
},
131+
"allowedNamespaceComponents": {
132+
"type": "array",
133+
"description": "Allowed namespace components, such as ones beginning with a lowercase letter.",
134+
"default": [],
135+
"items": {
136+
"type": "string",
137+
"uniqueItems": true
138+
}
139+
},
131140
"includeInferredTupleElementNames": {
132141
"type": "boolean",
133142
"description": "Specifies whether inferred tuple element names will be analyzed as well.",

documentation/Configuration.md

+24
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,30 @@ The following example shows a settings file which allows the common prefixes as
314314
}
315315
```
316316

317+
### Namespace Components
318+
319+
The following property is used to configure allowable namespace components (e.g. ones that start with a lowercase letter).
320+
321+
| Property | Default Value | Minimum Version | Summary |
322+
| --- | --- | --- | --- |
323+
| `allowedNamespaceComponents` | `[ ]` | 1.2.0 | Specifies namespace components that are allowed to be used. See the example below for more information. |
324+
325+
The following example shows a settings file which allows namespace components such as `eBay` or `Apple.iPod`.
326+
327+
```json
328+
{
329+
"settings": {
330+
"namingRules": {
331+
"allowedNamespaceComponents": [
332+
"eBay",
333+
"iPod"
334+
]
335+
}
336+
}
337+
}
338+
```
339+
340+
317341
### Tuple element names
318342

319343
The following properties are used to configure the behavior of the tuple element name analyzers.

documentation/SA1300.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ begin with a lower-case letter, place the field or variable within a special `Na
4242
class is any class which contains a name ending in `NativeMethods`, and is intended as a placeholder for Win32 or COM
4343
wrappers. StyleCop will ignore this violation if the item is placed within a `NativeMethods` class.
4444

45+
For namespace components that begin with a small letter, due to branding issues or other reasons, add the appropriate
46+
term to the `allowedNamespaceComponents` list.
47+
4548
## How to fix violations
4649

47-
To fix a violation of this rule, change the name of the element so that it begins with an upper-case letter, or place
48-
the item within a `NativeMethods` class if appropriate.
50+
To fix a violation of this rule, change the name of the element so that it begins with an upper-case letter, place
51+
the item within a `NativeMethods` class if appropriate, or add it to the `allowedNamespaceComponents` list if
52+
it is a namespace component.
4953

5054
## How to suppress violations
5155

0 commit comments

Comments
 (0)