Skip to content

Commit 98b07c5

Browse files
committed
Source generator for prototypes
1 parent 95f25aa commit 98b07c5

28 files changed

+6016
-311
lines changed

Jint.Benchmark/Jint.Benchmark.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<ProjectReference Include="..\Jint\Jint.csproj" />
2525
</ItemGroup>
2626
<ItemGroup>
27-
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
27+
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
2828
<PackageReference Include="Jurassic" Version="3.1.0" />
2929
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
3030
<PackageReference Include="NiL.JS.NetCore" Version="2.5.1419" />
+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
4+
namespace Jint.SourceGenerator;
5+
6+
internal class FunctionDefinition : IComparable
7+
{
8+
public FunctionDefinition(IMethodSymbol method, AttributeData attribute)
9+
{
10+
var attributes = SourceGenerationHelper.GetAttributes((AttributeSyntax) attribute.ApplicationSyntaxReference!.GetSyntax());
11+
attributes.TryGetValue("Name", out var name);
12+
attributes.TryGetValue("Length", out var length);
13+
14+
if (string.IsNullOrWhiteSpace(name))
15+
{
16+
name = method.Name;
17+
if (char.IsUpper(name[0]))
18+
{
19+
name = char.ToLowerInvariant(name[0]) + name.Substring(1);
20+
}
21+
}
22+
23+
Name = name ?? throw new InvalidOperationException("Could not get name");
24+
ClrName = method.Name;
25+
26+
ProvideThis = method.Parameters.Any(x => x.Name.StartsWith("thisObj"));
27+
ProvideArguments = method.Parameters.Any(x => x.Name.StartsWith("arguments"));
28+
29+
IsStatic = method.IsStatic;
30+
31+
if (string.IsNullOrWhiteSpace(length))
32+
{
33+
Length = method.Parameters.Length;
34+
if (ProvideThis)
35+
{
36+
Length--;
37+
}
38+
}
39+
else
40+
{
41+
Length = Convert.ToInt32(length);
42+
}
43+
44+
ParametersString = "";
45+
var needsComma = false;
46+
if (ProvideThis)
47+
{
48+
needsComma = true;
49+
ParametersString += "thisObject";
50+
}
51+
52+
var tmp = method.Parameters.Length;
53+
if (ProvideThis)
54+
{
55+
tmp--;
56+
}
57+
58+
if (ProvideArguments)
59+
{
60+
tmp--;
61+
}
62+
63+
for (var i = 0; i < tmp; ++i)
64+
{
65+
if (i > 0 || needsComma)
66+
{
67+
ParametersString += ", ";
68+
}
69+
70+
ParametersString += "arguments.At(" + i + ")";
71+
}
72+
73+
// arguments always last
74+
if (ProvideArguments)
75+
{
76+
if (needsComma)
77+
{
78+
ParametersString += ", ";
79+
}
80+
ParametersString += "arguments";
81+
}
82+
}
83+
84+
public string Name { get; }
85+
public string ClrName { get; }
86+
public bool IsStatic { get; }
87+
public int Length { get; }
88+
89+
public bool ProvideThis { get; }
90+
public bool ProvideArguments { get; }
91+
92+
public string ParametersString { get; }
93+
94+
public int CompareTo(object obj)
95+
{
96+
return string.Compare(Name, (obj as FunctionDefinition)?.Name, StringComparison.Ordinal);
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<LangVersion>latest</LangVersion>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
13+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="Fluid.Core" Version="2.2.14" PrivateAssets="all" GeneratePathProperty="true" />
18+
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="1.1.1" PrivateAssets="all" GeneratePathProperty="true" />
19+
<PackageReference Include="Parlot" Version="0.0.23" PrivateAssets="all" GeneratePathProperty="true" />
20+
<PackageReference Include="System.Text.Encodings.Web" Version="5.0.1" PrivateAssets="all" GeneratePathProperty="true" />
21+
</ItemGroup>
22+
23+
<PropertyGroup>
24+
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
25+
</PropertyGroup>
26+
27+
<Target Name="GetDependencyTargetPaths">
28+
<ItemGroup>
29+
<TargetPathWithTargetPlatformMoniker Include="$(PkgFluid_Core)\lib\netstandard2.0\Fluid.dll" IncludeRuntimeDependency="false" />
30+
<TargetPathWithTargetPlatformMoniker Include="$(PkgMicrosoft_Extensions_FileProviders_Abstractions)\lib\netstandard1.0\Microsoft.Extensions.FileProviders.Abstractions.dll" IncludeRuntimeDependency="false" />
31+
<TargetPathWithTargetPlatformMoniker Include="$(PkgParlot)\lib\netstandard2.0\Parlot.dll" IncludeRuntimeDependency="false" />
32+
<TargetPathWithTargetPlatformMoniker Include="$(PkgSystem_Text_Encodings_Web)\lib\netstandard2.0\System.Text.Encodings.Web.dll" IncludeRuntimeDependency="false" />
33+
</ItemGroup>
34+
</Target>
35+
36+
<ItemGroup>
37+
<EmbeddedResource Include="Templates\*.liquid" />
38+
</ItemGroup>
39+
40+
<ItemGroup>
41+
<Compile Include="..\Jint\HighPerformance\*.cs" LinkBase="Referenced" />
42+
</ItemGroup>
43+
44+
</Project>
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using Microsoft.CodeAnalysis.CSharp.Syntax;
2+
3+
namespace Jint.SourceGenerator;
4+
5+
internal class ObjectDefinition
6+
{
7+
public ObjectDefinition(
8+
ClassDeclarationSyntax syntax,
9+
List<FunctionDefinition> functions,
10+
List<PropertyDefinition> properties)
11+
{
12+
Namespace = SourceGenerationHelper.GetNamespace(syntax);
13+
Name = syntax.Identifier.ToString();
14+
Syntax = syntax;
15+
Functions = functions;
16+
Properties = properties;
17+
18+
PropertyLookup = SourceGenerationHelper.GenerateLookups(false, this, "str", "match", "_property", 12);
19+
ValueLookup = SourceGenerationHelper.GenerateLookups(true, this, "str", "match", "", 12);
20+
}
21+
22+
public string Namespace { get; }
23+
24+
public string Name { get; }
25+
public ClassDeclarationSyntax Syntax { get; }
26+
27+
public List<FunctionDefinition> Functions { get; }
28+
public List<PropertyDefinition> Properties { get; }
29+
public string PropertyLookup { get; }
30+
public string ValueLookup { get; }
31+
32+
}
+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
using System.Collections.Immutable;
2+
using System.Text;
3+
using Fluid;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
using Microsoft.CodeAnalysis.Text;
7+
8+
namespace Jint.SourceGenerator
9+
{
10+
[Generator]
11+
public class ObjectGenerator : IIncrementalGenerator
12+
{
13+
private IFluidTemplate _objectTemplate = null!;
14+
15+
private const string JsObjectAttribute = "Jint.JsObjectAttribute";
16+
private const string JsFunctionAttribute = "Jint.JsFunctionAttribute";
17+
18+
public void Initialize(IncrementalGeneratorInitializationContext context)
19+
{
20+
using var stream = new StreamReader(typeof(ObjectGenerator).Assembly.GetManifestResourceStream(typeof(ObjectGenerator), "Templates.JsObject.liquid")!);
21+
var template = stream.ReadToEnd();
22+
23+
var parser = new FluidParser();
24+
_objectTemplate = parser.Parse(template);
25+
26+
context.RegisterPostInitializationOutput(ctx => ctx.AddSource(
27+
"Attributes.g.cs", SourceText.From(SourceGenerationHelper.Attributes, Encoding.UTF8)));
28+
29+
IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations = context.SyntaxProvider
30+
.CreateSyntaxProvider(
31+
predicate: static (s, _) => IsSyntaxTargetForGeneration(s),
32+
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx))
33+
.Where(static m => m is not null)!;
34+
35+
IncrementalValueProvider<(Compilation, ImmutableArray<ClassDeclarationSyntax>)> compilationAndClasses
36+
= context.CompilationProvider.Combine(classDeclarations.Collect());
37+
38+
context.RegisterSourceOutput(compilationAndClasses,
39+
(spc, source) => Execute(source.Item1, source.Item2, spc));
40+
}
41+
42+
private static bool IsSyntaxTargetForGeneration(SyntaxNode node)
43+
=> node is ClassDeclarationSyntax { AttributeLists.Count: > 0 };
44+
45+
private static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
46+
{
47+
var classDeclarationSyntax = (ClassDeclarationSyntax) context.Node;
48+
49+
// loop through all the attributes on the method
50+
foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists)
51+
{
52+
foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
53+
{
54+
var symbolInfo = context.SemanticModel.GetSymbolInfo(attributeSyntax);
55+
IMethodSymbol symbol;
56+
if (symbolInfo.Symbol is IMethodSymbol methodSymbol)
57+
{
58+
symbol = methodSymbol;
59+
}
60+
else if (symbolInfo.CandidateSymbols.Length > 0 && symbolInfo.CandidateSymbols[0] is IMethodSymbol fromCandidate)
61+
{
62+
symbol = fromCandidate;
63+
}
64+
else
65+
{
66+
// weird, we couldn't get the symbol, ignore it
67+
continue;
68+
}
69+
70+
INamedTypeSymbol attributeContainingTypeSymbol = symbol.ContainingType;
71+
string fullName = attributeContainingTypeSymbol.ToDisplayString();
72+
73+
// Is the attribute the [EnumExtensions] attribute?
74+
if (fullName == JsObjectAttribute)
75+
{
76+
// return the enum
77+
return classDeclarationSyntax;
78+
}
79+
}
80+
}
81+
82+
// we didn't find the attribute we were looking for
83+
return null;
84+
}
85+
86+
private void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes, SourceProductionContext context)
87+
{
88+
if (classes.IsDefaultOrEmpty)
89+
{
90+
// nothing to do yet
91+
return;
92+
}
93+
94+
var distinctEnums = classes.Distinct();
95+
var toGenerate = GetTypesToGenerate(compilation, distinctEnums, context.CancellationToken);
96+
97+
var templateOptions = new TemplateOptions { MemberAccessStrategy = UnsafeMemberAccessStrategy.Instance };
98+
foreach (var target in toGenerate)
99+
{
100+
// generate the source code and add it to the output
101+
var templateContext = new TemplateContext(target, templateOptions);
102+
var result = _objectTemplate.Render(templateContext);
103+
context.AddSource(target.Name + ".g.cs", SourceText.From(result, Encoding.UTF8));
104+
}
105+
}
106+
107+
private static List<ObjectDefinition> GetTypesToGenerate(Compilation compilation, IEnumerable<ClassDeclarationSyntax> classes, CancellationToken ct)
108+
{
109+
var classesToGenerate = new List<ObjectDefinition>();
110+
INamedTypeSymbol? enumAttribute = compilation.GetTypeByMetadataName(JsObjectAttribute);
111+
if (enumAttribute == null)
112+
{
113+
// nothing to do if this type isn't available
114+
return classesToGenerate;
115+
}
116+
117+
foreach (var classDeclarationSyntax in classes)
118+
{
119+
// stop if we're asked to
120+
ct.ThrowIfCancellationRequested();
121+
122+
SemanticModel semanticModel = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree);
123+
if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol)
124+
{
125+
// something went wrong
126+
continue;
127+
}
128+
129+
var allMEmbers = classSymbol.GetMembers();
130+
var functions = new List<FunctionDefinition>();
131+
var properties = new List<PropertyDefinition>();
132+
133+
foreach (ISymbol member in allMEmbers)
134+
{
135+
if (member is IMethodSymbol method)
136+
{
137+
foreach (var attribute in method.GetAttributes())
138+
{
139+
if (attribute.AttributeClass?.Name == "JsFunctionAttribute")
140+
{
141+
functions.Add(new FunctionDefinition(method, attribute));
142+
break;
143+
}
144+
}
145+
}
146+
147+
if (member is IPropertySymbol property)
148+
{
149+
foreach (var attribute in property.GetAttributes())
150+
{
151+
if (attribute.AttributeClass?.Name == "JsPropertyAttribute")
152+
{
153+
properties.Add(new PropertyDefinition(property, attribute));
154+
break;
155+
}
156+
}
157+
}
158+
}
159+
160+
functions.Sort();
161+
classesToGenerate.Add(new ObjectDefinition(classDeclarationSyntax, functions, properties));
162+
}
163+
164+
return classesToGenerate;
165+
}
166+
}
167+
}
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
4+
namespace Jint.SourceGenerator;
5+
6+
internal class PropertyDefinition : IComparable
7+
{
8+
public PropertyDefinition(IPropertySymbol property, AttributeData attribute)
9+
{
10+
var attributes = SourceGenerationHelper.GetAttributes((AttributeSyntax) attribute.ApplicationSyntaxReference!.GetSyntax());
11+
12+
attributes.TryGetValue("Name", out var name);
13+
14+
if (string.IsNullOrWhiteSpace(name))
15+
{
16+
name = property.Name;
17+
if (char.IsUpper(name[0]))
18+
{
19+
name = char.ToLowerInvariant(name[0]) + name.Substring(1);
20+
}
21+
}
22+
Name = name ?? throw new InvalidOperationException("Could not get name");
23+
ClrName = property.Name;
24+
IsStatic = property.IsStatic;
25+
26+
Accessor = IsStatic ? property.ContainingType.Name + "." + ClrName : ClrName;
27+
}
28+
29+
public string Name { get; }
30+
public string ClrName { get; }
31+
public string Accessor { get; }
32+
public bool IsStatic { get; }
33+
34+
public int CompareTo(object obj)
35+
{
36+
return string.Compare(Name, (obj as PropertyDefinition)?.Name, StringComparison.Ordinal);
37+
}
38+
}

0 commit comments

Comments
 (0)