feat(tsconfig): parse strict and strictNullChecks compiler options#1166
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>
2 tasks
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
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