namespace Spectre.Console.Analyzer; /// /// Analyzer to suggest using available instances of AnsiConsole over the static methods. /// [DiagnosticAnalyzer(LanguageNames.CSharp)] public class FavorInstanceAnsiConsoleOverStaticAnalyzer : SpectreAnalyzer { private static readonly DiagnosticDescriptor _diagnosticDescriptor = Descriptors.S1010_FavorInstanceAnsiConsoleOverStatic; /// public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(_diagnosticDescriptor); /// 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 (!SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod.ContainingType, ansiConsoleType)) { return; } // if we aren't in a method then it might be too complex for us to handle. if (!invocationOperation.Syntax.Ancestors().OfType().Any()) { 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() .First() .ParameterList.Parameters .Any(i => i.Type?.NormalizeWhitespace()?.ToString() == "IAnsiConsole"); } private static bool HasFieldAnsiConsole(SyntaxNode syntaxNode) { var isStatic = syntaxNode .Ancestors() .OfType() .First() .Modifiers.Any(i => i.IsKind(SyntaxKind.StaticKeyword)); return syntaxNode .Ancestors().OfType() .First() .Members .OfType() .Any(i => i.Declaration.Type.NormalizeWhitespace().ToString() == "IAnsiConsole" && (!isStatic ^ i.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.StaticKeyword)))); } }