Skip to content

Generate compiler option definitions, create JSON schema - #64457

Open
Jake Bailey (jakebailey) wants to merge 38 commits into
microsoft:mainfrom
jakebailey:generate-compiler-options
Open

Jake Bailey (jakebailey) wants to merge 38 commits into
microsoft:mainfrom
jakebailey:generate-compiler-options

Conversation

@jakebailey

@jakebailey Jake Bailey (jakebailey) commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

For the new website playground, I need some sort of spec for our CompilerOptions. In Strada, this was done by parsing out at runtime our internal options declarations and turning that into a website UI and JSON schema (if we remembered to do it), which then would eventually get sent over to schemastore (if we remembered to do it...), and then eventually make it into VS Code etc (if we remembered to do it........).

Instead, let's just define our options as metadata like we do the AST and other stuff, then code generate all of the Go code, TS code, and even our own JSON schema files. #54192 is a long-open issue requesting that we ship a JSON schema with the package, so, this PR also adds that file to the package too so node_modules/typescript/schemas/tsconfig.schema.json is now valid. Eventually, we can use this in our VSIX, I think, though historically that one's been more permissive of dead options.

A good bonus is that we can now codegen CompilerOptions.Clone, parseCompilerOptions, the transpile options clearing func, and more.

Fixes #54192

Compiler option metadata is duplicated across compiler declarations, API
types, enum mappings, and configuration schemas. Maintaining these
surfaces independently makes new options and compatibility changes easy
to miss.

Use one authoritative definition so these surfaces stay synchronized,
while preserving compiler behavior and providing reusable config
schemas.
Single-file transpilation must ignore options that only make sense for
whole-program builds. Keeping a separate clearing list lets it drift
from the existing transpile metadata whenever options are added.

Derive the clearing list from that metadata while leaving mode-specific
overrides and conditional behavior in the transpile worker.
Option enums and SyntaxKind already have authoritative metadata. Reading
generated Go back into the TypeScript generator adds an unnecessary
intermediate representation and points contributors at the wrong source.

Use those definitions directly while retaining one shared enum emitter
and Go-value verification for every input source.
Ship version-matched configuration schemas with the main package so tools
can use the installed compiler version without relying on a hosted schema.
Comment thread packages/typescript/package.json
Comment thread tools/scripts/tsc/options.ts Outdated
Comment thread tsc/internal/core/buildoptions_generated.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The schemas incorrectly deprecate active compiler options and lose draft-07 $ref sibling documentation.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity · 2 Low severity

Open (3)
What changed in this PR

Centralizes compiler-option metadata to generate Go/TypeScript definitions and publish version-matched configuration schemas.

Changes:

  • Generates option structs, parsers, enums, maps, defaults, and transpile handling.
  • Adds generated tsconfig/jsconfig schemas to the npm package.
  • Expands code-generation, schema, packaging, and compatibility tests.
File Description
Herebyfile.mjs Integrates generation, validation, and schema packaging.
packages/​typescript/​package.json Exports packaged schemas.
packages/​typescript/​src/​enums/​jsxEmit.enum.ts Updates generation source.
packages/​typescript/​src/​enums/​jsxEmit.ts Updates generation source.
packages/​typescript/​src/​enums/​moduleDetectionKind.enum.ts Updates generation source.
packages/​typescript/​src/​enums/​moduleDetectionKind.ts Updates generation source.
packages/​typescript/​src/​enums/​moduleKind.enum.ts Updates generation source.
packages/​typescript/​src/​enums/​moduleKind.ts Updates generation source.
packages/​typescript/​src/​enums/​moduleResolutionKind.enum.ts Updates generation source.
packages/​typescript/​src/​enums/​moduleResolutionKind.ts Updates generation source.
packages/​typescript/​src/​enums/​newLineKind.enum.ts Updates generation source.
packages/​typescript/​src/​enums/​newLineKind.ts Updates generation source.
packages/​typescript/​src/​enums/​scriptTarget.enum.ts Makes ScriptTarget metadata-generated.
packages/​typescript/​src/​enums/​scriptTarget.ts Makes ScriptTarget metadata-generated.
packages/​typescript/​src/​enums/​syntaxKind.enum.ts Generates from AST metadata.
packages/​typescript/​src/​enums/​syntaxKind.ts Generates from AST metadata.
tools/​scripts/​gen/​generatedFile.test.mts Extends code-generation tests.
tools/​scripts/​tsc/​generate-enums.ts Generates enums from shared metadata.
tools/​scripts/​tsc/​generate-options.ts Adds the option artifact generator.
tools/​scripts/​tsc/​options-model.ts Defines shared metadata types.
tools/​scripts/​tsc/​options-schema.ts Generates configuration schemas.
tools/​scripts/​tsc/​options.test.ts Tests metadata and generated artifacts.
tools/​scripts/​tsc/​options.ts Centralizes option metadata.
tsc/​internal/​api/​enum_values_generated.go Adds generated option enum values.
tsc/​internal/​core/​buildoptions_generated.go Marks build options as generated.
tsc/​internal/​core/​compileroptions.go Removes moved generated definitions.
tsc/​internal/​core/​compileroptions_generated.go Generates CompilerOptions and cloning.
tsc/​internal/​core/​optionenums_generated.go Generates option enums.
tsc/​internal/​core/​typeacquisition.go Removes moved struct definition.
tsc/​internal/​core/​typeacquisition_generated.go Generates TypeAcquisition.
tsc/​internal/​core/​watchoptions.go Removes moved option definitions.
tsc/​internal/​core/​watchoptions_generated.go Generates WatchOptions.
tsc/​internal/​transpile/​compileroptions_generated.go Generates transpile option clearing.
tsc/​internal/​transpile/​options_test.go Tests transpile option isolation.
tsc/​internal/​transpile/​transpile.go Uses generated option clearing.
tsc/​internal/​tsoptions/​commandlineoption.go Removes generated maps.
tsc/​internal/​tsoptions/​compileroptions_generated.go Generates parsing and defaults.
tsc/​internal/​tsoptions/​compileroptions_test.go Tests parsing and cloning.
tsc/​internal/​tsoptions/​declarations_generated.go Generates option declarations.
tsc/​internal/​tsoptions/​declsbuild.go Removes superseded declarations.
tsc/​internal/​tsoptions/​declscompiler.go Retains declaration consumers.
tsc/​internal/​tsoptions/​declstypeacquisition.go Removes superseded declarations.
tsc/​internal/​tsoptions/​declswatch.go Removes superseded declarations.
tsc/​internal/​tsoptions/​enummaps.go Removes generated enum maps.
tsc/​internal/​tsoptions/​enummaps_generated.go Generates enum and library maps.
tsc/​internal/​tsoptions/​otheroptions_generated.go Generates auxiliary parsers.
tsc/​internal/​tsoptions/​parsinghelpers.go Removes generated parsers.
tsc/​internal/​tsoptions/​rootoptions_generated.go Generates root config declarations.
tsc/​internal/​tsoptions/​schemas/​jsconfig.schema.json Adds generated jsconfig schema.
tsc/​internal/​tsoptions/​schemas/​tsconfig.schema.json Adds generated tsconfig schema.
tsc/​internal/​tsoptions/​tsconfigparsing.go Removes generated declarations/defaults.
Files not reviewed (4)
  • tsc/internal/api/enum_values_generated.go: Generated file
  • tsc/internal/core/compileroptions_generated.go: Generated file
  • tsc/internal/core/optionenums_generated.go: Generated file
  • tsc/internal/core/typeacquisition_generated.go: Generated file

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread tools/scripts/tsc/options-schema.ts
Comment thread tools/scripts/tsc/options-schema.ts Outdated
Comment thread tools/scripts/tsc/options-schema.ts Outdated
Comment thread tsc/internal/tsoptions/declscompiler.go Outdated
Use diagnostic message keys for completion and typo checking instead of
untyped Go identifier strings. Preserve the original text for schema
output without a separate diagnostic lookup or generation step.
Draft-07 ignores siblings of a reference. Keep root option descriptions
outside the reference so consumers retain their documentation and links.
Keep the distinction between build options and compiler options visible
in the generated struct, as it was before generation.
Option metadata already identifies which fields affect diagnostics, emit,
declaration paths, and build info. Use it to avoid runtime reflection
without maintaining another handwritten list of fields.

Preserve effective strict defaults and build-info ordering and zero-value
semantics, with the previous reflection logic retained as a test oracle.
The option metadata already describes every field and its JSON name.
Use it to avoid reflective field access and tag parsing during config
merging while keeping new options covered automatically.

Preserve explicit-null overrides, nonzero source precedence, and shallow
sharing of slices and pointers.
@jakebailey
Jake Bailey (jakebailey) marked this pull request as draft September 26, 2026 02:32
Keep config directory substitution aligned with option metadata instead
of maintaining a separate handwritten field list. Explicitly exclude
project and pprofDir, preserving the existing substitution behavior and
copy-on-write semantics.

Keep substitution eligibility out of runtime declarations, narrow the
prefix helper to strings, and note the existing case-sensitivity mismatch
between prefix detection and replacement.
Reduce handwritten serialization machinery by deriving field handling
and enum names from the existing option metadata. This keeps option
coverage centralized and makes field access statically checked rather
than relying on reflection and runtime assertions.

Preserve output ordering, enum aliases, and unset-value behavior while
retaining handwritten formatting helpers and implied-option rules.
Keep whole-options equality aligned with the option definitions without
runtime reflection. Preserve stored values, pointer contents, collection
ordering, and nil-versus-empty distinctions.

Compare paths by their ordered entries rather than ordered-map backing
storage, so allocation history does not cause spurious project changes.
@jakebailey

Copy link
Copy Markdown
Member Author

Most of this is data, not types; the AST is already generated from a JSON file and a TS script, I just used a TS file as it means easy declaration of diags we have to pull from generated JSON etc. If I want to do this in Go it's just going to be the same thing but more annoying.

@andrewbranch

Copy link
Copy Markdown
Member

+1, I'm currently moving user preferences to be generated from JSON because I think that makes more sense than trying to infer the string literal union TS type to int Go type correspondence from the Go code. The AST and compiler options are the same thing; Go's type system is less expressive than TypeScript's, so to get proper TS types out, you either need a higher fidelity source of truth or you need to rely on heuristics in Go to infer out what was intended to be a literal, a union, an enum, etc.

Watch settings no longer affect the native filesystem watcher, and the
config parser never consumed watchOptions. Accepting these flags and
advertising them in help and schemas misleadingly implies support.

Remove the unused settings and their supporting code while preserving
watch mode itself.
Comment thread tsc/testdata/baselines/reference/tsc/commandLine/help-all.js
@weswigham

Copy link
Copy Markdown
Member

I'm currently moving user preferences to be generated from JSON because I think that makes more sense than trying to infer the string literal union TS type to int Go type correspondence from the Go code. The AST and compiler options are the same thing; Go's type system is less expressive than TypeScript's, so to get proper TS types out, you either need a higher fidelity source of truth or you need to rely on heuristics in Go to infer out what was intended to be a literal, a union, an enum, etc.

Welp, if we'd really rather that be the case, then generate-options.ts should probably also generate the CompilerOptions interface currently in packages\typescript\src\api\proto.generated.ts so it handles all CompilerOptions codegen and gen-proto be updated to import that pre-generated type instead, like it does the generated enums. As-is there's codegen dependent on codegen, making them sequencing-reliant, which is a bit weird.

@jakebailey

Copy link
Copy Markdown
Member Author

As-is there's codegen dependent on codegen, making them sequencing-reliant, which is a bit weird.

That's easy to fix; will do.

Deriving the API interface from generated Go makes compiler option
changes depend on the order of two generators. Use the option metadata
as the source for both languages so the protocol generator can import
the API types without traversing the generated Go fields.

Keep PluginImport in the same metadata to avoid a dependency back on
the protocol output, while preserving the existing API shape and exports.
Enum display names should stay aligned with their declarations and
configuration spellings. Preserve the existing diagnostics and strict
handling of zero and invalid values without another handwritten switch.
Single-file transpilation needs one source for both cleared and forced
options. Keep its declaration-mode and verbatim-module exceptions in
that source instead of splitting the policy between metadata and code.

The transpilation rules no longer need space in runtime declarations.
The generated serializer already excludes most of the options removed
by the handwritten post-processing list. Keep the remaining exception
with its option metadata so serialization has one filtering step.
File-listing options should not become part of the configuration printed
by showConfig. Omitting listFiles alongside listEmittedFiles restores
that behavior while keeping explainFiles visible and preserving which
options are accepted in tsconfig files.
Keep the canonical schemas at their published package paths so normal
package staging includes them without a separate copy step.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The generation ordering can compile newly generated option references before diagnostic symbols are generated.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (2)

Comment thread Herebyfile.mjs Outdated
New option descriptions can reference diagnostics that have not been
generated yet. Running enum verification while generating options then
loads an incomplete Go tree and stops generation before diagnostics
can catch up.

Finish the bulk Go-source generation pass before loading Go packages,
while retaining the standalone compiler-options task chaining.
Assertions about generated comments, field lists, and implementation
text duplicate metadata or existing behavioral coverage without adding
independent assurance. Keep schema validation, artifact freshness, and
behavioral checks, and focus transpile tests on conditional overrides
rather than repeating every generated assignment.

Limit this cleanup to tests introduced by this branch.
Comment thread tools/scripts/tsc/options-model.ts Outdated
comment?: string;
parseAliases?: string[];
parser?: "lib" | "plugins";
declarations?: (DeclarationMetadata & {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What compiler option actually has multiple of these since you removed watch options?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--help, but we can just handle that differently.

Only help needs multiple short names, so an array of full declarations
adds unnecessary complexity to every option. Represent its extra alias
separately while preserving generated declarations and help output.
…ptions

# Conflicts:
#	packages/typescript/src/api/proto.generated.ts
#	tools/gen-proto/main.go
#	tools/gen-proto/main_test.go
Alias expansion omits help text, causing inference to produce a union
whose alias member has no help properties. Expose the shared declaration
type so consumers can read those optional properties safely.
Protocol generation now ensures user preferences are generated first.
The nested compiler-options task therefore reports one additional cache
hit when all generated outputs are current.
Direct option generation no longer needs Go API visibility tags or
runtime flags retained only for reflection-based comparison tests.

Option assignment functions never report diagnostics; validation happens
before assignment. Drop the unused return values and their plumbing
without changing validation or compiler variation metadata.
Test variation eligibility should follow an option's finite set of values,
not unrelated incremental-build metadata. Generate the eligible set from
boolean and enum declarations instead of retaining runtime affects flags
solely for the test harness.

Keep the affects metadata needed for production code generation and check
its build-info invariant at generation time. Keep metadata explanations
in the source rather than repeating them in generated declarations.
The allowJs comparison needs special handling for its effective value,
but a separate flag adds no information beyond the option's name.
Redundant parser selectors and unreachable comparison handling obscure
which metadata actually controls generated behavior. Keep special parsing
tied to the option names and restrict extra validation to its supported
locale selector.

Explicitly select runtime declaration fields so new generation-only
metadata cannot accidentally become Go field initializers.
Section headings should preserve visual grouping without becoming Go
field documentation or TypeScript JSDoc. Represent them separately from
field comments and keep them detached in both generated outputs.

Remove the unused no-validation constant and the misplaced build-option
comment while retaining the internal-field sections.
The protocol types also consume BuildOptions field comments. Regenerate
them after detaching section headings so CI generation no longer removes
a stale JSDoc comment from the checked-in output.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

Add JSON schema to the typescript package for tsconfig.json and jsconfig.json

4 participants