Skip to content

Compile-time extension/option discovery source generator#371

Merged
jeremydmiller merged 1 commit into
mainfrom
feat-option-discovery-sourcegen
May 26, 2026
Merged

Compile-time extension/option discovery source generator#371
jeremydmiller merged 1 commit into
mainfrom
feat-option-discovery-sourcegen

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

What

Generalizes the existing command discovery pattern (CommandDiscoveryGeneratorDiscoveredCommands manifest) to extension / option types, so consuming Critter Stack frameworks can eliminate runtime assembly scanning for their extensions (e.g. Wolverine's ExtensionLoader + AssemblyFinder.FindAssemblies filesystem probe + Assembly.Load).

Piece Role
IJasperFxExtension New marker interface. IServiceRegistrations now extends it; framework extension interfaces (Wolverine's IWolverineExtension, etc.) extend it to be discoverable.
ExtensionDiscoveryGenerator (JasperFx.SourceGenerator) Emits a per-assembly JasperFx.Generated.DiscoveredExtensions.ExtensionTypes manifest, gated to assemblies carrying a [JasperFxAssembly]-derived attribute or executable (entry) assemblies.
GeneratedExtensionManifest Runtime reader that aggregates the manifests from loaded assemblies (no filesystem probe), with AnyManifestPresent() so consumers can fall back to reflective discovery when the generator isn't active.
JasperFx.SourceGenerator.Tests New test project.

Discovery (deduped, two sources)

  1. The extension type(s) declared by the assembly's attribute — generic args of [WolverineModule<T>]-style attributes and typeof(...) constructor args of [JasperFxAssembly(typeof(T))].
  2. Concrete classes implementing IJasperFxExtension in the compilation.

This "support both" approach is migration-friendly: existing [WolverineModule<T>] / [JasperFxAssembly(typeof(T))] declarations keep working, and new code can simply implement the marker.

Tests

ExtensionDiscoveryGeneratorTests covers: the eligibility gate (executable vs [JasperFxAssembly] vs plain library), marker-interface discovery, the generic-attribute declared-type path, dedup, and abstract/static exclusion. 5/5 green; JasperFx core builds clean.

Scope / follow-up

This PR is the JasperFx-side enabler — nothing consumes the manifest yet, so it's inert until a framework opts in. A follow-up Wolverine issue will adapt ExtensionLoader to consume GeneratedExtensionManifest (with the goal of removing ExtensionLoader's runtime scan entirely), make IWolverineExtension : IJasperFxExtension, and reference the analyzer from WolverineFx.

Heads-up: this touches JasperFx.SourceGenerator, where the service-provider-frame branch has in-progress work on CommandDiscoveryGenerator — these add separate files and shouldn't conflict, but worth a rebase check at merge.

🤖 Generated with Claude Code

Generalizes the existing command-discovery pattern to "extension"/"option" types so
consuming frameworks (Wolverine, Marten, ...) can eliminate runtime assembly scanning
(e.g. Wolverine's ExtensionLoader + AssemblyFinder.FindAssemblies filesystem probe).

- IJasperFxExtension: marker interface; IServiceRegistrations now extends it. Framework
  extension interfaces (e.g. Wolverine's IWolverineExtension) extend it to be discoverable.
- ExtensionDiscoveryGenerator (JasperFx.SourceGenerator): emits a per-assembly
  JasperFx.Generated.DiscoveredExtensions.ExtensionTypes manifest, gated to assemblies that
  carry a [JasperFxAssembly]-derived attribute OR are executable (entry) assemblies. Discovers
  via two deduped sources: (1) the type declared by the assembly's [JasperFxAssembly] /
  [WolverineModule<T>] attribute (generic args + typeof ctor args), and (2) concrete classes
  implementing IJasperFxExtension.
- GeneratedExtensionManifest: runtime reader that aggregates the manifests from loaded
  assemblies; consumers filter by their own extension interface.
- JasperFx.SourceGenerator.Tests: covers the eligibility gate, both discovery sources, dedup,
  and abstract/static exclusion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit b96af6c into main May 26, 2026
1 check passed
erdtsieck pushed a commit to erdtsieck/jasperfx that referenced this pull request May 29, 2026
…rFx#373)

Extends the compile-time discovery from JasperFx#371 so the source generator emits actual
IServiceCollection registrations, not just a type list.

- Add [JasperFxService(typeof(serviceType), ServiceLifetime)] to the JasperFx assembly
  (AllowMultiple), letting a concrete class declare one or more DI registrations.
- New ServiceRegistrationGenerator emits a per-assembly
  JasperFx.Generated.GeneratedServiceRegistrations.Register(IServiceCollection) full of plain
  services.Add(new ServiceDescriptor(...)) calls -- reflection-free, trim/AOT-clean. Open-generic
  service types (typeof(IValidator<>)) are closed from the implementation's implemented interface
  (=> IValidator<Foo>). Same eligibility gate as ExtensionDiscoveryGenerator ([JasperFxAssembly]
  or executable).
- Add runtime aggregator GeneratedExtensionManifest.RegisterAllDiscoveredServices(IServiceCollection)
  (+ an assemblies overload + AnyServiceRegistrationsPresent) that invokes each loaded assembly's
  generated Register method; trim-suppressed; no-op fallback when the generator wasn't run.
- Tests: generator text + eligibility + open/closed generics + multiple attributes per impl + a
  compile-verification test (emitted code compiles against the full framework reference set) +
  runtime aggregator tests. README documents [JasperFxService] and the aggregator and now lists
  all five bundled generators.

Scope: this first increment is the attribute mechanism (option A), covering the IServiceRegistrations,
IWolverineExtension (Singleton-against-interface) and closed-generic IValidator<Foo> shapes. The
MSBuild-config path for external interfaces (option C in the issue) is deferred to a follow-up.

Refs JasperFx#373.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant