// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines
{
using static MicrosoftCodeQualityAnalyzersResources;
/// <summary>
/// CA1707: <inheritdoc cref="IdentifiersShouldNotContainUnderscoresTitle"/>
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class IdentifiersShouldNotContainUnderscoresAnalyzer : DiagnosticAnalyzer
{
internal const string RuleId = "CA1707";
private static readonly IImmutableSet<string> s_GlobalAsaxSpecialMethodNames =
ImmutableHashSet.Create(
"Application_AuthenticateRequest",
"Application_BeginRequest",
"Application_End",
"Application_EndRequest",
"Application_Error",
"Application_Init",
"Application_Start",
"Session_End",
"Session_Start");
private static readonly LocalizableString s_localizableTitle = CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainUnderscoresTitle));
private static readonly LocalizableString s_localizableDescription = CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainUnderscoresDescription));
internal static readonly DiagnosticDescriptor AssemblyRule = DiagnosticDescriptorHelper.Create(
RuleId,
s_localizableTitle,
CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainUnderscoresMessageAssembly)),
DiagnosticCategory.Naming,
RuleLevel.IdeHidden_BulkConfigurable,
description: s_localizableDescription,
isPortedFxCopRule: true,
isDataflowRule: false);
internal static readonly DiagnosticDescriptor NamespaceRule = DiagnosticDescriptorHelper.Create(
RuleId,
s_localizableTitle,
CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainUnderscoresMessageNamespace)),
DiagnosticCategory.Naming,
RuleLevel.IdeHidden_BulkConfigurable,
description: s_localizableDescription,
isPortedFxCopRule: true,
isDataflowRule: false);
internal static readonly DiagnosticDescriptor TypeRule = DiagnosticDescriptorHelper.Create(
RuleId,
s_localizableTitle,
CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainUnderscoresMessageType)),
DiagnosticCategory.Naming,
RuleLevel.IdeHidden_BulkConfigurable,
description: s_localizableDescription,
isPortedFxCopRule: true,
isDataflowRule: false);
internal static readonly DiagnosticDescriptor MemberRule = DiagnosticDescriptorHelper.Create(
RuleId,
s_localizableTitle,
CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainUnderscoresMessageMember)),
DiagnosticCategory.Naming,
RuleLevel.IdeHidden_BulkConfigurable,
description: s_localizableDescription,
isPortedFxCopRule: true,
isDataflowRule: false);
internal static readonly DiagnosticDescriptor TypeTypeParameterRule = DiagnosticDescriptorHelper.Create(
RuleId,
s_localizableTitle,
CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainUnderscoresMessageTypeTypeParameter)),
DiagnosticCategory.Naming,
RuleLevel.IdeHidden_BulkConfigurable,
description: s_localizableDescription,
isPortedFxCopRule: true,
isDataflowRule: false);
internal static readonly DiagnosticDescriptor MethodTypeParameterRule = DiagnosticDescriptorHelper.Create(
RuleId,
s_localizableTitle,
CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainUnderscoresMessageMethodTypeParameter)),
DiagnosticCategory.Naming,
RuleLevel.IdeHidden_BulkConfigurable,
description: s_localizableDescription,
isPortedFxCopRule: true,
isDataflowRule: false);
internal static readonly DiagnosticDescriptor MemberParameterRule = DiagnosticDescriptorHelper.Create(
RuleId,
s_localizableTitle,
CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainUnderscoresMessageMemberParameter)),
DiagnosticCategory.Naming,
RuleLevel.IdeHidden_BulkConfigurable,
description: s_localizableDescription,
isPortedFxCopRule: true,
isDataflowRule: false);
internal static readonly DiagnosticDescriptor DelegateParameterRule = DiagnosticDescriptorHelper.Create(
RuleId,
s_localizableTitle,
CreateLocalizableResourceString(nameof(IdentifiersShouldNotContainUnderscoresMessageDelegateParameter)),
DiagnosticCategory.Naming,
RuleLevel.IdeHidden_BulkConfigurable,
description: s_localizableDescription,
isPortedFxCopRule: true,
isDataflowRule: false);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(AssemblyRule, NamespaceRule, TypeRule, MemberRule, TypeTypeParameterRule, MethodTypeParameterRule, MemberParameterRule, DelegateParameterRule);
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterSymbolAction(symbolAnalysisContext =>
{
var symbol = symbolAnalysisContext.Symbol;
// FxCop compat: only analyze externally visible symbols by default
// Note all the descriptors/rules for this analyzer have the same ID and category and hence
// will always have identical configured visibility.
if (!symbolAnalysisContext.Options.MatchesConfiguredVisibility(AssemblyRule, symbol, symbolAnalysisContext.Compilation))
{
return;
}
switch (symbol.Kind)
{
case SymbolKind.Namespace:
{
if (ContainsUnderScore(symbol.Name))
{
symbolAnalysisContext.ReportDiagnostic(symbol.CreateDiagnostic(NamespaceRule, symbol.ToDisplayString()));
}
return;
}
case SymbolKind.NamedType:
{
var namedType = (INamedTypeSymbol)symbol;
AnalyzeTypeParameters(symbolAnalysisContext, namedType.TypeParameters);
if (namedType.TypeKind == TypeKind.Delegate &&
namedType.DelegateInvokeMethod != null)
{
AnalyzeParameters(symbolAnalysisContext, namedType.DelegateInvokeMethod.Parameters);
}
if (!ContainsUnderScore(symbol.Name))
{
return;
}
symbolAnalysisContext.ReportDiagnostic(symbol.CreateDiagnostic(TypeRule, symbol.ToDisplayString()));
return;
}
case SymbolKind.Field:
{
var fieldSymbol = (IFieldSymbol)symbol;
if (ContainsUnderScore(symbol.Name) && (fieldSymbol.IsConst || (fieldSymbol.IsStatic && fieldSymbol.IsReadOnly)))
{
symbolAnalysisContext.ReportDiagnostic(symbol.CreateDiagnostic(MemberRule, symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
return;
}
return;
}
default:
{
if (symbol is IMethodSymbol methodSymbol)
{
if (methodSymbol.IsOperator())
{
// Do not flag for operators.
return;
}
if (methodSymbol.MethodKind == MethodKind.Conversion)
{
// Do not flag for conversion methods generated for operators.
return;
}
AnalyzeParameters(symbolAnalysisContext, methodSymbol.Parameters);
AnalyzeTypeParameters(symbolAnalysisContext, methodSymbol.TypeParameters);
if (s_GlobalAsaxSpecialMethodNames.Contains(methodSymbol.Name) &&
methodSymbol.ContainingType.Inherits(symbolAnalysisContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebHttpApplication)))
{
// Do not flag the convention based web methods.
return;
}
}
if (symbol is IPropertySymbol propertySymbol)
{
AnalyzeParameters(symbolAnalysisContext, propertySymbol.Parameters);
}
if (!ContainsUnderScore(symbol.Name) || IsInvalidSymbol(symbol, symbolAnalysisContext))
{
return;
}
symbolAnalysisContext.ReportDiagnostic(symbol.CreateDiagnostic(MemberRule, symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)));
return;
}
}
},
SymbolKind.Namespace, // Namespace
SymbolKind.NamedType, //Type
SymbolKind.Method, SymbolKind.Property, SymbolKind.Field, SymbolKind.Event // Members
);
context.RegisterCompilationAction(compilationAnalysisContext =>
{
var compilation = compilationAnalysisContext.Compilation!;
if (ContainsUnderScore(compilation.AssemblyName))
{
compilationAnalysisContext.ReportDiagnostic(compilation.Assembly.CreateDiagnostic(AssemblyRule, compilation.AssemblyName));
}
});
}
private static bool IsInvalidSymbol(ISymbol symbol, SymbolAnalysisContext context)
{
// Note all the descriptors/rules for this analyzer have the same ID and category and hence
// will always have identical configured visibility.
var matchesConfiguration = context.Options.MatchesConfiguredVisibility(AssemblyRule, symbol, context.Compilation);
return (!(matchesConfiguration && !symbol.IsOverride)) ||
symbol.IsAccessorMethod() || symbol.IsImplementationOfAnyInterfaceMember();
}
private static void AnalyzeParameters(SymbolAnalysisContext symbolAnalysisContext, IEnumerable<IParameterSymbol> parameters)
{
foreach (var parameter in parameters)
{
if (ContainsUnderScore(parameter.Name) && !parameter.IsSymbolWithSpecialDiscardName())
{
var containingType = parameter.ContainingType;
// Parameter in Delegate
if (containingType.TypeKind == TypeKind.Delegate)
{
if (containingType.IsPublic())
{
symbolAnalysisContext.ReportDiagnostic(parameter.CreateDiagnostic(DelegateParameterRule, containingType.ToDisplayString(), parameter.Name));
}
}
else if (!IsInvalidSymbol(parameter.ContainingSymbol, symbolAnalysisContext))
{
symbolAnalysisContext.ReportDiagnostic(parameter.CreateDiagnostic(MemberParameterRule, parameter.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), parameter.Name));
}
}
}
}
private static void AnalyzeTypeParameters(SymbolAnalysisContext symbolAnalysisContext, IEnumerable<ITypeParameterSymbol> typeParameters)
{
foreach (var typeParameter in typeParameters)
{
if (ContainsUnderScore(typeParameter.Name))
{
var containingSymbol = typeParameter.ContainingSymbol;
if (containingSymbol.Kind == SymbolKind.NamedType)
{
if (containingSymbol.IsPublic())
{
symbolAnalysisContext.ReportDiagnostic(typeParameter.CreateDiagnostic(TypeTypeParameterRule, containingSymbol.ToDisplayString(), typeParameter.Name));
}
}
else if (containingSymbol.Kind == SymbolKind.Method && !IsInvalidSymbol(containingSymbol, symbolAnalysisContext))
{
symbolAnalysisContext.ReportDiagnostic(typeParameter.CreateDiagnostic(MethodTypeParameterRule, containingSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), typeParameter.Name));
}
}
}
}
private static bool ContainsUnderScore([NotNullWhen(true)] string? identifier)
{
return identifier != null && identifier.IndexOf('_') != -1;
}
}
}