Merge branch 'master' into aot

This commit is contained in:
Tyrrrz
2024-05-27 20:57:50 +03:00
51 changed files with 235 additions and 274 deletions

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ public class OptionMustHaveNameOrShortNameAnalyzerSpecs
{ {
[CommandOption(null)] [CommandOption(null)]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -40,7 +40,7 @@ public class OptionMustHaveNameOrShortNameAnalyzerSpecs
{ {
[CommandOption("foo")] [CommandOption("foo")]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -60,7 +60,7 @@ public class OptionMustHaveNameOrShortNameAnalyzerSpecs
{ {
[CommandOption('f')] [CommandOption('f')]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -79,7 +79,7 @@ public class OptionMustHaveNameOrShortNameAnalyzerSpecs
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;

View File

@@ -19,10 +19,10 @@ public class OptionMustHaveUniqueNameAnalyzerSpecs
{ {
[CommandOption("foo")] [CommandOption("foo")]
public string? Foo { get; init; } public string? Foo { get; init; }
[CommandOption("foo")] [CommandOption("foo")]
public string? Bar { get; init; } public string? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -42,10 +42,10 @@ public class OptionMustHaveUniqueNameAnalyzerSpecs
{ {
[CommandOption("foo")] [CommandOption("foo")]
public string? Foo { get; init; } public string? Foo { get; init; }
[CommandOption("bar")] [CommandOption("bar")]
public string? Bar { get; init; } public string? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -65,7 +65,7 @@ public class OptionMustHaveUniqueNameAnalyzerSpecs
{ {
[CommandOption('f')] [CommandOption('f')]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -84,7 +84,7 @@ public class OptionMustHaveUniqueNameAnalyzerSpecs
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;

View File

@@ -20,10 +20,10 @@ public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{ {
[CommandOption('f')] [CommandOption('f')]
public string? Foo { get; init; } public string? Foo { get; init; }
[CommandOption('f')] [CommandOption('f')]
public string? Bar { get; init; } public string? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -43,10 +43,10 @@ public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{ {
[CommandOption('f')] [CommandOption('f')]
public string? Foo { get; init; } public string? Foo { get; init; }
[CommandOption('b')] [CommandOption('b')]
public string? Bar { get; init; } public string? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -66,10 +66,10 @@ public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{ {
[CommandOption('f')] [CommandOption('f')]
public string? Foo { get; init; } public string? Foo { get; init; }
[CommandOption('F')] [CommandOption('F')]
public string? Bar { get; init; } public string? Bar { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -89,7 +89,7 @@ public class OptionMustHaveUniqueShortNameAnalyzerSpecs
{ {
[CommandOption("foo")] [CommandOption("foo")]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -108,7 +108,7 @@ public class OptionMustHaveUniqueShortNameAnalyzerSpecs
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;

View File

@@ -19,7 +19,7 @@ public class OptionMustHaveValidNameAnalyzerSpecs
{ {
[CommandOption("f")] [CommandOption("f")]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -39,7 +39,7 @@ public class OptionMustHaveValidNameAnalyzerSpecs
{ {
[CommandOption("1foo")] [CommandOption("1foo")]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -59,7 +59,7 @@ public class OptionMustHaveValidNameAnalyzerSpecs
{ {
[CommandOption("foo")] [CommandOption("foo")]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -79,7 +79,7 @@ public class OptionMustHaveValidNameAnalyzerSpecs
{ {
[CommandOption('f')] [CommandOption('f')]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -98,7 +98,7 @@ public class OptionMustHaveValidNameAnalyzerSpecs
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;

View File

@@ -20,7 +20,7 @@ public class OptionMustHaveValidShortNameAnalyzerSpecs
{ {
[CommandOption('1')] [CommandOption('1')]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -40,7 +40,7 @@ public class OptionMustHaveValidShortNameAnalyzerSpecs
{ {
[CommandOption('f')] [CommandOption('f')]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -60,7 +60,7 @@ public class OptionMustHaveValidShortNameAnalyzerSpecs
{ {
[CommandOption("foo")] [CommandOption("foo")]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -79,7 +79,7 @@ public class OptionMustHaveValidShortNameAnalyzerSpecs
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;

View File

@@ -19,13 +19,13 @@ public class OptionMustHaveValidValidatorsAnalyzerSpecs
{ {
public void Validate(string value) {} public void Validate(string value) {}
} }
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
[CommandOption("foo", Validators = new[] { typeof(MyValidator) })] [CommandOption("foo", Validators = new[] { typeof(MyValidator) })]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -44,13 +44,13 @@ public class OptionMustHaveValidValidatorsAnalyzerSpecs
{ {
public override BindingValidationError Validate(int value) => Ok(); public override BindingValidationError Validate(int value) => Ok();
} }
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
[CommandOption("foo", Validators = new[] { typeof(MyValidator) })] [CommandOption("foo", Validators = new[] { typeof(MyValidator) })]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -69,13 +69,13 @@ public class OptionMustHaveValidValidatorsAnalyzerSpecs
{ {
public override BindingValidationError Validate(string value) => Ok(); public override BindingValidationError Validate(string value) => Ok();
} }
[Command] [Command]
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
[CommandOption("foo", Validators = new[] { typeof(MyValidator) })] [CommandOption("foo", Validators = new[] { typeof(MyValidator) })]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -95,7 +95,7 @@ public class OptionMustHaveValidValidatorsAnalyzerSpecs
{ {
[CommandOption("foo")] [CommandOption("foo")]
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;
@@ -114,7 +114,7 @@ public class OptionMustHaveValidValidatorsAnalyzerSpecs
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
public string? Foo { get; init; } public string? Foo { get; init; }
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;

View File

@@ -103,7 +103,7 @@ public class SystemConsoleShouldBeAvoidedAnalyzerSpecs
public class MyCommand : ICommand public class MyCommand : ICommand
{ {
public void SomeOtherMethod() => Console.WriteLine("Test"); public void SomeOtherMethod() => Console.WriteLine("Test");
public ValueTask ExecuteAsync(IConsole console) => default; public ValueTask ExecuteAsync(IConsole console) => default;
} }
"""; """;

View File

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

View File

@@ -17,11 +17,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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 --> <!-- Make sure to target the lowest possible version of the compiler for wider support -->
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.0.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis" Version="3.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" PrivateAssets="all" />
<PackageReference Include="PolyShim" Version="1.8.0" PrivateAssets="all" /> <PackageReference Include="PolyShim" Version="1.10.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
</PropertyGroup> </PropertyGroup>
<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.Extensions.DependencyInjection" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup> </ItemGroup>

View File

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

View File

@@ -9,18 +9,18 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Basic.Reference.Assemblies.Net80" Version="1.4.5" /> <PackageReference Include="Basic.Reference.Assemblies.Net80" Version="1.7.2" />
<PackageReference Include="CliWrap" Version="3.6.4" /> <PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" /> <PackageReference Include="coverlet.collector" Version="6.0.2" PrivateAssets="all" />
<PackageReference Include="CSharpier.MsBuild" Version="0.26.5" PrivateAssets="all" /> <PackageReference Include="CSharpier.MsBuild" Version="0.28.2" PrivateAssets="all" />
<PackageReference Include="FluentAssertions" Version="6.12.0" /> <PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" PrivateAssets="all" /> <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.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="PolyShim" Version="1.8.0" PrivateAssets="all" /> <PackageReference Include="PolyShim" Version="1.10.0" PrivateAssets="all" />
<PackageReference Include="xunit" Version="2.6.3" /> <PackageReference Include="xunit" Version="2.8.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5" PrivateAssets="all" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

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

View File

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

View File

@@ -56,11 +56,9 @@ public class CliApplication(
{ {
using (console.WithForegroundColor(ConsoleColor.Green)) using (console.WithForegroundColor(ConsoleColor.Green))
{ {
console console.Output.WriteLine(
.Output $"Attach the debugger to process with ID {ProcessEx.GetCurrentProcessId()} to continue."
.WriteLine( );
$"Attach the debugger to process with ID {ProcessEx.GetCurrentProcessId()} to continue."
);
} }
// Try to also launch the debugger ourselves (only works with Visual Studio) // Try to also launch the debugger ourselves (only works with Visual Studio)

View File

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

View File

@@ -23,9 +23,9 @@
</ItemGroup> </ItemGroup>
<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="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'" /> <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
</ItemGroup> </ItemGroup>

View File

@@ -42,13 +42,15 @@ internal class CommandBinder(ITypeActivator typeActivator)
// Special case for DateTimeOffset // Special case for DateTimeOffset
if (targetType == typeof(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 // Special case for TimeSpan
if (targetType == typeof(TimeSpan)) if (targetType == typeof(TimeSpan))
{ {
return TimeSpan.Parse(rawValue, _formatProvider); // Null reference exception will be handled upstream
return TimeSpan.Parse(rawValue!, _formatProvider);
} }
// Enum // Enum
@@ -143,10 +145,8 @@ internal class CommandBinder(ITypeActivator typeActivator)
try try
{ {
// Non-scalar // Non-scalar
var enumerableUnderlyingType = memberSchema var enumerableUnderlyingType =
.Property memberSchema.Property.Type.TryGetEnumerableUnderlyingType();
.Type
.TryGetEnumerableUnderlyingType();
if ( if (
enumerableUnderlyingType is not null 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 // Ensure there are no unexpected parameters and that all parameters are provided
var remainingParameterInputs = commandInput.Parameters.ToList(); var remainingParameterInputs = commandInput.Parameters.ToList();
var remainingRequiredParameterSchemas = commandSchema var remainingRequiredParameterSchemas = commandSchema
.Parameters .Parameters.Where(p => p.IsRequired)
.Where(p => p.IsRequired)
.ToList(); .ToList();
var position = 0; var position = 0;
@@ -298,7 +297,9 @@ internal class CommandBinder(ITypeActivator typeActivator)
throw CliFxException.UserError( throw CliFxException.UserError(
$""" $"""
Missing required parameter(s): 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 // Ensure there are no unrecognized options and that all required options are set
var remainingOptionInputs = commandInput.Options.ToList(); var remainingOptionInputs = commandInput.Options.ToList();
var remainingRequiredOptionSchemas = commandSchema var remainingRequiredOptionSchemas = commandSchema
.Options .Options.Where(o => o.IsRequired)
.Where(o => o.IsRequired)
.ToList(); .ToList();
foreach (var optionSchema in commandSchema.Options) foreach (var optionSchema in commandSchema.Options)
{ {
var optionInputs = commandInput var optionInputs = commandInput
.Options .Options.Where(o => optionSchema.MatchesIdentifier(o.Identifier))
.Where(o => optionSchema.MatchesIdentifier(o.Identifier))
.ToArray(); .ToArray();
var environmentVariableInput = commandInput var environmentVariableInput = commandInput.EnvironmentVariables.FirstOrDefault(e =>
.EnvironmentVariables optionSchema.MatchesEnvironmentVariable(e.Name)
.FirstOrDefault(e => optionSchema.MatchesEnvironmentVariable(e.Name)); );
// Direct input // Direct input
if (optionInputs.Any()) if (optionInputs.Any())
@@ -376,7 +375,9 @@ internal class CommandBinder(ITypeActivator typeActivator)
throw CliFxException.UserError( throw CliFxException.UserError(
$""" $"""
Missing required option(s): 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 // Child command usage
var childCommandSchemas = context var childCommandSchemas = context.ApplicationSchema.GetChildCommands(
.ApplicationSchema context.CommandSchema.Name
.GetChildCommands(context.CommandSchema.Name); );
if (childCommandSchemas.Any()) if (childCommandSchemas.Any())
{ {
@@ -359,8 +359,7 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
private void WriteCommandChildren() private void WriteCommandChildren()
{ {
var childCommandSchemas = context var childCommandSchemas = context
.ApplicationSchema .ApplicationSchema.GetChildCommands(context.CommandSchema.Name)
.GetChildCommands(context.CommandSchema.Name)
.OrderBy(a => a.Name, StringComparer.Ordinal) .OrderBy(a => a.Name, StringComparer.Ordinal)
.ToArray(); .ToArray();
@@ -393,8 +392,7 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
// Child commands of child command // Child commands of child command
var grandChildCommandSchemas = context var grandChildCommandSchemas = context
.ApplicationSchema .ApplicationSchema.GetChildCommands(childCommandSchema.Name)
.GetChildCommands(childCommandSchema.Name)
.OrderBy(c => c.Name, StringComparer.Ordinal) .OrderBy(c => c.Name, StringComparer.Ordinal)
.ToArray(); .ToArray();
@@ -418,8 +416,7 @@ internal class HelpConsoleFormatter(ConsoleWriter consoleWriter, HelpContext con
ConsoleColor.Cyan, ConsoleColor.Cyan,
// Relative to current command (not the parent) // Relative to current command (not the parent)
grandChildCommandSchema grandChildCommandSchema
.Name .Name?.Substring(context.CommandSchema.Name?.Length ?? 0)
?.Substring(context.CommandSchema.Name?.Length ?? 0)
.Trim() .Trim()
); );
} }

View File

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

View File

@@ -14,7 +14,13 @@ public class DefaultTypeActivator : ITypeActivator
{ {
try 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 // Only catch MemberAccessException because the constructor can throw for its own reasons too
catch (MemberAccessException ex) catch (MemberAccessException ex)

View File

@@ -9,21 +9,13 @@ namespace CliFx.Infrastructure;
public class DelegateTypeActivator(Func<Type, object> createInstance) : ITypeActivator public class DelegateTypeActivator(Func<Type, object> createInstance) : ITypeActivator
{ {
/// <inheritdoc /> /// <inheritdoc />
public object CreateInstance(Type type) public object CreateInstance(Type type) =>
{ createInstance(type)
var instance = createInstance(type); ?? throw CliFxException.InternalError(
$"""
if (instance is null) 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>.
throw CliFxException.InternalError( If you are relying on a dependency container, this error may indicate that the specified type has not been registered.
$""" """
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>.
If you are relying on a dependency container, this error may indicate that the specified type has not been registered.
"""
);
}
return instance;
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,33 +41,33 @@ internal partial class StackFrame
private static readonly Regex Pattern = private static readonly Regex Pattern =
new( new(
$$""" $$"""
^ ^
{{Space}}* {{Space}}*
\w+ {{Space}}+ \w+ {{Space}}+
(?<frame> (?<frame>
(?<type> {{NotSpace}}+ ) \. (?<type> {{NotSpace}}+ ) \.
(?<method> {{NotSpace}}+? ) {{Space}}* (?<method> {{NotSpace}}+? ) {{Space}}*
(?<params> \( ( {{Space}}* \) (?<params> \( ( {{Space}}* \)
| (?<pt> .+?) {{Space}}+ (?<pn> .+?) | (?<pt> .+?) {{Space}}+ (?<pn> .+?)
(, {{Space}}* (?<pt> .+?) {{Space}}+ (?<pn> .+?) )* \) ) ) (, {{Space}}* (?<pt> .+?) {{Space}}+ (?<pn> .+?) )* \) ) )
( {{Space}}+ ( {{Space}}+
( # Microsoft .NET stack traces ( # Microsoft .NET stack traces
\w+ {{Space}}+ \w+ {{Space}}+
(?<file> ( [a-z] \: # Windows rooted path starting with a drive letter (?<file> ( [a-z] \: # Windows rooted path starting with a drive letter
| / ) # Unix rooted path starting with a forward-slash | / ) # Unix rooted path starting with a forward-slash
.+? ) .+? )
\: \w+ {{Space}}+ \: \w+ {{Space}}+
(?<line> [0-9]+ ) \p{P}? (?<line> [0-9]+ ) \p{P}?
| # Mono stack traces | # Mono stack traces
\[0x[0-9a-f]+\] {{Space}}+ \w+ {{Space}}+ \[0x[0-9a-f]+\] {{Space}}+ \w+ {{Space}}+
<(?<file> [^>]+ )> <(?<file> [^>]+ )>
:(?<line> [0-9]+ ) :(?<line> [0-9]+ )
) )
)? )?
) )
\s* \s*
$ $
""", """,
RegexOptions.IgnoreCase RegexOptions.IgnoreCase
| RegexOptions.Multiline | RegexOptions.Multiline
| RegexOptions.ExplicitCapture | RegexOptions.ExplicitCapture