Repository navigation
feat(tsconfig): parse strict and strictNullChecks compiler options - #1166
Merged
Merged
Conversation
Add `strict` and `strictNullChecks` to `CompilerOptions`, inherit them independently through `extends`, and expose `effective_strict_null_checks()` which applies TypeScript's rule that an explicit `strictNullChecks` overrides the value implied by `strict` (returning `None` when neither is set, leaving the default to the consumer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
kylecannon
marked this pull request as ready for review
May 27, 2026 05:58
2 tasks done
Merging this PR will improve performance by 5.77%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | resolver_real[multi-thread] |
411 µs | 388.6 µs | +5.77% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing kylecannon:tsconfig-strict-null-checks (d4e53e3) with main (827b45a)
Footnotes
-
5 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
shulaoda
added a commit
to rolldown/rolldown
that referenced
this pull request
May 27, 2026
## Summary Builds on #9563, which upgraded oxc to `0.133.0` and surfaced the `strictNullChecks` decorator-metadata option (from oxc-project/oxc#22266) in the TypeScript types, the validator, and the generated binding. However, #9563's Rust `From<DecoratorOptions> for oxc::transformer::DecoratorOptions` hardcodes `strict_null_checks: true`, and the napi options normalizer doesn't carry the field, so `transform.decorator.strictNullChecks: false` is currently **silently ignored** through rolldown's transform/bundle pipeline (the option exists in the type surface but does nothing). This PR wires it through so the option actually takes effect. ## What changed - `rolldown_common::DecoratorOptions` gains a `strict_null_checks: Option<bool>` field; its `From` impl now uses `options.strict_null_checks.unwrap_or(true)` (mirroring oxc's own default) instead of the hardcoded `true`. - `normalize_binding_transform_options` forwards `strict_null_checks` from the incoming napi options into `DecoratorOptions` (previously dropped). - Docs (`transform.md`) + tests. ## Behavior When `emitDecoratorMetadata` is enabled (legacy decorators): | Source | `strictNullChecks: true` (default) | `strictNullChecks: false` | | --- | --- | --- | | `string \| null` | `Object` (matches `tsc` strict) | `String` (matches `tsc --strictNullChecks false` / `babel-plugin-transform-typescript-metadata`) | | `number \| undefined` | `Object` | `Number` | Defaults to `true`, so there is **no behavior change for existing users**; only an explicit `strictNullChecks: false` is newly honored. > Note: this is **not** inferred from `tsconfig.json` *yet*. `oxc_resolver`'s `CompilerOptions` does not currently parse `strictNullChecks`, so (unlike `experimentalDecorators`/`emitDecoratorMetadata`) it must be set explicitly on `transform.decorator`. Documented in `transform.md`. See Follow-up below. ## Follow-up: inferring from `tsconfig.json` [oxc-project/oxc-resolver#1166](oxc-project/oxc-resolver#1166) (`feat(tsconfig): parse strict and strictNullChecks compiler options`) is the enabling resolver-side change. It adds `strict`/`strictNullChecks` to `CompilerOptions`, inherits them through `extends`, and exposes `effective_strict_null_checks()` implementing tsc's `strictNullChecks ?? strict` precedence. Once it lands and is released, a small downstream follow-up here can forward `compiler_options.effective_strict_null_checks()` in `merge_transform_options_with_tsconfig` into `DecoratorOptions.strict_null_checks` (mapping `None` to the transformer default), plus add the field to the inline `BindingTsconfigCompilerOptions` path, making `strictNullChecks` auto-inferred from tsconfig like the other decorator options. This PR is intentionally scoped to the explicit option so it's useful immediately and independent of that release. ## Test plan - [x] Rebased/merged on top of the latest `main` (which already has oxc `0.133.0`); `cargo check --workspace` passes - [x] New `transform.test.ts` cases assert `string | null` emits `Object` by default and `String` under `strictNullChecks: false` (verified locally; the full suite runs in CI) ## AI usage disclosure Per the [AI Usage Policy](https://rolldown.rs/contribution-guide#ai-usage-policy): AI assistance (Claude Code) was used to implement and verify this change. I have reviewed and tested it locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
Boshen
pushed a commit
that referenced
this pull request
May 27, 2026
## 🤖 New release * `oxc_resolver`: 11.19.2 -> 11.20.0 * `oxc_resolver_napi`: 11.19.2 -> 11.20.0 <details><summary><i><b>Changelog</b></i></summary><p> ## `oxc_resolver` <blockquote> ## [11.20.0](v11.19.2...v11.20.0) - 2026-05-27 ### <!-- 0 -->🚀 Features - *(tsconfig)* parse `strict` and `strictNullChecks` compiler options ([#1166](#1166)) (by @kylecannon) ### <!-- 1 -->🐛 Bug Fixes - *(tsconfig)* scope default `**/*` include to the tsconfig directory ([#1161](#1161)) (by @Boshen) ### Contributors * @kylecannon * @Boshen </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). Co-authored-by: oxc-guard[bot] <276638029+oxc-guard[bot]@users.noreply.github.com>
3 tasks done
sapphi-red
pushed a commit
to rolldown/rolldown
that referenced
this pull request
Jun 18, 2026
## Summary Follow-up to #9580 (merged), which added the `transform.decorator.strictNullChecks` option and the `DecoratorOptions.strict_null_checks` field. That PR noted the value could not yet be inferred from `tsconfig.json` because `oxc_resolver` did not parse `strictNullChecks`. `oxc_resolver` now parses `strict` and `strictNullChecks` (oxc-project/oxc-resolver#1166, already on `main` via the resolver bump). This PR forwards the value, so `strictNullChecks` is now inferred from `tsconfig.json` like `experimentalDecorators` and `emitDecoratorMetadata`. ## What changed - `merge_transform_options_with_tsconfig` reads `strictNullChecks ?? strict` (matching tsc's precedence) from the resolved tsconfig and sets `DecoratorOptions.strict_null_checks` when the option is not set explicitly. - The explicit `transform.decorator.strictNullChecks` option still takes precedence over tsconfig (with a conflict warning when `warn_on_conflict` is set). - When tsconfig sets neither flag, the transformer default (`true`) is kept, so existing behavior is unchanged. - The inline tsconfig type `BindingTsconfigCompilerOptions` gains `strict` and `strictNullChecks`, mapped into `oxc_resolver::TsConfig`, so the non-disk `transform({ tsconfig: { compilerOptions } })` path works too. - `tsconfig.md` lists the new option; tests cover inference, the `strict` fallback, the default, and explicit-option precedence. ## Behavior With `emitDecoratorMetadata` enabled, for a `string | null` field: | tsconfig | emitted `design:type` | | --- | --- | | `strictNullChecks: true` (or `strict: true`, or unset) | `Object` | | `strictNullChecks: false` (or `strict: false`) | `String` | `strictNullChecks` overrides `strict`, and an explicit `transform.decorator.strictNullChecks` overrides tsconfig. ## Test plan - [x] `cargo check --workspace --locked`, `cargo clippy -p rolldown_common -p rolldown_binding --all-targets -- --deny warnings`, `cargo fmt --all --check` - [x] `vp check` (0 type/lint errors) - [x] Node suite (`test:main`): 752 passed. New `transform.test.ts` cases assert that tsconfig `strictNullChecks: false` (and `strict: false`) emit `String`, the default emits `Object`, and an explicit option overrides tsconfig. ## AI usage disclosure Per the [AI Usage Policy](https://rolldown.rs/contribution-guide#ai-usage-policy): AI assistance (Claude Code) was used to implement and verify this change. I have reviewed and tested it locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
CompilerOptionsmodels only the options the resolver/transformer pipeline consumes (paths,baseUrl,jsx*,experimentalDecorators,emitDecoratorMetadata,useDefineForClassFields,verbatimModuleSyntax,target,module, and so on). It has no field forstrictorstrictNullChecks, so even when a user sets them intsconfig.jsonthe value is dropped at parse time, leaving a downstream consumer nothing to read.This blocks consumers (for example rolldown's
merge_transform_options_with_tsconfig) from inferringstrictNullChecksfrom a tsconfig and forwarding it to the transformer, where it controls whethernull/undefinedare elided from uniondesign:typeannotations under legacyemitDecoratorMetadata. Today that can only be set via the explicittransform.decorator.strictNullChecksoption.This PR adds the two fields, inherits them through
extends, and exposes a resolver that applies TypeScript's precedence rule.TypeScript semantics
tscresolves the flag asstrictNullChecks ?? strict(seegetStrictOptionValue:options[flag] === undefined ? !!options.strict : options[flag]):strictNullChecksalways wins over the value implied bystrict, in either direction;strictNullChecksis absent it falls back tostrict;strict: falseon its own therefore yieldsfalse.effective_strict_null_checks()implements exactly this withself.strict_null_checks.or(self.strict). It returnsNoneonly when neither is set, deliberately leaving the final default to the consumer rather than baking one into the resolver (the common transformer default istrue).The two fields are merged independently in
extend_tsconfig(mirroringemitDecoratorMetadata), so the precedence rule stays correct across anextendschain: the nearest explicitstrictNullChecksstill wins over astrictset in any ancestor, consistent with the later-wins merge from #1156.Changes
src/tsconfig.rsCompilerOptions: addstrict: Option<bool>andstrict_null_checks: Option<bool>(serde camelCase mapping, no per-field rename).extend_tsconfig: inherit both fields independently from the extended config.effective_strict_null_checks()accessor (#[must_use]).src/tests/tsconfig_extends.rstest_effective_strict_null_checks_resolution: full truth table (strict:truealone, explicitstrictNullChecks:falseoverride,strict:falsealone, neither).test_extend_tsconfig_strict_null_checks: a child's explicitstrictNullChecks:falsesurvives inheriting a parent'sstrict:true; a child with neither inherits the parent'sstrict.Purely additive. All existing tests pass (
cargo testandcargo test --all-features, clippy-D warnings,cargo doc -D warnings, fmt, typos, and the NAPI JS suite all green locally).Follow-up (separate, downstream)
This is the enabling resolver-side change. Inferring the value end to end is a downstream PR in rolldown: forward
compiler_options.effective_strict_null_checks()inmerge_transform_options_with_tsconfigintoDecoratorOptions.strict_null_checks(mappingNoneto keep the transformer default), and add the field to its inlineBindingTsconfigCompilerOptionsfor the non-disk config path.🤖 Generated with Claude Code