| | | 1 | | using System.Text; |
| | | 2 | | |
| | | 3 | | using Microsoft.CodeAnalysis; |
| | | 4 | | using Microsoft.CodeAnalysis.Text; |
| | | 5 | | |
| | | 6 | | using NexusLabs.Needlr.Generators.CodeGen; |
| | | 7 | | using NexusLabs.Needlr.Generators.Models; |
| | | 8 | | |
| | | 9 | | namespace NexusLabs.Needlr.Generators; |
| | | 10 | | |
| | | 11 | | /// <summary> |
| | | 12 | | /// Incremental source generator that emits a public constructor for partial classes |
| | | 13 | | /// eligible for generated-constructor generation, either via |
| | | 14 | | /// <c>[GenerateConstructor]</c> on the class or a positive field-level constructor |
| | | 15 | | /// guard trigger. |
| | | 16 | | /// </summary> |
| | | 17 | | /// <remarks> |
| | | 18 | | /// The pipeline is genuinely per-type: <see cref="ConstructorGenerationDiscoveryHelper.TryCreateCanonicalModel"/> |
| | | 19 | | /// converts each candidate class declaration directly into an equatable |
| | | 20 | | /// <see cref="GeneratedConstructorModel"/> (never passing an <see cref="INamedTypeSymbol"/> |
| | | 21 | | /// further down the pipeline), and the only cross-type context combined into each |
| | | 22 | | /// model is a small, independently-cacheable <see cref="GeneratedConstructorEmitContext"/> |
| | | 23 | | /// -- never the whole <see cref="Compilation"/>. As a result, editing one type's fields |
| | | 24 | | /// recomputes and re-emits only that type; every other eligible type's cached model and |
| | | 25 | | /// generated source are reused unchanged. |
| | | 26 | | /// </remarks> |
| | | 27 | | [Generator(LanguageNames.CSharp)] |
| | | 28 | | public sealed class GeneratedConstructorGenerator : IIncrementalGenerator |
| | | 29 | | { |
| | | 30 | | public void Initialize(IncrementalGeneratorInitializationContext context) |
| | | 31 | | { |
| | 67 | 32 | | var candidateModels = context.SyntaxProvider.CreateSyntaxProvider( |
| | 2858 | 33 | | predicate: static (node, _) => ConstructorGenerationDiscoveryHelper.IsCandidateClassDeclaration(node), |
| | 78 | 34 | | transform: static (ctx, _) => ConstructorGenerationDiscoveryHelper.TryCreateCanonicalModel(ctx)) |
| | 67 | 35 | | .WithTrackingName(GeneratedConstructorTrackingNames.Candidates); |
| | | 36 | | |
| | 67 | 37 | | var models = candidateModels |
| | 75 | 38 | | .Where(static model => model is not null) |
| | 66 | 39 | | .Select(static (model, _) => model!.Value) |
| | 67 | 40 | | .WithTrackingName(GeneratedConstructorTrackingNames.Models); |
| | | 41 | | |
| | | 42 | | // Only cheap, independently-cacheable scalars are derived from the compilation |
| | | 43 | | // and analyzer config options -- never the Compilation or |
| | | 44 | | // AnalyzerConfigOptionsProvider themselves -- so an edit that leaves both the |
| | | 45 | | // assembly name and the breadcrumb MSBuild property unchanged reuses the |
| | | 46 | | // cached emission context instead of forcing every model to recombine with a |
| | | 47 | | // brand-new Compilation snapshot. |
| | 67 | 48 | | var assemblyName = context.CompilationProvider |
| | 70 | 49 | | .Select(static (compilation, _) => compilation.AssemblyName ?? "Generated") |
| | 67 | 50 | | .WithTrackingName(GeneratedConstructorTrackingNames.AssemblyName); |
| | | 51 | | |
| | 67 | 52 | | var breadcrumbLevel = context.AnalyzerConfigOptionsProvider |
| | 67 | 53 | | .Select(static (configOptions, _) => TypeRegistryGenerator.GetBreadcrumbLevel(configOptions)) |
| | 67 | 54 | | .WithTrackingName(GeneratedConstructorTrackingNames.BreadcrumbLevel); |
| | | 55 | | |
| | 67 | 56 | | var emitContext = assemblyName.Combine(breadcrumbLevel) |
| | 67 | 57 | | .Select(static (pair, _) => new GeneratedConstructorEmitContext(pair.Left, pair.Right)) |
| | 67 | 58 | | .WithTrackingName(GeneratedConstructorTrackingNames.EmitContext); |
| | | 59 | | |
| | 67 | 60 | | var modelsWithContext = models.Combine(emitContext) |
| | 67 | 61 | | .WithTrackingName(GeneratedConstructorTrackingNames.Output); |
| | | 62 | | |
| | 67 | 63 | | context.RegisterSourceOutput(modelsWithContext, static (spc, source) => |
| | 67 | 64 | | { |
| | 66 | 65 | | var (model, emitContext) = source; |
| | 66 | 66 | | var breadcrumbs = new BreadcrumbWriter(emitContext.BreadcrumbLevel); |
| | 66 | 67 | | var generatedSource = GeneratedConstructorCodeGenerator.GenerateConstructorSource(model, emitContext.Assembl |
| | 66 | 68 | | spc.AddSource(GeneratedConstructorCodeGenerator.BuildHintName(model), SourceText.From(generatedSource, Encod |
| | 133 | 69 | | }); |
| | 67 | 70 | | } |
| | | 71 | | } |