Skip to content

Commit e471fab

Browse files
add NotNot.GodotNet.SourceGen nuget package
1 parent 1b270a7 commit e471fab

13 files changed

+1236
-6
lines changed

src/nuget/NotNot.AppSettings/NotNot.AppSettings.csproj

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@
8686
<RepositoryUrl>https://github.com/NotNotTech/NotNot-MonoRepo</RepositoryUrl>
8787
<PackageIcon>[!!]-logos_red_cropped.png</PackageIcon>
8888
<PackageReadmeFile>README.md</PackageReadmeFile>
89-
<PackageTags>AppSettings; Source Generator; SourceGenerator; Json; NotNot; Novaleaf;</PackageTags>
90-
<Description>Automatically create strongly typed C# settings classes from AppSettings.json. Uses Source Generators. Includes a simple deserialization helper for when you are using Dependency Injection, or not.</Description>
9189
<!--if minVer runs properly, the following Version gets replaced. if it doesnt, restart visual studio and try again.-->
9290
<Version>0.0.0-0.invalidMinverProcess</Version>
9391
</PropertyGroup>
@@ -104,17 +102,24 @@
104102

105103
</ItemGroup>
106104

105+
106+
107+
<PropertyGroup>
108+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
109+
<Deterministic>true</Deterministic>
110+
</PropertyGroup>
111+
112+
113+
<!--project specific nuget settings-->
107114
<PropertyGroup>
108115
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
109116
<Title>NotNot.AppSettings</Title>
110117
<Authors>Jason Swearingen</Authors>
118+
<PackageTags>AppSettings; Source Generator; SourceGenerator; Json; NotNot; Novaleaf;</PackageTags>
119+
<Description>Automatically create strongly typed C# settings classes from AppSettings.json. Uses Source Generators. Includes a simple deserialization helper for when you are using Dependency Injection, or not.</Description>
111120

112121
</PropertyGroup>
113122

114-
<PropertyGroup>
115-
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
116-
<Deterministic>true</Deterministic>
117-
</PropertyGroup>
118123

119124
<ItemGroup>
120125
<!--required for sourcegen-->

src/nuget/NotNot.Bcl/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,10 @@ a "kitchen sink" utility library.
55
This is used by other NotNot libraries and Jason's private projects.
66
It is high quality and can be used independently, but doesn't have any documentation yet.
77

8+
## NotNot.Bcl.Core
9+
10+
`NotNot.Bcl` is mostly a wrapper around `NotNot.Bcl.Core`. Likely there is some refactoring between the two, but the seperation is meant to keep the core library as cross-platform as possible.
11+
12+
## interested?
13+
814
Raise an issue if you are interested and I will prioritize it sooner.
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.Diagnostics;
5+
using System.Text;
6+
using System.Text.RegularExpressions;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using Microsoft.CodeAnalysis.Text;
10+
using NotNot.GodotNet.SourceGen.Helpers;
11+
12+
namespace NotNot.GodotNet.SourceGen.Generators.Modular;
13+
14+
15+
public abstract class ModularGenerator_Base : IIncrementalGenerator
16+
{
17+
public List<string>? TargetAttributes = new();
18+
19+
public List<Regex>? TargetAdditionalFiles;
20+
21+
22+
23+
24+
25+
/// <summary>
26+
/// Predicate method to identify class declarations with attributes.
27+
/// </summary>
28+
/// <param name="node">The syntax node to check.</param>
29+
/// <param name="cancellationToken">The cancellation token.</param>
30+
/// <returns>True if the node is a class declaration with attributes, otherwise false.</returns>
31+
private bool Predicate(SyntaxNode node, CancellationToken cancellationToken)
32+
{
33+
return node is ClassDeclarationSyntax { AttributeLists: { Count: > 0 } };
34+
}
35+
36+
/// <summary>
37+
/// Transform method to extract class declarations with the NotNotScene attribute.
38+
/// </summary>
39+
/// <param name="context">The generator syntax context.</param>
40+
/// <param name="cancellationToken">The cancellation token.</param>
41+
/// <returns>The class declaration if it contains the NotNotScene attribute, otherwise null.</returns>
42+
private ClassDeclarationSyntax? Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken)
43+
{
44+
var classDeclaration = (ClassDeclarationSyntax)context.Node;
45+
foreach (var attributeList in classDeclaration.AttributeLists)
46+
{
47+
foreach (var attribute in attributeList.Attributes)
48+
{
49+
var name = attribute.Name.ToString();
50+
51+
if (this.TargetAttributes.Contains(name))
52+
{
53+
return classDeclaration;
54+
}
55+
}
56+
}
57+
return null;
58+
}
59+
60+
61+
/// <summary>
62+
/// Initializes the incremental generator.
63+
/// </summary>
64+
/// <param name="context">The context for initializing the generator.</param>
65+
public void Initialize(IncrementalGeneratorInitializationContext context)
66+
{
67+
////to debug
68+
//Debugger.Launch();
69+
70+
71+
//add the project.godot as this is our "project root folder"
72+
TargetAdditionalFiles.Add(new Regex(@"project\.godot$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled));
73+
74+
75+
// Register a syntax provider to find class declarations with attributes
76+
IncrementalValueProvider<ImmutableArray<ClassDeclarationSyntax?>> classDeclarations;
77+
78+
//if (this.TargetAttributes is null)
79+
//{
80+
// classDeclarations = new();
81+
//}
82+
//else
83+
{
84+
classDeclarations = context.SyntaxProvider
85+
.CreateSyntaxProvider(Predicate, Transform)
86+
.Where(static cls => cls != null)
87+
.Collect();
88+
}
89+
90+
HashSet<string> allAdditionalFilePaths = new();
91+
92+
// Collect additional files using multiple regex patterns
93+
var additionalFiles = context.AdditionalTextsProvider.Where(file =>
94+
{
95+
//list of all files
96+
allAdditionalFilePaths.Add(file.Path);
97+
foreach (var regex in TargetAdditionalFiles)
98+
{
99+
if (regex.IsMatch(file.Path))
100+
{
101+
return true;
102+
}
103+
}
104+
return false;
105+
});
106+
107+
108+
109+
110+
111+
// Transform additional files to a single Dictionary entry
112+
var combinedSourceTextsProvider = additionalFiles.Collect().Select((files, ct) =>
113+
{
114+
var combinedFiles = new Dictionary<string, SourceText>();
115+
foreach (var file in files)
116+
{
117+
var sourceText = file.GetText(ct);
118+
if (sourceText != null)
119+
{
120+
combinedFiles[file.Path] = sourceText;
121+
}
122+
}
123+
return combinedFiles;
124+
});
125+
126+
// Retrieve the root namespace from the AnalyzerConfigOptionsProvider
127+
var namespaceProvider = context.AnalyzerConfigOptionsProvider
128+
.Select(static (provider, ct) =>
129+
{
130+
// Try to get the "build_property.rootnamespace" from GlobalOptions
131+
provider.GlobalOptions.TryGetValue("build_property.rootnamespace", out string? rootNamespace);
132+
return rootNamespace; // Return the root namespace or null if not found
133+
});
134+
135+
// Combine the namespace provider with the combined source texts provider and class declarations
136+
var combinedProvider = namespaceProvider.Combine(combinedSourceTextsProvider).Combine(classDeclarations);
137+
138+
// Register the source output to pass the combined provider and class declarations to the ExecuteGenerator method
139+
context.RegisterSourceOutput(
140+
combinedProvider,
141+
(spc, content) =>
142+
{
143+
var rootNamespace = content.Left.Left; // The root namespace
144+
var combinedSourceTexts = content.Left.Right; // The combined source texts
145+
var classes = content.Right; // The class declarations
146+
147+
ExecuteGenerator(spc, rootNamespace, combinedSourceTexts, classes, allAdditionalFilePaths);
148+
});
149+
}
150+
151+
152+
/// <summary>
153+
/// Executes the generator to produce partial classes.
154+
/// </summary>
155+
/// <param name="context">The source production context.</param>
156+
/// <param name="rootNamespace">The root namespace of the project.</param>
157+
/// <param name="additionalFiles">The additional files to process.</param>
158+
/// <param name="classes">The class declarations to process.</param>
159+
public void ExecuteGenerator(SourceProductionContext context, string? rootNamespace, Dictionary<string, SourceText> additionalFiles, ImmutableArray<ClassDeclarationSyntax> classes, HashSet<string> allAdditionalFilePaths)
160+
{
161+
var config = new GodotResourceGeneratorContextConfig
162+
{
163+
Context = context,
164+
RootNamespace = rootNamespace,
165+
AdditionalFiles = additionalFiles,
166+
Classes = classes,
167+
AllAdditionalFilePaths = allAdditionalFilePaths,
168+
};
169+
config.Initialize();
170+
171+
GeneratePartialClasses(config);
172+
}
173+
174+
/// <summary>
175+
/// Generates the partial classes based on the configuration.
176+
/// </summary>
177+
/// <param name="config">The configuration object containing context and parameters.</param>
178+
public abstract void GeneratePartialClasses(GodotResourceGeneratorContextConfig config);
179+
180+
}

0 commit comments

Comments
 (0)