Refactor with C# 12 features

This commit is contained in:
Tyrrrz
2023-12-10 22:51:57 +02:00
parent 5854f36756
commit 490398f773
68 changed files with 371 additions and 622 deletions

View File

@@ -11,7 +11,7 @@
<ItemGroup>
<PackageReference Include="Basic.Reference.Assemblies.Net70" Version="1.4.5" />
<PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" PrivateAssets="all" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />

View File

@@ -13,12 +13,10 @@ using Microsoft.CodeAnalysis.Text;
namespace CliFx.Analyzers.Tests.Utils;
internal class AnalyzerAssertions : ReferenceTypeAssertions<DiagnosticAnalyzer, AnalyzerAssertions>
internal class AnalyzerAssertions(DiagnosticAnalyzer analyzer)
: ReferenceTypeAssertions<DiagnosticAnalyzer, AnalyzerAssertions>(analyzer)
{
protected override string Identifier { get; } = "analyzer";
public AnalyzerAssertions(DiagnosticAnalyzer analyzer)
: base(analyzer) { }
protected override string Identifier => "analyzer";
private Compilation Compile(string sourceCode)
{

View File

@@ -17,7 +17,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<!-- 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.CSharp" Version="3.0.0" PrivateAssets="all" />

View File

@@ -8,14 +8,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CommandMustBeAnnotatedAnalyzer : AnalyzerBase
public class CommandMustBeAnnotatedAnalyzer()
: AnalyzerBase(
$"Commands must be annotated with `{SymbolNames.CliFxCommandAttribute}`",
$"This type must be annotated with `{SymbolNames.CliFxCommandAttribute}` in order to be a valid command."
)
{
public CommandMustBeAnnotatedAnalyzer()
: base(
$"Commands must be annotated with `{SymbolNames.CliFxCommandAttribute}`",
$"This type must be annotated with `{SymbolNames.CliFxCommandAttribute}` in order to be a valid command."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
ClassDeclarationSyntax classDeclaration,

View File

@@ -8,14 +8,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CommandMustImplementInterfaceAnalyzer : AnalyzerBase
public class CommandMustImplementInterfaceAnalyzer()
: AnalyzerBase(
$"Commands must implement `{SymbolNames.CliFxCommandInterface}` interface",
$"This type must implement `{SymbolNames.CliFxCommandInterface}` interface in order to be a valid command."
)
{
public CommandMustImplementInterfaceAnalyzer()
: base(
$"Commands must implement `{SymbolNames.CliFxCommandInterface}` interface",
$"This type must implement `{SymbolNames.CliFxCommandInterface}` interface in order to be a valid command."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
ClassDeclarationSyntax classDeclaration,

View File

@@ -5,36 +5,26 @@ using Microsoft.CodeAnalysis;
namespace CliFx.Analyzers.ObjectModel;
internal partial class CommandOptionSymbol : ICommandMemberSymbol
internal partial class CommandOptionSymbol(
IPropertySymbol property,
string? name,
char? shortName,
bool? isRequired,
ITypeSymbol? converterType,
IReadOnlyList<ITypeSymbol> validatorTypes
) : ICommandMemberSymbol
{
public IPropertySymbol Property { get; }
public IPropertySymbol Property { get; } = property;
public string? Name { get; }
public string? Name { get; } = name;
public char? ShortName { get; }
public char? ShortName { get; } = shortName;
public bool? IsRequired { get; }
public bool? IsRequired { get; } = isRequired;
public ITypeSymbol? ConverterType { get; }
public ITypeSymbol? ConverterType { get; } = converterType;
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; }
public CommandOptionSymbol(
IPropertySymbol property,
string? name,
char? shortName,
bool? isRequired,
ITypeSymbol? converterType,
IReadOnlyList<ITypeSymbol> validatorTypes
)
{
Property = property;
Name = name;
ShortName = shortName;
IsRequired = isRequired;
ConverterType = converterType;
ValidatorTypes = validatorTypes;
}
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; } = validatorTypes;
}
internal partial class CommandOptionSymbol

View File

@@ -5,36 +5,26 @@ using Microsoft.CodeAnalysis;
namespace CliFx.Analyzers.ObjectModel;
internal partial class CommandParameterSymbol : ICommandMemberSymbol
internal partial class CommandParameterSymbol(
IPropertySymbol property,
int order,
string? name,
bool? isRequired,
ITypeSymbol? converterType,
IReadOnlyList<ITypeSymbol> validatorTypes
) : ICommandMemberSymbol
{
public IPropertySymbol Property { get; }
public IPropertySymbol Property { get; } = property;
public int Order { get; }
public int Order { get; } = order;
public string? Name { get; }
public string? Name { get; } = name;
public bool? IsRequired { get; }
public bool? IsRequired { get; } = isRequired;
public ITypeSymbol? ConverterType { get; }
public ITypeSymbol? ConverterType { get; } = converterType;
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; }
public CommandParameterSymbol(
IPropertySymbol property,
int order,
string? name,
bool? isRequired,
ITypeSymbol? converterType,
IReadOnlyList<ITypeSymbol> validatorTypes
)
{
Property = property;
Order = order;
Name = name;
IsRequired = isRequired;
ConverterType = converterType;
ValidatorTypes = validatorTypes;
}
public IReadOnlyList<ITypeSymbol> ValidatorTypes { get; } = validatorTypes;
}
internal partial class CommandParameterSymbol

View File

@@ -8,14 +8,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustBeInsideCommandAnalyzer : AnalyzerBase
public class OptionMustBeInsideCommandAnalyzer()
: AnalyzerBase(
"Options must be defined inside commands",
$"This option must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`."
)
{
public OptionMustBeInsideCommandAnalyzer()
: base(
"Options must be defined inside commands",
$"This option must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -7,14 +7,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustBeRequiredIfPropertyRequiredAnalyzer : AnalyzerBase
public class OptionMustBeRequiredIfPropertyRequiredAnalyzer()
: AnalyzerBase(
"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."
)
{
public OptionMustBeRequiredIfPropertyRequiredAnalyzer()
: base(
"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."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -7,14 +7,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveNameOrShortNameAnalyzer : AnalyzerBase
public class OptionMustHaveNameOrShortNameAnalyzer()
: AnalyzerBase(
"Options must have either a name or short name specified",
"This option must have either a name or short name specified."
)
{
public OptionMustHaveNameOrShortNameAnalyzer()
: base(
"Options must have either a name or short name specified",
"This option must have either a name or short name specified."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -9,16 +9,14 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveUniqueNameAnalyzer : AnalyzerBase
public class OptionMustHaveUniqueNameAnalyzer()
: AnalyzerBase(
"Options must have unique names",
"This option's name must be unique within the command (comparison IS NOT case sensitive). "
+ "Specified name: `{0}`. "
+ "Property bound to another option with the same name: `{1}`."
)
{
public OptionMustHaveUniqueNameAnalyzer()
: base(
"Options must have unique names",
"This option's name must be unique within the command (comparison IS NOT case sensitive). "
+ "Specified name: `{0}`. "
+ "Property bound to another option with the same name: `{1}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,16 +8,14 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveUniqueShortNameAnalyzer : AnalyzerBase
public class OptionMustHaveUniqueShortNameAnalyzer()
: AnalyzerBase(
"Options must have unique short names",
"This option's short name must be unique within the command (comparison IS case sensitive). "
+ "Specified short name: `{0}` "
+ "Property bound to another option with the same short name: `{1}`."
)
{
public OptionMustHaveUniqueShortNameAnalyzer()
: base(
"Options must have unique short names",
"This option's short name must be unique within the command (comparison IS case sensitive). "
+ "Specified short name: `{0}` "
+ "Property bound to another option with the same short name: `{1}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,14 +8,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidConverterAnalyzer : AnalyzerBase
public class OptionMustHaveValidConverterAnalyzer()
: AnalyzerBase(
$"Option converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
$"Converter specified for this option must derive from a compatible `{SymbolNames.CliFxBindingConverterClass}`."
)
{
public OptionMustHaveValidConverterAnalyzer()
: base(
$"Option converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
$"Converter specified for this option must derive from a compatible `{SymbolNames.CliFxBindingConverterClass}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -7,15 +7,13 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidNameAnalyzer : AnalyzerBase
public class OptionMustHaveValidNameAnalyzer()
: AnalyzerBase(
"Options must have valid names",
"This option's name must be at least 2 characters long and must start with a letter. "
+ "Specified name: `{0}`."
)
{
public OptionMustHaveValidNameAnalyzer()
: base(
"Options must have valid names",
"This option's name must be at least 2 characters long and must start with a letter. "
+ "Specified name: `{0}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -7,15 +7,13 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidShortNameAnalyzer : AnalyzerBase
public class OptionMustHaveValidShortNameAnalyzer()
: AnalyzerBase(
"Option short names must be letter characters",
"This option's short name must be a single letter character. "
+ "Specified short name: `{0}`."
)
{
public OptionMustHaveValidShortNameAnalyzer()
: base(
"Option short names must be letter characters",
"This option's short name must be a single letter character. "
+ "Specified short name: `{0}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,14 +8,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class OptionMustHaveValidValidatorsAnalyzer : AnalyzerBase
public class OptionMustHaveValidValidatorsAnalyzer()
: AnalyzerBase(
$"Option validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
$"Each validator specified for this option must derive from a compatible `{SymbolNames.CliFxBindingValidatorClass}`."
)
{
public OptionMustHaveValidValidatorsAnalyzer()
: base(
$"Option validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
$"Each validator specified for this option must derive from a compatible `{SymbolNames.CliFxBindingValidatorClass}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,14 +8,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeInsideCommandAnalyzer : AnalyzerBase
public class ParameterMustBeInsideCommandAnalyzer()
: AnalyzerBase(
"Parameters must be defined inside commands",
$"This parameter must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`."
)
{
public ParameterMustBeInsideCommandAnalyzer()
: base(
"Parameters must be defined inside commands",
$"This parameter must be defined inside a class that implements `{SymbolNames.CliFxCommandInterface}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,15 +8,13 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeLastIfNonRequiredAnalyzer : AnalyzerBase
public class ParameterMustBeLastIfNonRequiredAnalyzer()
: AnalyzerBase(
"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). "
+ "Property bound to another non-required parameter: `{0}`."
)
{
public ParameterMustBeLastIfNonRequiredAnalyzer()
: base(
"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). "
+ "Property bound to another non-required parameter: `{0}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,15 +8,13 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeLastIfNonScalarAnalyzer : AnalyzerBase
public class ParameterMustBeLastIfNonScalarAnalyzer()
: AnalyzerBase(
"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). "
+ "Property bound to another non-scalar parameter: `{0}`."
)
{
public ParameterMustBeLastIfNonScalarAnalyzer()
: base(
"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). "
+ "Property bound to another non-scalar parameter: `{0}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -7,14 +7,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeRequiredIfPropertyRequiredAnalyzer : AnalyzerBase
public class ParameterMustBeRequiredIfPropertyRequiredAnalyzer()
: AnalyzerBase(
"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."
)
{
public ParameterMustBeRequiredIfPropertyRequiredAnalyzer()
: base(
"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."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,15 +8,13 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeSingleIfNonRequiredAnalyzer : AnalyzerBase
public class ParameterMustBeSingleIfNonRequiredAnalyzer()
: AnalyzerBase(
"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. "
+ "Property bound to another non-required parameter: `{0}`."
)
{
public ParameterMustBeSingleIfNonRequiredAnalyzer()
: base(
"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. "
+ "Property bound to another non-required parameter: `{0}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,15 +8,13 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustBeSingleIfNonScalarAnalyzer : AnalyzerBase
public class ParameterMustBeSingleIfNonScalarAnalyzer()
: AnalyzerBase(
"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. "
+ "Property bound to another non-scalar parameter: `{0}`."
)
{
public ParameterMustBeSingleIfNonScalarAnalyzer()
: base(
"Parameters of non-scalar types are limited to one per command",
"This parameter has a non-scalar type so it must be the only such parameter in the command. "
+ "Property bound to another non-scalar parameter: `{0}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -9,16 +9,14 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveUniqueNameAnalyzer : AnalyzerBase
public class ParameterMustHaveUniqueNameAnalyzer()
: AnalyzerBase(
"Parameters must have unique names",
"This parameter's name must be unique within the command (comparison IS NOT case sensitive). "
+ "Specified name: `{0}`. "
+ "Property bound to another parameter with the same name: `{1}`."
)
{
public ParameterMustHaveUniqueNameAnalyzer()
: base(
"Parameters must have unique names",
"This parameter's name must be unique within the command (comparison IS NOT case sensitive). "
+ "Specified name: `{0}`. "
+ "Property bound to another parameter with the same name: `{1}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,16 +8,14 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveUniqueOrderAnalyzer : AnalyzerBase
public class ParameterMustHaveUniqueOrderAnalyzer()
: AnalyzerBase(
"Parameters must have unique order",
"This parameter's order must be unique within the command. "
+ "Specified order: {0}. "
+ "Property bound to another parameter with the same order: `{1}`."
)
{
public ParameterMustHaveUniqueOrderAnalyzer()
: base(
"Parameters must have unique order",
"This parameter's order must be unique within the command. "
+ "Specified order: {0}. "
+ "Property bound to another parameter with the same order: `{1}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,14 +8,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveValidConverterAnalyzer : AnalyzerBase
public class ParameterMustHaveValidConverterAnalyzer()
: AnalyzerBase(
$"Parameter converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
$"Converter specified for this parameter must derive from a compatible `{SymbolNames.CliFxBindingConverterClass}`."
)
{
public ParameterMustHaveValidConverterAnalyzer()
: base(
$"Parameter converters must derive from `{SymbolNames.CliFxBindingConverterClass}`",
$"Converter specified for this parameter must derive from a compatible `{SymbolNames.CliFxBindingConverterClass}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -8,14 +8,12 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ParameterMustHaveValidValidatorsAnalyzer : AnalyzerBase
public class ParameterMustHaveValidValidatorsAnalyzer()
: AnalyzerBase(
$"Parameter validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
$"Each validator specified for this parameter must derive from a compatible `{SymbolNames.CliFxBindingValidatorClass}`."
)
{
public ParameterMustHaveValidValidatorsAnalyzer()
: base(
$"Parameter validators must derive from `{SymbolNames.CliFxBindingValidatorClass}`",
$"Each validator specified for this parameter must derive from a compatible `{SymbolNames.CliFxBindingValidatorClass}`."
) { }
private void Analyze(
SyntaxNodeAnalysisContext context,
PropertyDeclarationSyntax propertyDeclaration,

View File

@@ -9,15 +9,13 @@ using Microsoft.CodeAnalysis.Diagnostics;
namespace CliFx.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SystemConsoleShouldBeAvoidedAnalyzer : AnalyzerBase
public class SystemConsoleShouldBeAvoidedAnalyzer()
: AnalyzerBase(
$"Avoid calling `System.Console` where `{SymbolNames.CliFxConsoleInterface}` is available",
$"Use the provided `{SymbolNames.CliFxConsoleInterface}` abstraction instead of `System.Console` to ensure that the command can be tested in isolation.",
DiagnosticSeverity.Warning
)
{
public SystemConsoleShouldBeAvoidedAnalyzer()
: base(
$"Avoid calling `System.Console` where `{SymbolNames.CliFxConsoleInterface}` is available",
$"Use the provided `{SymbolNames.CliFxConsoleInterface}` abstraction instead of `System.Console` to ensure that the command can be tested in isolation.",
DiagnosticSeverity.Warning
) { }
private MemberAccessExpressionSyntax? TryGetSystemConsoleMemberAccess(
SyntaxNodeAnalysisContext context,
SyntaxNode node

View File

@@ -6,11 +6,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.11" />
<PackageReference Include="clipr" Version="1.6.1" />
<PackageReference Include="Cocona" Version="2.2.0" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.0" />
<PackageReference Include="PowerArgs" Version="4.0.3" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>

View File

@@ -9,10 +9,8 @@ using CliFx.Infrastructure;
namespace CliFx.Demo.Commands;
[Command("book add", Description = "Adds a book to the library.")]
public partial class BookAddCommand : ICommand
public partial class BookAddCommand(LibraryProvider libraryProvider) : ICommand
{
private readonly LibraryProvider _libraryProvider;
[CommandParameter(0, Name = "title", Description = "Book title.")]
public required string Title { get; init; }
@@ -25,18 +23,13 @@ public partial class BookAddCommand : ICommand
[CommandOption("isbn", 'n', Description = "Book ISBN.")]
public Isbn Isbn { get; init; } = CreateRandomIsbn();
public BookAddCommand(LibraryProvider libraryProvider)
{
_libraryProvider = libraryProvider;
}
public ValueTask ExecuteAsync(IConsole console)
{
if (_libraryProvider.TryGetBook(Title) is not null)
if (libraryProvider.TryGetBook(Title) is not null)
throw new CommandException("Book already exists.", 10);
var book = new Book(Title, Author, Published, Isbn);
_libraryProvider.AddBook(book);
libraryProvider.AddBook(book);
console.Output.WriteLine("Book added.");
console.Output.WriteBook(book);

View File

@@ -8,21 +8,14 @@ using CliFx.Infrastructure;
namespace CliFx.Demo.Commands;
[Command("book", Description = "Retrieves a book from the library.")]
public class BookCommand : ICommand
public class BookCommand(LibraryProvider libraryProvider) : ICommand
{
private readonly LibraryProvider _libraryProvider;
[CommandParameter(0, Name = "title", Description = "Title of the book to retrieve.")]
public required string Title { get; init; }
public BookCommand(LibraryProvider libraryProvider)
{
_libraryProvider = libraryProvider;
}
public ValueTask ExecuteAsync(IConsole console)
{
var book = _libraryProvider.TryGetBook(Title);
var book = libraryProvider.TryGetBook(Title);
if (book is null)
throw new CommandException("Book not found.", 10);

View File

@@ -7,18 +7,11 @@ using CliFx.Infrastructure;
namespace CliFx.Demo.Commands;
[Command("book list", Description = "Lists all books in the library.")]
public class BookListCommand : ICommand
public class BookListCommand(LibraryProvider libraryProvider) : ICommand
{
private readonly LibraryProvider _libraryProvider;
public BookListCommand(LibraryProvider libraryProvider)
{
_libraryProvider = libraryProvider;
}
public ValueTask ExecuteAsync(IConsole console)
{
var library = _libraryProvider.GetLibrary();
var library = libraryProvider.GetLibrary();
for (var i = 0; i < library.Books.Count; i++)
{

View File

@@ -7,26 +7,19 @@ using CliFx.Infrastructure;
namespace CliFx.Demo.Commands;
[Command("book remove", Description = "Removes a book from the library.")]
public class BookRemoveCommand : ICommand
public class BookRemoveCommand(LibraryProvider libraryProvider) : ICommand
{
private readonly LibraryProvider _libraryProvider;
[CommandParameter(0, Name = "title", Description = "Title of the book to remove.")]
public required string Title { get; init; }
public BookRemoveCommand(LibraryProvider libraryProvider)
{
_libraryProvider = libraryProvider;
}
public ValueTask ExecuteAsync(IConsole console)
{
var book = _libraryProvider.TryGetBook(Title);
var book = libraryProvider.TryGetBook(Title);
if (book is null)
throw new CommandException("Book not found.", 10);
_libraryProvider.RemoveBook(book);
libraryProvider.RemoveBook(book);
console.Output.WriteLine($"Book {Title} removed.");

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>

View File

@@ -8,11 +8,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class ApplicationSpecs : SpecsBase
public class ApplicationSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public ApplicationSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact]
public async Task I_can_create_an_application_with_the_default_configuration()
{

View File

@@ -12,11 +12,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class CancellationSpecs : SpecsBase
public class CancellationSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public CancellationSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact(Timeout = 15000)]
public async Task I_can_configure_the_command_to_listen_to_the_interrupt_signal()
{

View File

@@ -12,15 +12,15 @@
<PackageReference Include="Basic.Reference.Assemblies.Net70" Version="1.4.5" />
<PackageReference Include="CliWrap" Version="3.6.4" />
<PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="PolyShim" Version="1.8.0" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.6.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.6.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>

View File

@@ -14,11 +14,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class ConsoleSpecs : SpecsBase
public class ConsoleSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public ConsoleSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact(Timeout = 15000)]
public async Task I_can_run_the_application_with_the_default_console_implementation_to_interact_with_the_system_console()
{

View File

@@ -8,11 +8,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class ConversionSpecs : SpecsBase
public class ConversionSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public ConversionSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact]
public async Task I_can_bind_a_parameter_or_an_option_to_a_string_property()
{

View File

@@ -11,11 +11,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class DirectivesSpecs : SpecsBase
public class DirectivesSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public DirectivesSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact(Timeout = 15000)]
public async Task I_can_use_the_debug_directive_to_make_the_application_wait_for_the_debugger_to_attach()
{

View File

@@ -12,11 +12,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class EnvironmentSpecs : SpecsBase
public class EnvironmentSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public EnvironmentSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[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()
{

View File

@@ -9,11 +9,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class ErrorReportingSpecs : SpecsBase
public class ErrorReportingSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public ErrorReportingSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact]
public async Task I_can_throw_an_exception_in_a_command_to_report_an_error_with_a_stacktrace()
{

View File

@@ -9,11 +9,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class HelpTextSpecs : SpecsBase
public class HelpTextSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public HelpTextSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact]
public async Task I_can_request_the_help_text_by_running_the_application_without_arguments_if_the_default_command_is_not_defined()
{

View File

@@ -9,11 +9,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class OptionBindingSpecs : SpecsBase
public class OptionBindingSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public OptionBindingSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact]
public async Task I_can_bind_an_option_to_a_property_and_get_the_value_from_the_corresponding_argument_by_name()
{

View File

@@ -8,11 +8,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class ParameterBindingSpecs : SpecsBase
public class ParameterBindingSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public ParameterBindingSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact]
public async Task I_can_bind_a_parameter_to_a_property_and_get_the_value_from_the_corresponding_argument()
{

View File

@@ -8,11 +8,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class RoutingSpecs : SpecsBase
public class RoutingSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public RoutingSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact]
public async Task I_can_configure_a_command_to_be_executed_by_default_when_the_user_does_not_specify_a_command_name()
{

View File

@@ -5,14 +5,12 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public abstract class SpecsBase : IDisposable
public abstract class SpecsBase(ITestOutputHelper testOutput) : IDisposable
{
public ITestOutputHelper TestOutput { get; }
public ITestOutputHelper TestOutput { get; } = testOutput;
public FakeInMemoryConsole FakeConsole { get; } = new();
protected SpecsBase(ITestOutputHelper testOutput) => TestOutput = testOutput;
public void Dispose()
{
FakeConsole.DumpToTestOutput(TestOutput);

View File

@@ -10,11 +10,8 @@ using Xunit.Abstractions;
namespace CliFx.Tests;
public class TypeActivationSpecs : SpecsBase
public class TypeActivationSpecs(ITestOutputHelper testOutput) : SpecsBase(testOutput)
{
public TypeActivationSpecs(ITestOutputHelper testOutput)
: base(testOutput) { }
[Fact]
public async Task I_can_configure_the_application_to_use_the_default_type_activator_to_initialize_types_through_parameterless_constructors()
{

View File

@@ -21,7 +21,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="PolyShim" Version="1.8.0" PrivateAssets="all" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" Condition="'$(TargetFramework)' == 'netstandard2.0'" />

View File

@@ -12,22 +12,16 @@ using CliFx.Utils.Extensions;
namespace CliFx;
internal class CommandBinder
internal class CommandBinder(ITypeActivator typeActivator)
{
private readonly ITypeActivator _typeActivator;
private readonly IFormatProvider _formatProvider = CultureInfo.InvariantCulture;
public CommandBinder(ITypeActivator typeActivator)
{
_typeActivator = typeActivator;
}
private object? ConvertSingle(IMemberSchema memberSchema, string? rawValue, Type targetType)
{
// Custom converter
if (memberSchema.ConverterType is not null)
{
var converter = _typeActivator.CreateInstance<IBindingConverter>(
var converter = typeActivator.CreateInstance<IBindingConverter>(
memberSchema.ConverterType
);
return converter.Convert(rawValue);
@@ -213,7 +207,7 @@ internal class CommandBinder
foreach (var validatorType in memberSchema.ValidatorTypes)
{
var validator = _typeActivator.CreateInstance<IBindingValidator>(validatorType);
var validator = typeActivator.CreateInstance<IBindingValidator>(validatorType);
var error = validator.Validate(convertedValue);
if (error is not null)

View File

@@ -4,11 +4,9 @@ using CliFx.Input;
namespace CliFx.Formatting;
internal class CommandInputConsoleFormatter : ConsoleFormatter
internal class CommandInputConsoleFormatter(ConsoleWriter consoleWriter)
: ConsoleFormatter(consoleWriter)
{
public CommandInputConsoleFormatter(ConsoleWriter consoleWriter)
: base(consoleWriter) { }
private void WriteCommandLineArguments(CommandInput commandInput)
{
Write("Command-line:");

View File

@@ -3,50 +3,46 @@ using CliFx.Infrastructure;
namespace CliFx.Formatting;
internal class ConsoleFormatter
internal class ConsoleFormatter(ConsoleWriter consoleWriter)
{
private readonly ConsoleWriter _consoleWriter;
private int _column;
private int _row;
public bool IsEmpty => _column == 0 && _row == 0;
public ConsoleFormatter(ConsoleWriter consoleWriter) => _consoleWriter = consoleWriter;
public void Write(string? value)
{
_consoleWriter.Write(value);
consoleWriter.Write(value);
_column += value?.Length ?? 0;
}
public void Write(char value)
{
_consoleWriter.Write(value);
consoleWriter.Write(value);
_column++;
}
public void Write(ConsoleColor foregroundColor, string? value)
{
using (_consoleWriter.Console.WithForegroundColor(foregroundColor))
using (consoleWriter.Console.WithForegroundColor(foregroundColor))
Write(value);
}
public void Write(ConsoleColor foregroundColor, char value)
{
using (_consoleWriter.Console.WithForegroundColor(foregroundColor))
using (consoleWriter.Console.WithForegroundColor(foregroundColor))
Write(value);
}
public void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string? value)
{
using (_consoleWriter.Console.WithColors(foregroundColor, backgroundColor))
using (consoleWriter.Console.WithColors(foregroundColor, backgroundColor))
Write(value);
}
public void WriteLine()
{
_consoleWriter.WriteLine();
consoleWriter.WriteLine();
_column = 0;
_row++;
}

View File

@@ -7,11 +7,9 @@ using CliFx.Utils.Extensions;
namespace CliFx.Formatting;
internal class ExceptionConsoleFormatter : ConsoleFormatter
internal class ExceptionConsoleFormatter(ConsoleWriter consoleWriter)
: ConsoleFormatter(consoleWriter)
{
public ExceptionConsoleFormatter(ConsoleWriter consoleWriter)
: base(consoleWriter) { }
private void WriteStackFrame(StackFrame stackFrame, int indentLevel)
{
WriteHorizontalMargin(2 + 4 * indentLevel);

View File

@@ -9,16 +9,9 @@ using CliFx.Utils.Extensions;
namespace CliFx.Formatting;
internal class HelpConsoleFormatter : ConsoleFormatter
internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext context)
: ConsoleFormatter(consoleWriter)
{
private readonly HelpContext _context;
public HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext context)
: base(consoleWriter)
{
_context = context;
}
private void WriteHeader(string text)
{
Write(ConsoleColor.White, text.ToUpperInvariant());
@@ -27,13 +20,13 @@ internal class HelpConsoleFormatter : ConsoleFormatter
private void WriteCommandInvocation()
{
Write(_context.ApplicationMetadata.ExecutableName);
Write(context.ApplicationMetadata.ExecutableName);
// Command name
if (!string.IsNullOrWhiteSpace(_context.CommandSchema.Name))
if (!string.IsNullOrWhiteSpace(context.CommandSchema.Name))
{
Write(' ');
Write(ConsoleColor.Cyan, _context.CommandSchema.Name);
Write(ConsoleColor.Cyan, context.CommandSchema.Name);
}
}
@@ -43,16 +36,16 @@ internal class HelpConsoleFormatter : ConsoleFormatter
WriteVerticalMargin();
// Title and version
Write(ConsoleColor.White, _context.ApplicationMetadata.Title);
Write(ConsoleColor.White, context.ApplicationMetadata.Title);
Write(' ');
Write(ConsoleColor.Yellow, _context.ApplicationMetadata.Version);
Write(ConsoleColor.Yellow, context.ApplicationMetadata.Version);
WriteLine();
// Description
if (!string.IsNullOrWhiteSpace(_context.ApplicationMetadata.Description))
if (!string.IsNullOrWhiteSpace(context.ApplicationMetadata.Description))
{
WriteHorizontalMargin();
Write(_context.ApplicationMetadata.Description);
Write(context.ApplicationMetadata.Description);
WriteLine();
}
}
@@ -72,7 +65,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
Write(' ');
// Parameters
foreach (var parameter in _context.CommandSchema.Parameters.OrderBy(p => p.Order))
foreach (var parameter in context.CommandSchema.Parameters.OrderBy(p => p.Order))
{
Write(
ConsoleColor.DarkCyan,
@@ -82,7 +75,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
}
// Required options
foreach (var option in _context.CommandSchema.Options.Where(o => o.IsRequired))
foreach (var option in context.CommandSchema.Options.Where(o => o.IsRequired))
{
Write(
ConsoleColor.Yellow,
@@ -97,7 +90,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
}
// Placeholder for non-required options
if (_context.CommandSchema.Options.Any(o => !o.IsRequired))
if (context.CommandSchema.Options.Any(o => !o.IsRequired))
{
Write(ConsoleColor.Yellow, "[options]");
}
@@ -106,9 +99,9 @@ internal class HelpConsoleFormatter : ConsoleFormatter
}
// Child command usage
var childCommandSchemas = _context
var childCommandSchemas = context
.ApplicationSchema
.GetChildCommands(_context.CommandSchema.Name);
.GetChildCommands(context.CommandSchema.Name);
if (childCommandSchemas.Any())
{
@@ -130,7 +123,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
private void WriteCommandDescription()
{
if (string.IsNullOrWhiteSpace(_context.CommandSchema.Description))
if (string.IsNullOrWhiteSpace(context.CommandSchema.Description))
return;
if (!IsEmpty)
@@ -140,13 +133,13 @@ internal class HelpConsoleFormatter : ConsoleFormatter
WriteHorizontalMargin();
Write(_context.CommandSchema.Description);
Write(context.CommandSchema.Description);
WriteLine();
}
private void WriteCommandParameters()
{
if (!_context.CommandSchema.Parameters.Any())
if (!context.CommandSchema.Parameters.Any())
return;
if (!IsEmpty)
@@ -154,7 +147,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
WriteHeader("Parameters");
foreach (var parameterSchema in _context.CommandSchema.Parameters.OrderBy(p => p.Order))
foreach (var parameterSchema in context.CommandSchema.Parameters.OrderBy(p => p.Order))
{
if (parameterSchema.IsRequired)
{
@@ -224,7 +217,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
WriteHeader("Options");
foreach (
var optionSchema in _context.CommandSchema.Options.OrderByDescending(o => o.IsRequired)
var optionSchema in context.CommandSchema.Options.OrderByDescending(o => o.IsRequired)
)
{
if (optionSchema.IsRequired)
@@ -314,7 +307,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
private void WriteDefaultValue(IMemberSchema schema)
{
var defaultValue = _context.CommandDefaultValues.GetValueOrDefault(schema);
var defaultValue = context.CommandDefaultValues.GetValueOrDefault(schema);
if (defaultValue is not null)
{
// Non-Scalar
@@ -365,9 +358,9 @@ internal class HelpConsoleFormatter : ConsoleFormatter
private void WriteCommandChildren()
{
var childCommandSchemas = _context
var childCommandSchemas = context
.ApplicationSchema
.GetChildCommands(_context.CommandSchema.Name)
.GetChildCommands(context.CommandSchema.Name)
.OrderBy(a => a.Name, StringComparer.Ordinal)
.ToArray();
@@ -386,7 +379,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
Write(
ConsoleColor.Cyan,
// Relative to current command
childCommandSchema.Name?.Substring(_context.CommandSchema.Name?.Length ?? 0).Trim()
childCommandSchema.Name?.Substring(context.CommandSchema.Name?.Length ?? 0).Trim()
);
WriteColumnMargin();
@@ -399,7 +392,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
}
// Child commands of child command
var grandChildCommandSchemas = _context
var grandChildCommandSchemas = context
.ApplicationSchema
.GetChildCommands(childCommandSchema.Name)
.OrderBy(c => c.Name, StringComparer.Ordinal)
@@ -426,7 +419,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
// Relative to current command (not the parent)
grandChildCommandSchema
.Name
?.Substring(_context.CommandSchema.Name?.Length ?? 0)
?.Substring(context.CommandSchema.Name?.Length ?? 0)
.Trim()
);
}

View File

@@ -3,26 +3,19 @@ using CliFx.Schema;
namespace CliFx.Formatting;
internal class HelpContext
internal class HelpContext(
ApplicationMetadata applicationMetadata,
ApplicationSchema applicationSchema,
CommandSchema commandSchema,
IReadOnlyDictionary<IMemberSchema, object?> commandDefaultValues
)
{
public ApplicationMetadata ApplicationMetadata { get; }
public ApplicationMetadata ApplicationMetadata { get; } = applicationMetadata;
public ApplicationSchema ApplicationSchema { get; }
public ApplicationSchema ApplicationSchema { get; } = applicationSchema;
public CommandSchema CommandSchema { get; }
public CommandSchema CommandSchema { get; } = commandSchema;
public IReadOnlyDictionary<IMemberSchema, object?> CommandDefaultValues { get; }
public HelpContext(
ApplicationMetadata applicationMetadata,
ApplicationSchema applicationSchema,
CommandSchema commandSchema,
IReadOnlyDictionary<IMemberSchema, object?> commandDefaultValues
)
{
ApplicationMetadata = applicationMetadata;
ApplicationSchema = applicationSchema;
CommandSchema = commandSchema;
CommandDefaultValues = commandDefaultValues;
}
public IReadOnlyDictionary<IMemberSchema, object?> CommandDefaultValues { get; } =
commandDefaultValues;
}

View File

@@ -5,17 +5,24 @@ using CliFx.Utils.Extensions;
namespace CliFx.Input;
internal partial class CommandInput
internal partial class CommandInput(
string? commandName,
IReadOnlyList<DirectiveInput> directives,
IReadOnlyList<ParameterInput> parameters,
IReadOnlyList<OptionInput> options,
IReadOnlyList<EnvironmentVariableInput> environmentVariables
)
{
public string? CommandName { get; }
public string? CommandName { get; } = commandName;
public IReadOnlyList<DirectiveInput> Directives { get; }
public IReadOnlyList<DirectiveInput> Directives { get; } = directives;
public IReadOnlyList<ParameterInput> Parameters { get; }
public IReadOnlyList<ParameterInput> Parameters { get; } = parameters;
public IReadOnlyList<OptionInput> Options { get; }
public IReadOnlyList<OptionInput> Options { get; } = options;
public IReadOnlyList<EnvironmentVariableInput> EnvironmentVariables { get; }
public IReadOnlyList<EnvironmentVariableInput> EnvironmentVariables { get; } =
environmentVariables;
public bool HasArguments =>
!string.IsNullOrWhiteSpace(CommandName)
@@ -30,21 +37,6 @@ internal partial class CommandInput
public bool IsHelpOptionSpecified => Options.Any(o => o.IsHelpOption);
public bool IsVersionOptionSpecified => Options.Any(o => o.IsVersionOption);
public CommandInput(
string? commandName,
IReadOnlyList<DirectiveInput> directives,
IReadOnlyList<ParameterInput> parameters,
IReadOnlyList<OptionInput> options,
IReadOnlyList<EnvironmentVariableInput> environmentVariables
)
{
CommandName = commandName;
Directives = directives;
Parameters = parameters;
Options = options;
EnvironmentVariables = environmentVariables;
}
}
internal partial class CommandInput

View File

@@ -2,15 +2,13 @@
namespace CliFx.Input;
internal class DirectiveInput
internal class DirectiveInput(string name)
{
public string Name { get; }
public string Name { get; } = name;
public bool IsDebugDirective =>
string.Equals(Name, "debug", StringComparison.OrdinalIgnoreCase);
public bool IsPreviewDirective =>
string.Equals(Name, "preview", StringComparison.OrdinalIgnoreCase);
public DirectiveInput(string name) => Name = name;
}

View File

@@ -3,17 +3,11 @@ using System.IO;
namespace CliFx.Input;
internal class EnvironmentVariableInput
internal class EnvironmentVariableInput(string name, string value)
{
public string Name { get; }
public string Name { get; } = name;
public string Value { get; }
public EnvironmentVariableInput(string name, string value)
{
Name = name;
Value = value;
}
public string Value { get; } = value;
public IReadOnlyList<string> SplitValues() => Value.Split(Path.PathSeparator);
}

View File

@@ -3,22 +3,16 @@ using CliFx.Schema;
namespace CliFx.Input;
internal class OptionInput
internal class OptionInput(string identifier, IReadOnlyList<string> values)
{
public string Identifier { get; }
public string Identifier { get; } = identifier;
public IReadOnlyList<string> Values { get; }
public IReadOnlyList<string> Values { get; } = values;
public bool IsHelpOption => OptionSchema.HelpOption.MatchesIdentifier(Identifier);
public bool IsVersionOption => OptionSchema.VersionOption.MatchesIdentifier(Identifier);
public OptionInput(string identifier, IReadOnlyList<string> values)
{
Identifier = identifier;
Values = values;
}
public string GetFormattedIdentifier() =>
Identifier switch
{

View File

@@ -1,10 +1,8 @@
namespace CliFx.Input;
internal class ParameterInput
internal class ParameterInput(string value)
{
public string Value { get; }
public ParameterInput(string value) => Value = value;
public string Value { get; } = value;
public string GetFormattedIdentifier() => $"<{Value}>";
}

View File

@@ -5,14 +5,9 @@ using CliFx.Utils.Extensions;
namespace CliFx.Schema;
internal partial class ApplicationSchema
internal partial class ApplicationSchema(IReadOnlyList<CommandSchema> commands)
{
public IReadOnlyList<CommandSchema> Commands { get; }
public ApplicationSchema(IReadOnlyList<CommandSchema> commands)
{
Commands = commands;
}
public IReadOnlyList<CommandSchema> Commands { get; } = commands;
public IReadOnlyList<string> GetCommandNames() =>
Commands.Select(c => c.Name).WhereNotNullOrWhiteSpace().ToArray();

View File

@@ -5,18 +5,14 @@ using CliFx.Utils.Extensions;
namespace CliFx.Schema;
internal class BindablePropertyDescriptor : IPropertyDescriptor
internal class BindablePropertyDescriptor(PropertyInfo property) : IPropertyDescriptor
{
private readonly PropertyInfo _property;
public Type Type => property.PropertyType;
public Type Type => _property.PropertyType;
public BindablePropertyDescriptor(PropertyInfo property) => _property = property;
public object? GetValue(ICommand commandInstance) => _property.GetValue(commandInstance);
public object? GetValue(ICommand commandInstance) => property.GetValue(commandInstance);
public void SetValue(ICommand commandInstance, object? value) =>
_property.SetValue(commandInstance, value);
property.SetValue(commandInstance, value);
public IReadOnlyList<object?> GetValidValues()
{

View File

@@ -8,17 +8,23 @@ using CliFx.Utils.Extensions;
namespace CliFx.Schema;
internal partial class CommandSchema
internal partial class CommandSchema(
Type type,
string? name,
string? description,
IReadOnlyList<ParameterSchema> parameters,
IReadOnlyList<OptionSchema> options
)
{
public Type Type { get; }
public Type Type { get; } = type;
public string? Name { get; }
public string? Name { get; } = name;
public string? Description { get; }
public string? Description { get; } = description;
public IReadOnlyList<ParameterSchema> Parameters { get; }
public IReadOnlyList<ParameterSchema> Parameters { get; } = parameters;
public IReadOnlyList<OptionSchema> Options { get; }
public IReadOnlyList<OptionSchema> Options { get; } = options;
public bool IsDefault => string.IsNullOrWhiteSpace(Name);
@@ -26,21 +32,6 @@ internal partial class CommandSchema
public bool IsVersionOptionAvailable => Options.Contains(OptionSchema.VersionOption);
public CommandSchema(
Type type,
string? name,
string? description,
IReadOnlyList<ParameterSchema> parameters,
IReadOnlyList<OptionSchema> options
)
{
Type = type;
Name = name;
Description = description;
Parameters = parameters;
Options = options;
}
public bool MatchesName(string? name) =>
!string.IsNullOrWhiteSpace(Name)
? string.Equals(name, Name, StringComparison.OrdinalIgnoreCase)

View File

@@ -7,44 +7,32 @@ using CliFx.Utils.Extensions;
namespace CliFx.Schema;
internal partial class OptionSchema : IMemberSchema
internal partial class OptionSchema(
IPropertyDescriptor property,
string? name,
char? shortName,
string? environmentVariable,
bool isRequired,
string? description,
Type? converterType,
IReadOnlyList<Type> validatorTypes
) : IMemberSchema
{
public IPropertyDescriptor Property { get; }
public IPropertyDescriptor Property { get; } = property;
public string? Name { get; }
public string? Name { get; } = name;
public char? ShortName { get; }
public char? ShortName { get; } = shortName;
public string? EnvironmentVariable { get; }
public string? EnvironmentVariable { get; } = environmentVariable;
public bool IsRequired { get; }
public bool IsRequired { get; } = isRequired;
public string? Description { get; }
public string? Description { get; } = description;
public Type? ConverterType { get; }
public Type? ConverterType { get; } = converterType;
public IReadOnlyList<Type> ValidatorTypes { get; }
public OptionSchema(
IPropertyDescriptor property,
string? name,
char? shortName,
string? environmentVariable,
bool isRequired,
string? description,
Type? converterType,
IReadOnlyList<Type> validatorTypes
)
{
Property = property;
Name = name;
ShortName = shortName;
EnvironmentVariable = environmentVariable;
IsRequired = isRequired;
Description = description;
ConverterType = converterType;
ValidatorTypes = validatorTypes;
}
public IReadOnlyList<Type> ValidatorTypes { get; } = validatorTypes;
public bool MatchesName(string? name) =>
!string.IsNullOrWhiteSpace(Name)

View File

@@ -6,40 +6,29 @@ using CliFx.Utils.Extensions;
namespace CliFx.Schema;
internal partial class ParameterSchema : IMemberSchema
internal partial class ParameterSchema(
IPropertyDescriptor property,
int order,
string name,
bool isRequired,
string? description,
Type? converterType,
IReadOnlyList<Type> validatorTypes
) : IMemberSchema
{
public IPropertyDescriptor Property { get; }
public IPropertyDescriptor Property { get; } = property;
public int Order { get; }
public int Order { get; } = order;
public string Name { get; }
public string Name { get; } = name;
public bool IsRequired { get; }
public bool IsRequired { get; } = isRequired;
public string? Description { get; }
public string? Description { get; } = description;
public Type? ConverterType { get; }
public Type? ConverterType { get; } = converterType;
public IReadOnlyList<Type> ValidatorTypes { get; }
public ParameterSchema(
IPropertyDescriptor property,
int order,
string name,
bool isRequired,
string? description,
Type? converterType,
IReadOnlyList<Type> validatorTypes
)
{
Property = property;
Order = order;
Name = name;
IsRequired = isRequired;
Description = description;
ConverterType = converterType;
ValidatorTypes = validatorTypes;
}
public IReadOnlyList<Type> ValidatorTypes { get; } = validatorTypes;
public string GetFormattedIdentifier() => Property.IsScalar() ? $"<{Name}>" : $"<{Name}...>";
}

View File

@@ -3,13 +3,9 @@ using System.Collections.Generic;
namespace CliFx.Utils;
internal partial class Disposable : IDisposable
internal partial class Disposable(Action dispose) : IDisposable
{
private readonly Action _dispose;
public Disposable(Action dispose) => _dispose = dispose;
public void Dispose() => _dispose();
public void Dispose() => dispose();
}
internal partial class Disposable

View File

@@ -10,65 +10,58 @@ namespace CliFx.Utils;
// https://source.dot.net/#System.Console/ConsoleEncoding.cs,5eedd083a4a4f4a2
// Majority of overrides are just proxy calls to avoid potentially more expensive base behavior.
// The important part is the GetPreamble() method that has been overriden to return an empty array.
internal class NoPreambleEncoding : Encoding
internal class NoPreambleEncoding(Encoding underlyingEncoding)
: Encoding(
underlyingEncoding.CodePage,
underlyingEncoding.EncoderFallback,
underlyingEncoding.DecoderFallback
)
{
private readonly Encoding _underlyingEncoding;
[ExcludeFromCodeCoverage]
public override string EncodingName => underlyingEncoding.EncodingName;
[ExcludeFromCodeCoverage]
public override string EncodingName => _underlyingEncoding.EncodingName;
public override string BodyName => underlyingEncoding.BodyName;
[ExcludeFromCodeCoverage]
public override string BodyName => _underlyingEncoding.BodyName;
public override int CodePage => underlyingEncoding.CodePage;
[ExcludeFromCodeCoverage]
public override int CodePage => _underlyingEncoding.CodePage;
public override int WindowsCodePage => underlyingEncoding.WindowsCodePage;
[ExcludeFromCodeCoverage]
public override int WindowsCodePage => _underlyingEncoding.WindowsCodePage;
public override string HeaderName => underlyingEncoding.HeaderName;
[ExcludeFromCodeCoverage]
public override string HeaderName => _underlyingEncoding.HeaderName;
public override string WebName => underlyingEncoding.WebName;
[ExcludeFromCodeCoverage]
public override string WebName => _underlyingEncoding.WebName;
public override bool IsBrowserDisplay => underlyingEncoding.IsBrowserDisplay;
[ExcludeFromCodeCoverage]
public override bool IsBrowserDisplay => _underlyingEncoding.IsBrowserDisplay;
public override bool IsBrowserSave => underlyingEncoding.IsBrowserSave;
[ExcludeFromCodeCoverage]
public override bool IsBrowserSave => _underlyingEncoding.IsBrowserSave;
public override bool IsSingleByte => underlyingEncoding.IsSingleByte;
[ExcludeFromCodeCoverage]
public override bool IsSingleByte => _underlyingEncoding.IsSingleByte;
public override bool IsMailNewsDisplay => underlyingEncoding.IsMailNewsDisplay;
[ExcludeFromCodeCoverage]
public override bool IsMailNewsDisplay => _underlyingEncoding.IsMailNewsDisplay;
[ExcludeFromCodeCoverage]
public override bool IsMailNewsSave => _underlyingEncoding.IsMailNewsSave;
public NoPreambleEncoding(Encoding underlyingEncoding)
: base(
underlyingEncoding.CodePage,
underlyingEncoding.EncoderFallback,
underlyingEncoding.DecoderFallback
)
{
_underlyingEncoding = underlyingEncoding;
}
public override bool IsMailNewsSave => underlyingEncoding.IsMailNewsSave;
// This is the only part that changes
public override byte[] GetPreamble() => Array.Empty<byte>();
[ExcludeFromCodeCoverage]
public override int GetByteCount(char[] chars, int index, int count) =>
_underlyingEncoding.GetByteCount(chars, index, count);
underlyingEncoding.GetByteCount(chars, index, count);
[ExcludeFromCodeCoverage]
public override int GetByteCount(char[] chars) => _underlyingEncoding.GetByteCount(chars);
public override int GetByteCount(char[] chars) => underlyingEncoding.GetByteCount(chars);
[ExcludeFromCodeCoverage]
public override int GetByteCount(string s) => _underlyingEncoding.GetByteCount(s);
public override int GetByteCount(string s) => underlyingEncoding.GetByteCount(s);
[ExcludeFromCodeCoverage]
public override int GetBytes(
@@ -77,14 +70,14 @@ internal class NoPreambleEncoding : Encoding
int charCount,
byte[] bytes,
int byteIndex
) => _underlyingEncoding.GetBytes(chars, charIndex, charCount, bytes, byteIndex);
) => underlyingEncoding.GetBytes(chars, charIndex, charCount, bytes, byteIndex);
[ExcludeFromCodeCoverage]
public override byte[] GetBytes(char[] chars, int index, int count) =>
_underlyingEncoding.GetBytes(chars, index, count);
underlyingEncoding.GetBytes(chars, index, count);
[ExcludeFromCodeCoverage]
public override byte[] GetBytes(char[] chars) => _underlyingEncoding.GetBytes(chars);
public override byte[] GetBytes(char[] chars) => underlyingEncoding.GetBytes(chars);
[ExcludeFromCodeCoverage]
public override int GetBytes(
@@ -93,17 +86,17 @@ internal class NoPreambleEncoding : Encoding
int charCount,
byte[] bytes,
int byteIndex
) => _underlyingEncoding.GetBytes(s, charIndex, charCount, bytes, byteIndex);
) => underlyingEncoding.GetBytes(s, charIndex, charCount, bytes, byteIndex);
[ExcludeFromCodeCoverage]
public override byte[] GetBytes(string s) => _underlyingEncoding.GetBytes(s);
public override byte[] GetBytes(string s) => underlyingEncoding.GetBytes(s);
[ExcludeFromCodeCoverage]
public override int GetCharCount(byte[] bytes, int index, int count) =>
_underlyingEncoding.GetCharCount(bytes, index, count);
underlyingEncoding.GetCharCount(bytes, index, count);
[ExcludeFromCodeCoverage]
public override int GetCharCount(byte[] bytes) => _underlyingEncoding.GetCharCount(bytes);
public override int GetCharCount(byte[] bytes) => underlyingEncoding.GetCharCount(bytes);
[ExcludeFromCodeCoverage]
public override int GetChars(
@@ -112,39 +105,39 @@ internal class NoPreambleEncoding : Encoding
int byteCount,
char[] chars,
int charIndex
) => _underlyingEncoding.GetChars(bytes, byteIndex, byteCount, chars, charIndex);
) => underlyingEncoding.GetChars(bytes, byteIndex, byteCount, chars, charIndex);
[ExcludeFromCodeCoverage]
public override char[] GetChars(byte[] bytes) => _underlyingEncoding.GetChars(bytes);
public override char[] GetChars(byte[] bytes) => underlyingEncoding.GetChars(bytes);
[ExcludeFromCodeCoverage]
public override char[] GetChars(byte[] bytes, int index, int count) =>
_underlyingEncoding.GetChars(bytes, index, count);
underlyingEncoding.GetChars(bytes, index, count);
[ExcludeFromCodeCoverage]
public override string GetString(byte[] bytes) => _underlyingEncoding.GetString(bytes);
public override string GetString(byte[] bytes) => underlyingEncoding.GetString(bytes);
[ExcludeFromCodeCoverage]
public override string GetString(byte[] bytes, int index, int count) =>
_underlyingEncoding.GetString(bytes, index, count);
underlyingEncoding.GetString(bytes, index, count);
[ExcludeFromCodeCoverage]
public override int GetMaxByteCount(int charCount) =>
_underlyingEncoding.GetMaxByteCount(charCount);
underlyingEncoding.GetMaxByteCount(charCount);
[ExcludeFromCodeCoverage]
public override int GetMaxCharCount(int byteCount) =>
_underlyingEncoding.GetMaxCharCount(byteCount);
underlyingEncoding.GetMaxCharCount(byteCount);
[ExcludeFromCodeCoverage]
public override bool IsAlwaysNormalized(NormalizationForm form) =>
_underlyingEncoding.IsAlwaysNormalized(form);
underlyingEncoding.IsAlwaysNormalized(form);
[ExcludeFromCodeCoverage]
public override Encoder GetEncoder() => _underlyingEncoding.GetEncoder();
public override Encoder GetEncoder() => underlyingEncoding.GetEncoder();
[ExcludeFromCodeCoverage]
public override Decoder GetDecoder() => _underlyingEncoding.GetDecoder();
public override Decoder GetDecoder() => underlyingEncoding.GetDecoder();
[ExcludeFromCodeCoverage]
public override object Clone() => new NoPreambleEncoding((Encoding)base.Clone());

View File

@@ -6,45 +6,30 @@ using CliFx.Utils.Extensions;
namespace CliFx.Utils;
internal class StackFrameParameter
internal class StackFrameParameter(string type, string? name)
{
public string Type { get; }
public string Type { get; } = type;
public string? Name { get; }
public StackFrameParameter(string type, string? name)
{
Type = type;
Name = name;
}
public string? Name { get; } = name;
}
internal partial class StackFrame
internal partial class StackFrame(
string parentType,
string methodName,
IReadOnlyList<StackFrameParameter> parameters,
string? filePath,
string? lineNumber
)
{
public string ParentType { get; }
public string ParentType { get; } = parentType;
public string MethodName { get; }
public string MethodName { get; } = methodName;
public IReadOnlyList<StackFrameParameter> Parameters { get; }
public IReadOnlyList<StackFrameParameter> Parameters { get; } = parameters;
public string? FilePath { get; }
public string? FilePath { get; } = filePath;
public string? LineNumber { get; }
public StackFrame(
string parentType,
string methodName,
IReadOnlyList<StackFrameParameter> parameters,
string? filePath,
string? lineNumber
)
{
ParentType = parentType;
MethodName = methodName;
Parameters = parameters;
FilePath = filePath;
LineNumber = lineNumber;
}
public string? LineNumber { get; } = lineNumber;
}
internal partial class StackFrame