Explore: emitter options defined in TypeSpec (TypeGraph)#11101
Explore: emitter options defined in TypeSpec (TypeGraph)#11101timotheeguerin wants to merge 17 commits into
Conversation
…tter-options-typespec
…tter-options-typespec
…tter-options-typespec
…tter-options-typespec
…-to-end - Restore incremental program reuse fast-path inside createTypeGraph (was disabled), and give TypeGraph its own sourceFiles/jsSourceFiles. - Expand the emitter-options validator: unions, enums, literals, optional/required, unknown-property rejection, custom scalars, Record<>. - Wire validation end-to-end in loadEmitter via validateEmitterOptionsAgainstModel, reporting located `invalid-emitter-options` diagnostics in tspconfig.yaml. - Transitional coexistence: legacy JSON-schema validator stays authoritative while present; the TypeSpec-options path is enforced only for emitters with no legacy validator. - Add validator unit tests + an end-to-end test for a greenfield emitter.
@typespec/bundler
@typespec/compiler
@typespec/json-schema
@typespec/openapi3
@typespec/tspd
commit: |
|
All changed packages have been documented.
Show changes
|
Define each emitter's options as a TypeSpec model (options/main.tsp, exported via exports["./options"].typespec) and remove the legacy hand-written JSON-schema validators so the compiler validates user options against the model. Adds end-to-end emitter-options tests.
Exports that only expose a `typespec` condition (e.g. `./emitter`, `./options`) have no JS module to bundle; their source files are already included via the sub-export compilation loop. Previously the bundler threw "missing import or default entrypoint" for these, breaking the playground bundle.
|
You can try these changes here
|
Add model-based emitter-options extraction so tspd reads options/main.tsp when no legacy JSON-schema validator is present, restoring the options reference sections for migrated emitters (openapi3, json-schema).
Gate TypeSpec-model option validation behind a per-emitter experimentalEmitterOptions package flag, broaden validator scalar support, restore absolutePath parity, register diagnostics, scope resolveTypeReference to the graph checker, and generate option TS types/docs from the model.
Concept
An emitter declares its options as a
.tspmodel (EmitterOptions) exported viapackage.jsonexports["./options"].typespec, instead of the current hand-written TS interface +JSONSchemaTypeschema. This gives a single source of truth that can drive config validation, editor completion, and docs.To support it,
Programgains an internalTypeGraph— each emitter's options file compiles into its own self-contained graph (checker / resolver / source resolution / global namespace).What's in this PR
createTypeGraph;TypeGraphnow owns itssourceFiles/jsSourceFiles.absolutePath extends string), andRecord<>.loadEmittervalidates user options against the resolvedEmitterOptionsmodel and reportsinvalid-emitter-optionsdiagnostics anchored intspconfig.yaml../options.✅ Suites green: compiler 3542, openapi3 2284, json-schema 173.
Key finding
The example option models (
openapi3,json-schema) are incomplete vs. the real options (e.g.openapi-versionsis missing"3.2.0"). Making them authoritative broke ~590 openapi3 tests — hence the transitional "prefer-legacy" rule. Migrating an emitter requires authoring a complete model and then removing its legacy validator.Remaining work
1. Complete & migrate the option models (per emitter)
openapi3/json-schemaEmitterOptionsso they are an exact superset of the real options (all enum values incl."3.2.0", every property, correct optionality), then delete the legacyJSONSchemaTypeschema +$lib.emitter.optionsso the TypeSpec path becomes authoritative.emitter-output-dir, etc.) are modeled vs. injected.2. Defaults & coercion
@defaults are not applied. Decide whether the compiler fills defaults / coerces values intoEmitterRef.options, or whether that stays the emitter's responsibility. If the compiler applies them, the resolved options that flow to$onEmitneed to carry the defaults.3. Diagnostic quality
invalid-emitter-optionsis a single generic code carrying the validator message. Consider per-category codes (type-mismatch / unknown-property / invalid-value) and registering them inmessages.tsso they get docs URLs and are greppable. Verify YAML target resolution for nested paths (arrays,Recordentries, nested models).4. First-class
TypeGraphisolation (deferred from this PR)program.checker/resolver/sourceFilesreflect the graph currently being built; the main graph builds last." It works for sequential builds but is implicit. Full isolation meanscreateResolver/createCheckerread per-graph source files (they iterateprogram.sourceFilesin ~9 places) so building an options graph never mutates shared program state. Larger, higher-risk core refactor — worth doing before this is non-experimental.program.stateMaps/stateSets) is program-global and shared across graphs; evaluate whether option-graph decorators need per-graph state.5. Performance
6. Editor & config schema
tspconfig.yamlcompletion JSON-schema foroptions.<emitter>from theEmitterOptionsmodel so editor completion reflects the same source of truth (today it comes from the legacy schema).7. Docs & polish
exports["./options"].typespecconvention and "defining emitter options in TypeSpec".TODO-TIMinloadEmitter(reuse the real module-resolution logic to locate the options entrypoint instead of readingmanifest.exportsdirectly).TypeGraphthe term we want for internal/public APIs?