Skip to content

Restore the ThisAssemblyClass module as an incremental source generator - #306

Merged
rdeago merged 3 commits into
Tenacom:mainfrom
rdeago:issue/295-thisassemblyclass-module
Jul 28, 2026
Merged

Restore the ThisAssemblyClass module as an incremental source generator#306
rdeago merged 3 commits into
Tenacom:mainfrom
rdeago:issue/295-thisassemblyclass-module

Conversation

@rdeago

@rdeago rdeago commented Jul 28, 2026

Copy link
Copy Markdown
Member

Proposed changes

WHY — Versioning Phase 6 (#295, sub-issue of #267): the ThisAssemblyClass module, removed in #158 along with the task-based code generation, returns as the future home of native version constants (Phase 7 will feed it version values). It lands dormantGenerateThisAssemblyClass defaults to false — so no project, including this self-hosted repo, changes behavior, and there is no collision with NBGV's ThisAssembly.

WHERE — SDK module targets (Modules/ThisAssemblyClass), Buildvana.Sdk.SourceGenerators, Buildvana.Sdk.Tasks, docs, and a new Buildvana.Sdk.SourceGenerators.Tests project.

WHAT — A C# project opting in with GenerateThisAssemblyClass=true gets a generated internal static partial ThisAssembly class (name/namespace configurable via ThisAssemblyClassName/ThisAssemblyClassNamespace) with CompilerGenerated and ExcludeFromCodeCoverage attributes, containing public const fields from ThisAssemblyConstant items plus the eight default constants (honoring EnableDefaultThisAssemblyConstants). C# only: any other language raises the new BVSDK2300 warning; invalid constant values raise BVSDK2301 as a compiler diagnostic. VB support is not restored, down to the parser's old Integer/Boolean type aliases.

HOW — Unlike the pre-#158 compiled-task design, generation happens in a Roslyn incremental source generator: IntelliSense sees the class without building, incrementality is content-keyed for free, and no generated .cs file in obj/ can go stale. Since generators cannot see MSBuild items — and version values are computed by targets, too late for CompilerVisibleProperty — a small WriteThisAssemblyConstantsFile task serializes the constants (percent-encoded name=value lines, written only on change) into a tagged AdditionalFiles item the generator reads. The restored type:value parser lives in the generators assembly as the single shared implementation. Reporting a generator diagnostic required analyzer release tracking (AnalyzerReleases.*.md); those files' format is machine-parsed and cannot satisfy markdownlint, so they are excluded in .markdownlint-cli2.jsonc — which also fixes that config's ignores option, previously nested inside config where it had no effect.

Sanity check: dotnet bv pack clean, 84 tests green (51 new generator/parser + 6 new task tests), ReSharper inspectcode reports nothing in changed code, markdownlint clean on all touched files.

Checklist of related issues / discussions

Types of changes

This pull request introduces the following types of changes:

  • Bug fix
  • New feature
  • Test addition / update (no changes to non-test code)
  • Refactor (no changes in public API syntax or semantics)
  • Performance improvement (no changes in public API syntax or semantics)
  • Documentation (docs directory) update
  • Dependency addition / update
  • Changes to the build scripts
  • Changes to CI (workflows, bot / app configurations)
  • Other

Other: markdownlint configuration fix (misplaced ignores option).

Breaking changes

This pull request introduces breaking changes:

  • Yes
  • No

Checklist

  • For all types of changes:
  • For code changes only:
    • The project builds on my machine, via the provided build script, with zero warnings
    • I have added tests that prove my feature works / my fix is effective
    • I have added / modified XML documentation according to changes in code
    • I have checked that all the links I added or modified in XML documentation point to their intended destination
  • For documentation changes (docs directory) only:
    • I have built and tested documentation locally
    • I have checked that all the links I added or modified point to their intended destination

🤖 Generated with Claude Code

Reintroduce the ThisAssemblyClass SDK module, removed in PR Tenacom#158, as a
dormant module (GenerateThisAssemblyClass defaults to false) built on a
Roslyn incremental source generator instead of the old compiled-task
code generation. C# only: enabling the module in any other language
raises the new BVSDK2300 warning.

- ThisAssemblyConstant items are serialized by the new
  WriteThisAssemblyConstantsFile task into a percent-encoded constants
  file under obj/, exposed to the generator as a tagged AdditionalFiles
  item; class name and namespace flow through CompilerVisibleProperty.
- ThisAssemblyClassGenerator emits the class with CompilerGenerated and
  ExcludeFromCodeCoverage attributes; invalid constant values raise
  BVSDK2301 as a compiler diagnostic (with analyzer release tracking).
- The constant-value parser is restored into the source generators
  assembly as the single shared implementation; VB type aliases
  (Integer, Boolean) are gone along with all VB support.
- Default constants and the EnableDefaultThisAssemblyConstants knob are
  preserved from the pre-removal module.
- New Buildvana.Sdk.SourceGenerators.Tests project (generator + parser
  tests); task tests added to Buildvana.Sdk.Tasks.Tests.
- ConstantsSyntax.md trimmed to ThisAssembly constants only; new
  ThisAssemblyClass diagnostics range (2300-2399) in SdkDiagnostics.md.
- markdownlint config: move "ignores" to its documented top-level
  position (it was inert inside "config") and exempt the
  machine-parsed AnalyzerReleases.*.md files.

Closes Tenacom#295

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rdeago rdeago added enhancement [issue/PR] requests / implements new or improved functionality. refactor [issue/PR] requests / implements a refactor of existing code without affecting functionality. labels Jul 28, 2026
@github-actions github-actions Bot added area:docs [issue/PR] affects documentation (excluding XML documentation that is part of source code). area:code [issue/PR] affects project code (excluding tests). labels Jul 28, 2026
@rdeago

rdeago commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

@claude please review this PR.

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Claude finished @rdeago's task in 4m 6s —— View job


Claude finished reviewing this PR

Tasks:

  • Gather context (diff, changed files, repo rules)
  • Review source generator (ThisAssemblyClassGenerator.cs)
  • Review ConstantValueParser.cs
  • Review WriteThisAssemblyConstantsFile task
  • Review MSBuild targets (Module.Core.targets, Module.targets, Sdk.props)
  • Review tests
  • Review docs / changelog / analyzer release files / markdownlint config
  • Post final review

Summary

Solid implementation overall. The incremental-generator design is clean, the percent-encoding round trip between the task and the generator is airtight (verified the escaping logic handles =, embedded newlines, and Unicode line separators U+2028/U+2029 correctly, including the C1 control char U+0085 which char.IsControl already catches), and the test coverage across parser/generator/task is thorough (edge cases like empty/untagged additional files, all supported types, malformed values, unchanged-content no-rewrite). The dormant-by-default rollout (GenerateThisAssemblyClass defaults to false) is a sensible way to land this without any behavior change.

Notes (non-blocking)

  1. Target ordering risk for default constantssrc/Buildvana.Sdk/Modules/ThisAssemblyClass/Module.Core.targets:71-82: BV_WriteThisAssemblyConstantsFile runs BeforeTargets="CoreCompile" with DependsOnTargets="PrepareForBuild;BV_DefineDefaultThisAssemblyConstants", but doesn't explicitly depend on the SDK targets that actually populate $(AssemblyVersion), $(FileVersion), and $(InformationalVersion) (used in BV_DefineDefaultThisAssemblyConstants, lines 52-54). MSBuild doesn't guarantee relative execution order between two independent targets that both specify BeforeTargets="CoreCompile" without an explicit dependency chain — so there's a theoretical risk the constants file gets written before those properties are computed, silently producing empty Version/FileVersion/InformationalVersion constants. Since the PR description says real version values are deferred to Phase 7, you may already be planning to validate/fix this then — flagging in case it's not on your radar yet, since it wouldn't surface as a build error, just a silently wrong constant.

  2. BVSDK2301 diagnostic has no locationThisAssemblyClassGenerator.cs:126: Diagnostic.Create(InvalidConstantValueDescriptor, Location.None, name, rawValue). Understandable since the "source" is an MSBuild item rather than a code file, but it means an invalid ThisAssemblyConstant value surfaces as a generic build error with no file/line to jump to. Low priority — just a UX polish opportunity, not a correctness issue.

  3. Default Version constant maps to $(AssemblyVersion), not $(Version)Module.Core.targets:52. Worth double-checking this is the intended mapping (vs. the NuGet package version), since other tools (e.g. Nerdbank.GitVersioning's ThisAssembly) distinguish AssemblyVersion from NuGetPackageVersion more explicitly. If this matches the pre-Replace code generation tasks with source generators #158 legacy behavior being restored, it's a non-issue — just calling it out since the name could be ambiguous to consumers.

Nothing here blocks merging — items 1 and 3 are worth a quick gut-check from you, item 2 is optional polish.
· Branch: issue/295-thisassemblyclass-module

rdeago and others added 2 commits July 29, 2026 01:22
…nstants

BV_DefineDefaultThisAssemblyConstants reads AssemblyVersion, FileVersion,
and InformationalVersion, but those are computed by the GetAssemblyVersion
target (with the source revision appended by
AddSourceRevisionToInformationalVersion), which had no ordering relation
with the constants-writing target: both hooked BeforeTargets=CoreCompile.
Declare both as dependencies so the default constants never capture empty
or incomplete version values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tyle

The default constant for $(AssemblyVersion) was named Version, which is
ambiguous: it could just as easily be read as the NuGet package version.
Follow the naming convention of Nerdbank.GitVersioning ThisAssembly
classes instead, where constants are named after the corresponding
assembly attributes (AssemblyVersion, AssemblyFileVersion,
AssemblyInformationalVersion, AssemblyTitle, and so on). Also add the
AssemblyName, AssemblyConfiguration, and RootNamespace constants that
NBGV provides and cost nothing to supply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rdeago

rdeago commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review — the findings have been addressed as follows:

  1. Target ordering — fixed in 4853167: BV_DefineDefaultThisAssemblyConstants now declares DependsOnTargets="GetAssemblyVersion;AddSourceRevisionToInformationalVersion", so the default constants can no longer capture empty version values, nor an InformationalVersion missing the source revision suffix.
  2. BVSDK2301 location — left as-is: the location that would actually help (the ThisAssemblyConstant item in the project file) is not reachable from a source generator, and pointing at the offending line of the percent-encoded intermediate file would add nothing over what the diagnostic message already shows.
  3. Default constant naming — reworked in f38dfd6: instead of restoring the pre-Replace code generation tasks with source generators #158 names, the default constants now follow the Nerdbank.GitVersioning ThisAssembly convention of naming constants after the corresponding assembly attributes (AssemblyVersion, AssemblyFileVersion, AssemblyInformationalVersion, AssemblyTitle, AssemblyDescription, AssemblyProduct, AssemblyCompany, AssemblyCopyright), removing the ambiguity of a plain Version constant. The AssemblyName, AssemblyConfiguration, and RootNamespace constants provided by NBGV have been added as well.

@claude please take another look.

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Claude finished @rdeago's task in 1m 54s —— View job


Follow-up review complete

Summary

Only src/Buildvana.Sdk/Modules/ThisAssemblyClass/Module.Core.targets changed across the two follow-up commits (31 insertions / 17 deletions total) — both fixes are scoped exactly where expected, and both look correct to me.

1. Target ordering (4853167)BV_DefineDefaultThisAssemblyConstants now declares DependsOnTargets="GetAssemblyVersion;AddSourceRevisionToInformationalVersion" (Module.Core.targets:42-44). Both are real .NET SDK targets (Microsoft.NET.GenerateAssemblyInfo.targets): GetAssemblyVersion computes AssemblyVersion/FileVersion/InformationalVersion from $(Version), and AddSourceRevisionToInformationalVersion appends the source revision suffix when enabled. Since BV_WriteThisAssemblyConstantsFile already depends on BV_DefineDefaultThisAssemblyConstants (line 87), MSBuild's transitive DependsOnTargets resolution now guarantees the full chain GetAssemblyVersion/AddSourceRevisionToInformationalVersionBV_DefineDefaultThisAssemblyConstantsBV_WriteThisAssemblyConstantsFileCoreCompile runs in the right order. This fully closes the gap I flagged — no properties used on lines 63-65 are computed after this dependency chain resolves. The properties used for the other default constants (Company, Copyright, Description, Product, AssemblyTitle, AssemblyName, RootNamespace, Configuration) are plain static properties that don't need extra target dependencies.

2. BVSDK2301 location — agreed, leaving it as-is is the right call. The generator has no way to reach the originating ThisAssemblyConstant item in the project file, and pointing at the intermediate percent-encoded file would be noise, not signal.

3. Default constant renaming (f38dfd6) — the Remove/Include item pairs are 1:1 and correctly matched (Module.Core.targets:46-74): 11 constants total (AssemblyVersion, AssemblyFileVersion, AssemblyInformationalVersion, AssemblyName, AssemblyTitle, AssemblyDescription, AssemblyProduct, AssemblyCompany, AssemblyCopyright, AssemblyConfiguration, RootNamespace), matching the NBGV ThisAssembly naming convention and resolving the Version-vs-$(Version) ambiguity from my original note. I checked for stale references to the old names (Version, FileVersion, Company, Title, etc.) across tests, docs, and the changelog — none found. docs/ConstantsSyntax.md only documents the general type:value parsing syntax and doesn't enumerate default constant names, and the CHANGELOG.md entry describes them generically ("assembly version, company, product, etc.") without naming them individually, so neither needed updating. The generator/parser/task tests don't hardcode default constant names either (they operate on arbitrary ThisAssemblyConstant items), so they're unaffected by the rename.

No remaining concerns from me — this looks ready to merge.
· Branch: issue/295-thisassemblyclass-module

@rdeago

rdeago commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

It should additionally be noted that the one issue found by CodeFactor (use of Substring where AsSpan would spare an allocation) is a false positive: the flagged code is part of a .NET Standard 2.0 project, where AsSpan is not available.

@rdeago
rdeago merged commit 3b4718f into Tenacom:main Jul 28, 2026
5 of 6 checks passed
@rdeago
rdeago deleted the issue/295-thisassemblyclass-module branch July 28, 2026 23:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:code [issue/PR] affects project code (excluding tests). area:docs [issue/PR] affects documentation (excluding XML documentation that is part of source code). enhancement [issue/PR] requests / implements new or improved functionality. refactor [issue/PR] requests / implements a refactor of existing code without affecting functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Versioning Phase 6 — restore the dormant ThisAssemblyClass SDK module and source generator

1 participant