File: src\Analyzers\Core\Analyzers\FileHeaders\AbstractFileHeaderDiagnosticAnalyzer.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.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.IO;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Microsoft.CodeAnalysis.FileHeaders;
 
internal abstract class AbstractFileHeaderDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
    private static readonly LocalizableString s_invalidHeaderTitle = new LocalizableResourceString(nameof(AnalyzersResources.The_file_header_does_not_match_the_required_text), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
    private static readonly LocalizableString s_invalidHeaderMessage = new LocalizableResourceString(nameof(AnalyzersResources.A_source_file_contains_a_header_that_does_not_match_the_required_text), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
    private static readonly DiagnosticDescriptor s_invalidHeaderDescriptor = CreateDescriptorForFileHeader(s_invalidHeaderTitle, s_invalidHeaderMessage);
 
    private static readonly LocalizableString s_missingHeaderTitle = new LocalizableResourceString(nameof(AnalyzersResources.The_file_header_is_missing_or_not_located_at_the_top_of_the_file), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
    private static readonly LocalizableString s_missingHeaderMessage = new LocalizableResourceString(nameof(AnalyzersResources.A_source_file_is_missing_a_required_header), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
    private static readonly DiagnosticDescriptor s_missingHeaderDescriptor = CreateDescriptorForFileHeader(s_missingHeaderTitle, s_missingHeaderMessage);
 
    private static DiagnosticDescriptor CreateDescriptorForFileHeader(LocalizableString title, LocalizableString message)
        => CreateDescriptorWithId(IDEDiagnosticIds.FileHeaderMismatch, EnforceOnBuildValues.FileHeaderMismatch, hasAnyCodeStyleOption: false, title, message);
 
    protected AbstractFileHeaderDiagnosticAnalyzer()
        : base(
            [
                (s_invalidHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate),
                (s_missingHeaderDescriptor, CodeStyleOptions2.FileHeaderTemplate)
            ])
    {
    }
 
    protected abstract AbstractFileHeaderHelper FileHeaderHelper { get; }
 
    public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis;
 
    protected override void InitializeWorker(AnalysisContext context)
        => context.RegisterCompilationStartAction(context =>
            context.RegisterSyntaxTreeAction(treeContext => HandleSyntaxTree(treeContext, context.Compilation.Options)));
 
    private void HandleSyntaxTree(SyntaxTreeAnalysisContext context, CompilationOptions compilationOptions)
    {
        if (ShouldSkipAnalysis(context, compilationOptions, notification: null))
            return;
 
        var tree = context.Tree;
        var root = tree.GetRoot(context.CancellationToken);
 
        // don't process empty files
        if (root.FullSpan.IsEmpty)
        {
            return;
        }
 
        var fileHeaderTemplate = context.GetAnalyzerOptions().FileHeaderTemplate;
        if (string.IsNullOrEmpty(fileHeaderTemplate))
        {
            return;
        }
 
        var fileHeader = FileHeaderHelper.ParseFileHeader(root);
 
        if (!context.ShouldAnalyzeSpan(fileHeader.GetLocation(tree).SourceSpan))
        {
            return;
        }
 
        if (fileHeader.IsMissing)
        {
            context.ReportDiagnostic(Diagnostic.Create(s_missingHeaderDescriptor, fileHeader.GetLocation(tree)));
            return;
        }
 
        var expectedFileHeader = fileHeaderTemplate.Replace("{fileName}", Path.GetFileName(tree.FilePath));
        if (!CompareCopyrightText(expectedFileHeader, fileHeader.CopyrightText))
        {
            context.ReportDiagnostic(Diagnostic.Create(s_invalidHeaderDescriptor, fileHeader.GetLocation(tree)));
            return;
        }
    }
 
    private static bool CompareCopyrightText(string expectedFileHeader, string copyrightText)
    {
        // make sure that both \n and \r\n are accepted from the settings.
        var reformattedCopyrightTextParts = expectedFileHeader.Replace("\r\n", "\n").Split('\n');
        var fileHeaderCopyrightTextParts = copyrightText.Replace("\r\n", "\n").Split('\n');
 
        if (reformattedCopyrightTextParts.Length != fileHeaderCopyrightTextParts.Length)
        {
            return false;
        }
 
        // compare line by line, ignoring leading and trailing whitespace on each line.
        for (var i = 0; i < reformattedCopyrightTextParts.Length; i++)
        {
            if (string.CompareOrdinal(reformattedCopyrightTextParts[i].Trim(), fileHeaderCopyrightTextParts[i].Trim()) != 0)
            {
                return false;
            }
        }
 
        return true;
    }
}