File: CoalesceAnalyzer.cs
Web Access
Project: src\src\Analyzers\Microsoft.Analyzers.Extra\Microsoft.Analyzers.Extra.csproj (Microsoft.Analyzers.Extra)
// 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.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.Extensions.ExtraAnalyzers.Utilities;
 
namespace Microsoft.Extensions.ExtraAnalyzers;
 
/// <summary>
/// C# analyzer that recommends removing superfluous uses of ?? and ??=.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CoalesceAnalyzer : DiagnosticAnalyzer
{
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DiagDescriptors.CoalesceAssignment, DiagDescriptors.Coalesce);
 
    public override void Initialize(AnalysisContext context)
    {
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
        context.EnableConcurrentExecution();
 
        context.RegisterCompilationStartAction(compilationStartContext =>
        {
            // only report diagnostics on .NET 6, since previous runtimes don't have good enough nullability annotations
            if (compilationStartContext.Compilation.IsNet6OrGreater())
            {
                compilationStartContext.RegisterOperationAction(operationAnalysisContext =>
                {
                    var op = (ICoalesceAssignmentOperation)operationAnalysisContext.Operation;
 
                    var type = op.Target.Type;
                    if (type != null
                        && type.NullableAnnotation == NullableAnnotation.NotAnnotated
                        && type.OriginalDefinition.SpecialType != SpecialType.System_Nullable_T)
                    {
                        if (op.Target.Kind == OperationKind.ParameterReference)
                        {
                            var pr = (IParameterReferenceOperation)op.Target;
                            var method = pr.Parameter.ContainingSymbol as IMethodSymbol;
 
                            if (pr.Parameter.ContainingSymbol.IsExternallyVisible()
                                || (method != null && method.ImplementsPublicInterface()))
                            {
                                // this is a ??= applied to a parameter of a public method, let it slide...
                                return;
                            }
                        }
 
                        var diagnostic = Diagnostic.Create(DiagDescriptors.CoalesceAssignment, op.Syntax.GetLocation());
                        operationAnalysisContext.ReportDiagnostic(diagnostic);
                    }
                }, OperationKind.CoalesceAssignment);
 
                compilationStartContext.RegisterOperationAction(operationAnalysisContext =>
                {
                    var op = (ICoalesceOperation)operationAnalysisContext.Operation;
 
                    var type = op.Value.Type;
                    if (type != null
                        && type.NullableAnnotation == NullableAnnotation.NotAnnotated
                        && type.OriginalDefinition.SpecialType != SpecialType.System_Nullable_T)
                    {
                        if (op.Value.Kind == OperationKind.ParameterReference)
                        {
                            var pr = (IParameterReferenceOperation)op.Value;
                            var method = pr.Parameter.ContainingSymbol as IMethodSymbol;
 
                            if (pr.Parameter.ContainingSymbol.IsExternallyVisible()
                                || (method != null && method.ImplementsPublicInterface()))
                            {
                                // this is a ?? applied to a parameter of a public method, let it slide...
                                return;
                            }
                        }
 
                        var diagnostic = Diagnostic.Create(DiagDescriptors.Coalesce, op.Syntax.GetLocation());
                        operationAnalysisContext.ReportDiagnostic(diagnostic);
                    }
                }, OperationKind.Coalesce);
            }
        });
    }
}