Skip to content

Generate compiler option definitions, create JSON schema - #64457

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

Jake Bailey (jakebailey) wants to merge 20 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 on lines +41 to +42
"./schemas/tsconfig.schema.json": "./schemas/tsconfig.schema.json",
"./schemas/jsconfig.schema.json": "./schemas/jsconfig.schema.json",

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.

I generated these into tsc/internal/tsoptions/schemas. Perhaps I should have just checked them in here?

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.

I think this is the only open question in this PR.

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.
Aggregate reflection bypassed compiler option equality, making watch mode
sensitive to ordered-map backing storage that the project system ignores.
Compose option equality methods so both callers honor the same semantics.

Treat absent and empty type acquisition lists alike, matching their
existing acquisition behavior, while retaining other presence and order
distinctions.
Handwritten equality can silently overlook fields added to parsed options
or content mappers. Require every field to have a populated test value
whose removal changes equality, including promoted mapper fields.
Per-feature outputs fragmented related option code across too many files.
Group generated code by package, retaining a separate declarations file
in tsoptions, and keep compiler option tests together.

This reduces file proliferation without changing generated declarations
or test coverage.

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 published schema advertises watchOptions, but tsconfig parsing does not recognize or populate them.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
Resolved since last review (2)
Files not reviewed (1)
  • tsc/internal/api/enum_values_generated.go: Generated file

Comment thread tools/scripts/tsc/options-schema.ts Outdated
};
const properties: Record<string, JSONSchema> = {
$schema: { type: "string", description: "The JSON schema used to validate this configuration." },
watchOptions: withDescription({ allOf: [{ $ref: "#/definitions/watchOptions" }] }, "Options for watching files and directories.", "watchOptions"),

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.

This is actually preexisting, we're apparently not processing these at all?

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.

will someone please just delete watchOptions; I keep fighting off people's Copilots desperately trying to resurface it in the API

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.

Should we just delete it? We'd then consider it to be random junk at the top level which is legal. Maybe that's for the best?

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.

I'm just going to delete it now. It actually complicates this PR a lot because watch options are screwy.

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.

Poof

Shared configuration schemas need to recognize older TypeScript configs
without restoring removed options to the native compiler or API.

Keep historical options and enum values in schema-only metadata, with
deprecation annotations and their original value types.
Carry main's ES2026 target and library updates into the shared option
metadata and regenerate the dependent definitions and schemas.
@jakebailey
Jake Bailey (jakebailey) marked this pull request as ready for review September 28, 2026 22:51

@weswigham Wesley Wigham (weswigham) left a comment

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.

Q: Why use a JSON document as a source of truth? Generally speaking we edit go code first and everything flows from there - therefore I'd think the go structs should be the source of truth, and any json files/JS APIs/serializers should be generated from there. It's not like we can't markup the go structs with any metadata we need for any codegen steps.

@weswigham

Copy link
Copy Markdown
Member

(I say this simply because gen-proto is already generating the API's CompilerOptions type - you should just be able to add a .json output to it)

@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

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