Adding analyzer project

Contains two analyzers with fixes

* Use AnsiConsole over System.Console
* Favor local instance over static implementation
This commit is contained in:
Phil Scott
2021-06-20 19:47:19 -04:00
committed by Patrik Svensson
parent d015a4966f
commit 4f293d887d
20 changed files with 974 additions and 0 deletions

View File

@ -0,0 +1,25 @@
using Microsoft.CodeAnalysis.Diagnostics;
namespace Spectre.Console.Analyzer
{
/// <summary>
/// Base class for Spectre analyzers.
/// </summary>
public abstract class BaseAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc />
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(AnalyzeCompilation);
}
/// <summary>
/// Analyze compilation.
/// </summary>
/// <param name="compilationStartContext">Compilation Start Analysis Context.</param>
protected abstract void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext);
}
}

View File

@ -0,0 +1,89 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace Spectre.Console.Analyzer
{
/// <summary>
/// Analyzer to suggest using available instances of AnsiConsole over the static methods.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class FavorInstanceAnsiConsoleOverStaticAnalyzer : BaseAnalyzer
{
private static readonly DiagnosticDescriptor _diagnosticDescriptor =
Descriptors.S1010_FavorInstanceAnsiConsoleOverStatic;
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(_diagnosticDescriptor);
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
compilationStartContext.RegisterOperationAction(
context =>
{
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
// if this operation isn't an invocation against one of the System.Console methods
// defined in _methods then we can safely stop analyzing and return;
var invocationOperation = (IInvocationOperation)context.Operation;
if (!Equals(invocationOperation.TargetMethod.ContainingType, ansiConsoleType))
{
return;
}
if (!HasFieldAnsiConsole(invocationOperation.Syntax) &&
!HasParameterAnsiConsole(invocationOperation.Syntax))
{
return;
}
var methodSymbol = invocationOperation.TargetMethod;
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
private static bool HasParameterAnsiConsole(SyntaxNode syntaxNode)
{
return syntaxNode
.Ancestors().OfType<MethodDeclarationSyntax>()
.First()
.ParameterList.Parameters
.Any(i => i.Type.NormalizeWhitespace().ToString() == "IAnsiConsole");
}
private static bool HasFieldAnsiConsole(SyntaxNode syntaxNode)
{
var isStatic = syntaxNode
.Ancestors()
.OfType<MethodDeclarationSyntax>()
.First()
.Modifiers.Any(i => i.Kind() == SyntaxKind.StaticKeyword);
return syntaxNode
.Ancestors().OfType<ClassDeclarationSyntax>()
.First()
.Members
.OfType<FieldDeclarationSyntax>()
.Any(i =>
i.Declaration.Type.NormalizeWhitespace().ToString() == "IAnsiConsole" &&
(!isStatic ^ i.Modifiers.Any(modifier => modifier.Kind() == SyntaxKind.StaticKeyword)));
}
}
}

View File

@ -0,0 +1,63 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace Spectre.Console.Analyzer
{
/// <summary>
/// Analyzer to enforce the use of AnsiConsole over System.Console for known methods.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class UseSpectreInsteadOfSystemConsoleAnalyzer : BaseAnalyzer
{
private static readonly DiagnosticDescriptor _diagnosticDescriptor =
Descriptors.S1000_UseAnsiConsoleOverSystemConsole;
private static readonly string[] _methods = { "WriteLine", "Write" };
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(_diagnosticDescriptor);
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
compilationStartContext.RegisterOperationAction(
context =>
{
// if this operation isn't an invocation against one of the System.Console methods
// defined in _methods then we can safely stop analyzing and return;
var invocationOperation = (IInvocationOperation)context.Operation;
var systemConsoleType = context.Compilation.GetTypeByMetadataName("System.Console");
if (!Equals(invocationOperation.TargetMethod.ContainingType, systemConsoleType))
{
return;
}
var methodName = System.Array.Find(_methods, i => i.Equals(invocationOperation.TargetMethod.Name));
if (methodName == null)
{
return;
}
var methodSymbol = invocationOperation.TargetMethod;
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
}
}