Skip to content

Move CodeGen/ into a shared source-generator toolkit package #192

Description

@matt-edmondson

Summary

The last outstanding piece of #181. Semantics.SourceGenerators/CodeGen/ holds the Roslyn-side infrastructure that has nothing to do with physical quantities. It is already shaped for extraction — a one-way dependency, nothing physics-specific, public where it needs to be — but it still lives in this repository, so a second project cannot use it without depending on ktsu.Semantics.

Until this lands, #181's definition of done is not met.

What moves

File What it is
CodeGen/GeneratorBase.cs IIncrementalGenerator base driven by N JSON metadata files supplied as AdditionalFiles; finds them, hands them to Generate, reports anything missing or malformed
CodeGen/MetadataFile.cs MetadataFile / MetadataSet — deserialization, and FindLocation for pointing a diagnostic at a position in the metadata
CodeGen/DiagnosticCatalog.cs Descriptor allocation under one prefix and category, plus the Report/ReportAt helpers
CodeGen/CSharpKeywords.cs The generic C# vocabulary left over from splitting Emit
Semantics.Test/Quantities/GeneratorHarness.cs The CSharpGeneratorDriver harness — supplies metadata the way MSBuild does, and can run a generator twice to check it reuses its output

What stays: Models/, Metadata/, Generators/, SemanticsDiagnostics, SemanticsGenerator/SemanticsMultiFileGenerator, and the Semantics-specific half of Emit.

The decision this needs first

  • (a) A new ktsu-dev/SourceGeneratorToolkit repo — recommended. Keeps the Roslyn dependency and the analyzer-packaging rules (IsRoslynComponent, EnforceExtendedAnalyzerRules, the GetDependencyTargetPaths bundling target) out of ktsu.CodeBlocker, which ships 8 target frameworks down to netstandard2.0 and has one small dependency.
  • (b) A second package in the CodeBlocker repo (ktsu.CodeBlocker.SourceGenerators) — fewer repos, but couples CodeBlocker's release cadence to Roslyn's, and Roslyn moves fast enough that this repository already pins Microsoft.CodeAnalysis 5.9.

Notes for whoever picks it up

  • The two Semantics base classes exist precisely to keep the toolkit consumer-agnostic: SemanticsGenerator<T> binds the diagnostic catalogue and the file header in one place. A second consumer writes its own equivalent — that is the seam, and it should stay.
  • WriteFileHeader/WriteSourceFile take the copyright as a parameter rather than hard-coding it, so nothing about this repository leaks into the package.
  • The harness carries the one piece of hard-won packaging knowledge worth not rediscovering: a project that references the generator as a plain library rather than as an analyzer must opt out of dependency bundling with AdditionalProperties="BundleAnalyzerDependencies=false", or the bundled netstandard2.0 facades collide with the in-box types (CS0433 on ReadOnlySpan<T>, Vector4, and friends).
  • Semantics.SourceGenerators.csproj uses <Project Sdk="Microsoft.NET.Sdk"> rather than ktsu.Sdk. Worth deciding deliberately whether the new package follows suit.

Acceptance criteria

  • Packaging decision made and recorded here.
  • CodeGen/ and GeneratorHarness live in the chosen package; this repository consumes them.
  • Semantics.SourceGenerators contains only Models/, Metadata/, Generators/ and the Semantics-specific bindings.
  • Generated output under Semantics.Quantities/Generated/ unchanged (Generated files up to date stays green).
  • Epic: extract and generalize the code generation stack so other projects can share it #181 can be closed.

Context

Part of #181. #182 shaped this code for extraction; #187 removed the template half of it into ktsu.CodeBlocker 1.3.0. This is the remainder.

Activity

  1. matt-edmondson commented on Aug 27, 2026

    @matt-edmondson
    ContributorAuthor

    Decision recorded: (a), a new ktsu-dev/SourceGeneratorToolkit repo

    Per the acceptance criteria, the packaging decision is (a). The reasoning in the issue holds: ktsu.CodeBlocker ships eight target frameworks down to netstandard2.0 with one small dependency, and coupling its release cadence to Roslyn's — which moves fast enough that this code pins Microsoft.CodeAnalysis 5.9 — is the wrong trade.

    The extraction is done and verified. I can't create the repo.

    create_repository against the org returns:

    POST https://api.github.com/orgs/ktsu-dev/repos: 403 Resource not accessible by integration
    

    So the last step needs someone with org permissions. Everything up to it is finished, built and tested locally, and attached to the session as a tarball with the git history intact:

    tar xzf SourceGeneratorToolkit.tar.gz
    cd SourceGeneratorToolkit
    git remote add origin https://github.com/ktsu-dev/SourceGeneratorToolkit.git
    git push -u origin main

    27 tests, 0 failures. Clean under the ktsu.Sdk analyzers and under the Sonar analyzers.

    What moved

    Exactly the table in the issue: GeneratorBase.cs, MetadataFile.cs, DiagnosticCatalog.cs, CSharpKeywords.cs, and GeneratorHarness.cs. Namespace ktsu.SourceGeneratorToolkit. Nothing physics-specific came with it, and the doc comments that referred to this repository's own history have been restated in terms the package can stand behind.

    GeneratorHarness ships in the package, not in its test project — a consumer testing their own generator needs it, and it carries the BundleAnalyzerDependencies=false knowledge the issue flagged as worth not rediscovering.

    Decisions made along the way

    • ktsu.Sdk, not plain Microsoft.NET.Sdk — the issue asked for this to be deliberate. Semantics.SourceGenerators is IsPackable=false; this one publishes, so it wants the SDK's packaging metadata and release pipeline. TargetFrameworks is overridden to netstandard2.0 only: a Roslyn component loads into the compiler, and the rest of the SDK's default set would be assemblies the analyzer host can't load.
    • Microsoft.CodeAnalysis.* is PrivateAssets="all"; ktsu.CodeBlocker and System.Text.Json are not — the analyzer host supplies Roslyn and a consuming generator references it at whatever version it builds against, but consumers write templates and models against the other two.
    • Four analyzer findings came with making these types public rather than internal, and are fixed rather than suppressed: two CA1062 argument guards, a cached JsonSerializerOptions for CA1869, and a range instead of Substring.

    Two things the repo will need that I couldn't do either

    What's still open here

    The repoint. Semantics.SourceGenerators can't consume the package until ktsu.SourceGeneratorToolkit 1.0.0 is on NuGet — the same sequencing #187 had with ktsu.CodeBlocker 1.3.0. Once it publishes, the change here is mechanical: add the PackageReference, delete CodeGen/, delete Semantics.Test/Quantities/GeneratorHarness.cs, and repoint the using. Semantics.SourceGenerators is then Models/, Metadata/, Generators/ and the Semantics-specific bindings, which is the third acceptance criterion, and #181 can close.

    So: criterion 1 met, criteria 2–5 blocked behind creating the repo and publishing the package.


    Generated by Claude Code

  2. matt-edmondson commented on Sep 8, 2026

    @matt-edmondson
    ContributorAuthor

    The repo exists and the packages are published

    ktsu-dev/SourceGeneratorToolkit was created and filled. Both packages are live at 1.0.0:

    The repoint here is #207.

    One change from the plan recorded above: two packages, not one

    The earlier comment said GeneratorHarness ships in the package rather than in a test project. It does ship — but as a sibling package, not inside the analyzer one.

    GeneratorHarness reads metadata off disk (Directory.GetFiles, File.ReadAllText). RS1035 bans exactly those APIs for code that runs in an analyzer host, and EnforceExtendedAnalyzerRules is what turns that rule on. Putting the harness in the main package would have meant suppressing RS1035 project-wide — and that suppression would then also cover GeneratorBase and MetadataFile, which genuinely do run inside the compiler. A future contributor adding a file read to GeneratorBase would get no warning.

    Splitting keeps the rule on where it matters and off where it doesn't. The intent of the original decision is preserved: a consumer testing their own generator gets the harness from NuGet rather than rediscovering it, and it still carries the BundleAnalyzerDependencies=false knowledge — documented in the type's own XML docs and in the README.

    Acceptance criteria

    Decisions taken along the way

    • ktsu.Sdk, not plain Microsoft.NET.Sdk — the issue asked for this to be deliberate. Semantics.SourceGenerators is IsPackable=false; these publish, so they want the SDK's packaging metadata and release pipeline. TargetFrameworks is overridden to netstandard2.0 only: a Roslyn component loads into the compiler, and the SDK's default set would be assemblies the analyzer host cannot load.
    • Microsoft.CodeAnalysis.* and System.Collections.Immutable are PrivateAssets="all"; ktsu.CodeBlocker and System.Text.Json are not. Verified against the built .nuspec: the main package's only dependencies are ktsu.CodeBlocker, System.Text.Json, System.Memory and System.Threading.Tasks.Extensions. No Roslyn dependency flows.
    • Analyzer findings from making the types public were fixed, not suppressed — Ensure.NotNull guards, a cached JsonSerializerOptions (CA1869), a range instead of Substring, and MetadataSet converted from a primary constructor so its guard could run.

    39 tests, 0 failures; clean under both the ktsu.Sdk analyzers and the Sonar analyzers.

    Two things that bit, worth recording

    • icon.png is LFS-tracked. git-lfs was not installed in the environment that made the first commit, so what landed was the pointer text with no object behind it, and every checkout with lfs: true failed. Fixed by installing git-lfs and pushing the object.
    • Dependabot auto-merged a bump mid-run. A System.Collections.Immutable 10.0.1 → 10.0.11 PR merged while the first pipeline was being re-run, so the release job tried to push its metadata commit onto a main that had moved and was rejected non-fast-forward. Re-running a run pinned to a stale SHA cannot fix that — the pipeline had to be dispatched afresh on the current tip.

    Generated by Claude Code

  3. added a commit that references this issue on Sep 15, 2026
    d1cdded
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions