Skip to content

feat(transform): respect decorator strictNullChecks option#9580

Merged
shulaoda merged 5 commits into
rolldown:mainfrom
kylecannon:feat/decorator-strict-null-checks
May 27, 2026
Merged

feat(transform): respect decorator strictNullChecks option#9580
shulaoda merged 5 commits into
rolldown:mainfrom
kylecannon:feat/decorator-strict-null-checks

Conversation

@kylecannon

@kylecannon kylecannon commented May 27, 2026

Copy link
Copy Markdown
Contributor

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 (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

  • Rebased/merged on top of the latest main (which already has oxc 0.133.0); cargo check --workspace passes
  • 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: AI assistance (Claude Code) was used to implement and verify this change. I have reviewed and tested it locally.

🤖 Generated with Claude Code

kylecannon and others added 2 commits May 26, 2026 22:26
Upgrade oxc 0.132.0 -> 0.133.0 (and oxc_resolver 11.19.1 -> 11.19.2) and
expose the decorator `strictNullChecks` metadata option added upstream in
oxc-project/oxc#22266.

transform.decorator.strictNullChecks (default true) controls whether null and
undefined are elided from union `design:type` metadata under
emitDecoratorMetadata: `string | null` emits `Object` when strict (the default,
matching tsc) and `String` when set to false (matching `tsc --strictNullChecks
false` and babel-plugin-transform-typescript-metadata). It is threaded through
rolldown_common DecoratorOptions (defaulting to oxc's `true` via unwrap_or(true)),
the napi binding normalizer, the validator schema, the regenerated
binding.d.cts, and the auto-generated --transform.decorator.strict-null-checks
CLI flag. It is not inferred from tsconfig.json (oxc_resolver does not parse
strictNullChecks).

Also handles the upgrade's breaking changes and fallout:
- oxc::transformer::DecoratorOptions gained a required strict_null_checks field
  (manual Default = true).
- oxc_minify_napi::CodegenOptions gained legal_comments (set to None); exposed
  as minify codegen.legalComments in the validator.
- Regenerated napi bindings (incl. LegalCommentsMode) and embedded runtime
  helpers (@oxc-project/runtime 0.133.0).
- Refreshed snapshots for oxc's new #__PURE__ / #__NO_SIDE_EFFECTS__ annotation
  syntax and updated minifier/DCE output.
- Added transform tests for both strictNullChecks modes and documented the
  option in transform.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ct-null-checks

# Conflicts:
#	crates/rolldown_common/src/inner_bundler_options/types/transform_option/decorator_options.rs
@kylecannon kylecannon changed the title feat(transform): expose decorator strictNullChecks (oxc 0.133.0) feat(transform): respect decorator strictNullChecks option May 27, 2026
Use a `:::tip` callout instead of a bare blockquote and add a fenced code
example showing the per-mode `design:type` result, matching the existing
transform/tsconfig option docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@kylecannon kylecannon marked this pull request as ready for review May 27, 2026 06:18
@codspeed-hq

codspeed-hq Bot commented May 27, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 4 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing kylecannon:feat/decorator-strict-null-checks (5df51a7) with main (0878d82)

Open in CodSpeed

Footnotes

  1. 10 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 shulaoda self-assigned this May 27, 2026

@shulaoda shulaoda 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.

Thanks!

@shulaoda shulaoda merged commit e3b7756 into rolldown:main May 27, 2026
51 of 52 checks passed
@shulaoda shulaoda mentioned this pull request May 27, 2026
shulaoda added a commit that referenced this pull request May 27, 2026
## [1.0.3] - 2026-05-27

### 🚀 Features

- transform: respect decorator strictNullChecks option (#9580) by @kylecannon
- drop `defer` keyword (#9503) by @TheAlexLichter

### 🐛 Bug Fixes

- ci: create target dir before cargo release-oxc update (#9584) by @shulaoda
- ci: reorder prepare-release steps to avoid dirty git check failure (#9583) by @shulaoda
- testing: canonicalize temp dir early and use platform-specific separator in test262 (#9582) by @shulaoda
- testing: resolve symlinked temp dir in test262 snapshot normalization (#9581) by @shulaoda
- testing: canonicalize temp dir path in test262 snapshot normalization (#9579) by @shulaoda
- dev: `onOutput` called twice when initial build fails (#9552) by @hyf0
- dev: make `ensureCurrentBuildFinish` not returning error when engine closes (#9564) by @h-a-n-a
- oxc-runtime: route require() to CJS helper variant (#9263) (#9526) by @IWANABETHATGUY
- generator: use exporter chunk's export mode for CJS default re-exports (#9299) (#9529) by @IWANABETHATGUY
- rolldown: always run reduced-atom static cycle check (#9441) (#9514) by @IWANABETHATGUY
- apply transform.dropLabels before scanning (#9521) (#9522) by @IWANABETHATGUY
- rolldown_watcher: take `rolldown` dep through the workspace (#9510) by @Boshen
- cache: keep the scan-stage cache consistent when a build fails (#9495) by @h-a-n-a
- skip JSON default-import namespace optimization for write targets (#9484) (#9489) by @IWANABETHATGUY
- deps: skip pnpm frozen-lockfile on Netlify to dodge catalog mismatch bug (#9471) by @Boshen

### 🚜 Refactor

- oxc-runtime: use Cow for helper path construction (#9538) by @IWANABETHATGUY
- fold import defer phase drop into PreProcessor (#9524) by @IWANABETHATGUY
- distinguish `map: null` vs `map: undefined` in transform hook output (#9497) by @sapphi-red

### 📚 Documentation

- explain the policy for Rust crates (#9547) by @sapphi-red
- cache: add design doc for cache (#9544) by @h-a-n-a
- guide/troubleshooting: add TDZ error section (#9537) by @sapphi-red
- dev-engine: add design doc for dev-engine (#9479) by @h-a-n-a
- lazy-barrel: tweak some words (#9483) by @shulaoda
- lazy-barrel: expand reasoning behind LARGE_BARREL_MODULES advice (#9477) by @shulaoda

### ⚡ Performance

- generate: thread ast_table by value into codegen consumer (#9555) by @Boshen
- finalizers: replace `_reExport` construction with a direct call to avoid calling `clone_in` (#9501) by @Dunqing
- reorder hot-path boolean checks to short-circuit on cheap predicates first (#9523) by @Boshen

### 🧪 Testing

- rolldown: regression fixture for #9401 (#9418) by @IWANABETHATGUY
- failing test for #9441 (#9504) by @TheAlexLichter

### ⚙️ Miscellaneous Tasks

- deps: upgrade oxc to 0.133.0 (#9563) by @Dunqing
- deps: update crate-ci/typos action to v1.46.3 (#9576) by @renovate[bot]
- deps: update mimalloc-safe to 0.1.62 (#9577) by @shulaoda
- mimalloc-safe: update to a bug-fix branch for verification (#9569) by @shulaoda
- deps: update test262 submodule for tests (#9551) by @rolldown-guard[bot]
- point published crates' readme to root README.md (#9553) by @Boshen
- replace actions-cool/issues-helper with gh CLI (#9543) by @Boshen
- deps: update cargo-shear to 1.12.4 (#9541) by @Boshen
- deps: update taiki-e/install-action action to v2.79.4 (#9535) by @renovate[bot]
- deps: update github actions (#9532) by @renovate[bot]
- deps: update rust crates (#9534) by @renovate[bot]
- deps: update npm packages (#9533) by @renovate[bot]
- gate experimental/testing-only items to silence dead_code in publish builds (#9517) by @Boshen
- docs: deploy to Void (#9509) by @Boshen
- release: set up cargo-release-oxc for publishing crates (#9476) by @Boshen
- rolldown_plugin_lazy_compilation: add missing description (#9507) by @Boshen
- mimalloc-safe: update to a bug-fix branch for verification (#9506) by @shulaoda
- deps: update crate-ci/typos action to v1.46.2 (#9468) by @renovate[bot]

### ❤️ New Contributors

* @kylecannon made their first contribution in [#9580](#9580)
shulaoda pushed a commit that referenced this pull request May 27, 2026
## [1.0.3] - 2026-05-27

### 🚀 Features

- transform: respect decorator strictNullChecks option (#9580) by @kylecannon
- drop `defer` keyword (#9503) by @TheAlexLichter

### 🐛 Bug Fixes

- ci: create target dir before cargo release-oxc update (#9584) by @shulaoda
- ci: reorder prepare-release steps to avoid dirty git check failure (#9583) by @shulaoda
- testing: canonicalize temp dir early and use platform-specific separator in test262 (#9582) by @shulaoda
- testing: resolve symlinked temp dir in test262 snapshot normalization (#9581) by @shulaoda
- testing: canonicalize temp dir path in test262 snapshot normalization (#9579) by @shulaoda
- dev: `onOutput` called twice when initial build fails (#9552) by @hyf0
- dev: make `ensureCurrentBuildFinish` not returning error when engine closes (#9564) by @h-a-n-a
- oxc-runtime: route require() to CJS helper variant (#9263) (#9526) by @IWANABETHATGUY
- generator: use exporter chunk's export mode for CJS default re-exports (#9299) (#9529) by @IWANABETHATGUY
- rolldown: always run reduced-atom static cycle check (#9441) (#9514) by @IWANABETHATGUY
- apply transform.dropLabels before scanning (#9521) (#9522) by @IWANABETHATGUY
- rolldown_watcher: take `rolldown` dep through the workspace (#9510) by @Boshen
- cache: keep the scan-stage cache consistent when a build fails (#9495) by @h-a-n-a
- skip JSON default-import namespace optimization for write targets (#9484) (#9489) by @IWANABETHATGUY
- deps: skip pnpm frozen-lockfile on Netlify to dodge catalog mismatch bug (#9471) by @Boshen

### 🚜 Refactor

- oxc-runtime: use Cow for helper path construction (#9538) by @IWANABETHATGUY
- fold import defer phase drop into PreProcessor (#9524) by @IWANABETHATGUY
- distinguish `map: null` vs `map: undefined` in transform hook output (#9497) by @sapphi-red

### 📚 Documentation

- explain the policy for Rust crates (#9547) by @sapphi-red
- cache: add design doc for cache (#9544) by @h-a-n-a
- guide/troubleshooting: add TDZ error section (#9537) by @sapphi-red
- dev-engine: add design doc for dev-engine (#9479) by @h-a-n-a
- lazy-barrel: tweak some words (#9483) by @shulaoda
- lazy-barrel: expand reasoning behind LARGE_BARREL_MODULES advice (#9477) by @shulaoda

### ⚡ Performance

- generate: thread ast_table by value into codegen consumer (#9555) by @Boshen
- finalizers: replace `_reExport` construction with a direct call to avoid calling `clone_in` (#9501) by @Dunqing
- reorder hot-path boolean checks to short-circuit on cheap predicates first (#9523) by @Boshen

### 🧪 Testing

- rolldown: regression fixture for #9401 (#9418) by @IWANABETHATGUY
- failing test for #9441 (#9504) by @TheAlexLichter

### ⚙️ Miscellaneous Tasks

- deps: upgrade oxc to 0.133.0 (#9563) by @Dunqing
- deps: update crate-ci/typos action to v1.46.3 (#9576) by @renovate[bot]
- deps: update mimalloc-safe to 0.1.62 (#9577) by @shulaoda
- mimalloc-safe: update to a bug-fix branch for verification (#9569) by @shulaoda
- deps: update test262 submodule for tests (#9551) by @rolldown-guard[bot]
- point published crates' readme to root README.md (#9553) by @Boshen
- replace actions-cool/issues-helper with gh CLI (#9543) by @Boshen
- deps: update cargo-shear to 1.12.4 (#9541) by @Boshen
- deps: update taiki-e/install-action action to v2.79.4 (#9535) by @renovate[bot]
- deps: update github actions (#9532) by @renovate[bot]
- deps: update rust crates (#9534) by @renovate[bot]
- deps: update npm packages (#9533) by @renovate[bot]
- gate experimental/testing-only items to silence dead_code in publish builds (#9517) by @Boshen
- docs: deploy to Void (#9509) by @Boshen
- release: set up cargo-release-oxc for publishing crates (#9476) by @Boshen
- rolldown_plugin_lazy_compilation: add missing description (#9507) by @Boshen
- mimalloc-safe: update to a bug-fix branch for verification (#9506) by @shulaoda
- deps: update crate-ci/typos action to v1.46.2 (#9468) by @renovate[bot]

### ❤️ New Contributors

* @kylecannon made their first contribution in [#9580](#9580)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants