mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 17:15:28 +08:00 
			
		
		
		
	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
					Phil Scott
				
			
				
					committed by
					
						 Patrik Svensson
						Patrik Svensson
					
				
			
			
				
	
			
			
			 Patrik Svensson
						Patrik Svensson
					
				
			
						parent
						
							d015a4966f
						
					
				
				
					commit
					4f293d887d
				
			
							
								
								
									
										16
									
								
								examples/Console/AnalyzerTester/AnalyzerTester.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								examples/Console/AnalyzerTester/AnalyzerTester.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <OutputType>Exe</OutputType> | ||||||
|  |     <TargetFramework>net5.0</TargetFramework> | ||||||
|  |   </PropertyGroup> | ||||||
|  |  | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\..\..\src\Spectre.Console\Spectre.Console.csproj" /> | ||||||
|  |     <ProjectReference Include="..\..\..\src\Spectre.Console.Analyzer\Spectre.Console.Analyzer.csproj" | ||||||
|  |                       PrivateAssets="all" | ||||||
|  |                       ReferenceOutputAssembly="false" | ||||||
|  |                       OutputItemType="Analyzer" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
							
								
								
									
										32
									
								
								examples/Console/AnalyzerTester/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								examples/Console/AnalyzerTester/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | namespace AnalyzerTester | ||||||
|  | { | ||||||
|  |     class Program | ||||||
|  |     { | ||||||
|  |         static void Main(string[] args) | ||||||
|  |         { | ||||||
|  |             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!"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								src/Spectre.Console.Analyzer/Analyzers/BaseAnalyzer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/Spectre.Console.Analyzer/Analyzers/BaseAnalyzer.cs
									
									
									
									
									
										Normal 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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))); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								src/Spectre.Console.Analyzer/Constants.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/Spectre.Console.Analyzer/Constants.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | namespace Spectre.Console.Analyzer | ||||||
|  | { | ||||||
|  |     internal static class Constants | ||||||
|  |     { | ||||||
|  |         internal const string StaticInstance = "AnsiConsole"; | ||||||
|  |         internal const string SpectreConsole = "Spectre.Console"; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								src/Spectre.Console.Analyzer/Descriptors.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/Spectre.Console.Analyzer/Descriptors.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | using System.Collections.Concurrent; | ||||||
|  | using Microsoft.CodeAnalysis; | ||||||
|  | using static Microsoft.CodeAnalysis.DiagnosticSeverity; | ||||||
|  | using static Spectre.Console.Analyzer.Descriptors.Category; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Analyzer | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Code analysis descriptors. | ||||||
|  |     /// </summary> | ||||||
|  |     public static class Descriptors | ||||||
|  |     { | ||||||
|  |         internal enum Category | ||||||
|  |         { | ||||||
|  |             Usage, // 1xxx | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static readonly ConcurrentDictionary<Category, string> _categoryMapping = new(); | ||||||
|  |  | ||||||
|  |         private static DiagnosticDescriptor Rule(string id, string title, Category category, DiagnosticSeverity defaultSeverity, string messageFormat, string? description = null) | ||||||
|  |         { | ||||||
|  |             var helpLink = $"https://spectreconsole.net/spectre.console.analyzers/rules/{id}"; | ||||||
|  |             const bool IsEnabledByDefault = true; | ||||||
|  |             return new DiagnosticDescriptor(id, title, messageFormat, _categoryMapping.GetOrAdd(category, c => c.ToString()), defaultSeverity, IsEnabledByDefault, description, helpLink); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets definitions of diagnostics Spectre1000. | ||||||
|  |         /// </summary> | ||||||
|  |         public static DiagnosticDescriptor S1000_UseAnsiConsoleOverSystemConsole { get; } = | ||||||
|  |             Rule( | ||||||
|  |                 "Spectre1000", | ||||||
|  |                 "Use AnsiConsole instead of System.Console", | ||||||
|  |                 Usage, | ||||||
|  |                 Warning, | ||||||
|  |                 "Use AnsiConsole instead of System.Console"); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets definitions of diagnostics Spectre1010. | ||||||
|  |         /// </summary> | ||||||
|  |         public static DiagnosticDescriptor S1010_FavorInstanceAnsiConsoleOverStatic { get; } = | ||||||
|  |             Rule( | ||||||
|  |                 "Spectre1010", | ||||||
|  |                 "Favor the use of the instance of AnsiConsole over the static helper.", | ||||||
|  |                 Usage, | ||||||
|  |                 Info, | ||||||
|  |                 "Favor the use of the instance of AnsiConsole over the static helper."); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,115 @@ | |||||||
|  | using System.Linq; | ||||||
|  | using System.Threading; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.CodeAnalysis; | ||||||
|  | using Microsoft.CodeAnalysis.CodeActions; | ||||||
|  | using Microsoft.CodeAnalysis.CSharp; | ||||||
|  | using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||||
|  | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Analyzer.CodeActions | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Code action to change calls to System.Console to AnsiConsole. | ||||||
|  |     /// </summary> | ||||||
|  |     public class SwitchToAnsiConsoleAction : CodeAction | ||||||
|  |     { | ||||||
|  |         private readonly Document _document; | ||||||
|  |         private readonly InvocationExpressionSyntax _originalInvocation; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="SwitchToAnsiConsoleAction"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="document">Document to change.</param> | ||||||
|  |         /// <param name="originalInvocation">The method to change.</param> | ||||||
|  |         /// <param name="title">Title of the fix.</param> | ||||||
|  |         public SwitchToAnsiConsoleAction(Document document, InvocationExpressionSyntax originalInvocation, string title) | ||||||
|  |         { | ||||||
|  |             _document = document; | ||||||
|  |             _originalInvocation = originalInvocation; | ||||||
|  |             Title = title; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public override string Title { get; } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public override string EquivalenceKey => Title; | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) | ||||||
|  |         { | ||||||
|  |             var originalCaller = ((MemberAccessExpressionSyntax)_originalInvocation.Expression).Name.ToString(); | ||||||
|  |  | ||||||
|  |             var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |             var root = (CompilationUnitSyntax)await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |             // If there is an ansiConsole passed into the method then we'll use it. | ||||||
|  |             // otherwise we'll check for a field level instance. | ||||||
|  |             // if neither of those exist we'll fall back to the static param. | ||||||
|  |             var ansiConsoleParameterDeclaration = GetAnsiConsoleParameterDeclaration(); | ||||||
|  |             var ansiConsoleFieldIdentifier = GetAnsiConsoleFieldDeclaration(); | ||||||
|  |             var ansiConsoleIdentifier = ansiConsoleParameterDeclaration ?? | ||||||
|  |                                         ansiConsoleFieldIdentifier ?? | ||||||
|  |                                         Constants.StaticInstance; | ||||||
|  |  | ||||||
|  |             // Replace the System.Console call with a call to the identifier above. | ||||||
|  |             var newRoot = root.ReplaceNode( | ||||||
|  |                 _originalInvocation, | ||||||
|  |                 GetImportedSpectreCall(originalCaller, ansiConsoleIdentifier)); | ||||||
|  |  | ||||||
|  |             // If we are calling the static instance and Spectre isn't imported yet we should do so. | ||||||
|  |             if (ansiConsoleIdentifier == Constants.StaticInstance && root.Usings.ToList().All(i => i.Name.ToString() != Constants.SpectreConsole)) | ||||||
|  |             { | ||||||
|  |                 newRoot = newRoot.AddUsings(Syntax.SpectreUsing); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return _document.WithSyntaxRoot(newRoot); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private string? GetAnsiConsoleParameterDeclaration() | ||||||
|  |         { | ||||||
|  |             return _originalInvocation | ||||||
|  |                 .Ancestors().OfType<MethodDeclarationSyntax>() | ||||||
|  |                 .First() | ||||||
|  |                 .ParameterList.Parameters | ||||||
|  |                 .FirstOrDefault(i => i.Type.NormalizeWhitespace().ToString() == "IAnsiConsole") | ||||||
|  |                 ?.Identifier.Text; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private string? GetAnsiConsoleFieldDeclaration() | ||||||
|  |         { | ||||||
|  |             // let's look to see if our call is in a static method. | ||||||
|  |             // if so we'll only want to look for static IAnsiConsoles | ||||||
|  |             // and vice-versa if we aren't. | ||||||
|  |             var isStatic = _originalInvocation | ||||||
|  |                 .Ancestors() | ||||||
|  |                 .OfType<MethodDeclarationSyntax>() | ||||||
|  |                 .First() | ||||||
|  |                 .Modifiers.Any(i => i.Kind() == SyntaxKind.StaticKeyword); | ||||||
|  |  | ||||||
|  |             return _originalInvocation | ||||||
|  |                 .Ancestors().OfType<ClassDeclarationSyntax>() | ||||||
|  |                 .First() | ||||||
|  |                 .Members | ||||||
|  |                 .OfType<FieldDeclarationSyntax>() | ||||||
|  |                 .FirstOrDefault(i => | ||||||
|  |                     i.Declaration.Type.NormalizeWhitespace().ToString() == "IAnsiConsole" && | ||||||
|  |                     (!isStatic ^ i.Modifiers.Any(modifier => modifier.Kind() == SyntaxKind.StaticKeyword))) | ||||||
|  |                 ?.Declaration.Variables.First().Identifier.Text; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private ExpressionSyntax GetImportedSpectreCall(string originalCaller, string ansiConsoleIdentifier) | ||||||
|  |         { | ||||||
|  |             return ExpressionStatement( | ||||||
|  |                     InvocationExpression( | ||||||
|  |                             MemberAccessExpression( | ||||||
|  |                                 SyntaxKind.SimpleMemberAccessExpression, | ||||||
|  |                                 IdentifierName(ansiConsoleIdentifier), | ||||||
|  |                                 IdentifierName(originalCaller))) | ||||||
|  |                         .WithArgumentList( | ||||||
|  |                         _originalInvocation.ArgumentList)) | ||||||
|  |             .Expression; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | using System.Collections.Immutable; | ||||||
|  | using System.Composition; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.CodeAnalysis; | ||||||
|  | using Microsoft.CodeAnalysis.CodeFixes; | ||||||
|  | using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||||
|  | using Spectre.Console.Analyzer.CodeActions; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Analyzer.FixProviders | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Fix provider to change System.Console calls to AnsiConsole calls. | ||||||
|  |     /// </summary> | ||||||
|  |     [ExportCodeFixProvider(LanguageNames.CSharp)] | ||||||
|  |     [Shared] | ||||||
|  |     public class StaticAnsiConsoleToInstanceFix : CodeFixProvider | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create( | ||||||
|  |             Descriptors.S1010_FavorInstanceAnsiConsoleOverStatic.Id); | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||||||
|  |         { | ||||||
|  |             var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||||||
|  |             var methodDeclaration = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>(); | ||||||
|  |             context.RegisterCodeFix( | ||||||
|  |                 new SwitchToAnsiConsoleAction(context.Document, methodDeclaration, "Convert static AnsiConsole calls to local instance."), | ||||||
|  |                 context.Diagnostics); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | using System.Collections.Immutable; | ||||||
|  | using System.Composition; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.CodeAnalysis; | ||||||
|  | using Microsoft.CodeAnalysis.CodeFixes; | ||||||
|  | using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||||
|  | using Spectre.Console.Analyzer.CodeActions; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Analyzer.FixProviders | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Fix provider to change System.Console calls to AnsiConsole calls. | ||||||
|  |     /// </summary> | ||||||
|  |     [ExportCodeFixProvider(LanguageNames.CSharp)] | ||||||
|  |     [Shared] | ||||||
|  |     public class SystemConsoleToAnsiConsoleFix : CodeFixProvider | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create( | ||||||
|  |             Descriptors.S1000_UseAnsiConsoleOverSystemConsole.Id); | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||||||
|  |         { | ||||||
|  |             var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||||||
|  |             var methodDeclaration = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>(); | ||||||
|  |             context.RegisterCodeFix( | ||||||
|  |                 new SwitchToAnsiConsoleAction(context.Document, methodDeclaration, "Convert static call to AnsiConsole to Spectre.Console.AnsiConsole"), | ||||||
|  |                 context.Diagnostics); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								src/Spectre.Console.Analyzer/Fixes/Syntax.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Spectre.Console.Analyzer/Fixes/Syntax.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||||
|  | using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Analyzer | ||||||
|  | { | ||||||
|  |     internal static class Syntax | ||||||
|  |     { | ||||||
|  |         public static readonly UsingDirectiveSyntax SpectreUsing = UsingDirective(QualifiedName(IdentifierName("Spectre"), IdentifierName("Console"))); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								src/Spectre.Console.Analyzer/Spectre.Console.Analyzer.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/Spectre.Console.Analyzer/Spectre.Console.Analyzer.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  |     <PropertyGroup> | ||||||
|  |         <TargetFramework>netstandard2.0</TargetFramework> | ||||||
|  |         <IsPackable>true</IsPackable> | ||||||
|  |         <Nullable>enable</Nullable> | ||||||
|  |     </PropertyGroup> | ||||||
|  |     <ItemGroup> | ||||||
|  |         <AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" /> | ||||||
|  |         <None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" /> | ||||||
|  |     </ItemGroup> | ||||||
|  |      | ||||||
|  |     <ItemGroup> | ||||||
|  |         <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="2.9.8" PrivateAssets="all" /> | ||||||
|  |         <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.6.1" /> | ||||||
|  |         <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.6.1" /> | ||||||
|  |     </ItemGroup> | ||||||
|  |  | ||||||
|  |     <ItemGroup> | ||||||
|  |         <None Update="tools\*.ps1" CopyToOutputDirectory="Always" Pack="true" PackagePath="tools" /> | ||||||
|  |         <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> | ||||||
|  |     </ItemGroup> | ||||||
|  | </Project> | ||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | using Microsoft.CodeAnalysis; | ||||||
|  | using Microsoft.CodeAnalysis.Testing; | ||||||
|  | using Spectre.Console.Analyzer; | ||||||
|  | using Xunit; | ||||||
|  | using AnalyzerVerify = | ||||||
|  |     Spectre.Console.Tests.CodeAnalyzers.SpectreAnalyzerVerifier< | ||||||
|  |         Spectre.Console.Analyzer.FavorInstanceAnsiConsoleOverStaticAnalyzer>; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Tests.CodeAnalyzers.Analyzers | ||||||
|  | { | ||||||
|  |     public class FavorInstanceAnsiConsoleOverStaticAnalyzerTests | ||||||
|  |     { | ||||||
|  |         private static readonly DiagnosticResult _expectedDiagnostics = new( | ||||||
|  |             Descriptors.S1010_FavorInstanceAnsiConsoleOverStatic.Id, | ||||||
|  |             DiagnosticSeverity.Info); | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public async void Console_Write_Has_Warning() | ||||||
|  |         { | ||||||
|  |             const string Source = @" | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     IAnsiConsole _ansiConsole = AnsiConsole.Console;     | ||||||
|  |  | ||||||
|  |     void TestMethod()  | ||||||
|  |     { | ||||||
|  |         _ansiConsole.Write(""this is fine""); | ||||||
|  |         AnsiConsole.Write(""Hello, World""); | ||||||
|  |     }  | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             await AnalyzerVerify | ||||||
|  |                 .VerifyAnalyzerAsync(Source, _expectedDiagnostics.WithLocation(11, 9)) | ||||||
|  |                 .ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,53 @@ | |||||||
|  | using Microsoft.CodeAnalysis; | ||||||
|  | using Microsoft.CodeAnalysis.Testing; | ||||||
|  | using Spectre.Console.Analyzer; | ||||||
|  | using Xunit; | ||||||
|  | using AnalyzerVerify = | ||||||
|  |     Spectre.Console.Tests.CodeAnalyzers.SpectreAnalyzerVerifier< | ||||||
|  |         Spectre.Console.Analyzer.UseSpectreInsteadOfSystemConsoleAnalyzer>; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Tests.CodeAnalyzers.Analyzers | ||||||
|  | { | ||||||
|  |     public class UseSpectreInsteadOfSystemConsoleAnalyzerTests | ||||||
|  |     { | ||||||
|  |         private static readonly DiagnosticResult _expectedDiagnostics = new( | ||||||
|  |             Descriptors.S1000_UseAnsiConsoleOverSystemConsole.Id, | ||||||
|  |             DiagnosticSeverity.Warning); | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public async void Console_Write_Has_Warning() | ||||||
|  |         { | ||||||
|  |             const string Source = @" | ||||||
|  | using System; | ||||||
|  |  | ||||||
|  | class TestClass { | ||||||
|  |     void TestMethod()  | ||||||
|  |     { | ||||||
|  |         Console.Write(""Hello, World""); | ||||||
|  |     }  | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             await AnalyzerVerify | ||||||
|  |                 .VerifyAnalyzerAsync(Source, _expectedDiagnostics.WithLocation(7, 9)) | ||||||
|  |                 .ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public async void Console_WriteLine_Has_Warning() | ||||||
|  |         { | ||||||
|  |             const string Source = @" | ||||||
|  | using System; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     void TestMethod() { | ||||||
|  |         Console.WriteLine(""Hello, World""); | ||||||
|  |     } | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             await AnalyzerVerify | ||||||
|  |                 .VerifyAnalyzerAsync(Source, _expectedDiagnostics.WithLocation(7, 9)) | ||||||
|  |                 .ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,56 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Collections.Immutable; | ||||||
|  | using System.Linq; | ||||||
|  | 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 | ||||||
|  | { | ||||||
|  |     internal static class CodeFixProviderDiscovery | ||||||
|  |     { | ||||||
|  |         private static readonly Lazy<IExportProviderFactory> _exportProviderFactory; | ||||||
|  |  | ||||||
|  |         static CodeFixProviderDiscovery() | ||||||
|  |         { | ||||||
|  |             _exportProviderFactory = new Lazy<IExportProviderFactory>( | ||||||
|  |                 () => | ||||||
|  |                 { | ||||||
|  |                     var discovery = new AttributedPartDiscovery(Resolver.DefaultInstance, isNonPublicSupported: true); | ||||||
|  |                     var parts = Task.Run(() => discovery.CreatePartsAsync(typeof(SystemConsoleToAnsiConsoleFix).Assembly)).GetAwaiter().GetResult(); | ||||||
|  |                     var catalog = ComposableCatalog.Create(Resolver.DefaultInstance).AddParts(parts); | ||||||
|  |  | ||||||
|  |                     var configuration = CompositionConfiguration.Create(catalog); | ||||||
|  |                     var runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration); | ||||||
|  |                     return runtimeComposition.CreateExportProviderFactory(); | ||||||
|  |                 }, | ||||||
|  |                 LazyThreadSafetyMode.ExecutionAndPublication); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static IEnumerable<CodeFixProvider> GetCodeFixProviders(string language) | ||||||
|  |         { | ||||||
|  |             var exportProvider = _exportProviderFactory.Value.CreateExportProvider(); | ||||||
|  |             var exports = exportProvider.GetExports<CodeFixProvider, LanguageMetadata>(); | ||||||
|  |             return exports.Where(export => export.Metadata.Languages.Contains(language)).Select(export => export.Value); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private class LanguageMetadata | ||||||
|  |         { | ||||||
|  |             public LanguageMetadata(IDictionary<string, object> data) | ||||||
|  |             { | ||||||
|  |                 if (!data.TryGetValue(nameof(ExportCodeFixProviderAttribute.Languages), out var languages)) | ||||||
|  |                 { | ||||||
|  |                     languages = Array.Empty<string>(); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 Languages = ((string[])languages).ToImmutableArray(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             public ImmutableArray<string> Languages { get; } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,54 @@ | |||||||
|  | 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.FavorInstanceAnsiConsoleOverStaticAnalyzer>; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Tests.CodeAnalyzers.Fixes | ||||||
|  | { | ||||||
|  |     public class UseInstanceOfStaticAnsiConsoleTests | ||||||
|  |     { | ||||||
|  |         private static readonly DiagnosticResult _expectedDiagnostic = new( | ||||||
|  |             Descriptors.S1010_FavorInstanceAnsiConsoleOverStatic.Id, | ||||||
|  |             DiagnosticSeverity.Info); | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public async Task SystemConsole_replaced_with_AnsiConsole() | ||||||
|  |         { | ||||||
|  |             const string Source = @" | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     IAnsiConsole _ansiConsole = AnsiConsole.Console;     | ||||||
|  |  | ||||||
|  |     void TestMethod()  | ||||||
|  |     { | ||||||
|  |         _ansiConsole.Write(""this is fine""); | ||||||
|  |         AnsiConsole.Write(""Hello, World""); | ||||||
|  |     }  | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             const string FixedSource = @" | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     IAnsiConsole _ansiConsole = AnsiConsole.Console;     | ||||||
|  |  | ||||||
|  |     void TestMethod()  | ||||||
|  |     { | ||||||
|  |         _ansiConsole.Write(""this is fine""); | ||||||
|  |         _ansiConsole.Write(""Hello, World""); | ||||||
|  |     }  | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             await AnalyzerVerify | ||||||
|  |                 .VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(11, 9), FixedSource) | ||||||
|  |                 .ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,152 @@ | |||||||
|  | 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.UseSpectreInsteadOfSystemConsoleAnalyzer>; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Tests.CodeAnalyzers.Fixes | ||||||
|  | { | ||||||
|  |     public class UseSpectreInsteadOfSystemConsoleFixTests | ||||||
|  |     { | ||||||
|  |         private static readonly DiagnosticResult _expectedDiagnostic = new( | ||||||
|  |             Descriptors.S1000_UseAnsiConsoleOverSystemConsole.Id, | ||||||
|  |             DiagnosticSeverity.Warning); | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public async Task SystemConsole_replaced_with_AnsiConsole() | ||||||
|  |         { | ||||||
|  |             const string Source = @" | ||||||
|  | using System; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     void TestMethod()  | ||||||
|  |     { | ||||||
|  |         Console.WriteLine(""Hello, World""); | ||||||
|  |     } | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             const string FixedSource = @" | ||||||
|  | using System; | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     void TestMethod()  | ||||||
|  |     { | ||||||
|  |         AnsiConsole.WriteLine(""Hello, World""); | ||||||
|  |     } | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             await AnalyzerVerify | ||||||
|  |                 .VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(8, 9), FixedSource) | ||||||
|  |                 .ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public async Task SystemConsole_replaced_with_imported_AnsiConsole() | ||||||
|  |         { | ||||||
|  |             const string Source = @" | ||||||
|  | using System; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     void TestMethod()  | ||||||
|  |     { | ||||||
|  |         Console.WriteLine(""Hello, World""); | ||||||
|  |     } | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             const string FixedSource = @" | ||||||
|  | using System; | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     void TestMethod()  | ||||||
|  |     { | ||||||
|  |         AnsiConsole.WriteLine(""Hello, World""); | ||||||
|  |     } | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             await AnalyzerVerify | ||||||
|  |                 .VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(8, 9), FixedSource) | ||||||
|  |                 .ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public async Task SystemConsole_replaced_with_field_AnsiConsole() | ||||||
|  |         { | ||||||
|  |             const string Source = @" | ||||||
|  | using System; | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     IAnsiConsole _ansiConsole; | ||||||
|  |  | ||||||
|  |     void TestMethod()  | ||||||
|  |     { | ||||||
|  |         Console.WriteLine(""Hello, World""); | ||||||
|  |     } | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             const string FixedSource = @" | ||||||
|  | using System; | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     IAnsiConsole _ansiConsole; | ||||||
|  |  | ||||||
|  |     void TestMethod()  | ||||||
|  |     { | ||||||
|  |         _ansiConsole.WriteLine(""Hello, World""); | ||||||
|  |     } | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             await AnalyzerVerify | ||||||
|  |                 .VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(11, 9), FixedSource) | ||||||
|  |                 .ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public async Task SystemConsole_replaced_with_static_field_AnsiConsole() | ||||||
|  |         { | ||||||
|  |             const string Source = @" | ||||||
|  | using System; | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     static IAnsiConsole _ansiConsole; | ||||||
|  |  | ||||||
|  |     static void TestMethod()  | ||||||
|  |     { | ||||||
|  |         Console.WriteLine(""Hello, World""); | ||||||
|  |     } | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             const string FixedSource = @" | ||||||
|  | using System; | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | class TestClass  | ||||||
|  | { | ||||||
|  |     static IAnsiConsole _ansiConsole; | ||||||
|  |  | ||||||
|  |     static void TestMethod()  | ||||||
|  |     { | ||||||
|  |         _ansiConsole.WriteLine(""Hello, World""); | ||||||
|  |     } | ||||||
|  | }"; | ||||||
|  |  | ||||||
|  |             await AnalyzerVerify | ||||||
|  |                 .VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(11, 9), FixedSource) | ||||||
|  |                 .ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,87 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Collections.Immutable; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.CodeAnalysis.CodeFixes; | ||||||
|  | using Microsoft.CodeAnalysis.CSharp.Testing; | ||||||
|  | using Microsoft.CodeAnalysis.Diagnostics; | ||||||
|  | using Microsoft.CodeAnalysis.Testing; | ||||||
|  | using Microsoft.CodeAnalysis.Testing.Verifiers; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Tests.CodeAnalyzers | ||||||
|  | { | ||||||
|  |     public static class SpectreAnalyzerVerifier<TAnalyzer> | ||||||
|  |         where TAnalyzer : DiagnosticAnalyzer, new() | ||||||
|  |     { | ||||||
|  |         public static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) | ||||||
|  |             => VerifyCodeFixAsync(source, new[] { expected }, fixedSource); | ||||||
|  |  | ||||||
|  |         private static Task VerifyCodeFixAsync(string source, IEnumerable<DiagnosticResult> expected, string fixedSource) | ||||||
|  |         { | ||||||
|  |             // Roslyn fixers always use \r\n for newlines, regardless of OS environment settings, so we normalize | ||||||
|  |             // the source as it typically comes from multi-line strings with varying newlines. | ||||||
|  |             if (Environment.NewLine != "\r\n") | ||||||
|  |             { | ||||||
|  |                 source = source.Replace(Environment.NewLine, "\r\n"); | ||||||
|  |                 fixedSource = fixedSource.Replace(Environment.NewLine, "\r\n"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var test = new Test | ||||||
|  |             { | ||||||
|  |                 TestCode = source, | ||||||
|  |                 FixedCode = fixedSource, | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             test.ExpectedDiagnostics.AddRange(expected); | ||||||
|  |             return test.RunAsync(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) | ||||||
|  |         { | ||||||
|  |             var test = new Test | ||||||
|  |             { | ||||||
|  |                 TestCode = source, | ||||||
|  |                 CompilerDiagnostics = CompilerDiagnostics.All, | ||||||
|  |                 ReferenceAssemblies = CodeAnalyzerHelper.CurrentSpectre, | ||||||
|  |                 TestBehaviors = TestBehaviors.SkipGeneratedCodeCheck, | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             test.ExpectedDiagnostics.AddRange(expected); | ||||||
|  |             return test.RunAsync(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Code fix tests support both analyzer and code fix testing. This test class is derived from the code fix test | ||||||
|  |         // to avoid the need to maintain duplicate copies of the customization work. | ||||||
|  |         private class Test : CSharpCodeFixTest<TAnalyzer, EmptyCodeFixProvider, XUnitVerifier> | ||||||
|  |         { | ||||||
|  |             public Test() | ||||||
|  |             { | ||||||
|  |                 ReferenceAssemblies = CodeAnalyzerHelper.CurrentSpectre; | ||||||
|  |                 TestBehaviors |= TestBehaviors.SkipGeneratedCodeCheck; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             protected override IEnumerable<CodeFixProvider> GetCodeFixProviders() | ||||||
|  |             { | ||||||
|  |                 var analyzer = new TAnalyzer(); | ||||||
|  |                 foreach (var provider in CodeFixProviderDiscovery.GetCodeFixProviders(Language)) | ||||||
|  |                 { | ||||||
|  |                     if (analyzer.SupportedDiagnostics.Any(diagnostic => provider.FixableDiagnosticIds.Contains(diagnostic.Id))) | ||||||
|  |                     { | ||||||
|  |                         yield return provider; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     internal static class CodeAnalyzerHelper | ||||||
|  |     { | ||||||
|  |         internal static ReferenceAssemblies CurrentSpectre { get; } | ||||||
|  |  | ||||||
|  |         static CodeAnalyzerHelper() | ||||||
|  |         { | ||||||
|  |             CurrentSpectre = ReferenceAssemblies.Net.Net50.AddAssemblies(ImmutableArray.Create(typeof(AnsiConsole).Assembly.Location.Replace(".dll", string.Empty))); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -17,6 +17,9 @@ | |||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  |     <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.0" /> | ||||||
|  |     <PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.1.0" /> | ||||||
|  |     <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.6.1" /> | ||||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> |     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> | ||||||
|     <PackageReference Include="Shouldly" Version="4.0.3" /> |     <PackageReference Include="Shouldly" Version="4.0.3" /> | ||||||
|     <PackageReference Include="Spectre.Verify.Extensions" Version="0.3.0" /> |     <PackageReference Include="Spectre.Verify.Extensions" Version="0.3.0" /> | ||||||
| @@ -29,6 +32,7 @@ | |||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\Spectre.Console.Analyzer\Spectre.Console.Analyzer.csproj" /> | ||||||
|     <ProjectReference Include="..\Spectre.Console.Testing\Spectre.Console.Testing.csproj" /> |     <ProjectReference Include="..\Spectre.Console.Testing\Spectre.Console.Testing.csproj" /> | ||||||
|     <ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" /> |     <ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   | |||||||
| @@ -90,6 +90,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "..\examples\Share | |||||||
| EndProject | EndProject | ||||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Live", "..\examples\Console\Live\Live.csproj", "{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Live", "..\examples\Console\Live\Live.csproj", "{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}" | ||||||
| EndProject | EndProject | ||||||
|  | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnalyzerTester", "..\examples\Console\AnalyzerTester\AnalyzerTester.csproj", "{D2B32355-D99F-480B-92BF-9FAABE79ADD4}" | ||||||
|  | EndProject | ||||||
|  | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectre.Console.Analyzer", "Spectre.Console.Analyzer\Spectre.Console.Analyzer.csproj", "{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}" | ||||||
|  | EndProject | ||||||
| Global | Global | ||||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||||
| 		Debug|Any CPU = Debug|Any CPU | 		Debug|Any CPU = Debug|Any CPU | ||||||
| @@ -472,6 +476,30 @@ Global | |||||||
| 		{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|x64.Build.0 = Release|Any CPU | 		{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|x64.Build.0 = Release|Any CPU | ||||||
| 		{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|x86.ActiveCfg = Release|Any CPU | 		{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
| 		{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|x86.Build.0 = Release|Any CPU | 		{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1}.Release|x86.Build.0 = Release|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Debug|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Debug|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Release|x64.ActiveCfg = Release|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Release|x64.Build.0 = Release|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4}.Release|x86.Build.0 = Release|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Debug|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Debug|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Release|x64.ActiveCfg = Release|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Release|x64.Build.0 = Release|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
|  | 		{26006ACD-F19D-4C2A-8864-FE0D6C15B58C}.Release|x86.Build.0 = Release|Any CPU | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
| 	GlobalSection(SolutionProperties) = preSolution | 	GlobalSection(SolutionProperties) = preSolution | ||||||
| 		HideSolutionNode = FALSE | 		HideSolutionNode = FALSE | ||||||
| @@ -508,6 +536,7 @@ Global | |||||||
| 		{A0C772BA-C5F4-451D-AA7A-4045F2FA0201} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | 		{A0C772BA-C5F4-451D-AA7A-4045F2FA0201} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | ||||||
| 		{8428A7DD-29FC-4417-9CA0-B90D34B26AB2} = {A0C772BA-C5F4-451D-AA7A-4045F2FA0201} | 		{8428A7DD-29FC-4417-9CA0-B90D34B26AB2} = {A0C772BA-C5F4-451D-AA7A-4045F2FA0201} | ||||||
| 		{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | 		{E607AA2A-A4A6-48E4-8AAB-B0EB74EACAA1} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | ||||||
|  | 		{D2B32355-D99F-480B-92BF-9FAABE79ADD4} = {A0C772BA-C5F4-451D-AA7A-4045F2FA0201} | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||||
| 		SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} | 		SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user