diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1300UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1300UnitTests.cs index 6ab51355c..b53f58c35 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1300UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/NamingRules/SA1300UnitTests.cs @@ -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() { @@ -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() { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs index 21454bca2..6a0747704 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/Settings/SettingsUnitTests.cs @@ -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); @@ -61,6 +62,7 @@ public async Task VerifySettingsAreReadCorrectlyAsync() ""namingRules"": { ""allowCommonHungarianPrefixes"": false, ""allowedHungarianPrefixes"": [""a"", ""ab"", ""ignoredTooLong""], + ""allowedNamespaceComponents"": [""eBay"", ""iMac""], ""unrecognizedValue"": 3 }, ""layoutRules"": { @@ -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); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1300ElementMustBeginWithUpperCaseLetter.cs b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1300ElementMustBeginWithUpperCaseLetter.cs index e486cc1da..88cff047a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1300ElementMustBeginWithUpperCaseLetter.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/SA1300ElementMustBeginWithUpperCaseLetter.cs @@ -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; /// /// The name of a C# element does not begin with an upper-case letter. @@ -29,6 +30,9 @@ namespace StyleCop.Analyzers.NamingRules /// class. A NativeMethods 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 + /// term to the allowedNamespaceComponents list. /// [DiagnosticAnalyzer(LanguageNames.CSharp)] internal class SA1300ElementMustBeginWithUpperCaseLetter : DiagnosticAnalyzer @@ -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 NamespaceDeclarationAction = HandleNamespaceDeclaration; + private static readonly Action NamespaceDeclarationAction = HandleNamespaceDeclaration; private static readonly Action ClassDeclarationAction = HandleClassDeclaration; private static readonly Action EnumDeclarationAction = HandleEnumDeclaration; private static readonly Action EnumMemberDeclarationAction = HandleEnumMemberDeclaration; @@ -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) { @@ -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; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/NamingSettings.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/NamingSettings.cs index 27367006f..0523281c9 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/NamingSettings.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/ObjectModel/NamingSettings.cs @@ -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; @@ -14,6 +16,11 @@ internal class NamingSettings /// private readonly ImmutableArray.Builder allowedHungarianPrefixes; + /// + /// This is the backing field for the property. + /// + private readonly ImmutableArray.Builder allowedNamespaceComponents; + /// /// Initializes a new instance of the class. /// @@ -21,6 +28,7 @@ protected internal NamingSettings() { this.AllowCommonHungarianPrefixes = true; this.allowedHungarianPrefixes = ImmutableArray.CreateBuilder(); + this.allowedNamespaceComponents = ImmutableArray.CreateBuilder(); this.IncludeInferredTupleElementNames = false; this.TupleElementNameCasing = TupleElementNameCase.PascalCase; @@ -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; @@ -76,6 +89,9 @@ protected internal NamingSettings(JsonObject namingSettingsObject) public ImmutableArray AllowedHungarianPrefixes => this.allowedHungarianPrefixes.ToImmutable(); + public ImmutableArray AllowedNamespaceComponents + => this.allowedNamespaceComponents.ToImmutable(); + public bool IncludeInferredTupleElementNames { get; } public TupleElementNameCase TupleElementNameCasing { get; } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json index 7835dbc00..34c319a7b 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json @@ -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.", diff --git a/documentation/Configuration.md b/documentation/Configuration.md index 7a849f309..420b3add4 100644 --- a/documentation/Configuration.md +++ b/documentation/Configuration.md @@ -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. diff --git a/documentation/SA1300.md b/documentation/SA1300.md index fa9ef4faf..000421d6d 100644 --- a/documentation/SA1300.md +++ b/documentation/SA1300.md @@ -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