| File: Copilot\CSharpImplementNotImplementedExceptionDiagnosticAnalyzer.cs | Web Access |
| Project: src\roslyn\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), 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; } }