File: Copilot\CSharpImplementNotImplementedExceptionDiagnosticAnalyzer.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// 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.
 
using System;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.CodeAnalysis.CSharp.Copilot;
 
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpImplementNotImplementedExceptionDiagnosticAnalyzer()
    : AbstractBuiltInCodeStyleDiagnosticAnalyzer(
        IDEDiagnosticIds.CopilotImplementNotImplementedExceptionDiagnosticId,
        EnforceOnBuildValues.CopilotImplementNotImplementedException,
        option: null,
        new LocalizableResourceString(
            nameof(CSharpAnalyzersResources.Implement_with_Copilot), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)),
        configurable: false)
{
    public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
 
    protected override void InitializeWorker(AnalysisContext context)
    {
        context.RegisterCompilationStartAction(context =>
        {
            var notImplementedExceptionType = context.Compilation.GetTypeByMetadataName(typeof(NotImplementedException).FullName!);
            if (notImplementedExceptionType is null)
                return;
 
            context.RegisterOperationBlockAction(context => AnalyzeOperationBlock(context, notImplementedExceptionType));
        });
    }
 
    private void AnalyzeOperationBlock(
        OperationBlockAnalysisContext context,
        INamedTypeSymbol notImplementedExceptionType)
    {
        foreach (var block in context.OperationBlocks)
            AnalyzeBlock(block);
 
        void AnalyzeBlock(IOperation block)
        {
            foreach (var operation in block.DescendantsAndSelf())
            {
                if (operation is IThrowOperation
                    {
                        Exception: IConversionOperation
                        {
                            Operand: IObjectCreationOperation
                            {
                                Constructor.ContainingType: INamedTypeSymbol constructedType,
                            },
                        },
                        Syntax: var throwSyntax
                    } throwOperation &&
                    notImplementedExceptionType.Equals(constructedType))
                {
                    // Report diagnostic for each throw operation
                    context.ReportDiagnostic(Diagnostic.Create(
                        Descriptor,
                        throwOperation.Syntax.GetLocation()));
 
                    // If the throw is the top-level operation in the containing symbol, report a diagnostic on the
                    // symbol as well. Note: consider reporting on the entire symbol, instead of just the name.  And in
                    // this case, do not report directly on the throw as well.
                    if (ShouldReportAsTopLevel(block, operation))
                    {
                        foreach (var location in context.OwningSymbol.Locations)
                        {
                            if (location.SourceTree == context.FilterTree)
                            {
                                context.ReportDiagnostic(Diagnostic.Create(
                                    Descriptor,
                                    location));
                            }
                        }
                    }
                }
            }
        }
    }
 
    private static bool ShouldReportAsTopLevel(IOperation block, IOperation operation)
    {
        if (block is IBlockOperation { Operations: [var child] })
        {
            // Handle: { throw new NotImplementedException(); }
            if (child == operation)
                return true;
 
            // Handle: => throw new NotImplementedException();
            if (child is IReturnOperation
                {
                    ReturnedValue: IConversionOperation
                    {
                        Operand: var operand
                    }
                } && operand == operation &&
                // Excluding property declarations with expression bodies
                // Because their location is an exact match with the throw operation
                // and we don't want to report the same location twice
                operation.Syntax.Parent is not ArrowExpressionClauseSyntax { Parent: PropertyDeclarationSyntax })
            {
                // Include expression-bodied methods and get accessors
                return true;
            }
 
            // Handle expression-bodied set accessors
            if (child is IExpressionStatementOperation { Operation: var throwOperation } && throwOperation == operation)
                return true;
        }
 
        return false;
    }
}