File: ConstructorInitializerPlacementDiagnosticAnalyzer.cs
Web Access
Project: ..\..\..\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 System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Microsoft.CodeAnalysis.CSharp.NewLines.ConstructorInitializerPlacement
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    internal sealed class ConstructorInitializerPlacementDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
    {
        public ConstructorInitializerPlacementDiagnosticAnalyzer()
            : base(IDEDiagnosticIds.ConstructorInitializerPlacementDiagnosticId,
                   EnforceOnBuildValues.ConstructorInitializerPlacement,
                   CSharpCodeStyleOptions.AllowBlankLineAfterColonInConstructorInitializer,
                   new LocalizableResourceString(
                       nameof(CSharpAnalyzersResources.Blank_line_not_allowed_after_constructor_initializer_colon), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
        {
        }
 
        public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
            => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis;
 
        protected override void InitializeWorker(AnalysisContext context)
            => context.RegisterSyntaxTreeAction(AnalyzeTree);
 
        private void AnalyzeTree(SyntaxTreeAnalysisContext context)
        {
            var option = context.GetCSharpAnalyzerOptions().AllowBlankLineAfterColonInConstructorInitializer;
            if (option.Value)
                return;
 
            Recurse(context, option.Notification.Severity, context.Tree.GetRoot(context.CancellationToken));
        }
 
        private void Recurse(SyntaxTreeAnalysisContext context, ReportDiagnostic severity, SyntaxNode node)
        {
            context.CancellationToken.ThrowIfCancellationRequested();
 
            // Don't bother analyzing nodes that have syntax errors in them.
            if (node.ContainsDiagnostics)
                return;
 
            if (node is ConstructorInitializerSyntax initializer)
                ProcessConstructorInitializer(context, severity, initializer);
 
            foreach (var child in node.ChildNodesAndTokens())
            {
                if (child.IsNode)
                    Recurse(context, severity, child.AsNode()!);
            }
        }
 
        private void ProcessConstructorInitializer(
            SyntaxTreeAnalysisContext context, ReportDiagnostic severity, ConstructorInitializerSyntax initializer)
        {
            var sourceText = context.Tree.GetText(context.CancellationToken);
 
            var colonToken = initializer.ColonToken;
            var thisOrBaseKeyword = initializer.ThisOrBaseKeyword;
 
            var colonLine = sourceText.Lines.GetLineFromPosition(colonToken.SpanStart);
            var thisBaseLine = sourceText.Lines.GetLineFromPosition(thisOrBaseKeyword.SpanStart);
            if (colonLine == thisBaseLine)
                return;
 
            if (colonToken.TrailingTrivia.Count == 0)
                return;
 
            if (colonToken.TrailingTrivia.Last().Kind() != SyntaxKind.EndOfLineTrivia)
                return;
 
            if (colonToken.TrailingTrivia.Any(t => !t.IsWhitespaceOrEndOfLine()))
                return;
 
            if (thisOrBaseKeyword.LeadingTrivia.Any(t => !t.IsWhitespaceOrEndOfLine() && !t.IsSingleOrMultiLineComment()))
                return;
 
            context.ReportDiagnostic(DiagnosticHelper.Create(
                this.Descriptor,
                colonToken.GetLocation(),
                severity,
                additionalLocations: ImmutableArray.Create(initializer.GetLocation()),
                properties: null));
        }
    }
}