| File: CSharpAvoidOptSuffixForNullableEnableCode.cs | Web Access |
| Project: src\roslyn\src\RoslynAnalyzers\Roslyn.Diagnostics.Analyzers\CSharp\Roslyn.Diagnostics.CSharp.Analyzers.csproj (Roslyn.Diagnostics.CSharp.Analyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable warnings using System; using System.Collections.Immutable; using System.Threading; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Roslyn.Diagnostics.Analyzers; namespace Roslyn.Diagnostics.CSharp.Analyzers { using static RoslynDiagnosticsAnalyzersResources; /// <summary> /// RS0046: <inheritdoc cref="AvoidOptSuffixForNullableEnableCodeTitle"/> /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CSharpAvoidOptSuffixForNullableEnableCode : DiagnosticAnalyzer { internal const string OptSuffix = "Opt"; internal static readonly DiagnosticDescriptor Rule = new( RoslynDiagnosticIds.AvoidOptSuffixForNullableEnableCodeRuleId, CreateLocalizableResourceString(nameof(AvoidOptSuffixForNullableEnableCodeTitle)), CreateLocalizableResourceString(nameof(AvoidOptSuffixForNullableEnableCodeMessage)), DiagnosticCategory.RoslynDiagnosticsDesign, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: CreateLocalizableResourceString(nameof(AvoidOptSuffixForNullableEnableCodeDescription)), helpLinkUri: null, customTags: WellKnownDiagnosticTagsExtensions.Telemetry); public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterSyntaxNodeAction(context => { var parameter = (ParameterSyntax)context.Node; ReportOnInvalidIdentifier(parameter.Identifier, context.SemanticModel, context.ReportDiagnostic, context.CancellationToken); }, SyntaxKind.Parameter); context.RegisterSyntaxNodeAction(context => { var variableDeclarator = (VariableDeclaratorSyntax)context.Node; ReportOnInvalidIdentifier(variableDeclarator.Identifier, context.SemanticModel, context.ReportDiagnostic, context.CancellationToken); }, SyntaxKind.VariableDeclarator); context.RegisterSyntaxNodeAction(context => { var propertyDeclaration = (PropertyDeclarationSyntax)context.Node; ReportOnInvalidIdentifier(propertyDeclaration.Identifier, context.SemanticModel, context.ReportDiagnostic, context.CancellationToken); }, SyntaxKind.PropertyDeclaration); } private static void ReportOnInvalidIdentifier(SyntaxToken identifier, SemanticModel semanticModel, Action<Diagnostic> reportAction, CancellationToken cancellationToken) { if (!identifier.Text.EndsWith(OptSuffix, StringComparison.Ordinal) || !semanticModel.GetNullableContext(identifier.SpanStart).AnnotationsEnabled()) { return; } var symbol = semanticModel.GetDeclaredSymbol(identifier.Parent, cancellationToken); if (ShouldReport(symbol)) { reportAction(identifier.CreateDiagnostic(Rule)); } } private static bool ShouldReport(ISymbol symbol) { if (symbol?.GetMemberOrLocalOrParameterType()?.NullableAnnotation != NullableAnnotation.Annotated) { // Not in a nullable context, bail-out return false; } return symbol.Kind switch { SymbolKind.Property => !symbol.IsOverride && !symbol.IsImplementationOfAnyInterfaceMember(), SymbolKind.Parameter => symbol.ContainingSymbol != null && !symbol.ContainingSymbol.IsOverride && !symbol.ContainingSymbol.IsImplementationOfAnyInterfaceMember(), _ => true }; } } }