Skip to content

Merge remote-tracking branch 'origin/master' into task/573-devcli-internal-handler-classes-trip-ca1812-in - #393

Merged
StevenTCramer merged 8 commits into
masterfrom
task/573-devcli-internal-handler-classes-trip-ca1812-in
Nov 13, 2026
Merged

StevenTCramer merged 8 commits into
masterfrom
task/573-devcli-internal-handler-classes-trip-ca1812-in

Conversation

@StevenTCramer

Copy link
Copy Markdown
Contributor

Summary

Reproduction

With CA1812 enabled in tests/devcli-consumer-check, exactly four shipped content types fired,
the four the description lists: CheckVersionCommand.Handler, CleanCommand.Handler,
ReleaseCommand.Handler, SelfInstallCommand.Handler. No service or other content type fired;
the generator already builds the content services and commands with new.

Cause

The TimeWarp.Mediator generator registers each nested handler as AddScoped<Handler>(services).
The container builds it by reflection, so no new Handler(...) appears in the compilation.

Fix (generator, no change to the shipped content)

  • EndpointExtractor.IsFactoryConstructible flags a nested [NuruRoute] Handler declared in
    this compilation, not abstract or generic, with exactly one public constructor whose
    parameters are all required (no default, params or ref). Model field:
    HandlerDefinition.NestedHandlerFactoryConstructible.
  • interceptor-emitter.handler-factories.cs emits __UseHandlerFactory<THandler> and, right
    after AddGeneratedMediator(services) in both the source-gen mediator container and the
    runtime-DI container, one call per flagged handler:
    __UseHandlerFactory<H>(services, static sp => new H(GetRequiredService<T1>(sp), ...)). It
    replaces the mediator's own descriptor with the same lifetime (it only swaps a non-keyed
    descriptor whose implementation type is the handler, and adds nothing otherwise). That matches
    how the container builds a single-constructor type, so dispatch does not change. Registrations
    made later (a runtime-DI app's ConfigureServices) still win. The explicit new in the
    generated code is what CA1812 counts.
  • Handlers the container could build another way (several public constructors, optional
    arguments) keep the reflection registration.
  • Shipped content is unchanged: still internal sealed class Handler (no CA1034), no pragma,
    [SuppressMessage] or <auto-generated/>. The consumer check also runs CA2000 and CA1034
    (AnalysisMode All, warnings as errors) and passes.

Regression gate

  • Root Directory.Build.props adds CA1812 to NoWarn only when NuruConsumerCheck is not
    true (same per-file suppression count). tests/devcli-consumer-check/Directory.Build.props
    sets NuruConsumerCheck=true before its import, so the check (CI samples stage,
    workflow.yml "DevCli consumer check") compiles the content with CA1812 on.
  • Negative check: with the factory emission disabled, the consumer check fails with CA1812 on
    all four handlers; restored, it passes.
  • tests/timewarp-nuru-tests/generator/generator-73-nested-handler-factories.cs (Roslyn-hosted,
    standalone list + CiTestExcludes): factory emitted with resolved arguments, emitted after
    AddGeneratedMediator, a zero-dependency handler uses static _, skipped for two
    constructors and for an optional argument, none without a generated mediator.

Gates

  • ./bin/dev build: passed (0 warnings, 0 errors).
  • ./bin/dev test: passed (exit 0; 2403 PASSED, 0 FAILED; generator-73 4/4 in standalone).
  • ganda repo audit: analyzer-suppressions and every check this branch touches pass. The
    run before the fix commit failed runfile-executable on generator-73 (index lacked +x, fixed
    with git add --chmod=+x). It also reported kanban-landed for task 575, which is not this
    task: 575 is a live ganda task work-set walk (into feature/overnight-2026-11-14) whose
    claimed kitchen has not landed yet. That walk's host owns that kitchen, so this branch leaves it alone.
  • vscode-window-icon is a Warning (missing local .vscode/settings.json, ignored file), not
    blocking.

Test plan

Smoke:

(cd tests/devcli-consumer-check && dotnet build)
grep -n "__UseHandlerFactory" artifacts/generated/devcli-consumer-check/TimeWarp.Nuru.Analyzers/*/NuruGenerated.g.cs
dotnet run tests/timewarp-nuru-tests/generator/generator-73-nested-handler-factories.cs
dotnet tests/devcli-consumer-check/bin/Debug/net11.0/devcli-consumer-check.dll check-version --package TimeWarp.Nuru

Expect: the consumer check prints Build succeeded. with no CA1812 (CA1812 is on there); the
generated file has the helper plus four __UseHandlerFactory<global::DevCli.*.Handler> lines
after AddGeneratedMediator(services);; generator-73 passes 4/4; the check-version run
dispatches through the factory-built handler and prints the version report (currently
"4.0.0-beta.13 was already released").

Kanban task 573
Session: b2020e9f-5c06-487a-a0b3-831ae1639f80

StevenTCramer and others added 8 commits November 14, 2026 02:42
…ories (CA1812)

The TimeWarp.Mediator generator registers nested [NuruRoute] handlers as
AddScoped<Handler>, so consumers compiling DevCli content saw CA1812 on the
four internal handlers. After AddGeneratedMediator the Nuru generator now
swaps each such registration for a same-lifetime factory that calls the
handler's single public constructor. The devcli-consumer-check gate now
compiles the content with CA1812 on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…merCheck

Root props adds CA1812 to NoWarn only when NuruConsumerCheck is not true, so
the consumer check enables the rule without a NoWarn property function the
suppression audit miscounts. Also set the executable bit on generator-73.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@StevenTCramer
StevenTCramer merged commit f178487 into master Nov 13, 2026
7 checks passed
@StevenTCramer
StevenTCramer deleted the task/573-devcli-internal-handler-classes-trip-ca1812-in branch November 13, 2026 20:17
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