File: src\Analyzers\CSharp\Analyzers\NewLines\ArrowExpressionClausePlacement\ArrowExpressionClausePlacementDiagnosticAnalyzer.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 System.Linq;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.CSharp.NewLines.ArrowExpressionClausePlacement;
 
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class ArrowExpressionClausePlacementDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
    public ArrowExpressionClausePlacementDiagnosticAnalyzer()
        : base(IDEDiagnosticIds.ArrowExpressionClausePlacementDiagnosticId,
               EnforceOnBuildValues.ArrowExpressionClausePlacement,
               CSharpCodeStyleOptions.AllowBlankLineAfterTokenInArrowExpressionClause,
               new LocalizableResourceString(
                   nameof(CSharpAnalyzersResources.Blank_line_not_allowed_after_arrow_expression_clause_token), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
    {
    }
 
    public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis;
 
    protected override void InitializeWorker(AnalysisContext context)
        => context.RegisterCompilationStartAction(context =>
            context.RegisterSyntaxTreeAction(treeContext => AnalyzeTree(treeContext, context.Compilation.Options)));
 
    private void AnalyzeTree(SyntaxTreeAnalysisContext context, CompilationOptions compilationOptions)
    {
        var option = context.GetCSharpAnalyzerOptions().AllowBlankLineAfterTokenInArrowExpressionClause;
        if (option.Value || ShouldSkipAnalysis(context, compilationOptions, option.Notification))
            return;
 
        Recurse(context, option.Notification, context.GetAnalysisRoot(findInTrivia: false));
    }
 
    private void Recurse(SyntaxTreeAnalysisContext context, NotificationOption2 notificationOption, SyntaxNode node)
    {
        context.CancellationToken.ThrowIfCancellationRequested();
 
        if (node is ArrowExpressionClauseSyntax arrowExpressionClause)
            ProcessArrowExpressionClause(context, notificationOption, arrowExpressionClause);
 
        foreach (var child in node.ChildNodesAndTokens())
        {
            if (!context.ShouldAnalyzeSpan(child.Span))
                continue;
 
            if (child.AsNode(out var childNode))
                Recurse(context, notificationOption, childNode);
        }
    }
 
    private void ProcessArrowExpressionClause(
        SyntaxTreeAnalysisContext context, NotificationOption2 notificationOption, ArrowExpressionClauseSyntax arrowExpressionClause)
    {
        // get
        //     => 1 + 2;
        //
        // Never looks good.  So we don't process in that case.
        if (arrowExpressionClause.Parent is AccessorDeclarationSyntax)
            return;
 
        // Don't bother analyzing nodes that have syntax errors in them.
        if (arrowExpressionClause.GetDiagnostics().Any(static d => d.Severity == DiagnosticSeverity.Error))
            return;
 
        if (IsOk(arrowExpressionClause.ArrowToken))
            return;
 
        context.ReportDiagnostic(DiagnosticHelper.Create(
            this.Descriptor,
            arrowExpressionClause.ArrowToken.GetLocation(),
            notificationOption,
            context.Options,
            additionalLocations: null,
            properties: null));
 
        return;
 
        static bool IsOk(SyntaxToken token)
        {
            // Only care about tokens that are actually present.  Missing ones mean the code is incomplete and we
            // don't want to complain about those.
            if (token.IsMissing)
                return true;
 
            // Arrow has to be at the end of the line for us to actually care.
            if (token.TrailingTrivia is not [.., SyntaxTrivia(SyntaxKind.EndOfLineTrivia)])
                return true;
 
            // if the next token has pp-directives on it, we don't want to move the token around as we may screw
            // things up in different pp-contexts.
            var nextToken = token.GetNextToken();
            if (nextToken == default)
                return true;
 
            if (nextToken.LeadingTrivia.Any(static t => t.Kind() is
                    SyntaxKind.IfDirectiveTrivia or SyntaxKind.ElseDirectiveTrivia or SyntaxKind.ElifDirectiveTrivia or SyntaxKind.EndIfDirectiveTrivia))
            {
                return true;
            }
 
            // Not ok.
            return false;
        }
    }
}