Skip to content

Commit fb40df4

Browse files
committed
???
1 parent 0c34c68 commit fb40df4

File tree

8 files changed

+482
-94
lines changed

8 files changed

+482
-94
lines changed
Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,15 @@
11
using ConsoleAppFramework;
2-
using Foo.Bar;
3-
using System.ComponentModel.DataAnnotations;
42

53
var app = ConsoleApp.Create();
6-
app.Add<Test>();
7-
app.Add("", (int x, int y) => // different
8-
{
9-
Console.WriteLine("body"); // body
10-
});
11-
app.Add("foo", () => { }); // newline
12-
app.UseFilter<MyFilter>();
13-
app.Run(args);
144

15-
Console.WriteLine(""); // unrelated line
16-
17-
public class Test
18-
{
19-
public void Show(string aaa, [Range(0, 1)] double value) => ConsoleApp.Log($"{value}");
20-
}
21-
22-
namespace Foo.Bar
23-
{
24-
public class MyFilter(ConsoleAppFilter next) : ConsoleAppFilter(next)
25-
{
26-
public override Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
27-
{
28-
throw new NotImplementedException();
29-
}
30-
}
31-
32-
public class MyFilter2(ConsoleAppFilter next) : ConsoleAppFilter(next)
33-
{
34-
public override Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
35-
{
36-
throw new NotImplementedException();
37-
}
38-
}
39-
}
5+
app.Add("foo", () => { });
6+
app.Add("fooa", () => { });
7+
8+
app.Add("choofooaiueo", (int z) => { });
9+
10+
app.Add("Y", Task<int> (int x, int y, int z) => { });
11+
12+
13+
14+
15+
app.Run(args);

src/ConsoleAppFramework/ConsoleAppFramework.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@
1414
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
1515
<IncludeSymbols>false</IncludeSymbols>
1616
<DevelopmentDependency>true</DevelopmentDependency>
17+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
1718

1819
<!-- NuGet -->
1920
<PackageId>ConsoleAppFramework</PackageId>
2021
<Description>Zero Dependency, Zero Overhead, Zero Reflection, Zero Allocation, AOT Safe CLI Framework powered by C# Source Generator.</Description>
2122
</PropertyGroup>
2223

2324
<ItemGroup>
24-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0" />
25+
<!-- Roslyn for .NET 8 / C# 12 -->
26+
<!-- https://learn.microsoft.com/en-us/visualstudio/extensibility/roslyn-version-support -->
27+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
2528
<PackageReference Include="PolySharp" Version="1.14.1">
2629
<PrivateAssets>all</PrivateAssets>
2730
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

src/ConsoleAppFramework/ConsoleAppGenerator.cs

Lines changed: 122 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
using Microsoft.CodeAnalysis.CSharp.Syntax;
44
using System.Collections.Immutable;
55
using System.Reflection;
6-
using System.Security.Cryptography.X509Certificates;
7-
using System.Xml.Linq;
86

97
namespace ConsoleAppFramework;
108

@@ -39,14 +37,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
3937

4038
return false;
4139
}, (context, ct) => new RunContext((InvocationExpressionSyntax)context.Node, context.SemanticModel))
42-
.WithTrackingName("ConsoleApp.Run.CreateSyntaxProvider"); // annotate for IncrementalGeneratorTest
40+
.WithTrackingName("ConsoleApp.Run.0_CreateSyntaxProvider"); // annotate for IncrementalGeneratorTest
4341

4442
context.RegisterSourceOutput(runSource, EmitConsoleAppRun);
4543

4644
// ConsoleAppBuilder
4745
var builderSource = context.SyntaxProvider
4846
.CreateSyntaxProvider((node, ct) =>
4947
{
48+
ct.ThrowIfCancellationRequested();
5049
if (node.IsKind(SyntaxKind.InvocationExpression))
5150
{
5251
var invocationExpression = (node as InvocationExpressionSyntax);
@@ -66,16 +65,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
6665
}, (context, ct) => new BuilderContext(
6766
(InvocationExpressionSyntax)context.Node,
6867
((context.Node as InvocationExpressionSyntax)!.Expression as MemberAccessExpressionSyntax)!.Name.Identifier.Text,
69-
context.SemanticModel))
70-
.WithTrackingName("ConsoleApp.Builder.CreateSyntaxProvider")
68+
context.SemanticModel,
69+
ct))
70+
.WithTrackingName("ConsoleApp.Builder.0_CreateSyntaxProvider")
7171
.Where(x =>
7272
{
73-
var model = x.Model.GetTypeInfo((x.Node.Expression as MemberAccessExpressionSyntax)!.Expression);
73+
var model = x.Model.GetTypeInfo((x.Node.Expression as MemberAccessExpressionSyntax)!.Expression, x.CancellationToken);
7474
return model.Type?.Name == "ConsoleAppBuilder";
7575
})
76-
.WithTrackingName("ConsoleApp.Builder.Where")
76+
.WithTrackingName("ConsoleApp.Builder.1_Where")
7777
.Collect()
78-
.WithTrackingName("ConsoleApp.Builder.Collect");
78+
.WithComparer(CollectBuilderContextComparer.Default)
79+
.WithTrackingName("ConsoleApp.Builder.2_Collect");
7980

8081
context.RegisterSourceOutput(builderSource, EmitConsoleAppBuilder);
8182
}
@@ -783,7 +784,8 @@ public static bool DelegateEquals(InvocationExpressionSyntax node, SemanticModel
783784
{
784785
// check async, returntype, parameters
785786

786-
var lambda2 = (ParenthesizedLambdaExpressionSyntax)args2[1].Expression;
787+
var lambda2 = args2[1].Expression as ParenthesizedLambdaExpressionSyntax;
788+
if (lambda2 == null) return false;
787789

788790
if (!lambda1.AsyncKeyword.IsKind(lambda2.AsyncKeyword.Kind())) return false;
789791

@@ -800,8 +802,8 @@ public static bool DelegateEquals(InvocationExpressionSyntax node, SemanticModel
800802
ImmutableArray<ISymbol> methodSymbols2;
801803
if (expression.IsKind(SyntaxKind.AddressOfExpression))
802804
{
803-
var operand1 = (expression as PrefixUnaryExpressionSyntax)!.Operand;
804-
var operand2 = (args2[1].Expression as PrefixUnaryExpressionSyntax)!.Operand;
805+
var operand1 = (expression as PrefixUnaryExpressionSyntax)?.Operand;
806+
var operand2 = (args2[1].Expression as PrefixUnaryExpressionSyntax)?.Operand;
805807
if (operand1 == null || operand2 == null) return false;
806808

807809
methodSymbols1 = model.GetMemberGroup(operand1);
@@ -854,35 +856,62 @@ public static bool MethodSymbolEquals(IMethodSymbol methodSymbol1, IMethodSymbol
854856
}
855857
}
856858

857-
readonly struct BuilderContext(InvocationExpressionSyntax node, string name, SemanticModel model) : IEquatable<BuilderContext>
859+
public class CollectBuilderContextComparer : IEqualityComparer<ImmutableArray<BuilderContext>>
858860
{
859-
public InvocationExpressionSyntax Node => node;
860-
public string Name => name;
861-
public SemanticModel Model => model;
861+
public static CollectBuilderContextComparer Default = new CollectBuilderContextComparer();
862862

863-
public bool Equals(BuilderContext other)
863+
bool IEqualityComparer<ImmutableArray<BuilderContext>>.Equals(ImmutableArray<BuilderContext> x, ImmutableArray<BuilderContext> y)
864+
{
865+
if (x.Length != y.Length) return false;
866+
867+
for (int i = 0; i < x.Length; i++)
868+
{
869+
if (!Equals(x[i], y[i])) return false;
870+
}
871+
872+
return true;
873+
}
874+
875+
int IEqualityComparer<ImmutableArray<BuilderContext>>.GetHashCode(ImmutableArray<BuilderContext> obj)
876+
{
877+
return 0;
878+
}
879+
880+
881+
static bool Equals(BuilderContext self, BuilderContext other)
864882
{
865-
if (this.Name != other.Name) return false;
883+
if (self.Name != other.Name) return false;
866884

867-
var typeInfo = Model.GetTypeInfo((Node.Expression as MemberAccessExpressionSyntax)!.Expression);
885+
var typeInfo = self.Model.GetTypeInfo((self.Node.Expression as MemberAccessExpressionSyntax)!.Expression, self.CancellationToken);
868886
if (typeInfo.Type?.Name != "ConsoleAppBuilder")
869887
{
870888
return false;
871889
}
872890

873-
switch (Name)
891+
var typeInfo2 = other.Model.GetTypeInfo((other.Node.Expression as MemberAccessExpressionSyntax)!.Expression, other.CancellationToken);
892+
if (typeInfo2.Type?.Name != "ConsoleAppBuilder")
893+
{
894+
return false;
895+
}
896+
897+
switch (self.Name)
874898
{
875899
case "Add": // Add or Add<T>
876-
if ((Node.Expression as MemberAccessExpressionSyntax)?.Name.IsKind(SyntaxKind.GenericName) ?? false)
900+
if ((self.Node.Expression as MemberAccessExpressionSyntax)?.Name.IsKind(SyntaxKind.GenericName) ?? false)
877901
{
878-
return EqualsAddClass(other);
902+
return EqualsAddClass(self, other);
879903
}
880904
else
881905
{
882-
return RunContext.DelegateEquals(node, model, (other.Node, other.Model));
906+
var first = GetFirstStringConstant(self.Node);
907+
var second = GetFirstStringConstant(other.Node);
908+
if (first == null || second == null) return false;
909+
if (first != second) return false;
910+
911+
return RunContext.DelegateEquals(self.Node, self.Model, (other.Node, other.Model));
883912
}
884913
case "UseFilter":
885-
return EqualsUseFilter(other);
914+
return EqualsUseFilter(self, other);
886915
case "Run":
887916
case "RunAsync":
888917
return true; // only check name
@@ -893,10 +922,27 @@ public bool Equals(BuilderContext other)
893922
return false;
894923
}
895924

896-
bool EqualsAddClass(BuilderContext other)
925+
static string? GetFirstStringConstant(InvocationExpressionSyntax invocationExpression)
926+
{
927+
if (invocationExpression.ArgumentList.Arguments.Count != 2) return null;
928+
var commandName = invocationExpression.ArgumentList.Arguments[0];
929+
930+
if (!commandName.Expression.IsKind(SyntaxKind.StringLiteralExpression))
931+
{
932+
return null;
933+
}
934+
935+
var name = (commandName.Expression as LiteralExpressionSyntax)!.Token.ValueText;
936+
return name;
937+
}
938+
939+
static bool EqualsAddClass(BuilderContext self, BuilderContext other)
897940
{
898-
var typeAndPath1 = GetTypeSymbolAndPath(node, model);
899-
var typeAndPath2 = GetTypeSymbolAndPath(other.Node, other.Model);
941+
var node = self.Node;
942+
var model = self.Model;
943+
944+
var typeAndPath1 = GetTypeSymbolAndPath(node, model, self.CancellationToken);
945+
var typeAndPath2 = GetTypeSymbolAndPath(other.Node, other.Model, other.CancellationToken);
900946

901947
if (typeAndPath1 == null || typeAndPath2 == null) return false;
902948

@@ -905,19 +951,44 @@ bool EqualsAddClass(BuilderContext other)
905951

906952
if (path1 != path2) return false;
907953

908-
// TODO:
954+
if (type1.DeclaringSyntaxReferences.Length == 0) return false;
955+
if (type2.DeclaringSyntaxReferences.Length == 0) return false;
956+
957+
var syntax1 = type1.DeclaringSyntaxReferences[0].GetSyntax() as TypeDeclarationSyntax;
958+
var syntax2 = type2.DeclaringSyntaxReferences[0].GetSyntax() as TypeDeclarationSyntax;
959+
960+
if (syntax1 == null || syntax2 == null) return false;
909961

910-
// Type:Attributes
911-
// Type:Interface
962+
// interface
963+
if (!type1.AllInterfaces.Select(x => x.Name).SequenceEqual(type2.AllInterfaces.Select(x => x.Name)))
964+
{
965+
return false;
966+
}
912967

913968
// Public Constructor
969+
var ctor1 = type1.GetMembers().FirstOrDefault(x => (x as IMethodSymbol)?.MethodKind == Microsoft.CodeAnalysis.MethodKind.Constructor);
970+
var ctor2 = type2.GetMembers().FirstOrDefault(x => (x as IMethodSymbol)?.MethodKind == Microsoft.CodeAnalysis.MethodKind.Constructor);
971+
var ctorParameter1 = ctor1?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetParameterListOfConstructor();
972+
var ctorParameter2 = ctor2?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetParameterListOfConstructor();
973+
974+
if (!SyntaxNodeTextEqualityComparer.Default.Equals(ctorParameter1, ctorParameter2))
975+
{
976+
return false;
977+
}
978+
979+
// attributes
980+
if (!SyntaxNodeTextEqualityComparer.Default.Equals(syntax1.AttributeLists!, syntax2.AttributeLists!))
981+
{
982+
return false;
983+
}
914984

915985
// Public Methods
916986
var methods1 = GetMethodSymbols(type1);
917987
var methods2 = GetMethodSymbols(type2);
918-
return methods1.ZipEquals(methods2, RunContext.MethodSymbolEquals);
988+
var methodEquals = methods1.ZipEquals(methods2, RunContext.MethodSymbolEquals);
989+
return methodEquals;
919990

920-
static (ITypeSymbol, string?)? GetTypeSymbolAndPath(InvocationExpressionSyntax node, SemanticModel model)
991+
static (ITypeSymbol, string?)? GetTypeSymbolAndPath(InvocationExpressionSyntax node, SemanticModel model, CancellationToken cancellationToken)
921992
{
922993
// Add<T>
923994
var genericName = (node.Expression as MemberAccessExpressionSyntax)?.Name as GenericNameSyntax;
@@ -938,40 +1009,49 @@ bool EqualsAddClass(BuilderContext other)
9381009
}
9391010

9401011
// T
941-
var type = model.GetTypeInfo(genericType).Type!;
1012+
var type = model.GetTypeInfo(genericType, cancellationToken).Type!;
9421013
return (type, commandPath);
9431014
}
9441015

9451016
static IEnumerable<IMethodSymbol> GetMethodSymbols(ITypeSymbol type)
9461017
{
9471018
return type.GetMembers()
948-
.Where(x => x.DeclaredAccessibility == Accessibility.Public)
9491019
.OfType<IMethodSymbol>()
9501020
.Where(x => x.DeclaredAccessibility == Accessibility.Public && !x.IsStatic)
9511021
.Where(x => x.MethodKind == Microsoft.CodeAnalysis.MethodKind.Ordinary)
9521022
.Where(x => !(x.Name is "Dispose" or "DisposeAsync" or "GetHashCode" or "Equals" or "ToString"));
9531023
}
9541024
}
9551025

956-
bool EqualsUseFilter(BuilderContext other)
1026+
static bool EqualsUseFilter(BuilderContext self, BuilderContext other)
9571027
{
958-
var l = GetType(Node, model);
959-
var r = GetType(other.Node, other.Model);
1028+
var node = self.Node;
1029+
var model = self.Model;
1030+
1031+
var l = GetType(node, model, self.CancellationToken);
1032+
var r = GetType(other.Node, other.Model, other.CancellationToken);
9601033

9611034
return l.EqualsNamespaceAndName(r);
9621035

963-
static ITypeSymbol? GetType(InvocationExpressionSyntax expression, SemanticModel model)
1036+
static ITypeSymbol? GetType(InvocationExpressionSyntax expression, SemanticModel model, CancellationToken cancellationToken)
9641037
{
9651038
var genericName = (expression.Expression as MemberAccessExpressionSyntax)?.Name as GenericNameSyntax;
9661039
var genericType = genericName!.TypeArgumentList.Arguments[0];
967-
return model.GetTypeInfo(genericType).Type;
1040+
return model.GetTypeInfo(genericType, cancellationToken).Type;
9681041
}
9691042
}
1043+
}
9701044

971-
public override int GetHashCode()
1045+
readonly struct BuilderContext(InvocationExpressionSyntax node, string name, SemanticModel model, CancellationToken cancellationToken) : IEquatable<BuilderContext>
1046+
{
1047+
public InvocationExpressionSyntax Node => node;
1048+
public string Name => name;
1049+
public SemanticModel Model => model;
1050+
public CancellationToken CancellationToken => cancellationToken;
1051+
1052+
public bool Equals(BuilderContext other)
9721053
{
973-
// maybe this does not called so don't care impl.
974-
return SyntaxNodeTextEqualityComparer.Default.GetHashCode(node);
1054+
return Node == other.Node;
9751055
}
9761056
}
977-
}
1057+
}

src/ConsoleAppFramework/Emitter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public void EmitRun(SourceBuilder sb, CommandWithId commandWithId, bool isRunAsy
4949
{
5050
sb.AppendLine("/// <summary>");
5151
var help = CommandHelpBuilder.BuildCommandHelpMessage(commandWithId.Command);
52-
foreach (var line in help.Split([Environment.NewLine], StringSplitOptions.None))
52+
foreach (var line in help.Split(["\n"], StringSplitOptions.None))
5353
{
5454
sb.AppendLine($"/// {line.Replace("<", "&lt;").Replace(">", "&gt;")}<br/>");
5555
}

src/ConsoleAppFramework/RoslynExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ public static bool ZipEquals<T>(this IEnumerable<T> left, IEnumerable<T> right,
6767
}
6868
}
6969

70+
public static ParameterListSyntax? GetParameterListOfConstructor(this SyntaxNode node)
71+
{
72+
if (node is ConstructorDeclarationSyntax ctor)
73+
{
74+
return ctor.ParameterList;
75+
}
76+
else if (node is ClassDeclarationSyntax primartyCtor)
77+
{
78+
return primartyCtor.ParameterList;
79+
}
80+
else
81+
{
82+
return null;
83+
}
84+
}
85+
7086
public static DocumentationCommentTriviaSyntax? GetDocumentationCommentTriviaSyntax(this SyntaxNode node)
7187
{
7288
// Hack note:

0 commit comments

Comments
 (0)