mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
asd
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Linq;
|
||||||
using System.Linq;
|
|
||||||
using CliFx.SourceGeneration.SemanticModel;
|
using CliFx.SourceGeneration.SemanticModel;
|
||||||
using CliFx.SourceGeneration.Utils.Extensions;
|
using CliFx.SourceGeneration.Utils.Extensions;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
@@ -21,13 +20,13 @@ public class CommandSchemaGenerator : IIncrementalGenerator
|
|||||||
(n, _) => n is TypeDeclarationSyntax,
|
(n, _) => n is TypeDeclarationSyntax,
|
||||||
(x, _) =>
|
(x, _) =>
|
||||||
{
|
{
|
||||||
// Predicate ensures that these casts are safe
|
// Predicate above ensures that these casts are safe
|
||||||
var typeDeclarationSyntax = (TypeDeclarationSyntax)x.TargetNode;
|
var commandTypeSyntax = (TypeDeclarationSyntax)x.TargetNode;
|
||||||
var namedTypeSymbol = (INamedTypeSymbol)x.TargetSymbol;
|
var commandTypeSymbol = (INamedTypeSymbol)x.TargetSymbol;
|
||||||
|
|
||||||
// Check if the target type and all its containing types are partial
|
// Check if the target type and all its containing types are partial
|
||||||
if (
|
if (
|
||||||
typeDeclarationSyntax
|
commandTypeSyntax
|
||||||
.AncestorsAndSelf()
|
.AncestorsAndSelf()
|
||||||
.Any(a =>
|
.Any(a =>
|
||||||
a is TypeDeclarationSyntax t
|
a is TypeDeclarationSyntax t
|
||||||
@@ -39,14 +38,14 @@ public class CommandSchemaGenerator : IIncrementalGenerator
|
|||||||
null,
|
null,
|
||||||
Diagnostic.Create(
|
Diagnostic.Create(
|
||||||
DiagnosticDescriptors.CommandMustBePartial,
|
DiagnosticDescriptors.CommandMustBePartial,
|
||||||
typeDeclarationSyntax.Identifier.GetLocation()
|
commandTypeSyntax.Identifier.GetLocation()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the target type implements ICommand
|
// Check if the target type implements ICommand
|
||||||
var hasCommandInterface = namedTypeSymbol.AllInterfaces.Any(i =>
|
var hasCommandInterface = commandTypeSymbol.AllInterfaces.Any(i =>
|
||||||
i.DisplayNameMatches("CliFx.ICommand")
|
i.DisplayNameMatches(KnownSymbolNames.CliFxCommandInterface)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasCommandInterface)
|
if (!hasCommandInterface)
|
||||||
@@ -55,220 +54,22 @@ public class CommandSchemaGenerator : IIncrementalGenerator
|
|||||||
null,
|
null,
|
||||||
Diagnostic.Create(
|
Diagnostic.Create(
|
||||||
DiagnosticDescriptors.CommandMustImplementInterface,
|
DiagnosticDescriptors.CommandMustImplementInterface,
|
||||||
namedTypeSymbol.Locations.First()
|
commandTypeSymbol.Locations.First()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the command name
|
// Resolve the command
|
||||||
var commandAttribute = x.Attributes.First(a =>
|
var commandAttribute = x.Attributes.First(a =>
|
||||||
a.AttributeClass?.DisplayNameMatches(KnownSymbolNames.CliFxCommandAttribute)
|
a.AttributeClass?.DisplayNameMatches(KnownSymbolNames.CliFxCommandAttribute)
|
||||||
== true
|
== true
|
||||||
);
|
);
|
||||||
|
|
||||||
var commandName =
|
var command = CommandSymbol.FromSymbol(commandTypeSymbol, commandAttribute);
|
||||||
commandAttribute.ConstructorArguments.FirstOrDefault().Value as string;
|
|
||||||
|
|
||||||
var commandDescription =
|
// TODO: validate command
|
||||||
commandAttribute
|
|
||||||
.NamedArguments.FirstOrDefault(a =>
|
|
||||||
string.Equals(a.Key, "Description", StringComparison.Ordinal)
|
|
||||||
)
|
|
||||||
.Value.Value as string;
|
|
||||||
|
|
||||||
// Get all parameter inputs
|
return (command, null);
|
||||||
var parameterSymbols = namedTypeSymbol
|
|
||||||
.GetMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Select(p =>
|
|
||||||
{
|
|
||||||
var parameterAttribute = p.GetAttributes()
|
|
||||||
.FirstOrDefault(a =>
|
|
||||||
a.AttributeClass?.DisplayNameMatches(
|
|
||||||
KnownSymbolNames.CliFxCommandParameterAttribute
|
|
||||||
) == true
|
|
||||||
);
|
|
||||||
|
|
||||||
if (parameterAttribute is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var isSequence = false; // TODO
|
|
||||||
|
|
||||||
var order = parameterAttribute.ConstructorArguments.First().Value as int?;
|
|
||||||
|
|
||||||
var isRequired =
|
|
||||||
parameterAttribute
|
|
||||||
.NamedArguments.FirstOrDefault(a =>
|
|
||||||
string.Equals(a.Key, "IsRequired", StringComparison.Ordinal)
|
|
||||||
)
|
|
||||||
.Value.Value as bool?
|
|
||||||
?? true;
|
|
||||||
|
|
||||||
var name =
|
|
||||||
parameterAttribute
|
|
||||||
.NamedArguments.FirstOrDefault(a =>
|
|
||||||
string.Equals(a.Key, "Name", StringComparison.Ordinal)
|
|
||||||
)
|
|
||||||
.Value.Value as string;
|
|
||||||
|
|
||||||
var description =
|
|
||||||
parameterAttribute
|
|
||||||
.NamedArguments.FirstOrDefault(a =>
|
|
||||||
string.Equals(a.Key, "Description", StringComparison.Ordinal)
|
|
||||||
)
|
|
||||||
.Value.Value as string;
|
|
||||||
|
|
||||||
var converter =
|
|
||||||
parameterAttribute
|
|
||||||
.NamedArguments.FirstOrDefault(a =>
|
|
||||||
string.Equals(a.Key, "Converter", StringComparison.Ordinal)
|
|
||||||
)
|
|
||||||
.Value.Value as ITypeSymbol;
|
|
||||||
|
|
||||||
var validators = parameterAttribute
|
|
||||||
.NamedArguments.FirstOrDefault(a =>
|
|
||||||
string.Equals(a.Key, "Validators", StringComparison.Ordinal)
|
|
||||||
)
|
|
||||||
.Value.Values.CastArray<ITypeSymbol>();
|
|
||||||
|
|
||||||
return new CommandParameterSymbol(
|
|
||||||
new PropertyDescriptor(
|
|
||||||
new TypeDescriptor(
|
|
||||||
p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
|
||||||
),
|
|
||||||
p.Name
|
|
||||||
),
|
|
||||||
isSequence,
|
|
||||||
order,
|
|
||||||
isRequired,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
converter
|
|
||||||
?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
|
||||||
?.Pipe(n => new TypeDescriptor(n)),
|
|
||||||
validators
|
|
||||||
.Select(v =>
|
|
||||||
v.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
|
||||||
)
|
|
||||||
.Select(n => new TypeDescriptor(n))
|
|
||||||
.ToArray()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.WhereNotNull()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
// Get all option inputs
|
|
||||||
var optionSymbols = namedTypeSymbol
|
|
||||||
.GetMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Select(p =>
|
|
||||||
{
|
|
||||||
var optionAttribute = p.GetAttributes()
|
|
||||||
.FirstOrDefault(a =>
|
|
||||||
a.AttributeClass?.DisplayNameMatches(
|
|
||||||
KnownSymbolNames.CliFxCommandOptionAttribute
|
|
||||||
) == true
|
|
||||||
);
|
|
||||||
|
|
||||||
if (optionAttribute is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var isSequence = false; // TODO
|
|
||||||
|
|
||||||
var name =
|
|
||||||
optionAttribute
|
|
||||||
.ConstructorArguments.Where(a =>
|
|
||||||
a.Type?.SpecialType == SpecialType.System_String
|
|
||||||
)
|
|
||||||
.Select(a => a.Value)
|
|
||||||
.FirstOrDefault() as string;
|
|
||||||
|
|
||||||
var shortName =
|
|
||||||
optionAttribute
|
|
||||||
.ConstructorArguments.Where(a =>
|
|
||||||
a.Type?.SpecialType == SpecialType.System_Char
|
|
||||||
)
|
|
||||||
.Select(a => a.Value)
|
|
||||||
.FirstOrDefault() as char?;
|
|
||||||
|
|
||||||
var environmentVariable =
|
|
||||||
optionAttribute
|
|
||||||
.NamedArguments.FirstOrDefault(a =>
|
|
||||||
string.Equals(
|
|
||||||
a.Key,
|
|
||||||
"EnvironmentVariable",
|
|
||||||
StringComparison.Ordinal
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.Value.Value as string;
|
|
||||||
|
|
||||||
var isRequired =
|
|
||||||
optionAttribute
|
|
||||||
.NamedArguments.Where(a => a.Key == "IsRequired")
|
|
||||||
.Select(a => a.Value.Value)
|
|
||||||
.FirstOrDefault() as bool?
|
|
||||||
?? p.IsRequired;
|
|
||||||
|
|
||||||
var description =
|
|
||||||
optionAttribute
|
|
||||||
.NamedArguments.FirstOrDefault(a =>
|
|
||||||
string.Equals(a.Key, "Description", StringComparison.Ordinal)
|
|
||||||
)
|
|
||||||
.Value.Value as string;
|
|
||||||
|
|
||||||
var converter =
|
|
||||||
optionAttribute
|
|
||||||
.NamedArguments.FirstOrDefault(a =>
|
|
||||||
string.Equals(a.Key, "Converter", StringComparison.Ordinal)
|
|
||||||
)
|
|
||||||
.Value.Value as ITypeSymbol;
|
|
||||||
|
|
||||||
var validators = optionAttribute
|
|
||||||
.NamedArguments.FirstOrDefault(a =>
|
|
||||||
string.Equals(a.Key, "Validators", StringComparison.Ordinal)
|
|
||||||
)
|
|
||||||
.Value.Values.CastArray<ITypeSymbol>();
|
|
||||||
|
|
||||||
return new CommandOptionSymbol(
|
|
||||||
new PropertyDescriptor(
|
|
||||||
new TypeDescriptor(
|
|
||||||
p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
|
||||||
),
|
|
||||||
p.Name
|
|
||||||
),
|
|
||||||
isSequence,
|
|
||||||
name,
|
|
||||||
shortName,
|
|
||||||
environmentVariable,
|
|
||||||
isRequired,
|
|
||||||
description,
|
|
||||||
converter
|
|
||||||
?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
|
||||||
?.Pipe(n => new TypeDescriptor(n)),
|
|
||||||
validators
|
|
||||||
.Select(v =>
|
|
||||||
v.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
|
||||||
)
|
|
||||||
.Select(n => new TypeDescriptor(n))
|
|
||||||
.ToArray()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.WhereNotNull()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
return (
|
|
||||||
new CommandSymbol(
|
|
||||||
new TypeDescriptor(
|
|
||||||
namedTypeSymbol.ToDisplayString(
|
|
||||||
SymbolDisplayFormat.FullyQualifiedFormat
|
|
||||||
)
|
|
||||||
),
|
|
||||||
commandName,
|
|
||||||
commandDescription,
|
|
||||||
parameterSymbols.Concat<CommandInputSymbol>(optionSymbols).ToArray()
|
|
||||||
),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -276,58 +77,54 @@ public class CommandSchemaGenerator : IIncrementalGenerator
|
|||||||
var diagnostics = values.Select((v, _) => v.Item2).WhereNotNull();
|
var diagnostics = values.Select((v, _) => v.Item2).WhereNotNull();
|
||||||
context.RegisterSourceOutput(diagnostics, (x, d) => x.ReportDiagnostic(d));
|
context.RegisterSourceOutput(diagnostics, (x, d) => x.ReportDiagnostic(d));
|
||||||
|
|
||||||
// Generate source
|
// Generate command schemas
|
||||||
var symbols = values.Select((v, _) => v.Item1).WhereNotNull();
|
var symbols = values.Select((v, _) => v.Item1).WhereNotNull();
|
||||||
context.RegisterSourceOutput(
|
context.RegisterSourceOutput(
|
||||||
symbols,
|
symbols,
|
||||||
(x, c) =>
|
(x, c) =>
|
||||||
{
|
x.AddSource(
|
||||||
var source =
|
$"{c.Type.FullyQualifiedName}.CommandSchema.Generated.cs",
|
||||||
// lang=csharp
|
// lang=csharp
|
||||||
$$"""
|
$$"""
|
||||||
using System.Linq;
|
namespace {{c.Type.Namespace}};
|
||||||
using CliFx.Schema;
|
|
||||||
using CliFx.Extensibility;
|
|
||||||
|
|
||||||
namespace {{ c.Type.Namespace }};
|
|
||||||
|
|
||||||
partial class {{ c.Type.Name }}
|
|
||||||
{
|
|
||||||
public static CommandSchema<{{ c.Type.FullyQualifiedName }}> Schema { get; } = new(
|
|
||||||
{{ c.Name }},
|
|
||||||
{{ c.Description }},
|
|
||||||
[
|
|
||||||
{{ c.Inputs.Select(i => i switch {
|
|
||||||
CommandParameterSymbol parameter =>
|
|
||||||
// lang=csharp
|
|
||||||
$$"""
|
|
||||||
new CommandParameterSchema<{{ c.Type.FullyQualifiedName }}, {{ i.Property.Type.FullyQualifiedName }}>(
|
|
||||||
new PropertyBinding<{{ c.Type.FullyQualifiedName }}, {{ i.Property.Type.FullyQualifiedName }}>(
|
|
||||||
obj => obj.{{ i.Property.Name }},
|
|
||||||
(obj, value) => obj.{{ i.Property.Name }} = value
|
|
||||||
),
|
|
||||||
p.Order,
|
|
||||||
p.IsRequired,
|
|
||||||
p.Name,
|
|
||||||
p.Description,
|
|
||||||
new {{ i.ConverterType.FullyQualifiedName }}(),
|
|
||||||
[
|
|
||||||
{{ i.ValidatorTypes.Select(v =>
|
|
||||||
// lang=csharp
|
|
||||||
$"new {v.FullyQualifiedName}()").JoinToString(",\n")
|
|
||||||
}}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
""",
|
|
||||||
CommandOptionSymbol option => ""
|
|
||||||
}).JoinToString(",\n")
|
|
||||||
}}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
x.AddSource($"{c.TypeName}.CommandSchema.Generated.cs", source);
|
partial class {{c.Type.Name}}
|
||||||
}
|
{
|
||||||
|
public static CliFx.Schema.CommandSchema<{{c.Type.FullyQualifiedName}}> Schema { get; } = {{c.GenerateSchemaInitializationCode()}};
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generate extension methods
|
||||||
|
var symbolsCollected = symbols.Collect();
|
||||||
|
context.RegisterSourceOutput(
|
||||||
|
symbolsCollected,
|
||||||
|
(x, cs) =>
|
||||||
|
x.AddSource(
|
||||||
|
"CommandSchemaExtensions.Generated.cs",
|
||||||
|
// lang=csharp
|
||||||
|
$$"""
|
||||||
|
namespace CliFx;
|
||||||
|
|
||||||
|
static partial class GeneratedExtensions
|
||||||
|
{
|
||||||
|
public static CliFx.CliApplicationBuilder AddCommandsFromThisAssembly(this CliFx.CliApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
{{
|
||||||
|
cs.Select(c => c.Type.FullyQualifiedName)
|
||||||
|
.Select(t =>
|
||||||
|
// lang=csharp
|
||||||
|
$"builder.AddCommand({t}.Schema);"
|
||||||
|
)
|
||||||
|
.JoinToString("\n")
|
||||||
|
}}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace CliFx.SourceGeneration.SemanticModel;
|
namespace CliFx.SourceGeneration.SemanticModel;
|
||||||
|
|
||||||
@@ -54,3 +55,12 @@ internal partial class CommandInputSymbol : IEquatable<CommandInputSymbol>
|
|||||||
public override int GetHashCode() =>
|
public override int GetHashCode() =>
|
||||||
HashCode.Combine(Property, IsSequence, Description, ConverterType, ValidatorTypes);
|
HashCode.Combine(Property, IsSequence, Description, ConverterType, ValidatorTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal partial class CommandInputSymbol
|
||||||
|
{
|
||||||
|
public static bool IsSequenceType(ITypeSymbol type) =>
|
||||||
|
type.AllInterfaces.Any(i =>
|
||||||
|
i.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T
|
||||||
|
)
|
||||||
|
&& type.SpecialType != SpecialType.System_String;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using CliFx.SourceGeneration.Utils.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace CliFx.SourceGeneration.SemanticModel;
|
namespace CliFx.SourceGeneration.SemanticModel;
|
||||||
|
|
||||||
@@ -55,3 +58,33 @@ internal partial class CommandOptionSymbol : IEquatable<CommandOptionSymbol>
|
|||||||
public override int GetHashCode() =>
|
public override int GetHashCode() =>
|
||||||
HashCode.Combine(base.GetHashCode(), Name, ShortName, EnvironmentVariable, IsRequired);
|
HashCode.Combine(base.GetHashCode(), Name, ShortName, EnvironmentVariable, IsRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal partial class CommandOptionSymbol
|
||||||
|
{
|
||||||
|
public static CommandOptionSymbol FromSymbol(
|
||||||
|
IPropertySymbol property,
|
||||||
|
AttributeData attribute
|
||||||
|
) =>
|
||||||
|
new(
|
||||||
|
PropertyDescriptor.FromSymbol(property),
|
||||||
|
IsSequenceType(property.Type),
|
||||||
|
attribute
|
||||||
|
.ConstructorArguments.FirstOrDefault(a =>
|
||||||
|
a.Type?.SpecialType == SpecialType.System_String
|
||||||
|
)
|
||||||
|
.Value as string,
|
||||||
|
attribute
|
||||||
|
.ConstructorArguments.FirstOrDefault(a =>
|
||||||
|
a.Type?.SpecialType == SpecialType.System_Char
|
||||||
|
)
|
||||||
|
.Value as char?,
|
||||||
|
attribute.GetNamedArgumentValue("EnvironmentVariable", default(string)),
|
||||||
|
attribute.GetNamedArgumentValue("IsRequired", property.IsRequired),
|
||||||
|
attribute.GetNamedArgumentValue("Description", default(string)),
|
||||||
|
TypeDescriptor.FromSymbol(attribute.GetNamedArgumentValue<ITypeSymbol?>("Converter")),
|
||||||
|
attribute
|
||||||
|
.GetNamedArgumentValues<ITypeSymbol>("Validators")
|
||||||
|
.Select(TypeDescriptor.FromSymbol)
|
||||||
|
.ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using CliFx.SourceGeneration.Utils.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace CliFx.SourceGeneration.SemanticModel;
|
namespace CliFx.SourceGeneration.SemanticModel;
|
||||||
|
|
||||||
@@ -51,3 +54,24 @@ internal partial class CommandParameterSymbol : IEquatable<CommandParameterSymbo
|
|||||||
public override int GetHashCode() =>
|
public override int GetHashCode() =>
|
||||||
HashCode.Combine(base.GetHashCode(), Order, Name, IsRequired);
|
HashCode.Combine(base.GetHashCode(), Order, Name, IsRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal partial class CommandParameterSymbol
|
||||||
|
{
|
||||||
|
public static CommandParameterSymbol FromSymbol(
|
||||||
|
IPropertySymbol property,
|
||||||
|
AttributeData attribute
|
||||||
|
) =>
|
||||||
|
new(
|
||||||
|
PropertyDescriptor.FromSymbol(property),
|
||||||
|
IsSequenceType(property.Type),
|
||||||
|
(int)attribute.ConstructorArguments.First().Value!,
|
||||||
|
attribute.GetNamedArgumentValue("Name", default(string)),
|
||||||
|
attribute.GetNamedArgumentValue("IsRequired", true),
|
||||||
|
attribute.GetNamedArgumentValue("Description", default(string)),
|
||||||
|
TypeDescriptor.FromSymbol(attribute.GetNamedArgumentValue<ITypeSymbol>("Converter")),
|
||||||
|
attribute
|
||||||
|
.GetNamedArgumentValues<ITypeSymbol>("Validators")
|
||||||
|
.Select(TypeDescriptor.FromSymbol)
|
||||||
|
.ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using CliFx.SourceGeneration.Utils.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace CliFx.SourceGeneration.SemanticModel;
|
namespace CliFx.SourceGeneration.SemanticModel;
|
||||||
|
|
||||||
@@ -24,6 +26,73 @@ internal partial class CommandSymbol(
|
|||||||
|
|
||||||
public IReadOnlyList<CommandOptionSymbol> Options =>
|
public IReadOnlyList<CommandOptionSymbol> Options =>
|
||||||
Inputs.OfType<CommandOptionSymbol>().ToArray();
|
Inputs.OfType<CommandOptionSymbol>().ToArray();
|
||||||
|
|
||||||
|
private string GeneratePropertyBindingInitializationCode(PropertyDescriptor property) =>
|
||||||
|
// lang=csharp
|
||||||
|
$$"""
|
||||||
|
new CliFx.Schema.PropertyBinding<{{Type.FullyQualifiedName}}, {{property
|
||||||
|
.Type
|
||||||
|
.FullyQualifiedName}}>(
|
||||||
|
(obj) => obj.{{property.Name}},
|
||||||
|
(obj, value) => obj.{{property.Name}} = value
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
|
||||||
|
private string GenerateSchemaInitializationCode(CommandInputSymbol input) =>
|
||||||
|
input switch
|
||||||
|
{
|
||||||
|
CommandParameterSymbol parameter
|
||||||
|
=>
|
||||||
|
// lang=csharp
|
||||||
|
$$"""
|
||||||
|
new CliFx.Schema.CommandParameterSchema<{{Type.FullyQualifiedName}}, {{parameter
|
||||||
|
.Property
|
||||||
|
.Type
|
||||||
|
.FullyQualifiedName}}>(
|
||||||
|
{{GeneratePropertyBindingInitializationCode(parameter.Property)}},
|
||||||
|
{{parameter.IsSequence}},
|
||||||
|
{{parameter.Order}},
|
||||||
|
"{{parameter.Name}}",
|
||||||
|
{{parameter.IsRequired}},
|
||||||
|
"{{parameter.Description}}",
|
||||||
|
// TODO,
|
||||||
|
// TODO
|
||||||
|
);
|
||||||
|
""",
|
||||||
|
CommandOptionSymbol option
|
||||||
|
=>
|
||||||
|
// lang=csharp
|
||||||
|
$$"""
|
||||||
|
new CliFx.Schema.CommandOptionSchema<{{Type.FullyQualifiedName}}, {{option
|
||||||
|
.Property
|
||||||
|
.Type
|
||||||
|
.FullyQualifiedName}}>(
|
||||||
|
{{GeneratePropertyBindingInitializationCode(option.Property)}},
|
||||||
|
{{option.IsSequence}},
|
||||||
|
"{{option.Name}}",
|
||||||
|
'{{option.ShortName}}',
|
||||||
|
"{{option.EnvironmentVariable}}",
|
||||||
|
{{option.IsRequired}},
|
||||||
|
"{{option.Description}}",
|
||||||
|
// TODO,
|
||||||
|
// TODO
|
||||||
|
);
|
||||||
|
""",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(input), input, null)
|
||||||
|
};
|
||||||
|
|
||||||
|
public string GenerateSchemaInitializationCode() =>
|
||||||
|
// lang=csharp
|
||||||
|
$$"""
|
||||||
|
new CliFx.Schema.CommandSchema<{{Type.FullyQualifiedName}}>(
|
||||||
|
"{{Name}}",
|
||||||
|
"{{Description}}",
|
||||||
|
new CliFx.Schema.CommandInputSchema[]
|
||||||
|
{
|
||||||
|
{{Inputs.Select(GenerateSchemaInitializationCode).JoinToString(",\n")}}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class CommandSymbol : IEquatable<CommandSymbol>
|
internal partial class CommandSymbol : IEquatable<CommandSymbol>
|
||||||
@@ -55,3 +124,44 @@ internal partial class CommandSymbol : IEquatable<CommandSymbol>
|
|||||||
|
|
||||||
public override int GetHashCode() => HashCode.Combine(Type, Name, Description, Inputs);
|
public override int GetHashCode() => HashCode.Combine(Type, Name, Description, Inputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal partial class CommandSymbol
|
||||||
|
{
|
||||||
|
public static CommandSymbol FromSymbol(INamedTypeSymbol symbol, AttributeData attribute)
|
||||||
|
{
|
||||||
|
var inputs = new List<CommandInputSymbol>();
|
||||||
|
foreach (var property in symbol.GetMembers().OfType<IPropertySymbol>())
|
||||||
|
{
|
||||||
|
var parameterAttribute = property
|
||||||
|
.GetAttributes()
|
||||||
|
.FirstOrDefault(a =>
|
||||||
|
a.AttributeClass?.Name == KnownSymbolNames.CliFxCommandParameterAttribute
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parameterAttribute is not null)
|
||||||
|
{
|
||||||
|
inputs.Add(CommandParameterSymbol.FromSymbol(property, parameterAttribute));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var optionAttribute = property
|
||||||
|
.GetAttributes()
|
||||||
|
.FirstOrDefault(a =>
|
||||||
|
a.AttributeClass?.Name == KnownSymbolNames.CliFxCommandOptionAttribute
|
||||||
|
);
|
||||||
|
|
||||||
|
if (optionAttribute is not null)
|
||||||
|
{
|
||||||
|
inputs.Add(CommandOptionSymbol.FromSymbol(property, optionAttribute));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CommandSymbol(
|
||||||
|
TypeDescriptor.FromSymbol(symbol),
|
||||||
|
attribute.ConstructorArguments.FirstOrDefault().Value as string,
|
||||||
|
attribute.GetNamedArgumentValue("Description", default(string)),
|
||||||
|
inputs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace CliFx.SourceGeneration.SemanticModel;
|
namespace CliFx.SourceGeneration.SemanticModel;
|
||||||
|
|
||||||
@@ -35,3 +36,9 @@ internal partial class PropertyDescriptor : IEquatable<PropertyDescriptor>
|
|||||||
|
|
||||||
public override int GetHashCode() => HashCode.Combine(Type, Name);
|
public override int GetHashCode() => HashCode.Combine(Type, Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal partial class PropertyDescriptor
|
||||||
|
{
|
||||||
|
public static PropertyDescriptor FromSymbol(IPropertySymbol symbol) =>
|
||||||
|
new(TypeDescriptor.FromSymbol(symbol.Type), symbol.Name);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using CliFx.SourceGeneration.Utils.Extensions;
|
using CliFx.SourceGeneration.Utils.Extensions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace CliFx.SourceGeneration.SemanticModel;
|
namespace CliFx.SourceGeneration.SemanticModel;
|
||||||
|
|
||||||
@@ -38,3 +39,9 @@ internal partial class TypeDescriptor : IEquatable<TypeDescriptor>
|
|||||||
|
|
||||||
public override int GetHashCode() => FullyQualifiedName.GetHashCode();
|
public override int GetHashCode() => FullyQualifiedName.GetHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal partial class TypeDescriptor
|
||||||
|
{
|
||||||
|
public static TypeDescriptor FromSymbol(ITypeSymbol symbol) =>
|
||||||
|
new(symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
namespace CliFx.SourceGeneration.Utils.Extensions;
|
namespace CliFx.SourceGeneration.Utils.Extensions;
|
||||||
@@ -13,6 +16,22 @@ internal static class RoslynExtensions
|
|||||||
StringComparison.Ordinal
|
StringComparison.Ordinal
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public static T GetNamedArgumentValue<T>(
|
||||||
|
this AttributeData attribute,
|
||||||
|
string name,
|
||||||
|
T defaultValue = default
|
||||||
|
) =>
|
||||||
|
attribute.NamedArguments.FirstOrDefault(i => i.Key == name).Value.Value is T valueAsT
|
||||||
|
? valueAsT
|
||||||
|
: defaultValue;
|
||||||
|
|
||||||
|
public static IReadOnlyList<T> GetNamedArgumentValues<T>(
|
||||||
|
this AttributeData attribute,
|
||||||
|
string name
|
||||||
|
)
|
||||||
|
where T : class =>
|
||||||
|
attribute.NamedArguments.FirstOrDefault(i => i.Key == name).Value.Values.CastArray<T>();
|
||||||
|
|
||||||
public static IncrementalValuesProvider<T> WhereNotNull<T>(
|
public static IncrementalValuesProvider<T> WhereNotNull<T>(
|
||||||
this IncrementalValuesProvider<T?> values
|
this IncrementalValuesProvider<T?> values
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -125,10 +125,7 @@ public abstract class CommandInputSchema(
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public abstract class CommandInputSchema<
|
public abstract class CommandInputSchema<
|
||||||
TCommand,
|
TCommand,
|
||||||
[DynamicallyAccessedMembers(
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty
|
||||||
DynamicallyAccessedMemberTypes.PublicMethods
|
|
||||||
)]
|
|
||||||
TProperty
|
|
||||||
>(
|
>(
|
||||||
PropertyBinding<TCommand, TProperty> property,
|
PropertyBinding<TCommand, TProperty> property,
|
||||||
bool isSequence,
|
bool isSequence,
|
||||||
|
|||||||
@@ -63,10 +63,7 @@ public class CommandOptionSchema(
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class CommandOptionSchema<
|
public class CommandOptionSchema<
|
||||||
TCommand,
|
TCommand,
|
||||||
[DynamicallyAccessedMembers(
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty
|
||||||
DynamicallyAccessedMemberTypes.PublicMethods
|
|
||||||
)]
|
|
||||||
TProperty
|
|
||||||
>(
|
>(
|
||||||
PropertyBinding<TCommand, TProperty> property,
|
PropertyBinding<TCommand, TProperty> property,
|
||||||
bool isSequence,
|
bool isSequence,
|
||||||
|
|||||||
@@ -42,10 +42,7 @@ public class CommandParameterSchema(
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class CommandParameterSchema<
|
public class CommandParameterSchema<
|
||||||
TCommand,
|
TCommand,
|
||||||
[DynamicallyAccessedMembers(
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty
|
||||||
DynamicallyAccessedMemberTypes.PublicMethods
|
|
||||||
)]
|
|
||||||
TProperty
|
|
||||||
>(
|
>(
|
||||||
PropertyBinding<TCommand, TProperty> property,
|
PropertyBinding<TCommand, TProperty> property,
|
||||||
bool isSequence,
|
bool isSequence,
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ namespace CliFx.Schema;
|
|||||||
/// Provides read and write access to a CLR property.
|
/// Provides read and write access to a CLR property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PropertyBinding(
|
public class PropertyBinding(
|
||||||
[DynamicallyAccessedMembers(
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type,
|
||||||
DynamicallyAccessedMemberTypes.PublicMethods
|
|
||||||
)]
|
|
||||||
Type type,
|
|
||||||
Func<object, object?> getValue,
|
Func<object, object?> getValue,
|
||||||
Action<object, object?> setValue
|
Action<object, object?> setValue
|
||||||
)
|
)
|
||||||
@@ -20,9 +17,7 @@ public class PropertyBinding(
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Underlying CLR type of the property.
|
/// Underlying CLR type of the property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DynamicallyAccessedMembers(
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
|
||||||
DynamicallyAccessedMemberTypes.PublicMethods
|
|
||||||
)]
|
|
||||||
public Type Type { get; } = type;
|
public Type Type { get; } = type;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -63,10 +58,7 @@ public class PropertyBinding(
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class PropertyBinding<
|
public class PropertyBinding<
|
||||||
TObject,
|
TObject,
|
||||||
[DynamicallyAccessedMembers(
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty
|
||||||
DynamicallyAccessedMemberTypes.PublicMethods
|
|
||||||
)]
|
|
||||||
TProperty
|
|
||||||
>(Func<TObject, TProperty?> getValue, Action<TObject, TProperty?> setValue)
|
>(Func<TObject, TProperty?> getValue, Action<TObject, TProperty?> setValue)
|
||||||
: PropertyBinding(
|
: PropertyBinding(
|
||||||
typeof(TProperty),
|
typeof(TProperty),
|
||||||
|
|||||||
Reference in New Issue
Block a user