Repository navigation
feat(csharp): hand emitters over on eject and make api-docs ejectable - #432
Merged
Merged
Conversation
…h; api-docs is ejectable An ejected C# generator used to be one file over packaged emitters, so a defect in the enum, navigation, doc-comment or prompt-tier writers still waited for a release. Eject now copies those emitters with the generator, into codegen/generators/ in the same Codegen.Generators namespace: Fr019SharedEnum, XmlDocBuilder, M2MNavigation and ReverseFinders with entity, db-context and routes as each calls them; ExtractDelegateEmitter, Fr010FieldMapping and OutputFormatSpecEmitter with output-parser, extractor and output-prompt. The list is each registry entry's Companions. A file two generators share is copied once and never overwritten. Naming and type-mapping tables, the model walks and the generator base stay in the package as the engine. api-docs is registered for C# and ejectable. The api reference is now a generator (IDocsGenerator) that `dotnet meta docs` runs; `gen` and `verify --codegen` skip it and say so. `docs` hands off to an owned codegen project once it owns api-docs, and what the reference documents comes from the generators in that project's list (GeneratorApplicability.From), so it describes an owned copy. Moved to the engine so an owned copy needs no emitter: CSharpStringLiteral to CSharpNaming, ValueObjectNames.ResolveValueObject out of RenderHelperGenerator. EjectedGeneratorsAloneTests derives its list from the registry: each ejectable generator is ejected alone, compiled against the public API, checked against a written engine surface, and compared byte for byte with the packaged generator. Packaged output is unchanged.
…d; fix pin wording
…es it The registry Note shown by gen --list and the matching manifest note still said a gen run that lists api-docs warns. After the gen change, an owned project that lists it in codegen/Program.cs is silent; only a --generators selection naming it warns. Text only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Changed
dotnet meta ejectnow copies the emitters each generator writes through (Fr019SharedEnum,XmlDocBuilder,M2MNavigation,ReverseFinders,ExtractDelegateEmitter,Fr010FieldMapping,OutputFormatSpecEmitter) intocodegen/generators/, so an owned generator no longer calls packaged writers. Naming and type-mapping tables and model walks stay packaged.templatestays non-ejectable.api-docsis a registered, ejectable docs generator (IDocsGenerator,DocsRunFacts,GeneratorApplicability).dotnet meta docsruns an owned copy whencodegen/generators/ApiDocsGenerator.csexists.genandverify --codegenskip docs generators. Eject warns when an ownedcodegen/Codegen.csprojpins an olderMetaObjects.Codegenthan the tool.CSharpStringLiteralmoved toCSharpNaming; the oldFr010FieldMappingmember is[Obsolete].main: code +504, tests +812, docs and CHANGELOG +149.api-docsregistry and manifest note was pushed (aa0aff88e), checked locally with the C# suites, the other ports' registry-conformance tests andscripts/ci-local.sh --quick. The run count stays 1.Validation
Risk Assessment
✅ Low: The round-1 and round-2 fixes hold when traced against the current code (engine no longer calls a handed-over emitter, the pin warning words non-inline pins safely, the gen skip warning fires only for a named selection on both paths), packaged output paths are unchanged, and the one remaining issue is a stale
--listnote.Testing
Baseline ts-fast/ts-unit already green. Drove the C# eject surface live with the built 1.1.0 CLI in disposable temp projects: eject of api-docs alone and of all 12 ejectables, refusals (unknown, template, no-force overwrite), older/equal pin warnings across inline, multi-line and child-element forms, and owned vs packaged gen selection behavior. Owned api-docs output matched packaged docs byte-for-byte (50 pages). Full C# suites (Cli 254, Codegen 825 with 1 pre-existing skip, Render 332, Conformance 1388) passed in-process; they are not live product runs. One workaround: owned project used a ProjectReference to the local codegen build because MetaObjects.Codegen 1.1.0 is not in the offline NuGet cache. Overall pass; no findings.
dotnet MetaObjects.Cli.dll eject api-docsin empty temp dir: exit 0; wrote codegen/generators/ApiDocsGenerator.cs, 6 emitter companions, Codegen.csproj, Program.cseject nopeandeject template: exit 2, 'Nothing was ejected', template absent from ejectable list, no files writteneject api-docs: exit 1 'already owned'; with --force: exit 0 [replaced]gen meta --out o --generators api-docs: stderr 'api-docs writes the api reference, so gen skipped it. Run it with dotnet meta docs.' exit 0; no selection exit 2docs meta --out(50 pages) vs packaged docs: diff -rq IDENTICALPipeline
Updates from git push no-mistakes
✅ **Intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
server/csharp/MetaObjects.Codegen/CSharpNaming.cs:1104- The packaged engine still calls an emitter this change hands over.CSharpNaming.EnumTypeName(engine, stays packaged) callsFr019SharedEnum.ResolveSharedEnumDecl, andFr019SharedEnum.csis now a companion ofentity,db-contextandextractor(GeneratorRegistry.cs:162, 172, 211). After eject there are two copies of the shared-enum predicate. The owned generators decide shared-versus-inline through the owned copy (EntityGenerator.cs:537, 869, 887; DbContextGenerator.cs:930; ExtractorGenerator.cs:228), then name the inline enum throughCSharpNaming.EnumTypeName(EntityGenerator.cs:540, 872, 889; DbContextGenerator.cs:406, 932; ExtractorGenerator.cs:230; CSharpApiModelBuilder.cs:284, 310), which asks the packaged copy. Concrete sequence: an adopter ejectsentityand edits the ownedResolveSharedEnumDeclorSharedEnumForFieldso that a declaration is treated as inline. The owned generator nests an enum, andEnumTypeNamestill returns the shared super's bare name. That is the name/declaration disagreement (CS0102) that the comment at CSharpNaming.cs:1092 says the single predicate exists to prevent.genexits 0.EjectedGeneratorsAloneTestschecks owned copy to package references only, so it does not see this direction. Fix: makeEnumTypeNamecall the core authoritySharedEnumDeclaration.ResolveSharedEnumDeclaration(field)directly, which is whatFr019SharedEnum.ResolveSharedEnumDeclalready delegates to, so packaged output is unchanged. Also add a guard assertion that no packaged non-generator type references a type declared in aCompanionsfile. This is the only such call site: a grep of the package for the seven emitters outside the generators and companions finds no other.server/csharp/MetaObjects.Codegen.Tests/EjectedGeneratorsAloneTests.cs:319- The byte-identity leg of the required structural guard asserts nothing forcallable.Ejected_alone_it_emits_what_the_packaged_generator_emits_byte_for_byteexemptscallablefrom theemitted > 0check because neither shared corpus has astoredProcortableFunctionsource. For that generator the test compares two empty lists, so an ejectedcallablethat emitted different text would pass. The change already solves the same problem for the prompt tier by adding theExtractorCodegenTests.Modelcorpus inCorpora()(line 253). A callable model exists inFr015CodegenTests.cs(storedProc and tableFunction entities with@parameterRef). Fix: yield that model fromCorpora("callable")and remove thename != "callable"exception. Oracle: the packaged generator's output over the same model. Wrong behavior rejected: an ejected copy whose rewritten namespace or usings change the emittedFromSqlInterpolatedwrapper.server/csharp/MetaObjects.Cli/EjectCommand.cs:169- Ejecting with the 1.1.0 tool into an owned project scaffolded by 1.0.x writes sources that do not compile against that project's pinned package, and nothing tells the user. Eject never editscodegen/Codegen.csproj, which pinsMetaObjects.Codegenat the version of the tool that first scaffolded it. The sources eject now writes call API this change adds:CSharpNaming.CSharpStringLiteral(NamesGenerator.cs:108, ExtractDelegateEmitter.cs:241 and 299, OutputFormatSpecEmitter.cs:80, Fr010FieldMapping.cs:49 and 231),ValueObjectNames.ResolveValueObject(RenderHelperGenerator.cs:114 and 149), andIDocsGenerator,DocsRunFactsandGenContext.Applicability(ApiDocsGenerator.cs). Concrete sequence: a project ownsentityfrom 1.0.13, upgrades the tool, and runsdotnet meta eject api-docsas the changelog advertises. The nextdotnet meta genhands off todotnet run --project codegenand fails with CS0246 onIDocsGenerator, so agenthat worked before the eject is now broken until the pin is edited by hand. The eject output only says to addnew ApiDocsGenerator()to Program.cs. Two statements in the change are wrong for this state. TheHasOwnedDocscomment (CodegenHandoff.cs:19-23) says the presence ofApiDocsGenerator.csmeans the pinned package knowsdocs. The upgrade recipe (docs/features/own-your-codegen.md:1080-1089, CHANGELOG.md:168-173) copies 1.1.0 emitter files in without saying the pin must be 1.1.0 or later first; foroutput-parser,extractorandoutput-promptthe copiedFr010FieldMapping.csfails with CS0117. The class of hazard predates this change, but movingCSharpStringLiteralandResolveValueObjectextends it to generators whose source compiled against 1.0.x before. Smallest remedy: when the scaffold already exists and itsMetaObjects.Codegenversion differs fromToolVersion(), eject prints a line telling the user to bump it, and the two docs passages say the same. That adds user-visible output, so it needs the author's decision rather than a silent fix.server/csharp/MetaObjects.Codegen/GeneratorApplicability.cs:70- Simplification:GeneratorApplicability.Fromis not required by a stated intent requirement. The intent requiresapi-docsto be a registered, ejectable generator that references no packaged emit type outside the engine surface.GenContext.Applicabilitydefaulting toGeneratorApplicability.Packagedsatisfies that.Fromadds a further behavior: it finds apublic static AppliesToon each owned generator by reflection on the method name and parameter types, and substitutes it for the packaged predicate. The contract is implicit and fails silently. A copy that renamesAppliesTo, adds a parameter, or moves it to a base class falls back to the packaged answer with no message (GetMethodwithoutFlattenHierarchy, line 77-80), and a copy that changes the return type makesDelegate.CreateDelegatethrow out ofRunDocsuncaught. It is also asymmetric: it takes effect only when the project ownsapi-docstoo, becausedocshands off only then (CodegenHandoff.cs:40). A project that ownsrouteswith a narrowedAppliesTobut notapi-docsstill gets the packaged surface documented, as before this change. Remedy per the intent's scope: removeFromandSources, and haveCodegenCli.RunDocs(CodegenCli.cs:296) passGeneratorApplicability.Packaged. If the owner wants docs to follow owned copies, that is a product decision to state, and an explicit contract (for example a marker interface the generators implement) would be the form to authorize.🔧 Fix applied.
3 issues (1 warning, 2 infos) still open:
server/csharp/MetaObjects.Cli/EjectCommand.cs:169- Ejecting with the 1.1.0 tool into an owned project scaffolded by 1.0.x writes sources that do not compile against that project's pinned package, and nothing tells the user. Eject never editscodegen/Codegen.csproj, which pinsMetaObjects.Codegenat the version of the tool that first scaffolded it. The sources eject now writes call API this change adds:CSharpNaming.CSharpStringLiteral(NamesGenerator.cs:108, ExtractDelegateEmitter.cs:241 and 299, OutputFormatSpecEmitter.cs:80, Fr010FieldMapping.cs:49 and 231),ValueObjectNames.ResolveValueObject(RenderHelperGenerator.cs:114 and 149), andIDocsGenerator,DocsRunFactsandGenContext.Applicability(ApiDocsGenerator.cs). Concrete sequence: a project ownsentityfrom 1.0.13, upgrades the tool, and runsdotnet meta eject api-docsas the changelog advertises. The nextdotnet meta genhands off todotnet run --project codegenand fails with CS0246 onIDocsGenerator, so agenthat worked before the eject is now broken until the pin is edited by hand. The eject output only says to addnew ApiDocsGenerator()to Program.cs. Two statements in the change are wrong for this state. TheHasOwnedDocscomment (CodegenHandoff.cs:19-23) says the presence ofApiDocsGenerator.csmeans the pinned package knowsdocs. The upgrade recipe (docs/features/own-your-codegen.md:1080-1089, CHANGELOG.md:168-173) copies 1.1.0 emitter files in without saying the pin must be 1.1.0 or later first; foroutput-parser,extractorandoutput-promptthe copiedFr010FieldMapping.csfails with CS0117. The class of hazard predates this change, but movingCSharpStringLiteralandResolveValueObjectextends it to generators whose source compiled against 1.0.x before. Smallest remedy: when the scaffold already exists and itsMetaObjects.Codegenversion differs fromToolVersion(), eject prints a line telling the user to bump it, and the two docs passages say the same. That adds user-visible output, so it needs the author's decision rather than a silent fix.server/csharp/MetaObjects.Codegen/CodegenCli.cs:251- A project that ownsapi-docsgets a warning on everydotnet meta gen, and it cannot clear the warning without giving up the owned docs generator. Sequence:dotnet meta eject api-docswrites (or tells the user to add)new ApiDocsGenerator()in the onegeneratorslist incodegen/Program.cs.dotnet meta genhands off;ComposeGeneratorsreturns the owned list with no selection, and appends owned generators the selection did not name when there is one (CodegenCli.cs:404).RunGenthen finds theIDocsGenerator(line 233) and adds"api-docs" writes the api reference, so gen skipped it. Run it with dotnet meta docs.on every run. Runningdotnet meta docsdoes not make it stop. This is documented as intended (docs/features/own-your-codegen.md, theProgram.cs lists new ApiDocsGenerator()paragraph), so it is the author's choice and not a defect to fix silently. The cross-port difference is that in TypeScript an owned docs generator lives in a separatedocs.generatorslist, so the same warning only fires on a misplaced entry; in C# the supported configuration is the one that triggers it. A narrower form: warn only when a--generatorsselection names the docs generator, and stay silent when it is only present in the owned list. That changes user-visible output, so it needs the owner's decision.server/csharp/MetaObjects.Cli/EjectCommand.cs:169- Defect in code the round-1 fix introduced (the older-pin warning).PinnedCodegenVersionaccepts three shapes: an inlineVersionattribute, aVersionattribute on its own line of a multi-line element, and a<Version>child element (line 152). For the last two it returns the line of the attribute or child element.OlderPinWarningthen always sayschange line N of codegen/Codegen.csproj to <PackageReference Include="MetaObjects.Codegen" Version="X" />. Concrete input:<PackageReference Include="MetaObjects.Codegen">on line 9 and<Version>1.0.13</Version>on line 10. The warning says to replace line 10 with a wholePackageReferenceelement, which nests onePackageReferenceinside another and breaks the project file. The scaffold eject writes uses the inline form, so this only affects a project whose owner reformatted the file. Fix: word the instruction as setting the version on line N to the tool version, and print the full replacement element only when the version is an attribute on the same line as the element start. The existing tests cover the inline form only.🔧 Fix applied.
1 info still open:
server/csharp/MetaObjects.Codegen/GeneratorRegistry.cs:265- Sibling left behind by the round-2 fix (warn only when a--generatorsselection names the docs generator). Theapi-docsregistryNote, whichdotnet meta gen --listprints, still says "agenrun that lists it skips it with a warning." In an owned project the supported way to list it isnew ApiDocsGenerator()incodegen/Program.cs, and that run is now silent by the maintainer's decision, so the note over-claims for exactly the configuration eject creates. Same statement, same staleness:fixtures/generator-registry-conformance/registry.json:93("On csharp ...dotnet meta genskips it with a warning"). The CHANGELOG entry,docs/features/own-your-codegen.mdand theDocsGenerator.csheader were already corrected in round 2. Fix: reword both to say the warning appears only when--generatorsnamesapi-docs. No behavior change.✅ **Test** - passed
✅ No issues found.
dotnet MetaObjects.Cli.dll eject api-docsin empty temp dir: exit 0; wrote codegen/generators/ApiDocsGenerator.cs, 6 emitter companions, Codegen.csproj, Program.cseject nopeandeject template: exit 2, 'Nothing was ejected', template absent from ejectable list, no files writteneject api-docs: exit 1 'already owned'; with --force: exit 0 [replaced]gen meta --out o --generators api-docs: stderr 'api-docs writes the api reference, so gen skipped it. Run it with dotnet meta docs.' exit 0; no selection exit 2docs meta --out(50 pages) vs packaged docs: diff -rq IDENTICALscripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchainsBuilt CLI from worktree to /tmp (disposable):dotnet build server/csharp/MetaObjects.Cli;dotnet MetaObjects.Cli.dll --version= 1.1.0Live:eject api-docsin empty temp project (exit 0; codegen/Codegen.csproj, Program.cs, generator, 6 emitters)Live:ejectof all 12 ejectable names in one call (exit 0; companions and codegen/runtime copied)Live:eject nopeandeject template(exit 2, 'Nothing was ejected', template absent from ejectable list)Live: re-eject without --force (exit 1, 'already owned'); with --force (exit 0, [replaced])Live: older pin 1.0.13 inline -> warning names line 9 and 1.1.0; multi-line Version attr -> line 10, version-only instruction; <Version> child -> line 10; equal pin 1.1.0 -> no warningLive: packagedgen --generators api-docs-> stderr skip warning, exit 0;genwithout selection -> exit 2Live (workaround: owned codegen/Codegen.csproj ProjectReference to worktree MetaObjects.Codegen, since 1.1.0 is not in offline NuGet cache): ownedgenwith no selection silent; with --generators api-docs warnsLive: owneddocs meta --out(50 pages) vs packaged docs:diff -rqIDENTICALdotnet test server/csharp/MetaObjects.Cli.Tests-> 254 passed, 0 failed (in-process, not live)dotnet test server/csharp/MetaObjects.Codegen.Tests-> 825 passed, 1 skipped (pre-existing mutation harness), 0 failed; includes EjectedGeneratorsAloneTests (in-process, not live)dotnet test server/csharp/MetaObjects.Render.Tests-> 332 passed;dotnet test server/csharp/MetaObjects.Conformance.Tests-> 1388 passed (in-process, not live)Cleanup: removed /tmp/mo-eval temp projects; worktree git status clean✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.