Upstream Tyrrrz/CliFx state of the touched files before 9ef5dcb635
This commit is contained in:
commit
e3e64a40e4
6 files changed
+2165
No files matched your search
@@ -0,0 +1,80 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CliFx.Generators.Utils;
|
||||
using CliFx.Generators.Utils.Extensions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace CliFx.Generators.Binding;
|
||||
|
||||
internal partial record CommandSymbol(
|
||||
INamedTypeSymbol Type,
|
||||
string? Name,
|
||||
string? Description,
|
||||
IReadOnlyList<CommandInputSymbol> Inputs
|
||||
)
|
||||
{
|
||||
public bool IsDefault => string.IsNullOrWhiteSpace(Name);
|
||||
|
||||
public IReadOnlyList<CommandParameterSymbol> Parameters { get; } =
|
||||
Inputs.OfType<CommandParameterSymbol>().ToArray();
|
||||
|
||||
public IReadOnlyList<CommandOptionSymbol> Options { get; } =
|
||||
Inputs.OfType<CommandOptionSymbol>().ToArray();
|
||||
|
||||
public override string ToString() => !string.IsNullOrWhiteSpace(Name) ? Name : "<default>";
|
||||
}
|
||||
|
||||
internal partial record CommandSymbol
|
||||
{
|
||||
internal static CommandSymbol? TryResolve(INamedTypeSymbol type, DiagnosticReporter diagnostics)
|
||||
{
|
||||
// Must have the [Command] attribute
|
||||
var attribute = type.TryGetAttribute("CliFx.Binding.CommandAttribute");
|
||||
if (attribute is null)
|
||||
{
|
||||
// Shouldn't happen when called by the generator since it filters types by attribute,
|
||||
// so not worth the effort to produce a diagnostic.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Must be partial by itself, along with all containing types
|
||||
foreach (var declaration in type.GetDeclarations().ToArray())
|
||||
{
|
||||
if (!declaration.IsFullyPartial())
|
||||
{
|
||||
diagnostics.Report(
|
||||
DiagnosticDescriptors.CommandTypeMustBePartial,
|
||||
declaration.Identifier.GetLocation(),
|
||||
type.Name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Must implement ICommand
|
||||
if (!type.Implements("CliFx.ICommand"))
|
||||
{
|
||||
diagnostics.Report(
|
||||
DiagnosticDescriptors.CommandTypeMustImplementICommand,
|
||||
type.Locations.FirstOrDefault(),
|
||||
type.Name
|
||||
);
|
||||
}
|
||||
|
||||
var name =
|
||||
attribute?.NamedArguments.FirstOrDefault(a => a.Key == "Name").Value.Value as string
|
||||
?? attribute
|
||||
?.ConstructorArguments.Where(a => a.Type?.SpecialType == SpecialType.System_String)
|
||||
.Select(a => a.Value as string)
|
||||
.FirstOrDefault();
|
||||
|
||||
var description =
|
||||
attribute?.NamedArguments.FirstOrDefault(a => a.Key == "Description").Value.Value
|
||||
as string;
|
||||
|
||||
var properties = type.GetProperties().ToArray();
|
||||
var parameters = CommandParameterSymbol.Resolve(properties, diagnostics);
|
||||
var options = CommandOptionSymbol.Resolve(properties, diagnostics);
|
||||
|
||||
return new CommandSymbol(type, name, description, [.. parameters, .. options]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CliFx.Generators.Binding;
|
||||
using CliFx.Generators.Utils;
|
||||
using CliFx.Generators.Utils.Extensions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using PowerKit.Extensions;
|
||||
|
||||
namespace CliFx.Generators.Emitters;
|
||||
|
||||
internal static class CommandDescriptor
|
||||
{
|
||||
private static string EmitValidators(
|
||||
IReadOnlyList<INamedTypeSymbol> validatorTypes,
|
||||
string propertyTypeName
|
||||
)
|
||||
{
|
||||
if (validatorTypes.Count == 0)
|
||||
return $"global::System.Array.Empty<global::CliFx.Activation.IInputValidator<{propertyTypeName}>>()";
|
||||
|
||||
// lang=csharp
|
||||
return $$"""
|
||||
new global::CliFx.Activation.IInputValidator<{{propertyTypeName}}>[]
|
||||
{
|
||||
{{string.Join(
|
||||
", ",
|
||||
validatorTypes.Select(v => $"new {v.GetGloballyQualifiedName()}()")
|
||||
)}}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
private static string? TryEmitDefaultScalarConverter(ITypeSymbol type)
|
||||
{
|
||||
// Object
|
||||
if (type.SpecialType == SpecialType.System_Object)
|
||||
return "new global::CliFx.Activation.StringScalarInputConverter()";
|
||||
|
||||
// String
|
||||
if (type.SpecialType == SpecialType.System_String)
|
||||
return "new global::CliFx.Activation.StringScalarInputConverter()";
|
||||
|
||||
// Bool
|
||||
if (type.SpecialType == SpecialType.System_Boolean)
|
||||
return "new global::CliFx.Activation.BoolScalarInputConverter()";
|
||||
|
||||
// Enum
|
||||
if (type.TypeKind == TypeKind.Enum)
|
||||
return $"new global::CliFx.Activation.EnumScalarInputConverter<{type.GetGloballyQualifiedName()}>()";
|
||||
|
||||
// Nullable<T>
|
||||
if (type.TryGetNullableUnderlyingType() is { } underlyingType)
|
||||
{
|
||||
var innerConverterCode = TryEmitDefaultScalarConverter(underlyingType);
|
||||
if (innerConverterCode is null)
|
||||
return null;
|
||||
|
||||
return $"global::CliFx.Activation.NullableScalarInputConverter.Create({innerConverterCode})";
|
||||
}
|
||||
|
||||
// Has static Parse(string, IFormatProvider)
|
||||
var parseMethodWithFormatProvider = type.GetMethods()
|
||||
.FirstOrDefault(m =>
|
||||
string.Equals(m.Name, "Parse", StringComparison.Ordinal)
|
||||
&& m.IsStatic
|
||||
&& m.DeclaredAccessibility == Accessibility.Public
|
||||
&& m.Parameters.Length == 2
|
||||
&& m.Parameters[0].Type.SpecialType == SpecialType.System_String
|
||||
&& m.Parameters[1].Type.IsMatchedBy("System.IFormatProvider")
|
||||
&& SymbolEqualityComparer.Default.Equals(m.ReturnType, type)
|
||||
);
|
||||
|
||||
if (parseMethodWithFormatProvider is not null)
|
||||
{
|
||||
return $"""
|
||||
global::CliFx.Activation.DelegateScalarInputConverter.Create(
|
||||
v => {parseMethodWithFormatProvider.GetGloballyQualifiedName()}(
|
||||
v!,
|
||||
global::System.Globalization.CultureInfo.InvariantCulture
|
||||
)
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
// Has static Parse(string)
|
||||
var parseMethod = type.GetMethods()
|
||||
.FirstOrDefault(m =>
|
||||
string.Equals(m.Name, "Parse", StringComparison.Ordinal)
|
||||
&& m.IsStatic
|
||||
&& m.DeclaredAccessibility == Accessibility.Public
|
||||
&& m.Parameters.Length == 1
|
||||
&& m.Parameters[0].Type.SpecialType == SpecialType.System_String
|
||||
&& SymbolEqualityComparer.Default.Equals(m.ReturnType, type)
|
||||
);
|
||||
|
||||
if (parseMethod is not null)
|
||||
{
|
||||
// lang=csharp
|
||||
return $"""
|
||||
global::CliFx.Activation.DelegateScalarInputConverter.Create(
|
||||
v => {parseMethod.GetGloballyQualifiedName()}(v!)
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
// Has ctor(string)
|
||||
if (
|
||||
type is INamedTypeSymbol namedConstructable
|
||||
&& namedConstructable.Constructors.Any(c =>
|
||||
c.DeclaredAccessibility == Accessibility.Public
|
||||
&& c.Parameters.Length == 1
|
||||
&& c.Parameters[0].Type.SpecialType == SpecialType.System_String
|
||||
)
|
||||
)
|
||||
{
|
||||
// lang=csharp
|
||||
return $"""
|
||||
global::CliFx.Activation.DelegateScalarInputConverter.Create(
|
||||
v => new {type.GetGloballyQualifiedName()}(v!)
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? TryEmitDefaultSequenceConverter(
|
||||
ITypeSymbol collectionType,
|
||||
ITypeSymbol elementType
|
||||
)
|
||||
{
|
||||
var elementConverterFragment = TryEmitDefaultScalarConverter(elementType);
|
||||
if (elementConverterFragment is null)
|
||||
return null;
|
||||
|
||||
// Assignable from T[]
|
||||
if (
|
||||
// T[]
|
||||
collectionType is IArrayTypeSymbol
|
||||
|| (
|
||||
collectionType
|
||||
is INamedTypeSymbol
|
||||
{
|
||||
TypeKind: TypeKind.Interface,
|
||||
TypeArguments: [var interfaceElementType],
|
||||
} collectionInterfaceType
|
||||
&& collectionInterfaceType.ConstructedFrom.SpecialType
|
||||
// IEnumerable<T>
|
||||
is SpecialType.System_Collections_Generic_IEnumerable_T
|
||||
// ICollection<T>
|
||||
or SpecialType.System_Collections_Generic_ICollection_T
|
||||
// IList<T>
|
||||
or SpecialType.System_Collections_Generic_IList_T
|
||||
// IReadOnlyCollection<T>
|
||||
or SpecialType.System_Collections_Generic_IReadOnlyCollection_T
|
||||
// IReadOnlyList<T>
|
||||
or SpecialType.System_Collections_Generic_IReadOnlyList_T
|
||||
&& SymbolEqualityComparer.Default.Equals(interfaceElementType, elementType)
|
||||
)
|
||||
)
|
||||
{
|
||||
return $"global::CliFx.Activation.ArraySequenceInputConverter.Create({elementConverterFragment})";
|
||||
}
|
||||
|
||||
// Has ctor(T[])
|
||||
if (
|
||||
collectionType is INamedTypeSymbol collectionNamedType
|
||||
&& collectionNamedType.Constructors.Any(c =>
|
||||
c.DeclaredAccessibility == Accessibility.Public
|
||||
&& c.Parameters.Length == 1
|
||||
&& (
|
||||
// T[]
|
||||
(
|
||||
c.Parameters[0].Type is IArrayTypeSymbol arrayParameterType
|
||||
&& SymbolEqualityComparer.Default.Equals(
|
||||
arrayParameterType.ElementType,
|
||||
elementType
|
||||
)
|
||||
)
|
||||
// Interfaces implemented by T[]
|
||||
|| (
|
||||
c.Parameters[0].Type is INamedTypeSymbol namedParameterType
|
||||
&& namedParameterType.ConstructedFrom.SpecialType
|
||||
// IEnumerable<T>
|
||||
is SpecialType.System_Collections_Generic_IEnumerable_T
|
||||
// ICollection<T>
|
||||
or SpecialType.System_Collections_Generic_ICollection_T
|
||||
// IList<T>
|
||||
or SpecialType.System_Collections_Generic_IList_T
|
||||
// IReadOnlyCollection<T>
|
||||
or SpecialType.System_Collections_Generic_IReadOnlyCollection_T
|
||||
// IReadOnlyList<T>
|
||||
or SpecialType.System_Collections_Generic_IReadOnlyList_T
|
||||
&& SymbolEqualityComparer.Default.Equals(
|
||||
namedParameterType.TypeArguments[0],
|
||||
elementType
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
{
|
||||
// lang=csharp
|
||||
return $"""
|
||||
global::CliFx.Activation.DelegateSequenceInputConverter.Create(
|
||||
global::CliFx.Activation.ArraySequenceInputConverter.Create({elementConverterFragment}),
|
||||
vs => new {collectionType.GetGloballyQualifiedName()}(vs)
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? TryEmitConverter(
|
||||
INamedTypeSymbol? userConverterType,
|
||||
IPropertySymbol property
|
||||
)
|
||||
{
|
||||
if (userConverterType is not null)
|
||||
return $"new {userConverterType.GetGloballyQualifiedName()}()";
|
||||
|
||||
// Sequence-based property
|
||||
if (
|
||||
property.Type.SpecialType != SpecialType.System_String
|
||||
&& property.Type.TryGetEnumerableUnderlyingType() is { } elementType
|
||||
)
|
||||
{
|
||||
return TryEmitDefaultSequenceConverter(property.Type, elementType);
|
||||
}
|
||||
|
||||
// Scalar property
|
||||
return TryEmitDefaultScalarConverter(property.Type);
|
||||
}
|
||||
|
||||
private static string? TryEmitCommandParameterDescriptor(
|
||||
CommandSymbol command,
|
||||
CommandParameterSymbol parameter,
|
||||
DiagnosticReporter diagnostics
|
||||
)
|
||||
{
|
||||
var converterFragment = TryEmitConverter(parameter.ConverterType, parameter.Property);
|
||||
if (converterFragment is null)
|
||||
{
|
||||
diagnostics.Report(
|
||||
DiagnosticDescriptors.CommandInputConverterNotInferrable,
|
||||
parameter.Property.Locations.FirstOrDefault(),
|
||||
parameter.Property.Name,
|
||||
parameter.Property.Type.GetGloballyQualifiedName()
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// lang=csharp
|
||||
return $$"""
|
||||
new global::CliFx.Binding.CommandParameterDescriptor<{{command.Type.GetGloballyQualifiedName()}}, {{parameter.Property.Type.GetGloballyQualifiedName()}}>(
|
||||
new global::CliFx.Binding.PropertyDescriptor<{{command.Type.GetGloballyQualifiedName()}}, {{parameter.Property.Type.GetGloballyQualifiedName()}}>(
|
||||
{{CSharp.Encode(parameter.Property.Name)}},
|
||||
c => c.{{parameter.Property.Name}},
|
||||
(c, v) => c.{{parameter.Property.Name}} = v
|
||||
),
|
||||
{{parameter.Order}},
|
||||
{{CSharp.Encode(parameter.Name)}},
|
||||
{{parameter.IsRequired.ToString().ToLowerInvariant()}},
|
||||
{{CSharp.Encode(parameter.Description)}},
|
||||
{{converterFragment}},
|
||||
{{EmitValidators(
|
||||
parameter.ValidatorTypes,
|
||||
parameter.Property.Type.GetGloballyQualifiedName()
|
||||
)}}
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
private static string? TryEmitCommandOptionDescriptor(
|
||||
CommandSymbol command,
|
||||
CommandOptionSymbol option,
|
||||
DiagnosticReporter diagnostics
|
||||
)
|
||||
{
|
||||
var converterFragment = TryEmitConverter(option.ConverterType, option.Property);
|
||||
if (converterFragment is null)
|
||||
{
|
||||
diagnostics.Report(
|
||||
DiagnosticDescriptors.CommandInputConverterNotInferrable,
|
||||
option.Property.Locations.FirstOrDefault(),
|
||||
option.Property.Name,
|
||||
option.Property.Type.GetGloballyQualifiedName()
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// lang=csharp
|
||||
return $$"""
|
||||
new global::CliFx.Binding.CommandOptionDescriptor<{{command.Type.GetGloballyQualifiedName()}}, {{option.Property.Type.GetGloballyQualifiedName()}}>(
|
||||
new global::CliFx.Binding.PropertyDescriptor<{{command.Type.GetGloballyQualifiedName()}}, {{option.Property.Type.GetGloballyQualifiedName()}}>(
|
||||
{{CSharp.Encode(option.Property.Name)}},
|
||||
c => c.{{option.Property.Name}},
|
||||
(c, v) => c.{{option.Property.Name}} = v
|
||||
),
|
||||
{{CSharp.Encode(option.Name)}},
|
||||
{{CSharp.Encode(option.ShortName)}},
|
||||
{{CSharp.Encode(option.EnvironmentVariable)}},
|
||||
{{option.IsRequired.ToString().ToLowerInvariant()}},
|
||||
{{CSharp.Encode(option.Description)}},
|
||||
{{converterFragment}},
|
||||
{{EmitValidators(
|
||||
option.ValidatorTypes,
|
||||
option.Property.Type.GetGloballyQualifiedName()
|
||||
)}}
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
private static string TryEmitCommandHelpOptionDescriptor(
|
||||
CommandSymbol command,
|
||||
DiagnosticReporter diagnostics
|
||||
)
|
||||
{
|
||||
// Check if the user already implemented a custom help option
|
||||
if (command.Type.Implements("CliFx.ICommandWithHelpOption"))
|
||||
{
|
||||
// Ensure that the user bound their IsHelpRequested property to an option
|
||||
var isHelpRequestedProperty = command
|
||||
.Type.GetProperties()
|
||||
.FirstOrDefault(p =>
|
||||
string.Equals(p.Name, "IsHelpRequested", StringComparison.Ordinal)
|
||||
);
|
||||
|
||||
var hasOptionBinding =
|
||||
isHelpRequestedProperty?.TryGetAttribute("CliFx.Binding.CommandOptionAttribute")
|
||||
is not null;
|
||||
|
||||
if (!hasOptionBinding)
|
||||
{
|
||||
diagnostics.Report(
|
||||
DiagnosticDescriptors.CommandHelpOptionPropertyMustBeBound,
|
||||
isHelpRequestedProperty?.Locations.FirstOrDefault()
|
||||
?? command.Type.Locations.FirstOrDefault(),
|
||||
"IsHelpRequested"
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the help option is being shadowed by an existing option
|
||||
var shadowingOptionByName = command.Options.FirstOrDefault(o =>
|
||||
string.Equals(o.Name, "help", StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
|
||||
var shadowingOptionByShortName = command.Options.FirstOrDefault(o => o.ShortName == 'h');
|
||||
|
||||
var shadowingOption = shadowingOptionByShortName ?? shadowingOptionByName;
|
||||
|
||||
if (shadowingOption is not null)
|
||||
{
|
||||
diagnostics.Report(
|
||||
DiagnosticDescriptors.CommandOptionShadowsConventionalHelpOption,
|
||||
shadowingOption.Property.Locations.FirstOrDefault(),
|
||||
shadowingOption.Property.Name,
|
||||
shadowingOptionByName is not null ? "name" : "short name"
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// lang=csharp
|
||||
return $$"""
|
||||
new global::CliFx.Binding.CommandOptionDescriptor<{{command.Type.GetGloballyQualifiedName()}}, bool>(
|
||||
new global::CliFx.Binding.PropertyDescriptor<{{command.Type.GetGloballyQualifiedName()}}, bool>(
|
||||
{{CSharp.Encode("IsHelpRequested")}},
|
||||
c => c.IsHelpRequested,
|
||||
(c, v) => c.IsHelpRequested = v
|
||||
),
|
||||
{{CSharp.Encode("help")}},
|
||||
{{CSharp.Encode('h')}},
|
||||
null,
|
||||
false,
|
||||
{{CSharp.Encode("Shows help text.")}},
|
||||
new global::CliFx.Activation.BoolScalarInputConverter(),
|
||||
global::System.Array.Empty<global::CliFx.Activation.IInputValidator<bool>>()
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
private static string TryEmitCommandVersionOptionDescriptor(
|
||||
CommandSymbol command,
|
||||
DiagnosticReporter diagnostics
|
||||
)
|
||||
{
|
||||
// Check if the user already implemented a custom version option
|
||||
if (command.Type.Implements("CliFx.ICommandWithVersionOption"))
|
||||
{
|
||||
var isVersionRequestedProperty = command
|
||||
.Type.GetProperties()
|
||||
.FirstOrDefault(p =>
|
||||
string.Equals(p.Name, "IsVersionRequested", StringComparison.Ordinal)
|
||||
);
|
||||
|
||||
var hasOptionBinding =
|
||||
isVersionRequestedProperty?.TryGetAttribute("CliFx.Binding.CommandOptionAttribute")
|
||||
is not null;
|
||||
|
||||
if (!hasOptionBinding)
|
||||
{
|
||||
diagnostics.Report(
|
||||
DiagnosticDescriptors.CommandVersionOptionPropertyMustBeBound,
|
||||
isVersionRequestedProperty?.Locations.FirstOrDefault()
|
||||
?? command.Type.Locations.FirstOrDefault(),
|
||||
"IsVersionRequested"
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only default commands get the conventional version option
|
||||
if (!command.IsDefault)
|
||||
return null;
|
||||
|
||||
// Check if the version option is being shadowed by an existing option
|
||||
var shadowingOptionByName = command.Options.FirstOrDefault(o =>
|
||||
string.Equals(o.Name, "version", StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
|
||||
if (shadowingOptionByName is not null)
|
||||
{
|
||||
diagnostics.Report(
|
||||
DiagnosticDescriptors.CommandOptionShadowsConventionalVersionOption,
|
||||
shadowingOptionByName.Property.Locations.FirstOrDefault(),
|
||||
shadowingOptionByName.Property.Name,
|
||||
"name"
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// lang=csharp
|
||||
return $$"""
|
||||
new global::CliFx.Binding.CommandOptionDescriptor<{{command.Type.GetGloballyQualifiedName()}}, bool>(
|
||||
new global::CliFx.Binding.PropertyDescriptor<{{command.Type.GetGloballyQualifiedName()}}, bool>(
|
||||
{{CSharp.Encode("IsVersionRequested")}},
|
||||
c => c.IsVersionRequested,
|
||||
(c, v) => c.IsVersionRequested = v
|
||||
),
|
||||
{{CSharp.Encode("version")}},
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
{{CSharp.Encode("Shows version information.")}},
|
||||
new global::CliFx.Activation.BoolScalarInputConverter(),
|
||||
global::System.Array.Empty<global::CliFx.Activation.IInputValidator<bool>>()
|
||||
)
|
||||
""";
|
||||
}
|
||||
|
||||
private static string? TryEmitCommandInputDescriptor(
|
||||
CommandSymbol command,
|
||||
CommandInputSymbol input,
|
||||
DiagnosticReporter diagnostics
|
||||
) =>
|
||||
input switch
|
||||
{
|
||||
CommandParameterSymbol parameter => TryEmitCommandParameterDescriptor(
|
||||
command,
|
||||
parameter,
|
||||
diagnostics
|
||||
),
|
||||
CommandOptionSymbol option => TryEmitCommandOptionDescriptor(
|
||||
command,
|
||||
option,
|
||||
diagnostics
|
||||
),
|
||||
_ => throw new InvalidOperationException(
|
||||
$"Unknown input type '{input.GetType().Name}'."
|
||||
),
|
||||
};
|
||||
|
||||
internal static string Emit(CommandSymbol command, DiagnosticReporter diagnostics) =>
|
||||
// lang=csharp
|
||||
$$"""
|
||||
// <auto-generated />
|
||||
|
||||
{{(command.Type.TryGetNamespaceName() is { } namespaceName
|
||||
? $"namespace {namespaceName};"
|
||||
: ""
|
||||
)}}
|
||||
|
||||
{{string.Concat(command.Type.GetContainingTypes().Reverse().Select(t =>
|
||||
// lang=csharp
|
||||
$$"""
|
||||
partial class {{t.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}}
|
||||
{
|
||||
"""
|
||||
))}}
|
||||
|
||||
partial class {{command.Type.Name}}
|
||||
{
|
||||
/// <summary>
|
||||
/// Generated command descriptor for <see cref="{{Xml.Escape(command.Type.GetGloballyQualifiedName())}}" /> ({{Xml.Escape(command.ToString())}}).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
{{string.Join(
|
||||
Environment.NewLine,
|
||||
command.Inputs.Select(i =>
|
||||
// lang=xml
|
||||
$"""
|
||||
/// <item>
|
||||
/// <see cref="{Xml.Escape(i.Property.GetGloballyQualifiedName())}" /> ({Xml.Escape(i.ToString())})
|
||||
/// </item>
|
||||
"""
|
||||
)
|
||||
).NullIfWhiteSpace() ?? "///"}}
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public static global::CliFx.Binding.CommandDescriptor Descriptor { get; } =
|
||||
new global::CliFx.Binding.CommandDescriptor<{{command.Type.GetGloballyQualifiedName()}}>(
|
||||
{{CSharp.Encode(command.Name)}},
|
||||
{{CSharp.Encode(command.Description)}},
|
||||
new global::CliFx.Binding.CommandInputDescriptor[]
|
||||
{
|
||||
{{string.Join(",", command.Inputs
|
||||
.Select(i => TryEmitCommandInputDescriptor(
|
||||
command,
|
||||
i,
|
||||
diagnostics
|
||||
))
|
||||
.Append(
|
||||
TryEmitCommandHelpOptionDescriptor(command, diagnostics) is {} helpOptionDescriptorFragment
|
||||
? helpOptionDescriptorFragment
|
||||
: null
|
||||
)
|
||||
.Append(
|
||||
TryEmitCommandVersionOptionDescriptor(command, diagnostics) is {} versionOptionDescriptorFragment
|
||||
? versionOptionDescriptorFragment
|
||||
: null
|
||||
)
|
||||
.WhereNotNull()
|
||||
)}}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
{{(!command.Type.Implements("CliFx.ICommandWithHelpOption")
|
||||
? // lang=csharp
|
||||
$$"""
|
||||
partial class {{command.Type.Name}} : global::CliFx.ICommandWithHelpOption
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool IsHelpRequested { get; set; }
|
||||
}
|
||||
"""
|
||||
: ""
|
||||
)}}
|
||||
|
||||
{{(command.IsDefault && !command.Type.Implements("CliFx.ICommandWithVersionOption")
|
||||
? // lang=csharp
|
||||
$$"""
|
||||
partial class {{command.Type.Name}} : global::CliFx.ICommandWithVersionOption
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool IsVersionRequested { get; set; }
|
||||
}
|
||||
"""
|
||||
: ""
|
||||
)}}
|
||||
|
||||
{{string.Concat(command.Type.GetContainingTypes().Reverse().Select(_ => "}"))}}
|
||||
""";
|
||||
}
|
||||
Reference in new issue
Block a user