|
// 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.Collections;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CodeStyle;
internal abstract class AbstractFormattingAnalyzer
: AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
protected AbstractFormattingAnalyzer()
: base(
IDEDiagnosticIds.FormattingDiagnosticId,
EnforceOnBuildValues.Formatting,
option: null,
new LocalizableResourceString(nameof(AnalyzersResources.Fix_formatting), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)),
new LocalizableResourceString(nameof(AnalyzersResources.Fix_formatting), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)))
{
}
public sealed override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis;
protected abstract ISyntaxFormatting SyntaxFormatting { get; }
protected sealed override void InitializeWorker(AnalysisContext context)
=> context.RegisterCompilationStartAction(context =>
context.RegisterSyntaxTreeAction(treeContext => AnalyzeSyntaxTree(treeContext, context.Compilation.Options)));
/// <summary>
/// Fixing formatting is high priority. It's something the user wants to be able to fix quickly, is driven by
/// them acting on an error reported in code, and can be computed fast as it only uses syntax not semantics.
/// It's also the 8th most common fix that people use, and is picked almost all the times it is shown.
/// </summary>
public override bool IsHighPriority => true;
private void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context, CompilationOptions compilationOptions)
{
if (ShouldSkipAnalysis(context, compilationOptions, notification: null))
return;
var tree = context.Tree;
var cancellationToken = context.CancellationToken;
var oldText = tree.GetText(cancellationToken);
var root = tree.GetRoot(cancellationToken);
var span = context.FilterSpan.HasValue ? context.FilterSpan.GetValueOrDefault() : root.FullSpan;
var spans = SpecializedCollections.SingletonEnumerable(span);
var formattingOptions = context.GetAnalyzerOptions().GetSyntaxFormattingOptions(SyntaxFormatting);
var formattingChanges = SyntaxFormatting.GetFormattingResult(root, spans, formattingOptions, rules: default, cancellationToken).GetTextChanges(cancellationToken);
// formattingChanges could include changes that impact a larger section of the original document than
// necessary. Before reporting diagnostics, process the changes to minimize the span of individual
// diagnostics.
foreach (var formattingChange in formattingChanges)
{
var change = formattingChange;
Contract.ThrowIfNull(change.NewText);
if (change.NewText.Length > 0 && !change.Span.IsEmpty)
{
// Handle cases where the change is a substring removal from the beginning. In these cases, we want
// the diagnostic span to cover the unwanted leading characters (which should be removed), and
// nothing more.
var offset = change.Span.Length - change.NewText.Length;
if (offset >= 0)
{
if (oldText.GetSubText(new TextSpan(change.Span.Start + offset, change.NewText.Length)).ContentEquals(SourceText.From(change.NewText)))
{
change = new TextChange(new TextSpan(change.Span.Start, offset), "");
}
else
{
// Handle cases where the change is a substring removal from the end. In these cases, we want
// the diagnostic span to cover the unwanted trailing characters (which should be removed), and
// nothing more.
if (oldText.GetSubText(new TextSpan(change.Span.Start, change.NewText.Length)).ContentEquals(SourceText.From(change.NewText)))
{
change = new TextChange(new TextSpan(change.Span.Start + change.NewText.Length, offset), "");
}
}
}
}
Contract.ThrowIfNull(change.NewText);
if (change.NewText.Length == 0 && change.Span.IsEmpty)
{
// No actual change (allows for the formatter to report a NOP change without triggering a
// diagnostic that can't be fixed).
continue;
}
var location = Location.Create(tree, change.Span);
context.ReportDiagnostic(Diagnostic.Create(
Descriptor,
location,
additionalLocations: null,
properties: null));
}
}
}
|