Skip to content

port to oxc#638

Draft
nathanwhit wants to merge 30 commits into
denoland:mainfrom
nathanwhit:oxc-port
Draft

port to oxc#638
nathanwhit wants to merge 30 commits into
denoland:mainfrom
nathanwhit:oxc-port

Conversation

@nathanwhit

Copy link
Copy Markdown
Member

#-private property and method definitions were not treated as private
(only TS `private` accessibility was), so they leaked into the public
API type inference and produced spurious missing-explicit-type errors.
Also emit the collapsed `#private` placeholder as `#private!: unknown`
without `declare` (declare is invalid on a private identifier, TS18019)
to match upstream.
… logical exprs as leavable

- Destructured (object/array) params with a leavable default (`{a} = {}`)
  were always flagged missing-explicit-type because the default lives in
  `FormalParameter.initializer` (not an `AssignmentPattern`) and the
  object/array pattern arms ignored it.
- `||`/`&&`/`??` (`LogicalExpression`) were treated as non-leavable; SWC
  models these as binary expressions, so they're leavable when both
  operands are. Fixes spurious missing-explicit-return-type on arrows like
  `() => a === b || c === d`.
nathanwhit added 16 commits June 1, 2026 20:09
`is_private_member` only recognized the TS `private` keyword for class
methods/properties, not `#`-private members (unlike auto-accessors). As a
result the range finder traced the types referenced by `#`-private members,
pulling non-exported internal types into the public API surface and
emitting spurious diagnostics for them.
… params

The dependency analyzer skipped two kinds of type references, so the
symbols/types they referenced were dropped from the fast-check output
(causing TS2304 'cannot find name' at type-check time):
- a variable declarator's explicit type annotation (`const x: T`) was
  never visited (only the initializer was, and only when there was no
  annotation).
- a function/method/constructor rest parameter's type (`...args: T`) was
  never visited (only `params.items`, not `params.rest`).
A public function/method parameter without a type annotation keeps its
(leavable) default value in the output, so the values it references are
dependencies. The dep analyzer only visited the parameter's pattern, so
defaults referencing non-exported values (e.g. `isNode = isAstNode`) had
those values dropped from the output, causing TS2304 at type-check time.
…ures

Extend the rest-parameter dependency handling to the remaining
param-visiting sites that only looked at `params.items`: arrow functions
and TS call/construct/method signatures. Their rest-param types
(`(...args: T) => ...`) are kept in the output and so are dependencies.
A parameter with both a type annotation and a default (`p: T = expr`)
kept the default in the emitted signature, which (a) diverged from the
upstream output and (b) referenced the default's value, dropping it from
the output and causing TS2304 at type-check time. Match upstream: drop the
default, marking the parameter optional when it can be trailing-optional,
otherwise widening its type to `T | undefined`.
The default-export symbol for `export default someIdent` used the
identifier's span rather than the full statement span, so the transform's
public-range check never matched and the `export default` statement was
dropped from the output (the referenced binding was kept), producing
TS1192 'no default export' for importers. Use the full statement span.
The OXC port's `CommentsMut` kept every comment, whereas the swc-based
deno_graph prunes leading comments down to JSDoc block comments and
`@ts-*` line comments. As a result `@deno-types="npm:@types/..."` pragmas
survived into the fast check emit. When such an emit is type-checked as a
root, the pragma pulls in a second `@types/react` instance whose
`ForwardRefExoticComponent` (a `unique symbol $$typeof`) no longer unifies
with the one seen through the dependency graph, producing spurious
`TS2344` errors (e.g. `React.ElementRef<typeof Primitive.div>` across the
radix-ui-fork / bureaudouble React packages).

Filter comments in `CommentsMut::new` to match swc: keep only `@ts-*`
line comments and `/** */` JSDoc blocks. Regenerated the 8 fast_check
spec snapshots, which now match the swc repo output.
oxc_codegen only emits comments at statement/declaration boundaries, not
on interior nodes like function parameters (confirmed still true on the
latest oxc 0.134). So an error-suppression directive written inside a
signature, e.g.

    function f(a: number, // @ts-ignore: ...
      b?: Foo<T, U>): T[]

was dropped from the fast check emit, resurfacing the error it suppressed
(the `edouardmisset/utils` TS2304 cluster).

Re-anchor `@ts-ignore` / `@ts-expect-error` line comments that sit interior
to a top-level statement onto that statement's start, so codegen emits them
as a leading comment. The emitted signature is collapsed to one line, so
the directive still suppresses the same error. Directives inside a
function/method body are left untouched: the body is replaced with
`{} as never`, so codegen already drops them and hoisting would produce
spurious TS2578 "unused directive" errors.
…mes, scope @ts hoist to params

Three related fast check emit fixes surfaced by the ecosystem corpus:

* Constructor parameter properties with a default (`constructor(public r = 0)`)
  were hoisted to a class field without a type (`declare r;`), making them
  implicitly `any` (TS7008). Infer the field type from the default initializer,
  matching the existing AssignmentPattern handling. (iz7n/std, gfx/canvas)

* Expando namespace members named with a reserved word (`alias.for = ...`)
  were emitted as `export var for = ...`, which is a syntax error. `is_valid_js_ident`
  now rejects reserved keywords, so such members are dropped like swc does.
  (decorators/alias)

* Restrict the interior `@ts-ignore`/`@ts-expect-error` hoist (added for the
  edouardmisset cluster) to directives inside a parameter list. Hoisting a
  class-member directive to the class statement would either miss the line it
  guards or become an unused-directive error (TS2578).

Regenerated the type_assertion fast_check spec, which now carries the inferred
parameter-property type (matching the swc output).
`expr as const` is kept verbatim in the fast check output, so identifiers it
references are dependencies. OXC represents it as a `TSAsExpression` with a
`const` type (swc uses a distinct `TsConstAssertion`), and our dep visitor
only walked the type annotation, dropping the value's references. As a result
a referenced binding (`const foo = ...; export const bar = { foo } as const`)
was pruned, producing a dangling shorthand reference (TS18004).

Walk the value expression when the assertion type is `const`. (marvinh/foo)
A typed destructured parameter (`{ x }: T = default` / `[x]: T = default`)
kept its runtime default in the emit. When the default referenced a pruned
binding (`{ components = empty }: Options = { components: empty }`), the
reference dangled (TS2304). Matching swc, always replace the default of a
*typed* destructured param with a `{} as never` / `[] as never` placeholder
(keeping the param optional); only an untyped param relies on a leavable
default to avoid requiring an explicit type.

Regenerated functions / class_params_initializers fast_check specs, whose
typed destructured defaults now read `= {} as never` like the swc output.
(bureaudouble/html-parse-stringify)
Decorators are runtime-only and aren't part of the fast check type surface.
Class-level decorators were already cleared, but method/property/accessor
decorators (and constructor parameter decorators) were left in the emit. When
a decorator's import was pruned as unused, the dangling reference failed type
checking (e.g. `@HostListener(...)` → TS2304). Clear member and parameter
decorators too, matching swc.

Regenerated basic / cache__basic fast_check specs, which no longer carry the
`@dec` method/parameter decorators (matching the swc output).
(lenicdev/ng-hcaptcha)
….134

Updates the deno_ast dependency to one built on oxc 0.134 and uses its new
`scope_analysis` (oxc_semantic) output to make symbol resolution scope-aware.

oxc 0.134:
- toolchain bumped to 1.96 (oxc_transformer uses stabilized `if let` guards)
- string-literal value type `Atom` -> `Str`
- the newer codegen emits interior (class member) comments, fixing a class
  of dropped `@ts-ignore`/`@ts-expect-error` directives (e.g. gfx/canvas)

Scope-aware resolution:
- `Id`'s discriminator is now the resolved oxc `SymbolId` (the swc
  `SyntaxContext` equivalent) instead of always `0`, threaded through
  `to_id`, the dep analyzer, the transformer's expando handling, and
  `EsModuleInfo`. This fixes declaration merging across scopes (e.g. a
  `type X` + `namespace X` pair reused under multiple parents), which
  previously dropped a merged declaration (agent/openai TS2694).
- Unresolved references (which oxc_semantic leaves symbol-less, e.g. the
  root of `X.prototype.y` type names) fall back to matching a uniquely
  named declaration.

Regenerated symbols specs (Id discriminators now reflect symbol ids) and the
ref_obj_type fast_check spec (interior JSDoc now emitted).
Re-blesses the ecosystem fast-check corpus after the emit fixes, oxc 0.134
update, and scope-aware symbol resolution. Net effect vs the prior corpus:
~39 previously-failing TYPE CHECK packages now pass, 0 EMIT regressions.

The only remaining type-check failures introduced are halvardm/sqlx (2
versions): a `@ts-ignore` written inside an `extends` clause's type
arguments. oxc codegen emits interior comments on class members now but not
yet inside type-argument lists, so that directive is still dropped. Tracked
as a known codegen limitation.
These versions couldn't be re-blessed locally earlier because the JSR mirror
was incomplete (missing source files), so their committed expectations were
stale swc-era output. They still fail type checking on a pre-existing
`Cannot find namespace 'JSX'` (no DOM/JSX lib); the emit shape just matches
the oxc port now (e.g. object rest params kept as `{ ...props }`). CI (with a
complete mirror) caught the drift; this captures the correct deterministic
output.
@nathanwhit nathanwhit marked this pull request as draft June 3, 2026 01:46
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.

1 participant