feat(ts): opt-in import_style=modules and oneof_style=discriminated for TS generators#198
Closed
hishamank wants to merge 4 commits into
Closed
feat(ts): opt-in import_style=modules and oneof_style=discriminated for TS generators#198hishamank wants to merge 4 commits into
hishamank wants to merge 4 commits into
Conversation
…-in) Both TS generators (protoc-gen-ts-client, protoc-gen-ts-server) gain two orthogonal, opt-in plugin options parsed from the protoc parameter string: - import_style=modules: emit one canonical type module per proto file (<proto>.ts) with cross-file imports + a single shared errors.ts, instead of inlining the full type closure into every service file. Service files become slimmed clients/servers that import their request/response types. - oneof_style=discriminated: render un-annotated (non-synthetic) oneofs as $case discriminated unions instead of flattened optional fields. Defaults (inline / flatten) are byte-identical to today — implemented via *Ctx emitter variants in tscommon with the existing functions kept as nil-context wrappers, so no existing golden changes. Core pieces: tscommon/config.go (ParseOptions), tscommon/imports.go (EmitContext + ImportTracker + relative-path resolution), tscommon/modules.go (shared per-proto type modules + errors), and import-aware *Ctx variants in tscommon/types.go. Generators branch to modules mode and thread ctx through type/enum reference sites. Verified: go vet + full suite green (9 pkgs); existing inline goldens unchanged; modules output type-checks (client). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…scriminated Add an `opts`/`subdir` field to both TS golden harnesses so opt-variant cases generate into testdata/golden/<subdir>/. New cases on existing fixtures: import_style=modules (complex_features -> per-proto type module + slimmed client/server + shared errors.ts) and oneof_style=discriminated (oneof_discriminator -> $case unions). Default cases keep empty opts and unchanged goldens, guarding byte-identical default output. Client and server emit byte-identical shared type modules. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The inline (default) generation path now builds an inline EmitContext from the parsed options and emits interfaces through GenerateInterfaceCtx, so oneof_style=discriminated takes effect without import_style=modules. With the default flatten style the inline context resolves bare names and records no imports, keeping output byte-identical (existing goldens unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🔍 CI Pipeline Status❌ Lint: failure 📊 Coverage Report: Available in checks above |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #198 +/- ##
========================================
+ Coverage 3.32% 4.09% +0.76%
========================================
Files 62 67 +5
Lines 10966 11290 +324
========================================
+ Hits 365 462 +97
- Misses 10595 10819 +224
- Partials 6 9 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The shared errors import previously always pulled ApiError, FieldViolation
and ValidationError, tripping noUnusedLocals in strict consumers (clients
never use FieldViolation; servers never use ApiError). Scan the buffered body
and import only the referenced helpers — clients get {ApiError,
ValidationError}, servers get {FieldViolation, ValidationError}.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Split into two independently-reviewable PRs so each feature can be accepted on its own:
Closing this combined PR in favor of those. |
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.
Summary
Adds two orthogonal, opt-in plugin options to both TypeScript generators (
protoc-gen-ts-client,protoc-gen-ts-server), parsed from the protoc parameter string:import_style=modules— emit one canonical type module per proto file (<proto>.ts) with cross-file imports, plus a single sharederrors.ts, instead of inlining the full type closure into every service file. Service files become slimmed clients/servers that import their request/response types. Fixes the type-duplication problem (e.g.SongID/ValidationErrorre-declared in every service file).oneof_style=discriminated— render un-annotated (non-synthetic) oneofs as$casediscriminated unions instead of flattened optional fields. An explicitsebuf.httponeof_configannotation still wins.Defaults are
import_style=inline/oneof_style=flatten.Backward compatibility
With no options, output is byte-for-byte identical to today. This is enforced by the design: the existing emitter functions become thin wrappers that delegate to new
*Ctxvariants with a nil context; a nil/inline context resolves every type reference to its bare name and records no import. All existing goldens are unchanged (the new behavior lives in new golden subdirectories).import_style=modulesrequiresstrategy: allThe modules layout emits shared files (
errors.ts, cross-package type modules). buf's defaultstrategy: directoryinvokes the plugin once per directory, which would emit those shared files repeatedly. Setstrategy: allon the plugin inbuf.gen.yamlwhen usingimport_style=modules:Implementation
internal/tscommon/config.go—Options+ParseOptions(ignores unknown keys likepaths=source_relative; errors on bad values).internal/tscommon/imports.go—EmitContext+ImportTracker(relative specifier computation, deterministic collision aliasing, type-only vs value imports).internal/tscommon/modules.go—EmitSharedModules: per-proto type modules grouped by source file + sharederrors.ts; reserved-name guard (ValidationError/ApiError/FieldViolation).internal/tscommon/types.go—*Ctxvariants of the emitters (nil-wrapper preserves defaults);SynthesizeOneofInfo($case);MessagesBySourceFile/EnumsBySourceFile.EmitContextthrough type/enum reference sites; the shared type modules emitted by client and server are byte-identical.Tests / verification
go vet ./...+ full suite green (9 packages). Existing inline goldens unchanged.import_style=modules(per-proto module + slimmed service file + sharederrors.ts) andoneof_style=discriminated($caseunions). Client/server shared type modules verified byte-identical.ParseOptions,RelativeImportSpecifier,ImportTracker(collision aliasing, render ordering).strategy: all): 98 files, 0 duplicate-file warnings,SongIDdefined once and imported, oneofs discriminated, and the full tree type-checks undertsc --strict --moduleResolution bundler.Follow-ups (out of scope)