This commit is contained in:
Tyrrrz
2024-05-20 22:42:04 +03:00
parent a5a4ad05a0
commit 30bc1d3330
51 changed files with 236 additions and 275 deletions

View File

@@ -9,15 +9,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Basic.Reference.Assemblies.Net80" Version="1.4.5" />
<PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<PackageReference Include="Basic.Reference.Assemblies.Net80" Version="1.7.2" />
<PackageReference Include="coverlet.collector" Version="6.0.2" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.28.2" 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" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageReference Include="xunit" Version="2.6.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageReference Include="xunit" Version="2.8.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>

View File

@@ -13,8 +13,7 @@ public class GeneralSpecs
{
// Arrange
var analyzers = typeof(AnalyzerBase)
.Assembly
.GetTypes()
.Assembly.GetTypes()
.Where(t => !t.IsAbstract && t.IsAssignableTo(typeof(DiagnosticAnalyzer)))
.Select(t => (DiagnosticAnalyzer)Activator.CreateInstance(t)!)
.ToArray();

View File

@@ -30,8 +30,7 @@ internal class AnalyzerAssertions(DiagnosticAnalyzer analyzer)
// Get default CliFx namespaces
var defaultCliFxNamespaces = typeof(ICommand)
.Assembly
.GetTypes()
.Assembly.GetTypes()
.Where(t => t.IsPublic)
.Select(t => t.Namespace)
.Distinct()
@@ -54,10 +53,9 @@ internal class AnalyzerAssertions(DiagnosticAnalyzer analyzer)
var compilation = CSharpCompilation.Create(
"CliFxTests_DynamicAssembly_" + Guid.NewGuid(),
[ast],
Net80
.References
.All
.Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)),
Net80.References.All.Append(
MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)
),
// DLL to avoid having to define the Main() method
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);
@@ -105,8 +103,7 @@ internal class AnalyzerAssertions(DiagnosticAnalyzer analyzer)
== expectedDiagnosticIds.Length;
Execute
.Assertion
.ForCondition(isSuccessfulAssertion)
.Assertion.ForCondition(isSuccessfulAssertion)
.FailWith(() =>
{
var buffer = new StringBuilder();
@@ -150,8 +147,7 @@ internal class AnalyzerAssertions(DiagnosticAnalyzer analyzer)
var isSuccessfulAssertion = !producedDiagnostics.Any();
Execute
.Assertion
.ForCondition(isSuccessfulAssertion)
.Assertion.ForCondition(isSuccessfulAssertion)
.FailWith(() =>
{
var buffer = new StringBuilder();

View File

@@ -17,11 +17,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.28.2" 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" />
<PackageReference Include="PolyShim" Version="1.8.0" PrivateAssets="all" />
<PackageReference Include="PolyShim" Version="1.10.0" PrivateAssets="all" />
</ItemGroup>
</Project>

View File

@@ -26,8 +26,8 @@ public class CommandMustBeAnnotatedAnalyzer()
if (type.IsAbstract)
return;
var implementsCommandInterface = type.AllInterfaces.Any(
i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
var implementsCommandInterface = type.AllInterfaces.Any(i =>
i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
);
var hasCommandAttribute = type.GetAttributes()

View File

@@ -24,8 +24,8 @@ public class CommandMustImplementInterfaceAnalyzer()
.Select(a => a.AttributeClass)
.Any(c => c.DisplayNameMatches(SymbolNames.CliFxCommandAttribute));
var implementsCommandInterface = type.AllInterfaces.Any(
i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
var implementsCommandInterface = type.AllInterfaces.Any(i =>
i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
);
// If the attribute is present, but the interface is not implemented,

View File

@@ -32,8 +32,7 @@ internal partial class CommandOptionSymbol
private static AttributeData? TryGetOptionAttribute(IPropertySymbol property) =>
property
.GetAttributes()
.FirstOrDefault(
a =>
.FirstOrDefault(a =>
a.AttributeClass?.DisplayNameMatches(SymbolNames.CliFxCommandOptionAttribute)
== true
);
@@ -46,35 +45,30 @@ internal partial class CommandOptionSymbol
var name =
attribute
.ConstructorArguments
.Where(a => a.Type?.SpecialType == SpecialType.System_String)
.ConstructorArguments.Where(a => a.Type?.SpecialType == SpecialType.System_String)
.Select(a => a.Value)
.FirstOrDefault() as string;
var shortName =
attribute
.ConstructorArguments
.Where(a => a.Type?.SpecialType == SpecialType.System_Char)
.ConstructorArguments.Where(a => a.Type?.SpecialType == SpecialType.System_Char)
.Select(a => a.Value)
.FirstOrDefault() as char?;
var isRequired =
attribute
.NamedArguments
.Where(a => a.Key == "IsRequired")
.NamedArguments.Where(a => a.Key == "IsRequired")
.Select(a => a.Value.Value)
.FirstOrDefault() as bool?;
var converter = attribute
.NamedArguments
.Where(a => a.Key == "Converter")
.NamedArguments.Where(a => a.Key == "Converter")
.Select(a => a.Value.Value)
.Cast<ITypeSymbol?>()
.FirstOrDefault();
var validators = attribute
.NamedArguments
.Where(a => a.Key == "Validators")
.NamedArguments.Where(a => a.Key == "Validators")
.SelectMany(a => a.Value.Values)
.Select(c => c.Value)
.Cast<ITypeSymbol>()

View File

@@ -32,8 +32,7 @@ internal partial class CommandParameterSymbol
private static AttributeData? TryGetParameterAttribute(IPropertySymbol property) =>
property
.GetAttributes()
.FirstOrDefault(
a =>
.FirstOrDefault(a =>
a.AttributeClass?.DisplayNameMatches(SymbolNames.CliFxCommandParameterAttribute)
== true
);
@@ -48,28 +47,24 @@ internal partial class CommandParameterSymbol
var name =
attribute
.NamedArguments
.Where(a => a.Key == "Name")
.NamedArguments.Where(a => a.Key == "Name")
.Select(a => a.Value.Value)
.FirstOrDefault() as string;
var isRequired =
attribute
.NamedArguments
.Where(a => a.Key == "IsRequired")
.NamedArguments.Where(a => a.Key == "IsRequired")
.Select(a => a.Value.Value)
.FirstOrDefault() as bool?;
var converter = attribute
.NamedArguments
.Where(a => a.Key == "Converter")
.NamedArguments.Where(a => a.Key == "Converter")
.Select(a => a.Value.Value)
.Cast<ITypeSymbol?>()
.FirstOrDefault();
var validators = attribute
.NamedArguments
.Where(a => a.Key == "Validators")
.NamedArguments.Where(a => a.Key == "Validators")
.SelectMany(a => a.Value.Values)
.Select(c => c.Value)
.Cast<ITypeSymbol>()

View File

@@ -29,10 +29,9 @@ public class OptionMustBeInsideCommandAnalyzer()
if (!CommandOptionSymbol.IsOptionProperty(property))
return;
var isInsideCommand = property
.ContainingType
.AllInterfaces
.Any(i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
var isInsideCommand = property.ContainingType.AllInterfaces.Any(i =>
i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
);
if (!isInsideCommand)
{

View File

@@ -34,8 +34,7 @@ public class OptionMustHaveUniqueNameAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -33,8 +33,7 @@ public class OptionMustHaveUniqueShortNameAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -28,13 +28,11 @@ public class OptionMustHaveValidConverterAnalyzer()
return;
var converterValueType = option
.ConverterType
.GetBaseTypes()
.FirstOrDefault(
t => t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass)
.ConverterType.GetBaseTypes()
.FirstOrDefault(t =>
t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass)
)
?.TypeArguments
.FirstOrDefault();
?.TypeArguments.FirstOrDefault();
// Value returned by the converter must be assignable to the property type
var isCompatible =
@@ -45,9 +43,10 @@ public class OptionMustHaveValidConverterAnalyzer()
? context.Compilation.IsAssignable(converterValueType, property.Type)
// Non-scalar (assume we can handle all IEnumerable types for simplicity)
: property.Type.TryGetEnumerableUnderlyingType() is { } enumerableUnderlyingType
&& context
.Compilation
.IsAssignable(converterValueType, enumerableUnderlyingType)
&& context.Compilation.IsAssignable(
converterValueType,
enumerableUnderlyingType
)
);
if (!isCompatible)

View File

@@ -28,12 +28,10 @@ public class OptionMustHaveValidValidatorsAnalyzer()
{
var validatorValueType = validatorType
.GetBaseTypes()
.FirstOrDefault(
t =>
.FirstOrDefault(t =>
t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingValidatorClass)
)
?.TypeArguments
.FirstOrDefault();
?.TypeArguments.FirstOrDefault();
// Value passed to the validator must be assignable from the property type
var isCompatible =

View File

@@ -29,10 +29,9 @@ public class ParameterMustBeInsideCommandAnalyzer()
if (!CommandParameterSymbol.IsParameterProperty(property))
return;
var isInsideCommand = property
.ContainingType
.AllInterfaces
.Any(i => i.DisplayNameMatches(SymbolNames.CliFxCommandInterface));
var isInsideCommand = property.ContainingType.AllInterfaces.Any(i =>
i.DisplayNameMatches(SymbolNames.CliFxCommandInterface)
);
if (!isInsideCommand)
{

View File

@@ -32,8 +32,7 @@ public class ParameterMustBeLastIfNonRequiredAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -32,8 +32,7 @@ public class ParameterMustBeLastIfNonScalarAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -32,8 +32,7 @@ public class ParameterMustBeSingleIfNonRequiredAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -32,8 +32,7 @@ public class ParameterMustBeSingleIfNonScalarAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -34,8 +34,7 @@ public class ParameterMustHaveUniqueNameAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -30,8 +30,7 @@ public class ParameterMustHaveUniqueOrderAnalyzer()
return;
var otherProperties = property
.ContainingType
.GetMembers()
.ContainingType.GetMembers()
.OfType<IPropertySymbol>()
.Where(m => !m.Equals(property))
.ToArray();

View File

@@ -28,13 +28,11 @@ public class ParameterMustHaveValidConverterAnalyzer()
return;
var converterValueType = parameter
.ConverterType
.GetBaseTypes()
.FirstOrDefault(
t => t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass)
.ConverterType.GetBaseTypes()
.FirstOrDefault(t =>
t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingConverterClass)
)
?.TypeArguments
.FirstOrDefault();
?.TypeArguments.FirstOrDefault();
// Value returned by the converter must be assignable to the property type
var isCompatible =
@@ -45,9 +43,10 @@ public class ParameterMustHaveValidConverterAnalyzer()
? context.Compilation.IsAssignable(converterValueType, property.Type)
// Non-scalar (assume we can handle all IEnumerable types for simplicity)
: property.Type.TryGetEnumerableUnderlyingType() is { } enumerableUnderlyingType
&& context
.Compilation
.IsAssignable(converterValueType, enumerableUnderlyingType)
&& context.Compilation.IsAssignable(
converterValueType,
enumerableUnderlyingType
)
);
if (!isCompatible)

View File

@@ -28,12 +28,10 @@ public class ParameterMustHaveValidValidatorsAnalyzer()
{
var validatorValueType = validatorType
.GetBaseTypes()
.FirstOrDefault(
t =>
.FirstOrDefault(t =>
t.ConstructedFrom.DisplayNameMatches(SymbolNames.CliFxBindingValidatorClass)
)
?.TypeArguments
.FirstOrDefault();
?.TypeArguments.FirstOrDefault();
// Value passed to the validator must be assignable from the property type
var isCompatible =

View File

@@ -52,8 +52,7 @@ public class SystemConsoleShouldBeAvoidedAnalyzer()
// Check if IConsole is available in scope as an alternative to System.Console
var isConsoleInterfaceAvailable = context
.Node
.Ancestors()
.Node.Ancestors()
.OfType<MethodDeclarationSyntax>()
.SelectMany(m => m.ParameterList.Parameters)
.Select(p => p.Type)

View File

@@ -30,9 +30,8 @@ internal static class RoslynExtensions
}
public static ITypeSymbol? TryGetEnumerableUnderlyingType(this ITypeSymbol type) =>
type.AllInterfaces
.FirstOrDefault(
i =>
type
.AllInterfaces.FirstOrDefault(i =>
i.ConstructedFrom.SpecialType
== SpecialType.System_Collections_Generic_IEnumerable_T
)
@@ -44,8 +43,7 @@ internal static class RoslynExtensions
property
// Can't rely on the RequiredMemberAttribute because it's generated by the compiler, not added by the user,
// so we have to check for the presence of the `required` modifier in the syntax tree instead.
.DeclaringSyntaxReferences
.Select(r => r.GetSyntax())
.DeclaringSyntaxReferences.Select(r => r.GetSyntax())
.OfType<PropertyDeclarationSyntax>()
.SelectMany(p => p.Modifiers)
.Any(m => m.IsKind((SyntaxKind)8447));

View File

@@ -6,12 +6,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.11" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
<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.5" PrivateAssets="all" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.0" />
<PackageReference Include="CSharpier.MsBuild" Version="0.28.2" PrivateAssets="all" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageReference Include="PowerArgs" Version="4.0.3" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
</ItemGroup>

View File

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

View File

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

View File

@@ -9,18 +9,18 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Basic.Reference.Assemblies.Net80" 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.5" PrivateAssets="all" />
<PackageReference Include="Basic.Reference.Assemblies.Net80" Version="1.7.2" />
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="coverlet.collector" Version="6.0.2" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.28.2" 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.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<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.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="PolyShim" Version="1.10.0" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.8.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>

View File

@@ -32,8 +32,7 @@ internal static class DynamicCommandBuilder
// Get default CliFx namespaces
var defaultCliFxNamespaces = typeof(ICommand)
.Assembly
.GetTypes()
.Assembly.GetTypes()
.Where(t => t.IsPublic)
.Select(t => t.Namespace)
.Distinct()
@@ -57,9 +56,9 @@ internal static class DynamicCommandBuilder
"CliFxTests_DynamicAssembly_" + Guid.NewGuid(),
[ast],
Net80
.References
.All
.Append(MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location))
.References.All.Append(
MetadataReference.CreateFromFile(typeof(ICommand).Assembly.Location)
)
.Append(
MetadataReference.CreateFromFile(
typeof(DynamicCommandBuilder).Assembly.Location
@@ -88,8 +87,8 @@ internal static class DynamicCommandBuilder
using var buffer = new MemoryStream();
var emit = compilation.Emit(buffer);
var emitErrors = emit.Diagnostics
.Where(d => d.Severity >= DiagnosticSeverity.Error)
var emitErrors = emit
.Diagnostics.Where(d => d.Severity >= DiagnosticSeverity.Error)
.ToArray();
if (emitErrors.Any())

View File

@@ -13,8 +13,7 @@ internal static class AssertionExtensions
IEnumerable<string> lines
) =>
assertions
.Subject
.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)
.Subject.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)
.Should()
.Equal(lines);
@@ -34,9 +33,7 @@ internal static class AssertionExtensions
if (index < 0)
{
Execute
.Assertion
.FailWith(
Execute.Assertion.FailWith(
$"Expected string '{assertions.Subject}' to contain '{value}' after position {lastIndex}."
);
}

View File

@@ -56,9 +56,7 @@ public class CliApplication(
{
using (console.WithForegroundColor(ConsoleColor.Green))
{
console
.Output
.WriteLine(
console.Output.WriteLine(
$"Attach the debugger to process with ID {ProcessEx.GetCurrentProcessId()} to continue."
);
}

View File

@@ -186,7 +186,8 @@ public partial class CliApplicationBuilder
/// Configures the application to use the specified service provider for activating types.
/// </summary>
public CliApplicationBuilder UseTypeActivator(IServiceProvider serviceProvider) =>
UseTypeActivator(serviceProvider.GetService);
// Null returns are handled by DelegateTypeActivator
UseTypeActivator(serviceProvider.GetService!);
/// <summary>
/// Configures the application to use the specified service provider for activating types.

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks>
<IsPackable>true</IsPackable>
</PropertyGroup>
@@ -21,9 +21,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.28.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="PolyShim" Version="1.8.0" PrivateAssets="all" />
<PackageReference Include="PolyShim" Version="1.10.0" PrivateAssets="all" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
</ItemGroup>

View File

@@ -42,13 +42,15 @@ internal class CommandBinder(ITypeActivator typeActivator)
// Special case for DateTimeOffset
if (targetType == typeof(DateTimeOffset))
{
return DateTimeOffset.Parse(rawValue, _formatProvider);
// Null reference exception will be handled upstream
return DateTimeOffset.Parse(rawValue!, _formatProvider);
}
// Special case for TimeSpan
if (targetType == typeof(TimeSpan))
{
return TimeSpan.Parse(rawValue, _formatProvider);
// Null reference exception will be handled upstream
return TimeSpan.Parse(rawValue!, _formatProvider);
}
// Enum
@@ -143,10 +145,8 @@ internal class CommandBinder(ITypeActivator typeActivator)
try
{
// Non-scalar
var enumerableUnderlyingType = memberSchema
.Property
.Type
.TryGetEnumerableUnderlyingType();
var enumerableUnderlyingType =
memberSchema.Property.Type.TryGetEnumerableUnderlyingType();
if (
enumerableUnderlyingType is not null
@@ -244,8 +244,7 @@ internal class CommandBinder(ITypeActivator typeActivator)
// Ensure there are no unexpected parameters and that all parameters are provided
var remainingParameterInputs = commandInput.Parameters.ToList();
var remainingRequiredParameterSchemas = commandSchema
.Parameters
.Where(p => p.IsRequired)
.Parameters.Where(p => p.IsRequired)
.ToList();
var position = 0;
@@ -298,7 +297,9 @@ internal class CommandBinder(ITypeActivator typeActivator)
throw CliFxException.UserError(
$"""
Missing required parameter(s):
{remainingRequiredParameterSchemas.Select(p => p.GetFormattedIdentifier()).JoinToString(" ")}
{remainingRequiredParameterSchemas
.Select(p => p.GetFormattedIdentifier())
.JoinToString(" ")}
"""
);
}
@@ -313,20 +314,18 @@ internal class CommandBinder(ITypeActivator typeActivator)
// Ensure there are no unrecognized options and that all required options are set
var remainingOptionInputs = commandInput.Options.ToList();
var remainingRequiredOptionSchemas = commandSchema
.Options
.Where(o => o.IsRequired)
.Options.Where(o => o.IsRequired)
.ToList();
foreach (var optionSchema in commandSchema.Options)
{
var optionInputs = commandInput
.Options
.Where(o => optionSchema.MatchesIdentifier(o.Identifier))
.Options.Where(o => optionSchema.MatchesIdentifier(o.Identifier))
.ToArray();
var environmentVariableInput = commandInput
.EnvironmentVariables
.FirstOrDefault(e => optionSchema.MatchesEnvironmentVariable(e.Name));
var environmentVariableInput = commandInput.EnvironmentVariables.FirstOrDefault(e =>
optionSchema.MatchesEnvironmentVariable(e.Name)
);
// Direct input
if (optionInputs.Any())
@@ -376,7 +375,9 @@ internal class CommandBinder(ITypeActivator typeActivator)
throw CliFxException.UserError(
$"""
Missing required option(s):
{remainingRequiredOptionSchemas.Select(o => o.GetFormattedIdentifier()).JoinToString(", ")}
{remainingRequiredOptionSchemas
.Select(o => o.GetFormattedIdentifier())
.JoinToString(", ")}
"""
);
}

View File

@@ -99,9 +99,9 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
}
// Child command usage
var childCommandSchemas = context
.ApplicationSchema
.GetChildCommands(context.CommandSchema.Name);
var childCommandSchemas = context.ApplicationSchema.GetChildCommands(
context.CommandSchema.Name
);
if (childCommandSchemas.Any())
{
@@ -359,8 +359,7 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
private void WriteCommandChildren()
{
var childCommandSchemas = context
.ApplicationSchema
.GetChildCommands(context.CommandSchema.Name)
.ApplicationSchema.GetChildCommands(context.CommandSchema.Name)
.OrderBy(a => a.Name, StringComparer.Ordinal)
.ToArray();
@@ -393,8 +392,7 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
// Child commands of child command
var grandChildCommandSchemas = context
.ApplicationSchema
.GetChildCommands(childCommandSchema.Name)
.ApplicationSchema.GetChildCommands(childCommandSchema.Name)
.OrderBy(c => c.Name, StringComparer.Ordinal)
.ToArray();
@@ -418,8 +416,7 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
ConsoleColor.Cyan,
// Relative to current command (not the parent)
grandChildCommandSchema
.Name
?.Substring(context.CommandSchema.Name?.Length ?? 0)
.Name?.Substring(context.CommandSchema.Name?.Length ?? 0)
.Trim()
);
}

View File

@@ -45,7 +45,7 @@ public class ConsoleWriter : StreamWriter
/// <inheritdoc />
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
public override void Write(char[] buffer) => base.Write(buffer);
public override void Write(char[]? buffer) => base.Write(buffer);
/// <inheritdoc />
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
@@ -147,7 +147,7 @@ public class ConsoleWriter : StreamWriter
/// <inheritdoc />
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]
public override void WriteLine(char[] buffer) => base.WriteLine(buffer);
public override void WriteLine(char[]? buffer) => base.WriteLine(buffer);
/// <inheritdoc />
[ExcludeFromCodeCoverage, MethodImpl(MethodImplOptions.Synchronized)]

View File

@@ -14,7 +14,13 @@ public class DefaultTypeActivator : ITypeActivator
{
try
{
return Activator.CreateInstance(type);
return Activator.CreateInstance(type)
?? throw CliFxException.InternalError(
$"""
Failed to create an instance of type `{type.FullName}`, received <null> instead.
This may be caused by the type's constructor being trimmed away.
"""
);
}
// Only catch MemberAccessException because the constructor can throw for its own reasons too
catch (MemberAccessException ex)

View File

@@ -9,13 +9,9 @@ namespace CliFx.Infrastructure;
public class DelegateTypeActivator(Func<Type, object> createInstance) : ITypeActivator
{
/// <inheritdoc />
public object CreateInstance(Type type)
{
var instance = createInstance(type);
if (instance is null)
{
throw CliFxException.InternalError(
public object CreateInstance(Type type) =>
createInstance(type)
?? throw CliFxException.InternalError(
$"""
Failed to create an instance of type `{type.FullName}`, received <null> instead.
To fix this, ensure that the provided type activator is configured correctly, as it's not expected to return <null>.
@@ -23,7 +19,3 @@ public class DelegateTypeActivator(Func<Type, object> createInstance) : ITypeAct
"""
);
}
return instance;
}
}

View File

@@ -39,9 +39,10 @@ internal partial class ApplicationSchema(IReadOnlyList<CommandSchema> commands)
string.IsNullOrWhiteSpace(parentCommandName)
||
// Otherwise a command is a descendant if it starts with the same name segments
potentialParentCommandSchema
.Name
.StartsWith(parentCommandName + ' ', StringComparison.OrdinalIgnoreCase);
potentialParentCommandSchema.Name.StartsWith(
parentCommandName + ' ',
StringComparison.OrdinalIgnoreCase
);
if (isDescendant)
result.Add(potentialParentCommandSchema);

View File

@@ -86,10 +86,14 @@ internal partial class CommandSchema
type.GetInterfaces()
// Only interfaces implementing ICommand for explicitness
.Where(i => i != typeof(ICommand) && i.IsAssignableTo(typeof(ICommand)))
.SelectMany(
i =>
.SelectMany(i =>
i.GetProperties()
.Where(p => !p.GetMethod.IsAbstract && !p.SetMethod.IsAbstract)
.Where(p =>
p.GetMethod is not null
&& !p.GetMethod.IsAbstract
&& p.SetMethod is not null
&& !p.SetMethod.IsAbstract
)
)
)
.ToArray();

View File

@@ -42,10 +42,11 @@ internal static class CollectionExtensions
public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(
this IDictionary dictionary,
IEqualityComparer<TKey> comparer
) =>
)
where TKey : notnull =>
dictionary
.Cast<DictionaryEntry>()
.ToDictionary(entry => (TKey)entry.Key, entry => (TValue)entry.Value, comparer);
.ToDictionary(entry => (TKey)entry.Key, entry => (TValue)entry.Value!, comparer);
public static Array ToNonGenericArray<T>(this IEnumerable<T> source, Type elementType)
{

View File

@@ -10,8 +10,7 @@ internal static class PropertyExtensions
// Match attribute by name to avoid depending on .NET 7.0+ and to allow polyfilling
propertyInfo
.GetCustomAttributes()
.Any(
a =>
.Any(a =>
string.Equals(
a.GetType().FullName,
"System.Runtime.CompilerServices.RequiredMemberAttribute",

View File

@@ -15,7 +15,7 @@ internal static class StringExtensions
public static string JoinToString<T>(this IEnumerable<T> source, string separator) =>
string.Join(separator, source);
public static string ToString(
public static string? ToString(
this object obj,
IFormatProvider? formatProvider = null,
string? format = null