From bdcc01ea68cbf3b49d5601c56f581cb73c65ba0f Mon Sep 17 00:00:00 2001 From: Phil Scott Date: Mon, 21 Jun 2021 13:49:55 -0400 Subject: [PATCH] Adding analyzers for common mistakes with live renderables --- examples/Console/AnalyzerTester/Program.cs | 25 +--- .../NoConcurrentLiveRenderablesAnalyzer.cs | 78 +++++++++++++ .../NoPromptsDuringLiveRenderablesAnalyzer.cs | 84 ++++++++++++++ ...seSpectreInsteadOfSystemConsoleAnalyzer.cs | 54 ++++----- src/Spectre.Console.Analyzer/Constants.cs | 7 ++ src/Spectre.Console.Analyzer/Descriptors.cs | 22 ++++ .../NoConcurrentLiveRenderablesTests.cs | 86 ++++++++++++++ .../NoPromptsDuringLiveRenderablesTests.cs | 108 ++++++++++++++++++ .../Analyzers/UseInstanceAnsiConsoleTests.cs | 40 +++++++ ...ctreInsteadOfSystemConsoleAnalyzerTests.cs | 18 +++ .../CodeAnalyzer/CodeFixProviderDiscovery.cs | 1 - .../UseInstanceOfStaticAnsiConsoleTests.cs | 68 ++++++++++- .../CodeAnalyzer/SpectreAnalyzerVerifier.cs | 2 - 13 files changed, 540 insertions(+), 53 deletions(-) create mode 100644 src/Spectre.Console.Analyzer/Analyzers/NoConcurrentLiveRenderablesAnalyzer.cs create mode 100644 src/Spectre.Console.Analyzer/Analyzers/NoPromptsDuringLiveRenderablesAnalyzer.cs create mode 100644 src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/NoConcurrentLiveRenderablesTests.cs create mode 100644 src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/NoPromptsDuringLiveRenderablesTests.cs diff --git a/examples/Console/AnalyzerTester/Program.cs b/examples/Console/AnalyzerTester/Program.cs index ed5b945..c7f48f0 100644 --- a/examples/Console/AnalyzerTester/Program.cs +++ b/examples/Console/AnalyzerTester/Program.cs @@ -1,32 +1,13 @@ +using System.Threading; using Spectre.Console; namespace AnalyzerTester { class Program { - static void Main(string[] args) + static void Main() { - AnsiConsole.WriteLine("Hello World!"); - } - } - - class Dependency - { - private readonly IAnsiConsole _ansiConsole; - - public Dependency(IAnsiConsole ansiConsole) - { - _ansiConsole = ansiConsole; - } - - public void DoIt() - { - _ansiConsole.WriteLine("Hey mom!"); - } - - public void DoIt(IAnsiConsole thisConsole) - { - thisConsole.WriteLine("Hey mom!"); + AnsiConsole.WriteLine("Project is set up with a reference to Spectre.Console.Analyzer"); } } } diff --git a/src/Spectre.Console.Analyzer/Analyzers/NoConcurrentLiveRenderablesAnalyzer.cs b/src/Spectre.Console.Analyzer/Analyzers/NoConcurrentLiveRenderablesAnalyzer.cs new file mode 100644 index 0000000..5d77816 --- /dev/null +++ b/src/Spectre.Console.Analyzer/Analyzers/NoConcurrentLiveRenderablesAnalyzer.cs @@ -0,0 +1,78 @@ +using System.Collections.Immutable; +using System.Composition; +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 +{ + /// + /// Analyzer to detect calls to live renderables within a live renderable context. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + [Shared] + public class NoConcurrentLiveRenderablesAnalyzer : BaseAnalyzer + { + private static readonly DiagnosticDescriptor _diagnosticDescriptor = + Descriptors.S1020_AvoidConcurrentCallsToMultipleLiveRenderables; + + /// + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(_diagnosticDescriptor); + + /// + protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext) + { + compilationStartContext.RegisterOperationAction( + context => + { + var invocationOperation = (IInvocationOperation)context.Operation; + var methodSymbol = invocationOperation.TargetMethod; + + const string StartMethod = "Start"; + if (methodSymbol.Name != StartMethod) + { + return; + } + + var liveTypes = Constants.LiveRenderables + .Select(i => context.Compilation.GetTypeByMetadataName(i)) + .ToImmutableArray(); + + if (liveTypes.All(i => !Equals(i, methodSymbol.ContainingType))) + { + return; + } + + var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree); + var parentInvocations = invocationOperation + .Syntax.Ancestors() + .OfType() + .Select(i => model.GetOperation(i)) + .OfType() + .ToList(); + + if (parentInvocations.All(parent => + parent.TargetMethod.Name != StartMethod || !liveTypes.Contains(parent.TargetMethod.ContainingType))) + { + return; + } + + var displayString = SymbolDisplay.ToDisplayString( + methodSymbol, + SymbolDisplayFormat.CSharpShortErrorMessageFormat + .WithParameterOptions(SymbolDisplayParameterOptions.None) + .WithGenericsOptions(SymbolDisplayGenericsOptions.None)); + + context.ReportDiagnostic( + Diagnostic.Create( + _diagnosticDescriptor, + invocationOperation.Syntax.GetLocation(), + displayString)); + }, OperationKind.Invocation); + } + } +} \ No newline at end of file diff --git a/src/Spectre.Console.Analyzer/Analyzers/NoPromptsDuringLiveRenderablesAnalyzer.cs b/src/Spectre.Console.Analyzer/Analyzers/NoPromptsDuringLiveRenderablesAnalyzer.cs new file mode 100644 index 0000000..662431a --- /dev/null +++ b/src/Spectre.Console.Analyzer/Analyzers/NoPromptsDuringLiveRenderablesAnalyzer.cs @@ -0,0 +1,84 @@ +using System.Collections.Immutable; +using System.Composition; +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 +{ + /// + /// Analyzer to detect calls to live renderables within a live renderable context. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + [Shared] + public class NoPromptsDuringLiveRenderablesAnalyzer : BaseAnalyzer + { + private static readonly DiagnosticDescriptor _diagnosticDescriptor = + Descriptors.S1021_AvoidPromptCallsDuringLiveRenderables; + + /// + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(_diagnosticDescriptor); + + /// + 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 methodSymbol = invocationOperation.TargetMethod; + + var promptMethods = ImmutableArray.Create("Ask", "Confirm", "Prompt"); + if (!promptMethods.Contains(methodSymbol.Name)) + { + return; + } + + var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole"); + var ansiConsoleExtensionsType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsoleExtensions"); + + if (!Equals(methodSymbol.ContainingType, ansiConsoleType) && !Equals(methodSymbol.ContainingType, ansiConsoleExtensionsType)) + { + return; + } + + var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree); + var parentInvocations = invocationOperation + .Syntax.Ancestors() + .OfType() + .Select(i => model.GetOperation(i)) + .OfType() + .ToList(); + + var liveTypes = Constants.LiveRenderables + .Select(i => context.Compilation.GetTypeByMetadataName(i)) + .ToImmutableArray(); + + if (parentInvocations.All(parent => + parent.TargetMethod.Name != "Start" || + !liveTypes.Contains(parent.TargetMethod.ContainingType))) + { + return; + } + + var displayString = SymbolDisplay.ToDisplayString( + methodSymbol, + SymbolDisplayFormat.CSharpShortErrorMessageFormat + .WithParameterOptions(SymbolDisplayParameterOptions.None) + .WithGenericsOptions(SymbolDisplayGenericsOptions.None)); + + context.ReportDiagnostic( + Diagnostic.Create( + _diagnosticDescriptor, + invocationOperation.Syntax.GetLocation(), + displayString)); + }, OperationKind.Invocation); + } + } +} \ No newline at end of file diff --git a/src/Spectre.Console.Analyzer/Analyzers/UseSpectreInsteadOfSystemConsoleAnalyzer.cs b/src/Spectre.Console.Analyzer/Analyzers/UseSpectreInsteadOfSystemConsoleAnalyzer.cs index 5dbe6c0..5415bb2 100644 --- a/src/Spectre.Console.Analyzer/Analyzers/UseSpectreInsteadOfSystemConsoleAnalyzer.cs +++ b/src/Spectre.Console.Analyzer/Analyzers/UseSpectreInsteadOfSystemConsoleAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; @@ -27,37 +26,38 @@ namespace Spectre.Console.Analyzer { 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; - } + // 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 methodName = System.Array.Find(_methods, i => i.Equals(invocationOperation.TargetMethod.Name)); - if (methodName == null) - { - return; - } + var methodName = System.Array.Find(_methods, i => i.Equals(invocationOperation.TargetMethod.Name)); + if (methodName == null) + { + return; + } - var methodSymbol = invocationOperation.TargetMethod; + var systemConsoleType = context.Compilation.GetTypeByMetadataName("System.Console"); - var displayString = SymbolDisplay.ToDisplayString( - methodSymbol, - SymbolDisplayFormat.CSharpShortErrorMessageFormat - .WithParameterOptions(SymbolDisplayParameterOptions.None) - .WithGenericsOptions(SymbolDisplayGenericsOptions.None)); + if (!Equals(invocationOperation.TargetMethod.ContainingType, systemConsoleType)) + { + return; + } - context.ReportDiagnostic( - Diagnostic.Create( - _diagnosticDescriptor, - invocationOperation.Syntax.GetLocation(), - displayString)); - }, OperationKind.Invocation); + 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); } } } \ No newline at end of file diff --git a/src/Spectre.Console.Analyzer/Constants.cs b/src/Spectre.Console.Analyzer/Constants.cs index 6c14084..63b079c 100644 --- a/src/Spectre.Console.Analyzer/Constants.cs +++ b/src/Spectre.Console.Analyzer/Constants.cs @@ -4,5 +4,12 @@ namespace Spectre.Console.Analyzer { internal const string StaticInstance = "AnsiConsole"; internal const string SpectreConsole = "Spectre.Console"; + + internal static readonly string[] LiveRenderables = + { + "Spectre.Console.LiveDisplay", + "Spectre.Console.Progress", + "Spectre.Console.Status", + }; } } \ No newline at end of file diff --git a/src/Spectre.Console.Analyzer/Descriptors.cs b/src/Spectre.Console.Analyzer/Descriptors.cs index 3d4d242..8eb76fc 100644 --- a/src/Spectre.Console.Analyzer/Descriptors.cs +++ b/src/Spectre.Console.Analyzer/Descriptors.cs @@ -45,5 +45,27 @@ namespace Spectre.Console.Analyzer Usage, Info, "Favor the use of the instance of AnsiConsole over the static helper."); + + /// + /// Gets definitions of diagnostics Spectre1020. + /// + public static DiagnosticDescriptor S1020_AvoidConcurrentCallsToMultipleLiveRenderables { get; } = + Rule( + "Spectre1020", + "Avoid calling other live renderables while a current renderable is running.", + Usage, + Warning, + "Avoid calling other live renderables while a current renderable is running."); + + /// + /// Gets definitions of diagnostics Spectre1020. + /// + public static DiagnosticDescriptor S1021_AvoidPromptCallsDuringLiveRenderables { get; } = + Rule( + "Spectre1021", + "Avoid prompting for input while a current renderable is running.", + Usage, + Warning, + "Avoid prompting for input while a current renderable is running."); } } \ No newline at end of file diff --git a/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/NoConcurrentLiveRenderablesTests.cs b/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/NoConcurrentLiveRenderablesTests.cs new file mode 100644 index 0000000..5a9bd61 --- /dev/null +++ b/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/NoConcurrentLiveRenderablesTests.cs @@ -0,0 +1,86 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using Spectre.Console.Analyzer; +using Xunit; +using AnalyzerVerify = + Spectre.Console.Tests.CodeAnalyzers.SpectreAnalyzerVerifier< + Spectre.Console.Analyzer.NoConcurrentLiveRenderablesAnalyzer>; + +namespace Spectre.Console.Tests.CodeAnalyzers.Analyzers +{ + public class NoCurrentLiveRenderablesTests + { + private static readonly DiagnosticResult _expectedDiagnostics = new( + Descriptors.S1020_AvoidConcurrentCallsToMultipleLiveRenderables.Id, + DiagnosticSeverity.Warning); + + [Fact] + public async void Status_call_within_live_call_warns() + { + const string Source = @" +using Spectre.Console; + +class TestClass +{ + void Go() + { + AnsiConsole.Live(new Table()).Start(ctx => + { + AnsiConsole.Status().Start(""go"", innerCtx => {}); + }); + } +}"; + + await AnalyzerVerify + .VerifyAnalyzerAsync(Source, _expectedDiagnostics.WithLocation(10, 13)) + .ConfigureAwait(false); + } + + [Fact] + public async void Status_call_within_live_call_warns_with_instance() + { + const string Source = @" +using Spectre.Console; + +class Child +{ + public readonly IAnsiConsole _console = AnsiConsole.Console; + + public void Go() + { + _console.Status().Start(""starting"", context => + { + _console.Progress().Start(progressContext => { }); + }); + } +}"; + + await AnalyzerVerify + .VerifyAnalyzerAsync(Source, _expectedDiagnostics.WithLocation(12, 13)) + .ConfigureAwait(false); + } + + [Fact] + public async void Calling_start_on_non_live_renderable_has_no_warning() + { + const string Source = @" +using Spectre.Console; + +class Program +{ + static void Main() + { + Start(); + } + + static void Start() => AnsiConsole.WriteLine(""Starting...""); +}"; + + await AnalyzerVerify + .VerifyAnalyzerAsync(Source) + .ConfigureAwait(false); + } + } + + +} \ No newline at end of file diff --git a/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/NoPromptsDuringLiveRenderablesTests.cs b/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/NoPromptsDuringLiveRenderablesTests.cs new file mode 100644 index 0000000..7e040ea --- /dev/null +++ b/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/NoPromptsDuringLiveRenderablesTests.cs @@ -0,0 +1,108 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using Spectre.Console.Analyzer; +using Xunit; +using AnalyzerVerify = + Spectre.Console.Tests.CodeAnalyzers.SpectreAnalyzerVerifier< + Spectre.Console.Analyzer.NoPromptsDuringLiveRenderablesAnalyzer>; + +namespace Spectre.Console.Tests.CodeAnalyzers.Analyzers +{ + public class NoPromptsDuringLiveRenderablesTests + { + private static readonly DiagnosticResult _expectedDiagnostics = new( + Descriptors.S1021_AvoidPromptCallsDuringLiveRenderables.Id, + DiagnosticSeverity.Warning); + + [Fact] + public async Task Prompt_out_of_progress_does_not_warn() + { + const string Source = @" +using Spectre.Console; + +class TestClass +{ + void Go() + { + var s = AnsiConsole.Ask(""How are you?""); + } +}"; + + await AnalyzerVerify + .VerifyAnalyzerAsync(Source) + .ConfigureAwait(false); + } + + [Fact] + public async Task Instance_variables_warn() + { + const string Source = @" +using Spectre.Console; + +class TestClass +{ + public IAnsiConsole _console = AnsiConsole.Console; + + public void Go() + { + _console.Status().Start(""starting"", context => + { + var result = _console.Confirm(""we ok?""); + }); + } +}"; + + await AnalyzerVerify + .VerifyAnalyzerAsync(Source, _expectedDiagnostics.WithLocation(12, 26)) + .ConfigureAwait(false); + } + + [Fact] + public async Task Prompt_in_progress_warns() + { + const string Source = @" +using Spectre.Console; + +class TestClass +{ + void Go() + { + AnsiConsole.Progress().Start(_ => + { + AnsiConsole.Ask(""How are you?""); + }); + } +}"; + + await AnalyzerVerify + .VerifyAnalyzerAsync(Source, _expectedDiagnostics.WithLocation(10, 13)) + .ConfigureAwait(false); + } + + [Fact] + public async Task Can_call_other_methods_from_within_renderables() + { + const string Source = @" +using Spectre.Console; + +class Program +{ + static void Main() + { + AnsiConsole.Status().Start(""here we go"", context => + { + var result = Confirm(); + + }); + } + + static string Confirm() => string.Empty; +}"; + + await AnalyzerVerify + .VerifyAnalyzerAsync(Source) + .ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/UseInstanceAnsiConsoleTests.cs b/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/UseInstanceAnsiConsoleTests.cs index 5c57e7b..a54600f 100644 --- a/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/UseInstanceAnsiConsoleTests.cs +++ b/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/UseInstanceAnsiConsoleTests.cs @@ -14,6 +14,46 @@ namespace Spectre.Console.Tests.CodeAnalyzers.Analyzers Descriptors.S1010_FavorInstanceAnsiConsoleOverStatic.Id, DiagnosticSeverity.Info); + [Fact] + public async void Instance_console_has_no_warnings() + { + const string Source = @" +using Spectre.Console; + +class TestClass +{ + IAnsiConsole _ansiConsole = AnsiConsole.Console; + + void TestMethod() + { + _ansiConsole.Write(""this is fine""); + } +}"; + + await AnalyzerVerify + .VerifyAnalyzerAsync(Source) + .ConfigureAwait(false); + } + + [Fact] + public async void Static_console_with_no_instance_variables_has_no_warnings() + { + const string Source = @" +using Spectre.Console; + +class TestClass +{ + void TestMethod() + { + AnsiConsole.Write(""this is fine""); + } +}"; + + await AnalyzerVerify + .VerifyAnalyzerAsync(Source) + .ConfigureAwait(false); + } + [Fact] public async void Console_Write_Has_Warning() { diff --git a/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/UseSpectreInsteadOfSystemConsoleAnalyzerTests.cs b/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/UseSpectreInsteadOfSystemConsoleAnalyzerTests.cs index f63ee88..0e3ea5c 100644 --- a/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/UseSpectreInsteadOfSystemConsoleAnalyzerTests.cs +++ b/src/Spectre.Console.Tests/CodeAnalyzer/Analyzers/UseSpectreInsteadOfSystemConsoleAnalyzerTests.cs @@ -14,6 +14,24 @@ namespace Spectre.Console.Tests.CodeAnalyzers.Analyzers Descriptors.S1000_UseAnsiConsoleOverSystemConsole.Id, DiagnosticSeverity.Warning); + [Fact] + public async void Non_configured_SystemConsole_methods_report_no_warnings() + { + const string Source = @" +using System; + +class TestClass { + void TestMethod() + { + var s = Console.ReadLine(); + } +}"; + + await AnalyzerVerify + .VerifyAnalyzerAsync(Source) + .ConfigureAwait(false); + } + [Fact] public async void Console_Write_Has_Warning() { diff --git a/src/Spectre.Console.Tests/CodeAnalyzer/CodeFixProviderDiscovery.cs b/src/Spectre.Console.Tests/CodeAnalyzer/CodeFixProviderDiscovery.cs index daecc51..d115699 100644 --- a/src/Spectre.Console.Tests/CodeAnalyzer/CodeFixProviderDiscovery.cs +++ b/src/Spectre.Console.Tests/CodeAnalyzer/CodeFixProviderDiscovery.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.VisualStudio.Composition; -using Spectre.Console.Analyzer; using Spectre.Console.Analyzer.FixProviders; namespace Spectre.Console.Tests.CodeAnalyzers diff --git a/src/Spectre.Console.Tests/CodeAnalyzer/Fixes/UseInstanceOfStaticAnsiConsoleTests.cs b/src/Spectre.Console.Tests/CodeAnalyzer/Fixes/UseInstanceOfStaticAnsiConsoleTests.cs index 74a13e1..c3fec8f 100644 --- a/src/Spectre.Console.Tests/CodeAnalyzer/Fixes/UseInstanceOfStaticAnsiConsoleTests.cs +++ b/src/Spectre.Console.Tests/CodeAnalyzer/Fixes/UseInstanceOfStaticAnsiConsoleTests.cs @@ -16,7 +16,7 @@ namespace Spectre.Console.Tests.CodeAnalyzers.Fixes DiagnosticSeverity.Info); [Fact] - public async Task SystemConsole_replaced_with_AnsiConsole() + public async Task Static_call_replaced_with_field_call() { const string Source = @" using Spectre.Console; @@ -50,5 +50,71 @@ class TestClass .VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(11, 9), FixedSource) .ConfigureAwait(false); } + + [Fact] + public async Task Static_call_replaced_with_parameter_call() + { + const string Source = @" +using Spectre.Console; + +class TestClass +{ + void TestMethod(IAnsiConsole ansiConsole) + { + AnsiConsole.Write(""Hello, World""); + } +}"; + + const string FixedSource = @" +using Spectre.Console; + +class TestClass +{ + void TestMethod(IAnsiConsole ansiConsole) + { + ansiConsole.Write(""Hello, World""); + } +}"; + + await AnalyzerVerify + .VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(8, 9), FixedSource) + .ConfigureAwait(false); + } + + [Fact] + public async Task Static_call_replaced_with_static_field_if_valid() + { + const string Source = @" +using Spectre.Console; + +class TestClass +{ + static IAnsiConsole staticConsole; + IAnsiConsole instanceConsole; + + static void TestMethod() + { + AnsiConsole.Write(""Hello, World""); + } +}"; + + const string FixedSource = @" +using Spectre.Console; + +class TestClass +{ + static IAnsiConsole staticConsole; + IAnsiConsole instanceConsole; + + static void TestMethod() + { + staticConsole.Write(""Hello, World""); + } +}"; + + await AnalyzerVerify + .VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(11, 9), FixedSource) + .ConfigureAwait(false); + } } } \ No newline at end of file diff --git a/src/Spectre.Console.Tests/CodeAnalyzer/SpectreAnalyzerVerifier.cs b/src/Spectre.Console.Tests/CodeAnalyzer/SpectreAnalyzerVerifier.cs index 2f00a09..cbf696c 100644 --- a/src/Spectre.Console.Tests/CodeAnalyzer/SpectreAnalyzerVerifier.cs +++ b/src/Spectre.Console.Tests/CodeAnalyzer/SpectreAnalyzerVerifier.cs @@ -43,8 +43,6 @@ namespace Spectre.Console.Tests.CodeAnalyzers { TestCode = source, CompilerDiagnostics = CompilerDiagnostics.All, - ReferenceAssemblies = CodeAnalyzerHelper.CurrentSpectre, - TestBehaviors = TestBehaviors.SkipGeneratedCodeCheck, }; test.ExpectedDiagnostics.AddRange(expected);