Use CSharpier

This commit is contained in:
Tyrrrz
2023-08-22 21:20:04 +03:00
parent a4726fcefd
commit 21b601da66
99 changed files with 975 additions and 1163 deletions

View File

@@ -11,6 +11,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Basic.Reference.Assemblies.Net70" Version="1.4.2" /> <PackageReference Include="Basic.Reference.Assemblies.Net70" Version="1.4.2" />
<PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" /> <PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.2" PrivateAssets="all" /> <PackageReference Include="GitHubActionsTestLogger" Version="2.3.2" PrivateAssets="all" />
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />

View File

@@ -13,8 +13,7 @@ public class CommandMustBeAnnotatedAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
@@ -30,8 +29,7 @@ public class CommandMustBeAnnotatedAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public abstract class MyCommand : ICommand public abstract class MyCommand : ICommand
{ {
@@ -48,8 +46,7 @@ public class CommandMustBeAnnotatedAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public abstract class MyCommand : ICommand public abstract class MyCommand : ICommand
{ {
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
@@ -65,8 +62,7 @@ public class CommandMustBeAnnotatedAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class Foo public class Foo
{ {
public int Bar { get; init; } = 5; public int Bar { get; init; } = 5;

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class CommandMustImplementInterfaceAnalyzerSpecs public class CommandMustImplementInterfaceAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new CommandMustImplementInterfaceAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new CommandMustImplementInterfaceAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_a_command_does_not_implement_ICommand_interface() public void Analyzer_reports_an_error_if_a_command_does_not_implement_ICommand_interface()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand public class MyCommand
{ {
@@ -31,8 +31,7 @@ public class CommandMustImplementInterfaceAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -49,8 +48,7 @@ public class CommandMustImplementInterfaceAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class Foo public class Foo
{ {
public int Bar { get; init; } = 5; public int Bar { get; init; } = 5;

View File

@@ -12,11 +12,10 @@ public class GeneralSpecs
public void All_analyzers_have_unique_diagnostic_IDs() public void All_analyzers_have_unique_diagnostic_IDs()
{ {
// Arrange // Arrange
var analyzers = typeof(AnalyzerBase) var analyzers = typeof(AnalyzerBase).Assembly
.Assembly
.GetTypes() .GetTypes()
.Where(t => !t.IsAbstract && t.IsAssignableTo(typeof(DiagnosticAnalyzer))) .Where(t => !t.IsAbstract && t.IsAssignableTo(typeof(DiagnosticAnalyzer)))
.Select(t => (DiagnosticAnalyzer) Activator.CreateInstance(t)!) .Select(t => (DiagnosticAnalyzer)Activator.CreateInstance(t)!)
.ToArray(); .ToArray();
// Act // Act

View File

@@ -13,8 +13,7 @@ public class OptionMustBeInsideCommandAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyClass public class MyClass
{ {
[CommandOption("foo")] [CommandOption("foo")]
@@ -31,8 +30,7 @@ public class OptionMustBeInsideCommandAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -52,8 +50,7 @@ public class OptionMustBeInsideCommandAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public abstract class MyCommand public abstract class MyCommand
{ {
[CommandOption("foo")] [CommandOption("foo")]
@@ -70,8 +67,7 @@ public class OptionMustBeInsideCommandAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class OptionMustBeRequiredIfPropertyRequiredAnalyzerSpecs public class OptionMustBeRequiredIfPropertyRequiredAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustBeRequiredIfPropertyRequiredAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new OptionMustBeRequiredIfPropertyRequiredAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_a_non_required_option_is_bound_to_a_required_property() public void Analyzer_reports_an_error_if_a_non_required_option_is_bound_to_a_required_property()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -34,8 +34,7 @@ public class OptionMustBeRequiredIfPropertyRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -55,8 +54,7 @@ public class OptionMustBeRequiredIfPropertyRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -76,8 +74,7 @@ public class OptionMustBeRequiredIfPropertyRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -97,8 +94,7 @@ public class OptionMustBeRequiredIfPropertyRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class OptionMustHaveNameOrShortNameAnalyzerSpecs public class OptionMustHaveNameOrShortNameAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveNameOrShortNameAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new OptionMustHaveNameOrShortNameAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_an_option_does_not_have_a_name_or_short_name() public void Analyzer_reports_an_error_if_an_option_does_not_have_a_name_or_short_name()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -34,8 +34,7 @@ public class OptionMustHaveNameOrShortNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -55,8 +54,7 @@ public class OptionMustHaveNameOrShortNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -76,8 +74,7 @@ public class OptionMustHaveNameOrShortNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -13,8 +13,7 @@ public class OptionMustHaveUniqueNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -37,8 +36,7 @@ public class OptionMustHaveUniqueNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -61,8 +59,7 @@ public class OptionMustHaveUniqueNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -82,8 +79,7 @@ public class OptionMustHaveUniqueNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class OptionMustHaveUniqueShortNameAnalyzerSpecs public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveUniqueShortNameAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new OptionMustHaveUniqueShortNameAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_an_option_has_the_same_short_name_as_another_option() public void Analyzer_reports_an_error_if_an_option_has_the_same_short_name_as_another_option()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -37,8 +37,7 @@ public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -61,8 +60,7 @@ public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -85,8 +83,7 @@ public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -106,8 +103,7 @@ public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class OptionMustHaveValidConverterAnalyzerSpecs public class OptionMustHaveValidConverterAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidConverterAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new OptionMustHaveValidConverterAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_an_option_has_a_converter_that_does_not_derive_from_BindingConverter() public void Analyzer_reports_an_error_if_an_option_has_a_converter_that_does_not_derive_from_BindingConverter()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyConverter public class MyConverter
{ {
public string Convert(string? rawValue) => rawValue; public string Convert(string? rawValue) => rawValue;
@@ -39,8 +39,7 @@ public class OptionMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyConverter : BindingConverter<int> public class MyConverter : BindingConverter<int>
{ {
public override int Convert(string? rawValue) => 42; public override int Convert(string? rawValue) => 42;
@@ -65,8 +64,7 @@ public class OptionMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyConverter : BindingConverter<string> public class MyConverter : BindingConverter<string>
{ {
public override string Convert(string? rawValue) => rawValue; public override string Convert(string? rawValue) => rawValue;
@@ -91,8 +89,7 @@ public class OptionMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyConverter : BindingConverter<int> public class MyConverter : BindingConverter<int>
{ {
public override int Convert(string? rawValue) => 42; public override int Convert(string? rawValue) => 42;
@@ -117,8 +114,7 @@ public class OptionMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyConverter : BindingConverter<string> public class MyConverter : BindingConverter<string>
{ {
public override string Convert(string? rawValue) => rawValue; public override string Convert(string? rawValue) => rawValue;
@@ -143,8 +139,7 @@ public class OptionMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -164,8 +159,7 @@ public class OptionMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -13,8 +13,7 @@ public class OptionMustHaveValidNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -34,8 +33,7 @@ public class OptionMustHaveValidNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -55,8 +53,7 @@ public class OptionMustHaveValidNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -76,8 +73,7 @@ public class OptionMustHaveValidNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -97,8 +93,7 @@ public class OptionMustHaveValidNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class OptionMustHaveValidShortNameAnalyzerSpecs public class OptionMustHaveValidShortNameAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidShortNameAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new OptionMustHaveValidShortNameAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_an_option_has_a_short_name_which_is_not_a_letter_character() public void Analyzer_reports_an_error_if_an_option_has_a_short_name_which_is_not_a_letter_character()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -34,8 +34,7 @@ public class OptionMustHaveValidShortNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -55,8 +54,7 @@ public class OptionMustHaveValidShortNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -76,8 +74,7 @@ public class OptionMustHaveValidShortNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class OptionMustHaveValidValidatorsAnalyzerSpecs public class OptionMustHaveValidValidatorsAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new OptionMustHaveValidValidatorsAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new OptionMustHaveValidValidatorsAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_an_option_has_a_validator_that_does_not_derive_from_BindingValidator() public void Analyzer_reports_an_error_if_an_option_has_a_validator_that_does_not_derive_from_BindingValidator()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyValidator public class MyValidator
{ {
public void Validate(string value) {} public void Validate(string value) {}
@@ -39,8 +39,7 @@ public class OptionMustHaveValidValidatorsAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyValidator : BindingValidator<int> public class MyValidator : BindingValidator<int>
{ {
public override BindingValidationError Validate(int value) => Ok(); public override BindingValidationError Validate(int value) => Ok();
@@ -65,8 +64,7 @@ public class OptionMustHaveValidValidatorsAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyValidator : BindingValidator<string> public class MyValidator : BindingValidator<string>
{ {
public override BindingValidationError Validate(string value) => Ok(); public override BindingValidationError Validate(string value) => Ok();
@@ -91,8 +89,7 @@ public class OptionMustHaveValidValidatorsAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -112,8 +109,7 @@ public class OptionMustHaveValidValidatorsAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class ParameterMustBeInsideCommandAnalyzerSpecs public class ParameterMustBeInsideCommandAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeInsideCommandAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new ParameterMustBeInsideCommandAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_a_parameter_is_inside_a_class_that_is_not_a_command() public void Analyzer_reports_an_error_if_a_parameter_is_inside_a_class_that_is_not_a_command()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyClass public class MyClass
{ {
[CommandParameter(0)] [CommandParameter(0)]
@@ -31,8 +31,7 @@ public class ParameterMustBeInsideCommandAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -52,8 +51,7 @@ public class ParameterMustBeInsideCommandAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public abstract class MyCommand public abstract class MyCommand
{ {
[CommandParameter(0)] [CommandParameter(0)]
@@ -70,8 +68,7 @@ public class ParameterMustBeInsideCommandAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class ParameterMustBeLastIfNonRequiredAnalyzerSpecs public class ParameterMustBeLastIfNonRequiredAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonRequiredAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new ParameterMustBeLastIfNonRequiredAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_a_non_required_parameter_is_not_the_last_in_order() public void Analyzer_reports_an_error_if_a_non_required_parameter_is_not_the_last_in_order()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -37,8 +37,7 @@ public class ParameterMustBeLastIfNonRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -61,8 +60,7 @@ public class ParameterMustBeLastIfNonRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -85,8 +83,7 @@ public class ParameterMustBeLastIfNonRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class ParameterMustBeLastIfNonScalarAnalyzerSpecs public class ParameterMustBeLastIfNonScalarAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonScalarAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new ParameterMustBeLastIfNonScalarAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_the_last_in_order() public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_the_last_in_order()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -37,8 +37,7 @@ public class ParameterMustBeLastIfNonScalarAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -61,8 +60,7 @@ public class ParameterMustBeLastIfNonScalarAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -85,8 +83,7 @@ public class ParameterMustBeLastIfNonScalarAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class ParameterMustBeRequiredIfPropertyRequiredAnalyzerSpecs public class ParameterMustBeRequiredIfPropertyRequiredAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeRequiredIfPropertyRequiredAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new ParameterMustBeRequiredIfPropertyRequiredAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_a_non_required_parameter_is_bound_to_a_required_property() public void Analyzer_reports_an_error_if_a_non_required_parameter_is_bound_to_a_required_property()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -34,8 +34,7 @@ public class ParameterMustBeRequiredIfPropertyRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -55,8 +54,7 @@ public class ParameterMustBeRequiredIfPropertyRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -76,8 +74,7 @@ public class ParameterMustBeRequiredIfPropertyRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -97,8 +94,7 @@ public class ParameterMustBeRequiredIfPropertyRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class ParameterMustBeSingleIfNonRequiredAnalyzerSpecs public class ParameterMustBeSingleIfNonRequiredAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeSingleIfNonRequiredAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new ParameterMustBeSingleIfNonRequiredAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_more_than_one_non_required_parameters_are_defined() public void Analyzer_reports_an_error_if_more_than_one_non_required_parameters_are_defined()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -37,8 +37,7 @@ public class ParameterMustBeSingleIfNonRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -61,8 +60,7 @@ public class ParameterMustBeSingleIfNonRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -85,8 +83,7 @@ public class ParameterMustBeSingleIfNonRequiredAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeSingleIfNonScalarAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new ParameterMustBeSingleIfNonScalarAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_more_than_one_non_scalar_parameters_are_defined() public void Analyzer_reports_an_error_if_more_than_one_non_scalar_parameters_are_defined()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -37,8 +37,7 @@ public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -61,8 +60,7 @@ public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -85,8 +83,7 @@ public class ParameterMustBeSingleIfNonScalarAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -13,8 +13,7 @@ public class ParameterMustHaveUniqueNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -37,8 +36,7 @@ public class ParameterMustHaveUniqueNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -61,8 +59,7 @@ public class ParameterMustHaveUniqueNameAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class ParameterMustHaveUniqueOrderAnalyzerSpecs public class ParameterMustHaveUniqueOrderAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveUniqueOrderAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new ParameterMustHaveUniqueOrderAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_a_parameter_has_the_same_order_as_another_parameter() public void Analyzer_reports_an_error_if_a_parameter_has_the_same_order_as_another_parameter()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -37,8 +37,7 @@ public class ParameterMustHaveUniqueOrderAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -61,8 +60,7 @@ public class ParameterMustHaveUniqueOrderAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class ParameterMustHaveValidConverterAnalyzerSpecs public class ParameterMustHaveValidConverterAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidConverterAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new ParameterMustHaveValidConverterAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_a_parameter_has_a_converter_that_does_not_derive_from_BindingConverter() public void Analyzer_reports_an_error_if_a_parameter_has_a_converter_that_does_not_derive_from_BindingConverter()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyConverter public class MyConverter
{ {
public string Convert(string? rawValue) => rawValue; public string Convert(string? rawValue) => rawValue;
@@ -39,8 +39,7 @@ public class ParameterMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyConverter : BindingConverter<int> public class MyConverter : BindingConverter<int>
{ {
public override int Convert(string? rawValue) => 42; public override int Convert(string? rawValue) => 42;
@@ -56,7 +55,6 @@ public class ParameterMustHaveValidConverterAnalyzerSpecs
} }
"""; """;
// Act & assert // Act & assert
Analyzer.Should().ProduceDiagnostics(code); Analyzer.Should().ProduceDiagnostics(code);
} }
@@ -66,8 +64,7 @@ public class ParameterMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyConverter : BindingConverter<string> public class MyConverter : BindingConverter<string>
{ {
public override string Convert(string? rawValue) => rawValue; public override string Convert(string? rawValue) => rawValue;
@@ -92,8 +89,7 @@ public class ParameterMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyConverter : BindingConverter<int> public class MyConverter : BindingConverter<int>
{ {
public override int Convert(string? rawValue) => 42; public override int Convert(string? rawValue) => 42;
@@ -118,8 +114,7 @@ public class ParameterMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyConverter : BindingConverter<string> public class MyConverter : BindingConverter<string>
{ {
public override string Convert(string? rawValue) => rawValue; public override string Convert(string? rawValue) => rawValue;
@@ -144,8 +139,7 @@ public class ParameterMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -165,8 +159,7 @@ public class ParameterMustHaveValidConverterAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class ParameterMustHaveValidValidatorsAnalyzerSpecs public class ParameterMustHaveValidValidatorsAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustHaveValidValidatorsAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new ParameterMustHaveValidValidatorsAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_a_parameter_has_a_validator_that_does_not_derive_from_BindingValidator() public void Analyzer_reports_an_error_a_parameter_has_a_validator_that_does_not_derive_from_BindingValidator()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyValidator public class MyValidator
{ {
public void Validate(string value) {} public void Validate(string value) {}
@@ -39,8 +39,7 @@ public class ParameterMustHaveValidValidatorsAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyValidator : BindingValidator<int> public class MyValidator : BindingValidator<int>
{ {
public override BindingValidationError Validate(int value) => Ok(); public override BindingValidationError Validate(int value) => Ok();
@@ -65,8 +64,7 @@ public class ParameterMustHaveValidValidatorsAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
public class MyValidator : BindingValidator<string> public class MyValidator : BindingValidator<string>
{ {
public override BindingValidationError Validate(string value) => Ok(); public override BindingValidationError Validate(string value) => Ok();
@@ -91,8 +89,7 @@ public class ParameterMustHaveValidValidatorsAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -112,8 +109,7 @@ public class ParameterMustHaveValidValidatorsAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -6,15 +6,15 @@ namespace CliFx.Analyzers.Tests;
public class SystemConsoleShouldBeAvoidedAnalyzerSpecs public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
{ {
private static DiagnosticAnalyzer Analyzer { get; } = new SystemConsoleShouldBeAvoidedAnalyzer(); private static DiagnosticAnalyzer Analyzer { get; } =
new SystemConsoleShouldBeAvoidedAnalyzer();
[Fact] [Fact]
public void Analyzer_reports_an_error_if_a_command_calls_a_method_on_SystemConsole() public void Analyzer_reports_an_error_if_a_command_calls_a_method_on_SystemConsole()
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -35,8 +35,7 @@ public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -57,8 +56,7 @@ public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -79,8 +77,7 @@ public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -101,8 +98,7 @@ public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
@@ -121,8 +117,7 @@ public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
{ {
// Arrange // Arrange
// lang=csharp // lang=csharp
const string code = const string code = """
"""
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {

View File

@@ -18,9 +18,7 @@ internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer,
protected override string Identifier { get; } = "analyzer"; protected override string Identifier { get; } = "analyzer";
public AnalyzerAssertions(DiagnosticAnalyzer analyzer) public AnalyzerAssertions(DiagnosticAnalyzer analyzer)
: base(analyzer) : base(analyzer) { }
{
}
private Compilation Compile(string sourceCode) private Compilation Compile(string sourceCode)
{ {
@@ -33,8 +31,7 @@ internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer,
}; };
// Get default CliFx namespaces // Get default CliFx namespaces
var defaultCliFxNamespaces = typeof(ICommand) var defaultCliFxNamespaces = typeof(ICommand).Assembly
.Assembly
.GetTypes() .GetTypes()
.Where(t => t.IsPublic) .Where(t => t.IsPublic)
.Select(t => t.Namespace) .Select(t => t.Namespace)
@@ -43,10 +40,10 @@ internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer,
// Append default imports to the source code // Append default imports to the source code
var sourceCodeWithUsings = var sourceCodeWithUsings =
string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};")) + string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};"))
string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};")) + + string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};"))
Environment.NewLine + + Environment.NewLine
sourceCode; + sourceCode;
// Parse the source code // Parse the source code
var ast = SyntaxFactory.ParseSyntaxTree( var ast = SyntaxFactory.ParseSyntaxTree(
@@ -58,8 +55,9 @@ internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer,
var compilation = CSharpCompilation.Create( var compilation = CSharpCompilation.Create(
"CliFxTests_DynamicAssembly_" + Guid.NewGuid(), "CliFxTests_DynamicAssembly_" + Guid.NewGuid(),
new[] { ast }, new[] { ast },
Net70.References.All Net70.References.All.Append(
.Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)), MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)
),
// DLL to avoid having to define the Main() method // DLL to avoid having to define the Main() method
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
); );
@@ -103,44 +101,46 @@ internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer,
var producedDiagnosticIds = producedDiagnostics.Select(d => d.Id).Distinct().ToArray(); var producedDiagnosticIds = producedDiagnostics.Select(d => d.Id).Distinct().ToArray();
var isSuccessfulAssertion = var isSuccessfulAssertion =
expectedDiagnosticIds.Intersect(producedDiagnosticIds).Count() == expectedDiagnosticIds.Intersect(producedDiagnosticIds).Count()
expectedDiagnosticIds.Length; == expectedDiagnosticIds.Length;
Execute.Assertion.ForCondition(isSuccessfulAssertion).FailWith(() => Execute.Assertion
{ .ForCondition(isSuccessfulAssertion)
var buffer = new StringBuilder(); .FailWith(() =>
buffer.AppendLine("Expected and produced diagnostics do not match.");
buffer.AppendLine();
buffer.AppendLine("Expected diagnostics:");
foreach (var expectedDiagnostic in expectedDiagnostics)
{ {
buffer.Append(" - "); var buffer = new StringBuilder();
buffer.Append(expectedDiagnostic.Id);
buffer.AppendLine("Expected and produced diagnostics do not match.");
buffer.AppendLine(); buffer.AppendLine();
}
buffer.AppendLine(); buffer.AppendLine("Expected diagnostics:");
buffer.AppendLine("Produced diagnostics:"); foreach (var expectedDiagnostic in expectedDiagnostics)
if (producedDiagnostics.Any())
{
foreach (var producedDiagnostic in producedDiagnostics)
{ {
buffer.Append(" - "); buffer.Append(" - ");
buffer.Append(producedDiagnostic); buffer.Append(expectedDiagnostic.Id);
buffer.AppendLine();
} }
}
else
{
buffer.AppendLine(" < none >");
}
return new FailReason(buffer.ToString()); buffer.AppendLine();
});
buffer.AppendLine("Produced diagnostics:");
if (producedDiagnostics.Any())
{
foreach (var producedDiagnostic in producedDiagnostics)
{
buffer.Append(" - ");
buffer.Append(producedDiagnostic);
}
}
else
{
buffer.AppendLine(" < none >");
}
return new FailReason(buffer.ToString());
});
} }
public void NotProduceDiagnostics(string sourceCode) public void NotProduceDiagnostics(string sourceCode)
@@ -148,23 +148,25 @@ internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer,
var producedDiagnostics = GetProducedDiagnostics(sourceCode); var producedDiagnostics = GetProducedDiagnostics(sourceCode);
var isSuccessfulAssertion = !producedDiagnostics.Any(); var isSuccessfulAssertion = !producedDiagnostics.Any();
Execute.Assertion.ForCondition(isSuccessfulAssertion).FailWith(() => Execute.Assertion
{ .ForCondition(isSuccessfulAssertion)
var buffer = new StringBuilder(); .FailWith(() =>
buffer.AppendLine("Expected no produced diagnostics.");
buffer.AppendLine();
buffer.AppendLine("Produced diagnostics:");
foreach (var producedDiagnostic in producedDiagnostics)
{ {
buffer.Append(" - "); var buffer = new StringBuilder();
buffer.Append(producedDiagnostic);
}
return new FailReason(buffer.ToString()); 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());
});
} }
} }

View File

@@ -14,7 +14,8 @@ public abstract class AnalyzerBase : DiagnosticAnalyzer
protected AnalyzerBase( protected AnalyzerBase(
string diagnosticTitle, string diagnosticTitle,
string diagnosticMessage, string diagnosticMessage,
DiagnosticSeverity diagnosticSeverity = DiagnosticSeverity.Error) DiagnosticSeverity diagnosticSeverity = DiagnosticSeverity.Error
)
{ {
SupportedDiagnostic = new DiagnosticDescriptor( SupportedDiagnostic = new DiagnosticDescriptor(
"CliFx_" + GetType().Name.TrimEnd("Analyzer"), "CliFx_" + GetType().Name.TrimEnd("Analyzer"),

View File

@@ -17,6 +17,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
<!-- Make sure to target the lowest possible version of the compiler for wider support --> <!-- Make sure to target the lowest possible version of the compiler for wider support -->
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.0.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis" Version="3.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" PrivateAssets="all" />

View File

@@ -13,14 +13,14 @@ public class CommandMustBeAnnotatedAnalyzer : AnalyzerBase
public CommandMustBeAnnotatedAnalyzer() public CommandMustBeAnnotatedAnalyzer()
: base( : base(
$"Commands must be annotated with `{SymbolNames.CliFxCommandAttribute}`", $"Commands must be annotated with `{SymbolNames.CliFxCommandAttribute}`",
$"This type must be annotated with `{SymbolNames.CliFxCommandAttribute}` in order to be a valid command.") $"This type must be annotated with `{SymbolNames.CliFxCommandAttribute}` in order to be a valid command."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
ClassDeclarationSyntax classDeclaration, ClassDeclarationSyntax classDeclaration,
ITypeSymbol type) ITypeSymbol type
)
{ {
// Ignore abstract classes, because they may be used to define // Ignore abstract classes, because they may be used to define
// base implementations for commands, in which case the command // base implementations for commands, in which case the command
@@ -28,12 +28,11 @@ public class CommandMustBeAnnotatedAnalyzer : AnalyzerBase
if (type.IsAbstract) if (type.IsAbstract)
return; return;
var implementsCommandInterface = type var implementsCommandInterface = type.AllInterfaces.Any(
.AllInterfaces i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
.Any(i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)); );
var hasCommandAttribute = type var hasCommandAttribute = type.GetAttributes()
.GetAttributes()
.Select(a => a.AttributeClass) .Select(a => a.AttributeClass)
.Any(c => c.DisplayNameMatches(SymbolNames.CliFxCommandAttribute)); .Any(c => c.DisplayNameMatches(SymbolNames.CliFxCommandAttribute));
@@ -41,9 +40,7 @@ public class CommandMustBeAnnotatedAnalyzer : AnalyzerBase
// then it's very likely a user error. // then it's very likely a user error.
if (implementsCommandInterface && !hasCommandAttribute) if (implementsCommandInterface && !hasCommandAttribute)
{ {
context.ReportDiagnostic( context.ReportDiagnostic(CreateDiagnostic(classDeclaration.Identifier.GetLocation()));
CreateDiagnostic(classDeclaration.Identifier.GetLocation())
);
} }
} }

View File

@@ -13,31 +13,28 @@ public class CommandMustImplementInterfaceAnalyzer : AnalyzerBase
public CommandMustImplementInterfaceAnalyzer() public CommandMustImplementInterfaceAnalyzer()
: base( : base(
$"Commands must implement `{SymbolNames.CliFxCommandInterface}` interface", $"Commands must implement `{SymbolNames.CliFxCommandInterface}` interface",
$"This type must implement `{SymbolNames.CliFxCommandInterface}` interface in order to be a valid command.") $"This type must implement `{SymbolNames.CliFxCommandInterface}` interface in order to be a valid command."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
ClassDeclarationSyntax classDeclaration, ClassDeclarationSyntax classDeclaration,
ITypeSymbol type) ITypeSymbol type
)
{ {
var hasCommandAttribute = type var hasCommandAttribute = type.GetAttributes()
.GetAttributes()
.Select(a => a.AttributeClass) .Select(a => a.AttributeClass)
.Any(c => c.DisplayNameMatches(SymbolNames.CliFxCommandAttribute)); .Any(c => c.DisplayNameMatches(SymbolNames.CliFxCommandAttribute));
var implementsCommandInterface = type var implementsCommandInterface = type.AllInterfaces.Any(
.AllInterfaces i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
.Any(i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)); );
// If the attribute is present, but the interface is not implemented, // If the attribute is present, but the interface is not implemented,
// it's very likely a user error. // it's very likely a user error.
if (hasCommandAttribute && !implementsCommandInterface) if (hasCommandAttribute && !implementsCommandInterface)
{ {
context.ReportDiagnostic( context.ReportDiagnostic(CreateDiagnostic(classDeclaration.Identifier.GetLocation()));
CreateDiagnostic(classDeclaration.Identifier.GetLocation())
);
} }
} }

View File

@@ -25,7 +25,8 @@ internal partial class CommandOptionSymbol : ICommandMemberSymbol
char? shortName, char? shortName,
bool? isRequired, bool? isRequired,
ITypeSymbol? converterType, ITypeSymbol? converterType,
IReadOnlyList<ITypeSymbol> validatorTypes) IReadOnlyList<ITypeSymbol> validatorTypes
)
{ {
Property = property; Property = property;
Name = name; Name = name;
@@ -38,9 +39,14 @@ internal partial class CommandOptionSymbol : ICommandMemberSymbol
internal partial class CommandOptionSymbol internal partial class CommandOptionSymbol
{ {
private static AttributeData? TryGetOptionAttribute(IPropertySymbol property) => property private static AttributeData? TryGetOptionAttribute(IPropertySymbol property) =>
.GetAttributes() property
.FirstOrDefault(a => a.AttributeClass?.DisplayNameMatches(SymbolNames.CliFxCommandOptionAttribute) == true); .GetAttributes()
.FirstOrDefault(
a =>
a.AttributeClass?.DisplayNameMatches(SymbolNames.CliFxCommandOptionAttribute)
== true
);
public static CommandOptionSymbol? TryResolve(IPropertySymbol property) public static CommandOptionSymbol? TryResolve(IPropertySymbol property)
{ {
@@ -48,40 +54,45 @@ internal partial class CommandOptionSymbol
if (attribute is null) if (attribute is null)
return null; return null;
var name = attribute var name =
.ConstructorArguments attribute.ConstructorArguments
.Where(a => a.Type?.SpecialType == SpecialType.System_String) .Where(a => a.Type?.SpecialType == SpecialType.System_String)
.Select(a => a.Value) .Select(a => a.Value)
.FirstOrDefault() as string; .FirstOrDefault() as string;
var shortName = attribute var shortName =
.ConstructorArguments attribute.ConstructorArguments
.Where(a => a.Type?.SpecialType == SpecialType.System_Char) .Where(a => a.Type?.SpecialType == SpecialType.System_Char)
.Select(a => a.Value) .Select(a => a.Value)
.FirstOrDefault() as char?; .FirstOrDefault() as char?;
var isRequired = attribute var isRequired =
.NamedArguments attribute.NamedArguments
.Where(a => a.Key == "IsRequired") .Where(a => a.Key == "IsRequired")
.Select(a => a.Value.Value) .Select(a => a.Value.Value)
.FirstOrDefault() as bool?; .FirstOrDefault() as bool?;
var converter = attribute var converter = attribute.NamedArguments
.NamedArguments
.Where(a => a.Key == "Converter") .Where(a => a.Key == "Converter")
.Select(a => a.Value.Value) .Select(a => a.Value.Value)
.Cast<ITypeSymbol?>() .Cast<ITypeSymbol?>()
.FirstOrDefault(); .FirstOrDefault();
var validators = attribute var validators = attribute.NamedArguments
.NamedArguments
.Where(a => a.Key == "Validators") .Where(a => a.Key == "Validators")
.SelectMany(a => a.Value.Values) .SelectMany(a => a.Value.Values)
.Select(c => c.Value) .Select(c => c.Value)
.Cast<ITypeSymbol>() .Cast<ITypeSymbol>()
.ToArray(); .ToArray();
return new CommandOptionSymbol(property, name, shortName, isRequired, converter, validators); return new CommandOptionSymbol(
property,
name,
shortName,
isRequired,
converter,
validators
);
} }
public static bool IsOptionProperty(IPropertySymbol property) => public static bool IsOptionProperty(IPropertySymbol property) =>

View File

@@ -25,7 +25,8 @@ internal partial class CommandParameterSymbol : ICommandMemberSymbol
string? name, string? name,
bool? isRequired, bool? isRequired,
ITypeSymbol? converterType, ITypeSymbol? converterType,
IReadOnlyList<ITypeSymbol> validatorTypes) IReadOnlyList<ITypeSymbol> validatorTypes
)
{ {
Property = property; Property = property;
Order = order; Order = order;
@@ -38,9 +39,14 @@ internal partial class CommandParameterSymbol : ICommandMemberSymbol
internal partial class CommandParameterSymbol internal partial class CommandParameterSymbol
{ {
private static AttributeData? TryGetParameterAttribute(IPropertySymbol property) => property private static AttributeData? TryGetParameterAttribute(IPropertySymbol property) =>
.GetAttributes() property
.FirstOrDefault(a => a.AttributeClass?.DisplayNameMatches(SymbolNames.CliFxCommandParameterAttribute) == true); .GetAttributes()
.FirstOrDefault(
a =>
a.AttributeClass?.DisplayNameMatches(SymbolNames.CliFxCommandParameterAttribute)
== true
);
public static CommandParameterSymbol? TryResolve(IPropertySymbol property) public static CommandParameterSymbol? TryResolve(IPropertySymbol property)
{ {
@@ -48,32 +54,27 @@ internal partial class CommandParameterSymbol
if (attribute is null) if (attribute is null)
return null; return null;
var order = (int)attribute var order = (int)attribute.ConstructorArguments.Select(a => a.Value).First()!;
.ConstructorArguments
.Select(a => a.Value)
.First()!;
var name = attribute var name =
.NamedArguments attribute.NamedArguments
.Where(a => a.Key == "Name") .Where(a => a.Key == "Name")
.Select(a => a.Value.Value) .Select(a => a.Value.Value)
.FirstOrDefault() as string; .FirstOrDefault() as string;
var isRequired = attribute var isRequired =
.NamedArguments attribute.NamedArguments
.Where(a => a.Key == "IsRequired") .Where(a => a.Key == "IsRequired")
.Select(a => a.Value.Value) .Select(a => a.Value.Value)
.FirstOrDefault() as bool?; .FirstOrDefault() as bool?;
var converter = attribute var converter = attribute.NamedArguments
.NamedArguments
.Where(a => a.Key == "Converter") .Where(a => a.Key == "Converter")
.Select(a => a.Value.Value) .Select(a => a.Value.Value)
.Cast<ITypeSymbol?>() .Cast<ITypeSymbol?>()
.FirstOrDefault(); .FirstOrDefault();
var validators = attribute var validators = attribute.NamedArguments
.NamedArguments
.Where(a => a.Key == "Validators") .Where(a => a.Key == "Validators")
.SelectMany(a => a.Value.Values) .SelectMany(a => a.Value.Values)
.Select(c => c.Value) .Select(c => c.Value)

View File

@@ -16,6 +16,6 @@ internal interface ICommandMemberSymbol
internal static class CommandMemberSymbolExtensions internal static class CommandMemberSymbolExtensions
{ {
public static bool IsScalar(this ICommandMemberSymbol member) => public static bool IsScalar(this ICommandMemberSymbol member) =>
member.Property.Type.SpecialType == SpecialType.System_String || member.Property.Type.SpecialType == SpecialType.System_String
member.Property.Type.TryGetEnumerableUnderlyingType() is null; || member.Property.Type.TryGetEnumerableUnderlyingType() is null;
} }

View File

@@ -4,7 +4,8 @@ internal static class SymbolNames
{ {
public const string CliFxCommandInterface = "CliFx.ICommand"; public const string CliFxCommandInterface = "CliFx.ICommand";
public const string CliFxCommandAttribute = "CliFx.Attributes.CommandAttribute"; public const string CliFxCommandAttribute = "CliFx.Attributes.CommandAttribute";
public const string CliFxCommandParameterAttribute = "CliFx.Attributes.CommandParameterAttribute"; public const string CliFxCommandParameterAttribute =
"CliFx.Attributes.CommandParameterAttribute";
public const string CliFxCommandOptionAttribute = "CliFx.Attributes.CommandOptionAttribute"; public const string CliFxCommandOptionAttribute = "CliFx.Attributes.CommandOptionAttribute";
public const string CliFxConsoleInterface = "CliFx.Infrastructure.IConsole"; public const string CliFxConsoleInterface = "CliFx.Infrastructure.IConsole";
public const string CliFxBindingConverterClass = "CliFx.Extensibility.BindingConverter<T>"; public const string CliFxBindingConverterClass = "CliFx.Extensibility.BindingConverter<T>";

View File

@@ -13,14 +13,14 @@ public class OptionMustBeInsideCommandAnalyzer : AnalyzerBase
public OptionMustBeInsideCommandAnalyzer() public OptionMustBeInsideCommandAnalyzer()
: base( : base(
"Options must be defined inside commands", "Options must be defined inside commands",
$"This option must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`.") $"This option must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -31,10 +31,9 @@ public class OptionMustBeInsideCommandAnalyzer : AnalyzerBase
if (!CommandOptionSymbol.IsOptionProperty(property)) if (!CommandOptionSymbol.IsOptionProperty(property))
return; return;
var isInsideCommand = property var isInsideCommand = property.ContainingType.AllInterfaces.Any(
.ContainingType i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
.AllInterfaces );
.Any(i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
if (!isInsideCommand) if (!isInsideCommand)
{ {

View File

@@ -12,14 +12,14 @@ public class OptionMustBeRequiredIfPropertyRequiredAnalyzer : AnalyzerBase
public OptionMustBeRequiredIfPropertyRequiredAnalyzer() public OptionMustBeRequiredIfPropertyRequiredAnalyzer()
: base( : base(
"Options bound to required properties cannot be marked as non-required", "Options bound to required properties cannot be marked as non-required",
"This option cannot be marked as non-required because it's bound to a required property.") "This option cannot be marked as non-required because it's bound to a required property."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -34,11 +34,7 @@ public class OptionMustBeRequiredIfPropertyRequiredAnalyzer : AnalyzerBase
if (option.IsRequired != false) if (option.IsRequired != false)
return; return;
context.ReportDiagnostic( context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.Identifier.GetLocation()));
CreateDiagnostic(
propertyDeclaration.Identifier.GetLocation()
)
);
} }
public override void Initialize(AnalysisContext context) public override void Initialize(AnalysisContext context)

View File

@@ -12,14 +12,14 @@ public class OptionMustHaveNameOrShortNameAnalyzer : AnalyzerBase
public OptionMustHaveNameOrShortNameAnalyzer() public OptionMustHaveNameOrShortNameAnalyzer()
: base( : base(
"Options must have either a name or short name specified", "Options must have either a name or short name specified",
"This option must have either a name or short name specified.") "This option must have either a name or short name specified."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
var option = CommandOptionSymbol.TryResolve(property); var option = CommandOptionSymbol.TryResolve(property);
if (option is null) if (option is null)

View File

@@ -14,16 +14,16 @@ public class OptionMustHaveUniqueNameAnalyzer : AnalyzerBase
public OptionMustHaveUniqueNameAnalyzer() public OptionMustHaveUniqueNameAnalyzer()
: base( : base(
"Options must have unique names", "Options must have unique names",
"This option's name must be unique within the command (comparison IS NOT case sensitive). " + "This option's name must be unique within the command (comparison IS NOT case sensitive). "
"Specified name: `{0}`. " + + "Specified name: `{0}`. "
"Property bound to another option with the same name: `{1}`.") + "Property bound to another option with the same name: `{1}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -35,8 +35,7 @@ public class OptionMustHaveUniqueNameAnalyzer : AnalyzerBase
if (string.IsNullOrWhiteSpace(option.Name)) if (string.IsNullOrWhiteSpace(option.Name))
return; return;
var otherProperties = property var otherProperties = property.ContainingType
.ContainingType
.GetMembers() .GetMembers()
.OfType<IPropertySymbol>() .OfType<IPropertySymbol>()
.Where(m => !m.Equals(property)) .Where(m => !m.Equals(property))

View File

@@ -13,16 +13,16 @@ public class OptionMustHaveUniqueShortNameAnalyzer : AnalyzerBase
public OptionMustHaveUniqueShortNameAnalyzer() public OptionMustHaveUniqueShortNameAnalyzer()
: base( : base(
"Options must have unique short names", "Options must have unique short names",
"This option's short name must be unique within the command (comparison IS case sensitive). " + "This option's short name must be unique within the command (comparison IS case sensitive). "
"Specified short name: `{0}` " + + "Specified short name: `{0}` "
"Property bound to another option with the same short name: `{1}`.") + "Property bound to another option with the same short name: `{1}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -34,8 +34,7 @@ public class OptionMustHaveUniqueShortNameAnalyzer : AnalyzerBase
if (option.ShortName is null) if (option.ShortName is null)
return; return;
var otherProperties = property var otherProperties = property.ContainingType
.ContainingType
.GetMembers() .GetMembers()
.OfType<IPropertySymbol>() .OfType<IPropertySymbol>()
.Where(m => !m.Equals(property)) .Where(m => !m.Equals(property))

View File

@@ -13,14 +13,14 @@ public class OptionMustHaveValidConverterAnalyzer : AnalyzerBase
public OptionMustHaveValidConverterAnalyzer() public OptionMustHaveValidConverterAnalyzer()
: base( : base(
$"Option converters must derive from `{SymbolNames.CliFxBindingConverterClass}`", $"Option converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
$"Converter specified for this option must derive from a compatible `{SymbolNames.CliFxBindingConverterClass}`.") $"Converter specified for this option must derive from a compatible `{SymbolNames.CliFxBindingConverterClass}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
var option = CommandOptionSymbol.TryResolve(property); var option = CommandOptionSymbol.TryResolve(property);
if (option is null) if (option is null)
@@ -29,21 +29,26 @@ public class OptionMustHaveValidConverterAnalyzer : AnalyzerBase
if (option.ConverterType is null) if (option.ConverterType is null)
return; return;
var converterValueType = option var converterValueType = option.ConverterType
.ConverterType
.GetBaseTypes() .GetBaseTypes()
.FirstOrDefault(t => t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass))? .FirstOrDefault(
.TypeArguments t => t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass)
.FirstOrDefault(); )
?.TypeArguments.FirstOrDefault();
// Value returned by the converter must be assignable to the property type // Value returned by the converter must be assignable to the property type
var isCompatible = var isCompatible =
converterValueType is not null && (option.IsScalar() converterValueType is not null
// Scalar && (
? context.Compilation.IsAssignable(converterValueType, property.Type) option.IsScalar()
// Non-scalar (assume we can handle all IEnumerable types for simplicity) // Scalar
: property.Type.TryGetEnumerableUnderlyingType() is { } enumerableUnderlyingType && ? context.Compilation.IsAssignable(converterValueType, property.Type)
context.Compilation.IsAssignable(converterValueType, enumerableUnderlyingType) // Non-scalar (assume we can handle all IEnumerable types for simplicity)
: property.Type.TryGetEnumerableUnderlyingType() is { } enumerableUnderlyingType
&& context.Compilation.IsAssignable(
converterValueType,
enumerableUnderlyingType
)
); );
if (!isCompatible) if (!isCompatible)

View File

@@ -12,15 +12,15 @@ public class OptionMustHaveValidNameAnalyzer : AnalyzerBase
public OptionMustHaveValidNameAnalyzer() public OptionMustHaveValidNameAnalyzer()
: base( : base(
"Options must have valid names", "Options must have valid names",
"This option's name must be at least 2 characters long and must start with a letter. " + "This option's name must be at least 2 characters long and must start with a letter. "
"Specified name: `{0}`.") + "Specified name: `{0}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
var option = CommandOptionSymbol.TryResolve(property); var option = CommandOptionSymbol.TryResolve(property);
if (option is null) if (option is null)
@@ -32,10 +32,7 @@ public class OptionMustHaveValidNameAnalyzer : AnalyzerBase
if (option.Name.Length < 2 || !char.IsLetter(option.Name[0])) if (option.Name.Length < 2 || !char.IsLetter(option.Name[0]))
{ {
context.ReportDiagnostic( context.ReportDiagnostic(
CreateDiagnostic( CreateDiagnostic(propertyDeclaration.Identifier.GetLocation(), option.Name)
propertyDeclaration.Identifier.GetLocation(),
option.Name
)
); );
} }
} }

View File

@@ -12,15 +12,15 @@ public class OptionMustHaveValidShortNameAnalyzer : AnalyzerBase
public OptionMustHaveValidShortNameAnalyzer() public OptionMustHaveValidShortNameAnalyzer()
: base( : base(
"Option short names must be letter characters", "Option short names must be letter characters",
"This option's short name must be a single letter character. " + "This option's short name must be a single letter character. "
"Specified short name: `{0}`.") + "Specified short name: `{0}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
var option = CommandOptionSymbol.TryResolve(property); var option = CommandOptionSymbol.TryResolve(property);
if (option is null) if (option is null)
@@ -32,10 +32,7 @@ public class OptionMustHaveValidShortNameAnalyzer : AnalyzerBase
if (!char.IsLetter(option.ShortName.Value)) if (!char.IsLetter(option.ShortName.Value))
{ {
context.ReportDiagnostic( context.ReportDiagnostic(
CreateDiagnostic( CreateDiagnostic(propertyDeclaration.Identifier.GetLocation(), option.ShortName)
propertyDeclaration.Identifier.GetLocation(),
option.ShortName
)
); );
} }
} }

View File

@@ -13,14 +13,14 @@ public class OptionMustHaveValidValidatorsAnalyzer : AnalyzerBase
public OptionMustHaveValidValidatorsAnalyzer() public OptionMustHaveValidValidatorsAnalyzer()
: base( : base(
$"Option validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`", $"Option validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
$"Each validator specified for this option must derive from a compatible `{SymbolNames.CliFxBindingValidatorClass}`.") $"Each validator specified for this option must derive from a compatible `{SymbolNames.CliFxBindingValidatorClass}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
var option = CommandOptionSymbol.TryResolve(property); var option = CommandOptionSymbol.TryResolve(property);
if (option is null) if (option is null)
@@ -30,14 +30,16 @@ public class OptionMustHaveValidValidatorsAnalyzer : AnalyzerBase
{ {
var validatorValueType = validatorType var validatorValueType = validatorType
.GetBaseTypes() .GetBaseTypes()
.FirstOrDefault(t => t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingValidatorClass))? .FirstOrDefault(
.TypeArguments t =>
.FirstOrDefault(); t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingValidatorClass)
)
?.TypeArguments.FirstOrDefault();
// Value passed to the validator must be assignable from the property type // Value passed to the validator must be assignable from the property type
var isCompatible = var isCompatible =
validatorValueType is not null && validatorValueType is not null
context.Compilation.IsAssignable(property.Type, validatorValueType); && context.Compilation.IsAssignable(property.Type, validatorValueType);
if (!isCompatible) if (!isCompatible)
{ {

View File

@@ -13,14 +13,14 @@ public class ParameterMustBeInsideCommandAnalyzer : AnalyzerBase
public ParameterMustBeInsideCommandAnalyzer() public ParameterMustBeInsideCommandAnalyzer()
: base( : base(
"Parameters must be defined inside commands", "Parameters must be defined inside commands",
$"This parameter must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`.") $"This parameter must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -31,10 +31,9 @@ public class ParameterMustBeInsideCommandAnalyzer : AnalyzerBase
if (!CommandParameterSymbol.IsParameterProperty(property)) if (!CommandParameterSymbol.IsParameterProperty(property))
return; return;
var isInsideCommand = property var isInsideCommand = property.ContainingType.AllInterfaces.Any(
.ContainingType i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
.AllInterfaces );
.Any(i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
if (!isInsideCommand) if (!isInsideCommand)
{ {

View File

@@ -13,15 +13,15 @@ public class ParameterMustBeLastIfNonRequiredAnalyzer : AnalyzerBase
public ParameterMustBeLastIfNonRequiredAnalyzer() public ParameterMustBeLastIfNonRequiredAnalyzer()
: base( : base(
"Parameters marked as non-required must be the last in order", "Parameters marked as non-required must be the last in order",
"This parameter is non-required so it must be the last in order (its order must be highest within the command). " + "This parameter is non-required so it must be the last in order (its order must be highest within the command). "
"Property bound to another non-required parameter: `{0}`.") + "Property bound to another non-required parameter: `{0}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -33,8 +33,7 @@ public class ParameterMustBeLastIfNonRequiredAnalyzer : AnalyzerBase
if (parameter.IsRequired != false) if (parameter.IsRequired != false)
return; return;
var otherProperties = property var otherProperties = property.ContainingType
.ContainingType
.GetMembers() .GetMembers()
.OfType<IPropertySymbol>() .OfType<IPropertySymbol>()
.Where(m => !m.Equals(property)) .Where(m => !m.Equals(property))

View File

@@ -13,15 +13,15 @@ public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase
public ParameterMustBeLastIfNonScalarAnalyzer() public ParameterMustBeLastIfNonScalarAnalyzer()
: base( : base(
"Parameters of non-scalar types must be the last in order", "Parameters of non-scalar types must be the last in order",
"This parameter has a non-scalar type so it must be the last in order (its order must be highest within the command). " + "This parameter has a non-scalar type so it must be the last in order (its order must be highest within the command). "
"Property bound to another non-scalar parameter: `{0}`.") + "Property bound to another non-scalar parameter: `{0}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -33,8 +33,7 @@ public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase
if (parameter.IsScalar()) if (parameter.IsScalar())
return; return;
var otherProperties = property var otherProperties = property.ContainingType
.ContainingType
.GetMembers() .GetMembers()
.OfType<IPropertySymbol>() .OfType<IPropertySymbol>()
.Where(m => !m.Equals(property)) .Where(m => !m.Equals(property))

View File

@@ -12,14 +12,14 @@ public class ParameterMustBeRequiredIfPropertyRequiredAnalyzer : AnalyzerBase
public ParameterMustBeRequiredIfPropertyRequiredAnalyzer() public ParameterMustBeRequiredIfPropertyRequiredAnalyzer()
: base( : base(
"Parameters bound to required properties cannot be marked as non-required", "Parameters bound to required properties cannot be marked as non-required",
"This parameter cannot be marked as non-required because it's bound to a required property.") "This parameter cannot be marked as non-required because it's bound to a required property."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -34,11 +34,7 @@ public class ParameterMustBeRequiredIfPropertyRequiredAnalyzer : AnalyzerBase
if (parameter.IsRequired != false) if (parameter.IsRequired != false)
return; return;
context.ReportDiagnostic( context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.Identifier.GetLocation()));
CreateDiagnostic(
propertyDeclaration.Identifier.GetLocation()
)
);
} }
public override void Initialize(AnalysisContext context) public override void Initialize(AnalysisContext context)

View File

@@ -13,15 +13,15 @@ public class ParameterMustBeSingleIfNonRequiredAnalyzer : AnalyzerBase
public ParameterMustBeSingleIfNonRequiredAnalyzer() public ParameterMustBeSingleIfNonRequiredAnalyzer()
: base( : base(
"Parameters marked as non-required are limited to one per command", "Parameters marked as non-required are limited to one per command",
"This parameter is non-required so it must be the only such parameter in the command. " + "This parameter is non-required so it must be the only such parameter in the command. "
"Property bound to another non-required parameter: `{0}`.") + "Property bound to another non-required parameter: `{0}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -33,8 +33,7 @@ public class ParameterMustBeSingleIfNonRequiredAnalyzer : AnalyzerBase
if (parameter.IsRequired != false) if (parameter.IsRequired != false)
return; return;
var otherProperties = property var otherProperties = property.ContainingType
.ContainingType
.GetMembers() .GetMembers()
.OfType<IPropertySymbol>() .OfType<IPropertySymbol>()
.Where(m => !m.Equals(property)) .Where(m => !m.Equals(property))

View File

@@ -13,15 +13,15 @@ public class ParameterMustBeSingleIfNonScalarAnalyzer : AnalyzerBase
public ParameterMustBeSingleIfNonScalarAnalyzer() public ParameterMustBeSingleIfNonScalarAnalyzer()
: base( : base(
"Parameters of non-scalar types are limited to one per command", "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. " + "This parameter has a non-scalar type so it must be the only such parameter in the command. "
"Property bound to another non-scalar parameter: `{0}`.") + "Property bound to another non-scalar parameter: `{0}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -33,8 +33,7 @@ public class ParameterMustBeSingleIfNonScalarAnalyzer : AnalyzerBase
if (parameter.IsScalar()) if (parameter.IsScalar())
return; return;
var otherProperties = property var otherProperties = property.ContainingType
.ContainingType
.GetMembers() .GetMembers()
.OfType<IPropertySymbol>() .OfType<IPropertySymbol>()
.Where(m => !m.Equals(property)) .Where(m => !m.Equals(property))

View File

@@ -14,16 +14,16 @@ public class ParameterMustHaveUniqueNameAnalyzer : AnalyzerBase
public ParameterMustHaveUniqueNameAnalyzer() public ParameterMustHaveUniqueNameAnalyzer()
: base( : base(
"Parameters must have unique names", "Parameters must have unique names",
"This parameter's name must be unique within the command (comparison IS NOT case sensitive). " + "This parameter's name must be unique within the command (comparison IS NOT case sensitive). "
"Specified name: `{0}`. " + + "Specified name: `{0}`. "
"Property bound to another parameter with the same name: `{1}`.") + "Property bound to another parameter with the same name: `{1}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -35,8 +35,7 @@ public class ParameterMustHaveUniqueNameAnalyzer : AnalyzerBase
if (string.IsNullOrWhiteSpace(parameter.Name)) if (string.IsNullOrWhiteSpace(parameter.Name))
return; return;
var otherProperties = property var otherProperties = property.ContainingType
.ContainingType
.GetMembers() .GetMembers()
.OfType<IPropertySymbol>() .OfType<IPropertySymbol>()
.Where(m => !m.Equals(property)) .Where(m => !m.Equals(property))
@@ -51,7 +50,13 @@ public class ParameterMustHaveUniqueNameAnalyzer : AnalyzerBase
if (string.IsNullOrWhiteSpace(otherParameter.Name)) if (string.IsNullOrWhiteSpace(otherParameter.Name))
continue; continue;
if (string.Equals(parameter.Name, otherParameter.Name, StringComparison.OrdinalIgnoreCase)) if (
string.Equals(
parameter.Name,
otherParameter.Name,
StringComparison.OrdinalIgnoreCase
)
)
{ {
context.ReportDiagnostic( context.ReportDiagnostic(
CreateDiagnostic( CreateDiagnostic(

View File

@@ -13,16 +13,16 @@ public class ParameterMustHaveUniqueOrderAnalyzer : AnalyzerBase
public ParameterMustHaveUniqueOrderAnalyzer() public ParameterMustHaveUniqueOrderAnalyzer()
: base( : base(
"Parameters must have unique order", "Parameters must have unique order",
"This parameter's order must be unique within the command. " + "This parameter's order must be unique within the command. "
"Specified order: {0}. " + + "Specified order: {0}. "
"Property bound to another parameter with the same order: `{1}`.") + "Property bound to another parameter with the same order: `{1}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
if (property.ContainingType is null) if (property.ContainingType is null)
return; return;
@@ -31,8 +31,7 @@ public class ParameterMustHaveUniqueOrderAnalyzer : AnalyzerBase
if (parameter is null) if (parameter is null)
return; return;
var otherProperties = property var otherProperties = property.ContainingType
.ContainingType
.GetMembers() .GetMembers()
.OfType<IPropertySymbol>() .OfType<IPropertySymbol>()
.Where(m => !m.Equals(property)) .Where(m => !m.Equals(property))

View File

@@ -13,14 +13,14 @@ public class ParameterMustHaveValidConverterAnalyzer : AnalyzerBase
public ParameterMustHaveValidConverterAnalyzer() public ParameterMustHaveValidConverterAnalyzer()
: base( : base(
$"Parameter converters must derive from `{SymbolNames.CliFxBindingConverterClass}`", $"Parameter converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
$"Converter specified for this parameter must derive from a compatible `{SymbolNames.CliFxBindingConverterClass}`.") $"Converter specified for this parameter must derive from a compatible `{SymbolNames.CliFxBindingConverterClass}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
var parameter = CommandParameterSymbol.TryResolve(property); var parameter = CommandParameterSymbol.TryResolve(property);
if (parameter is null) if (parameter is null)
@@ -29,21 +29,26 @@ public class ParameterMustHaveValidConverterAnalyzer : AnalyzerBase
if (parameter.ConverterType is null) if (parameter.ConverterType is null)
return; return;
var converterValueType = parameter var converterValueType = parameter.ConverterType
.ConverterType
.GetBaseTypes() .GetBaseTypes()
.FirstOrDefault(t => t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass))? .FirstOrDefault(
.TypeArguments t => t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass)
.FirstOrDefault(); )
?.TypeArguments.FirstOrDefault();
// Value returned by the converter must be assignable to the property type // Value returned by the converter must be assignable to the property type
var isCompatible = var isCompatible =
converterValueType is not null && (parameter.IsScalar() converterValueType is not null
// Scalar && (
? context.Compilation.IsAssignable(converterValueType, property.Type) parameter.IsScalar()
// Non-scalar (assume we can handle all IEnumerable types for simplicity) // Scalar
: property.Type.TryGetEnumerableUnderlyingType() is { } enumerableUnderlyingType && ? context.Compilation.IsAssignable(converterValueType, property.Type)
context.Compilation.IsAssignable(converterValueType, enumerableUnderlyingType) // Non-scalar (assume we can handle all IEnumerable types for simplicity)
: property.Type.TryGetEnumerableUnderlyingType() is { } enumerableUnderlyingType
&& context.Compilation.IsAssignable(
converterValueType,
enumerableUnderlyingType
)
); );
if (!isCompatible) if (!isCompatible)

View File

@@ -13,14 +13,14 @@ public class ParameterMustHaveValidValidatorsAnalyzer : AnalyzerBase
public ParameterMustHaveValidValidatorsAnalyzer() public ParameterMustHaveValidValidatorsAnalyzer()
: base( : base(
$"Parameter validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`", $"Parameter validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
$"Each validator specified for this parameter must derive from a compatible `{SymbolNames.CliFxBindingValidatorClass}`.") $"Each validator specified for this parameter must derive from a compatible `{SymbolNames.CliFxBindingValidatorClass}`."
{ ) { }
}
private void Analyze( private void Analyze(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration, PropertyDeclarationSyntax propertyDeclaration,
IPropertySymbol property) IPropertySymbol property
)
{ {
var parameter = CommandParameterSymbol.TryResolve(property); var parameter = CommandParameterSymbol.TryResolve(property);
if (parameter is null) if (parameter is null)
@@ -30,14 +30,16 @@ public class ParameterMustHaveValidValidatorsAnalyzer : AnalyzerBase
{ {
var validatorValueType = validatorType var validatorValueType = validatorType
.GetBaseTypes() .GetBaseTypes()
.FirstOrDefault(t => t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingValidatorClass))? .FirstOrDefault(
.TypeArguments t =>
.FirstOrDefault(); t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingValidatorClass)
)
?.TypeArguments.FirstOrDefault();
// Value passed to the validator must be assignable from the property type // Value passed to the validator must be assignable from the property type
var isCompatible = var isCompatible =
validatorValueType is not null && validatorValueType is not null
context.Compilation.IsAssignable(property.Type, validatorValueType); && context.Compilation.IsAssignable(property.Type, validatorValueType);
if (!isCompatible) if (!isCompatible)
{ {

View File

@@ -15,13 +15,13 @@ public class SystemConsoleShouldBeAvoidedAnalyzer : AnalyzerBase
: base( : base(
$"Avoid calling `System.Console` where `{SymbolNames.CliFxConsoleInterface}` is available", $"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.", $"Use the provided `{SymbolNames.CliFxConsoleInterface}` abstraction instead of `System.Console` to ensure that the command can be tested in isolation.",
DiagnosticSeverity.Warning) DiagnosticSeverity.Warning
{ ) { }
}
private MemberAccessExpressionSyntax? TryGetSystemConsoleMemberAccess( private MemberAccessExpressionSyntax? TryGetSystemConsoleMemberAccess(
SyntaxNodeAnalysisContext context, SyntaxNodeAnalysisContext context,
SyntaxNode node) SyntaxNode node
)
{ {
var currentNode = node; var currentNode = node;
@@ -53,8 +53,7 @@ public class SystemConsoleShouldBeAvoidedAnalyzer : AnalyzerBase
return; return;
// Check if IConsole is available in scope as an alternative to System.Console // Check if IConsole is available in scope as an alternative to System.Console
var isConsoleInterfaceAvailable = context var isConsoleInterfaceAvailable = context.Node
.Node
.Ancestors() .Ancestors()
.OfType<MethodDeclarationSyntax>() .OfType<MethodDeclarationSyntax>()
.SelectMany(m => m.ParameterList.Parameters) .SelectMany(m => m.ParameterList.Parameters)
@@ -65,9 +64,7 @@ public class SystemConsoleShouldBeAvoidedAnalyzer : AnalyzerBase
if (isConsoleInterfaceAvailable) if (isConsoleInterfaceAvailable)
{ {
context.ReportDiagnostic( context.ReportDiagnostic(CreateDiagnostic(systemConsoleMemberAccess.GetLocation()));
CreateDiagnostic(systemConsoleMemberAccess.GetLocation())
);
} }
} }

View File

@@ -29,56 +29,72 @@ internal static class RoslynExtensions
} }
} }
public static ITypeSymbol? TryGetEnumerableUnderlyingType(this ITypeSymbol type) => type public static ITypeSymbol? TryGetEnumerableUnderlyingType(this ITypeSymbol type) =>
.AllInterfaces type.AllInterfaces
.FirstOrDefault(i => i.ConstructedFrom.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T)? .FirstOrDefault(
.TypeArguments[0]; i =>
i.ConstructedFrom.SpecialType
== SpecialType.System_Collections_Generic_IEnumerable_T
)
?.TypeArguments[0];
// Detect if the property is required through roundabout means so as to not have to take dependency // Detect if the property is required through roundabout means so as to not have to take dependency
// on higher versions of the C# compiler. // on higher versions of the C# compiler.
public static bool IsRequired(this IPropertySymbol property) => property public static bool IsRequired(this IPropertySymbol property) =>
property
// Can't rely on the RequiredMemberAttribute because it's generated by the compiler, not added by the user, // Can't rely on the RequiredMemberAttribute because it's generated by the compiler, not added by the user,
// so we have to check for the presence of the `required` modifier in the syntax tree instead. // so we have to check for the presence of the `required` modifier in the syntax tree instead.
.DeclaringSyntaxReferences .DeclaringSyntaxReferences
.Select(r => r.GetSyntax()) .Select(r => r.GetSyntax())
.OfType<PropertyDeclarationSyntax>() .OfType<PropertyDeclarationSyntax>()
.SelectMany(p => p.Modifiers) .SelectMany(p => p.Modifiers)
.Any(m => m.IsKind((SyntaxKind)8447)); .Any(m => m.IsKind((SyntaxKind)8447));
public static bool IsAssignable(this Compilation compilation, ITypeSymbol source, ITypeSymbol destination) => public static bool IsAssignable(
compilation.ClassifyConversion(source, destination).Exists; this Compilation compilation,
ITypeSymbol source,
ITypeSymbol destination
) => compilation.ClassifyConversion(source, destination).Exists;
public static void HandleClassDeclaration( public static void HandleClassDeclaration(
this AnalysisContext analysisContext, this AnalysisContext analysisContext,
Action<SyntaxNodeAnalysisContext, ClassDeclarationSyntax, ITypeSymbol> analyze) 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); var type = ctx.SemanticModel.GetDeclaredSymbol(classDeclaration);
if (type is null) if (type is null)
return; return;
analyze(ctx, classDeclaration, type); analyze(ctx, classDeclaration, type);
}, SyntaxKind.ClassDeclaration); },
SyntaxKind.ClassDeclaration
);
} }
public static void HandlePropertyDeclaration( public static void HandlePropertyDeclaration(
this AnalysisContext analysisContext, this AnalysisContext analysisContext,
Action<SyntaxNodeAnalysisContext, PropertyDeclarationSyntax, IPropertySymbol> analyze) 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); var property = ctx.SemanticModel.GetDeclaredSymbol(propertyDeclaration);
if (property is null) if (property is null)
return; return;
analyze(ctx, propertyDeclaration, property); analyze(ctx, propertyDeclaration, property);
}, SyntaxKind.PropertyDeclaration); },
SyntaxKind.PropertyDeclaration
);
} }
} }

View File

@@ -7,7 +7,8 @@ internal static class StringExtensions
public static string TrimEnd( public static string TrimEnd(
this string str, this string str,
string sub, string sub,
StringComparison comparison = StringComparison.Ordinal) StringComparison comparison = StringComparison.Ordinal
)
{ {
while (str.EndsWith(sub, comparison)) while (str.EndsWith(sub, comparison))
str = str[..^sub.Length]; str = str[..^sub.Length];

View File

@@ -16,9 +16,7 @@ public partial class Benchmarks
[NamedArgument('b', "bool", Constraint = NumArgsConstraint.Optional, Const = true)] [NamedArgument('b', "bool", Constraint = NumArgsConstraint.Optional, Const = true)]
public bool BoolOption { get; set; } public bool BoolOption { get; set; }
public void Execute() public void Execute() { }
{
}
} }
[Benchmark(Description = "Clipr")] [Benchmark(Description = "Clipr")]

View File

@@ -8,14 +8,10 @@ public partial class Benchmarks
public class CoconaCommand public class CoconaCommand
{ {
public void Execute( public void Execute(
[Option("str", new []{'s'})] [Option("str", new[] { 's' })] string? strOption,
string? strOption, [Option("int", new[] { 'i' })] int intOption,
[Option("int", new []{'i'})] [Option("bool", new[] { 'b' })] bool boolOption
int intOption, ) { }
[Option("bool", new []{'b'})]
bool boolOption)
{
}
} }
[Benchmark(Description = "Cocona")] [Benchmark(Description = "Cocona")]

View File

@@ -16,9 +16,7 @@ public partial class Benchmarks
[Option('b', "bool")] [Option('b', "bool")]
public bool BoolOption { get; set; } public bool BoolOption { get; set; }
public void Execute() public void Execute() { }
{
}
} }
[Benchmark(Description = "CommandLineParser")] [Benchmark(Description = "CommandLineParser")]

View File

@@ -16,9 +16,7 @@ public partial class Benchmarks
[ArgShortcut("--bool"), ArgShortcut("-b")] [ArgShortcut("--bool"), ArgShortcut("-b")]
public bool BoolOption { get; set; } public bool BoolOption { get; set; }
public void Main() public void Main() { }
{
}
} }
[Benchmark(Description = "PowerArgs")] [Benchmark(Description = "PowerArgs")]

View File

@@ -15,18 +15,9 @@ public partial class Benchmarks
{ {
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>() },
Argument = new Argument<string?>() new Option(new[] { "--bool", "-b" }) { Argument = new Argument<bool>() }
},
new Option(new[] {"--int", "-i"})
{
Argument = new Argument<int>()
},
new Option(new[] {"--bool", "-b"})
{
Argument = new Argument<bool>()
}
}; };
command.Handler = CommandHandler.Create( command.Handler = CommandHandler.Create(

View File

@@ -9,11 +9,10 @@ namespace CliFx.Benchmarks;
[Orderer(SummaryOrderPolicy.FastestToSlowest)] [Orderer(SummaryOrderPolicy.FastestToSlowest)]
public partial class Benchmarks public partial class Benchmarks
{ {
private static readonly string[] Arguments = {"--str", "hello world", "-i", "13", "-b"}; private static readonly string[] Arguments = { "--str", "hello world", "-i", "13", "-b" };
public static void Main() => BenchmarkRunner.Run<Benchmarks>( public static void Main() =>
DefaultConfig BenchmarkRunner.Run<Benchmarks>(
.Instance DefaultConfig.Instance.WithOptions(ConfigOptions.DisableOptimizationsValidator)
.WithOptions(ConfigOptions.DisableOptimizationsValidator) );
);
} }

View File

@@ -10,6 +10,7 @@
<PackageReference Include="clipr" Version="1.6.1" /> <PackageReference Include="clipr" Version="1.6.1" />
<PackageReference Include="Cocona" Version="2.2.0" /> <PackageReference Include="Cocona" Version="2.2.0" />
<PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.2" /> <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.2" />
<PackageReference Include="PowerArgs" Version="4.0.2" /> <PackageReference Include="PowerArgs" Version="4.0.2" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" /> <PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />

View File

@@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
</ItemGroup> </ItemGroup>

View File

@@ -49,21 +49,23 @@ public partial class BookAddCommand
{ {
private static readonly Random Random = new(); private static readonly Random Random = new();
private static DateTimeOffset CreateRandomDate() => new( private static DateTimeOffset CreateRandomDate() =>
Random.Next(1800, 2020), new(
Random.Next(1, 12), Random.Next(1800, 2020),
Random.Next(1, 28), Random.Next(1, 12),
Random.Next(1, 23), Random.Next(1, 28),
Random.Next(1, 59), Random.Next(1, 23),
Random.Next(1, 59), Random.Next(1, 59),
TimeSpan.Zero Random.Next(1, 59),
); TimeSpan.Zero
);
private static Isbn CreateRandomIsbn() => new( private static Isbn CreateRandomIsbn() =>
Random.Next(0, 999), new(
Random.Next(0, 99), Random.Next(0, 999),
Random.Next(0, 99999), Random.Next(0, 99),
Random.Next(0, 99), Random.Next(0, 99999),
Random.Next(0, 9) Random.Next(0, 99),
); Random.Next(0, 9)
);
} }

View File

@@ -2,7 +2,13 @@
namespace CliFx.Demo.Domain; namespace CliFx.Demo.Domain;
public partial record Isbn(int EanPrefix, int RegistrationGroup, int Registrant, int Publication, int CheckDigit) public partial record Isbn(
int EanPrefix,
int RegistrationGroup,
int Registrant,
int Publication,
int CheckDigit
)
{ {
public override string ToString() => public override string ToString() =>
$"{EanPrefix:000}-{RegistrationGroup:00}-{Registrant:00000}-{Publication:00}-{CheckDigit:0}"; $"{EanPrefix:000}-{RegistrationGroup:00}-{Registrant:00000}-{Publication:00}-{CheckDigit:0}";

View File

@@ -6,7 +6,8 @@ namespace CliFx.Demo.Domain;
public class LibraryProvider public class LibraryProvider
{ {
private static string StorageFilePath { get; } = Path.Combine(Directory.GetCurrentDirectory(), "Library.json"); private static string StorageFilePath { get; } =
Path.Combine(Directory.GetCurrentDirectory(), "Library.json");
private void StoreLibrary(Library library) private void StoreLibrary(Library library)
{ {
@@ -24,7 +25,8 @@ public class LibraryProvider
return JsonSerializer.Deserialize<Library>(data) ?? Library.Empty; return JsonSerializer.Deserialize<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) public void AddBook(Book book)
{ {

View File

@@ -6,6 +6,10 @@
<ApplicationIcon>../favicon.ico</ApplicationIcon> <ApplicationIcon>../favicon.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CliFx\CliFx.csproj" /> <ProjectReference Include="..\CliFx\CliFx.csproj" />
<ProjectReference Include="..\CliFx.Analyzers\CliFx.Analyzers.csproj" ReferenceOutputAssembly="false" OutputItemType="analyzer" /> <ProjectReference Include="..\CliFx.Analyzers\CliFx.Analyzers.csproj" ReferenceOutputAssembly="false" OutputItemType="analyzer" />

View File

@@ -14,10 +14,7 @@ public class CancellationTestCommand : ICommand
{ {
console.Output.WriteLine("Started."); console.Output.WriteLine("Started.");
await Task.Delay( await Task.Delay(TimeSpan.FromSeconds(3), console.RegisterCancellationHandler());
TimeSpan.FromSeconds(3),
console.RegisterCancellationHandler()
);
console.Output.WriteLine("Completed."); console.Output.WriteLine("Completed.");
} }

View File

@@ -10,10 +10,11 @@ namespace CliFx.Tests.Dummy;
public static class Program public static class Program
{ {
// Path to the apphost // Path to the apphost
public static string FilePath { get; } = Path.ChangeExtension( public static string FilePath { get; } =
Assembly.GetExecutingAssembly().Location, Path.ChangeExtension(
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "exe" : null Assembly.GetExecutingAssembly().Location,
); RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "exe" : null
);
public static async Task Main() public static async Task Main()
{ {
@@ -23,9 +24,6 @@ public static class Program
"false" "false"
); );
await new CliApplicationBuilder() await new CliApplicationBuilder().AddCommandsFromThisAssembly().Build().RunAsync();
.AddCommandsFromThisAssembly()
.Build()
.RunAsync();
} }
} }

View File

@@ -11,9 +11,7 @@ namespace CliFx.Tests;
public class ApplicationSpecs : SpecsBase public class ApplicationSpecs : SpecsBase
{ {
public ApplicationSpecs(ITestOutputHelper testOutput) public ApplicationSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact] [Fact]
public async Task I_can_create_an_application_with_the_default_configuration() public async Task I_can_create_an_application_with_the_default_configuration()
@@ -24,10 +22,7 @@ public class ApplicationSpecs : SpecsBase
.UseConsole(FakeConsole) .UseConsole(FakeConsole)
.Build(); .Build();
var exitCode = await app.RunAsync( var exitCode = await app.RunAsync(Array.Empty<string>(), new Dictionary<string, string>());
Array.Empty<string>(),
new Dictionary<string, string>()
);
// Assert // Assert
exitCode.Should().Be(0); exitCode.Should().Be(0);
@@ -40,8 +35,8 @@ public class ApplicationSpecs : SpecsBase
var app = new CliApplicationBuilder() var app = new CliApplicationBuilder()
.AddCommand<NoOpCommand>() .AddCommand<NoOpCommand>()
.AddCommandsFrom(typeof(NoOpCommand).Assembly) .AddCommandsFrom(typeof(NoOpCommand).Assembly)
.AddCommands(new[] {typeof(NoOpCommand)}) .AddCommands(new[] { typeof(NoOpCommand) })
.AddCommandsFrom(new[] {typeof(NoOpCommand).Assembly}) .AddCommandsFrom(new[] { typeof(NoOpCommand).Assembly })
.AddCommandsFromThisAssembly() .AddCommandsFromThisAssembly()
.AllowDebugMode() .AllowDebugMode()
.AllowPreviewMode() .AllowPreviewMode()
@@ -53,10 +48,7 @@ public class ApplicationSpecs : SpecsBase
.UseTypeActivator(Activator.CreateInstance!) .UseTypeActivator(Activator.CreateInstance!)
.Build(); .Build();
var exitCode = await app.RunAsync( var exitCode = await app.RunAsync(Array.Empty<string>(), new Dictionary<string, string>());
Array.Empty<string>(),
new Dictionary<string, string>()
);
// Assert // Assert
exitCode.Should().Be(0); exitCode.Should().Be(0);
@@ -71,10 +63,7 @@ public class ApplicationSpecs : SpecsBase
.UseConsole(FakeConsole) .UseConsole(FakeConsole)
.Build(); .Build();
var exitCode = await app.RunAsync( var exitCode = await app.RunAsync(Array.Empty<string>(), new Dictionary<string, string>());
Array.Empty<string>(),
new Dictionary<string, string>()
);
// Assert // Assert
exitCode.Should().NotBe(0); exitCode.Should().NotBe(0);

View File

@@ -15,9 +15,7 @@ namespace CliFx.Tests;
public class CancellationSpecs : SpecsBase public class CancellationSpecs : SpecsBase
{ {
public CancellationSpecs(ITestOutputHelper testOutput) public CancellationSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact(Timeout = 15000)] [Fact(Timeout = 15000)]
public async Task I_can_configure_the_command_to_listen_to_the_interrupt_signal() public async Task I_can_configure_the_command_to_listen_to_the_interrupt_signal()
@@ -41,24 +39,20 @@ public class CancellationSpecs : SpecsBase
PipeTarget.ToStringBuilder(stdOutBuffer) PipeTarget.ToStringBuilder(stdOutBuffer)
); );
var command = var command = Cli.Wrap(Dummy.Program.FilePath).WithArguments("cancel-test") | pipeTarget;
Cli.Wrap(Dummy.Program.FilePath).WithArguments("cancel-test") |
pipeTarget;
// Act & assert // Act & assert
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await Assert.ThrowsAnyAsync<OperationCanceledException>(
await command.ExecuteAsync( async () =>
// Forceful cancellation (not required because we have a timeout) await command.ExecuteAsync(
CancellationToken.None, // Forceful cancellation (not required because we have a timeout)
// Graceful cancellation CancellationToken.None,
cts.Token // Graceful cancellation
) cts.Token
)
); );
stdOutBuffer.ToString().Trim().Should().ConsistOfLines( stdOutBuffer.ToString().Trim().Should().ConsistOfLines("Started.", "Cancelled.");
"Started.",
"Cancelled."
);
} }
[Fact] [Fact]
@@ -111,9 +105,6 @@ public class CancellationSpecs : SpecsBase
exitCode.Should().NotBe(0); exitCode.Should().NotBe(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().ConsistOfLines( stdOut.Trim().Should().ConsistOfLines("Started.", "Cancelled.");
"Started.",
"Cancelled."
);
} }
} }

View File

@@ -12,6 +12,7 @@
<PackageReference Include="Basic.Reference.Assemblies.Net70" Version="1.4.2" /> <PackageReference Include="Basic.Reference.Assemblies.Net70" Version="1.4.2" />
<PackageReference Include="CliWrap" Version="3.6.4" /> <PackageReference Include="CliWrap" Version="3.6.4" />
<PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" /> <PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.2" PrivateAssets="all" /> <PackageReference Include="GitHubActionsTestLogger" Version="2.3.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />

View File

@@ -17,9 +17,7 @@ namespace CliFx.Tests;
public class ConsoleSpecs : SpecsBase public class ConsoleSpecs : SpecsBase
{ {
public ConsoleSpecs(ITestOutputHelper testOutput) public ConsoleSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact(Timeout = 15000)] [Fact(Timeout = 15000)]
public async Task I_can_run_the_application_with_the_default_console_implementation_to_interact_with_the_system_console() public async Task I_can_run_the_application_with_the_default_console_implementation_to_interact_with_the_system_console()
@@ -28,8 +26,7 @@ public class ConsoleSpecs : SpecsBase
// Arrange // Arrange
var command = var command =
"Hello world" | "Hello world" | Cli.Wrap(Dummy.Program.FilePath).WithArguments("console-test");
Cli.Wrap(Dummy.Program.FilePath).WithArguments("console-test");
// Act // Act
var result = await command.ExecuteBufferedAsync(); var result = await command.ExecuteBufferedAsync();
@@ -205,10 +202,6 @@ public class ConsoleSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().ConsistOfLines( stdOut.Trim().Should().ConsistOfLines("D0", "A", "Backspace");
"D0",
"A",
"Backspace"
);
} }
} }

View File

@@ -11,9 +11,7 @@ namespace CliFx.Tests;
public class ConversionSpecs : SpecsBase public class ConversionSpecs : SpecsBase
{ {
public ConversionSpecs(ITestOutputHelper testOutput) public ConversionSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact] [Fact]
public async Task I_can_bind_a_parameter_or_an_option_to_a_string_property() public async Task I_can_bind_a_parameter_or_an_option_to_a_string_property()
@@ -44,7 +42,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "xyz"}, new[] { "-f", "xyz" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -84,7 +82,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "xyz"}, new[] { "-f", "xyz" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -133,12 +131,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] new[] { "-f", "true", "-b", "false", "-c" },
{
"-f", "true",
"-b", "false",
"-c"
},
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -146,11 +139,7 @@ public class ConversionSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = True", "Bar = False", "Baz = True");
"Foo = True",
"Bar = False",
"Baz = True"
);
} }
[Fact] [Fact]
@@ -182,7 +171,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "32"}, new[] { "-f", "32" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -222,7 +211,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "32.14"}, new[] { "-f", "32.14" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -262,7 +251,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "1995-04-28Z"}, new[] { "-f", "1995-04-28Z" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -302,7 +291,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "12:34:56"}, new[] { "-f", "12:34:56" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -344,7 +333,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "two"}, new[] { "-f", "two" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -389,7 +378,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-b", "123"}, new[] { "-b", "123" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -397,10 +386,7 @@ public class ConversionSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = ", "Bar = 123");
"Foo = ",
"Bar = 123"
);
} }
[Fact] [Fact]
@@ -439,7 +425,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-b", "two"}, new[] { "-b", "two" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -447,10 +433,7 @@ public class ConversionSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = ", "Bar = 2");
"Foo = ",
"Bar = 2"
);
} }
[Fact] [Fact]
@@ -489,7 +472,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "xyz"}, new[] { "-f", "xyz" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -554,7 +537,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "hello", "-b", "world"}, new[] { "-f", "hello", "-b", "world" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -562,10 +545,7 @@ public class ConversionSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = hello", "Bar = world");
"Foo = hello",
"Bar = world"
);
} }
[Fact] [Fact]
@@ -603,7 +583,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "hello world"}, new[] { "-f", "hello world" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -645,7 +625,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "one", "two", "three"}, new[] { "-f", "one", "two", "three" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -653,11 +633,7 @@ public class ConversionSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("one", "two", "three");
"one",
"two",
"three"
);
} }
[Fact] [Fact]
@@ -691,7 +667,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "one", "two", "three"}, new[] { "-f", "one", "two", "three" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -699,11 +675,7 @@ public class ConversionSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("one", "two", "three");
"one",
"two",
"three"
);
} }
[Fact] [Fact]
@@ -737,7 +709,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "one", "two", "three"}, new[] { "-f", "one", "two", "three" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -745,11 +717,7 @@ public class ConversionSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("one", "two", "three");
"one",
"two",
"three"
);
} }
[Fact] [Fact]
@@ -783,7 +751,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "1", "13", "27"}, new[] { "-f", "1", "13", "27" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -791,11 +759,7 @@ public class ConversionSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("1", "13", "27");
"1",
"13",
"27"
);
} }
[Fact] [Fact]
@@ -827,7 +791,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "xyz"}, new[] { "-f", "xyz" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -870,7 +834,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "one", "two"}, new[] { "-f", "one", "two" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -906,7 +870,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "12.34"}, new[] { "-f", "12.34" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -952,7 +916,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "12"}, new[] { "-f", "12" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -997,7 +961,7 @@ public class ConversionSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "bar"}, new[] { "-f", "bar" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );

View File

@@ -14,9 +14,7 @@ namespace CliFx.Tests;
public class DirectivesSpecs : SpecsBase public class DirectivesSpecs : SpecsBase
{ {
public DirectivesSpecs(ITestOutputHelper testOutput) public DirectivesSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact(Timeout = 15000)] [Fact(Timeout = 15000)]
public async Task I_can_use_the_debug_directive_to_make_the_application_wait_for_the_debugger_to_attach() public async Task I_can_use_the_debug_directive_to_make_the_application_wait_for_the_debugger_to_attach()
@@ -32,9 +30,7 @@ public class DirectivesSpecs : SpecsBase
cts.Cancel(); cts.Cancel();
} }
var command = var command = Cli.Wrap(Dummy.Program.FilePath).WithArguments("[debug]") | HandleStdOut;
Cli.Wrap(Dummy.Program.FilePath).WithArguments("[debug]") |
HandleStdOut;
// Act & assert // Act & assert
try try
@@ -70,22 +66,29 @@ public class DirectivesSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"[preview]", "cmd", "param", "-abc", "--option", "foo"}, new[] { "[preview]", "cmd", "param", "-abc", "--option", "foo" },
new Dictionary<string, string> new Dictionary<string, string> { ["ENV_QOP"] = "hello", ["ENV_KIL"] = "world" }
{
["ENV_QOP"] = "hello",
["ENV_KIL"] = "world"
}
); );
// Assert // Assert
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"cmd", "<param>", "[-a]", "[-b]", "[-c]", "[--option \"foo\"]", .Should()
"ENV_QOP", "=", "\"hello\"", .ContainAllInOrder(
"ENV_KIL", "=", "\"world\"" "cmd",
); "<param>",
"[-a]",
"[-b]",
"[-c]",
"[--option \"foo\"]",
"ENV_QOP",
"=",
"\"hello\"",
"ENV_KIL",
"=",
"\"world\""
);
} }
} }

View File

@@ -15,9 +15,7 @@ namespace CliFx.Tests;
public class EnvironmentSpecs : SpecsBase public class EnvironmentSpecs : SpecsBase
{ {
public EnvironmentSpecs(ITestOutputHelper testOutput) public EnvironmentSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact] [Fact]
public async Task I_can_configure_an_option_to_fall_back_to_an_environment_variable_if_the_user_does_not_provide_the_corresponding_argument() public async Task I_can_configure_an_option_to_fall_back_to_an_environment_variable_if_the_user_does_not_provide_the_corresponding_argument()
@@ -53,22 +51,15 @@ public class EnvironmentSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--foo", "42"}, new[] { "--foo", "42" },
new Dictionary<string, string> new Dictionary<string, string> { ["ENV_FOO"] = "100", ["ENV_BAR"] = "200" }
{
["ENV_FOO"] = "100",
["ENV_BAR"] = "200"
}
); );
// Assert // Assert
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Trim().Should().ConsistOfLines( stdOut.Trim().Should().ConsistOfLines("42", "200");
"42",
"200"
);
} }
[Fact] [Fact]
@@ -103,20 +94,14 @@ public class EnvironmentSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
Array.Empty<string>(), Array.Empty<string>(),
new Dictionary<string, string> new Dictionary<string, string> { ["ENV_FOO"] = $"bar{Path.PathSeparator}baz" }
{
["ENV_FOO"] = $"bar{Path.PathSeparator}baz"
}
); );
// Assert // Assert
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("bar", "baz");
"bar",
"baz"
);
} }
[Fact] [Fact]
@@ -149,10 +134,7 @@ public class EnvironmentSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
Array.Empty<string>(), Array.Empty<string>(),
new Dictionary<string, string> new Dictionary<string, string> { ["ENV_FOO"] = $"bar{Path.PathSeparator}baz" }
{
["ENV_FOO"] = $"bar{Path.PathSeparator}baz"
}
); );
// Assert // Assert
@@ -171,9 +153,7 @@ public class EnvironmentSpecs : SpecsBase
// Arrange // Arrange
var command = Cli.Wrap(Dummy.Program.FilePath) var command = Cli.Wrap(Dummy.Program.FilePath)
.WithArguments("env-test") .WithArguments("env-test")
.WithEnvironmentVariables(e => e .WithEnvironmentVariables(e => e.Set("ENV_TARGET", "Mars"));
.Set("ENV_TARGET", "Mars")
);
// Act // Act
var result = await command.ExecuteBufferedAsync(); var result = await command.ExecuteBufferedAsync();

View File

@@ -12,9 +12,7 @@ namespace CliFx.Tests;
public class ErrorReportingSpecs : SpecsBase public class ErrorReportingSpecs : SpecsBase
{ {
public ErrorReportingSpecs(ITestOutputHelper testOutput) public ErrorReportingSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact] [Fact]
public async Task I_can_throw_an_exception_in_a_command_to_report_an_error_with_a_stacktrace() public async Task I_can_throw_an_exception_in_a_command_to_report_an_error_with_a_stacktrace()
@@ -50,10 +48,9 @@ public class ErrorReportingSpecs : SpecsBase
stdOut.Should().BeEmpty(); stdOut.Should().BeEmpty();
var stdErr = FakeConsole.ReadErrorString(); var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().ContainAllInOrder( stdErr
"System.Exception", "Something went wrong", .Should()
"at", "CliFx." .ContainAllInOrder("System.Exception", "Something went wrong", "at", "CliFx.");
);
} }
[Fact] [Fact]
@@ -90,11 +87,16 @@ public class ErrorReportingSpecs : SpecsBase
stdOut.Should().BeEmpty(); stdOut.Should().BeEmpty();
var stdErr = FakeConsole.ReadErrorString(); var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().ContainAllInOrder( stdErr
"System.Exception", "Something went wrong", .Should()
"System.Exception", "Another exception", .ContainAllInOrder(
"at", "CliFx." "System.Exception",
); "Something went wrong",
"System.Exception",
"Another exception",
"at",
"CliFx."
);
} }
[Fact] [Fact]
@@ -168,10 +170,7 @@ public class ErrorReportingSpecs : SpecsBase
stdOut.Should().BeEmpty(); stdOut.Should().BeEmpty();
var stdErr = FakeConsole.ReadErrorString(); var stdErr = FakeConsole.ReadErrorString();
stdErr.Should().ContainAllInOrder( stdErr.Should().ContainAllInOrder("CliFx.Exceptions.CommandException", "at", "CliFx.");
"CliFx.Exceptions.CommandException",
"at", "CliFx."
);
} }
[Fact] [Fact]

View File

@@ -12,9 +12,7 @@ namespace CliFx.Tests;
public class HelpTextSpecs : SpecsBase public class HelpTextSpecs : SpecsBase
{ {
public HelpTextSpecs(ITestOutputHelper testOutput) public HelpTextSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact] [Fact]
public async Task I_can_request_the_help_text_by_running_the_application_without_arguments_if_the_default_command_is_not_defined() public async Task I_can_request_the_help_text_by_running_the_application_without_arguments_if_the_default_command_is_not_defined()
@@ -61,7 +59,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -101,7 +99,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -146,7 +144,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"cmd", "--help"}, new[] { "cmd", "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -191,7 +189,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"cmd", "sub", "--help"}, new[] { "cmd", "sub", "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -214,7 +212,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"invalid-command", "--invalid-option"}, new[] { "invalid-command", "--invalid-option" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -241,7 +239,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -249,11 +247,7 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAll( stdOut.Should().ContainAll("App title", "App description", "App version");
"App title",
"App description",
"App version"
);
} }
[Fact] [Fact]
@@ -278,7 +272,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -286,10 +280,7 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut.Should().ContainAllInOrder("DESCRIPTION", "Description of the default command.");
"DESCRIPTION",
"Description of the default command."
);
} }
[Fact] [Fact]
@@ -320,7 +311,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -328,10 +319,7 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut.Should().ContainAllInOrder("USAGE", "[command]", "[...]");
"USAGE",
"[command]", "[...]"
);
} }
[Fact] [Fact]
@@ -365,7 +353,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -373,10 +361,7 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut.Should().ContainAllInOrder("USAGE", "<foo>", "<bar>", "<baz...>");
"USAGE",
"<foo>", "<bar>", "<baz...>"
);
} }
// https://github.com/Tyrrrz/CliFx/issues/117 // https://github.com/Tyrrrz/CliFx/issues/117
@@ -425,10 +410,7 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut.Should().ContainAllInOrder("USAGE", "<foo>", "<bar>", "<baz...>");
"USAGE",
"<foo>", "<bar>", "<baz...>"
);
} }
[Fact] [Fact]
@@ -462,7 +444,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -470,10 +452,9 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"USAGE", .Should()
"--foo <value>", "--baz <values...>", "[options]" .ContainAllInOrder("USAGE", "--foo <value>", "--baz <values...>", "[options]");
);
} }
[Fact] [Fact]
@@ -504,7 +485,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -512,12 +493,16 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"PARAMETERS", .Should()
"foo", "Description of foo.", .ContainAllInOrder(
"OPTIONS", "PARAMETERS",
"--bar", "Description of bar." "foo",
); "Description of foo.",
"OPTIONS",
"--bar",
"Description of bar."
);
} }
[Fact] [Fact]
@@ -542,7 +527,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -550,11 +535,16 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"OPTIONS", .Should()
"-h", "--help", "Shows help text", .ContainAllInOrder(
"--version", "Shows version information" "OPTIONS",
); "-h",
"--help",
"Shows help text",
"--version",
"Shows version information"
);
} }
[Fact] [Fact]
@@ -579,7 +569,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"cmd", "--help"}, new[] { "cmd", "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -588,14 +578,9 @@ public class HelpTextSpecs : SpecsBase
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut.Should().ContainAllInOrder("OPTIONS", "-h", "--help", "Shows help text");
"OPTIONS",
"-h", "--help", "Shows help text"
);
stdOut.Should().NotContainAny( stdOut.Should().NotContainAny("--version", "Shows version information");
"--version", "Shows version information"
);
} }
[Fact] [Fact]
@@ -628,7 +613,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -636,12 +621,22 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"PARAMETERS", .Should()
"foo", "Choices:", "One", "Two", "Three", .ContainAllInOrder(
"OPTIONS", "PARAMETERS",
"--bar", "Choices:", "One", "Two", "Three" "foo",
); "Choices:",
"One",
"Two",
"Three",
"OPTIONS",
"--bar",
"Choices:",
"One",
"Two",
"Three"
);
} }
[Fact] [Fact]
@@ -674,7 +669,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -682,12 +677,22 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"PARAMETERS", .Should()
"foo", "Choices:", "One", "Two", "Three", .ContainAllInOrder(
"OPTIONS", "PARAMETERS",
"--bar", "Choices:", "One", "Two", "Three" "foo",
); "Choices:",
"One",
"Two",
"Three",
"OPTIONS",
"--bar",
"Choices:",
"One",
"Two",
"Three"
);
} }
[Fact] [Fact]
@@ -720,7 +725,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -728,12 +733,22 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"PARAMETERS", .Should()
"foo", "Choices:", "One", "Two", "Three", .ContainAllInOrder(
"OPTIONS", "PARAMETERS",
"--bar", "Choices:", "One", "Two", "Three" "foo",
); "Choices:",
"One",
"Two",
"Three",
"OPTIONS",
"--bar",
"Choices:",
"One",
"Two",
"Three"
);
} }
[Fact] [Fact]
@@ -766,7 +781,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -774,11 +789,17 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"OPTIONS", .Should()
"--foo", "Environment variable:", "ENV_FOO", .ContainAllInOrder(
"--bar", "Environment variable:", "ENV_BAR" "OPTIONS",
); "--foo",
"Environment variable:",
"ENV_FOO",
"--bar",
"Environment variable:",
"ENV_BAR"
);
} }
[Fact] [Fact]
@@ -829,7 +850,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -838,16 +859,34 @@ public class HelpTextSpecs : SpecsBase
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"OPTIONS", .Should()
"--foo", "Default:", "42", .ContainAllInOrder(
"--bar", "Default:", "hello", "OPTIONS",
"--baz", "Default:", "one", "two", "three", "--foo",
"--qwe", "Default:", "True", "Default:",
"--qop", "Default:", "1337", "42",
"--zor", "Default:", "02:03:00", "--bar",
"--lol", "Default:", "Two" "Default:",
); "hello",
"--baz",
"Default:",
"one",
"two",
"three",
"--qwe",
"Default:",
"True",
"--qop",
"Default:",
"1337",
"--zor",
"Default:",
"02:03:00",
"--lol",
"Default:",
"Two"
);
stdOut.Should().NotContain("not printed"); stdOut.Should().NotContain("not printed");
} }
@@ -892,7 +931,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -901,20 +940,23 @@ public class HelpTextSpecs : SpecsBase
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"COMMANDS", .Should()
"cmd1", "Description of one command.", .ContainAllInOrder(
"cmd2", "Description of another command.", "COMMANDS",
// `cmd2 child` will appear as an immediate command because it does not "cmd1",
// have a more specific parent. "Description of one command.",
"cmd3 child", "Description of an orphaned command." "cmd2",
); "Description of another command.",
// `cmd2 child` will appear as an immediate command because it does not
// have a more specific parent.
"cmd3 child",
"Description of an orphaned command."
);
// `cmd2 child` will still appear in the list of `cmd2` subcommands, // `cmd2 child` will still appear in the list of `cmd2` subcommands,
// but its description will not be visible. // but its description will not be visible.
stdOut.Should().NotContain( stdOut.Should().NotContain("Description of another command's child command.");
"Description of another command's child command."
);
} }
[Fact] [Fact]
@@ -963,7 +1005,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--help"}, new[] { "--help" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -971,11 +1013,18 @@ public class HelpTextSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ContainAllInOrder( stdOut
"COMMANDS", .Should()
"cmd1", "Subcommands:", "cmd1 child1", .ContainAllInOrder(
"cmd2", "Subcommands:", "cmd2 child1", "cmd2 child2" "COMMANDS",
); "cmd1",
"Subcommands:",
"cmd1 child1",
"cmd2",
"Subcommands:",
"cmd2 child1",
"cmd2 child2"
);
} }
[Fact] [Fact]
@@ -990,7 +1039,7 @@ public class HelpTextSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--version"}, new[] { "--version" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );

View File

@@ -12,9 +12,7 @@ namespace CliFx.Tests;
public class OptionBindingSpecs : SpecsBase public class OptionBindingSpecs : SpecsBase
{ {
public OptionBindingSpecs(ITestOutputHelper testOutput) public OptionBindingSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact] [Fact]
public async Task I_can_bind_an_option_to_a_property_and_get_the_value_from_the_corresponding_argument_by_name() public async Task I_can_bind_an_option_to_a_property_and_get_the_value_from_the_corresponding_argument_by_name()
@@ -45,7 +43,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--foo"}, new[] { "--foo" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -84,10 +82,7 @@ public class OptionBindingSpecs : SpecsBase
.Build(); .Build();
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(new[] { "-f" }, new Dictionary<string, string>());
new[] {"-f"},
new Dictionary<string, string>()
);
// Assert // Assert
exitCode.Should().Be(0); exitCode.Should().Be(0);
@@ -130,11 +125,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] new[] { "--foo", "one", "--bar", "two" },
{
"--foo", "one",
"--bar", "two"
},
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -142,10 +133,7 @@ public class OptionBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = one", "Bar = two");
"Foo = one",
"Bar = two"
);
} }
[Fact] [Fact]
@@ -182,11 +170,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] new[] { "-f", "one", "-b", "two" },
{
"-f", "one",
"-b", "two"
},
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -194,10 +178,7 @@ public class OptionBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = one", "Bar = two");
"Foo = one",
"Bar = two"
);
} }
[Fact] [Fact]
@@ -234,7 +215,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-fb", "value"}, new[] { "-fb", "value" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -242,10 +223,7 @@ public class OptionBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = ", "Bar = value");
"Foo = ",
"Bar = value"
);
} }
[Fact] [Fact]
@@ -279,7 +257,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--foo", "one", "two", "three"}, new[] { "--foo", "one", "two", "three" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -287,11 +265,7 @@ public class OptionBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("one", "two", "three");
"one",
"two",
"three"
);
} }
[Fact] [Fact]
@@ -325,7 +299,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"-f", "one", "two", "three"}, new[] { "-f", "one", "two", "three" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -333,11 +307,7 @@ public class OptionBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("one", "two", "three");
"one",
"two",
"three"
);
} }
[Fact] [Fact]
@@ -371,12 +341,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] new[] { "--foo", "one", "--foo", "two", "--foo", "three" },
{
"--foo", "one",
"--foo", "two",
"--foo", "three"
},
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -384,11 +349,7 @@ public class OptionBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("one", "two", "three");
"one",
"two",
"three"
);
} }
[Fact] [Fact]
@@ -422,12 +383,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] new[] { "-f", "one", "-f", "two", "-f", "three" },
{
"-f", "one",
"-f", "two",
"-f", "three"
},
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -435,11 +391,7 @@ public class OptionBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("one", "two", "three");
"one",
"two",
"three"
);
} }
[Fact] [Fact]
@@ -473,12 +425,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] new[] { "--foo", "one", "-f", "two", "--foo", "three" },
{
"--foo", "one",
"-f", "two",
"--foo", "three"
},
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -486,11 +433,7 @@ public class OptionBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("one", "two", "three");
"one",
"two",
"three"
);
} }
[Fact] [Fact]
@@ -527,7 +470,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--foo", "one"}, new[] { "--foo", "one" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -535,10 +478,7 @@ public class OptionBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = one", "Bar = hello");
"Foo = one",
"Bar = hello"
);
} }
[Fact] [Fact]
@@ -604,24 +544,13 @@ public class OptionBindingSpecs : SpecsBase
.Build(); .Build();
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(new[] { "--foo", "42", "--bar", "--baz", "xyz" });
new[]
{
"--foo", "42",
"--bar",
"--baz", "xyz"
}
);
// Assert // Assert
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = 42", "Bar = True", "Baz = xyz");
"Foo = 42",
"Bar = True",
"Baz = xyz"
);
} }
[Fact] [Fact]
@@ -653,7 +582,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--foo", "-13"}, new[] { "--foo", "-13" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -725,7 +654,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--foo"}, new[] { "--foo" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -761,7 +690,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--foo"}, new[] { "--foo" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -797,11 +726,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] new[] { "--foo", "one", "--bar", "two" },
{
"--foo", "one",
"--bar", "two"
},
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -837,7 +762,7 @@ public class OptionBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"--foo", "one", "two", "three"}, new[] { "--foo", "one", "two", "three" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );

View File

@@ -11,9 +11,7 @@ namespace CliFx.Tests;
public class ParameterBindingSpecs : SpecsBase public class ParameterBindingSpecs : SpecsBase
{ {
public ParameterBindingSpecs(ITestOutputHelper testOutput) public ParameterBindingSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact] [Fact]
public async Task I_can_bind_a_parameter_to_a_property_and_get_the_value_from_the_corresponding_argument() public async Task I_can_bind_a_parameter_to_a_property_and_get_the_value_from_the_corresponding_argument()
@@ -49,7 +47,7 @@ public class ParameterBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"one", "two"}, new[] { "one", "two" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -57,10 +55,7 @@ public class ParameterBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = one", "Bar = two");
"Foo = one",
"Bar = two"
);
} }
[Fact] [Fact]
@@ -106,7 +101,7 @@ public class ParameterBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"one", "two", "three", "four", "five", "--boo", "xxx"}, new[] { "one", "two", "three", "four", "five", "--boo", "xxx" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -114,13 +109,9 @@ public class ParameterBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut
"Foo = one", .Should()
"Bar = two", .ConsistOfLines("Foo = one", "Bar = two", "Baz = three", "Baz = four", "Baz = five");
"Baz = three",
"Baz = four",
"Baz = five"
);
} }
[Fact] [Fact]
@@ -151,7 +142,7 @@ public class ParameterBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"one"}, new[] { "one" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -190,7 +181,7 @@ public class ParameterBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"one"}, new[] { "one" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -235,7 +226,7 @@ public class ParameterBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"abc"}, new[] { "abc" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -243,10 +234,7 @@ public class ParameterBindingSpecs : SpecsBase
exitCode.Should().Be(0); exitCode.Should().Be(0);
var stdOut = FakeConsole.ReadOutputString(); var stdOut = FakeConsole.ReadOutputString();
stdOut.Should().ConsistOfLines( stdOut.Should().ConsistOfLines("Foo = abc", "Bar = xyz");
"Foo = abc",
"Bar = xyz"
);
} }
[Fact] [Fact]
@@ -277,7 +265,7 @@ public class ParameterBindingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"one", "two", "three"}, new[] { "one", "two", "three" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );

View File

@@ -11,9 +11,7 @@ namespace CliFx.Tests;
public class RoutingSpecs : SpecsBase public class RoutingSpecs : SpecsBase
{ {
public RoutingSpecs(ITestOutputHelper testOutput) public RoutingSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact] [Fact]
public async Task I_can_configure_a_command_to_be_executed_by_default_when_the_user_does_not_specify_a_command_name() public async Task I_can_configure_a_command_to_be_executed_by_default_when_the_user_does_not_specify_a_command_name()
@@ -118,7 +116,7 @@ public class RoutingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"cmd"}, new[] { "cmd" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );
@@ -175,7 +173,7 @@ public class RoutingSpecs : SpecsBase
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] {"cmd", "child"}, new[] { "cmd", "child" },
new Dictionary<string, string>() new Dictionary<string, string>()
); );

View File

@@ -11,8 +11,7 @@ public abstract class SpecsBase : IDisposable
public FakeInMemoryConsole FakeConsole { get; } = new(); public FakeInMemoryConsole FakeConsole { get; } = new();
protected SpecsBase(ITestOutputHelper testOutput) => protected SpecsBase(ITestOutputHelper testOutput) => TestOutput = testOutput;
TestOutput = testOutput;
public void Dispose() public void Dispose()
{ {

View File

@@ -13,9 +13,7 @@ namespace CliFx.Tests;
public class TypeActivationSpecs : SpecsBase public class TypeActivationSpecs : SpecsBase
{ {
public TypeActivationSpecs(ITestOutputHelper testOutput) public TypeActivationSpecs(ITestOutputHelper testOutput)
: base(testOutput) : base(testOutput) { }
{
}
[Fact] [Fact]
public async Task I_can_configure_the_application_to_use_the_default_type_activator_to_initialize_types_through_parameterless_constructors() public async Task I_can_configure_the_application_to_use_the_default_type_activator_to_initialize_types_through_parameterless_constructors()

View File

@@ -31,8 +31,7 @@ internal static class DynamicCommandBuilder
}; };
// Get default CliFx namespaces // Get default CliFx namespaces
var defaultCliFxNamespaces = typeof(ICommand) var defaultCliFxNamespaces = typeof(ICommand).Assembly
.Assembly
.GetTypes() .GetTypes()
.Where(t => t.IsPublic) .Where(t => t.IsPublic)
.Select(t => t.Namespace) .Select(t => t.Namespace)
@@ -41,10 +40,10 @@ internal static class DynamicCommandBuilder
// Append default imports to the source code // Append default imports to the source code
var sourceCodeWithUsings = var sourceCodeWithUsings =
string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};")) + string.Join(Environment.NewLine, defaultSystemNamespaces.Select(n => $"using {n};"))
string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};")) + + string.Join(Environment.NewLine, defaultCliFxNamespaces.Select(n => $"using {n};"))
Environment.NewLine + + Environment.NewLine
sourceCode; + sourceCode;
// Parse the source code // Parse the source code
var ast = SyntaxFactory.ParseSyntaxTree( var ast = SyntaxFactory.ParseSyntaxTree(
@@ -55,10 +54,14 @@ internal static class DynamicCommandBuilder
// Compile the code to IL // Compile the code to IL
var compilation = CSharpCompilation.Create( var compilation = CSharpCompilation.Create(
"CliFxTests_DynamicAssembly_" + Guid.NewGuid(), "CliFxTests_DynamicAssembly_" + Guid.NewGuid(),
new[] {ast}, new[] { ast },
Net70.References.All Net70.References.All
.Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)) .Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location))
.Append(MetadataReference.CreateFromFile(typeof(DynamicCommandBuilder).Assembly.Location)), .Append(
MetadataReference.CreateFromFile(
typeof(DynamicCommandBuilder).Assembly.Location
)
),
// DLL to avoid having to define the Main() method // DLL to avoid having to define the Main() method
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
); );
@@ -82,8 +85,7 @@ internal static class DynamicCommandBuilder
using var buffer = new MemoryStream(); using var buffer = new MemoryStream();
var emit = compilation.Emit(buffer); var emit = compilation.Emit(buffer);
var emitErrors = emit var emitErrors = emit.Diagnostics
.Diagnostics
.Where(d => d.Severity >= DiagnosticSeverity.Error) .Where(d => d.Severity >= DiagnosticSeverity.Error)
.ToArray(); .ToArray();

View File

@@ -8,22 +8,22 @@ namespace CliFx.Tests.Utils.Extensions;
internal static class AssertionExtensions internal static class AssertionExtensions
{ {
public static void ConsistOfLines( public static void ConsistOfLines(this StringAssertions assertions, IEnumerable<string> lines)
this StringAssertions assertions,
IEnumerable<string> lines)
{ {
var actualLines = assertions.Subject.Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries); var actualLines = assertions.Subject.Split(
new[] { '\n', '\r' },
StringSplitOptions.RemoveEmptyEntries
);
actualLines.Should().Equal(lines); actualLines.Should().Equal(lines);
} }
public static void ConsistOfLines( public static void ConsistOfLines(this StringAssertions assertions, params string[] lines) =>
this StringAssertions assertions, assertions.ConsistOfLines((IEnumerable<string>)lines);
params string[] lines) =>
assertions.ConsistOfLines((IEnumerable<string>) lines);
public static AndConstraint<StringAssertions> ContainAllInOrder( public static AndConstraint<StringAssertions> ContainAllInOrder(
this StringAssertions assertions, this StringAssertions assertions,
IEnumerable<string> values) IEnumerable<string> values
)
{ {
var lastIndex = 0; var lastIndex = 0;
@@ -46,6 +46,6 @@ internal static class AssertionExtensions
public static AndConstraint<StringAssertions> ContainAllInOrder( public static AndConstraint<StringAssertions> ContainAllInOrder(
this StringAssertions assertions, this StringAssertions assertions,
params string[] values) => params string[] values
assertions.ContainAllInOrder((IEnumerable<string>) values); ) => assertions.ContainAllInOrder((IEnumerable<string>)values);
} }

View File

@@ -5,7 +5,10 @@ namespace CliFx.Tests.Utils.Extensions;
internal static class ConsoleExtensions internal static class ConsoleExtensions
{ {
public static void DumpToTestOutput(this FakeInMemoryConsole console, ITestOutputHelper testOutputHelper) public static void DumpToTestOutput(
this FakeInMemoryConsole console,
ITestOutputHelper testOutputHelper
)
{ {
testOutputHelper.WriteLine("[*] Captured standard output:"); testOutputHelper.WriteLine("[*] Captured standard output:");
testOutputHelper.WriteLine(console.ReadOutputString()); testOutputHelper.WriteLine(console.ReadOutputString());

View File

@@ -24,6 +24,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" />
<PackageReference Include="PolyShim" Version="1.7.0" PrivateAssets="all" /> <PackageReference Include="PolyShim" Version="1.7.0" PrivateAssets="all" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" Condition="'$(TargetFramework)' == 'netstandard2.0'" /> <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" Condition="'$(TargetFramework)' == 'netstandard2.0'" />