Skip to content

Commit

Permalink
Merge pull request #2996 from jedidja/namespace-casing
Browse files Browse the repository at this point in the history
Support for nontraditional casing in namespaces (SA1300) #2923
  • Loading branch information
sharwell authored Feb 14, 2020
2 parents dc78ed3 + 477c0fe commit 2c7770d
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,31 @@ public async Task TestLowerCaseNamespaceAsync()
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
}

[Fact]
public async Task TestAllowedLowerCaseNamespaceIsNotReportedAsync()
{
var customTestSettings = @"
{
""settings"": {
""namingRules"": {
""allowedNamespaceComponents"": [ ""eBay"" ]
}
}
}
";

var testCode = @"namespace eBay
{
}";

await new CSharpTest
{
TestCode = testCode,
Settings = customTestSettings,
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
}

[Fact]
public async Task TestLowerCaseComlicatedNamespaceAsync()
{
Expand All @@ -67,6 +92,31 @@ public async Task TestLowerCaseComlicatedNamespaceAsync()
await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
}

[Fact]
public async Task TestAllowedLowerCaseComplicatedNamespaceIsNotReportedAsync()
{
var customTestSettings = @"
{
""settings"": {
""namingRules"": {
""allowedNamespaceComponents"": [ ""iPod"" ]
}
}
}
";

var testCode = @"namespace Apple.iPod.Library
{
}";

await new CSharpTest
{
TestCode = testCode,
Settings = customTestSettings,
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
}

[Fact]
public async Task TestUpperCaseClassAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public void VerifySettingsDefaults()
Assert.Equal("Copyright (c) PlaceholderCompany. All rights reserved.", styleCopSettings.DocumentationRules.GetCopyrightText("unused"));
Assert.True(styleCopSettings.NamingRules.AllowCommonHungarianPrefixes);
Assert.Empty(styleCopSettings.NamingRules.AllowedHungarianPrefixes);
Assert.Empty(styleCopSettings.NamingRules.AllowedNamespaceComponents);

Assert.NotNull(styleCopSettings.OrderingRules);
Assert.Equal(UsingDirectivesPlacement.InsideNamespace, styleCopSettings.OrderingRules.UsingDirectivesPlacement);
Expand Down Expand Up @@ -61,6 +62,7 @@ public async Task VerifySettingsAreReadCorrectlyAsync()
""namingRules"": {
""allowCommonHungarianPrefixes"": false,
""allowedHungarianPrefixes"": [""a"", ""ab"", ""ignoredTooLong""],
""allowedNamespaceComponents"": [""eBay"", ""iMac""],
""unrecognizedValue"": 3
},
""layoutRules"": {
Expand Down Expand Up @@ -93,6 +95,7 @@ public async Task VerifySettingsAreReadCorrectlyAsync()
Assert.Equal("ru-RU", styleCopSettings.DocumentationRules.DocumentationCulture);
Assert.False(styleCopSettings.NamingRules.AllowCommonHungarianPrefixes);
Assert.Equal(new[] { "a", "ab" }, styleCopSettings.NamingRules.AllowedHungarianPrefixes);
Assert.Equal(new[] { "eBay", "iMac" }, styleCopSettings.NamingRules.AllowedNamespaceComponents);

Assert.NotNull(styleCopSettings.LayoutRules);
Assert.Equal(OptionSetting.Require, styleCopSettings.LayoutRules.NewlineAtEndOfFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace StyleCop.Analyzers.NamingRules
using Microsoft.CodeAnalysis.Diagnostics;
using StyleCop.Analyzers.Helpers;
using StyleCop.Analyzers.Lightup;
using StyleCop.Analyzers.Settings.ObjectModel;

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

private static readonly Action<SyntaxNodeAnalysisContext> NamespaceDeclarationAction = HandleNamespaceDeclaration;
private static readonly Action<SyntaxNodeAnalysisContext, StyleCopSettings> NamespaceDeclarationAction = HandleNamespaceDeclaration;
private static readonly Action<SyntaxNodeAnalysisContext> ClassDeclarationAction = HandleClassDeclaration;
private static readonly Action<SyntaxNodeAnalysisContext> EnumDeclarationAction = HandleEnumDeclaration;
private static readonly Action<SyntaxNodeAnalysisContext> EnumMemberDeclarationAction = HandleEnumMemberDeclaration;
Expand Down Expand Up @@ -82,13 +86,13 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(PropertyDeclarationAction, SyntaxKind.PropertyDeclaration);
}

private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context)
private static void HandleNamespaceDeclaration(SyntaxNodeAnalysisContext context, StyleCopSettings settings)
{
NameSyntax nameSyntax = ((NamespaceDeclarationSyntax)context.Node).Name;
CheckNameSyntax(context, nameSyntax);
CheckNamespaceNameSyntax(context, nameSyntax, settings);
}

private static void CheckNameSyntax(SyntaxNodeAnalysisContext context, NameSyntax nameSyntax)
private static void CheckNamespaceNameSyntax(SyntaxNodeAnalysisContext context, NameSyntax nameSyntax, StyleCopSettings settings)
{
if (nameSyntax == null || nameSyntax.IsMissing)
{
Expand All @@ -97,12 +101,13 @@ private static void CheckNameSyntax(SyntaxNodeAnalysisContext context, NameSynta

if (nameSyntax is QualifiedNameSyntax qualifiedNameSyntax)
{
CheckNameSyntax(context, qualifiedNameSyntax.Left);
CheckNameSyntax(context, qualifiedNameSyntax.Right);
CheckNamespaceNameSyntax(context, qualifiedNameSyntax.Left, settings);
CheckNamespaceNameSyntax(context, qualifiedNameSyntax.Right, settings);
return;
}

if (nameSyntax is SimpleNameSyntax simpleNameSyntax)
if (nameSyntax is SimpleNameSyntax simpleNameSyntax &&
!settings.NamingRules.AllowedNamespaceComponents.Contains(simpleNameSyntax.Identifier.ValueText))
{
CheckElementNameToken(context, simpleNameSyntax.Identifier);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

namespace StyleCop.Analyzers.Settings.ObjectModel
{
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text.RegularExpressions;
using LightJson;

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

/// <summary>
/// This is the backing field for the <see cref="AllowedNamespaceComponents"/> property.
/// </summary>
private readonly ImmutableArray<string>.Builder allowedNamespaceComponents;

/// <summary>
/// Initializes a new instance of the <see cref="NamingSettings"/> class.
/// </summary>
protected internal NamingSettings()
{
this.AllowCommonHungarianPrefixes = true;
this.allowedHungarianPrefixes = ImmutableArray.CreateBuilder<string>();
this.allowedNamespaceComponents = ImmutableArray.CreateBuilder<string>();

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

break;

case "allowedNamespaceComponents":
kvp.AssertIsArray();
this.allowedNamespaceComponents.AddRange(kvp.Value.AsJsonArray.Select(x => x.ToStringValue(kvp.Key)));
break;

case "includeInferredTupleElementNames":
this.IncludeInferredTupleElementNames = kvp.ToBooleanValue();
break;
Expand All @@ -76,6 +89,9 @@ protected internal NamingSettings(JsonObject namingSettingsObject)
public ImmutableArray<string> AllowedHungarianPrefixes
=> this.allowedHungarianPrefixes.ToImmutable();

public ImmutableArray<string> AllowedNamespaceComponents
=> this.allowedNamespaceComponents.ToImmutable();

public bool IncludeInferredTupleElementNames { get; }

public TupleElementNameCase TupleElementNameCasing { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@
"uniqueItems": true
}
},
"allowedNamespaceComponents": {
"type": "array",
"description": "Allowed namespace components, such as ones beginning with a lowercase letter.",
"default": [],
"items": {
"type": "string",
"uniqueItems": true
}
},
"includeInferredTupleElementNames": {
"type": "boolean",
"description": "Specifies whether inferred tuple element names will be analyzed as well.",
Expand Down
24 changes: 24 additions & 0 deletions documentation/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,30 @@ The following example shows a settings file which allows the common prefixes as
}
```

### Namespace Components

The following property is used to configure allowable namespace components (e.g. ones that start with a lowercase letter).

| Property | Default Value | Minimum Version | Summary |
| --- | --- | --- | --- |
| `allowedNamespaceComponents` | `[ ]` | 1.2.0 | Specifies namespace components that are allowed to be used. See the example below for more information. |

The following example shows a settings file which allows namespace components such as `eBay` or `Apple.iPod`.

```json
{
"settings": {
"namingRules": {
"allowedNamespaceComponents": [
"eBay",
"iPod"
]
}
}
}
```


### Tuple element names

The following properties are used to configure the behavior of the tuple element name analyzers.
Expand Down
8 changes: 6 additions & 2 deletions documentation/SA1300.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,14 @@ begin with a lower-case letter, place the field or variable within a special `Na
class is any class which contains a name ending in `NativeMethods`, and is intended as a placeholder for Win32 or COM
wrappers. StyleCop will ignore this violation if the item is placed within a `NativeMethods` class.

For namespace components that begin with a small letter, due to branding issues or other reasons, add the appropriate
term to the `allowedNamespaceComponents` list.

## How to fix violations

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

## How to suppress violations

Expand Down

0 comments on commit 2c7770d

Please sign in to comment.