File: src\roslyn\src\Analyzers\CSharp\Analyzers\UseLabeledJumpStatements\CSharpUseLabeledJumpStatementsDiagnosticAnalyzer.cs
Web Access
Project: src\roslyn\src\CodeStyle\CSharp\Analyzers\Microsoft.CodeAnalysis.CSharp.CodeStyle.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle)
// 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 Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.CodeAnalysis.CSharp.UseLabeledJumpStatements;

/// <summary>
/// Offers to replace the workarounds people write to emulate labeled <see langword="break"/>/<see
/// langword="continue"/> with the real labeled jump statements. See <see
/// cref="CSharpUseLabeledJumpStatementsHelpers"/> for the patterns that are detected.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpUseLabeledJumpStatementsDiagnosticAnalyzer()
    : AbstractBuiltInCodeStyleDiagnosticAnalyzer(
        IDEDiagnosticIds.UseLabeledJumpStatementDiagnosticId,
        EnforceOnBuildValues.UseLabeledJumpStatement,
        CSharpCodeStyleOptions.PreferLabeledJumpStatements,
        new LocalizableResourceString(
            nameof(CSharpAnalyzersResources.Use_labeled_jump_statement), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
{
    public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis;

    protected override void InitializeWorker(AnalysisContext context)
    {
        context.RegisterCompilationStartAction(context =>
        {
            if (!context.Compilation.LanguageVersion().IsCSharp15OrAbove())
                return;

            context.RegisterSyntaxNodeAction(AnalyzeStatement, SyntaxKind.GotoStatement, SyntaxKind.BreakStatement);
        });

        return;

        void AnalyzeStatement(SyntaxNodeAnalysisContext context)
        {
            var option = context.GetCSharpAnalyzerOptions().PreferLabeledJumpStatements;
            if (!option.Value || ShouldSkipAnalysis(context, option.Notification))
                return;

            var statement = context.Node;
            var semanticModel = context.SemanticModel;
            var cancellationToken = context.CancellationToken;

            if (statement is GotoStatementSyntax gotoStatement)
            {
                if (CSharpUseLabeledJumpStatementsHelpers.TryGetGotoBreakPattern(gotoStatement, semanticModel, cancellationToken, out _, out _, out _) ||
                    CSharpUseLabeledJumpStatementsHelpers.TryGetGotoContinuePattern(gotoStatement, semanticModel, cancellationToken, out _, out _, out _))
                {
                    ReportDiagnostic(context, gotoStatement, option.Notification);
                }
            }
            else if (statement is BreakStatementSyntax breakStatement)
            {
                // We register on 'break' (not 'continue') because the flag pattern's inner jump is always a 'break': it
                // has to exit the inner loop so control returns to the outer loop where the flag is checked.  Whether
                // that emulates a break or a continue of the outer loop is decided by the guard ('if (flag)
                // break/continue;'), not by this inner jump, so this single registration covers both.
                if (CSharpUseLabeledJumpStatementsHelpers.TryGetFlagPatternFromInnerBreak(breakStatement, semanticModel, cancellationToken, out _))
                    ReportDiagnostic(context, breakStatement, option.Notification);
            }
        }

        void ReportDiagnostic(SyntaxNodeAnalysisContext context, StatementSyntax statement, NotificationOption2 notification)
        {
            context.ReportDiagnostic(DiagnosticHelper.Create(
                Descriptor,
                statement.GetFirstToken().GetLocation(),
                notification,
                context.Options,
                additionalLocations: [statement.GetLocation()],
                properties: null));
        }
    }
}