File: System\Windows\Forms\CSharp\Analyzers\Designer\InitializeComponentAnalyzer.cs
Web Access
Project: ..\..\..\src\winforms\src\System.Windows.Forms.Analyzers.CSharp\src\System.Windows.Forms.Analyzers.CSharp.csproj (System.Windows.Forms.Analyzers.CSharp)
// 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 System.Windows.Forms.Analyzers;
using System.Windows.Forms.Analyzers.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
 
namespace System.Windows.Forms.CSharp.Analyzers.Designer;
 
/// <summary>
///  Reports control flow that should not be emitted in <c>InitializeComponent</c>.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class InitializeComponentAnalyzer : DiagnosticAnalyzer
{
    /// <inheritdoc/>
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
        =>
        [
            SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode,
            SharedDiagnosticDescriptors.s_unsupportedNameOfExpression,
            SharedDiagnosticDescriptors.s_unsupportedConditionalExpression,
            SharedDiagnosticDescriptors.s_unsupportedNullCoalescingExpression,
            SharedDiagnosticDescriptors.s_unsupportedNullConditionalExpression,
            SharedDiagnosticDescriptors.s_unsupportedInterpolatedString,
            SharedDiagnosticDescriptors.s_unsupportedAnonymousFunction
        ];
 
    /// <inheritdoc/>
    public override void Initialize(AnalysisContext context)
    {
        context.EnableConcurrentExecution();
        context.ConfigureGeneratedCodeAnalysis(
            GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
        context.RegisterCompilationStartAction(startContext =>
        {
            DesignerTypeFacts facts = new(startContext.Compilation);
 
            startContext.RegisterSyntaxNodeAction(
                context => AnalyzeInitializeComponent(context, facts),
                SyntaxKind.MethodDeclaration);
        });
    }
 
    private static void AnalyzeInitializeComponent(SyntaxNodeAnalysisContext context, DesignerTypeFacts facts)
    {
        var method = (MethodDeclarationSyntax)context.Node;
        if (!DesignerTypeFacts.IsDesignerFile(method.SyntaxTree)
            || method.Identifier.ValueText != "InitializeComponent"
            || method.ParameterList.Parameters.Count != 0
            || method.ReturnType is not PredefinedTypeSyntax returnType
            || !returnType.Keyword.IsKind(SyntaxKind.VoidKeyword)
            || method.Modifiers.Any(SyntaxKind.StaticKeyword)
            || context.SemanticModel.GetDeclaredSymbol(
                method,
                context.CancellationToken) is not IMethodSymbol symbol
            || !DesignerTypeFacts.IsInitializeComponent(symbol)
            || !facts.IsDesignerDeclaration(symbol.ContainingType, method.SyntaxTree))
        {
            return;
        }
 
        if (method.ExpressionBody is { } expressionBody)
        {
            context.ReportDiagnostic(Diagnostic.Create(
                SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode,
                expressionBody.ArrowToken.GetLocation(),
                "expression-bodied method"));
        }
 
        SyntaxNode? body = (SyntaxNode?)method.Body ?? method.ExpressionBody;
 
        if (body is null)
        {
            return;
        }
 
        foreach (SyntaxNode node in body.DescendantNodes(descendIntoChildren: node => node is not AnonymousFunctionExpressionSyntax and not LocalFunctionStatementSyntax))
        {
            context.CancellationToken.ThrowIfCancellationRequested();
 
            if (TryGetUnsupportedConstruct(
                node,
                out SyntaxToken token,
                out DiagnosticDescriptor descriptor,
                out string? construct))
            {
                if (descriptor == SharedDiagnosticDescriptors.s_unsupportedNameOfExpression
                    && context.SemanticModel.GetOperation(node, context.CancellationToken) is not INameOfOperation)
                {
                    continue;
                }
 
                Diagnostic diagnostic = construct is null
                    ? Diagnostic.Create(descriptor, token.GetLocation())
                    : Diagnostic.Create(descriptor, token.GetLocation(), construct);
 
                context.ReportDiagnostic(diagnostic);
            }
        }
    }
 
    private static bool TryGetUnsupportedConstruct(
        SyntaxNode node,
        out SyntaxToken token,
        out DiagnosticDescriptor descriptor,
        out string? construct)
    {
        (token, descriptor, construct) = node switch
        {
            ForStatementSyntax statement
                => (statement.ForKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "for loop"),
 
            ForEachStatementSyntax statement
                => (statement.ForEachKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "foreach loop"),
 
            ForEachVariableStatementSyntax statement
                => (statement.ForEachKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "foreach loop"),
 
            WhileStatementSyntax statement
                => (statement.WhileKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "while loop"),
 
            DoStatementSyntax statement
                => (statement.DoKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "do loop"),
 
            IfStatementSyntax statement
                => (statement.IfKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "if statement"),
 
            SwitchStatementSyntax statement
                => (statement.SwitchKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "switch statement"),
 
            SwitchExpressionSyntax expression
                => (expression.SwitchKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "switch expression"),
 
            LocalFunctionStatementSyntax function
                => (function.Identifier, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "local function"),
 
            GotoStatementSyntax statement
                => (statement.GotoKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "goto statement"),
 
            InvocationExpressionSyntax invocation
                when invocation.Expression is IdentifierNameSyntax identifier
                    && identifier.Identifier.Text == "nameof"
                => (identifier.Identifier, SharedDiagnosticDescriptors.s_unsupportedNameOfExpression, null),
 
            ConditionalExpressionSyntax expression
                => (expression.QuestionToken, SharedDiagnosticDescriptors.s_unsupportedConditionalExpression, null),
 
            BinaryExpressionSyntax expression when expression.IsKind(SyntaxKind.CoalesceExpression)
                => (expression.OperatorToken, SharedDiagnosticDescriptors.s_unsupportedNullCoalescingExpression, null),
 
            AssignmentExpressionSyntax expression when expression.IsKind(SyntaxKind.CoalesceAssignmentExpression)
                => (expression.OperatorToken, SharedDiagnosticDescriptors.s_unsupportedNullCoalescingExpression, null),
 
            ConditionalAccessExpressionSyntax expression
                => (expression.OperatorToken, SharedDiagnosticDescriptors.s_unsupportedNullConditionalExpression, null),
 
            InterpolatedStringExpressionSyntax expression
                => (expression.StringStartToken, SharedDiagnosticDescriptors.s_unsupportedInterpolatedString, null),
 
            LambdaExpressionSyntax expression
                => (expression.ArrowToken, SharedDiagnosticDescriptors.s_unsupportedAnonymousFunction, null),
 
            AnonymousMethodExpressionSyntax expression
                => (expression.DelegateKeyword, SharedDiagnosticDescriptors.s_unsupportedAnonymousFunction, null),
 
            TryStatementSyntax statement
                => (statement.TryKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "try statement"),
 
            LockStatementSyntax statement
                => (statement.LockKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "lock statement"),
 
            UsingStatementSyntax statement
                => (statement.UsingKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "using statement"),
 
            LocalDeclarationStatementSyntax statement when !statement.UsingKeyword.IsKind(SyntaxKind.None)
                => (statement.UsingKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "using declaration"),
 
            AwaitExpressionSyntax expression
                => (expression.AwaitKeyword, SharedDiagnosticDescriptors.s_unsupportedInitializeComponentCode, "await expression"),
            _ => (default, null!, null)
        };
 
        return descriptor is not null;
    }
}