mirror of
				https://github.com/Tyrrrz/CliFx.git
				synced 2025-10-25 15:19:17 +00:00 
			
		
		
		
	Add CliFx.Analyzers (#50)
This commit is contained in:
		
							
								
								
									
										43
									
								
								CliFx.Analyzers.Tests/AnalyzerTestCase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								CliFx.Analyzers.Tests/AnalyzerTestCase.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Microsoft.CodeAnalysis; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class AnalyzerTestCase | ||||
|     { | ||||
|         public string Name { get; } | ||||
|  | ||||
|         public IReadOnlyList<DiagnosticDescriptor> TestedDiagnostics { get; } | ||||
|  | ||||
|         public IReadOnlyList<string> SourceCodes { get; } | ||||
|  | ||||
|         public AnalyzerTestCase( | ||||
|             string name, | ||||
|             IReadOnlyList<DiagnosticDescriptor> testedDiagnostics, | ||||
|             IReadOnlyList<string> sourceCodes) | ||||
|         { | ||||
|             Name = name; | ||||
|             TestedDiagnostics = testedDiagnostics; | ||||
|             SourceCodes = sourceCodes; | ||||
|         } | ||||
|  | ||||
|         public AnalyzerTestCase( | ||||
|             string name, | ||||
|             IReadOnlyList<DiagnosticDescriptor> testedDiagnostics, | ||||
|             string sourceCode) | ||||
|             : this(name, testedDiagnostics, new[] {sourceCode}) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         public AnalyzerTestCase( | ||||
|             string name, | ||||
|             DiagnosticDescriptor testedDiagnostic, | ||||
|             string sourceCode) | ||||
|             : this(name, new[] {testedDiagnostic}, sourceCode) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         public override string ToString() => $"{Name} [{string.Join(", ", TestedDiagnostics.Select(d => d.Id))}]"; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										29
									
								
								CliFx.Analyzers.Tests/CliFx.Analyzers.Tests.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								CliFx.Analyzers.Tests/CliFx.Analyzers.Tests.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <Import Project="../CliFx.props" /> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||
|     <IsPackable>false</IsPackable> | ||||
|     <IsTestProject>true</IsTestProject> | ||||
|     <CollectCoverage>true</CollectCoverage> | ||||
|     <CoverletOutputFormat>opencover</CoverletOutputFormat> | ||||
|     <CoverletOutput>bin/$(Configuration)/Coverage.xml</CoverletOutput> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Gu.Roslyn.Asserts" Version="3.3.0" /> | ||||
|     <PackageReference Include="GitHubActionsTestLogger" Version="1.0.0" /> | ||||
|     <PackageReference Include="FluentAssertions" Version="5.10.2" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" /> | ||||
|     <PackageReference Include="xunit" Version="2.4.0" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" /> | ||||
|     <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.4.0" /> | ||||
|     <PackageReference Include="coverlet.msbuild" Version="2.8.0" PrivateAssets="all" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\CliFx.Analyzers\CliFx.Analyzers.csproj" /> | ||||
|     <ProjectReference Include="..\CliFx\CliFx.csproj" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
							
								
								
									
										489
									
								
								CliFx.Analyzers.Tests/CommandSchemaAnalyzerTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										489
									
								
								CliFx.Analyzers.Tests/CommandSchemaAnalyzerTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,489 @@ | ||||
| using System.Collections.Generic; | ||||
| using CliFx.Analyzers.Tests.Internal; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class CommandSchemaAnalyzerTests | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new CommandSchemaAnalyzer(); | ||||
|  | ||||
|         public static IEnumerable<object[]> GetValidCases() | ||||
|         { | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Non-command type", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| public class Foo | ||||
| { | ||||
|     public int Bar { get; set; } = 5; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Command implements interface and has attribute", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Command doesn't have an attribute but is an abstract type", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| public abstract class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Parameters with unique order", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandParameter(13)] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandParameter(15)] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Parameters with unique names", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandParameter(13, Name = ""foo"")] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandParameter(15, Name = ""bar"")] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Single non-scalar parameter", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandParameter(1)] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandParameter(2)] | ||||
|     public HashSet<string> ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Non-scalar parameter is last in order", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandParameter(1)] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandParameter(2)] | ||||
|     public IReadOnlyList<string> ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Option with a proper name", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandOption(""foo"")] | ||||
|     public string Param { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Option with a proper name and short name", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandOption(""foo"", 'f')] | ||||
|     public string Param { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Options with unique names", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandOption(""foo"")] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandOption(""bar"")] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Options with unique short names", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandOption('f')] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandOption('x')] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Options with unique environment variable names", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandOption('a', EnvironmentVariableName = ""env_var_a"")] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandOption('b', EnvironmentVariableName = ""env_var_b"")] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         public static IEnumerable<object[]> GetInvalidCases() | ||||
|         { | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Command is missing the attribute", | ||||
|                     DiagnosticDescriptors.CliFx0002, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Command doesn't implement the interface", | ||||
|                     DiagnosticDescriptors.CliFx0001, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Parameters with duplicate order", | ||||
|                     DiagnosticDescriptors.CliFx0021, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandParameter(13)] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandParameter(13)] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Parameters with duplicate names", | ||||
|                     DiagnosticDescriptors.CliFx0022, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandParameter(13, Name = ""foo"")] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandParameter(15, Name = ""foo"")] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Multiple non-scalar parameters", | ||||
|                     DiagnosticDescriptors.CliFx0023, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandParameter(1)] | ||||
|     public IReadOnlyList<string> ParamA { get; set; } | ||||
|      | ||||
|     [CommandParameter(2)] | ||||
|     public HashSet<string> ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Non-last non-scalar parameter", | ||||
|                     DiagnosticDescriptors.CliFx0024, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandParameter(1)] | ||||
|     public IReadOnlyList<string> ParamA { get; set; } | ||||
|      | ||||
|     [CommandParameter(2)] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Option with an empty name", | ||||
|                     DiagnosticDescriptors.CliFx0041, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandOption("""")] | ||||
|     public string Param { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Option with a name which is too short", | ||||
|                     DiagnosticDescriptors.CliFx0042, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandOption(""a"")] | ||||
|     public string Param { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Options with duplicate names", | ||||
|                     DiagnosticDescriptors.CliFx0043, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandOption(""foo"")] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandOption(""foo"")] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Options with duplicate short names", | ||||
|                     DiagnosticDescriptors.CliFx0044, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandOption('f')] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandOption('f')] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Options with duplicate environment variable names", | ||||
|                     DiagnosticDescriptors.CliFx0045, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     [CommandOption('a', EnvironmentVariableName = ""env_var"")] | ||||
|     public string ParamA { get; set; } | ||||
|      | ||||
|     [CommandOption('b', EnvironmentVariableName = ""env_var"")] | ||||
|     public string ParamB { get; set; } | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [MemberData(nameof(GetValidCases))] | ||||
|         public void Valid(AnalyzerTestCase testCase) => | ||||
|             Analyzer.Should().NotProduceDiagnostics(testCase); | ||||
|  | ||||
|         [Theory] | ||||
|         [MemberData(nameof(GetInvalidCases))] | ||||
|         public void Invalid(AnalyzerTestCase testCase) => | ||||
|             Analyzer.Should().ProduceDiagnostics(testCase); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										144
									
								
								CliFx.Analyzers.Tests/ConsoleUsageAnalyzerTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								CliFx.Analyzers.Tests/ConsoleUsageAnalyzerTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| using System.Collections.Generic; | ||||
| using CliFx.Analyzers.Tests.Internal; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class ConsoleUsageAnalyzerTests | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new ConsoleUsageAnalyzer(); | ||||
|  | ||||
|         public static IEnumerable<object[]> GetValidCases() | ||||
|         { | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Using console abstraction", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) | ||||
|     { | ||||
|         console.Output.WriteLine(""Hello world""); | ||||
|         return default; | ||||
|     } | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Console abstraction is not available in scope", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     public void SomeOtherMethod() => Console.WriteLine(""Test""); | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         public static IEnumerable<object[]> GetInvalidCases() | ||||
|         { | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Not using available console abstraction in the ExecuteAsync method", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) | ||||
|     { | ||||
|         Console.WriteLine(""Hello world""); | ||||
|         return default; | ||||
|     } | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Not using available console abstraction in the ExecuteAsync method when writing stderr", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) | ||||
|     { | ||||
|         Console.Error.WriteLine(""Hello world""); | ||||
|         return default; | ||||
|     } | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Not using available console abstraction while referencing System.Console by full name", | ||||
|                     Analyzer.SupportedDiagnostics, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) | ||||
|     { | ||||
|         System.Console.Error.WriteLine(""Hello world""); | ||||
|         return default; | ||||
|     } | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|  | ||||
|             yield return new object[] | ||||
|             { | ||||
|                 new AnalyzerTestCase( | ||||
|                     "Not using available console abstraction in another method", | ||||
|                     DiagnosticDescriptors.CliFx0100, | ||||
|  | ||||
|                     // language=cs | ||||
|                     @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     public void SomeOtherMethod(IConsole console) => Console.WriteLine(""Test""); | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }" | ||||
|                 ) | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [MemberData(nameof(GetValidCases))] | ||||
|         public void Valid(AnalyzerTestCase testCase) => | ||||
|             Analyzer.Should().NotProduceDiagnostics(testCase); | ||||
|  | ||||
|         [Theory] | ||||
|         [MemberData(nameof(GetInvalidCases))] | ||||
|         public void Invalid(AnalyzerTestCase testCase) => | ||||
|             Analyzer.Should().ProduceDiagnostics(testCase); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										107
									
								
								CliFx.Analyzers.Tests/Internal/AnalyzerAssertions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								CliFx.Analyzers.Tests/Internal/AnalyzerAssertions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using FluentAssertions.Execution; | ||||
| using FluentAssertions.Primitives; | ||||
| using Gu.Roslyn.Asserts; | ||||
| using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests.Internal | ||||
| { | ||||
|     internal partial class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer, AnalyzerAssertions> | ||||
|     { | ||||
|         protected override string Identifier { get; } = "analyzer"; | ||||
|  | ||||
|         public AnalyzerAssertions(DiagnosticAnalyzer analyzer) | ||||
|             : base(analyzer) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         public void ProduceDiagnostics( | ||||
|             IReadOnlyList<DiagnosticDescriptor> diagnostics, | ||||
|             IReadOnlyList<string> sourceCodes) | ||||
|         { | ||||
|             var producedDiagnostics = GetProducedDiagnostics(Subject, sourceCodes); | ||||
|  | ||||
|             var expectedIds = diagnostics.Select(d => d.Id).Distinct().OrderBy(d => d).ToArray(); | ||||
|             var producedIds = producedDiagnostics.Select(d => d.Id).Distinct().OrderBy(d => d).ToArray(); | ||||
|  | ||||
|             var result = expectedIds.Intersect(producedIds).Count() == expectedIds.Length; | ||||
|  | ||||
|             Execute.Assertion.ForCondition(result).FailWith($@" | ||||
| Expected and produced diagnostics do not match. | ||||
|  | ||||
| Expected: {string.Join(", ", expectedIds)} | ||||
| Produced: {(producedIds.Any() ? string.Join(", ", producedIds) : "<none>")} | ||||
| ".Trim()); | ||||
|         } | ||||
|  | ||||
|         public void ProduceDiagnostics(AnalyzerTestCase testCase) => | ||||
|             ProduceDiagnostics(testCase.TestedDiagnostics, testCase.SourceCodes); | ||||
|  | ||||
|         public void NotProduceDiagnostics( | ||||
|             IReadOnlyList<DiagnosticDescriptor> diagnostics, | ||||
|             IReadOnlyList<string> sourceCodes) | ||||
|         { | ||||
|             var producedDiagnostics = GetProducedDiagnostics(Subject, sourceCodes); | ||||
|  | ||||
|             var expectedIds = diagnostics.Select(d => d.Id).Distinct().OrderBy(d => d).ToArray(); | ||||
|             var producedIds = producedDiagnostics.Select(d => d.Id).Distinct().OrderBy(d => d).ToArray(); | ||||
|  | ||||
|             var result = !expectedIds.Intersect(producedIds).Any(); | ||||
|  | ||||
|             Execute.Assertion.ForCondition(result).FailWith($@" | ||||
| Expected no produced diagnostics. | ||||
|  | ||||
| Produced: {string.Join(", ", producedIds)} | ||||
| ".Trim()); | ||||
|         } | ||||
|  | ||||
|         public void NotProduceDiagnostics(AnalyzerTestCase testCase) => | ||||
|             NotProduceDiagnostics(testCase.TestedDiagnostics, testCase.SourceCodes); | ||||
|     } | ||||
|  | ||||
|     internal partial class AnalyzerAssertions | ||||
|     { | ||||
|         private static IReadOnlyList<MetadataReference> DefaultMetadataReferences { get; } = | ||||
|             MetadataReferences.Transitive(typeof(CliApplication).Assembly).ToArray(); | ||||
|  | ||||
|         private static string WrapCodeWithUsingDirectives(string code) | ||||
|         { | ||||
|             var usingDirectives = new[] | ||||
|             { | ||||
|                 "using System;", | ||||
|                 "using System.Collections.Generic;", | ||||
|                 "using System.Threading.Tasks;", | ||||
|                 "using CliFx;", | ||||
|                 "using CliFx.Attributes;", | ||||
|                 "using CliFx.Exceptions;", | ||||
|                 "using CliFx.Utilities;" | ||||
|             }; | ||||
|  | ||||
|             return | ||||
|                 string.Join(Environment.NewLine, usingDirectives) + | ||||
|                 Environment.NewLine + | ||||
|                 code; | ||||
|         } | ||||
|  | ||||
|         private static IReadOnlyList<Diagnostic> GetProducedDiagnostics( | ||||
|             DiagnosticAnalyzer analyzer, | ||||
|             IReadOnlyList<string> sourceCodes) | ||||
|         { | ||||
|             var compilationOptions = new CSharpCompilationOptions(OutputKind.ConsoleApplication); | ||||
|             var wrappedSourceCodes = sourceCodes.Select(WrapCodeWithUsingDirectives).ToArray(); | ||||
|  | ||||
|             return Analyze.GetDiagnostics(analyzer, wrappedSourceCodes, compilationOptions, DefaultMetadataReferences) | ||||
|                 .SelectMany(d => d) | ||||
|                 .ToArray(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal static class AnalyzerAssertionsExtensions | ||||
|     { | ||||
|         public static AnalyzerAssertions Should(this DiagnosticAnalyzer analyzer) => new AnalyzerAssertions(analyzer); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user