From 4ff1e1d3e1f431d48366dbdbb2f8f25f00b7f3bd Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Mon, 10 Jan 2022 23:41:28 +0200 Subject: [PATCH] Cleanup --- ...ionMustHaveNameOrShortNameAnalyzerSpecs.cs | 6 +-- .../OptionMustHaveUniqueNameAnalyzerSpecs.cs | 6 +-- ...ionMustHaveUniqueShortNameAnalyzerSpecs.cs | 8 ++-- ...tionMustHaveValidConverterAnalyzerSpecs.cs | 6 +-- .../OptionMustHaveValidNameAnalyzerSpecs.cs | 8 ++-- ...tionMustHaveValidShortNameAnalyzerSpecs.cs | 6 +-- ...ionMustHaveValidValidatorsAnalyzerSpecs.cs | 6 +-- ...meterMustBeLastIfNonScalarAnalyzerSpecs.cs | 10 ++-- ...terMustBeLastIfNotRequiredAnalyzerSpecs.cs | 32 ++++++++++--- ...terMustBeSingleIfNonScalarAnalyzerSpecs.cs | 6 +-- ...arameterMustHaveUniqueNameAnalyzerSpecs.cs | 4 +- ...rameterMustHaveUniqueOrderAnalyzerSpecs.cs | 4 +- ...eterMustHaveValidConverterAnalyzerSpecs.cs | 6 +-- ...terMustHaveValidValidatorsAnalyzerSpecs.cs | 6 +-- .../ObjectModel/CommandOptionSymbol.cs | 7 ++- .../ObjectModel/CommandParameterSymbol.cs | 8 ++-- .../ParameterMustBeLastIfNonScalarAnalyzer.cs | 4 +- ...arameterMustBeLastIfNotRequiredAnalyzer.cs | 17 +++---- CliFx.Tests/ConversionSpecs.cs | 48 +++++++++---------- CliFx.Tests/EnvironmentSpecs.cs | 12 ++--- CliFx.Tests/HelpTextSpecs.cs | 30 ++++++------ CliFx.Tests/OptionBindingSpecs.cs | 44 ++++++++--------- CliFx.Tests/ParameterBindingSpecs.cs | 22 ++++----- CliFx/Attributes/CommandOptionAttribute.cs | 2 +- CliFx/Attributes/CommandParameterAttribute.cs | 15 +++--- CliFx/Infrastructure/IConsole.cs | 2 +- CliFx/Schema/BindablePropertyDescriptor.cs | 2 +- CliFx/Schema/ParameterSchema.cs | 15 +++--- CliFx/Utils/Polyfills.cs | 1 - Readme.md | 6 +-- 30 files changed, 184 insertions(+), 165 deletions(-) diff --git a/CliFx.Analyzers.Tests/OptionMustHaveNameOrShortNameAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/OptionMustHaveNameOrShortNameAnalyzerSpecs.cs index d96ca66..dce4010 100644 --- a/CliFx.Analyzers.Tests/OptionMustHaveNameOrShortNameAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/OptionMustHaveNameOrShortNameAnalyzerSpecs.cs @@ -19,7 +19,7 @@ public class MyCommand : ICommand { [CommandOption(null)] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -38,7 +38,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -57,7 +57,7 @@ public class MyCommand : ICommand { [CommandOption('f')] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; diff --git a/CliFx.Analyzers.Tests/OptionMustHaveUniqueNameAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/OptionMustHaveUniqueNameAnalyzerSpecs.cs index e238b62..18a4837 100644 --- a/CliFx.Analyzers.Tests/OptionMustHaveUniqueNameAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/OptionMustHaveUniqueNameAnalyzerSpecs.cs @@ -19,7 +19,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + [CommandOption(""foo"")] public string Bar { get; set; } @@ -41,7 +41,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + [CommandOption(""bar"")] public string Bar { get; set; } @@ -63,7 +63,7 @@ public class MyCommand : ICommand { [CommandOption('f')] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; diff --git a/CliFx.Analyzers.Tests/OptionMustHaveUniqueShortNameAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/OptionMustHaveUniqueShortNameAnalyzerSpecs.cs index d80ddd1..80a2177 100644 --- a/CliFx.Analyzers.Tests/OptionMustHaveUniqueShortNameAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/OptionMustHaveUniqueShortNameAnalyzerSpecs.cs @@ -19,7 +19,7 @@ public class MyCommand : ICommand { [CommandOption('f')] public string Foo { get; set; } - + [CommandOption('f')] public string Bar { get; set; } @@ -41,7 +41,7 @@ public class MyCommand : ICommand { [CommandOption('f')] public string Foo { get; set; } - + [CommandOption('b')] public string Bar { get; set; } @@ -63,7 +63,7 @@ public class MyCommand : ICommand { [CommandOption('f')] public string Foo { get; set; } - + [CommandOption('F')] public string Bar { get; set; } @@ -85,7 +85,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; diff --git a/CliFx.Analyzers.Tests/OptionMustHaveValidConverterAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/OptionMustHaveValidConverterAnalyzerSpecs.cs index 2c02039..649bf2f 100644 --- a/CliFx.Analyzers.Tests/OptionMustHaveValidConverterAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/OptionMustHaveValidConverterAnalyzerSpecs.cs @@ -24,7 +24,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"", Converter = typeof(MyConverter))] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -48,7 +48,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"", Converter = typeof(MyConverter))] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -67,7 +67,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; diff --git a/CliFx.Analyzers.Tests/OptionMustHaveValidNameAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/OptionMustHaveValidNameAnalyzerSpecs.cs index b4352ed..665ede0 100644 --- a/CliFx.Analyzers.Tests/OptionMustHaveValidNameAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/OptionMustHaveValidNameAnalyzerSpecs.cs @@ -19,7 +19,7 @@ public class MyCommand : ICommand { [CommandOption(""f"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -38,7 +38,7 @@ public class MyCommand : ICommand { [CommandOption(""1foo"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -57,7 +57,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -76,7 +76,7 @@ public class MyCommand : ICommand { [CommandOption('f')] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; diff --git a/CliFx.Analyzers.Tests/OptionMustHaveValidShortNameAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/OptionMustHaveValidShortNameAnalyzerSpecs.cs index b077098..1e3eea8 100644 --- a/CliFx.Analyzers.Tests/OptionMustHaveValidShortNameAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/OptionMustHaveValidShortNameAnalyzerSpecs.cs @@ -19,7 +19,7 @@ public class MyCommand : ICommand { [CommandOption('1')] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -38,7 +38,7 @@ public class MyCommand : ICommand { [CommandOption('f')] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -57,7 +57,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; diff --git a/CliFx.Analyzers.Tests/OptionMustHaveValidValidatorsAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/OptionMustHaveValidValidatorsAnalyzerSpecs.cs index c97ccaf..dbbdbc1 100644 --- a/CliFx.Analyzers.Tests/OptionMustHaveValidValidatorsAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/OptionMustHaveValidValidatorsAnalyzerSpecs.cs @@ -24,7 +24,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"", Validators = new[] {typeof(MyValidator)})] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -48,7 +48,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"", Validators = new[] {typeof(MyValidator)})] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -67,7 +67,7 @@ public class MyCommand : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; diff --git a/CliFx.Analyzers.Tests/ParameterMustBeLastIfNonScalarAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/ParameterMustBeLastIfNonScalarAnalyzerSpecs.cs index f2cac83..13fa257 100644 --- a/CliFx.Analyzers.Tests/ParameterMustBeLastIfNonScalarAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/ParameterMustBeLastIfNonScalarAnalyzerSpecs.cs @@ -9,7 +9,7 @@ public class ParameterMustBeLastIfNonScalarAnalyzerSpecs private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNonScalarAnalyzer(); [Fact] - public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_last_in_order() + public void Analyzer_reports_an_error_if_a_non_scalar_parameter_is_not_the_last_in_order() { // Arrange // language=cs @@ -19,7 +19,7 @@ public class MyCommand : ICommand { [CommandParameter(0)] public string[] Foo { get; set; } - + [CommandParameter(1)] public string Bar { get; set; } @@ -31,7 +31,7 @@ public class MyCommand : ICommand } [Fact] - public void Analyzer_does_not_report_an_error_if_a_non_scalar_parameter_is_last_in_order() + public void Analyzer_does_not_report_an_error_if_a_non_scalar_parameter_is_the_last_in_order() { // Arrange // language=cs @@ -41,7 +41,7 @@ public class MyCommand : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public string[] Bar { get; set; } @@ -63,7 +63,7 @@ public class MyCommand : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public string Bar { get; set; } diff --git a/CliFx.Analyzers.Tests/ParameterMustBeLastIfNotRequiredAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/ParameterMustBeLastIfNotRequiredAnalyzerSpecs.cs index 935eefc..84a9ffe 100644 --- a/CliFx.Analyzers.Tests/ParameterMustBeLastIfNotRequiredAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/ParameterMustBeLastIfNotRequiredAnalyzerSpecs.cs @@ -9,7 +9,7 @@ public class ParameterMustBeLastIfNotRequiredAnalyzerSpecs private static DiagnosticAnalyzer Analyzer { get; } = new ParameterMustBeLastIfNotRequiredAnalyzer(); [Fact] - public void Analyzer_reports_an_error_if_a_parameter_is_not_required_and_is_not_last_parameter() + public void Analyzer_reports_an_error_if_a_non_required_parameter_is_not_the_last_in_order() { // Arrange // language=cs @@ -19,7 +19,7 @@ public class MyCommand : ICommand { [CommandParameter(0, Name = ""foo"", IsRequired = false)] public string Foo { get; set; } - + [CommandParameter(1, Name = ""bar"", IsRequired = false)] public string Bar { get; set; } @@ -31,7 +31,7 @@ public class MyCommand : ICommand } [Fact] - public void Analyzer_does_not_report_an_error_if_isRequired_is_false_on_last_parameter() + public void Analyzer_does_not_report_an_error_if_a_non_required_parameter_is_the_last_in_order() { // Arrange // language=cs @@ -41,7 +41,7 @@ public class MyCommand : ICommand { [CommandParameter(0, Name = ""foo"", IsRequired = true)] public string Foo { get; set; } - + [CommandParameter(1, Name = ""bar"", IsRequired = false)] public string Bar { get; set; } @@ -53,7 +53,7 @@ public class MyCommand : ICommand } [Fact] - public void Analyzer_does_not_report_an_error_on_a_property_when_isRequired_is_not_set() + public void Analyzer_does_not_report_an_error_if_no_non_required_parameters_are_defined() { // Arrange // language=cs @@ -63,8 +63,8 @@ public class MyCommand : ICommand { [CommandParameter(0, Name = ""foo"")] public string Foo { get; set; } - - [CommandParameter(1, Name = ""bar"")] + + [CommandParameter(1, Name = ""bar"", IsRequired = true)] public string Bar { get; set; } public ValueTask ExecuteAsync(IConsole console) => default; @@ -73,4 +73,22 @@ public class MyCommand : ICommand // Act & assert Analyzer.Should().NotProduceDiagnostics(code); } + + [Fact] + public void Analyzer_does_not_report_an_error_on_a_property_that_is_not_a_parameter() + { + // Arrange + // language=cs + const string code = @" +[Command] +public class MyCommand : ICommand +{ + public string Foo { get; set; } + + public ValueTask ExecuteAsync(IConsole console) => default; +}"; + + // Act & assert + Analyzer.Should().NotProduceDiagnostics(code); + } } \ No newline at end of file diff --git a/CliFx.Analyzers.Tests/ParameterMustBeSingleIfNonScalarAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/ParameterMustBeSingleIfNonScalarAnalyzerSpecs.cs index a7901b7..0718cc0 100644 --- a/CliFx.Analyzers.Tests/ParameterMustBeSingleIfNonScalarAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/ParameterMustBeSingleIfNonScalarAnalyzerSpecs.cs @@ -19,7 +19,7 @@ public class MyCommand : ICommand { [CommandParameter(0)] public string[] Foo { get; set; } - + [CommandParameter(1)] public string[] Bar { get; set; } @@ -41,7 +41,7 @@ public class MyCommand : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public string[] Bar { get; set; } @@ -63,7 +63,7 @@ public class MyCommand : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public string Bar { get; set; } diff --git a/CliFx.Analyzers.Tests/ParameterMustHaveUniqueNameAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/ParameterMustHaveUniqueNameAnalyzerSpecs.cs index 6fffa25..2529b98 100644 --- a/CliFx.Analyzers.Tests/ParameterMustHaveUniqueNameAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/ParameterMustHaveUniqueNameAnalyzerSpecs.cs @@ -19,7 +19,7 @@ public class MyCommand : ICommand { [CommandParameter(0, Name = ""foo"")] public string Foo { get; set; } - + [CommandParameter(1, Name = ""foo"")] public string Bar { get; set; } @@ -41,7 +41,7 @@ public class MyCommand : ICommand { [CommandParameter(0, Name = ""foo"")] public string Foo { get; set; } - + [CommandParameter(1, Name = ""bar"")] public string Bar { get; set; } diff --git a/CliFx.Analyzers.Tests/ParameterMustHaveUniqueOrderAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/ParameterMustHaveUniqueOrderAnalyzerSpecs.cs index 4aab278..c09257d 100644 --- a/CliFx.Analyzers.Tests/ParameterMustHaveUniqueOrderAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/ParameterMustHaveUniqueOrderAnalyzerSpecs.cs @@ -19,7 +19,7 @@ public class MyCommand : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(0)] public string Bar { get; set; } @@ -41,7 +41,7 @@ public class MyCommand : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public string Bar { get; set; } diff --git a/CliFx.Analyzers.Tests/ParameterMustHaveValidConverterAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/ParameterMustHaveValidConverterAnalyzerSpecs.cs index e0b2c3d..01e3c15 100644 --- a/CliFx.Analyzers.Tests/ParameterMustHaveValidConverterAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/ParameterMustHaveValidConverterAnalyzerSpecs.cs @@ -24,7 +24,7 @@ public class MyCommand : ICommand { [CommandParameter(0, Converter = typeof(MyConverter))] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -48,7 +48,7 @@ public class MyCommand : ICommand { [CommandParameter(0, Converter = typeof(MyConverter))] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -67,7 +67,7 @@ public class MyCommand : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; diff --git a/CliFx.Analyzers.Tests/ParameterMustHaveValidValidatorsAnalyzerSpecs.cs b/CliFx.Analyzers.Tests/ParameterMustHaveValidValidatorsAnalyzerSpecs.cs index 349673b..f559afd 100644 --- a/CliFx.Analyzers.Tests/ParameterMustHaveValidValidatorsAnalyzerSpecs.cs +++ b/CliFx.Analyzers.Tests/ParameterMustHaveValidValidatorsAnalyzerSpecs.cs @@ -24,7 +24,7 @@ public class MyCommand : ICommand { [CommandParameter(0, Validators = new[] {typeof(MyValidator)})] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -48,7 +48,7 @@ public class MyCommand : ICommand { [CommandParameter(0, Validators = new[] {typeof(MyValidator)})] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; @@ -67,7 +67,7 @@ public class MyCommand : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"; diff --git a/CliFx.Analyzers/ObjectModel/CommandOptionSymbol.cs b/CliFx.Analyzers/ObjectModel/CommandOptionSymbol.cs index d8ac629..2d53e1a 100644 --- a/CliFx.Analyzers/ObjectModel/CommandOptionSymbol.cs +++ b/CliFx.Analyzers/ObjectModel/CommandOptionSymbol.cs @@ -71,10 +71,9 @@ internal partial class CommandOptionSymbol { var attribute = TryGetOptionAttribute(property); - if (attribute is null) - return null; - - return FromAttribute(attribute); + return attribute is not null + ? FromAttribute(attribute) + : null; } public static bool IsOptionProperty(IPropertySymbol property) => diff --git a/CliFx.Analyzers/ObjectModel/CommandParameterSymbol.cs b/CliFx.Analyzers/ObjectModel/CommandParameterSymbol.cs index 04712bb..5a29829 100644 --- a/CliFx.Analyzers/ObjectModel/CommandParameterSymbol.cs +++ b/CliFx.Analyzers/ObjectModel/CommandParameterSymbol.cs @@ -10,9 +10,9 @@ internal partial class CommandParameterSymbol public int Order { get; } public string? Name { get; } - + public bool? IsRequired { get; } - + public ITypeSymbol? ConverterType { get; } public IReadOnlyList ValidatorTypes { get; } @@ -41,7 +41,7 @@ internal partial class CommandParameterSymbol private static CommandParameterSymbol FromAttribute(AttributeData attribute) { - var order = (int) attribute + var order = (int)attribute .ConstructorArguments .Select(a => a.Value) .First()!; @@ -51,7 +51,7 @@ internal partial class CommandParameterSymbol .Where(a => a.Key == "Name") .Select(a => a.Value.Value) .FirstOrDefault() as string; - + var isRequired = attribute .NamedArguments .Where(a => a.Key == "IsRequired") diff --git a/CliFx.Analyzers/ParameterMustBeLastIfNonScalarAnalyzer.cs b/CliFx.Analyzers/ParameterMustBeLastIfNonScalarAnalyzer.cs index 0039a0a..ccfbb09 100644 --- a/CliFx.Analyzers/ParameterMustBeLastIfNonScalarAnalyzer.cs +++ b/CliFx.Analyzers/ParameterMustBeLastIfNonScalarAnalyzer.cs @@ -12,8 +12,8 @@ public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase { public ParameterMustBeLastIfNonScalarAnalyzer() : base( - "Parameters of non-scalar types must be last in order", - "This parameter has a non-scalar type so it must be last in order (its order must be highest within the command).") + "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).") { } diff --git a/CliFx.Analyzers/ParameterMustBeLastIfNotRequiredAnalyzer.cs b/CliFx.Analyzers/ParameterMustBeLastIfNotRequiredAnalyzer.cs index 19a9153..9545048 100644 --- a/CliFx.Analyzers/ParameterMustBeLastIfNotRequiredAnalyzer.cs +++ b/CliFx.Analyzers/ParameterMustBeLastIfNotRequiredAnalyzer.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using CliFx.Analyzers.ObjectModel; using CliFx.Analyzers.Utils.Extensions; using Microsoft.CodeAnalysis; @@ -13,8 +12,8 @@ public class ParameterMustBeLastIfNotRequiredAnalyzer : AnalyzerBase { public ParameterMustBeLastIfNotRequiredAnalyzer() : base( - "Only last parameter can be optional", - "IsRequired can only be set to false on the last parameter.") + "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).") { } @@ -27,10 +26,12 @@ public class ParameterMustBeLastIfNotRequiredAnalyzer : AnalyzerBase return; var parameter = CommandParameterSymbol.TryResolve(property); - - if (parameter is null || parameter.IsRequired != false) + if (parameter is null) return; - + + if (parameter.IsRequired != false) + return; + var otherProperties = property .ContainingType .GetMembers() @@ -44,7 +45,7 @@ public class ParameterMustBeLastIfNotRequiredAnalyzer : AnalyzerBase if (otherParameter is null) continue; - if (parameter.IsRequired is false && parameter.Order < otherParameter.Order) + if (otherParameter.Order > parameter.Order) { context.ReportDiagnostic(CreateDiagnostic(propertyDeclaration.GetLocation())); } diff --git a/CliFx.Tests/ConversionSpecs.cs b/CliFx.Tests/ConversionSpecs.cs index 8761223..4cf7358 100644 --- a/CliFx.Tests/ConversionSpecs.cs +++ b/CliFx.Tests/ConversionSpecs.cs @@ -103,7 +103,7 @@ public class Command : ICommand { [CommandOption('f')] public bool Foo { get; set; } - + [CommandOption('b')] public bool Bar { get; set; } @@ -379,7 +379,7 @@ public class Command : ICommand { [CommandOption('f')] public int? Foo { get; set; } - + [CommandOption('b')] public int? Bar { get; set; } @@ -427,7 +427,7 @@ public class Command : ICommand { [CommandOption('f')] public CustomEnum? Foo { get; set; } - + [CommandOption('b')] public CustomEnum? Bar { get; set; } @@ -471,7 +471,7 @@ public class Command : ICommand public class CustomType { public string Value { get; } - + public CustomType(string value) => Value = value; } @@ -480,7 +480,7 @@ public class Command : ICommand { [CommandOption('f')] public CustomType Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(Foo.Value); @@ -516,9 +516,9 @@ public class Command : ICommand public class CustomTypeA { public string Value { get; } - + private CustomTypeA(string value) => Value = value; - + public static CustomTypeA Parse(string value) => new CustomTypeA(value); } @@ -526,9 +526,9 @@ public class CustomTypeA public class CustomTypeB { public string Value { get; } - + private CustomTypeB(string value) => Value = value; - + public static CustomTypeB Parse(string value, IFormatProvider formatProvider) => new CustomTypeB(value); } @@ -538,10 +538,10 @@ public class Command : ICommand { [CommandOption('f')] public CustomTypeA Foo { get; set; } - + [CommandOption('b')] public CustomTypeB Bar { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(""Foo = "" + Foo.Value); @@ -590,7 +590,7 @@ public class Command : ICommand { [CommandOption('f', Converter = typeof(CustomConverter))] public int Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(Foo); @@ -628,7 +628,7 @@ public class Command : ICommand { [CommandOption('f')] public string[] Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { foreach (var i in Foo) @@ -672,7 +672,7 @@ public class Command : ICommand { [CommandOption('f')] public IReadOnlyList Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { foreach (var i in Foo) @@ -716,7 +716,7 @@ public class Command : ICommand { [CommandOption('f')] public List Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { foreach (var i in Foo) @@ -760,7 +760,7 @@ public class Command : ICommand { [CommandOption('f')] public int[] Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { foreach (var i in Foo) @@ -804,7 +804,7 @@ public class Command : ICommand { [CommandOption('f')] public int Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; } "); @@ -840,7 +840,7 @@ public class Command : ICommand { [CommandOption('f')] public CustomType Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; } "); @@ -872,7 +872,7 @@ public class Command : ICommand public class CustomType : IEnumerable { public IEnumerator GetEnumerator() => Enumerable.Empty().GetEnumerator(); - + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } @@ -881,7 +881,7 @@ public class Command : ICommand { [CommandOption('f')] public CustomType Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; } "); @@ -925,7 +925,7 @@ public class Command : ICommand { [CommandOption('f', Validators = new[] {typeof(ValidatorA), typeof(ValidatorB)})] public int Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; } "); @@ -957,9 +957,9 @@ public class Command : ICommand public class CustomType { public string Value { get; } - + private CustomType(string value) => Value = value; - + public static CustomType Parse(string value) => throw new Exception(""Hello world""); } @@ -968,7 +968,7 @@ public class Command : ICommand { [CommandOption('f')] public CustomType Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; } "); diff --git a/CliFx.Tests/EnvironmentSpecs.cs b/CliFx.Tests/EnvironmentSpecs.cs index 1af03c8..9fd1ce7 100644 --- a/CliFx.Tests/EnvironmentSpecs.cs +++ b/CliFx.Tests/EnvironmentSpecs.cs @@ -31,7 +31,7 @@ public class Command : ICommand { [CommandOption(""foo"", IsRequired = true, EnvironmentVariable = ""ENV_FOO"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(Foo); @@ -73,7 +73,7 @@ public class Command : ICommand { [CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(Foo); @@ -115,12 +115,12 @@ public class Command : ICommand { [CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")] public IReadOnlyList Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { foreach (var i in Foo) console.Output.WriteLine(i); - + return default; } } @@ -162,7 +162,7 @@ public class Command : ICommand { [CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(Foo); @@ -204,7 +204,7 @@ public class Command : ICommand { [CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(Foo); diff --git a/CliFx.Tests/HelpTextSpecs.cs b/CliFx.Tests/HelpTextSpecs.cs index 9359ec8..96541b7 100644 --- a/CliFx.Tests/HelpTextSpecs.cs +++ b/CliFx.Tests/HelpTextSpecs.cs @@ -339,13 +339,13 @@ public class Command : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public string Bar { get; set; } - + [CommandParameter(2)] public IReadOnlyList Baz { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; } "); @@ -396,7 +396,7 @@ public class Command : CommandBase [CommandParameter(1)] public string Bar { get; set; } - + public override ValueTask ExecuteAsync(IConsole console) => default; } "); @@ -434,13 +434,13 @@ public class Command : ICommand { [CommandOption(""foo"", IsRequired = true)] public string Foo { get; set; } - + [CommandOption(""bar"")] public string Bar { get; set; } - + [CommandOption(""baz"", IsRequired = true)] public IReadOnlyList Baz { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; } "); @@ -478,10 +478,10 @@ public class Command : ICommand { [CommandParameter(0, Name = ""foo"", Description = ""Description of foo."")] public string Foo { get; set; } - + [CommandOption(""bar"", Description = ""Description of bar."")] public string Bar { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; } "); @@ -597,10 +597,10 @@ public class Command : ICommand { [CommandParameter(0)] public CustomEnum Foo { get; set; } - + [CommandOption(""bar"")] public CustomEnum Bar { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; } "); @@ -732,10 +732,10 @@ public class Command : ICommand { [CommandOption(""foo"", EnvironmentVariable = ""ENV_FOO"")] public CustomEnum Foo { get; set; } - + [CommandOption(""bar"", EnvironmentVariable = ""ENV_BAR"")] public CustomEnum Bar { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; } "); @@ -794,10 +794,10 @@ public class Command : ICommand [CommandOption(""lol"")] public CustomEnum Lol { get; set; } = CustomEnum.Two; - + [CommandOption(""hmm"", IsRequired = true)] public string Hmm { get; set; } = ""not printed""; - + public ValueTask ExecuteAsync(IConsole console) => default; } "); diff --git a/CliFx.Tests/OptionBindingSpecs.cs b/CliFx.Tests/OptionBindingSpecs.cs index 3fbc891..d6a0a29 100644 --- a/CliFx.Tests/OptionBindingSpecs.cs +++ b/CliFx.Tests/OptionBindingSpecs.cs @@ -28,7 +28,7 @@ public class Command : ICommand { [CommandOption(""foo"")] public bool Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(Foo); @@ -66,7 +66,7 @@ public class Command : ICommand { [CommandOption('f')] public bool Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(Foo); @@ -104,10 +104,10 @@ public class Command : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + [CommandOption(""bar"")] public string Bar { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(""Foo = "" + Foo); @@ -150,10 +150,10 @@ public class Command : ICommand { [CommandOption('f')] public string Foo { get; set; } - + [CommandOption('b')] public string Bar { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(""Foo = "" + Foo); @@ -196,10 +196,10 @@ public class Command : ICommand { [CommandOption('f')] public string Foo { get; set; } - + [CommandOption('b')] public string Bar { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(""Foo = "" + Foo); @@ -242,7 +242,7 @@ public class Command : ICommand { [CommandOption(""Foo"")] public IReadOnlyList Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { foreach (var i in Foo) @@ -286,7 +286,7 @@ public class Command : ICommand { [CommandOption('f')] public IReadOnlyList Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { foreach (var i in Foo) @@ -330,7 +330,7 @@ public class Command : ICommand { [CommandOption(""foo"")] public IReadOnlyList Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { foreach (var i in Foo) @@ -374,7 +374,7 @@ public class Command : ICommand { [CommandOption('f')] public IReadOnlyList Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { foreach (var i in Foo) @@ -418,7 +418,7 @@ public class Command : ICommand { [CommandOption(""foo"", 'f')] public IReadOnlyList Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { foreach (var i in Foo) @@ -462,10 +462,10 @@ public class Command : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + [CommandOption(""bar"")] public string Bar { get; set; } = ""hello""; - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(""Foo = "" + Foo); @@ -506,7 +506,7 @@ public class Command : ICommand public static class SharedContext { public static int Foo { get; set; } - + public static bool Bar { get; set; } } @@ -585,7 +585,7 @@ public class Command : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) { console.Output.WriteLine(Foo); @@ -624,7 +624,7 @@ public class Command : ICommand { [CommandOption(""foo"", IsRequired = true)] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"); @@ -658,7 +658,7 @@ public class Command : ICommand { [CommandOption(""foo"", IsRequired = true)] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"); @@ -692,7 +692,7 @@ public class Command : ICommand { [CommandOption(""foo"", IsRequired = true)] public IReadOnlyList Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"); @@ -726,7 +726,7 @@ public class Command : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"); @@ -760,7 +760,7 @@ public class Command : ICommand { [CommandOption(""foo"")] public string Foo { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"); diff --git a/CliFx.Tests/ParameterBindingSpecs.cs b/CliFx.Tests/ParameterBindingSpecs.cs index 8298f3d..62e731c 100644 --- a/CliFx.Tests/ParameterBindingSpecs.cs +++ b/CliFx.Tests/ParameterBindingSpecs.cs @@ -27,7 +27,7 @@ public class Command : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public string Bar { get; set; } @@ -35,7 +35,7 @@ public class Command : ICommand { console.Output.WriteLine(""Foo = "" + Foo); console.Output.WriteLine(""Bar = "" + Bar); - + return default; } }"); @@ -73,13 +73,13 @@ public class Command : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public string Bar { get; set; } - + [CommandParameter(2)] public IReadOnlyList Baz { get; set; } - + [CommandOption(""boo"")] public string Boo { get; set; } @@ -87,10 +87,10 @@ public class Command : ICommand { console.Output.WriteLine(""Foo = "" + Foo); console.Output.WriteLine(""Bar = "" + Bar); - + foreach (var i in Baz) console.Output.WriteLine(""Baz = "" + i); - + return default; } }"); @@ -131,7 +131,7 @@ public class Command : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public string Bar { get; set; } @@ -168,10 +168,10 @@ public class Command : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public IReadOnlyList Bar { get; set; } - + public ValueTask ExecuteAsync(IConsole console) => default; }"); @@ -205,7 +205,7 @@ public class Command : ICommand { [CommandParameter(0)] public string Foo { get; set; } - + [CommandParameter(1)] public string Bar { get; set; } diff --git a/CliFx/Attributes/CommandOptionAttribute.cs b/CliFx/Attributes/CommandOptionAttribute.cs index 0a137ca..729b6d8 100644 --- a/CliFx/Attributes/CommandOptionAttribute.cs +++ b/CliFx/Attributes/CommandOptionAttribute.cs @@ -29,7 +29,7 @@ public sealed class CommandOptionAttribute : Attribute public char? ShortName { get; } /// - /// Whether this option is required. + /// Whether this option is required (default: false). /// If an option is required, the user will get an error if they don't set it. /// public bool IsRequired { get; set; } diff --git a/CliFx/Attributes/CommandParameterAttribute.cs b/CliFx/Attributes/CommandParameterAttribute.cs index 11ac318..feb37aa 100644 --- a/CliFx/Attributes/CommandParameterAttribute.cs +++ b/CliFx/Attributes/CommandParameterAttribute.cs @@ -11,23 +11,24 @@ public sealed class CommandParameterAttribute : Attribute { /// /// Parameter order. - /// - /// /// Higher order means the parameter appears later, lower order means /// it appears earlier. - /// + /// + /// /// All parameters in a command must have unique order. - /// /// Parameter whose type is a non-scalar (e.g. array), must always be the last in order. /// Only one non-scalar parameter is allowed in a command. /// public int Order { get; } - + /// - /// Whether this parameter is required. (default: true) + /// Whether this parameter is required (default: true). /// If a parameter is required, the user will get an error if they don't set it. - /// Only the last parameter in order can be optional. /// + /// + /// Parameter marked as non-required must always be the last in order. + /// Only one non-required parameter is allowed in a command. + /// public bool IsRequired { get; set; } = true; /// diff --git a/CliFx/Infrastructure/IConsole.cs b/CliFx/Infrastructure/IConsole.cs index c0a0bd7..2d79e79 100644 --- a/CliFx/Infrastructure/IConsole.cs +++ b/CliFx/Infrastructure/IConsole.cs @@ -82,7 +82,7 @@ public interface IConsole /// /// CancellationToken RegisterCancellationHandler(); - + /// /// Clears the console buffer and corresponding console window of display information. /// diff --git a/CliFx/Schema/BindablePropertyDescriptor.cs b/CliFx/Schema/BindablePropertyDescriptor.cs index be861e0..7ad0c94 100644 --- a/CliFx/Schema/BindablePropertyDescriptor.cs +++ b/CliFx/Schema/BindablePropertyDescriptor.cs @@ -33,7 +33,7 @@ internal class BindablePropertyDescriptor : IPropertyDescriptor return type; } - + var underlyingType = GetUnderlyingType(Type); // We can only get valid values for enums diff --git a/CliFx/Schema/ParameterSchema.cs b/CliFx/Schema/ParameterSchema.cs index a2f4601..b4f8a94 100644 --- a/CliFx/Schema/ParameterSchema.cs +++ b/CliFx/Schema/ParameterSchema.cs @@ -13,27 +13,28 @@ internal partial class ParameterSchema : IMemberSchema public string Name { get; } - public string? Description { get; } - public bool IsRequired { get; } - + + public string? Description { get; } + public Type? ConverterType { get; } public IReadOnlyList ValidatorTypes { get; } - public ParameterSchema(IPropertyDescriptor property, + public ParameterSchema( + IPropertyDescriptor property, int order, string name, - string? description, bool isRequired, + string? description, Type? converterType, IReadOnlyList validatorTypes) { Property = property; Order = order; Name = name; - Description = description; IsRequired = isRequired; + Description = description; ConverterType = converterType; ValidatorTypes = validatorTypes; } @@ -58,8 +59,8 @@ internal partial class ParameterSchema new BindablePropertyDescriptor(property), attribute.Order, name, - description, attribute.IsRequired, + description, attribute.Converter, attribute.Validators ); diff --git a/CliFx/Utils/Polyfills.cs b/CliFx/Utils/Polyfills.cs index f094280..63b8d34 100644 --- a/CliFx/Utils/Polyfills.cs +++ b/CliFx/Utils/Polyfills.cs @@ -32,7 +32,6 @@ internal static partial class PolyfillExtensions stream.Write(buffer, 0, buffer.Length); } - namespace System.Linq { internal static class PolyfillExtensions diff --git a/Readme.md b/Readme.md index 38e69ed..470ad0a 100644 --- a/Readme.md +++ b/Readme.md @@ -226,7 +226,7 @@ Similarly, unseparated arguments in the form of `myapp -ofile` will be treated a Because of these rules, order of arguments is semantically important and must always follow this pattern: -```ini +```txt [directives] [command name] [parameters] [options] ``` @@ -596,7 +596,7 @@ public async Task ConcatCommand_executes_successfully() // Act await command.ExecuteAsync(console); - + var stdOut = console.ReadOutputString(); // Assert @@ -624,7 +624,7 @@ public async Task ConcatCommand_executes_successfully() // Act await app.RunAsync(args, envVars); - + var stdOut = console.ReadOutputString(); // Assert