mirror of
				https://github.com/Tyrrrz/CliFx.git
				synced 2025-10-25 15:19:17 +00:00 
			
		
		
		
	Compare commits
	
		
			30 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 05a70175cc | ||
|  | 33ec2eb3a0 | ||
|  | f6ef6cd4c0 | ||
|  | a9ef693dc1 | ||
|  | 98bbd666dc | ||
|  | 4e7ed830f8 | ||
|  | ef87ff76fc | ||
|  | 2feeb21270 | ||
|  | 9990387cfa | ||
|  | bc1bdca7c6 | ||
|  | 2a992d37df | ||
|  | 15c87aecbb | ||
|  | 10a46451ac | ||
|  | e4c6a4174b | ||
|  | 4c65f7bbee | ||
|  | 5f21de0df5 | ||
|  | 9b01b67d98 | ||
|  | 4508f5e211 | ||
|  | f0cbc46df4 | ||
|  | 6c96e9e173 | ||
|  | 51cca36d2a | ||
|  | 84672c92f6 | ||
|  | b1d01898b6 | ||
|  | 441a47a1a8 | ||
|  | 8abd7219a1 | ||
|  | df73a0bfe8 | ||
|  | 55d12dc721 | ||
|  | a6ee44c1bb | ||
|  | 76816e22f1 | ||
|  | daf25e59d6 | 
							
								
								
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +0,0 @@ | ||||
| github: Tyrrrz | ||||
| patreon: Tyrrrz | ||||
| custom: ['buymeacoffee.com/Tyrrrz', 'tyrrrz.me/donate'] | ||||
							
								
								
									
										24
									
								
								.github/ISSUE_TEMPLATE/bug-report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/ISSUE_TEMPLATE/bug-report.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,30 +1,42 @@ | ||||
| name: "\U0001F41E Bug report" | ||||
| name: 🐞 Bug report | ||||
| description: Report broken functionality. | ||||
| labels: [bug] | ||||
|  | ||||
| body: | ||||
| - type: markdown | ||||
|   attributes: | ||||
|     value: | | ||||
|       - Please check existing issues (both opened and closed) to ensure that this bug hasn't been reported before. | ||||
|       - If you want to ask a question instead of reporting a bug, use [discussions](https://github.com/Tyrrrz/CliFx/discussions/new) instead. | ||||
|       🧐 **Guidelines:** | ||||
|  | ||||
|       - Search through [existing issues](https://github.com/Tyrrrz/CliFx/issues?q=is%3Aissue) first to ensure that this bug has not been reported before. | ||||
|       - Write a descriptive title for your issue. Avoid generic or vague titles such as "Something's not working" or "A couple of problems". | ||||
|       - Keep your issue focused on one single problem. If you have multiple bug reports, please create separate issues for each of them. | ||||
|       - Provide as much context as possible in the details section. Include screenshots, screen recordings, links, references, or anything else you may consider relevant. | ||||
|       - If you want to ask a question instead of reporting a bug, please use [discussions](https://github.com/Tyrrrz/CliFx/discussions/new) instead. | ||||
|  | ||||
| - type: input | ||||
|   attributes: | ||||
|     label: Version | ||||
|     description: "Which version(s) of CliFx does this bug affect?" | ||||
|     description: Which version of CliFx does this bug affect? | ||||
|     placeholder: ver X.Y.Z | ||||
|   validations: | ||||
|     required: true | ||||
|  | ||||
| - type: textarea | ||||
|   attributes: | ||||
|     label: Details | ||||
|     description: "Clear and thorough explanation of the bug. If relevant, include screenshots or screen recordings." | ||||
|     description: Clear and thorough explanation of the bug. | ||||
|     placeholder: I was doing X expecting Y to happen, but Z happened instead. | ||||
|   validations: | ||||
|     required: true | ||||
|  | ||||
| - type: textarea | ||||
|   attributes: | ||||
|     label: Steps to reproduce | ||||
|     description: "Minimum steps or code required to reproduce the bug." | ||||
|     description: Minimum steps required to reproduce the bug. | ||||
|     placeholder: | | ||||
|       - Step 1 | ||||
|       - Step 2 | ||||
|       - Step 3 | ||||
|   validations: | ||||
|     required: true | ||||
							
								
								
									
										7
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,8 @@ | ||||
| blank_issues_enabled: false | ||||
| contact_links: | ||||
|   - name: "\U0001F5E8 Ask a question" | ||||
|   - name: 💬 Discord server | ||||
|     url: https://discord.gg/2SUWKFnHSm | ||||
|     about: Chat with the project community. | ||||
|   - name: 🗨 Discussions | ||||
|     url: https://github.com/Tyrrrz/CliFx/discussions/new | ||||
|     about: Please ask and answer questions here.   | ||||
|     about: Ask and answer questions. | ||||
							
								
								
									
										14
									
								
								.github/ISSUE_TEMPLATE/feature-request.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/ISSUE_TEMPLATE/feature-request.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,16 +1,22 @@ | ||||
| name: "\U00002728 Feature request" | ||||
| name: ✨ Feature request | ||||
| description: Request a new feature. | ||||
| labels: [enhancement] | ||||
|  | ||||
| body: | ||||
| - type: markdown | ||||
|   attributes: | ||||
|     value: | | ||||
|       - Please check existing issues (both opened and closed) to ensure that this feature hasn't been requested before. | ||||
|       - If you want to ask a question instead of requesting a feature, use [**discussions**](https://github.com/Tyrrrz/CliFx/discussions/new) instead. | ||||
|       🧐 **Guidelines:** | ||||
|  | ||||
|       - Search through [existing issues](https://github.com/Tyrrrz/CliFx/issues?q=is%3Aissue) first to ensure that this feature has not been requested before. | ||||
|       - Write a descriptive title for your issue. Avoid generic or vague titles such as "Some suggestions" or "Ideas for improvement". | ||||
|       - Keep your issue focused on one single problem. If you have multiple feature requests, please create separate issues for each of them. | ||||
|       - Provide as much context as possible in the details section. Include screenshots, screen recordings, links, references, or anything else you may consider relevant. | ||||
|       - If you want to ask a question instead of requesting a feature, please use [discussions](https://github.com/Tyrrrz/CliFx/discussions/new) instead. | ||||
|  | ||||
| - type: textarea | ||||
|   attributes: | ||||
|     label: Details | ||||
|     description: "Clear and thorough explanation of the feature you have in mind." | ||||
|     description: Clear and thorough explanation of the feature you have in mind. | ||||
|   validations: | ||||
|     required: true | ||||
							
								
								
									
										25
									
								
								.github/workflows/CD.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/CD.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,25 +0,0 @@ | ||||
| name: CD | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     tags: | ||||
|       - "*" | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v2.3.3 | ||||
|  | ||||
|       - name: Install .NET | ||||
|         uses: actions/setup-dotnet@v1.7.2 | ||||
|         with: | ||||
|           dotnet-version: 5.0.x | ||||
|  | ||||
|       - name: Pack | ||||
|         run: dotnet pack CliFx --configuration Release | ||||
|  | ||||
|       - name: Deploy | ||||
|         run: dotnet nuget push CliFx/bin/Release/*.nupkg -s nuget.org -k ${{ secrets.NUGET_TOKEN }} | ||||
							
								
								
									
										28
									
								
								.github/workflows/CI.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.github/workflows/CI.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,28 +0,0 @@ | ||||
| name: CI | ||||
|  | ||||
| on: [push, pull_request] | ||||
|  | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: ${{ matrix.os }} | ||||
|  | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ubuntu-latest, windows-latest, macos-latest] | ||||
|  | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v2.3.3 | ||||
|  | ||||
|       - name: Install .NET | ||||
|         uses: actions/setup-dotnet@v1.7.2 | ||||
|         with: | ||||
|           dotnet-version: 5.0.x | ||||
|  | ||||
|       - name: Build & test | ||||
|         run: dotnet test --configuration Release --logger GitHubActions | ||||
|  | ||||
|       - name: Upload coverage | ||||
|         uses: codecov/codecov-action@v1.0.5 | ||||
|         with: | ||||
|           token: ${{ secrets.CODECOV_TOKEN }} | ||||
							
								
								
									
										11
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| name: main | ||||
|  | ||||
| on: [push, pull_request] | ||||
|  | ||||
| jobs: | ||||
|   main: | ||||
|     uses: Tyrrrz/.github/.github/workflows/NuGet.yml@master | ||||
|     secrets: | ||||
|       CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} | ||||
|       NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} | ||||
|       DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} | ||||
							
								
								
									
										15
									
								
								Changelog.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								Changelog.md
									
									
									
									
									
								
							| @@ -1,3 +1,18 @@ | ||||
| ### v2.1 (04-Jan-2022) | ||||
|  | ||||
| - Added `IConsole.Clear()` with corresponding implementations in `SystemConsole`, `FakeConsole`, and `FakeInMemoryConsole`. (Thanks [@Alex Rosenfeld](https://github.com/alexrosenfeld10)) | ||||
| - Added `IConsole.ReadKey()` with corresponding implementations in `SystemConsole`, `FakeConsole`, and `FakeInMemoryConsole`. (Thanks [@Alex Rosenfeld](https://github.com/alexrosenfeld10)) | ||||
| - Fixed an issue that caused parameters to appear out of order in the usage format section of the help text. (Thanks [@David Fallah](https://github.com/TAGC)) | ||||
|  | ||||
| ### v2.0.6 (17-Jul-2021) | ||||
|  | ||||
| - Fixed an issue where an exception thrown via reflection during parameter or option binding resulted in `Exception has been thrown by the target of an invocation` error instead of a more useful message. Such exceptions will now be unwrapped to provide better user experience. | ||||
|  | ||||
| ### v2.0.5 (09-Jul-2021) | ||||
|  | ||||
| - Fixed an issue where calling `IConsole.Output.Encoding.EncodingName` and some other members threw an exception. | ||||
| - Added readme file to the package. | ||||
|  | ||||
| ### v2.0.4 (24-Apr-2021) | ||||
|  | ||||
| - Fixed an issue where output and error streams in `SystemConsole` defaulted to UTF8 encoding with BOM when the application was running with UTF8 codepage. `ConsoleWriter` will now discard preamble from the specified encoding. This fix brings the behavior of `SystemConsole` in line with .NET's own `System.Console` which also discards preamble for output and error streams. | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net5.0</TargetFramework> | ||||
|     <TargetFramework>net6.0</TargetFramework> | ||||
|     <IsPackable>false</IsPackable> | ||||
|     <IsTestProject>true</IsTestProject> | ||||
|     <CollectCoverage>true</CollectCoverage> | ||||
| @@ -13,13 +13,14 @@ | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Basic.Reference.Assemblies" Version="1.2.4" /> | ||||
|     <PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" /> | ||||
|     <PackageReference Include="FluentAssertions" Version="5.10.3" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> | ||||
|     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" /> | ||||
|     <PackageReference Include="xunit" Version="2.4.0" /> | ||||
|     <PackageReference Include="FluentAssertions" Version="6.2.0" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> | ||||
|     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" /> | ||||
|     <PackageReference Include="xunit" Version="2.4.1" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" PrivateAssets="all" /> | ||||
|     <PackageReference Include="coverlet.msbuild" Version="3.0.3" PrivateAssets="all" /> | ||||
|     <PackageReference Include="coverlet.msbuild" Version="3.1.0" PrivateAssets="all" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -2,71 +2,70 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class CommandMustBeAnnotatedAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustBeAnnotatedAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_a_command_is_not_annotated_with_the_command_attribute() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class CommandMustBeAnnotatedAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustBeAnnotatedAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_a_command_is_not_annotated_with_the_command_attribute() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_command_is_annotated_with_the_command_attribute() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_command_is_annotated_with_the_command_attribute() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public abstract class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_command_is_implemented_as_an_abstract_class() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_command_is_implemented_as_an_abstract_class() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public abstract class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_class_that_is_not_a_command() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_class_that_is_not_a_command() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class Foo | ||||
| { | ||||
|     public int Bar { get; set; } = 5; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,57 +2,56 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class CommandMustImplementInterfaceAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustImplementInterfaceAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_a_command_does_not_implement_ICommand_interface() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class CommandMustImplementInterfaceAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustImplementInterfaceAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_a_command_does_not_implement_ICommand_interface() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_command_implements_ICommand_interface() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_command_implements_ICommand_interface() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_class_that_is_not_a_command() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_class_that_is_not_a_command() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class Foo | ||||
| { | ||||
|     public int Bar { get; set; } = 5; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -4,28 +4,27 @@ using FluentAssertions; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
| public class GeneralSpecs | ||||
| { | ||||
|     public class GeneralSpecs | ||||
|     [Fact] | ||||
|     public void All_analyzers_have_unique_diagnostic_IDs() | ||||
|     { | ||||
|         [Fact] | ||||
|         public void All_analyzers_have_unique_diagnostic_IDs() | ||||
|         { | ||||
|             // Arrange | ||||
|             var analyzers = typeof(AnalyzerBase) | ||||
|                 .Assembly | ||||
|                 .GetTypes() | ||||
|                 .Where(t => !t.IsAbstract && t.IsAssignableTo(typeof(DiagnosticAnalyzer))) | ||||
|                 .Select(t => (DiagnosticAnalyzer) Activator.CreateInstance(t)!) | ||||
|                 .ToArray(); | ||||
|         // Arrange | ||||
|         var analyzers = typeof(AnalyzerBase) | ||||
|             .Assembly | ||||
|             .GetTypes() | ||||
|             .Where(t => !t.IsAbstract && t.IsAssignableTo(typeof(DiagnosticAnalyzer))) | ||||
|             .Select(t => (DiagnosticAnalyzer) Activator.CreateInstance(t)!) | ||||
|             .ToArray(); | ||||
|  | ||||
|             // Act | ||||
|             var diagnosticIds = analyzers | ||||
|                 .SelectMany(a => a.SupportedDiagnostics.Select(d => d.Id)) | ||||
|                 .ToArray(); | ||||
|         // Act | ||||
|         var diagnosticIds = analyzers | ||||
|             .SelectMany(a => a.SupportedDiagnostics.Select(d => d.Id)) | ||||
|             .ToArray(); | ||||
|  | ||||
|             // Assert | ||||
|             diagnosticIds.Should().OnlyHaveUniqueItems(); | ||||
|         } | ||||
|         // Assert | ||||
|         diagnosticIds.Should().OnlyHaveUniqueItems(); | ||||
|     } | ||||
| } | ||||
| @@ -2,34 +2,34 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class OptionMustBeInsideCommandAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustBeInsideCommandAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_an_option_is_inside_a_class_that_is_not_a_command() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class OptionMustBeInsideCommandAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustBeInsideCommandAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_an_option_is_inside_a_class_that_is_not_a_command() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyClass | ||||
| { | ||||
|     [CommandOption(""foo"")] | ||||
|     public string Foo { get; set; } | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_is_inside_a_command() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_is_inside_a_command() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -39,32 +39,32 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_is_inside_an_abstract_class() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_is_inside_an_abstract_class() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public abstract class MyCommand | ||||
| { | ||||
|     [CommandOption(""foo"")] | ||||
|     public string Foo { get; set; } | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -73,8 +73,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class OptionMustHaveNameOrShortNameAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveNameOrShortNameAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_an_option_does_not_have_a_name_or_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class OptionMustHaveNameOrShortNameAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveNameOrShortNameAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_an_option_does_not_have_a_name_or_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -23,16 +23,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_has_a_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_has_a_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -42,16 +42,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_has_a_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_has_a_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -61,16 +61,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -79,8 +79,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class OptionMustHaveUniqueNameAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueNameAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_an_option_has_the_same_name_as_another_option() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class OptionMustHaveUniqueNameAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueNameAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_an_option_has_the_same_name_as_another_option() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -26,16 +26,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_has_a_unique_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_has_a_unique_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -48,16 +48,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -67,16 +67,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -85,8 +85,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class OptionMustHaveUniqueShortNameAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueShortNameAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_an_option_has_the_same_short_name_as_another_option() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class OptionMustHaveUniqueShortNameAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueShortNameAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_an_option_has_the_same_short_name_as_another_option() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -26,16 +26,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_has_a_unique_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_has_a_unique_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -48,16 +48,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_has_a_short_name_which_is_unique_only_in_casing() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_has_a_short_name_which_is_unique_only_in_casing() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -70,16 +70,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -89,16 +89,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -107,8 +107,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class OptionMustHaveValidConverterAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidConverterAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_the_specified_option_converter_does_not_derive_from_BindingConverter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class OptionMustHaveValidConverterAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidConverterAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_the_specified_option_converter_does_not_derive_from_BindingConverter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyConverter | ||||
| { | ||||
|     public string Convert(string rawValue) => rawValue; | ||||
| @@ -28,16 +28,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_the_specified_option_converter_derives_from_BindingConverter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_the_specified_option_converter_derives_from_BindingConverter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyConverter : BindingConverter<string> | ||||
| { | ||||
|     public override string Convert(string rawValue) => rawValue; | ||||
| @@ -52,16 +52,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_converter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_converter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -71,16 +71,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -89,8 +89,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class OptionMustHaveValidNameAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidNameAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_an_option_has_a_name_which_is_too_short() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class OptionMustHaveValidNameAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidNameAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_an_option_has_a_name_which_is_too_short() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -23,16 +23,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_an_option_has_a_name_that_starts_with_a_non_letter_character() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_an_option_has_a_name_that_starts_with_a_non_letter_character() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -42,16 +42,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_has_a_valid_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_has_a_valid_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -61,16 +61,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -80,16 +80,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -98,8 +98,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class OptionMustHaveValidShortNameAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidShortNameAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_an_option_has_a_short_name_which_is_not_a_letter_character() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class OptionMustHaveValidShortNameAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidShortNameAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_an_option_has_a_short_name_which_is_not_a_letter_character() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -23,16 +23,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_has_a_valid_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_has_a_valid_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -42,16 +42,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_a_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -61,16 +61,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -79,8 +79,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class OptionMustHaveValidValidatorsAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidValidatorsAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_one_of_the_specified_option_validators_does_not_derive_from_BindingValidator() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class OptionMustHaveValidValidatorsAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidValidatorsAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_one_of_the_specified_option_validators_does_not_derive_from_BindingValidator() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyValidator | ||||
| { | ||||
|     public void Validate(string value) {} | ||||
| @@ -28,16 +28,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_all_specified_option_validators_derive_from_BindingValidator() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_all_specified_option_validators_derive_from_BindingValidator() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyValidator : BindingValidator<string> | ||||
| { | ||||
|     public override BindingValidationError Validate(string value) => Ok(); | ||||
| @@ -52,16 +52,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_validators() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_an_option_does_not_have_validators() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -71,16 +71,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_an_option() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -89,8 +89,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,34 +2,34 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class ParameterMustBeInsideCommandAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeInsideCommandAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_a_parameter_is_inside_a_class_that_is_not_a_command() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class ParameterMustBeInsideCommandAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeInsideCommandAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_a_parameter_is_inside_a_class_that_is_not_a_command() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyClass | ||||
| { | ||||
|     [CommandParameter(0)] | ||||
|     public string Foo { get; set; } | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_parameter_is_inside_a_command() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_parameter_is_inside_a_command() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -39,32 +39,32 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_parameter_is_inside_an_abstract_class() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_parameter_is_inside_an_abstract_class() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public abstract class MyCommand | ||||
| { | ||||
|     [CommandParameter(0)] | ||||
|     public string Foo { get; set; } | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -73,8 +73,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class ParameterMustBeLastIfNonScalarAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonScalarAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_last_in_order() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class ParameterMustBeLastIfNonScalarAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonScalarAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_last_in_order() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -26,16 +26,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_non_scalar_parameter_is_last_in_order() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_non_scalar_parameter_is_last_in_order() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -48,16 +48,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_no_non_scalar_parameters_are_defined() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_no_non_scalar_parameters_are_defined() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -70,16 +70,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -88,8 +88,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeSingleIfNonScalarAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_more_than_one_non_scalar_parameters_are_defined() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeSingleIfNonScalarAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_more_than_one_non_scalar_parameters_are_defined() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -26,16 +26,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_only_one_non_scalar_parameter_is_defined() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_only_one_non_scalar_parameter_is_defined() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -48,16 +48,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_no_non_scalar_parameters_are_defined() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_no_non_scalar_parameters_are_defined() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -70,16 +70,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -88,8 +88,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class ParameterMustHaveUniqueNameAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueNameAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_a_parameter_has_the_same_name_as_another_parameter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class ParameterMustHaveUniqueNameAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueNameAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_a_parameter_has_the_same_name_as_another_parameter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -26,16 +26,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_parameter_has_unique_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_parameter_has_unique_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -48,16 +48,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -66,8 +66,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class ParameterMustHaveUniqueOrderAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueOrderAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_a_parameter_has_the_same_order_as_another_parameter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class ParameterMustHaveUniqueOrderAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueOrderAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_a_parameter_has_the_same_order_as_another_parameter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -26,16 +26,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_parameter_has_unique_order() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_parameter_has_unique_order() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -48,16 +48,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -66,8 +66,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class ParameterMustHaveValidConverterAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidConverterAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_the_specified_parameter_converter_does_not_derive_from_BindingConverter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class ParameterMustHaveValidConverterAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidConverterAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_the_specified_parameter_converter_does_not_derive_from_BindingConverter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyConverter | ||||
| { | ||||
|     public string Convert(string rawValue) => rawValue; | ||||
| @@ -28,16 +28,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_the_specified_parameter_converter_derives_from_BindingConverter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_the_specified_parameter_converter_derives_from_BindingConverter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyConverter : BindingConverter<string> | ||||
| { | ||||
|     public override string Convert(string rawValue) => rawValue; | ||||
| @@ -52,16 +52,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_parameter_does_not_have_a_converter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_parameter_does_not_have_a_converter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -71,16 +71,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -89,8 +89,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class ParameterMustHaveValidValidatorsAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidValidatorsAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_one_of_the_specified_parameter_validators_does_not_derive_from_BindingValidator() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class ParameterMustHaveValidValidatorsAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidValidatorsAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_one_of_the_specified_parameter_validators_does_not_derive_from_BindingValidator() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyValidator | ||||
| { | ||||
|     public void Validate(string value) {} | ||||
| @@ -28,16 +28,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_all_specified_parameter_validators_derive_from_BindingValidator() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_all_specified_parameter_validators_derive_from_BindingValidator() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| public class MyValidator : BindingValidator<string> | ||||
| { | ||||
|     public override BindingValidationError Validate(string value) => Ok(); | ||||
| @@ -52,16 +52,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_parameter_does_not_have_validators() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_parameter_does_not_have_validators() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -71,16 +71,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -89,8 +89,7 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,18 +2,18 @@ | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Xunit; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests | ||||
| { | ||||
|     public class SystemConsoleShouldBeAvoidedAnalyzerSpecs | ||||
|     { | ||||
|         private static DiagnosticAnalyzer Analyzer { get; } = new SystemConsoleShouldBeAvoidedAnalyzer(); | ||||
| namespace CliFx.Analyzers.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_a_command_calls_a_method_on_SystemConsole() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
| public class SystemConsoleShouldBeAvoidedAnalyzerSpecs | ||||
| { | ||||
|     private static DiagnosticAnalyzer Analyzer { get; } = new SystemConsoleShouldBeAvoidedAnalyzer(); | ||||
|  | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_a_command_calls_a_method_on_SystemConsole() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -24,16 +24,16 @@ public class MyCommand : ICommand | ||||
|     } | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_a_command_accesses_a_property_on_SystemConsole() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_a_command_accesses_a_property_on_SystemConsole() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -43,16 +43,16 @@ public class MyCommand : ICommand | ||||
|         return default; | ||||
|     } | ||||
| }"; | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_reports_an_error_if_a_command_calls_a_method_on_a_property_of_SystemConsole() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_reports_an_error_if_a_command_calls_a_method_on_a_property_of_SystemConsole() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -63,16 +63,16 @@ public class MyCommand : ICommand | ||||
|     } | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().ProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().ProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_command_interacts_with_the_console_through_IConsole() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_command_interacts_with_the_console_through_IConsole() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -83,16 +83,16 @@ public class MyCommand : ICommand | ||||
|     } | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_IConsole_is_not_available_in_the_current_method() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_IConsole_is_not_available_in_the_current_method() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -101,16 +101,16 @@ public class MyCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Analyzer_does_not_report_an_error_if_a_command_does_not_access_SystemConsole() | ||||
|         { | ||||
|             // Arrange | ||||
|             // language=cs | ||||
|             const string code = @" | ||||
|     [Fact] | ||||
|     public void Analyzer_does_not_report_an_error_if_a_command_does_not_access_SystemConsole() | ||||
|     { | ||||
|         // Arrange | ||||
|         // language=cs | ||||
|         const string code = @" | ||||
| [Command] | ||||
| public class MyCommand : ICommand | ||||
| { | ||||
| @@ -120,8 +120,7 @@ public class MyCommand : ICommand | ||||
|     } | ||||
| }"; | ||||
|  | ||||
|             // Act & assert | ||||
|             Analyzer.Should().NotProduceDiagnostics(code); | ||||
|         } | ||||
|         // Act & assert | ||||
|         Analyzer.Should().NotProduceDiagnostics(code); | ||||
|     } | ||||
| } | ||||
| @@ -2,8 +2,8 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | ||||
| using System.Linq; | ||||
| using System.Reflection; | ||||
| using System.Text; | ||||
| using Basic.Reference.Assemblies; | ||||
| using FluentAssertions.Execution; | ||||
| using FluentAssertions.Primitives; | ||||
| using Microsoft.CodeAnalysis; | ||||
| @@ -11,164 +11,157 @@ using Microsoft.CodeAnalysis.CSharp; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
| using Microsoft.CodeAnalysis.Text; | ||||
|  | ||||
| namespace CliFx.Analyzers.Tests.Utils | ||||
| { | ||||
|     internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer, AnalyzerAssertions> | ||||
|     { | ||||
|         protected override string Identifier { get; } = "analyzer"; | ||||
| namespace CliFx.Analyzers.Tests.Utils; | ||||
|  | ||||
|         public AnalyzerAssertions(DiagnosticAnalyzer analyzer) | ||||
|             : base(analyzer) | ||||
| internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer, AnalyzerAssertions> | ||||
| { | ||||
|     protected override string Identifier { get; } = "analyzer"; | ||||
|  | ||||
|     public AnalyzerAssertions(DiagnosticAnalyzer analyzer) | ||||
|         : base(analyzer) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     private Compilation Compile(string sourceCode) | ||||
|     { | ||||
|         // Get default system namespaces | ||||
|         var defaultSystemNamespaces = new[] | ||||
|         { | ||||
|             "System", | ||||
|             "System.Collections.Generic", | ||||
|             "System.Threading.Tasks" | ||||
|         }; | ||||
|  | ||||
|         // Get default CliFx namespaces | ||||
|         var defaultCliFxNamespaces = typeof(ICommand) | ||||
|             .Assembly | ||||
|             .GetTypes() | ||||
|             .Where(t => t.IsPublic) | ||||
|             .Select(t => t.Namespace) | ||||
|             .Distinct() | ||||
|             .ToArray(); | ||||
|  | ||||
|         // Append default imports to the source code | ||||
|         var sourceCodeWithUsings = | ||||
|             string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};")) + | ||||
|             string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};")) + | ||||
|             Environment.NewLine + | ||||
|             sourceCode; | ||||
|  | ||||
|         // Parse the source code | ||||
|         var ast = SyntaxFactory.ParseSyntaxTree( | ||||
|             SourceText.From(sourceCodeWithUsings), | ||||
|             CSharpParseOptions.Default | ||||
|         ); | ||||
|  | ||||
|         // Compile the code to IL | ||||
|         var compilation = CSharpCompilation.Create( | ||||
|             "CliFxTests_DynamicAssembly_" + Guid.NewGuid(), | ||||
|             new[] {ast}, | ||||
|             ReferenceAssemblies.Net50 | ||||
|                 .Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)), | ||||
|             // DLL to avoid having to define the Main() method | ||||
|             new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) | ||||
|         ); | ||||
|  | ||||
|         var compilationErrors = compilation | ||||
|             .GetDiagnostics() | ||||
|             .Where(d => d.Severity >= DiagnosticSeverity.Error) | ||||
|             .ToArray(); | ||||
|  | ||||
|         if (compilationErrors.Any()) | ||||
|         { | ||||
|             throw new InvalidOperationException( | ||||
|                 "Failed to compile code." + | ||||
|                 Environment.NewLine + | ||||
|                 string.Join(Environment.NewLine, compilationErrors.Select(e => e.ToString())) | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         private Compilation Compile(string sourceCode) | ||||
|         return compilation; | ||||
|     } | ||||
|  | ||||
|     private IReadOnlyList<Diagnostic> GetProducedDiagnostics(string sourceCode) | ||||
|     { | ||||
|         var analyzers = ImmutableArray.Create(Subject); | ||||
|         var compilation = Compile(sourceCode); | ||||
|  | ||||
|         return compilation | ||||
|             .WithAnalyzers(analyzers) | ||||
|             .GetAnalyzerDiagnosticsAsync(analyzers, default) | ||||
|             .GetAwaiter() | ||||
|             .GetResult(); | ||||
|     } | ||||
|  | ||||
|     public void ProduceDiagnostics(string sourceCode) | ||||
|     { | ||||
|         var expectedDiagnostics = Subject.SupportedDiagnostics; | ||||
|         var producedDiagnostics = GetProducedDiagnostics(sourceCode); | ||||
|  | ||||
|         var expectedDiagnosticIds = expectedDiagnostics.Select(d => d.Id).Distinct().ToArray(); | ||||
|         var producedDiagnosticIds = producedDiagnostics.Select(d => d.Id).Distinct().ToArray(); | ||||
|  | ||||
|         var result = | ||||
|             expectedDiagnosticIds.Intersect(producedDiagnosticIds).Count() == | ||||
|             expectedDiagnosticIds.Length; | ||||
|  | ||||
|         Execute.Assertion.ForCondition(result).FailWith(() => | ||||
|         { | ||||
|             // Get default system namespaces | ||||
|             var defaultSystemNamespaces = new[] | ||||
|             var buffer = new StringBuilder(); | ||||
|  | ||||
|             buffer.AppendLine("Expected and produced diagnostics do not match."); | ||||
|             buffer.AppendLine(); | ||||
|  | ||||
|             buffer.AppendLine("Expected diagnostics:"); | ||||
|  | ||||
|             foreach (var expectedDiagnostic in expectedDiagnostics) | ||||
|             { | ||||
|                 "System", | ||||
|                 "System.Collections.Generic", | ||||
|                 "System.Threading.Tasks" | ||||
|             }; | ||||
|  | ||||
|             // Get default CliFx namespaces | ||||
|             var defaultCliFxNamespaces = typeof(ICommand) | ||||
|                 .Assembly | ||||
|                 .GetTypes() | ||||
|                 .Where(t => t.IsPublic) | ||||
|                 .Select(t => t.Namespace) | ||||
|                 .Distinct() | ||||
|                 .ToArray(); | ||||
|  | ||||
|             // Append default imports to the source code | ||||
|             var sourceCodeWithUsings = | ||||
|                 string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};")) + | ||||
|                 string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};")) + | ||||
|                 Environment.NewLine + | ||||
|                 sourceCode; | ||||
|  | ||||
|             // Parse the source code | ||||
|             var ast = SyntaxFactory.ParseSyntaxTree( | ||||
|                 SourceText.From(sourceCodeWithUsings), | ||||
|                 CSharpParseOptions.Default | ||||
|             ); | ||||
|  | ||||
|             // Compile the code to IL | ||||
|             var compilation = CSharpCompilation.Create( | ||||
|                 "CliFxTests_DynamicAssembly_" + Guid.NewGuid(), | ||||
|                 new[] {ast}, | ||||
|                 new[] | ||||
|                 { | ||||
|                     MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location), | ||||
|                     MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), | ||||
|                     MetadataReference.CreateFromFile(typeof(object).Assembly.Location), | ||||
|                     MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), | ||||
|                     MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location) | ||||
|                 }, | ||||
|                 // DLL to avoid having to define the Main() method | ||||
|                 new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) | ||||
|             ); | ||||
|  | ||||
|             var compilationErrors = compilation | ||||
|                 .GetDiagnostics() | ||||
|                 .Where(d => d.Severity >= DiagnosticSeverity.Error) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             if (compilationErrors.Any()) | ||||
|             { | ||||
|                 throw new InvalidOperationException( | ||||
|                     "Failed to compile code." + | ||||
|                     Environment.NewLine + | ||||
|                     string.Join(Environment.NewLine, compilationErrors.Select(e => e.ToString())) | ||||
|                 ); | ||||
|                 buffer.Append("  - "); | ||||
|                 buffer.Append(expectedDiagnostic.Id); | ||||
|                 buffer.AppendLine(); | ||||
|             } | ||||
|  | ||||
|             return compilation; | ||||
|         } | ||||
|             buffer.AppendLine(); | ||||
|  | ||||
|         private IReadOnlyList<Diagnostic> GetProducedDiagnostics(string sourceCode) | ||||
|         { | ||||
|             var analyzers = ImmutableArray.Create(Subject); | ||||
|             var compilation = Compile(sourceCode); | ||||
|             buffer.AppendLine("Produced diagnostics:"); | ||||
|  | ||||
|             return compilation | ||||
|                 .WithAnalyzers(analyzers) | ||||
|                 .GetAnalyzerDiagnosticsAsync(analyzers, default) | ||||
|                 .GetAwaiter() | ||||
|                 .GetResult(); | ||||
|         } | ||||
|  | ||||
|         public void ProduceDiagnostics(string sourceCode) | ||||
|         { | ||||
|             var expectedDiagnostics = Subject.SupportedDiagnostics; | ||||
|             var producedDiagnostics = GetProducedDiagnostics(sourceCode); | ||||
|  | ||||
|             var expectedDiagnosticIds = expectedDiagnostics.Select(d => d.Id).Distinct().ToArray(); | ||||
|             var producedDiagnosticIds = producedDiagnostics.Select(d => d.Id).Distinct().ToArray(); | ||||
|  | ||||
|             var result = | ||||
|                 expectedDiagnosticIds.Intersect(producedDiagnosticIds).Count() == | ||||
|                 expectedDiagnosticIds.Length; | ||||
|  | ||||
|             Execute.Assertion.ForCondition(result).FailWith(() => | ||||
|             foreach (var producedDiagnostic in producedDiagnostics) | ||||
|             { | ||||
|                 var buffer = new StringBuilder(); | ||||
|                 buffer.Append("  - "); | ||||
|                 buffer.Append(producedDiagnostic); | ||||
|             } | ||||
|  | ||||
|                 buffer.AppendLine("Expected and produced diagnostics do not match."); | ||||
|                 buffer.AppendLine(); | ||||
|  | ||||
|                 buffer.AppendLine("Expected diagnostics:"); | ||||
|  | ||||
|                 foreach (var expectedDiagnostic in expectedDiagnostics) | ||||
|                 { | ||||
|                     buffer.Append("  - "); | ||||
|                     buffer.Append(expectedDiagnostic.Id); | ||||
|                     buffer.AppendLine(); | ||||
|                 } | ||||
|  | ||||
|                 buffer.AppendLine(); | ||||
|  | ||||
|                 buffer.AppendLine("Produced diagnostics:"); | ||||
|  | ||||
|                 foreach (var producedDiagnostic in producedDiagnostics) | ||||
|                 { | ||||
|                     buffer.Append("  - "); | ||||
|                     buffer.Append(producedDiagnostic); | ||||
|                 } | ||||
|  | ||||
|                 return new FailReason(buffer.ToString()); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         public void NotProduceDiagnostics(string sourceCode) | ||||
|         { | ||||
|             var producedDiagnostics = GetProducedDiagnostics(sourceCode); | ||||
|  | ||||
|             var result = !producedDiagnostics.Any(); | ||||
|  | ||||
|             Execute.Assertion.ForCondition(result).FailWith(() => | ||||
|             { | ||||
|                 var buffer = new StringBuilder(); | ||||
|  | ||||
|                 buffer.AppendLine("Expected no produced diagnostics."); | ||||
|                 buffer.AppendLine(); | ||||
|  | ||||
|                 buffer.AppendLine("Produced diagnostics:"); | ||||
|  | ||||
|                 foreach (var producedDiagnostic in producedDiagnostics) | ||||
|                 { | ||||
|                     buffer.Append("  - "); | ||||
|                     buffer.Append(producedDiagnostic); | ||||
|                 } | ||||
|  | ||||
|                 return new FailReason(buffer.ToString()); | ||||
|             }); | ||||
|         } | ||||
|             return new FailReason(buffer.ToString()); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     internal static class AnalyzerAssertionsExtensions | ||||
|     public void NotProduceDiagnostics(string sourceCode) | ||||
|     { | ||||
|         public static AnalyzerAssertions Should(this DiagnosticAnalyzer analyzer) => new(analyzer); | ||||
|         var producedDiagnostics = GetProducedDiagnostics(sourceCode); | ||||
|  | ||||
|         var result = !producedDiagnostics.Any(); | ||||
|  | ||||
|         Execute.Assertion.ForCondition(result).FailWith(() => | ||||
|         { | ||||
|             var buffer = new StringBuilder(); | ||||
|  | ||||
|             buffer.AppendLine("Expected no produced diagnostics."); | ||||
|             buffer.AppendLine(); | ||||
|  | ||||
|             buffer.AppendLine("Produced diagnostics:"); | ||||
|  | ||||
|             foreach (var producedDiagnostic in producedDiagnostics) | ||||
|             { | ||||
|                 buffer.Append("  - "); | ||||
|                 buffer.Append(producedDiagnostic); | ||||
|             } | ||||
|  | ||||
|             return new FailReason(buffer.ToString()); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| internal static class AnalyzerAssertionsExtensions | ||||
| { | ||||
|     public static AnalyzerAssertions Should(this DiagnosticAnalyzer analyzer) => new(analyzer); | ||||
| } | ||||
| @@ -3,38 +3,37 @@ using CliFx.Analyzers.Utils.Extensions; | ||||
| using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| public abstract class AnalyzerBase : DiagnosticAnalyzer | ||||
| { | ||||
|     public abstract class AnalyzerBase : DiagnosticAnalyzer | ||||
|     public DiagnosticDescriptor SupportedDiagnostic { get; } | ||||
|  | ||||
|     public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } | ||||
|  | ||||
|     protected AnalyzerBase( | ||||
|         string diagnosticTitle, | ||||
|         string diagnosticMessage, | ||||
|         DiagnosticSeverity diagnosticSeverity = DiagnosticSeverity.Error) | ||||
|     { | ||||
|         public DiagnosticDescriptor SupportedDiagnostic { get; } | ||||
|         SupportedDiagnostic = new DiagnosticDescriptor( | ||||
|             "CliFx_" + GetType().Name.TrimEnd("Analyzer"), | ||||
|             diagnosticTitle, | ||||
|             diagnosticMessage, | ||||
|             "CliFx", | ||||
|             diagnosticSeverity, | ||||
|             true | ||||
|         ); | ||||
|  | ||||
|         public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } | ||||
|         SupportedDiagnostics = ImmutableArray.Create(SupportedDiagnostic); | ||||
|     } | ||||
|  | ||||
|         protected AnalyzerBase( | ||||
|             string diagnosticTitle, | ||||
|             string diagnosticMessage, | ||||
|             DiagnosticSeverity diagnosticSeverity = DiagnosticSeverity.Error) | ||||
|         { | ||||
|             SupportedDiagnostic = new DiagnosticDescriptor( | ||||
|                 "CliFx_" + GetType().Name.TrimEnd("Analyzer"), | ||||
|                 diagnosticTitle, | ||||
|                 diagnosticMessage, | ||||
|                 "CliFx", | ||||
|                 diagnosticSeverity, | ||||
|                 true | ||||
|             ); | ||||
|     protected Diagnostic CreateDiagnostic(Location location) => | ||||
|         Diagnostic.Create(SupportedDiagnostic, location); | ||||
|  | ||||
|             SupportedDiagnostics = ImmutableArray.Create(SupportedDiagnostic); | ||||
|         } | ||||
|  | ||||
|         protected Diagnostic CreateDiagnostic(Location location) => | ||||
|             Diagnostic.Create(SupportedDiagnostic, location); | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             context.EnableConcurrentExecution(); | ||||
|             context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||||
|         } | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         context.EnableConcurrentExecution(); | ||||
|         context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||||
|     } | ||||
| } | ||||
| @@ -5,50 +5,49 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class CommandMustBeAnnotatedAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class CommandMustBeAnnotatedAnalyzer : AnalyzerBase | ||||
|     public CommandMustBeAnnotatedAnalyzer() | ||||
|         : base( | ||||
|             $"Commands must be annotated with `{SymbolNames.CliFxCommandAttribute}`", | ||||
|             $"This type must be annotated with `{SymbolNames.CliFxCommandAttribute}` in order to be a valid command.") | ||||
|     { | ||||
|         public CommandMustBeAnnotatedAnalyzer() | ||||
|             : base( | ||||
|                 $"Commands must be annotated with `{SymbolNames.CliFxCommandAttribute}`", | ||||
|                 $"This type must be annotated with `{SymbolNames.CliFxCommandAttribute}` in order to be a valid command.") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         ClassDeclarationSyntax classDeclaration, | ||||
|         ITypeSymbol type) | ||||
|     { | ||||
|         // Ignore abstract classes, because they may be used to define | ||||
|         // base implementations for commands, in which case the command | ||||
|         // attribute doesn't make sense. | ||||
|         if (type.IsAbstract) | ||||
|             return; | ||||
|  | ||||
|         var implementsCommandInterface = type | ||||
|             .AllInterfaces | ||||
|             .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface)); | ||||
|  | ||||
|         var hasCommandAttribute = type | ||||
|             .GetAttributes() | ||||
|             .Select(a => a.AttributeClass) | ||||
|             .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandAttribute)); | ||||
|  | ||||
|         // If the interface is implemented, but the attribute is missing, | ||||
|         // then it's very likely a user error. | ||||
|         if (implementsCommandInterface && !hasCommandAttribute) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             ClassDeclarationSyntax classDeclaration, | ||||
|             ITypeSymbol type) | ||||
|         { | ||||
|             // Ignore abstract classes, because they may be used to define | ||||
|             // base implementations for commands, in which case the command | ||||
|             // attribute doesn't make sense. | ||||
|             if (type.IsAbstract) | ||||
|                 return; | ||||
|  | ||||
|             var implementsCommandInterface = type | ||||
|                 .AllInterfaces | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface)); | ||||
|  | ||||
|             var hasCommandAttribute = type | ||||
|                 .GetAttributes() | ||||
|                 .Select(a => a.AttributeClass) | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandAttribute)); | ||||
|  | ||||
|             // If the interface is implemented, but the attribute is missing, | ||||
|             // then it's very likely a user error. | ||||
|             if (implementsCommandInterface && !hasCommandAttribute) | ||||
|             { | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(classDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandleClassDeclaration(Analyze); | ||||
|             context.ReportDiagnostic(CreateDiagnostic(classDeclaration.GetLocation())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandleClassDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -5,44 +5,43 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class CommandMustImplementInterfaceAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class CommandMustImplementInterfaceAnalyzer : AnalyzerBase | ||||
|     public CommandMustImplementInterfaceAnalyzer() | ||||
|         : base( | ||||
|             $"Commands must implement `{SymbolNames.CliFxCommandInterface}` interface", | ||||
|             $"This type must implement `{SymbolNames.CliFxCommandInterface}` interface in order to be a valid command.") | ||||
|     { | ||||
|         public CommandMustImplementInterfaceAnalyzer() | ||||
|             : base( | ||||
|                 $"Commands must implement `{SymbolNames.CliFxCommandInterface}` interface", | ||||
|                 $"This type must implement `{SymbolNames.CliFxCommandInterface}` interface in order to be a valid command.") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         ClassDeclarationSyntax classDeclaration, | ||||
|         ITypeSymbol type) | ||||
|     { | ||||
|         var hasCommandAttribute = type | ||||
|             .GetAttributes() | ||||
|             .Select(a => a.AttributeClass) | ||||
|             .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandAttribute)); | ||||
|  | ||||
|         var implementsCommandInterface = type | ||||
|             .AllInterfaces | ||||
|             .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface)); | ||||
|  | ||||
|         // If the attribute is present, but the interface is not implemented, | ||||
|         // it's very likely a user error. | ||||
|         if (hasCommandAttribute && !implementsCommandInterface) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             ClassDeclarationSyntax classDeclaration, | ||||
|             ITypeSymbol type) | ||||
|         { | ||||
|             var hasCommandAttribute = type | ||||
|                 .GetAttributes() | ||||
|                 .Select(a => a.AttributeClass) | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandAttribute)); | ||||
|  | ||||
|             var implementsCommandInterface = type | ||||
|                 .AllInterfaces | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface)); | ||||
|  | ||||
|             // If the attribute is present, but the interface is not implemented, | ||||
|             // it's very likely a user error. | ||||
|             if (hasCommandAttribute && !implementsCommandInterface) | ||||
|             { | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(classDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandleClassDeclaration(Analyze); | ||||
|             context.ReportDiagnostic(CreateDiagnostic(classDeclaration.GetLocation())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandleClassDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -3,81 +3,80 @@ using Microsoft.CodeAnalysis; | ||||
| using System.Linq; | ||||
| using CliFx.Analyzers.Utils.Extensions; | ||||
|  | ||||
| namespace CliFx.Analyzers.ObjectModel | ||||
| namespace CliFx.Analyzers.ObjectModel; | ||||
|  | ||||
| internal partial class CommandOptionSymbol | ||||
| { | ||||
|     internal partial class CommandOptionSymbol | ||||
|     public string? Name { get; } | ||||
|  | ||||
|     public char? ShortName { get; } | ||||
|  | ||||
|     public ITypeSymbol? ConverterType { get; } | ||||
|  | ||||
|     public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; } | ||||
|  | ||||
|     public CommandOptionSymbol( | ||||
|         string? name, | ||||
|         char? shortName, | ||||
|         ITypeSymbol? converterType, | ||||
|         IReadOnlyList<ITypeSymbol> validatorTypes) | ||||
|     { | ||||
|         public string? Name { get; } | ||||
|  | ||||
|         public char? ShortName { get; } | ||||
|  | ||||
|         public ITypeSymbol? ConverterType { get; } | ||||
|  | ||||
|         public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; } | ||||
|  | ||||
|         public CommandOptionSymbol( | ||||
|             string? name, | ||||
|             char? shortName, | ||||
|             ITypeSymbol? converterType, | ||||
|             IReadOnlyList<ITypeSymbol> validatorTypes) | ||||
|         { | ||||
|             Name = name; | ||||
|             ShortName = shortName; | ||||
|             ConverterType = converterType; | ||||
|             ValidatorTypes = validatorTypes; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal partial class CommandOptionSymbol | ||||
|     { | ||||
|         private static AttributeData? TryGetOptionAttribute(IPropertySymbol property) => | ||||
|             property | ||||
|                 .GetAttributes() | ||||
|                 .FirstOrDefault(a => a.AttributeClass.DisplayNameMatches(SymbolNames.CliFxCommandOptionAttribute)); | ||||
|  | ||||
|         private static CommandOptionSymbol FromAttribute(AttributeData attribute) | ||||
|         { | ||||
|             var name = attribute | ||||
|                 .ConstructorArguments | ||||
|                 .Where(a => a.Type.DisplayNameMatches("string") || a.Type.DisplayNameMatches("System.String")) | ||||
|                 .Select(a => a.Value) | ||||
|                 .FirstOrDefault() as string; | ||||
|  | ||||
|             var shortName = attribute | ||||
|                 .ConstructorArguments | ||||
|                 .Where(a => a.Type.DisplayNameMatches("char") || a.Type.DisplayNameMatches("System.Char")) | ||||
|                 .Select(a => a.Value) | ||||
|                 .FirstOrDefault() as char?; | ||||
|  | ||||
|             var converter = attribute | ||||
|                 .NamedArguments | ||||
|                 .Where(a => a.Key == "Converter") | ||||
|                 .Select(a => a.Value.Value) | ||||
|                 .Cast<ITypeSymbol?>() | ||||
|                 .FirstOrDefault(); | ||||
|  | ||||
|             var validators = attribute | ||||
|                 .NamedArguments | ||||
|                 .Where(a => a.Key == "Validators") | ||||
|                 .SelectMany(a => a.Value.Values) | ||||
|                 .Select(c => c.Value) | ||||
|                 .Cast<ITypeSymbol>() | ||||
|                 .ToArray(); | ||||
|  | ||||
|             return new CommandOptionSymbol(name, shortName, converter, validators); | ||||
|         } | ||||
|  | ||||
|         public static CommandOptionSymbol? TryResolve(IPropertySymbol property) | ||||
|         { | ||||
|             var attribute = TryGetOptionAttribute(property); | ||||
|  | ||||
|             if (attribute is null) | ||||
|                 return null; | ||||
|  | ||||
|             return FromAttribute(attribute); | ||||
|         } | ||||
|  | ||||
|         public static bool IsOptionProperty(IPropertySymbol property) => | ||||
|             TryGetOptionAttribute(property) is not null; | ||||
|         Name = name; | ||||
|         ShortName = shortName; | ||||
|         ConverterType = converterType; | ||||
|         ValidatorTypes = validatorTypes; | ||||
|     } | ||||
| } | ||||
|  | ||||
| internal partial class CommandOptionSymbol | ||||
| { | ||||
|     private static AttributeData? TryGetOptionAttribute(IPropertySymbol property) => | ||||
|         property | ||||
|             .GetAttributes() | ||||
|             .FirstOrDefault(a => a.AttributeClass.DisplayNameMatches(SymbolNames.CliFxCommandOptionAttribute)); | ||||
|  | ||||
|     private static CommandOptionSymbol FromAttribute(AttributeData attribute) | ||||
|     { | ||||
|         var name = attribute | ||||
|             .ConstructorArguments | ||||
|             .Where(a => a.Type.DisplayNameMatches("string") || a.Type.DisplayNameMatches("System.String")) | ||||
|             .Select(a => a.Value) | ||||
|             .FirstOrDefault() as string; | ||||
|  | ||||
|         var shortName = attribute | ||||
|             .ConstructorArguments | ||||
|             .Where(a => a.Type.DisplayNameMatches("char") || a.Type.DisplayNameMatches("System.Char")) | ||||
|             .Select(a => a.Value) | ||||
|             .FirstOrDefault() as char?; | ||||
|  | ||||
|         var converter = attribute | ||||
|             .NamedArguments | ||||
|             .Where(a => a.Key == "Converter") | ||||
|             .Select(a => a.Value.Value) | ||||
|             .Cast<ITypeSymbol?>() | ||||
|             .FirstOrDefault(); | ||||
|  | ||||
|         var validators = attribute | ||||
|             .NamedArguments | ||||
|             .Where(a => a.Key == "Validators") | ||||
|             .SelectMany(a => a.Value.Values) | ||||
|             .Select(c => c.Value) | ||||
|             .Cast<ITypeSymbol>() | ||||
|             .ToArray(); | ||||
|  | ||||
|         return new CommandOptionSymbol(name, shortName, converter, validators); | ||||
|     } | ||||
|  | ||||
|     public static CommandOptionSymbol? TryResolve(IPropertySymbol property) | ||||
|     { | ||||
|         var attribute = TryGetOptionAttribute(property); | ||||
|  | ||||
|         if (attribute is null) | ||||
|             return null; | ||||
|  | ||||
|         return FromAttribute(attribute); | ||||
|     } | ||||
|  | ||||
|     public static bool IsOptionProperty(IPropertySymbol property) => | ||||
|         TryGetOptionAttribute(property) is not null; | ||||
| } | ||||
| @@ -3,80 +3,79 @@ using System.Linq; | ||||
| using CliFx.Analyzers.Utils.Extensions; | ||||
| using Microsoft.CodeAnalysis; | ||||
|  | ||||
| namespace CliFx.Analyzers.ObjectModel | ||||
| namespace CliFx.Analyzers.ObjectModel; | ||||
|  | ||||
| internal partial class CommandParameterSymbol | ||||
| { | ||||
|     internal partial class CommandParameterSymbol | ||||
|     public int Order { get; } | ||||
|  | ||||
|     public string? Name { get; } | ||||
|  | ||||
|     public ITypeSymbol? ConverterType { get; } | ||||
|  | ||||
|     public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; } | ||||
|  | ||||
|     public CommandParameterSymbol( | ||||
|         int order, | ||||
|         string? name, | ||||
|         ITypeSymbol? converterType, | ||||
|         IReadOnlyList<ITypeSymbol> validatorTypes) | ||||
|     { | ||||
|         public int Order { get; } | ||||
|  | ||||
|         public string? Name { get; } | ||||
|  | ||||
|         public ITypeSymbol? ConverterType { get; } | ||||
|  | ||||
|         public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; } | ||||
|  | ||||
|         public CommandParameterSymbol( | ||||
|             int order, | ||||
|             string? name, | ||||
|             ITypeSymbol? converterType, | ||||
|             IReadOnlyList<ITypeSymbol> validatorTypes) | ||||
|         { | ||||
|             Order = order; | ||||
|             Name = name; | ||||
|             ConverterType = converterType; | ||||
|             ValidatorTypes = validatorTypes; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal partial class CommandParameterSymbol | ||||
|     { | ||||
|         private static AttributeData? TryGetParameterAttribute(IPropertySymbol property) => | ||||
|             property | ||||
|                 .GetAttributes() | ||||
|                 .FirstOrDefault(a => a.AttributeClass.DisplayNameMatches(SymbolNames.CliFxCommandParameterAttribute)); | ||||
|  | ||||
|         private static CommandParameterSymbol FromAttribute(AttributeData attribute) | ||||
|         { | ||||
|             var order = (int) attribute | ||||
|                 .ConstructorArguments | ||||
|                 .Select(a => a.Value) | ||||
|                 .First()!; | ||||
|  | ||||
|             var name = attribute | ||||
|                 .NamedArguments | ||||
|                 .Where(a => a.Key == "Name") | ||||
|                 .Select(a => a.Value.Value) | ||||
|                 .FirstOrDefault() as string; | ||||
|  | ||||
|             var converter = attribute | ||||
|                 .NamedArguments | ||||
|                 .Where(a => a.Key == "Converter") | ||||
|                 .Select(a => a.Value.Value) | ||||
|                 .Cast<ITypeSymbol?>() | ||||
|                 .FirstOrDefault(); | ||||
|  | ||||
|             var validators = attribute | ||||
|                 .NamedArguments | ||||
|                 .Where(a => a.Key == "Validators") | ||||
|                 .SelectMany(a => a.Value.Values) | ||||
|                 .Select(c => c.Value) | ||||
|                 .Cast<ITypeSymbol>() | ||||
|                 .ToArray(); | ||||
|  | ||||
|             return new CommandParameterSymbol(order, name, converter, validators); | ||||
|         } | ||||
|  | ||||
|         public static CommandParameterSymbol? TryResolve(IPropertySymbol property) | ||||
|         { | ||||
|             var attribute = TryGetParameterAttribute(property); | ||||
|  | ||||
|             if (attribute is null) | ||||
|                 return null; | ||||
|  | ||||
|             return FromAttribute(attribute); | ||||
|         } | ||||
|  | ||||
|         public static bool IsParameterProperty(IPropertySymbol property) => | ||||
|             TryGetParameterAttribute(property) is not null; | ||||
|         Order = order; | ||||
|         Name = name; | ||||
|         ConverterType = converterType; | ||||
|         ValidatorTypes = validatorTypes; | ||||
|     } | ||||
| } | ||||
|  | ||||
| internal partial class CommandParameterSymbol | ||||
| { | ||||
|     private static AttributeData? TryGetParameterAttribute(IPropertySymbol property) => | ||||
|         property | ||||
|             .GetAttributes() | ||||
|             .FirstOrDefault(a => a.AttributeClass.DisplayNameMatches(SymbolNames.CliFxCommandParameterAttribute)); | ||||
|  | ||||
|     private static CommandParameterSymbol FromAttribute(AttributeData attribute) | ||||
|     { | ||||
|         var order = (int) attribute | ||||
|             .ConstructorArguments | ||||
|             .Select(a => a.Value) | ||||
|             .First()!; | ||||
|  | ||||
|         var name = attribute | ||||
|             .NamedArguments | ||||
|             .Where(a => a.Key == "Name") | ||||
|             .Select(a => a.Value.Value) | ||||
|             .FirstOrDefault() as string; | ||||
|  | ||||
|         var converter = attribute | ||||
|             .NamedArguments | ||||
|             .Where(a => a.Key == "Converter") | ||||
|             .Select(a => a.Value.Value) | ||||
|             .Cast<ITypeSymbol?>() | ||||
|             .FirstOrDefault(); | ||||
|  | ||||
|         var validators = attribute | ||||
|             .NamedArguments | ||||
|             .Where(a => a.Key == "Validators") | ||||
|             .SelectMany(a => a.Value.Values) | ||||
|             .Select(c => c.Value) | ||||
|             .Cast<ITypeSymbol>() | ||||
|             .ToArray(); | ||||
|  | ||||
|         return new CommandParameterSymbol(order, name, converter, validators); | ||||
|     } | ||||
|  | ||||
|     public static CommandParameterSymbol? TryResolve(IPropertySymbol property) | ||||
|     { | ||||
|         var attribute = TryGetParameterAttribute(property); | ||||
|  | ||||
|         if (attribute is null) | ||||
|             return null; | ||||
|  | ||||
|         return FromAttribute(attribute); | ||||
|     } | ||||
|  | ||||
|     public static bool IsParameterProperty(IPropertySymbol property) => | ||||
|         TryGetParameterAttribute(property) is not null; | ||||
| } | ||||
| @@ -1,15 +1,14 @@ | ||||
| namespace CliFx.Analyzers.ObjectModel | ||||
| namespace CliFx.Analyzers.ObjectModel; | ||||
|  | ||||
| internal static class SymbolNames | ||||
| { | ||||
|     internal static class SymbolNames | ||||
|     { | ||||
|         public const string CliFxCommandInterface = "CliFx.ICommand"; | ||||
|         public const string CliFxCommandAttribute = "CliFx.Attributes.CommandAttribute"; | ||||
|         public const string CliFxCommandParameterAttribute = "CliFx.Attributes.CommandParameterAttribute"; | ||||
|         public const string CliFxCommandOptionAttribute = "CliFx.Attributes.CommandOptionAttribute"; | ||||
|         public const string CliFxConsoleInterface = "CliFx.Infrastructure.IConsole"; | ||||
|         public const string CliFxBindingConverterInterface = "CliFx.Extensibility.IBindingConverter"; | ||||
|         public const string CliFxBindingConverterClass = "CliFx.Extensibility.BindingConverter<T>"; | ||||
|         public const string CliFxBindingValidatorInterface = "CliFx.Extensibility.IBindingValidator"; | ||||
|         public const string CliFxBindingValidatorClass = "CliFx.Extensibility.BindingValidator<T>"; | ||||
|     } | ||||
|     public const string CliFxCommandInterface = "CliFx.ICommand"; | ||||
|     public const string CliFxCommandAttribute = "CliFx.Attributes.CommandAttribute"; | ||||
|     public const string CliFxCommandParameterAttribute = "CliFx.Attributes.CommandParameterAttribute"; | ||||
|     public const string CliFxCommandOptionAttribute = "CliFx.Attributes.CommandOptionAttribute"; | ||||
|     public const string CliFxConsoleInterface = "CliFx.Infrastructure.IConsole"; | ||||
|     public const string CliFxBindingConverterInterface = "CliFx.Extensibility.IBindingConverter"; | ||||
|     public const string CliFxBindingConverterClass = "CliFx.Extensibility.BindingConverter<T>"; | ||||
|     public const string CliFxBindingValidatorInterface = "CliFx.Extensibility.IBindingValidator"; | ||||
|     public const string CliFxBindingValidatorClass = "CliFx.Extensibility.BindingValidator<T>"; | ||||
| } | ||||
| @@ -5,47 +5,46 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class OptionMustBeInsideCommandAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class OptionMustBeInsideCommandAnalyzer : AnalyzerBase | ||||
|     public OptionMustBeInsideCommandAnalyzer() | ||||
|         : base( | ||||
|             "Options must be defined inside commands", | ||||
|             $"This option must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`.") | ||||
|     { | ||||
|         public OptionMustBeInsideCommandAnalyzer() | ||||
|             : base( | ||||
|                 "Options must be defined inside commands", | ||||
|                 $"This option must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`.") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         if (property.ContainingType is null) | ||||
|             return; | ||||
|  | ||||
|         if (property.ContainingType.IsAbstract) | ||||
|             return; | ||||
|  | ||||
|         if (!CommandOptionSymbol.IsOptionProperty(property)) | ||||
|             return; | ||||
|  | ||||
|         var isInsideCommand = property | ||||
|             .ContainingType | ||||
|             .AllInterfaces | ||||
|             .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface)); | ||||
|  | ||||
|         if (!isInsideCommand) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             if (property.ContainingType is null) | ||||
|                 return; | ||||
|  | ||||
|             if (property.ContainingType.IsAbstract) | ||||
|                 return; | ||||
|  | ||||
|             if (!CommandOptionSymbol.IsOptionProperty(property)) | ||||
|                 return; | ||||
|  | ||||
|             var isInsideCommand = property | ||||
|                 .ContainingType | ||||
|                 .AllInterfaces | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface)); | ||||
|  | ||||
|             if (!isInsideCommand) | ||||
|             { | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|             context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -4,37 +4,36 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class OptionMustHaveNameOrShortNameAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class OptionMustHaveNameOrShortNameAnalyzer : AnalyzerBase | ||||
|     public OptionMustHaveNameOrShortNameAnalyzer() | ||||
|         : base( | ||||
|             "Options must have either a name or short name specified", | ||||
|             "This option must have either a name or short name specified.") | ||||
|     { | ||||
|         public OptionMustHaveNameOrShortNameAnalyzer() | ||||
|             : base( | ||||
|                 "Options must have either a name or short name specified", | ||||
|                 "This option must have either a name or short name specified.") | ||||
|         { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             var option = CommandOptionSymbol.TryResolve(property); | ||||
|             if (option is null) | ||||
|                 return; | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         var option = CommandOptionSymbol.TryResolve(property); | ||||
|         if (option is null) | ||||
|             return; | ||||
|  | ||||
|             if (string.IsNullOrWhiteSpace(option.Name) && option.ShortName is null) | ||||
|             { | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         if (string.IsNullOrWhiteSpace(option.Name) && option.ShortName is null) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|             context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -6,60 +6,59 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class OptionMustHaveUniqueNameAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class OptionMustHaveUniqueNameAnalyzer : AnalyzerBase | ||||
|     public OptionMustHaveUniqueNameAnalyzer() | ||||
|         : base( | ||||
|             "Options must have unique names", | ||||
|             "This option's name must be unique within the command (comparison IS NOT case sensitive).") | ||||
|     { | ||||
|         public OptionMustHaveUniqueNameAnalyzer() | ||||
|             : base( | ||||
|                 "Options must have unique names", | ||||
|                 "This option's name must be unique within the command (comparison IS NOT case sensitive).") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         if (property.ContainingType is null) | ||||
|             return; | ||||
|  | ||||
|         var option = CommandOptionSymbol.TryResolve(property); | ||||
|         if (option is null) | ||||
|             return; | ||||
|  | ||||
|         if (string.IsNullOrWhiteSpace(option.Name)) | ||||
|             return; | ||||
|  | ||||
|         var otherProperties = property | ||||
|             .ContainingType | ||||
|             .GetMembers() | ||||
|             .OfType<IPropertySymbol>() | ||||
|             .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|             .ToArray(); | ||||
|  | ||||
|         foreach (var otherProperty in otherProperties) | ||||
|         { | ||||
|         } | ||||
|             var otherOption = CommandOptionSymbol.TryResolve(otherProperty); | ||||
|             if (otherOption is null) | ||||
|                 continue; | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             if (property.ContainingType is null) | ||||
|                 return; | ||||
|             if (string.IsNullOrWhiteSpace(otherOption.Name)) | ||||
|                 continue; | ||||
|  | ||||
|             var option = CommandOptionSymbol.TryResolve(property); | ||||
|             if (option is null) | ||||
|                 return; | ||||
|  | ||||
|             if (string.IsNullOrWhiteSpace(option.Name)) | ||||
|                 return; | ||||
|  | ||||
|             var otherProperties = property | ||||
|                 .ContainingType | ||||
|                 .GetMembers() | ||||
|                 .OfType<IPropertySymbol>() | ||||
|                 .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             foreach (var otherProperty in otherProperties) | ||||
|             if (string.Equals(option.Name, otherOption.Name, StringComparison.OrdinalIgnoreCase)) | ||||
|             { | ||||
|                 var otherOption = CommandOptionSymbol.TryResolve(otherProperty); | ||||
|                 if (otherOption is null) | ||||
|                     continue; | ||||
|  | ||||
|                 if (string.IsNullOrWhiteSpace(otherOption.Name)) | ||||
|                     continue; | ||||
|  | ||||
|                 if (string.Equals(option.Name, otherOption.Name, StringComparison.OrdinalIgnoreCase)) | ||||
|                 { | ||||
|                     context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|                 } | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|         } | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -5,60 +5,59 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class OptionMustHaveUniqueShortNameAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class OptionMustHaveUniqueShortNameAnalyzer : AnalyzerBase | ||||
|     public OptionMustHaveUniqueShortNameAnalyzer() | ||||
|         : base( | ||||
|             "Options must have unique short names", | ||||
|             "This option's short name must be unique within the command (comparison IS case sensitive).") | ||||
|     { | ||||
|         public OptionMustHaveUniqueShortNameAnalyzer() | ||||
|             : base( | ||||
|                 "Options must have unique short names", | ||||
|                 "This option's short name must be unique within the command (comparison IS case sensitive).") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         if (property.ContainingType is null) | ||||
|             return; | ||||
|  | ||||
|         var option = CommandOptionSymbol.TryResolve(property); | ||||
|         if (option is null) | ||||
|             return; | ||||
|  | ||||
|         if (option.ShortName is null) | ||||
|             return; | ||||
|  | ||||
|         var otherProperties = property | ||||
|             .ContainingType | ||||
|             .GetMembers() | ||||
|             .OfType<IPropertySymbol>() | ||||
|             .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|             .ToArray(); | ||||
|  | ||||
|         foreach (var otherProperty in otherProperties) | ||||
|         { | ||||
|         } | ||||
|             var otherOption = CommandOptionSymbol.TryResolve(otherProperty); | ||||
|             if (otherOption is null) | ||||
|                 continue; | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             if (property.ContainingType is null) | ||||
|                 return; | ||||
|             if (otherOption.ShortName is null) | ||||
|                 continue; | ||||
|  | ||||
|             var option = CommandOptionSymbol.TryResolve(property); | ||||
|             if (option is null) | ||||
|                 return; | ||||
|  | ||||
|             if (option.ShortName is null) | ||||
|                 return; | ||||
|  | ||||
|             var otherProperties = property | ||||
|                 .ContainingType | ||||
|                 .GetMembers() | ||||
|                 .OfType<IPropertySymbol>() | ||||
|                 .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             foreach (var otherProperty in otherProperties) | ||||
|             if (option.ShortName == otherOption.ShortName) | ||||
|             { | ||||
|                 var otherOption = CommandOptionSymbol.TryResolve(otherProperty); | ||||
|                 if (otherOption is null) | ||||
|                     continue; | ||||
|  | ||||
|                 if (otherOption.ShortName is null) | ||||
|                     continue; | ||||
|  | ||||
|                 if (option.ShortName == otherOption.ShortName) | ||||
|                 { | ||||
|                     context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|                 } | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|         } | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -5,46 +5,45 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class OptionMustHaveValidConverterAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class OptionMustHaveValidConverterAnalyzer : AnalyzerBase | ||||
|     public OptionMustHaveValidConverterAnalyzer() | ||||
|         : base( | ||||
|             $"Option converters must derive from `{SymbolNames.CliFxBindingConverterClass}`", | ||||
|             $"Converter specified for this option must derive from `{SymbolNames.CliFxBindingConverterClass}`.") | ||||
|     { | ||||
|         public OptionMustHaveValidConverterAnalyzer() | ||||
|             : base( | ||||
|                 $"Option converters must derive from `{SymbolNames.CliFxBindingConverterClass}`", | ||||
|                 $"Converter specified for this option must derive from `{SymbolNames.CliFxBindingConverterClass}`.") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         var option = CommandOptionSymbol.TryResolve(property); | ||||
|         if (option is null) | ||||
|             return; | ||||
|  | ||||
|         if (option.ConverterType is null) | ||||
|             return; | ||||
|  | ||||
|         // We check against an internal interface because checking against a generic class is a pain | ||||
|         var converterImplementsInterface = option | ||||
|             .ConverterType | ||||
|             .AllInterfaces | ||||
|             .Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingConverterInterface)); | ||||
|  | ||||
|         if (!converterImplementsInterface) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             var option = CommandOptionSymbol.TryResolve(property); | ||||
|             if (option is null) | ||||
|                 return; | ||||
|  | ||||
|             if (option.ConverterType is null) | ||||
|                 return; | ||||
|  | ||||
|             // We check against an internal interface because checking against a generic class is a pain | ||||
|             var converterImplementsInterface = option | ||||
|                 .ConverterType | ||||
|                 .AllInterfaces | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingConverterInterface)); | ||||
|  | ||||
|             if (!converterImplementsInterface) | ||||
|             { | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|             context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -4,40 +4,39 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class OptionMustHaveValidNameAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class OptionMustHaveValidNameAnalyzer : AnalyzerBase | ||||
|     public OptionMustHaveValidNameAnalyzer() | ||||
|         : base( | ||||
|             "Options must have valid names", | ||||
|             "This option's name must be at least 2 characters long and must start with a letter.") | ||||
|     { | ||||
|         public OptionMustHaveValidNameAnalyzer() | ||||
|             : base( | ||||
|                 "Options must have valid names", | ||||
|                 "This option's name must be at least 2 characters long and must start with a letter.") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         var option = CommandOptionSymbol.TryResolve(property); | ||||
|         if (option is null) | ||||
|             return; | ||||
|  | ||||
|         if (string.IsNullOrWhiteSpace(option.Name)) | ||||
|             return; | ||||
|  | ||||
|         if (option.Name.Length < 2 || !char.IsLetter(option.Name[0])) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             var option = CommandOptionSymbol.TryResolve(property); | ||||
|             if (option is null) | ||||
|                 return; | ||||
|  | ||||
|             if (string.IsNullOrWhiteSpace(option.Name)) | ||||
|                 return; | ||||
|  | ||||
|             if (option.Name.Length < 2 || !char.IsLetter(option.Name[0])) | ||||
|             { | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|             context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -4,40 +4,39 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class OptionMustHaveValidShortNameAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class OptionMustHaveValidShortNameAnalyzer : AnalyzerBase | ||||
|     public OptionMustHaveValidShortNameAnalyzer() | ||||
|         : base( | ||||
|             "Option short names must be letter characters", | ||||
|             "This option's short name must be a single letter character.") | ||||
|     { | ||||
|         public OptionMustHaveValidShortNameAnalyzer() | ||||
|             : base( | ||||
|                 "Option short names must be letter characters", | ||||
|                 "This option's short name must be a single letter character.") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         var option = CommandOptionSymbol.TryResolve(property); | ||||
|         if (option is null) | ||||
|             return; | ||||
|  | ||||
|         if (option.ShortName is null) | ||||
|             return; | ||||
|  | ||||
|         if (!char.IsLetter(option.ShortName.Value)) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             var option = CommandOptionSymbol.TryResolve(property); | ||||
|             if (option is null) | ||||
|                 return; | ||||
|  | ||||
|             if (option.ShortName is null) | ||||
|                 return; | ||||
|  | ||||
|             if (!char.IsLetter(option.ShortName.Value)) | ||||
|             { | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|             context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -5,48 +5,47 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class OptionMustHaveValidValidatorsAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class OptionMustHaveValidValidatorsAnalyzer : AnalyzerBase | ||||
|     public OptionMustHaveValidValidatorsAnalyzer() | ||||
|         : base( | ||||
|             $"Option validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`", | ||||
|             $"All validators specified for this option must derive from `{SymbolNames.CliFxBindingValidatorClass}`.") | ||||
|     { | ||||
|         public OptionMustHaveValidValidatorsAnalyzer() | ||||
|             : base( | ||||
|                 $"Option validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`", | ||||
|                 $"All validators specified for this option must derive from `{SymbolNames.CliFxBindingValidatorClass}`.") | ||||
|         { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             var option = CommandOptionSymbol.TryResolve(property); | ||||
|             if (option is null) | ||||
|                 return; | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         var option = CommandOptionSymbol.TryResolve(property); | ||||
|         if (option is null) | ||||
|             return; | ||||
|  | ||||
|             foreach (var validatorType in option.ValidatorTypes) | ||||
|         foreach (var validatorType in option.ValidatorTypes) | ||||
|         { | ||||
|             // We check against an internal interface because checking against a generic class is a pain | ||||
|             var validatorImplementsInterface = validatorType | ||||
|                 .AllInterfaces | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingValidatorInterface)); | ||||
|  | ||||
|             if (!validatorImplementsInterface) | ||||
|             { | ||||
|                 // We check against an internal interface because checking against a generic class is a pain | ||||
|                 var validatorImplementsInterface = validatorType | ||||
|                     .AllInterfaces | ||||
|                     .Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingValidatorInterface)); | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|  | ||||
|                 if (!validatorImplementsInterface) | ||||
|                 { | ||||
|                     context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|  | ||||
|                     // No need to report multiple identical diagnostics on the same node | ||||
|                     break; | ||||
|                 } | ||||
|                 // No need to report multiple identical diagnostics on the same node | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|         } | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -5,47 +5,46 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class ParameterMustBeInsideCommandAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class ParameterMustBeInsideCommandAnalyzer : AnalyzerBase | ||||
|     public ParameterMustBeInsideCommandAnalyzer() | ||||
|         : base( | ||||
|             "Parameters must be defined inside commands", | ||||
|             $"This parameter must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`.") | ||||
|     { | ||||
|         public ParameterMustBeInsideCommandAnalyzer() | ||||
|             : base( | ||||
|                 "Parameters must be defined inside commands", | ||||
|                 $"This parameter must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`.") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         if (property.ContainingType is null) | ||||
|             return; | ||||
|  | ||||
|         if (property.ContainingType.IsAbstract) | ||||
|             return; | ||||
|  | ||||
|         if (!CommandParameterSymbol.IsParameterProperty(property)) | ||||
|             return; | ||||
|  | ||||
|         var isInsideCommand = property | ||||
|             .ContainingType | ||||
|             .AllInterfaces | ||||
|             .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface)); | ||||
|  | ||||
|         if (!isInsideCommand) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             if (property.ContainingType is null) | ||||
|                 return; | ||||
|  | ||||
|             if (property.ContainingType.IsAbstract) | ||||
|                 return; | ||||
|  | ||||
|             if (!CommandParameterSymbol.IsParameterProperty(property)) | ||||
|                 return; | ||||
|  | ||||
|             var isInsideCommand = property | ||||
|                 .ContainingType | ||||
|                 .AllInterfaces | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxCommandInterface)); | ||||
|  | ||||
|             if (!isInsideCommand) | ||||
|             { | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|             context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -5,64 +5,63 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase | ||||
|     public ParameterMustBeLastIfNonScalarAnalyzer() | ||||
|         : base( | ||||
|             "Parameters of non-scalar types must be last in order", | ||||
|             "This parameter has a non-scalar type so it must be last in order (its order must be highest within the command).") | ||||
|     { | ||||
|         public ParameterMustBeLastIfNonScalarAnalyzer() | ||||
|             : base( | ||||
|                 "Parameters of non-scalar types must be last in order", | ||||
|                 "This parameter has a non-scalar type so it must be last in order (its order must be highest within the command).") | ||||
|     } | ||||
|  | ||||
|     private static bool IsScalar(ITypeSymbol type) => | ||||
|         type.DisplayNameMatches("string") || | ||||
|         type.DisplayNameMatches("System.String") || | ||||
|         !type.AllInterfaces | ||||
|             .Select(i => i.ConstructedFrom) | ||||
|             .Any(s => s.DisplayNameMatches("System.Collections.Generic.IEnumerable<T>")); | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         if (property.ContainingType is null) | ||||
|             return; | ||||
|  | ||||
|         if (IsScalar(property.Type)) | ||||
|             return; | ||||
|  | ||||
|         var parameter = CommandParameterSymbol.TryResolve(property); | ||||
|         if (parameter is null) | ||||
|             return; | ||||
|  | ||||
|         var otherProperties = property | ||||
|             .ContainingType | ||||
|             .GetMembers() | ||||
|             .OfType<IPropertySymbol>() | ||||
|             .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|             .ToArray(); | ||||
|  | ||||
|         foreach (var otherProperty in otherProperties) | ||||
|         { | ||||
|         } | ||||
|             var otherParameter = CommandParameterSymbol.TryResolve(otherProperty); | ||||
|             if (otherParameter is null) | ||||
|                 continue; | ||||
|  | ||||
|         private static bool IsScalar(ITypeSymbol type) => | ||||
|             type.DisplayNameMatches("string") || | ||||
|             type.DisplayNameMatches("System.String") || | ||||
|             !type.AllInterfaces | ||||
|                 .Select(i => i.ConstructedFrom) | ||||
|                 .Any(s => s.DisplayNameMatches("System.Collections.Generic.IEnumerable<T>")); | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             if (property.ContainingType is null) | ||||
|                 return; | ||||
|  | ||||
|             if (IsScalar(property.Type)) | ||||
|                 return; | ||||
|  | ||||
|             var parameter = CommandParameterSymbol.TryResolve(property); | ||||
|             if (parameter is null) | ||||
|                 return; | ||||
|  | ||||
|             var otherProperties = property | ||||
|                 .ContainingType | ||||
|                 .GetMembers() | ||||
|                 .OfType<IPropertySymbol>() | ||||
|                 .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             foreach (var otherProperty in otherProperties) | ||||
|             if (otherParameter.Order > parameter.Order) | ||||
|             { | ||||
|                 var otherParameter = CommandParameterSymbol.TryResolve(otherProperty); | ||||
|                 if (otherParameter is null) | ||||
|                     continue; | ||||
|  | ||||
|                 if (otherParameter.Order > parameter.Order) | ||||
|                 { | ||||
|                     context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|                 } | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|         } | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -5,62 +5,61 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class ParameterMustBeSingleIfNonScalarAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class ParameterMustBeSingleIfNonScalarAnalyzer : AnalyzerBase | ||||
|     public ParameterMustBeSingleIfNonScalarAnalyzer() | ||||
|         : base( | ||||
|             "Parameters of non-scalar types are limited to one per command", | ||||
|             "This parameter has a non-scalar type so it must be the only such parameter in the command.") | ||||
|     { | ||||
|         public ParameterMustBeSingleIfNonScalarAnalyzer() | ||||
|             : base( | ||||
|                 "Parameters of non-scalar types are limited to one per command", | ||||
|                 "This parameter has a non-scalar type so it must be the only such parameter in the command.") | ||||
|     } | ||||
|  | ||||
|     private static bool IsScalar(ITypeSymbol type) => | ||||
|         type.DisplayNameMatches("string") || | ||||
|         type.DisplayNameMatches("System.String") || | ||||
|         !type.AllInterfaces | ||||
|             .Select(i => i.ConstructedFrom) | ||||
|             .Any(s => s.DisplayNameMatches("System.Collections.Generic.IEnumerable<T>")); | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         if (property.ContainingType is null) | ||||
|             return; | ||||
|  | ||||
|         if (!CommandParameterSymbol.IsParameterProperty(property)) | ||||
|             return; | ||||
|  | ||||
|         if (IsScalar(property.Type)) | ||||
|             return; | ||||
|  | ||||
|         var otherProperties = property | ||||
|             .ContainingType | ||||
|             .GetMembers() | ||||
|             .OfType<IPropertySymbol>() | ||||
|             .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|             .ToArray(); | ||||
|  | ||||
|         foreach (var otherProperty in otherProperties) | ||||
|         { | ||||
|         } | ||||
|             if (!CommandParameterSymbol.IsParameterProperty(otherProperty)) | ||||
|                 continue; | ||||
|  | ||||
|         private static bool IsScalar(ITypeSymbol type) => | ||||
|             type.DisplayNameMatches("string") || | ||||
|             type.DisplayNameMatches("System.String") || | ||||
|             !type.AllInterfaces | ||||
|                 .Select(i => i.ConstructedFrom) | ||||
|                 .Any(s => s.DisplayNameMatches("System.Collections.Generic.IEnumerable<T>")); | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             if (property.ContainingType is null) | ||||
|                 return; | ||||
|  | ||||
|             if (!CommandParameterSymbol.IsParameterProperty(property)) | ||||
|                 return; | ||||
|  | ||||
|             if (IsScalar(property.Type)) | ||||
|                 return; | ||||
|  | ||||
|             var otherProperties = property | ||||
|                 .ContainingType | ||||
|                 .GetMembers() | ||||
|                 .OfType<IPropertySymbol>() | ||||
|                 .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             foreach (var otherProperty in otherProperties) | ||||
|             if (!IsScalar(otherProperty.Type)) | ||||
|             { | ||||
|                 if (!CommandParameterSymbol.IsParameterProperty(otherProperty)) | ||||
|                     continue; | ||||
|  | ||||
|                 if (!IsScalar(otherProperty.Type)) | ||||
|                 { | ||||
|                     context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|                 } | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|         } | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -6,60 +6,59 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class ParameterMustHaveUniqueNameAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class ParameterMustHaveUniqueNameAnalyzer : AnalyzerBase | ||||
|     public ParameterMustHaveUniqueNameAnalyzer() | ||||
|         : base( | ||||
|             "Parameters must have unique names", | ||||
|             "This parameter's name must be unique within the command (comparison IS NOT case sensitive).") | ||||
|     { | ||||
|         public ParameterMustHaveUniqueNameAnalyzer() | ||||
|             : base( | ||||
|                 "Parameters must have unique names", | ||||
|                 "This parameter's name must be unique within the command (comparison IS NOT case sensitive).") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         if (property.ContainingType is null) | ||||
|             return; | ||||
|  | ||||
|         var parameter = CommandParameterSymbol.TryResolve(property); | ||||
|         if (parameter is null) | ||||
|             return; | ||||
|  | ||||
|         if (string.IsNullOrWhiteSpace(parameter.Name)) | ||||
|             return; | ||||
|  | ||||
|         var otherProperties = property | ||||
|             .ContainingType | ||||
|             .GetMembers() | ||||
|             .OfType<IPropertySymbol>() | ||||
|             .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|             .ToArray(); | ||||
|  | ||||
|         foreach (var otherProperty in otherProperties) | ||||
|         { | ||||
|         } | ||||
|             var otherParameter = CommandParameterSymbol.TryResolve(otherProperty); | ||||
|             if (otherParameter is null) | ||||
|                 continue; | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             if (property.ContainingType is null) | ||||
|                 return; | ||||
|             if (string.IsNullOrWhiteSpace(otherParameter.Name)) | ||||
|                 continue; | ||||
|  | ||||
|             var parameter = CommandParameterSymbol.TryResolve(property); | ||||
|             if (parameter is null) | ||||
|                 return; | ||||
|  | ||||
|             if (string.IsNullOrWhiteSpace(parameter.Name)) | ||||
|                 return; | ||||
|  | ||||
|             var otherProperties = property | ||||
|                 .ContainingType | ||||
|                 .GetMembers() | ||||
|                 .OfType<IPropertySymbol>() | ||||
|                 .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             foreach (var otherProperty in otherProperties) | ||||
|             if (string.Equals(parameter.Name, otherParameter.Name, StringComparison.OrdinalIgnoreCase)) | ||||
|             { | ||||
|                 var otherParameter = CommandParameterSymbol.TryResolve(otherProperty); | ||||
|                 if (otherParameter is null) | ||||
|                     continue; | ||||
|  | ||||
|                 if (string.IsNullOrWhiteSpace(otherParameter.Name)) | ||||
|                     continue; | ||||
|  | ||||
|                 if (string.Equals(parameter.Name, otherParameter.Name, StringComparison.OrdinalIgnoreCase)) | ||||
|                 { | ||||
|                     context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|                 } | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|         } | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -5,54 +5,53 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class ParameterMustHaveUniqueOrderAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class ParameterMustHaveUniqueOrderAnalyzer : AnalyzerBase | ||||
|     public ParameterMustHaveUniqueOrderAnalyzer() | ||||
|         : base( | ||||
|             "Parameters must have unique order", | ||||
|             "This parameter's order must be unique within the command.") | ||||
|     { | ||||
|         public ParameterMustHaveUniqueOrderAnalyzer() | ||||
|             : base( | ||||
|                 "Parameters must have unique order", | ||||
|                 "This parameter's order must be unique within the command.") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         if (property.ContainingType is null) | ||||
|             return; | ||||
|  | ||||
|         var parameter = CommandParameterSymbol.TryResolve(property); | ||||
|         if (parameter is null) | ||||
|             return; | ||||
|  | ||||
|         var otherProperties = property | ||||
|             .ContainingType | ||||
|             .GetMembers() | ||||
|             .OfType<IPropertySymbol>() | ||||
|             .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|             .ToArray(); | ||||
|  | ||||
|         foreach (var otherProperty in otherProperties) | ||||
|         { | ||||
|         } | ||||
|             var otherParameter = CommandParameterSymbol.TryResolve(otherProperty); | ||||
|             if (otherParameter is null) | ||||
|                 continue; | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             if (property.ContainingType is null) | ||||
|                 return; | ||||
|  | ||||
|             var parameter = CommandParameterSymbol.TryResolve(property); | ||||
|             if (parameter is null) | ||||
|                 return; | ||||
|  | ||||
|             var otherProperties = property | ||||
|                 .ContainingType | ||||
|                 .GetMembers() | ||||
|                 .OfType<IPropertySymbol>() | ||||
|                 .Where(m => !m.Equals(property, SymbolEqualityComparer.Default)) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             foreach (var otherProperty in otherProperties) | ||||
|             if (parameter.Order == otherParameter.Order) | ||||
|             { | ||||
|                 var otherParameter = CommandParameterSymbol.TryResolve(otherProperty); | ||||
|                 if (otherParameter is null) | ||||
|                     continue; | ||||
|  | ||||
|                 if (parameter.Order == otherParameter.Order) | ||||
|                 { | ||||
|                     context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|                 } | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|         } | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -5,46 +5,45 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class ParameterMustHaveValidConverterAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class ParameterMustHaveValidConverterAnalyzer : AnalyzerBase | ||||
|     public ParameterMustHaveValidConverterAnalyzer() | ||||
|         : base( | ||||
|             $"Parameter converters must derive from `{SymbolNames.CliFxBindingConverterClass}`", | ||||
|             $"Converter specified for this parameter must derive from `{SymbolNames.CliFxBindingConverterClass}`.") | ||||
|     { | ||||
|         public ParameterMustHaveValidConverterAnalyzer() | ||||
|             : base( | ||||
|                 $"Parameter converters must derive from `{SymbolNames.CliFxBindingConverterClass}`", | ||||
|                 $"Converter specified for this parameter must derive from `{SymbolNames.CliFxBindingConverterClass}`.") | ||||
|     } | ||||
|  | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         var parameter = CommandParameterSymbol.TryResolve(property); | ||||
|         if (parameter is null) | ||||
|             return; | ||||
|  | ||||
|         if (parameter.ConverterType is null) | ||||
|             return; | ||||
|  | ||||
|         // We check against an internal interface because checking against a generic class is a pain | ||||
|         var converterImplementsInterface = parameter | ||||
|             .ConverterType | ||||
|             .AllInterfaces | ||||
|             .Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingConverterInterface)); | ||||
|  | ||||
|         if (!converterImplementsInterface) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             var parameter = CommandParameterSymbol.TryResolve(property); | ||||
|             if (parameter is null) | ||||
|                 return; | ||||
|  | ||||
|             if (parameter.ConverterType is null) | ||||
|                 return; | ||||
|  | ||||
|             // We check against an internal interface because checking against a generic class is a pain | ||||
|             var converterImplementsInterface = parameter | ||||
|                 .ConverterType | ||||
|                 .AllInterfaces | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingConverterInterface)); | ||||
|  | ||||
|             if (!converterImplementsInterface) | ||||
|             { | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|             context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -5,48 +5,47 @@ using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class ParameterMustHaveValidValidatorsAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class ParameterMustHaveValidValidatorsAnalyzer : AnalyzerBase | ||||
|     public ParameterMustHaveValidValidatorsAnalyzer() | ||||
|         : base( | ||||
|             $"Parameter validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`", | ||||
|             $"All validators specified for this parameter must derive from `{SymbolNames.CliFxBindingValidatorClass}`.") | ||||
|     { | ||||
|         public ParameterMustHaveValidValidatorsAnalyzer() | ||||
|             : base( | ||||
|                 $"Parameter validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`", | ||||
|                 $"All validators specified for this parameter must derive from `{SymbolNames.CliFxBindingValidatorClass}`.") | ||||
|         { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         private void Analyze( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             PropertyDeclarationSyntax propertyDeclaration, | ||||
|             IPropertySymbol property) | ||||
|         { | ||||
|             var parameter = CommandParameterSymbol.TryResolve(property); | ||||
|             if (parameter is null) | ||||
|                 return; | ||||
|     private void Analyze( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         PropertyDeclarationSyntax propertyDeclaration, | ||||
|         IPropertySymbol property) | ||||
|     { | ||||
|         var parameter = CommandParameterSymbol.TryResolve(property); | ||||
|         if (parameter is null) | ||||
|             return; | ||||
|  | ||||
|             foreach (var validatorType in parameter.ValidatorTypes) | ||||
|         foreach (var validatorType in parameter.ValidatorTypes) | ||||
|         { | ||||
|             // We check against an internal interface because checking against a generic class is a pain | ||||
|             var validatorImplementsInterface = validatorType | ||||
|                 .AllInterfaces | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingValidatorInterface)); | ||||
|  | ||||
|             if (!validatorImplementsInterface) | ||||
|             { | ||||
|                 // We check against an internal interface because checking against a generic class is a pain | ||||
|                 var validatorImplementsInterface = validatorType | ||||
|                     .AllInterfaces | ||||
|                     .Any(s => s.DisplayNameMatches(SymbolNames.CliFxBindingValidatorInterface)); | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|  | ||||
|                 if (!validatorImplementsInterface) | ||||
|                 { | ||||
|                     context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); | ||||
|  | ||||
|                     // No need to report multiple identical diagnostics on the same node | ||||
|                     break; | ||||
|                 } | ||||
|                 // No need to report multiple identical diagnostics on the same node | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.HandlePropertyDeclaration(Analyze); | ||||
|         } | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.HandlePropertyDeclaration(Analyze); | ||||
|     } | ||||
| } | ||||
| @@ -6,73 +6,72 @@ using Microsoft.CodeAnalysis.CSharp; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers | ||||
| namespace CliFx.Analyzers; | ||||
|  | ||||
| [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
| public class SystemConsoleShouldBeAvoidedAnalyzer : AnalyzerBase | ||||
| { | ||||
|     [DiagnosticAnalyzer(LanguageNames.CSharp)] | ||||
|     public class SystemConsoleShouldBeAvoidedAnalyzer : AnalyzerBase | ||||
|     public SystemConsoleShouldBeAvoidedAnalyzer() | ||||
|         : base( | ||||
|             $"Avoid calling `System.Console` where `{SymbolNames.CliFxConsoleInterface}` is available", | ||||
|             $"Use the provided `{SymbolNames.CliFxConsoleInterface}` abstraction instead of `System.Console` to ensure that the command can be tested in isolation.", | ||||
|             DiagnosticSeverity.Warning) | ||||
|     { | ||||
|         public SystemConsoleShouldBeAvoidedAnalyzer() | ||||
|             : base( | ||||
|                 $"Avoid calling `System.Console` where `{SymbolNames.CliFxConsoleInterface}` is available", | ||||
|                 $"Use the provided `{SymbolNames.CliFxConsoleInterface}` abstraction instead of `System.Console` to ensure that the command can be tested in isolation.", | ||||
|                 DiagnosticSeverity.Warning) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         private MemberAccessExpressionSyntax? TryGetSystemConsoleMemberAccess( | ||||
|             SyntaxNodeAnalysisContext context, | ||||
|             SyntaxNode node) | ||||
|         { | ||||
|             var currentNode = node; | ||||
|     private MemberAccessExpressionSyntax? TryGetSystemConsoleMemberAccess( | ||||
|         SyntaxNodeAnalysisContext context, | ||||
|         SyntaxNode node) | ||||
|     { | ||||
|         var currentNode = node; | ||||
|  | ||||
|             while (currentNode is MemberAccessExpressionSyntax memberAccess) | ||||
|         while (currentNode is MemberAccessExpressionSyntax memberAccess) | ||||
|         { | ||||
|             var member = context.SemanticModel.GetSymbolInfo(memberAccess).Symbol; | ||||
|  | ||||
|             if (member?.ContainingType?.DisplayNameMatches("System.Console") == true) | ||||
|             { | ||||
|                 var member = context.SemanticModel.GetSymbolInfo(memberAccess).Symbol; | ||||
|  | ||||
|                 if (member?.ContainingType?.DisplayNameMatches("System.Console") == true) | ||||
|                 { | ||||
|                     return memberAccess; | ||||
|                 } | ||||
|  | ||||
|                 // Get inner expression, which may be another member access expression. | ||||
|                 // Example: System.Console.Error | ||||
|                 //          ~~~~~~~~~~~~~~          <- inner member access expression | ||||
|                 //          --------------------    <- outer member access expression | ||||
|                 currentNode = memberAccess.Expression; | ||||
|                 return memberAccess; | ||||
|             } | ||||
|  | ||||
|             return null; | ||||
|             // Get inner expression, which may be another member access expression. | ||||
|             // Example: System.Console.Error | ||||
|             //          ~~~~~~~~~~~~~~          <- inner member access expression | ||||
|             //          --------------------    <- outer member access expression | ||||
|             currentNode = memberAccess.Expression; | ||||
|         } | ||||
|  | ||||
|         private void Analyze(SyntaxNodeAnalysisContext context) | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     private void Analyze(SyntaxNodeAnalysisContext context) | ||||
|     { | ||||
|         // Try to get a member access on System.Console in the current expression, | ||||
|         // or in any of its inner expressions. | ||||
|         var systemConsoleMemberAccess = TryGetSystemConsoleMemberAccess(context, context.Node); | ||||
|         if (systemConsoleMemberAccess is null) | ||||
|             return; | ||||
|  | ||||
|         // Check if IConsole is available in scope as an alternative to System.Console | ||||
|         var isConsoleInterfaceAvailable = context | ||||
|             .Node | ||||
|             .Ancestors() | ||||
|             .OfType<MethodDeclarationSyntax>() | ||||
|             .SelectMany(m => m.ParameterList.Parameters) | ||||
|             .Select(p => p.Type) | ||||
|             .Select(t => context.SemanticModel.GetSymbolInfo(t).Symbol) | ||||
|             .Where(s => s is not null) | ||||
|             .Any(s => s.DisplayNameMatches(SymbolNames.CliFxConsoleInterface)); | ||||
|  | ||||
|         if (isConsoleInterfaceAvailable) | ||||
|         { | ||||
|             // Try to get a member access on System.Console in the current expression, | ||||
|             // or in any of its inner expressions. | ||||
|             var systemConsoleMemberAccess = TryGetSystemConsoleMemberAccess(context, context.Node); | ||||
|             if (systemConsoleMemberAccess is null) | ||||
|                 return; | ||||
|  | ||||
|             // Check if IConsole is available in scope as an alternative to System.Console | ||||
|             var isConsoleInterfaceAvailable = context | ||||
|                 .Node | ||||
|                 .Ancestors() | ||||
|                 .OfType<MethodDeclarationSyntax>() | ||||
|                 .SelectMany(m => m.ParameterList.Parameters) | ||||
|                 .Select(p => p.Type) | ||||
|                 .Select(t => context.SemanticModel.GetSymbolInfo(t).Symbol) | ||||
|                 .Where(s => s is not null) | ||||
|                 .Any(s => s.DisplayNameMatches(SymbolNames.CliFxConsoleInterface)); | ||||
|  | ||||
|             if (isConsoleInterfaceAvailable) | ||||
|             { | ||||
|                 context.ReportDiagnostic(CreateDiagnostic(systemConsoleMemberAccess.GetLocation())); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public override void Initialize(AnalysisContext context) | ||||
|         { | ||||
|             base.Initialize(context); | ||||
|             context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.SimpleMemberAccessExpression); | ||||
|             context.ReportDiagnostic(CreateDiagnostic(systemConsoleMemberAccess.GetLocation())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void Initialize(AnalysisContext context) | ||||
|     { | ||||
|         base.Initialize(context); | ||||
|         context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.SimpleMemberAccessExpression); | ||||
|     } | ||||
| } | ||||
| @@ -4,50 +4,49 @@ using Microsoft.CodeAnalysis.CSharp; | ||||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||||
| using Microsoft.CodeAnalysis.Diagnostics; | ||||
|  | ||||
| namespace CliFx.Analyzers.Utils.Extensions | ||||
| namespace CliFx.Analyzers.Utils.Extensions; | ||||
|  | ||||
| internal static class RoslynExtensions | ||||
| { | ||||
|     internal static class RoslynExtensions | ||||
|     public static bool DisplayNameMatches(this ISymbol symbol, string name) => | ||||
|         string.Equals( | ||||
|             // Fully qualified name, without `global::` | ||||
|             symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), | ||||
|             name, | ||||
|             StringComparison.Ordinal | ||||
|         ); | ||||
|  | ||||
|     public static void HandleClassDeclaration( | ||||
|         this AnalysisContext analysisContext, | ||||
|         Action<SyntaxNodeAnalysisContext, ClassDeclarationSyntax, ITypeSymbol> analyze) | ||||
|     { | ||||
|         public static bool DisplayNameMatches(this ISymbol symbol, string name) => | ||||
|             string.Equals( | ||||
|                 // Fully qualified name, without `global::` | ||||
|                 symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), | ||||
|                 name, | ||||
|                 StringComparison.Ordinal | ||||
|             ); | ||||
|  | ||||
|         public static void HandleClassDeclaration( | ||||
|             this AnalysisContext analysisContext, | ||||
|             Action<SyntaxNodeAnalysisContext, ClassDeclarationSyntax, ITypeSymbol> analyze) | ||||
|         analysisContext.RegisterSyntaxNodeAction(ctx => | ||||
|         { | ||||
|             analysisContext.RegisterSyntaxNodeAction(ctx => | ||||
|             { | ||||
|                 if (ctx.Node is not ClassDeclarationSyntax classDeclaration) | ||||
|                     return; | ||||
|             if (ctx.Node is not ClassDeclarationSyntax classDeclaration) | ||||
|                 return; | ||||
|  | ||||
|                 var type = ctx.SemanticModel.GetDeclaredSymbol(classDeclaration); | ||||
|                 if (type is null) | ||||
|                     return; | ||||
|             var type = ctx.SemanticModel.GetDeclaredSymbol(classDeclaration); | ||||
|             if (type is null) | ||||
|                 return; | ||||
|  | ||||
|                 analyze(ctx, classDeclaration, type); | ||||
|             }, SyntaxKind.ClassDeclaration); | ||||
|         } | ||||
|             analyze(ctx, classDeclaration, type); | ||||
|         }, SyntaxKind.ClassDeclaration); | ||||
|     } | ||||
|  | ||||
|         public static void HandlePropertyDeclaration( | ||||
|             this AnalysisContext analysisContext, | ||||
|             Action<SyntaxNodeAnalysisContext, PropertyDeclarationSyntax, IPropertySymbol> analyze) | ||||
|     public static void HandlePropertyDeclaration( | ||||
|         this AnalysisContext analysisContext, | ||||
|         Action<SyntaxNodeAnalysisContext, PropertyDeclarationSyntax, IPropertySymbol> analyze) | ||||
|     { | ||||
|         analysisContext.RegisterSyntaxNodeAction(ctx => | ||||
|         { | ||||
|             analysisContext.RegisterSyntaxNodeAction(ctx => | ||||
|             { | ||||
|                 if (ctx.Node is not PropertyDeclarationSyntax propertyDeclaration) | ||||
|                     return; | ||||
|             if (ctx.Node is not PropertyDeclarationSyntax propertyDeclaration) | ||||
|                 return; | ||||
|  | ||||
|                 var property = ctx.SemanticModel.GetDeclaredSymbol(propertyDeclaration); | ||||
|                 if (property is null) | ||||
|                     return; | ||||
|             var property = ctx.SemanticModel.GetDeclaredSymbol(propertyDeclaration); | ||||
|             if (property is null) | ||||
|                 return; | ||||
|  | ||||
|                 analyze(ctx, propertyDeclaration, property); | ||||
|             }, SyntaxKind.PropertyDeclaration); | ||||
|         } | ||||
|             analyze(ctx, propertyDeclaration, property); | ||||
|         }, SyntaxKind.PropertyDeclaration); | ||||
|     } | ||||
| } | ||||
| @@ -1,18 +1,17 @@ | ||||
| using System; | ||||
|  | ||||
| namespace CliFx.Analyzers.Utils.Extensions | ||||
| { | ||||
|     internal static class StringExtensions | ||||
|     { | ||||
|         public static string TrimEnd( | ||||
|             this string str, | ||||
|             string sub, | ||||
|             StringComparison comparison = StringComparison.Ordinal) | ||||
|         { | ||||
|             while (str.EndsWith(sub, comparison)) | ||||
|                 str = str.Substring(0, str.Length - sub.Length); | ||||
| namespace CliFx.Analyzers.Utils.Extensions; | ||||
|  | ||||
|             return str; | ||||
|         } | ||||
| internal static class StringExtensions | ||||
| { | ||||
|     public static string TrimEnd( | ||||
|         this string str, | ||||
|         string sub, | ||||
|         StringComparison comparison = StringComparison.Ordinal) | ||||
|     { | ||||
|         while (str.EndsWith(sub, comparison)) | ||||
|             str = str.Substring(0, str.Length - sub.Length); | ||||
|  | ||||
|         return str; | ||||
|     } | ||||
| } | ||||
| @@ -4,30 +4,29 @@ using BenchmarkDotNet.Attributes; | ||||
| using CliFx.Attributes; | ||||
| using CliFx.Infrastructure; | ||||
|  | ||||
| namespace CliFx.Benchmarks | ||||
| namespace CliFx.Benchmarks; | ||||
|  | ||||
| public partial class Benchmarks | ||||
| { | ||||
|     public partial class Benchmarks | ||||
|     [Command] | ||||
|     public class CliFxCommand : ICommand | ||||
|     { | ||||
|         [Command] | ||||
|         public class CliFxCommand : ICommand | ||||
|         { | ||||
|             [CommandOption("str", 's')] | ||||
|             public string? StrOption { get; set; } | ||||
|         [CommandOption("str", 's')] | ||||
|         public string? StrOption { get; set; } | ||||
|  | ||||
|             [CommandOption("int", 'i')] | ||||
|             public int IntOption { get; set; } | ||||
|         [CommandOption("int", 'i')] | ||||
|         public int IntOption { get; set; } | ||||
|  | ||||
|             [CommandOption("bool", 'b')] | ||||
|             public bool BoolOption { get; set; } | ||||
|         [CommandOption("bool", 'b')] | ||||
|         public bool BoolOption { get; set; } | ||||
|  | ||||
|             public ValueTask ExecuteAsync(IConsole console) => default; | ||||
|         } | ||||
|  | ||||
|         [Benchmark(Description = "CliFx", Baseline = true)] | ||||
|         public async ValueTask<int> ExecuteWithCliFx() => | ||||
|             await new CliApplicationBuilder() | ||||
|                 .AddCommand<CliFxCommand>() | ||||
|                 .Build() | ||||
|                 .RunAsync(Arguments, new Dictionary<string, string>()); | ||||
|         public ValueTask ExecuteAsync(IConsole console) => default; | ||||
|     } | ||||
|  | ||||
|     [Benchmark(Description = "CliFx", Baseline = true)] | ||||
|     public async ValueTask<int> ExecuteWithCliFx() => | ||||
|         await new CliApplicationBuilder() | ||||
|             .AddCommand<CliFxCommand>() | ||||
|             .Build() | ||||
|             .RunAsync(Arguments, new Dictionary<string, string>()); | ||||
| } | ||||
| @@ -1,27 +1,26 @@ | ||||
| using BenchmarkDotNet.Attributes; | ||||
| using clipr; | ||||
|  | ||||
| namespace CliFx.Benchmarks | ||||
| namespace CliFx.Benchmarks; | ||||
|  | ||||
| public partial class Benchmarks | ||||
| { | ||||
|     public partial class Benchmarks | ||||
|     public class CliprCommand | ||||
|     { | ||||
|         public class CliprCommand | ||||
|         [NamedArgument('s', "str")] | ||||
|         public string? StrOption { get; set; } | ||||
|  | ||||
|         [NamedArgument('i', "int")] | ||||
|         public int IntOption { get; set; } | ||||
|  | ||||
|         [NamedArgument('b', "bool", Constraint = NumArgsConstraint.Optional, Const = true)] | ||||
|         public bool BoolOption { get; set; } | ||||
|  | ||||
|         public void Execute() | ||||
|         { | ||||
|             [NamedArgument('s', "str")] | ||||
|             public string? StrOption { get; set; } | ||||
|  | ||||
|             [NamedArgument('i', "int")] | ||||
|             public int IntOption { get; set; } | ||||
|  | ||||
|             [NamedArgument('b', "bool", Constraint = NumArgsConstraint.Optional, Const = true)] | ||||
|             public bool BoolOption { get; set; } | ||||
|  | ||||
|             public void Execute() | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [Benchmark(Description = "Clipr")] | ||||
|         public void ExecuteWithClipr() => CliParser.Parse<CliprCommand>(Arguments).Execute(); | ||||
|     } | ||||
|  | ||||
|     [Benchmark(Description = "Clipr")] | ||||
|     public void ExecuteWithClipr() => CliParser.Parse<CliprCommand>(Arguments).Execute(); | ||||
| } | ||||
| @@ -1,24 +1,23 @@ | ||||
| using BenchmarkDotNet.Attributes; | ||||
| using Cocona; | ||||
|  | ||||
| namespace CliFx.Benchmarks | ||||
| { | ||||
|     public partial class Benchmarks | ||||
|     { | ||||
|         public class CoconaCommand | ||||
|         { | ||||
|             public void Execute( | ||||
|                 [Option("str", new []{'s'})] | ||||
|                 string? strOption, | ||||
|                 [Option("int", new []{'i'})] | ||||
|                 int intOption, | ||||
|                 [Option("bool", new []{'b'})] | ||||
|                 bool boolOption) | ||||
|             { | ||||
|             } | ||||
|         } | ||||
| namespace CliFx.Benchmarks; | ||||
|  | ||||
|         [Benchmark(Description = "Cocona")] | ||||
|         public void ExecuteWithCocona() => CoconaApp.Run<CoconaCommand>(Arguments); | ||||
| public partial class Benchmarks | ||||
| { | ||||
|     public class CoconaCommand | ||||
|     { | ||||
|         public void Execute( | ||||
|             [Option("str", new []{'s'})] | ||||
|             string? strOption, | ||||
|             [Option("int", new []{'i'})] | ||||
|             int intOption, | ||||
|             [Option("bool", new []{'b'})] | ||||
|             bool boolOption) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Benchmark(Description = "Cocona")] | ||||
|     public void ExecuteWithCocona() => CoconaApp.Run<CoconaCommand>(Arguments); | ||||
| } | ||||
| @@ -1,30 +1,29 @@ | ||||
| using BenchmarkDotNet.Attributes; | ||||
| using CommandLine; | ||||
|  | ||||
| namespace CliFx.Benchmarks | ||||
| namespace CliFx.Benchmarks; | ||||
|  | ||||
| public partial class Benchmarks | ||||
| { | ||||
|     public partial class Benchmarks | ||||
|     public class CommandLineParserCommand | ||||
|     { | ||||
|         public class CommandLineParserCommand | ||||
|         [Option('s', "str")] | ||||
|         public string? StrOption { get; set; } | ||||
|  | ||||
|         [Option('i', "int")] | ||||
|         public int IntOption { get; set; } | ||||
|  | ||||
|         [Option('b', "bool")] | ||||
|         public bool BoolOption { get; set; } | ||||
|  | ||||
|         public void Execute() | ||||
|         { | ||||
|             [Option('s', "str")] | ||||
|             public string? StrOption { get; set; } | ||||
|  | ||||
|             [Option('i', "int")] | ||||
|             public int IntOption { get; set; } | ||||
|  | ||||
|             [Option('b', "bool")] | ||||
|             public bool BoolOption { get; set; } | ||||
|  | ||||
|             public void Execute() | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [Benchmark(Description = "CommandLineParser")] | ||||
|         public void ExecuteWithCommandLineParser() => | ||||
|             new Parser() | ||||
|                 .ParseArguments(Arguments, typeof(CommandLineParserCommand)) | ||||
|                 .WithParsed<CommandLineParserCommand>(c => c.Execute()); | ||||
|     } | ||||
|  | ||||
|     [Benchmark(Description = "CommandLineParser")] | ||||
|     public void ExecuteWithCommandLineParser() => | ||||
|         new Parser() | ||||
|             .ParseArguments(Arguments, typeof(CommandLineParserCommand)) | ||||
|             .WithParsed<CommandLineParserCommand>(c => c.Execute()); | ||||
| } | ||||
| @@ -1,25 +1,24 @@ | ||||
| using BenchmarkDotNet.Attributes; | ||||
| using McMaster.Extensions.CommandLineUtils; | ||||
|  | ||||
| namespace CliFx.Benchmarks | ||||
| namespace CliFx.Benchmarks; | ||||
|  | ||||
| public partial class Benchmarks | ||||
| { | ||||
|     public partial class Benchmarks | ||||
|     public class McMasterCommand | ||||
|     { | ||||
|         public class McMasterCommand | ||||
|         { | ||||
|             [Option("--str|-s")] | ||||
|             public string? StrOption { get; set; } | ||||
|         [Option("--str|-s")] | ||||
|         public string? StrOption { get; set; } | ||||
|  | ||||
|             [Option("--int|-i")] | ||||
|             public int IntOption { get; set; } | ||||
|         [Option("--int|-i")] | ||||
|         public int IntOption { get; set; } | ||||
|  | ||||
|             [Option("--bool|-b")] | ||||
|             public bool BoolOption { get; set; } | ||||
|         [Option("--bool|-b")] | ||||
|         public bool BoolOption { get; set; } | ||||
|  | ||||
|             public int OnExecute() => 0; | ||||
|         } | ||||
|  | ||||
|         [Benchmark(Description = "McMaster.Extensions.CommandLineUtils")] | ||||
|         public int ExecuteWithMcMaster() => CommandLineApplication.Execute<McMasterCommand>(Arguments); | ||||
|         public int OnExecute() => 0; | ||||
|     } | ||||
|  | ||||
|     [Benchmark(Description = "McMaster.Extensions.CommandLineUtils")] | ||||
|     public int ExecuteWithMcMaster() => CommandLineApplication.Execute<McMasterCommand>(Arguments); | ||||
| } | ||||
| @@ -1,27 +1,26 @@ | ||||
| using BenchmarkDotNet.Attributes; | ||||
| using PowerArgs; | ||||
|  | ||||
| namespace CliFx.Benchmarks | ||||
| namespace CliFx.Benchmarks; | ||||
|  | ||||
| public partial class Benchmarks | ||||
| { | ||||
|     public partial class Benchmarks | ||||
|     public class PowerArgsCommand | ||||
|     { | ||||
|         public class PowerArgsCommand | ||||
|         [ArgShortcut("--str"), ArgShortcut("-s")] | ||||
|         public string? StrOption { get; set; } | ||||
|  | ||||
|         [ArgShortcut("--int"), ArgShortcut("-i")] | ||||
|         public int IntOption { get; set; } | ||||
|  | ||||
|         [ArgShortcut("--bool"), ArgShortcut("-b")] | ||||
|         public bool BoolOption { get; set; } | ||||
|  | ||||
|         public void Main() | ||||
|         { | ||||
|             [ArgShortcut("--str"), ArgShortcut("-s")] | ||||
|             public string? StrOption { get; set; } | ||||
|  | ||||
|             [ArgShortcut("--int"), ArgShortcut("-i")] | ||||
|             public int IntOption { get; set; } | ||||
|  | ||||
|             [ArgShortcut("--bool"), ArgShortcut("-b")] | ||||
|             public bool BoolOption { get; set; } | ||||
|  | ||||
|             public void Main() | ||||
|             { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [Benchmark(Description = "PowerArgs")] | ||||
|         public void ExecuteWithPowerArgs() => Args.InvokeMain<PowerArgsCommand>(Arguments); | ||||
|     } | ||||
|  | ||||
|     [Benchmark(Description = "PowerArgs")] | ||||
|     public void ExecuteWithPowerArgs() => Args.InvokeMain<PowerArgsCommand>(Arguments); | ||||
| } | ||||
| @@ -3,42 +3,41 @@ using System.CommandLine.Invocation; | ||||
| using System.Threading.Tasks; | ||||
| using BenchmarkDotNet.Attributes; | ||||
|  | ||||
| namespace CliFx.Benchmarks | ||||
| namespace CliFx.Benchmarks; | ||||
|  | ||||
| public partial class Benchmarks | ||||
| { | ||||
|     public partial class Benchmarks | ||||
|     public class SystemCommandLineCommand | ||||
|     { | ||||
|         public class SystemCommandLineCommand | ||||
|         public static int ExecuteHandler(string s, int i, bool b) => 0; | ||||
|  | ||||
|         public Task<int> ExecuteAsync(string[] args) | ||||
|         { | ||||
|             public static int ExecuteHandler(string s, int i, bool b) => 0; | ||||
|  | ||||
|             public Task<int> ExecuteAsync(string[] args) | ||||
|             var command = new RootCommand | ||||
|             { | ||||
|                 var command = new RootCommand | ||||
|                 new Option(new[] {"--str", "-s"}) | ||||
|                 { | ||||
|                     new Option(new[] {"--str", "-s"}) | ||||
|                     { | ||||
|                         Argument = new Argument<string?>() | ||||
|                     }, | ||||
|                     new Option(new[] {"--int", "-i"}) | ||||
|                     { | ||||
|                         Argument = new Argument<int>() | ||||
|                     }, | ||||
|                     new Option(new[] {"--bool", "-b"}) | ||||
|                     { | ||||
|                         Argument = new Argument<bool>() | ||||
|                     } | ||||
|                 }; | ||||
|                     Argument = new Argument<string?>() | ||||
|                 }, | ||||
|                 new Option(new[] {"--int", "-i"}) | ||||
|                 { | ||||
|                     Argument = new Argument<int>() | ||||
|                 }, | ||||
|                 new Option(new[] {"--bool", "-b"}) | ||||
|                 { | ||||
|                     Argument = new Argument<bool>() | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|                 command.Handler = CommandHandler.Create( | ||||
|                     typeof(SystemCommandLineCommand).GetMethod(nameof(ExecuteHandler))! | ||||
|                 ); | ||||
|             command.Handler = CommandHandler.Create( | ||||
|                 typeof(SystemCommandLineCommand).GetMethod(nameof(ExecuteHandler))! | ||||
|             ); | ||||
|  | ||||
|                 return command.InvokeAsync(args); | ||||
|             } | ||||
|             return command.InvokeAsync(args); | ||||
|         } | ||||
|  | ||||
|         [Benchmark(Description = "System.CommandLine")] | ||||
|         public async Task<int> ExecuteWithSystemCommandLine() => | ||||
|             await new SystemCommandLineCommand().ExecuteAsync(Arguments); | ||||
|     } | ||||
|  | ||||
|     [Benchmark(Description = "System.CommandLine")] | ||||
|     public async Task<int> ExecuteWithSystemCommandLine() => | ||||
|         await new SystemCommandLineCommand().ExecuteAsync(Arguments); | ||||
| } | ||||
| @@ -3,18 +3,17 @@ using BenchmarkDotNet.Configs; | ||||
| using BenchmarkDotNet.Order; | ||||
| using BenchmarkDotNet.Running; | ||||
|  | ||||
| namespace CliFx.Benchmarks | ||||
| { | ||||
|     [RankColumn] | ||||
|     [Orderer(SummaryOrderPolicy.FastestToSlowest)] | ||||
|     public partial class Benchmarks | ||||
|     { | ||||
|         private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"}; | ||||
| namespace CliFx.Benchmarks; | ||||
|  | ||||
|         public static void Main() => BenchmarkRunner.Run<Benchmarks>( | ||||
|             DefaultConfig | ||||
|                 .Instance | ||||
|                 .With(ConfigOptions.DisableOptimizationsValidator) | ||||
|         ); | ||||
|     } | ||||
| [RankColumn] | ||||
| [Orderer(SummaryOrderPolicy.FastestToSlowest)] | ||||
| public partial class Benchmarks | ||||
| { | ||||
|     private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"}; | ||||
|  | ||||
|     public static void Main() => BenchmarkRunner.Run<Benchmarks>( | ||||
|         DefaultConfig | ||||
|             .Instance | ||||
|             .WithOptions(ConfigOptions.DisableOptimizationsValidator) | ||||
|     ); | ||||
| } | ||||
| @@ -2,13 +2,13 @@ | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <OutputType>Exe</OutputType> | ||||
|     <TargetFramework>net5.0</TargetFramework> | ||||
|     <TargetFramework>net6.0</TargetFramework> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="BenchmarkDotNet" Version="0.12.1" /> | ||||
|     <PackageReference Include="BenchmarkDotNet" Version="0.13.1" /> | ||||
|     <PackageReference Include="clipr" Version="1.6.1" /> | ||||
|     <PackageReference Include="Cocona" Version="1.5.0" /> | ||||
|     <PackageReference Include="Cocona" Version="1.6.0" /> | ||||
|     <PackageReference Include="CommandLineParser" Version="2.8.0" /> | ||||
|     <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="3.1.0" /> | ||||
|     <PackageReference Include="PowerArgs" Version="3.6.0" /> | ||||
|   | ||||
| @@ -2,11 +2,11 @@ | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <OutputType>Exe</OutputType> | ||||
|     <TargetFramework>net5.0</TargetFramework> | ||||
|     <TargetFramework>net6.0</TargetFramework> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> | ||||
|     <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   | ||||
| @@ -6,65 +6,64 @@ using CliFx.Demo.Utils; | ||||
| using CliFx.Exceptions; | ||||
| using CliFx.Infrastructure; | ||||
|  | ||||
| namespace CliFx.Demo.Commands | ||||
| namespace CliFx.Demo.Commands; | ||||
|  | ||||
| [Command("book add", Description = "Add a book to the library.")] | ||||
| public partial class BookAddCommand : ICommand | ||||
| { | ||||
|     [Command("book add", Description = "Add a book to the library.")] | ||||
|     public partial class BookAddCommand : ICommand | ||||
|     private readonly LibraryProvider _libraryProvider; | ||||
|  | ||||
|     [CommandParameter(0, Name = "title", Description = "Book title.")] | ||||
|     public string Title { get; init; } = ""; | ||||
|  | ||||
|     [CommandOption("author", 'a', IsRequired = true, Description = "Book author.")] | ||||
|     public string Author { get; init; } = ""; | ||||
|  | ||||
|     [CommandOption("published", 'p', Description = "Book publish date.")] | ||||
|     public DateTimeOffset Published { get; init; } = CreateRandomDate(); | ||||
|  | ||||
|     [CommandOption("isbn", 'n', Description = "Book ISBN.")] | ||||
|     public Isbn Isbn { get; init; } = CreateRandomIsbn(); | ||||
|  | ||||
|     public BookAddCommand(LibraryProvider libraryProvider) | ||||
|     { | ||||
|         private readonly LibraryProvider _libraryProvider; | ||||
|  | ||||
|         [CommandParameter(0, Name = "title", Description = "Book title.")] | ||||
|         public string Title { get; init; } = ""; | ||||
|  | ||||
|         [CommandOption("author", 'a', IsRequired = true, Description = "Book author.")] | ||||
|         public string Author { get; init; } = ""; | ||||
|  | ||||
|         [CommandOption("published", 'p', Description = "Book publish date.")] | ||||
|         public DateTimeOffset Published { get; init; } = CreateRandomDate(); | ||||
|  | ||||
|         [CommandOption("isbn", 'n', Description = "Book ISBN.")] | ||||
|         public Isbn Isbn { get; init; } = CreateRandomIsbn(); | ||||
|  | ||||
|         public BookAddCommand(LibraryProvider libraryProvider) | ||||
|         { | ||||
|             _libraryProvider = libraryProvider; | ||||
|         } | ||||
|  | ||||
|         public ValueTask ExecuteAsync(IConsole console) | ||||
|         { | ||||
|             if (_libraryProvider.TryGetBook(Title) is not null) | ||||
|                 throw new CommandException("Book already exists.", 10); | ||||
|  | ||||
|             var book = new Book(Title, Author, Published, Isbn); | ||||
|             _libraryProvider.AddBook(book); | ||||
|  | ||||
|             console.Output.WriteLine("Book added."); | ||||
|             console.Output.WriteBook(book); | ||||
|  | ||||
|             return default; | ||||
|         } | ||||
|         _libraryProvider = libraryProvider; | ||||
|     } | ||||
|  | ||||
|     public partial class BookAddCommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) | ||||
|     { | ||||
|         private static readonly Random Random = new(); | ||||
|         if (_libraryProvider.TryGetBook(Title) is not null) | ||||
|             throw new CommandException("Book already exists.", 10); | ||||
|  | ||||
|         private static DateTimeOffset CreateRandomDate() => new( | ||||
|             Random.Next(1800, 2020), | ||||
|             Random.Next(1, 12), | ||||
|             Random.Next(1, 28), | ||||
|             Random.Next(1, 23), | ||||
|             Random.Next(1, 59), | ||||
|             Random.Next(1, 59), | ||||
|             TimeSpan.Zero | ||||
|         ); | ||||
|         var book = new Book(Title, Author, Published, Isbn); | ||||
|         _libraryProvider.AddBook(book); | ||||
|  | ||||
|         private static Isbn CreateRandomIsbn() => new( | ||||
|             Random.Next(0, 999), | ||||
|             Random.Next(0, 99), | ||||
|             Random.Next(0, 99999), | ||||
|             Random.Next(0, 99), | ||||
|             Random.Next(0, 9) | ||||
|         ); | ||||
|         console.Output.WriteLine("Book added."); | ||||
|         console.Output.WriteBook(book); | ||||
|  | ||||
|         return default; | ||||
|     } | ||||
| } | ||||
|  | ||||
| public partial class BookAddCommand | ||||
| { | ||||
|     private static readonly Random Random = new(); | ||||
|  | ||||
|     private static DateTimeOffset CreateRandomDate() => new( | ||||
|         Random.Next(1800, 2020), | ||||
|         Random.Next(1, 12), | ||||
|         Random.Next(1, 28), | ||||
|         Random.Next(1, 23), | ||||
|         Random.Next(1, 59), | ||||
|         Random.Next(1, 59), | ||||
|         TimeSpan.Zero | ||||
|     ); | ||||
|  | ||||
|     private static Isbn CreateRandomIsbn() => new( | ||||
|         Random.Next(0, 999), | ||||
|         Random.Next(0, 99), | ||||
|         Random.Next(0, 99999), | ||||
|         Random.Next(0, 99), | ||||
|         Random.Next(0, 9) | ||||
|     ); | ||||
| } | ||||
| @@ -5,31 +5,30 @@ using CliFx.Demo.Utils; | ||||
| using CliFx.Exceptions; | ||||
| using CliFx.Infrastructure; | ||||
|  | ||||
| namespace CliFx.Demo.Commands | ||||
| namespace CliFx.Demo.Commands; | ||||
|  | ||||
| [Command("book", Description = "Retrieve a book from the library.")] | ||||
| public class BookCommand : ICommand | ||||
| { | ||||
|     [Command("book", Description = "Retrieve a book from the library.")] | ||||
|     public class BookCommand : ICommand | ||||
|     private readonly LibraryProvider _libraryProvider; | ||||
|  | ||||
|     [CommandParameter(0, Name = "title", Description = "Title of the book to retrieve.")] | ||||
|     public string Title { get; init; } = ""; | ||||
|  | ||||
|     public BookCommand(LibraryProvider libraryProvider) | ||||
|     { | ||||
|         private readonly LibraryProvider _libraryProvider; | ||||
|         _libraryProvider = libraryProvider; | ||||
|     } | ||||
|  | ||||
|         [CommandParameter(0, Name = "title", Description = "Title of the book to retrieve.")] | ||||
|         public string Title { get; init; } = ""; | ||||
|     public ValueTask ExecuteAsync(IConsole console) | ||||
|     { | ||||
|         var book = _libraryProvider.TryGetBook(Title); | ||||
|  | ||||
|         public BookCommand(LibraryProvider libraryProvider) | ||||
|         { | ||||
|             _libraryProvider = libraryProvider; | ||||
|         } | ||||
|         if (book is null) | ||||
|             throw new CommandException("Book not found.", 10); | ||||
|  | ||||
|         public ValueTask ExecuteAsync(IConsole console) | ||||
|         { | ||||
|             var book = _libraryProvider.TryGetBook(Title); | ||||
|         console.Output.WriteBook(book); | ||||
|  | ||||
|             if (book is null) | ||||
|                 throw new CommandException("Book not found.", 10); | ||||
|  | ||||
|             console.Output.WriteBook(book); | ||||
|  | ||||
|             return default; | ||||
|         } | ||||
|         return default; | ||||
|     } | ||||
| } | ||||
| @@ -4,34 +4,33 @@ using CliFx.Demo.Domain; | ||||
| using CliFx.Demo.Utils; | ||||
| using CliFx.Infrastructure; | ||||
|  | ||||
| namespace CliFx.Demo.Commands | ||||
| namespace CliFx.Demo.Commands; | ||||
|  | ||||
| [Command("book list", Description = "List all books in the library.")] | ||||
| public class BookListCommand : ICommand | ||||
| { | ||||
|     [Command("book list", Description = "List all books in the library.")] | ||||
|     public class BookListCommand : ICommand | ||||
|     private readonly LibraryProvider _libraryProvider; | ||||
|  | ||||
|     public BookListCommand(LibraryProvider libraryProvider) | ||||
|     { | ||||
|         private readonly LibraryProvider _libraryProvider; | ||||
|         _libraryProvider = libraryProvider; | ||||
|     } | ||||
|  | ||||
|         public BookListCommand(LibraryProvider libraryProvider) | ||||
|     public ValueTask ExecuteAsync(IConsole console) | ||||
|     { | ||||
|         var library = _libraryProvider.GetLibrary(); | ||||
|  | ||||
|         for (var i = 0; i < library.Books.Count; i++) | ||||
|         { | ||||
|             _libraryProvider = libraryProvider; | ||||
|             // Add margin | ||||
|             if (i != 0) | ||||
|                 console.Output.WriteLine(); | ||||
|  | ||||
|             // Render book | ||||
|             var book = library.Books[i]; | ||||
|             console.Output.WriteBook(book); | ||||
|         } | ||||
|  | ||||
|         public ValueTask ExecuteAsync(IConsole console) | ||||
|         { | ||||
|             var library = _libraryProvider.GetLibrary(); | ||||
|  | ||||
|             for (var i = 0; i < library.Books.Count; i++) | ||||
|             { | ||||
|                 // Add margin | ||||
|                 if (i != 0) | ||||
|                     console.Output.WriteLine(); | ||||
|  | ||||
|                 // Render book | ||||
|                 var book = library.Books[i]; | ||||
|                 console.Output.WriteBook(book); | ||||
|             } | ||||
|  | ||||
|             return default; | ||||
|         } | ||||
|         return default; | ||||
|     } | ||||
| } | ||||
| @@ -4,33 +4,32 @@ using CliFx.Demo.Domain; | ||||
| using CliFx.Exceptions; | ||||
| using CliFx.Infrastructure; | ||||
|  | ||||
| namespace CliFx.Demo.Commands | ||||
| namespace CliFx.Demo.Commands; | ||||
|  | ||||
| [Command("book remove", Description = "Remove a book from the library.")] | ||||
| public class BookRemoveCommand : ICommand | ||||
| { | ||||
|     [Command("book remove", Description = "Remove a book from the library.")] | ||||
|     public class BookRemoveCommand : ICommand | ||||
|     private readonly LibraryProvider _libraryProvider; | ||||
|  | ||||
|     [CommandParameter(0, Name = "title", Description = "Title of the book to remove.")] | ||||
|     public string Title { get; init; } = ""; | ||||
|  | ||||
|     public BookRemoveCommand(LibraryProvider libraryProvider) | ||||
|     { | ||||
|         private readonly LibraryProvider _libraryProvider; | ||||
|         _libraryProvider = libraryProvider; | ||||
|     } | ||||
|  | ||||
|         [CommandParameter(0, Name = "title", Description = "Title of the book to remove.")] | ||||
|         public string Title { get; init; } = ""; | ||||
|     public ValueTask ExecuteAsync(IConsole console) | ||||
|     { | ||||
|         var book = _libraryProvider.TryGetBook(Title); | ||||
|  | ||||
|         public BookRemoveCommand(LibraryProvider libraryProvider) | ||||
|         { | ||||
|             _libraryProvider = libraryProvider; | ||||
|         } | ||||
|         if (book is null) | ||||
|             throw new CommandException("Book not found.", 10); | ||||
|  | ||||
|         public ValueTask ExecuteAsync(IConsole console) | ||||
|         { | ||||
|             var book = _libraryProvider.TryGetBook(Title); | ||||
|         _libraryProvider.RemoveBook(book); | ||||
|  | ||||
|             if (book is null) | ||||
|                 throw new CommandException("Book not found.", 10); | ||||
|         console.Output.WriteLine($"Book {Title} removed."); | ||||
|  | ||||
|             _libraryProvider.RemoveBook(book); | ||||
|  | ||||
|             console.Output.WriteLine($"Book {Title} removed."); | ||||
|  | ||||
|             return default; | ||||
|         } | ||||
|         return default; | ||||
|     } | ||||
| } | ||||
| @@ -1,23 +1,22 @@ | ||||
| using System; | ||||
|  | ||||
| namespace CliFx.Demo.Domain | ||||
| namespace CliFx.Demo.Domain; | ||||
|  | ||||
| public class Book | ||||
| { | ||||
|     public class Book | ||||
|     public string Title { get; } | ||||
|  | ||||
|     public string Author { get; } | ||||
|  | ||||
|     public DateTimeOffset Published { get; } | ||||
|  | ||||
|     public Isbn Isbn { get; } | ||||
|  | ||||
|     public Book(string title, string author, DateTimeOffset published, Isbn isbn) | ||||
|     { | ||||
|         public string Title { get; } | ||||
|  | ||||
|         public string Author { get; } | ||||
|  | ||||
|         public DateTimeOffset Published { get; } | ||||
|  | ||||
|         public Isbn Isbn { get; } | ||||
|  | ||||
|         public Book(string title, string author, DateTimeOffset published, Isbn isbn) | ||||
|         { | ||||
|             Title = title; | ||||
|             Author = author; | ||||
|             Published = published; | ||||
|             Isbn = isbn; | ||||
|         } | ||||
|         Title = title; | ||||
|         Author = author; | ||||
|         Published = published; | ||||
|         Isbn = isbn; | ||||
|     } | ||||
| } | ||||
| @@ -1,45 +1,44 @@ | ||||
| using System; | ||||
|  | ||||
| namespace CliFx.Demo.Domain | ||||
| namespace CliFx.Demo.Domain; | ||||
|  | ||||
| public partial class Isbn | ||||
| { | ||||
|     public partial class Isbn | ||||
|     public int EanPrefix { get; } | ||||
|  | ||||
|     public int RegistrationGroup { get; } | ||||
|  | ||||
|     public int Registrant { get; } | ||||
|  | ||||
|     public int Publication { get; } | ||||
|  | ||||
|     public int CheckDigit { get; } | ||||
|  | ||||
|     public Isbn(int eanPrefix, int registrationGroup, int registrant, int publication, int checkDigit) | ||||
|     { | ||||
|         public int EanPrefix { get; } | ||||
|  | ||||
|         public int RegistrationGroup { get; } | ||||
|  | ||||
|         public int Registrant { get; } | ||||
|  | ||||
|         public int Publication { get; } | ||||
|  | ||||
|         public int CheckDigit { get; } | ||||
|  | ||||
|         public Isbn(int eanPrefix, int registrationGroup, int registrant, int publication, int checkDigit) | ||||
|         { | ||||
|             EanPrefix = eanPrefix; | ||||
|             RegistrationGroup = registrationGroup; | ||||
|             Registrant = registrant; | ||||
|             Publication = publication; | ||||
|             CheckDigit = checkDigit; | ||||
|         } | ||||
|  | ||||
|         public override string ToString() => | ||||
|             $"{EanPrefix:000}-{RegistrationGroup:00}-{Registrant:00000}-{Publication:00}-{CheckDigit:0}"; | ||||
|         EanPrefix = eanPrefix; | ||||
|         RegistrationGroup = registrationGroup; | ||||
|         Registrant = registrant; | ||||
|         Publication = publication; | ||||
|         CheckDigit = checkDigit; | ||||
|     } | ||||
|  | ||||
|     public partial class Isbn | ||||
|     { | ||||
|         public static Isbn Parse(string value, IFormatProvider formatProvider) | ||||
|         { | ||||
|             var components = value.Split('-', 5, StringSplitOptions.RemoveEmptyEntries); | ||||
|     public override string ToString() => | ||||
|         $"{EanPrefix:000}-{RegistrationGroup:00}-{Registrant:00000}-{Publication:00}-{CheckDigit:0}"; | ||||
| } | ||||
|  | ||||
|             return new Isbn( | ||||
|                 int.Parse(components[0], formatProvider), | ||||
|                 int.Parse(components[1], formatProvider), | ||||
|                 int.Parse(components[2], formatProvider), | ||||
|                 int.Parse(components[3], formatProvider), | ||||
|                 int.Parse(components[4], formatProvider) | ||||
|             ); | ||||
|         } | ||||
| public partial class Isbn | ||||
| { | ||||
|     public static Isbn Parse(string value, IFormatProvider formatProvider) | ||||
|     { | ||||
|         var components = value.Split('-', 5, StringSplitOptions.RemoveEmptyEntries); | ||||
|  | ||||
|         return new Isbn( | ||||
|             int.Parse(components[0], formatProvider), | ||||
|             int.Parse(components[1], formatProvider), | ||||
|             int.Parse(components[2], formatProvider), | ||||
|             int.Parse(components[3], formatProvider), | ||||
|             int.Parse(components[4], formatProvider) | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| @@ -2,35 +2,34 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
|  | ||||
| namespace CliFx.Demo.Domain | ||||
| namespace CliFx.Demo.Domain; | ||||
|  | ||||
| public partial class Library | ||||
| { | ||||
|     public partial class Library | ||||
|     public IReadOnlyList<Book> Books { get; } | ||||
|  | ||||
|     public Library(IReadOnlyList<Book> books) | ||||
|     { | ||||
|         public IReadOnlyList<Book> Books { get; } | ||||
|  | ||||
|         public Library(IReadOnlyList<Book> books) | ||||
|         { | ||||
|             Books = books; | ||||
|         } | ||||
|  | ||||
|         public Library WithBook(Book book) | ||||
|         { | ||||
|             var books = Books.ToList(); | ||||
|             books.Add(book); | ||||
|  | ||||
|             return new Library(books); | ||||
|         } | ||||
|  | ||||
|         public Library WithoutBook(Book book) | ||||
|         { | ||||
|             var books = Books.Where(b => b != book).ToArray(); | ||||
|  | ||||
|             return new Library(books); | ||||
|         } | ||||
|         Books = books; | ||||
|     } | ||||
|  | ||||
|     public partial class Library | ||||
|     public Library WithBook(Book book) | ||||
|     { | ||||
|         public static Library Empty { get; } = new(Array.Empty<Book>()); | ||||
|         var books = Books.ToList(); | ||||
|         books.Add(book); | ||||
|  | ||||
|         return new Library(books); | ||||
|     } | ||||
|  | ||||
|     public Library WithoutBook(Book book) | ||||
|     { | ||||
|         var books = Books.Where(b => b != book).ToArray(); | ||||
|  | ||||
|         return new Library(books); | ||||
|     } | ||||
| } | ||||
|  | ||||
| public partial class Library | ||||
| { | ||||
|     public static Library Empty { get; } = new(Array.Empty<Book>()); | ||||
| } | ||||
| @@ -2,40 +2,39 @@ | ||||
| using System.Linq; | ||||
| using Newtonsoft.Json; | ||||
|  | ||||
| namespace CliFx.Demo.Domain | ||||
| namespace CliFx.Demo.Domain; | ||||
|  | ||||
| public class LibraryProvider | ||||
| { | ||||
|     public class LibraryProvider | ||||
|     private static string StorageFilePath { get; } = Path.Combine(Directory.GetCurrentDirectory(), "Library.json"); | ||||
|  | ||||
|     private void StoreLibrary(Library library) | ||||
|     { | ||||
|         private static string StorageFilePath { get; } = Path.Combine(Directory.GetCurrentDirectory(), "Library.json"); | ||||
|         var data = JsonConvert.SerializeObject(library); | ||||
|         File.WriteAllText(StorageFilePath, data); | ||||
|     } | ||||
|  | ||||
|         private void StoreLibrary(Library library) | ||||
|         { | ||||
|             var data = JsonConvert.SerializeObject(library); | ||||
|             File.WriteAllText(StorageFilePath, data); | ||||
|         } | ||||
|     public Library GetLibrary() | ||||
|     { | ||||
|         if (!File.Exists(StorageFilePath)) | ||||
|             return Library.Empty; | ||||
|  | ||||
|         public Library GetLibrary() | ||||
|         { | ||||
|             if (!File.Exists(StorageFilePath)) | ||||
|                 return Library.Empty; | ||||
|         var data = File.ReadAllText(StorageFilePath); | ||||
|  | ||||
|             var data = File.ReadAllText(StorageFilePath); | ||||
|         return JsonConvert.DeserializeObject<Library>(data) ?? Library.Empty; | ||||
|     } | ||||
|  | ||||
|             return JsonConvert.DeserializeObject<Library>(data) ?? Library.Empty; | ||||
|         } | ||||
|     public Book? TryGetBook(string title) => GetLibrary().Books.FirstOrDefault(b => b.Title == title); | ||||
|  | ||||
|         public Book? TryGetBook(string title) => GetLibrary().Books.FirstOrDefault(b => b.Title == title); | ||||
|     public void AddBook(Book book) | ||||
|     { | ||||
|         var updatedLibrary = GetLibrary().WithBook(book); | ||||
|         StoreLibrary(updatedLibrary); | ||||
|     } | ||||
|  | ||||
|         public void AddBook(Book book) | ||||
|         { | ||||
|             var updatedLibrary = GetLibrary().WithBook(book); | ||||
|             StoreLibrary(updatedLibrary); | ||||
|         } | ||||
|  | ||||
|         public void RemoveBook(Book book) | ||||
|         { | ||||
|             var updatedLibrary = GetLibrary().WithoutBook(book); | ||||
|             StoreLibrary(updatedLibrary); | ||||
|         } | ||||
|     public void RemoveBook(Book book) | ||||
|     { | ||||
|         var updatedLibrary = GetLibrary().WithoutBook(book); | ||||
|         StoreLibrary(updatedLibrary); | ||||
|     } | ||||
| } | ||||
| @@ -1,36 +1,25 @@ | ||||
| using System; | ||||
| using System.Threading.Tasks; | ||||
| using CliFx; | ||||
| using CliFx.Demo.Commands; | ||||
| using CliFx.Demo.Domain; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
|  | ||||
| namespace CliFx.Demo | ||||
| { | ||||
|     public static class Program | ||||
|     { | ||||
|         private static IServiceProvider GetServiceProvider() | ||||
|         { | ||||
|             // We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands | ||||
|             var services = new ServiceCollection(); | ||||
| // We use Microsoft.Extensions.DependencyInjection for injecting dependencies in commands | ||||
| var services = new ServiceCollection(); | ||||
|  | ||||
|             // Register services | ||||
|             services.AddSingleton<LibraryProvider>(); | ||||
| // Register services | ||||
| services.AddSingleton<LibraryProvider>(); | ||||
|  | ||||
|             // Register commands | ||||
|             services.AddTransient<BookCommand>(); | ||||
|             services.AddTransient<BookAddCommand>(); | ||||
|             services.AddTransient<BookRemoveCommand>(); | ||||
|             services.AddTransient<BookListCommand>(); | ||||
| // Register commands | ||||
| services.AddTransient<BookCommand>(); | ||||
| services.AddTransient<BookAddCommand>(); | ||||
| services.AddTransient<BookRemoveCommand>(); | ||||
| services.AddTransient<BookListCommand>(); | ||||
|  | ||||
|             return services.BuildServiceProvider(); | ||||
|         } | ||||
| var serviceProvider = services.BuildServiceProvider(); | ||||
|  | ||||
|         public static async Task<int> Main() => | ||||
|             await new CliApplicationBuilder() | ||||
|                 .SetDescription("Demo application showcasing CliFx features.") | ||||
|                 .AddCommandsFromThisAssembly() | ||||
|                 .UseTypeActivator(GetServiceProvider().GetRequiredService) | ||||
|                 .Build() | ||||
|                 .RunAsync(); | ||||
|     } | ||||
| } | ||||
| return await new CliApplicationBuilder() | ||||
|     .SetDescription("Demo application showcasing CliFx features.") | ||||
|     .AddCommandsFromThisAssembly() | ||||
|     .UseTypeActivator(serviceProvider.GetRequiredService) | ||||
|     .Build() | ||||
|     .RunAsync(); | ||||
| @@ -2,36 +2,35 @@ | ||||
| using CliFx.Demo.Domain; | ||||
| using CliFx.Infrastructure; | ||||
|  | ||||
| namespace CliFx.Demo.Utils | ||||
| namespace CliFx.Demo.Utils; | ||||
|  | ||||
| internal static class ConsoleExtensions | ||||
| { | ||||
|     internal static class ConsoleExtensions | ||||
|     public static void WriteBook(this ConsoleWriter writer, Book book) | ||||
|     { | ||||
|         public static void WriteBook(this ConsoleWriter writer, Book book) | ||||
|         { | ||||
|             // Title | ||||
|             using (writer.Console.WithForegroundColor(ConsoleColor.White)) | ||||
|                 writer.WriteLine(book.Title); | ||||
|         // Title | ||||
|         using (writer.Console.WithForegroundColor(ConsoleColor.White)) | ||||
|             writer.WriteLine(book.Title); | ||||
|  | ||||
|             // Author | ||||
|             writer.Write("  "); | ||||
|             writer.Write("Author: "); | ||||
|         // Author | ||||
|         writer.Write("  "); | ||||
|         writer.Write("Author: "); | ||||
|  | ||||
|             using (writer.Console.WithForegroundColor(ConsoleColor.White)) | ||||
|                 writer.WriteLine(book.Author); | ||||
|         using (writer.Console.WithForegroundColor(ConsoleColor.White)) | ||||
|             writer.WriteLine(book.Author); | ||||
|  | ||||
|             // Published | ||||
|             writer.Write("  "); | ||||
|             writer.Write("Published: "); | ||||
|         // Published | ||||
|         writer.Write("  "); | ||||
|         writer.Write("Published: "); | ||||
|  | ||||
|             using (writer.Console.WithForegroundColor(ConsoleColor.White)) | ||||
|                 writer.WriteLine($"{book.Published:d}"); | ||||
|         using (writer.Console.WithForegroundColor(ConsoleColor.White)) | ||||
|             writer.WriteLine($"{book.Published:d}"); | ||||
|  | ||||
|             // ISBN | ||||
|             writer.Write("  "); | ||||
|             writer.Write("ISBN: "); | ||||
|         // ISBN | ||||
|         writer.Write("  "); | ||||
|         writer.Write("ISBN: "); | ||||
|  | ||||
|             using (writer.Console.WithForegroundColor(ConsoleColor.White)) | ||||
|                 writer.WriteLine(book.Isbn); | ||||
|         } | ||||
|         using (writer.Console.WithForegroundColor(ConsoleColor.White)) | ||||
|             writer.WriteLine(book.Isbn); | ||||
|     } | ||||
| } | ||||
| @@ -2,7 +2,7 @@ | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <OutputType>Exe</OutputType> | ||||
|     <TargetFramework>net5.0</TargetFramework> | ||||
|     <TargetFramework>net6.0</TargetFramework> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -3,22 +3,21 @@ using System.Threading.Tasks; | ||||
| using CliFx.Attributes; | ||||
| using CliFx.Infrastructure; | ||||
|  | ||||
| namespace CliFx.Tests.Dummy.Commands | ||||
| namespace CliFx.Tests.Dummy.Commands; | ||||
|  | ||||
| [Command("console-test")] | ||||
| public class ConsoleTestCommand : ICommand | ||||
| { | ||||
|     [Command("console-test")] | ||||
|     public class ConsoleTestCommand : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) | ||||
|     { | ||||
|         public ValueTask ExecuteAsync(IConsole console) | ||||
|         var input = console.Input.ReadToEnd(); | ||||
|  | ||||
|         using (console.WithColors(ConsoleColor.Black, ConsoleColor.White)) | ||||
|         { | ||||
|             var input = console.Input.ReadToEnd(); | ||||
|  | ||||
|             using (console.WithColors(ConsoleColor.Black, ConsoleColor.White)) | ||||
|             { | ||||
|                 console.Output.WriteLine(input); | ||||
|                 console.Error.WriteLine(input); | ||||
|             } | ||||
|  | ||||
|             return default; | ||||
|             console.Output.WriteLine(input); | ||||
|             console.Error.WriteLine(input); | ||||
|         } | ||||
|  | ||||
|         return default; | ||||
|     } | ||||
| } | ||||
| @@ -2,19 +2,18 @@ | ||||
| using CliFx.Attributes; | ||||
| using CliFx.Infrastructure; | ||||
|  | ||||
| namespace CliFx.Tests.Dummy.Commands | ||||
| namespace CliFx.Tests.Dummy.Commands; | ||||
|  | ||||
| [Command("env-test")] | ||||
| public class EnvironmentTestCommand : ICommand | ||||
| { | ||||
|     [Command("env-test")] | ||||
|     public class EnvironmentTestCommand : ICommand | ||||
|     [CommandOption("target", EnvironmentVariable = "ENV_TARGET")] | ||||
|     public string GreetingTarget { get; set; } = "World"; | ||||
|  | ||||
|     public ValueTask ExecuteAsync(IConsole console) | ||||
|     { | ||||
|         [CommandOption("target", EnvironmentVariable = "ENV_TARGET")] | ||||
|         public string GreetingTarget { get; set; } = "World"; | ||||
|         console.Output.WriteLine($"Hello {GreetingTarget}!"); | ||||
|  | ||||
|         public ValueTask ExecuteAsync(IConsole console) | ||||
|         { | ||||
|             console.Output.WriteLine($"Hello {GreetingTarget}!"); | ||||
|  | ||||
|             return default; | ||||
|         } | ||||
|         return default; | ||||
|     } | ||||
| } | ||||
| @@ -1,24 +1,22 @@ | ||||
| using System.Reflection; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace CliFx.Tests.Dummy | ||||
| namespace CliFx.Tests.Dummy; | ||||
| // This dummy application is used in tests for scenarios | ||||
| // that require an external process to properly verify. | ||||
|  | ||||
| public static partial class Program | ||||
| { | ||||
|     // This dummy application is used in tests for scenarios | ||||
|     // that require an external process to properly verify. | ||||
|     public static Assembly Assembly { get; } = typeof(Program).Assembly; | ||||
|  | ||||
|     public static partial class Program | ||||
|     { | ||||
|         public static Assembly Assembly { get; } = typeof(Program).Assembly; | ||||
|  | ||||
|         public static string Location { get; } = Assembly.Location; | ||||
|     } | ||||
|  | ||||
|     public static partial class Program | ||||
|     { | ||||
|         public static async Task Main() => | ||||
|             await new CliApplicationBuilder() | ||||
|                 .AddCommandsFromThisAssembly() | ||||
|                 .Build() | ||||
|                 .RunAsync(); | ||||
|     } | ||||
|     public static string Location { get; } = Assembly.Location; | ||||
| } | ||||
|  | ||||
| public static partial class Program | ||||
| { | ||||
|     public static async Task Main() => | ||||
|         await new CliApplicationBuilder() | ||||
|             .AddCommandsFromThisAssembly() | ||||
|             .Build() | ||||
|             .RunAsync(); | ||||
| } | ||||
| @@ -6,81 +6,80 @@ using FluentAssertions; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
| public class ApplicationSpecs : SpecsBase | ||||
| { | ||||
|     public class ApplicationSpecs : SpecsBase | ||||
|     public ApplicationSpecs(ITestOutputHelper testOutput) | ||||
|         : base(testOutput) | ||||
|     { | ||||
|         public ApplicationSpecs(ITestOutputHelper testOutput) | ||||
|             : base(testOutput) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Application_can_be_created_with_minimal_configuration() | ||||
|         { | ||||
|             // Act | ||||
|             var app = new CliApplicationBuilder() | ||||
|                 .AddCommandsFromThisAssembly() | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|     [Fact] | ||||
|     public async Task Application_can_be_created_with_minimal_configuration() | ||||
|     { | ||||
|         // Act | ||||
|         var app = new CliApplicationBuilder() | ||||
|             .AddCommandsFromThisAssembly() | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             var exitCode = await app.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         var exitCode = await app.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Application_can_be_created_with_a_fully_customized_configuration() | ||||
|         { | ||||
|             // Act | ||||
|             var app = new CliApplicationBuilder() | ||||
|                 .AddCommand<NoOpCommand>() | ||||
|                 .AddCommandsFrom(typeof(NoOpCommand).Assembly) | ||||
|                 .AddCommands(new[] {typeof(NoOpCommand)}) | ||||
|                 .AddCommandsFrom(new[] {typeof(NoOpCommand).Assembly}) | ||||
|                 .AddCommandsFromThisAssembly() | ||||
|                 .AllowDebugMode() | ||||
|                 .AllowPreviewMode() | ||||
|                 .SetTitle("test") | ||||
|                 .SetExecutableName("test") | ||||
|                 .SetVersion("test") | ||||
|                 .SetDescription("test") | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .UseTypeActivator(Activator.CreateInstance!) | ||||
|                 .Build(); | ||||
|     [Fact] | ||||
|     public async Task Application_can_be_created_with_a_fully_customized_configuration() | ||||
|     { | ||||
|         // Act | ||||
|         var app = new CliApplicationBuilder() | ||||
|             .AddCommand<NoOpCommand>() | ||||
|             .AddCommandsFrom(typeof(NoOpCommand).Assembly) | ||||
|             .AddCommands(new[] {typeof(NoOpCommand)}) | ||||
|             .AddCommandsFrom(new[] {typeof(NoOpCommand).Assembly}) | ||||
|             .AddCommandsFromThisAssembly() | ||||
|             .AllowDebugMode() | ||||
|             .AllowPreviewMode() | ||||
|             .SetTitle("test") | ||||
|             .SetExecutableName("test") | ||||
|             .SetVersion("test") | ||||
|             .SetDescription("test") | ||||
|             .UseConsole(FakeConsole) | ||||
|             .UseTypeActivator(Activator.CreateInstance!) | ||||
|             .Build(); | ||||
|  | ||||
|             var exitCode = await app.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         var exitCode = await app.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Application_configuration_fails_if_an_invalid_command_is_registered() | ||||
|         { | ||||
|             // Act | ||||
|             var app = new CliApplicationBuilder() | ||||
|                 .AddCommand(typeof(ApplicationSpecs)) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|     [Fact] | ||||
|     public async Task Application_configuration_fails_if_an_invalid_command_is_registered() | ||||
|     { | ||||
|         // Act | ||||
|         var app = new CliApplicationBuilder() | ||||
|             .AddCommand(typeof(ApplicationSpecs)) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             var exitCode = await app.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         var exitCode = await app.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("not a valid command"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("not a valid command"); | ||||
|     } | ||||
| } | ||||
| @@ -6,22 +6,22 @@ using FluentAssertions; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| { | ||||
|     public class CancellationSpecs : SpecsBase | ||||
|     { | ||||
|         public CancellationSpecs(ITestOutputHelper testOutput) | ||||
|             : base(testOutput) | ||||
|         { | ||||
|         } | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Command_can_register_to_receive_a_cancellation_signal_from_the_console() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
| public class CancellationSpecs : SpecsBase | ||||
| { | ||||
|     public CancellationSpecs(ITestOutputHelper testOutput) | ||||
|         : base(testOutput) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task Command_can_register_to_receive_a_cancellation_signal_from_the_console() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -44,24 +44,23 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             FakeConsole.RequestCancellation(TimeSpan.FromSeconds(0.2)); | ||||
|         // Act | ||||
|         FakeConsole.RequestCancellation(TimeSpan.FromSeconds(0.2)); | ||||
|  | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdOut.Trim().Should().Be("Cancelled"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdOut.Trim().Should().Be("Cancelled"); | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net5.0</TargetFramework> | ||||
|     <TargetFramework>net6.0</TargetFramework> | ||||
|     <IsPackable>false</IsPackable> | ||||
|     <IsTestProject>true</IsTestProject> | ||||
|     <CollectCoverage>true</CollectCoverage> | ||||
| @@ -13,14 +13,15 @@ | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="CliWrap" Version="3.3.2" /> | ||||
|     <PackageReference Include="FluentAssertions" Version="5.10.3" /> | ||||
|     <PackageReference Include="Basic.Reference.Assemblies" Version="1.2.4" /> | ||||
|     <PackageReference Include="CliWrap" Version="3.3.3" /> | ||||
|     <PackageReference Include="FluentAssertions" Version="6.2.0" /> | ||||
|     <PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" /> | ||||
|     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" /> | ||||
|     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" /> | ||||
|     <PackageReference Include="xunit" Version="2.4.1" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" PrivateAssets="all" /> | ||||
|     <PackageReference Include="coverlet.msbuild" Version="3.0.3" PrivateAssets="all" /> | ||||
|     <PackageReference Include="coverlet.msbuild" Version="3.1.0" PrivateAssets="all" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| using System.Threading.Tasks; | ||||
| using CliFx.Infrastructure; | ||||
| using CliFx.Tests.Utils; | ||||
| using CliWrap; | ||||
| using CliWrap.Buffered; | ||||
| @@ -8,42 +11,42 @@ using FluentAssertions; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
| public class ConsoleSpecs : SpecsBase | ||||
| { | ||||
|     public class ConsoleSpecs : SpecsBase | ||||
|     public ConsoleSpecs(ITestOutputHelper testOutput) | ||||
|         : base(testOutput) | ||||
|     { | ||||
|         public ConsoleSpecs(ITestOutputHelper testOutput) | ||||
|             : base(testOutput) | ||||
|         { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Real_console_maps_directly_to_system_console() | ||||
|         { | ||||
|             // Can't verify our own console output, so using an | ||||
|             // external process for this test. | ||||
|     [Fact] | ||||
|     public async Task Real_console_maps_directly_to_system_console() | ||||
|     { | ||||
|         // Can't verify our own console output, so using an | ||||
|         // external process for this test. | ||||
|  | ||||
|             // Arrange | ||||
|             var command = "Hello world" | Cli.Wrap("dotnet") | ||||
|                 .WithArguments(a => a | ||||
|                     .Add(Dummy.Program.Location) | ||||
|                     .Add("console-test")); | ||||
|         // Arrange | ||||
|         var command = "Hello world" | Cli.Wrap("dotnet") | ||||
|             .WithArguments(a => a | ||||
|                 .Add(Dummy.Program.Location) | ||||
|                 .Add("console-test")); | ||||
|  | ||||
|             // Act | ||||
|             var result = await command.ExecuteBufferedAsync(); | ||||
|         // Act | ||||
|         var result = await command.ExecuteBufferedAsync(); | ||||
|  | ||||
|             // Assert | ||||
|             result.StandardOutput.Trim().Should().Be("Hello world"); | ||||
|             result.StandardError.Trim().Should().Be("Hello world"); | ||||
|         } | ||||
|         // Assert | ||||
|         result.StandardOutput.Trim().Should().Be("Hello world"); | ||||
|         result.StandardError.Trim().Should().Be("Hello world"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Fake_console_does_not_leak_to_system_console() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Fake_console_does_not_leak_to_system_console() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| {    | ||||
| @@ -63,43 +66,43 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|  | ||||
|             Console.OpenStandardInput().Should().NotBe(FakeConsole.Input.BaseStream); | ||||
|             Console.OpenStandardOutput().Should().NotBe(FakeConsole.Output.BaseStream); | ||||
|             Console.OpenStandardError().Should().NotBe(FakeConsole.Error.BaseStream); | ||||
|         Console.OpenStandardInput().Should().NotBeSameAs(FakeConsole.Input.BaseStream); | ||||
|         Console.OpenStandardOutput().Should().NotBeSameAs(FakeConsole.Output.BaseStream); | ||||
|         Console.OpenStandardError().Should().NotBeSameAs(FakeConsole.Error.BaseStream); | ||||
|  | ||||
|             Console.ForegroundColor.Should().NotBe(ConsoleColor.DarkMagenta); | ||||
|             Console.BackgroundColor.Should().NotBe(ConsoleColor.DarkMagenta); | ||||
|         Console.ForegroundColor.Should().NotBe(ConsoleColor.DarkMagenta); | ||||
|         Console.BackgroundColor.Should().NotBe(ConsoleColor.DarkMagenta); | ||||
|  | ||||
|             // This fails because tests don't spawn a console window | ||||
|             //Console.CursorLeft.Should().NotBe(42); | ||||
|             //Console.CursorTop.Should().NotBe(24); | ||||
|         // This fails because tests don't spawn a console window | ||||
|         //Console.CursorLeft.Should().NotBe(42); | ||||
|         //Console.CursorTop.Should().NotBe(24); | ||||
|  | ||||
|             FakeConsole.IsInputRedirected.Should().BeTrue(); | ||||
|             FakeConsole.IsOutputRedirected.Should().BeTrue(); | ||||
|             FakeConsole.IsErrorRedirected.Should().BeTrue(); | ||||
|         } | ||||
|         FakeConsole.IsInputRedirected.Should().BeTrue(); | ||||
|         FakeConsole.IsOutputRedirected.Should().BeTrue(); | ||||
|         FakeConsole.IsErrorRedirected.Should().BeTrue(); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Fake_console_can_be_used_with_an_in_memory_backing_store() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Fake_console_can_be_used_with_an_in_memory_backing_store() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| {    | ||||
| @@ -114,26 +117,42 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             FakeConsole.WriteInput("Hello world"); | ||||
|         // Act | ||||
|         FakeConsole.WriteInput("Hello world"); | ||||
|  | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("Hello world"); | ||||
|             stdErr.Trim().Should().Be("Hello world"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("Hello world"); | ||||
|         stdErr.Trim().Should().Be("Hello world"); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public void Console_does_not_emit_preamble_when_used_with_encoding_that_has_it() | ||||
|     { | ||||
|         // Arrange | ||||
|         using var buffer = new MemoryStream(); | ||||
|         using var consoleWriter = new ConsoleWriter(FakeConsole, buffer, Encoding.UTF8); | ||||
|  | ||||
|         // Act | ||||
|         consoleWriter.Write("Hello world"); | ||||
|         consoleWriter.Flush(); | ||||
|  | ||||
|         var output = consoleWriter.Encoding.GetString(buffer.ToArray()); | ||||
|  | ||||
|         // Assert | ||||
|         output.Should().Be("Hello world"); | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -10,71 +10,71 @@ using FluentAssertions; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
| public class DirectivesSpecs : SpecsBase | ||||
| { | ||||
|     public class DirectivesSpecs : SpecsBase | ||||
|     public DirectivesSpecs(ITestOutputHelper testOutput) | ||||
|         : base(testOutput) | ||||
|     { | ||||
|         public DirectivesSpecs(ITestOutputHelper testOutput) | ||||
|             : base(testOutput) | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task Debug_directive_can_be_specified_to_interrupt_execution_until_a_debugger_is_attached() | ||||
|     { | ||||
|         // Arrange | ||||
|         var stdOutBuffer = new StringBuilder(); | ||||
|  | ||||
|         var command = Cli.Wrap("dotnet") | ||||
|             .WithArguments(a => a | ||||
|                 .Add(Dummy.Program.Location) | ||||
|                 .Add("[debug]")) | stdOutBuffer; | ||||
|  | ||||
|         // Act | ||||
|         try | ||||
|         { | ||||
|         } | ||||
|             // This has a timeout just in case the execution hangs forever | ||||
|             using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Debug_directive_can_be_specified_to_interrupt_execution_until_a_debugger_is_attached() | ||||
|         { | ||||
|             // Arrange | ||||
|             var stdOutBuffer = new StringBuilder(); | ||||
|             var task = command.ExecuteAsync(cts.Token); | ||||
|  | ||||
|             var command = Cli.Wrap("dotnet") | ||||
|                 .WithArguments(a => a | ||||
|                     .Add(Dummy.Program.Location) | ||||
|                     .Add("[debug]")) | stdOutBuffer; | ||||
|  | ||||
|             // Act | ||||
|             try | ||||
|             // We can't attach a debugger programmatically, so the application | ||||
|             // will hang indefinitely. | ||||
|             // To work around it, we will wait until the application writes | ||||
|             // something to the standard output and then kill it. | ||||
|             while (true) | ||||
|             { | ||||
|                 // This has a timeout just in case the execution hangs forever | ||||
|                 using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); | ||||
|  | ||||
|                 var task = command.ExecuteAsync(cts.Token); | ||||
|  | ||||
|                 // We can't attach a debugger programmatically, so the application | ||||
|                 // will hang indefinitely. | ||||
|                 // To work around it, we will wait until the application writes | ||||
|                 // something to the standard output and then kill it. | ||||
|                 while (true) | ||||
|                 if (stdOutBuffer.Length > 0) | ||||
|                 { | ||||
|                     if (stdOutBuffer.Length > 0) | ||||
|                     { | ||||
|                         cts.Cancel(); | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     await Task.Delay(100, cts.Token); | ||||
|                     cts.Cancel(); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 await task; | ||||
|             } | ||||
|             catch (OperationCanceledException) | ||||
|             { | ||||
|                 // It's expected to fail | ||||
|                 await Task.Delay(100, cts.Token); | ||||
|             } | ||||
|  | ||||
|             var stdOut = stdOutBuffer.ToString(); | ||||
|  | ||||
|             // Assert | ||||
|             stdOut.Should().Contain("Attach debugger to"); | ||||
|  | ||||
|             TestOutput.WriteLine(stdOut); | ||||
|             await task; | ||||
|         } | ||||
|         catch (OperationCanceledException) | ||||
|         { | ||||
|             // It's expected to fail | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Preview_directive_can_be_specified_to_print_command_input() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|         var stdOut = stdOutBuffer.ToString(); | ||||
|  | ||||
|         // Assert | ||||
|         stdOut.Should().Contain("Attach debugger to"); | ||||
|  | ||||
|         TestOutput.WriteLine(stdOut); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task Preview_directive_can_be_specified_to_print_command_input() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command(""cmd"")] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -82,31 +82,30 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .AllowPreviewMode() | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .AllowPreviewMode() | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"[preview]", "cmd", "param", "-abc", "--option", "foo"}, | ||||
|                 new Dictionary<string, string> | ||||
|                 { | ||||
|                     ["ENV_QOP"] = "hello", | ||||
|                     ["ENV_KIL"] = "world" | ||||
|                 } | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"[preview]", "cmd", "param", "-abc", "--option", "foo"}, | ||||
|             new Dictionary<string, string> | ||||
|             { | ||||
|                 ["ENV_QOP"] = "hello", | ||||
|                 ["ENV_KIL"] = "world" | ||||
|             } | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ContainAllInOrder( | ||||
|                 "cmd", "<param>", "[-a]", "[-b]", "[-c]", "[--option \"foo\"]", | ||||
|                 "ENV_QOP", "=", "\"hello\"", | ||||
|                 "ENV_KIL", "=", "\"world\"" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ContainAllInOrder( | ||||
|             "cmd", "<param>", "[-a]", "[-b]", "[-c]", "[--option \"foo\"]", | ||||
|             "ENV_QOP", "=", "\"hello\"", | ||||
|             "ENV_KIL", "=", "\"world\"" | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| @@ -10,22 +10,22 @@ using FluentAssertions; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| { | ||||
|     public class EnvironmentSpecs : SpecsBase | ||||
|     { | ||||
|         public EnvironmentSpecs(ITestOutputHelper testOutput) | ||||
|             : base(testOutput) | ||||
|         { | ||||
|         } | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_can_fall_back_to_an_environment_variable() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
| public class EnvironmentSpecs : SpecsBase | ||||
| { | ||||
|     public EnvironmentSpecs(ITestOutputHelper testOutput) | ||||
|         : base(testOutput) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task Option_can_fall_back_to_an_environment_variable() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -40,34 +40,34 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string> | ||||
|                 { | ||||
|                     ["ENV_FOO"] = "bar" | ||||
|                 } | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string> | ||||
|             { | ||||
|                 ["ENV_FOO"] = "bar" | ||||
|             } | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("bar"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("bar"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_does_not_fall_back_to_an_environment_variable_if_a_value_is_provided_through_arguments() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_does_not_fall_back_to_an_environment_variable_if_a_value_is_provided_through_arguments() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -82,34 +82,34 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo", "baz"}, | ||||
|                 new Dictionary<string, string> | ||||
|                 { | ||||
|                     ["ENV_FOO"] = "bar" | ||||
|                 } | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo", "baz"}, | ||||
|             new Dictionary<string, string> | ||||
|             { | ||||
|                 ["ENV_FOO"] = "bar" | ||||
|             } | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("baz"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("baz"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_of_non_scalar_type_can_receive_multiple_values_from_an_environment_variable() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_of_non_scalar_type_can_receive_multiple_values_from_an_environment_variable() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -126,37 +126,37 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string> | ||||
|                 { | ||||
|                     ["ENV_FOO"] = $"bar{Path.PathSeparator}baz" | ||||
|                 } | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string> | ||||
|             { | ||||
|                 ["ENV_FOO"] = $"bar{Path.PathSeparator}baz" | ||||
|             } | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "bar", | ||||
|                 "baz" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "bar", | ||||
|             "baz" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_of_scalar_type_always_receives_a_single_value_from_an_environment_variable() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_of_scalar_type_always_receives_a_single_value_from_an_environment_variable() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -171,34 +171,34 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string> | ||||
|                 { | ||||
|                     ["ENV_FOO"] = $"bar{Path.PathSeparator}baz" | ||||
|                 } | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string> | ||||
|             { | ||||
|                 ["ENV_FOO"] = $"bar{Path.PathSeparator}baz" | ||||
|             } | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be($"bar{Path.PathSeparator}baz"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be($"bar{Path.PathSeparator}baz"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Environment_variables_are_matched_case_sensitively() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Environment_variables_are_matched_case_sensitively() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -213,48 +213,47 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string> | ||||
|                 { | ||||
|                     ["ENV_foo"] = "baz", | ||||
|                     ["ENV_FOO"] = "bar", | ||||
|                     ["env_FOO"] = "qop" | ||||
|                 } | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string> | ||||
|             { | ||||
|                 ["ENV_foo"] = "baz", | ||||
|                 ["ENV_FOO"] = "bar", | ||||
|                 ["env_FOO"] = "qop" | ||||
|             } | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("bar"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("bar"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Environment_variables_are_extracted_automatically() | ||||
|         { | ||||
|             // Ensures that the environment variables are properly obtained from | ||||
|             // System.Environment when they are not provided explicitly to CliApplication. | ||||
|     [Fact] | ||||
|     public async Task Environment_variables_are_extracted_automatically() | ||||
|     { | ||||
|         // Ensures that the environment variables are properly obtained from | ||||
|         // System.Environment when they are not provided explicitly to CliApplication. | ||||
|  | ||||
|             // Arrange | ||||
|             var command = Cli.Wrap("dotnet") | ||||
|                 .WithArguments(a => a | ||||
|                     .Add(Dummy.Program.Location) | ||||
|                     .Add("env-test")) | ||||
|                 .WithEnvironmentVariables(e => e | ||||
|                     .Set("ENV_TARGET", "Mars")); | ||||
|         // Arrange | ||||
|         var command = Cli.Wrap("dotnet") | ||||
|             .WithArguments(a => a | ||||
|                 .Add(Dummy.Program.Location) | ||||
|                 .Add("env-test")) | ||||
|             .WithEnvironmentVariables(e => e | ||||
|                 .Set("ENV_TARGET", "Mars")); | ||||
|  | ||||
|             // Act | ||||
|             var result = await command.ExecuteBufferedAsync(); | ||||
|         // Act | ||||
|         var result = await command.ExecuteBufferedAsync(); | ||||
|  | ||||
|             // Assert | ||||
|             result.StandardOutput.Trim().Should().Be("Hello Mars!"); | ||||
|         } | ||||
|         // Assert | ||||
|         result.StandardOutput.Trim().Should().Be("Hello Mars!"); | ||||
|     } | ||||
| } | ||||
| @@ -7,22 +7,22 @@ using FluentAssertions; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| { | ||||
|     public class ErrorReportingSpecs : SpecsBase | ||||
|     { | ||||
|         public ErrorReportingSpecs(ITestOutputHelper testOutput) | ||||
|             : base(testOutput) | ||||
|         { | ||||
|         } | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Command_can_throw_an_exception_which_exits_with_a_stacktrace() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
| public class ErrorReportingSpecs : SpecsBase | ||||
| { | ||||
|     public ErrorReportingSpecs(ITestOutputHelper testOutput) | ||||
|         : base(testOutput) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task Command_can_throw_an_exception_which_exits_with_a_stacktrace() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -31,36 +31,36 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdOut.Should().BeEmpty(); | ||||
|             stdErr.Should().ContainAllInOrder( | ||||
|                 "System.Exception", "Something went wrong", | ||||
|                 "at", "CliFx." | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdOut.Should().BeEmpty(); | ||||
|         stdErr.Should().ContainAllInOrder( | ||||
|             "System.Exception", "Something went wrong", | ||||
|             "at", "CliFx." | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Command_can_throw_an_exception_with_an_inner_exception_which_exits_with_a_stacktrace() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Command_can_throw_an_exception_with_an_inner_exception_which_exits_with_a_stacktrace() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -69,37 +69,37 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdOut.Should().BeEmpty(); | ||||
|             stdErr.Should().ContainAllInOrder( | ||||
|                 "System.Exception", "Something went wrong", | ||||
|                 "System.Exception", "Another exception", | ||||
|                 "at", "CliFx." | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdOut.Should().BeEmpty(); | ||||
|         stdErr.Should().ContainAllInOrder( | ||||
|             "System.Exception", "Something went wrong", | ||||
|             "System.Exception", "Another exception", | ||||
|             "at", "CliFx." | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Command_can_throw_a_special_exception_which_exits_with_specified_code_and_message() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Command_can_throw_a_special_exception_which_exits_with_specified_code_and_message() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -108,33 +108,33 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(69); | ||||
|             stdOut.Should().BeEmpty(); | ||||
|             stdErr.Trim().Should().Be("Something went wrong"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(69); | ||||
|         stdOut.Should().BeEmpty(); | ||||
|         stdErr.Trim().Should().Be("Something went wrong"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Command_can_throw_a_special_exception_without_message_which_exits_with_a_stacktrace() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Command_can_throw_a_special_exception_without_message_which_exits_with_a_stacktrace() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -143,36 +143,36 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(69); | ||||
|             stdOut.Should().BeEmpty(); | ||||
|             stdErr.Should().ContainAllInOrder( | ||||
|                 "CliFx.Exceptions.CommandException", | ||||
|                 "at", "CliFx." | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(69); | ||||
|         stdOut.Should().BeEmpty(); | ||||
|         stdErr.Should().ContainAllInOrder( | ||||
|             "CliFx.Exceptions.CommandException", | ||||
|             "at", "CliFx." | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Command_can_throw_a_special_exception_which_prints_help_text_before_exiting() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Command_can_throw_a_special_exception_which_prints_help_text_before_exiting() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -181,25 +181,24 @@ public class Command : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .SetDescription("This will be in help text") | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .SetDescription("This will be in help text") | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(69); | ||||
|             stdOut.Should().Contain("This will be in help text"); | ||||
|             stdErr.Trim().Should().Be("Something went wrong"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(69); | ||||
|         stdOut.Should().Contain("This will be in help text"); | ||||
|         stdErr.Trim().Should().Be("Something went wrong"); | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -7,22 +7,22 @@ using FluentAssertions; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| { | ||||
|     public class OptionBindingSpecs : SpecsBase | ||||
|     { | ||||
|         public OptionBindingSpecs(ITestOutputHelper testOutput) | ||||
|             : base(testOutput) | ||||
|         { | ||||
|         } | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_is_bound_from_an_argument_matching_its_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
| public class OptionBindingSpecs : SpecsBase | ||||
| { | ||||
|     public OptionBindingSpecs(ITestOutputHelper testOutput) | ||||
|         : base(testOutput) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task Option_is_bound_from_an_argument_matching_its_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -36,31 +36,31 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("True"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("True"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_is_bound_from_an_argument_matching_its_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_is_bound_from_an_argument_matching_its_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -74,31 +74,31 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"-f"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"-f"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("True"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("True"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_is_bound_from_a_set_of_arguments_matching_its_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_is_bound_from_a_set_of_arguments_matching_its_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -117,34 +117,34 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo", "one", "--bar", "two"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo", "one", "--bar", "two"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "Foo = one", | ||||
|                 "Bar = two" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "Foo = one", | ||||
|             "Bar = two" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_is_bound_from_a_set_of_arguments_matching_its_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_is_bound_from_a_set_of_arguments_matching_its_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -163,34 +163,34 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"-f", "one", "-b", "two"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"-f", "one", "-b", "two"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "Foo = one", | ||||
|                 "Bar = two" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "Foo = one", | ||||
|             "Bar = two" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_is_bound_from_a_stack_of_arguments_matching_its_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_is_bound_from_a_stack_of_arguments_matching_its_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -209,34 +209,34 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"-fb", "value"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"-fb", "value"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "Foo = ", | ||||
|                 "Bar = value" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "Foo = ", | ||||
|             "Bar = value" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_of_non_scalar_type_is_bound_from_a_set_of_arguments_matching_its_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_of_non_scalar_type_is_bound_from_a_set_of_arguments_matching_its_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -252,35 +252,35 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo", "one", "two", "three"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo", "one", "two", "three"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "one", | ||||
|                 "two", | ||||
|                 "three" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "one", | ||||
|             "two", | ||||
|             "three" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_of_non_scalar_type_is_bound_from_a_set_of_arguments_matching_its_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_of_non_scalar_type_is_bound_from_a_set_of_arguments_matching_its_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -296,35 +296,35 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"-f", "one", "two", "three"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"-f", "one", "two", "three"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "one", | ||||
|                 "two", | ||||
|                 "three" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "one", | ||||
|             "two", | ||||
|             "three" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -340,35 +340,35 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo", "one", "--foo", "two", "--foo", "three"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo", "one", "--foo", "two", "--foo", "three"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "one", | ||||
|                 "two", | ||||
|                 "three" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "one", | ||||
|             "two", | ||||
|             "three" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -384,35 +384,35 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"-f", "one", "-f", "two", "-f", "three"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"-f", "one", "-f", "two", "-f", "three"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "one", | ||||
|                 "two", | ||||
|                 "three" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "one", | ||||
|             "two", | ||||
|             "three" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_name_or_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_of_non_scalar_type_is_bound_from_multiple_sets_of_arguments_matching_its_name_or_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -428,35 +428,35 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo", "one", "-f", "two", "--foo", "three"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo", "one", "-f", "two", "--foo", "three"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "one", | ||||
|                 "two", | ||||
|                 "three" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "one", | ||||
|             "two", | ||||
|             "three" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_is_not_bound_if_there_are_no_arguments_matching_its_name_or_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_is_not_bound_if_there_are_no_arguments_matching_its_name_or_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -475,34 +475,34 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo", "one"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo", "one"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "Foo = one", | ||||
|                 "Bar = hello" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "Foo = one", | ||||
|             "Bar = hello" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_binding_does_not_consider_a_negative_number_as_an_option_name_or_short_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_binding_does_not_consider_a_negative_number_as_an_option_name_or_short_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -517,31 +517,31 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo", "-13"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo", "-13"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("-13"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("-13"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_binding_fails_if_a_required_option_has_not_been_provided() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_binding_fails_if_a_required_option_has_not_been_provided() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -551,31 +551,31 @@ public class Command : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("Missing required option(s)"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("Missing required option(s)"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_binding_fails_if_a_required_option_has_been_provided_with_an_empty_value() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_binding_fails_if_a_required_option_has_been_provided_with_an_empty_value() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -585,31 +585,31 @@ public class Command : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("Missing required option(s)"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("Missing required option(s)"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_binding_fails_if_a_required_option_of_non_scalar_type_has_not_been_provided_with_at_least_one_value() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_binding_fails_if_a_required_option_of_non_scalar_type_has_not_been_provided_with_at_least_one_value() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -619,31 +619,31 @@ public class Command : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("Missing required option(s)"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("Missing required option(s)"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_binding_fails_if_one_of_the_provided_option_names_is_not_recognized() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_binding_fails_if_one_of_the_provided_option_names_is_not_recognized() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -653,31 +653,31 @@ public class Command : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo", "one", "--bar", "two"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo", "one", "--bar", "two"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("Unrecognized option(s)"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("Unrecognized option(s)"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Option_binding_fails_if_an_option_of_scalar_type_has_been_provided_with_multiple_values() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Option_binding_fails_if_an_option_of_scalar_type_has_been_provided_with_multiple_values() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -687,22 +687,21 @@ public class Command : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"--foo", "one", "two", "three"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"--foo", "one", "two", "three"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("expects a single argument, but provided with multiple"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("expects a single argument, but provided with multiple"); | ||||
|     } | ||||
| } | ||||
| @@ -6,22 +6,22 @@ using FluentAssertions; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| { | ||||
|     public class ParameterBindingSpecs : SpecsBase | ||||
|     { | ||||
|         public ParameterBindingSpecs(ITestOutputHelper testOutput) | ||||
|             : base(testOutput) | ||||
|         { | ||||
|         } | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Parameter_is_bound_from_an_argument_matching_its_order() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
| public class ParameterBindingSpecs : SpecsBase | ||||
| { | ||||
|     public ParameterBindingSpecs(ITestOutputHelper testOutput) | ||||
|         : base(testOutput) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task Parameter_is_bound_from_an_argument_matching_its_order() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -40,34 +40,34 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"one", "two"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"one", "two"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "Foo = one", | ||||
|                 "Bar = two" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "Foo = one", | ||||
|             "Bar = two" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Parameter_of_non_scalar_type_is_bound_from_remaining_non_option_arguments() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Parameter_of_non_scalar_type_is_bound_from_remaining_non_option_arguments() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -95,37 +95,37 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"one", "two", "three", "four", "five", "--boo", "xxx"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"one", "two", "three", "four", "five", "--boo", "xxx"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Should().ConsistOfLines( | ||||
|                 "Foo = one", | ||||
|                 "Bar = two", | ||||
|                 "Baz = three", | ||||
|                 "Baz = four", | ||||
|                 "Baz = five" | ||||
|             ); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Should().ConsistOfLines( | ||||
|             "Foo = one", | ||||
|             "Bar = two", | ||||
|             "Baz = three", | ||||
|             "Baz = four", | ||||
|             "Baz = five" | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Parameter_binding_fails_if_one_of_the_parameters_has_not_been_provided() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Parameter_binding_fails_if_one_of_the_parameters_has_not_been_provided() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -138,31 +138,31 @@ public class Command : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"one"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"one"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("Missing parameter(s)"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("Missing parameter(s)"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Parameter_binding_fails_if_a_parameter_of_non_scalar_type_has_not_been_provided_with_at_least_one_value() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Parameter_binding_fails_if_a_parameter_of_non_scalar_type_has_not_been_provided_with_at_least_one_value() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -175,31 +175,31 @@ public class Command : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"one"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"one"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("Missing parameter(s)"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("Missing parameter(s)"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Parameter_binding_fails_if_one_of_the_provided_parameters_is_unexpected() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Parameter_binding_fails_if_one_of_the_provided_parameters_is_unexpected() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -212,22 +212,21 @@ public class Command : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"one", "two", "three"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"one", "two", "three"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("Unexpected parameter(s)"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("Unexpected parameter(s)"); | ||||
|     } | ||||
| } | ||||
| @@ -6,22 +6,22 @@ using FluentAssertions; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| { | ||||
|     public class RoutingSpecs : SpecsBase | ||||
|     { | ||||
|         public RoutingSpecs(ITestOutputHelper testOutput) | ||||
|             : base(testOutput) | ||||
|         { | ||||
|         } | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Default_command_is_executed_if_provided_arguments_do_not_match_any_named_command() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandTypes = DynamicCommandBuilder.CompileMany( | ||||
|                 // language=cs | ||||
|                 @" | ||||
| public class RoutingSpecs : SpecsBase | ||||
| { | ||||
|     public RoutingSpecs(ITestOutputHelper testOutput) | ||||
|         : base(testOutput) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task Default_command_is_executed_if_provided_arguments_do_not_match_any_named_command() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandTypes = DynamicCommandBuilder.CompileMany( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class DefaultCommand : ICommand | ||||
| { | ||||
| @@ -53,31 +53,31 @@ public class NamedChildCommand : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommands(commandTypes) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommands(commandTypes) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("default"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("default"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Specific_named_command_is_executed_if_provided_arguments_match_its_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandTypes = DynamicCommandBuilder.CompileMany( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Specific_named_command_is_executed_if_provided_arguments_match_its_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandTypes = DynamicCommandBuilder.CompileMany( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class DefaultCommand : ICommand | ||||
| { | ||||
| @@ -109,31 +109,31 @@ public class NamedChildCommand : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommands(commandTypes) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommands(commandTypes) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"cmd"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"cmd"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("cmd"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("cmd"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Specific_named_child_command_is_executed_if_provided_arguments_match_its_name() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandTypes = DynamicCommandBuilder.CompileMany( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Specific_named_child_command_is_executed_if_provided_arguments_match_its_name() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandTypes = DynamicCommandBuilder.CompileMany( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class DefaultCommand : ICommand | ||||
| { | ||||
| @@ -165,22 +165,21 @@ public class NamedChildCommand : ICommand | ||||
| } | ||||
| "); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommands(commandTypes) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommands(commandTypes) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 new[] {"cmd", "child"}, | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             new[] {"cmd", "child"}, | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("cmd child"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("cmd child"); | ||||
|     } | ||||
| } | ||||
| @@ -3,21 +3,20 @@ using CliFx.Infrastructure; | ||||
| using CliFx.Tests.Utils.Extensions; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
| public abstract class SpecsBase : IDisposable | ||||
| { | ||||
|     public abstract class SpecsBase : IDisposable | ||||
|     public ITestOutputHelper TestOutput { get; } | ||||
|  | ||||
|     public FakeInMemoryConsole FakeConsole { get; } = new(); | ||||
|  | ||||
|     protected SpecsBase(ITestOutputHelper testOutput) => | ||||
|         TestOutput = testOutput; | ||||
|  | ||||
|     public void Dispose() | ||||
|     { | ||||
|         public ITestOutputHelper TestOutput { get; } | ||||
|  | ||||
|         public FakeInMemoryConsole FakeConsole { get; } = new(); | ||||
|  | ||||
|         protected SpecsBase(ITestOutputHelper testOutput) => | ||||
|             TestOutput = testOutput; | ||||
|  | ||||
|         public void Dispose() | ||||
|         { | ||||
|             FakeConsole.DumpToTestOutput(TestOutput); | ||||
|             FakeConsole.Dispose(); | ||||
|         } | ||||
|         FakeConsole.DumpToTestOutput(TestOutput); | ||||
|         FakeConsole.Dispose(); | ||||
|     } | ||||
| } | ||||
| @@ -7,22 +7,22 @@ using FluentAssertions; | ||||
| using Xunit; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests | ||||
| { | ||||
|     public class TypeActivationSpecs : SpecsBase | ||||
|     { | ||||
|         public TypeActivationSpecs(ITestOutputHelper testOutput) | ||||
|             : base(testOutput) | ||||
|         { | ||||
|         } | ||||
| namespace CliFx.Tests; | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Default_type_activator_can_initialize_a_type_if_it_has_a_parameterless_constructor() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
| public class TypeActivationSpecs : SpecsBase | ||||
| { | ||||
|     public TypeActivationSpecs(ITestOutputHelper testOutput) | ||||
|         : base(testOutput) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task Default_type_activator_can_initialize_a_type_if_it_has_a_parameterless_constructor() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -33,32 +33,32 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .UseTypeActivator(new DefaultTypeActivator()) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .UseTypeActivator(new DefaultTypeActivator()) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("foo"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("foo"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Default_type_activator_fails_if_the_type_does_not_have_a_parameterless_constructor() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Default_type_activator_fails_if_the_type_does_not_have_a_parameterless_constructor() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -67,32 +67,32 @@ public class Command : ICommand | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .UseTypeActivator(new DefaultTypeActivator()) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .UseTypeActivator(new DefaultTypeActivator()) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("Failed to create an instance of type"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("Failed to create an instance of type"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Delegate_type_activator_can_initialize_a_type_using_a_custom_function() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Delegate_type_activator_can_initialize_a_type_using_a_custom_function() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -107,32 +107,32 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .UseTypeActivator(type => Activator.CreateInstance(type, "hello world")!) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .UseTypeActivator(type => Activator.CreateInstance(type, "hello world")!) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdOut = FakeConsole.ReadOutputString(); | ||||
|         var stdOut = FakeConsole.ReadOutputString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().Be(0); | ||||
|             stdOut.Trim().Should().Be("hello world"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().Be(0); | ||||
|         stdOut.Trim().Should().Be("hello world"); | ||||
|     } | ||||
|  | ||||
|         [Fact] | ||||
|         public async Task Delegate_type_activator_fails_if_the_underlying_function_returns_null() | ||||
|         { | ||||
|             // Arrange | ||||
|             var commandType = DynamicCommandBuilder.Compile( | ||||
|                 // language=cs | ||||
|                 @" | ||||
|     [Fact] | ||||
|     public async Task Delegate_type_activator_fails_if_the_underlying_function_returns_null() | ||||
|     { | ||||
|         // Arrange | ||||
|         var commandType = DynamicCommandBuilder.Compile( | ||||
|             // language=cs | ||||
|             @" | ||||
| [Command] | ||||
| public class Command : ICommand | ||||
| { | ||||
| @@ -143,23 +143,22 @@ public class Command : ICommand | ||||
|     } | ||||
| }"); | ||||
|  | ||||
|             var application = new CliApplicationBuilder() | ||||
|                 .AddCommand(commandType) | ||||
|                 .UseConsole(FakeConsole) | ||||
|                 .UseTypeActivator(_ => null!) | ||||
|                 .Build(); | ||||
|         var application = new CliApplicationBuilder() | ||||
|             .AddCommand(commandType) | ||||
|             .UseConsole(FakeConsole) | ||||
|             .UseTypeActivator(_ => null!) | ||||
|             .Build(); | ||||
|  | ||||
|             // Act | ||||
|             var exitCode = await application.RunAsync( | ||||
|                 Array.Empty<string>(), | ||||
|                 new Dictionary<string, string>() | ||||
|             ); | ||||
|         // Act | ||||
|         var exitCode = await application.RunAsync( | ||||
|             Array.Empty<string>(), | ||||
|             new Dictionary<string, string>() | ||||
|         ); | ||||
|  | ||||
|             var stdErr = FakeConsole.ReadErrorString(); | ||||
|         var stdErr = FakeConsole.ReadErrorString(); | ||||
|  | ||||
|             // Assert | ||||
|             exitCode.Should().NotBe(0); | ||||
|             stdErr.Should().Contain("Failed to create an instance of type"); | ||||
|         } | ||||
|         // Assert | ||||
|         exitCode.Should().NotBe(0); | ||||
|         stdErr.Should().Contain("Failed to create an instance of type"); | ||||
|     } | ||||
| } | ||||
| @@ -3,140 +3,133 @@ using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Reflection; | ||||
| using Basic.Reference.Assemblies; | ||||
| using Microsoft.CodeAnalysis; | ||||
| using Microsoft.CodeAnalysis.CSharp; | ||||
| using Microsoft.CodeAnalysis.Text; | ||||
|  | ||||
| namespace CliFx.Tests.Utils | ||||
| namespace CliFx.Tests.Utils; | ||||
|  | ||||
| // This class uses Roslyn to compile commands dynamically. | ||||
| // | ||||
| // It allows us to collocate commands with tests more | ||||
| // easily, which helps a lot when reasoning about them. | ||||
| // Unfortunately, this comes at a cost of static typing, | ||||
| // but this is still a worthwhile trade off. | ||||
| // | ||||
| // Maybe one day C# will allow declaring classes inside | ||||
| // methods and doing this will no longer be necessary. | ||||
| // Language proposal: https://github.com/dotnet/csharplang/discussions/130 | ||||
| internal static class DynamicCommandBuilder | ||||
| { | ||||
|     // This class uses Roslyn to compile commands dynamically. | ||||
|     // | ||||
|     // It allows us to collocate commands with tests more | ||||
|     // easily, which helps a lot when reasoning about them. | ||||
|     // Unfortunately, this comes at a cost of static typing, | ||||
|     // but this is still a worthwhile trade off. | ||||
|     // | ||||
|     // Maybe one day C# will allow declaring classes inside | ||||
|     // methods and doing this will no longer be necessary. | ||||
|     // Language proposal: https://github.com/dotnet/csharplang/discussions/130 | ||||
|     internal static class DynamicCommandBuilder | ||||
|     public static IReadOnlyList<Type> CompileMany(string sourceCode) | ||||
|     { | ||||
|         public static IReadOnlyList<Type> CompileMany(string sourceCode) | ||||
|         // Get default system namespaces | ||||
|         var defaultSystemNamespaces = new[] | ||||
|         { | ||||
|             // Get default system namespaces | ||||
|             var defaultSystemNamespaces = new[] | ||||
|             { | ||||
|                 "System", | ||||
|                 "System.Collections", | ||||
|                 "System.Collections.Generic", | ||||
|                 "System.Linq", | ||||
|                 "System.Threading.Tasks", | ||||
|                 "System.Globalization" | ||||
|             }; | ||||
|             "System", | ||||
|             "System.Collections", | ||||
|             "System.Collections.Generic", | ||||
|             "System.Linq", | ||||
|             "System.Threading.Tasks", | ||||
|             "System.Globalization" | ||||
|         }; | ||||
|  | ||||
|             // Get default CliFx namespaces | ||||
|             var defaultCliFxNamespaces = typeof(ICommand) | ||||
|                 .Assembly | ||||
|                 .GetTypes() | ||||
|                 .Where(t => t.IsPublic) | ||||
|                 .Select(t => t.Namespace) | ||||
|                 .Distinct() | ||||
|                 .ToArray(); | ||||
|         // Get default CliFx namespaces | ||||
|         var defaultCliFxNamespaces = typeof(ICommand) | ||||
|             .Assembly | ||||
|             .GetTypes() | ||||
|             .Where(t => t.IsPublic) | ||||
|             .Select(t => t.Namespace) | ||||
|             .Distinct() | ||||
|             .ToArray(); | ||||
|  | ||||
|             // Append default imports to the source code | ||||
|             var sourceCodeWithUsings = | ||||
|                 string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};")) + | ||||
|                 string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};")) + | ||||
|         // Append default imports to the source code | ||||
|         var sourceCodeWithUsings = | ||||
|             string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};")) + | ||||
|             string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};")) + | ||||
|             Environment.NewLine + | ||||
|             sourceCode; | ||||
|  | ||||
|         // Parse the source code | ||||
|         var ast = SyntaxFactory.ParseSyntaxTree( | ||||
|             SourceText.From(sourceCodeWithUsings), | ||||
|             CSharpParseOptions.Default | ||||
|         ); | ||||
|  | ||||
|         // Compile the code to IL | ||||
|         var compilation = CSharpCompilation.Create( | ||||
|             "CliFxTests_DynamicAssembly_" + Guid.NewGuid(), | ||||
|             new[] {ast}, | ||||
|             ReferenceAssemblies.Net50 | ||||
|                 .Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)) | ||||
|                 .Append(MetadataReference.CreateFromFile(typeof(DynamicCommandBuilder).Assembly.Location)), | ||||
|             // DLL to avoid having to define the Main() method | ||||
|             new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) | ||||
|         ); | ||||
|  | ||||
|         var compilationErrors = compilation | ||||
|             .GetDiagnostics() | ||||
|             .Where(d => d.Severity >= DiagnosticSeverity.Error) | ||||
|             .ToArray(); | ||||
|  | ||||
|         if (compilationErrors.Any()) | ||||
|         { | ||||
|             throw new InvalidOperationException( | ||||
|                 "Failed to compile code." + | ||||
|                 Environment.NewLine + | ||||
|                 sourceCode; | ||||
|  | ||||
|             // Parse the source code | ||||
|             var ast = SyntaxFactory.ParseSyntaxTree( | ||||
|                 SourceText.From(sourceCodeWithUsings), | ||||
|                 CSharpParseOptions.Default | ||||
|                 string.Join(Environment.NewLine, compilationErrors.Select(e => e.ToString())) | ||||
|             ); | ||||
|  | ||||
|             // Compile the code to IL | ||||
|             var compilation = CSharpCompilation.Create( | ||||
|                 "CliFxTests_DynamicAssembly_" + Guid.NewGuid(), | ||||
|                 new[] {ast}, | ||||
|                 new[] | ||||
|                 { | ||||
|                     MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location), | ||||
|                     MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location), | ||||
|                     MetadataReference.CreateFromFile(typeof(object).Assembly.Location), | ||||
|                     MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), | ||||
|                     MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), | ||||
|                     MetadataReference.CreateFromFile(typeof(DynamicCommandBuilder).Assembly.Location), | ||||
|                     MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location) | ||||
|                 }, | ||||
|                 // DLL to avoid having to define the Main() method | ||||
|                 new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) | ||||
|             ); | ||||
|  | ||||
|             var compilationErrors = compilation | ||||
|                 .GetDiagnostics() | ||||
|                 .Where(d => d.Severity >= DiagnosticSeverity.Error) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             if (compilationErrors.Any()) | ||||
|             { | ||||
|                 throw new InvalidOperationException( | ||||
|                     "Failed to compile code." + | ||||
|                     Environment.NewLine + | ||||
|                     string.Join(Environment.NewLine, compilationErrors.Select(e => e.ToString())) | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             // Emit the code to an in-memory buffer | ||||
|             using var buffer = new MemoryStream(); | ||||
|             var emit = compilation.Emit(buffer); | ||||
|  | ||||
|             var emitErrors = emit | ||||
|                 .Diagnostics | ||||
|                 .Where(d => d.Severity >= DiagnosticSeverity.Error) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             if (emitErrors.Any()) | ||||
|             { | ||||
|                 throw new InvalidOperationException( | ||||
|                     "Failed to emit code." + | ||||
|                     Environment.NewLine + | ||||
|                     string.Join(Environment.NewLine, emitErrors.Select(e => e.ToString())) | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             // Load the generated assembly | ||||
|             var generatedAssembly = Assembly.Load(buffer.ToArray()); | ||||
|  | ||||
|             // Return all defined commands | ||||
|             var commandTypes = generatedAssembly | ||||
|                 .GetTypes() | ||||
|                 .Where(t => t.IsAssignableTo(typeof(ICommand))) | ||||
|                 .ToArray(); | ||||
|  | ||||
|             if (commandTypes.Length <= 0) | ||||
|             { | ||||
|                 throw new InvalidOperationException( | ||||
|                     "There are no command definitions in the provide source code." | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             return commandTypes; | ||||
|         } | ||||
|  | ||||
|         public static Type Compile(string sourceCode) | ||||
|         // Emit the code to an in-memory buffer | ||||
|         using var buffer = new MemoryStream(); | ||||
|         var emit = compilation.Emit(buffer); | ||||
|  | ||||
|         var emitErrors = emit | ||||
|             .Diagnostics | ||||
|             .Where(d => d.Severity >= DiagnosticSeverity.Error) | ||||
|             .ToArray(); | ||||
|  | ||||
|         if (emitErrors.Any()) | ||||
|         { | ||||
|             var commandTypes = CompileMany(sourceCode); | ||||
|  | ||||
|             if (commandTypes.Count > 1) | ||||
|             { | ||||
|                 throw new InvalidOperationException( | ||||
|                     "There are more than one command definitions in the provide source code." | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             return commandTypes.Single(); | ||||
|             throw new InvalidOperationException( | ||||
|                 "Failed to emit code." + | ||||
|                 Environment.NewLine + | ||||
|                 string.Join(Environment.NewLine, emitErrors.Select(e => e.ToString())) | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         // Load the generated assembly | ||||
|         var generatedAssembly = Assembly.Load(buffer.ToArray()); | ||||
|  | ||||
|         // Return all defined commands | ||||
|         var commandTypes = generatedAssembly | ||||
|             .GetTypes() | ||||
|             .Where(t => t.IsAssignableTo(typeof(ICommand)) && !t.IsAbstract) | ||||
|             .ToArray(); | ||||
|  | ||||
|         if (commandTypes.Length <= 0) | ||||
|         { | ||||
|             throw new InvalidOperationException( | ||||
|                 "There are no command definitions in the provided source code." | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         return commandTypes; | ||||
|     } | ||||
|  | ||||
|     public static Type Compile(string sourceCode) | ||||
|     { | ||||
|         var commandTypes = CompileMany(sourceCode); | ||||
|  | ||||
|         if (commandTypes.Count > 1) | ||||
|         { | ||||
|             throw new InvalidOperationException( | ||||
|                 "There are more than one command definitions in the provided source code." | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         return commandTypes.Single(); | ||||
|     } | ||||
| } | ||||
| @@ -1,54 +1,51 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using FluentAssertions; | ||||
| using FluentAssertions.Collections; | ||||
| using FluentAssertions.Execution; | ||||
| using FluentAssertions.Primitives; | ||||
|  | ||||
| namespace CliFx.Tests.Utils.Extensions | ||||
| namespace CliFx.Tests.Utils.Extensions; | ||||
|  | ||||
| internal static class AssertionExtensions | ||||
| { | ||||
|     internal static class AssertionExtensions | ||||
|     public static void ConsistOfLines( | ||||
|         this StringAssertions assertions, | ||||
|         IEnumerable<string> lines) | ||||
|     { | ||||
|         public static AndConstraint<StringCollectionAssertions> ConsistOfLines( | ||||
|             this StringAssertions assertions, | ||||
|             IEnumerable<string> lines) | ||||
|         var actualLines = assertions.Subject.Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries); | ||||
|         actualLines.Should().Equal(lines); | ||||
|     } | ||||
|  | ||||
|     public static void ConsistOfLines( | ||||
|         this StringAssertions assertions, | ||||
|         params string[] lines) => | ||||
|         assertions.ConsistOfLines((IEnumerable<string>) lines); | ||||
|  | ||||
|     public static AndConstraint<StringAssertions> ContainAllInOrder( | ||||
|         this StringAssertions assertions, | ||||
|         IEnumerable<string> values) | ||||
|     { | ||||
|         var lastIndex = 0; | ||||
|  | ||||
|         foreach (var value in values) | ||||
|         { | ||||
|             var actualLines = assertions.Subject.Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries); | ||||
|             var index = assertions.Subject.IndexOf(value, lastIndex, StringComparison.Ordinal); | ||||
|  | ||||
|             return actualLines.Should().Equal(lines); | ||||
|         } | ||||
|  | ||||
|         public static AndConstraint<StringCollectionAssertions> ConsistOfLines( | ||||
|             this StringAssertions assertions, | ||||
|             params string[] lines) => | ||||
|             assertions.ConsistOfLines((IEnumerable<string>) lines); | ||||
|  | ||||
|         public static AndConstraint<StringAssertions> ContainAllInOrder( | ||||
|             this StringAssertions assertions, | ||||
|             IEnumerable<string> values) | ||||
|         { | ||||
|             var lastIndex = 0; | ||||
|  | ||||
|             foreach (var value in values) | ||||
|             if (index < 0) | ||||
|             { | ||||
|                 var index = assertions.Subject.IndexOf(value, lastIndex, StringComparison.Ordinal); | ||||
|  | ||||
|                 if (index < 0) | ||||
|                 { | ||||
|                     Execute.Assertion.FailWith( | ||||
|                         $"Expected string '{assertions.Subject}' to contain '{value}' after position {lastIndex}." | ||||
|                     ); | ||||
|                 } | ||||
|  | ||||
|                 lastIndex = index; | ||||
|                 Execute.Assertion.FailWith( | ||||
|                     $"Expected string '{assertions.Subject}' to contain '{value}' after position {lastIndex}." | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             return new(assertions); | ||||
|             lastIndex = index; | ||||
|         } | ||||
|  | ||||
|         public static AndConstraint<StringAssertions> ContainAllInOrder( | ||||
|             this StringAssertions assertions, | ||||
|             params string[] values) => | ||||
|             assertions.ContainAllInOrder((IEnumerable<string>) values); | ||||
|         return new(assertions); | ||||
|     } | ||||
|  | ||||
|     public static AndConstraint<StringAssertions> ContainAllInOrder( | ||||
|         this StringAssertions assertions, | ||||
|         params string[] values) => | ||||
|         assertions.ContainAllInOrder((IEnumerable<string>) values); | ||||
| } | ||||
| @@ -1,17 +1,16 @@ | ||||
| using CliFx.Infrastructure; | ||||
| using Xunit.Abstractions; | ||||
|  | ||||
| namespace CliFx.Tests.Utils.Extensions | ||||
| { | ||||
|     internal static class ConsoleExtensions | ||||
|     { | ||||
|         public static void DumpToTestOutput(this FakeInMemoryConsole console, ITestOutputHelper testOutputHelper) | ||||
|         { | ||||
|             testOutputHelper.WriteLine("[*] Captured standard output:"); | ||||
|             testOutputHelper.WriteLine(console.ReadOutputString()); | ||||
| namespace CliFx.Tests.Utils.Extensions; | ||||
|  | ||||
|             testOutputHelper.WriteLine("[*] Captured standard error:"); | ||||
|             testOutputHelper.WriteLine(console.ReadErrorString()); | ||||
|         } | ||||
| internal static class ConsoleExtensions | ||||
| { | ||||
|     public static void DumpToTestOutput(this FakeInMemoryConsole console, ITestOutputHelper testOutputHelper) | ||||
|     { | ||||
|         testOutputHelper.WriteLine("[*] Captured standard output:"); | ||||
|         testOutputHelper.WriteLine(console.ReadOutputString()); | ||||
|  | ||||
|         testOutputHelper.WriteLine("[*] Captured standard error:"); | ||||
|         testOutputHelper.WriteLine(console.ReadErrorString()); | ||||
|     } | ||||
| } | ||||
| @@ -2,11 +2,10 @@ | ||||
| using CliFx.Attributes; | ||||
| using CliFx.Infrastructure; | ||||
|  | ||||
| namespace CliFx.Tests.Utils | ||||
| namespace CliFx.Tests.Utils; | ||||
|  | ||||
| [Command] | ||||
| public class NoOpCommand : ICommand | ||||
| { | ||||
|     [Command] | ||||
|     public class NoOpCommand : ICommand | ||||
|     { | ||||
|         public ValueTask ExecuteAsync(IConsole console) => default; | ||||
|     } | ||||
|     public ValueTask ExecuteAsync(IConsole console) => default; | ||||
| } | ||||
| @@ -1,39 +1,38 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace CliFx | ||||
| namespace CliFx; | ||||
|  | ||||
| /// <summary> | ||||
| /// Configuration of an application. | ||||
| /// </summary> | ||||
| public class ApplicationConfiguration | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Configuration of an application. | ||||
|     /// Command types defined in this application. | ||||
|     /// </summary> | ||||
|     public class ApplicationConfiguration | ||||
|     public IReadOnlyList<Type> CommandTypes { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Whether debug mode is allowed in this application. | ||||
|     /// </summary> | ||||
|     public bool IsDebugModeAllowed { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Whether preview mode is allowed in this application. | ||||
|     /// </summary> | ||||
|     public bool IsPreviewModeAllowed { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Initializes an instance of <see cref="ApplicationConfiguration"/>. | ||||
|     /// </summary> | ||||
|     public ApplicationConfiguration( | ||||
|         IReadOnlyList<Type> commandTypes, | ||||
|         bool isDebugModeAllowed, | ||||
|         bool isPreviewModeAllowed) | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Command types defined in this application. | ||||
|         /// </summary> | ||||
|         public IReadOnlyList<Type> CommandTypes { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Whether debug mode is allowed in this application. | ||||
|         /// </summary> | ||||
|         public bool IsDebugModeAllowed { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Whether preview mode is allowed in this application. | ||||
|         /// </summary> | ||||
|         public bool IsPreviewModeAllowed { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes an instance of <see cref="ApplicationConfiguration"/>. | ||||
|         /// </summary> | ||||
|         public ApplicationConfiguration( | ||||
|             IReadOnlyList<Type> commandTypes, | ||||
|             bool isDebugModeAllowed, | ||||
|             bool isPreviewModeAllowed) | ||||
|         { | ||||
|             CommandTypes = commandTypes; | ||||
|             IsDebugModeAllowed = isDebugModeAllowed; | ||||
|             IsPreviewModeAllowed = isPreviewModeAllowed; | ||||
|         } | ||||
|         CommandTypes = commandTypes; | ||||
|         IsDebugModeAllowed = isDebugModeAllowed; | ||||
|         IsPreviewModeAllowed = isPreviewModeAllowed; | ||||
|     } | ||||
| } | ||||
| @@ -1,43 +1,42 @@ | ||||
| namespace CliFx | ||||
| namespace CliFx; | ||||
|  | ||||
| /// <summary> | ||||
| /// Metadata associated with an application. | ||||
| /// </summary> | ||||
| public class ApplicationMetadata | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Metadata associated with an application. | ||||
|     /// Application title. | ||||
|     /// </summary> | ||||
|     public class ApplicationMetadata | ||||
|     public string Title { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Application executable name. | ||||
|     /// </summary> | ||||
|     public string ExecutableName { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Application version. | ||||
|     /// </summary> | ||||
|     public string Version { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Application description. | ||||
|     /// </summary> | ||||
|     public string? Description { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Initializes an instance of <see cref="ApplicationMetadata"/>. | ||||
|     /// </summary> | ||||
|     public ApplicationMetadata( | ||||
|         string title, | ||||
|         string executableName, | ||||
|         string version, | ||||
|         string? description) | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Application title. | ||||
|         /// </summary> | ||||
|         public string Title { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Application executable name. | ||||
|         /// </summary> | ||||
|         public string ExecutableName { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Application version. | ||||
|         /// </summary> | ||||
|         public string Version { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Application description. | ||||
|         /// </summary> | ||||
|         public string? Description { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes an instance of <see cref="ApplicationMetadata"/>. | ||||
|         /// </summary> | ||||
|         public ApplicationMetadata( | ||||
|             string title, | ||||
|             string executableName, | ||||
|             string version, | ||||
|             string? description) | ||||
|         { | ||||
|             Title = title; | ||||
|             ExecutableName = executableName; | ||||
|             Version = version; | ||||
|             Description = description; | ||||
|         } | ||||
|         Title = title; | ||||
|         ExecutableName = executableName; | ||||
|         Version = version; | ||||
|         Description = description; | ||||
|     } | ||||
| } | ||||
| @@ -1,43 +1,42 @@ | ||||
| using System; | ||||
|  | ||||
| namespace CliFx.Attributes | ||||
| namespace CliFx.Attributes; | ||||
|  | ||||
| /// <summary> | ||||
| /// Annotates a type that defines a command. | ||||
| /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Class, Inherited = false)] | ||||
| public sealed class CommandAttribute : Attribute | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Annotates a type that defines a command. | ||||
|     /// Command's name. | ||||
|     /// </summary> | ||||
|     [AttributeUsage(AttributeTargets.Class, Inherited = false)] | ||||
|     public sealed class CommandAttribute : Attribute | ||||
|     /// <remarks> | ||||
|     /// Command can have no name, in which case it's treated as the default command. | ||||
|     /// | ||||
|     /// All commands registered in an application must have unique names (comparison IS NOT case-sensitive). | ||||
|     /// Only one command without a name is allowed in an application. | ||||
|     /// </remarks> | ||||
|     public string? Name { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Command description. | ||||
|     /// This is shown to the user in the help text. | ||||
|     /// </summary> | ||||
|     public string? Description { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Initializes an instance of <see cref="CommandAttribute"/>. | ||||
|     /// </summary> | ||||
|     public CommandAttribute(string name) | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Command's name. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// Command can have no name, in which case it's treated as the default command. | ||||
|         /// | ||||
|         /// All commands registered in an application must have unique names (comparison IS NOT case-sensitive). | ||||
|         /// Only one command without a name is allowed in an application. | ||||
|         /// </remarks> | ||||
|         public string? Name { get; } | ||||
|         Name = name; | ||||
|     } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Command description. | ||||
|         /// This is shown to the user in the help text. | ||||
|         /// </summary> | ||||
|         public string? Description { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes an instance of <see cref="CommandAttribute"/>. | ||||
|         /// </summary> | ||||
|         public CommandAttribute(string name) | ||||
|         { | ||||
|             Name = name; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes an instance of <see cref="CommandAttribute"/>. | ||||
|         /// </summary> | ||||
|         public CommandAttribute() | ||||
|         { | ||||
|         } | ||||
|     /// <summary> | ||||
|     /// Initializes an instance of <see cref="CommandAttribute"/>. | ||||
|     /// </summary> | ||||
|     public CommandAttribute() | ||||
|     { | ||||
|     } | ||||
| } | ||||
| @@ -1,100 +1,99 @@ | ||||
| using System; | ||||
| using CliFx.Extensibility; | ||||
|  | ||||
| namespace CliFx.Attributes | ||||
| namespace CliFx.Attributes; | ||||
|  | ||||
| /// <summary> | ||||
| /// Annotates a property that defines a command option. | ||||
| /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Property)] | ||||
| public sealed class CommandOptionAttribute : Attribute | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Annotates a property that defines a command option. | ||||
|     /// Option name. | ||||
|     /// </summary> | ||||
|     [AttributeUsage(AttributeTargets.Property)] | ||||
|     public sealed class CommandOptionAttribute : Attribute | ||||
|     /// <remarks> | ||||
|     /// Must contain at least two characters and start with a letter. | ||||
|     /// Either <see cref="Name"/> or <see cref="ShortName"/> must be set. | ||||
|     /// All options in a command must have unique names (comparison IS NOT case-sensitive). | ||||
|     /// </remarks> | ||||
|     public string? Name { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Option short name. | ||||
|     /// </summary> | ||||
|     /// <remarks> | ||||
|     /// Either <see cref="Name"/> or <see cref="ShortName"/> must be set. | ||||
|     /// All options in a command must have unique short names (comparison IS case-sensitive). | ||||
|     /// </remarks> | ||||
|     public char? ShortName { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Whether this option is required. | ||||
|     /// If an option is required, the user will get an error if they don't set it. | ||||
|     /// </summary> | ||||
|     public bool IsRequired { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Environment variable whose value will be used as a fallback if the option | ||||
|     /// has not been explicitly set through command line arguments. | ||||
|     /// </summary> | ||||
|     public string? EnvironmentVariable { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Option description. | ||||
|     /// This is shown to the user in the help text. | ||||
|     /// </summary> | ||||
|     public string? Description { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Custom converter used for mapping the raw command line argument into | ||||
|     /// a value expected by the underlying property. | ||||
|     /// </summary> | ||||
|     /// <remarks> | ||||
|     /// Converter must derive from <see cref="BindingConverter{T}"/>. | ||||
|     /// </remarks> | ||||
|     public Type? Converter { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Custom validators used for verifying the value of the underlying | ||||
|     /// property, after it has been bound. | ||||
|     /// </summary> | ||||
|     /// <remarks> | ||||
|     /// Validators must derive from <see cref="BindingValidator{T}"/>. | ||||
|     /// </remarks> | ||||
|     public Type[] Validators { get; set; } = Array.Empty<Type>(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Initializes an instance of <see cref="CommandOptionAttribute"/>. | ||||
|     /// </summary> | ||||
|     private CommandOptionAttribute(string? name, char? shortName) | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Option name. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// Must contain at least two characters and start with a letter. | ||||
|         /// Either <see cref="Name"/> or <see cref="ShortName"/> must be set. | ||||
|         /// All options in a command must have unique names (comparison IS NOT case-sensitive). | ||||
|         /// </remarks> | ||||
|         public string? Name { get; } | ||||
|         Name = name; | ||||
|         ShortName = shortName; | ||||
|     } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Option short name. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// Either <see cref="Name"/> or <see cref="ShortName"/> must be set. | ||||
|         /// All options in a command must have unique short names (comparison IS case-sensitive). | ||||
|         /// </remarks> | ||||
|         public char? ShortName { get; } | ||||
|     /// <summary> | ||||
|     /// Initializes an instance of <see cref="CommandOptionAttribute"/>. | ||||
|     /// </summary> | ||||
|     public CommandOptionAttribute(string name, char shortName) | ||||
|         : this(name, (char?) shortName) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Whether this option is required. | ||||
|         /// If an option is required, the user will get an error if they don't set it. | ||||
|         /// </summary> | ||||
|         public bool IsRequired { get; set; } | ||||
|     /// <summary> | ||||
|     /// Initializes an instance of <see cref="CommandOptionAttribute"/>. | ||||
|     /// </summary> | ||||
|     public CommandOptionAttribute(string name) | ||||
|         : this(name, null) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Environment variable whose value will be used as a fallback if the option | ||||
|         /// has not been explicitly set through command line arguments. | ||||
|         /// </summary> | ||||
|         public string? EnvironmentVariable { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Option description. | ||||
|         /// This is shown to the user in the help text. | ||||
|         /// </summary> | ||||
|         public string? Description { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Custom converter used for mapping the raw command line argument into | ||||
|         /// a value expected by the underlying property. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// Converter must derive from <see cref="BindingConverter{T}"/>. | ||||
|         /// </remarks> | ||||
|         public Type? Converter { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Custom validators used for verifying the value of the underlying | ||||
|         /// property, after it has been bound. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// Validators must derive from <see cref="BindingValidator{T}"/>. | ||||
|         /// </remarks> | ||||
|         public Type[] Validators { get; set; } = Array.Empty<Type>(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes an instance of <see cref="CommandOptionAttribute"/>. | ||||
|         /// </summary> | ||||
|         private CommandOptionAttribute(string? name, char? shortName) | ||||
|         { | ||||
|             Name = name; | ||||
|             ShortName = shortName; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes an instance of <see cref="CommandOptionAttribute"/>. | ||||
|         /// </summary> | ||||
|         public CommandOptionAttribute(string name, char shortName) | ||||
|             : this(name, (char?) shortName) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes an instance of <see cref="CommandOptionAttribute"/>. | ||||
|         /// </summary> | ||||
|         public CommandOptionAttribute(string name) | ||||
|             : this(name, null) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes an instance of <see cref="CommandOptionAttribute"/>. | ||||
|         /// </summary> | ||||
|         public CommandOptionAttribute(char shortName) | ||||
|             : this(null, (char?) shortName) | ||||
|         { | ||||
|         } | ||||
|     /// <summary> | ||||
|     /// Initializes an instance of <see cref="CommandOptionAttribute"/>. | ||||
|     /// </summary> | ||||
|     public CommandOptionAttribute(char shortName) | ||||
|         : this(null, (char?) shortName) | ||||
|     { | ||||
|     } | ||||
| } | ||||
| @@ -1,67 +1,66 @@ | ||||
| using System; | ||||
| using CliFx.Extensibility; | ||||
|  | ||||
| namespace CliFx.Attributes | ||||
| namespace CliFx.Attributes; | ||||
|  | ||||
| /// <summary> | ||||
| /// Annotates a property that defines a command parameter. | ||||
| /// </summary> | ||||
| [AttributeUsage(AttributeTargets.Property)] | ||||
| public sealed class CommandParameterAttribute : Attribute | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Annotates a property that defines a command parameter. | ||||
|     /// Parameter order. | ||||
|     /// </summary> | ||||
|     [AttributeUsage(AttributeTargets.Property)] | ||||
|     public sealed class CommandParameterAttribute : Attribute | ||||
|     /// <remarks> | ||||
|     /// Higher order means the parameter appears later, lower order means | ||||
|     /// it appears earlier. | ||||
|     /// | ||||
|     /// All parameters in a command must have unique order. | ||||
|     /// | ||||
|     /// Parameter whose type is a non-scalar (e.g. array), must always be the last in order. | ||||
|     /// Only one non-scalar parameter is allowed in a command. | ||||
|     /// </remarks> | ||||
|     public int Order { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Parameter name. | ||||
|     /// This is shown to the user in the help text. | ||||
|     /// </summary> | ||||
|     /// <remarks> | ||||
|     /// If this isn't specified, parameter name is inferred from the property name. | ||||
|     /// </remarks> | ||||
|     public string? Name { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Parameter description. | ||||
|     /// This is shown to the user in the help text. | ||||
|     /// </summary> | ||||
|     public string? Description { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Custom converter used for mapping the raw command line argument into | ||||
|     /// a value expected by the underlying property. | ||||
|     /// </summary> | ||||
|     /// <remarks> | ||||
|     /// Converter must derive from <see cref="BindingConverter{T}"/>. | ||||
|     /// </remarks> | ||||
|     public Type? Converter { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Custom validators used for verifying the value of the underlying | ||||
|     /// property, after it has been bound. | ||||
|     /// </summary> | ||||
|     /// <remarks> | ||||
|     /// Validators must derive from <see cref="BindingValidator{T}"/>. | ||||
|     /// </remarks> | ||||
|     public Type[] Validators { get; set; } = Array.Empty<Type>(); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Initializes an instance of <see cref="CommandParameterAttribute"/>. | ||||
|     /// </summary> | ||||
|     public CommandParameterAttribute(int order) | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Parameter order. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// Higher order means the parameter appears later, lower order means | ||||
|         /// it appears earlier. | ||||
|         /// | ||||
|         /// All parameters in a command must have unique order. | ||||
|         /// | ||||
|         /// Parameter whose type is a non-scalar (e.g. array), must always be the last in order. | ||||
|         /// Only one non-scalar parameter is allowed in a command. | ||||
|         /// </remarks> | ||||
|         public int Order { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Parameter name. | ||||
|         /// This is shown to the user in the help text. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// If this isn't specified, parameter name is inferred from the property name. | ||||
|         /// </remarks> | ||||
|         public string? Name { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Parameter description. | ||||
|         /// This is shown to the user in the help text. | ||||
|         /// </summary> | ||||
|         public string? Description { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Custom converter used for mapping the raw command line argument into | ||||
|         /// a value expected by the underlying property. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// Converter must derive from <see cref="BindingConverter{T}"/>. | ||||
|         /// </remarks> | ||||
|         public Type? Converter { get; set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Custom validators used for verifying the value of the underlying | ||||
|         /// property, after it has been bound. | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// Validators must derive from <see cref="BindingValidator{T}"/>. | ||||
|         /// </remarks> | ||||
|         public Type[] Validators { get; set; } = Array.Empty<Type>(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes an instance of <see cref="CommandParameterAttribute"/>. | ||||
|         /// </summary> | ||||
|         public CommandParameterAttribute(int order) | ||||
|         { | ||||
|             Order = order; | ||||
|         } | ||||
|         Order = order; | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user