Avatar for the swc-project user
swc-project
swc
BlogDocsChangelog

Performance History

Latest Results

fix(es/minifier): preserve class self-references with keep_classnames (#12768) **Description:** With `mangle.keep_classnames: true`, a class self-reference in a static initializer can be rewritten to an outer variable that has not been initialized, making valid input throw `ReferenceError`. This PR ports the fix from #12766, preserving Kristjan Tulmin's original commit author and author date, and completes the fix for shorthand properties and destructuring assignments. ### Regression introduced by #12607 #12607 (`aa970dfaed81f2191ab98933e0022f6a4c41918e`) stopped preserving the outer binding of class declarations when `keep_classnames` is enabled. This fixes duplicate class bindings after scope merging, but exposes an existing bug in the renamer's class self-reference rebinding. For example, this input should print `true`: ```js (() => { class Foo { static self = Foo; } console.log(Foo.self === Foo); })(); ``` The incorrect output is equivalent to: ```js (() => { let e = class Foo { static self = e; }; console.log(e.self === e); })(); ``` The static initializer runs while the right-hand class expression is being evaluated, before the outer `let e` is initialized. Reading `e` therefore throws. The correct output keeps the self-reference bound to the inner class name: ```js (() => { let e = class Foo { static self = Foo; }; console.log(e.self === e); })(); ``` ### Failure chain and the fix from #12766 SWC identifies a binding by `(symbol, SyntaxContext)`. The following context numbers are illustrative, not JavaScript syntax: ```text Original binding and self-reference: Foo#123 Rebind the class-internal reference: Foo#123 -> Foo#0 Mangle the outer binding: Foo#123 -> e#0 ``` `keep_class_name` first needs to rebind internal references to the named class expression. Previously, `rename_ident` returned early because both symbols were spelled `Foo`, leaving those references as `Foo#123`. The later outer-binding rename then changed them to `e#0` as well. The fix ported from #12766 updates `SyntaxContext` before checking whether the symbol changed. Internal references become `Foo#0` and no longer match the outer `Foo#123 -> e#0` mapping. The return value continues to indicate whether the symbol changed. ### Complete the shorthand paths Updating `rename_ident` alone leaves a second failure path: ```js (() => { class Foo { static refs = { Foo }; } console.log(Foo.refs.Foo === Foo); })(); ``` The shorthand visitor calls `rename_ident` on a clone. A context-only replacement changes that clone from `Foo#123` to `Foo#0`, but returns `Err(())` because the spelling did not change. The visitor discards the updated clone, leaving the actual shorthand at `Foo#123`. The later outer rename still produces the incorrect `static refs = { Foo: e }` and throws. Rename the actual shorthand identifier in place so the context update survives, producing `static refs = { Foo }`. Expand the shorthand only when its spelling changes, preserving the original property key. Apply the same correction to destructuring shorthand bindings, including those with defaults. Add five runtime fixtures covering shorthand references in static fields and blocks, class rebinding with a shadowed-name control, and destructuring assignments with and without defaults. All five fail with the original #12766 fix alone and pass with the additional fix. ### Validation The following checks passed on `352ca9e912`, which contains all source and fixture changes: - `cargo fmt --all` - `cargo clippy --all --all-targets -- -D warnings` - `cargo test -p swc_ecma_transforms_base` - `cargo test -p swc_ecma_minifier` - `./scripts/exec.sh` from `crates/swc_ecma_minifier` The class-name fixtures were also rerun without `UPDATE`. The only subsequent change adds patch changeset metadata for `swc_ecma_transforms_base`, `swc_ecma_minifier`, and `swc_core`; validation was not rerun for that metadata-only commit. **Related issue (if exists):** Fixes web-infra-dev/rspack#16586. Co-authored-by: Kristjan Tulmin <689050+Krizz@users.noreply.github.com> --------- Co-authored-by: Kristjan Tulmin <kristjan.tulmin@restream.io> Co-authored-by: Kristjan Tulmin <689050+Krizz@users.noreply.github.com>
main
10 hours ago
fix(es/module): Resolve `jsc.paths` fallback targets against `baseUrl` (#12726) **Description:** With `jsc.preserveSymlinks: true`, a `jsc.paths` mapping to a missing file and a relative input filename produced a wrong import specifier. For example, `@modules/moduleA` became `.././modules/moduleA` instead of `./modules/moduleA`. `TsConfigResolver` returned the unresolved replacement path (`./modules/moduleA`) when the mapped file did not exist. That path is relative to `jsc.baseUrl`. With `preserveSymlinks`, SWC does not canonicalize the input filename, so it stayed relative. The import rewriter then compared two relative paths that used different roots. This PR changes two things: - `TsConfigResolver` now joins the fallback path to `baseUrl`. The target is the same path that callers computed before for an absolute input filename. - `NodeImportResolver` now cleans `..` components in absolute base and target paths before it calculates the relative specifier. A regression test in `path_node.rs` covers a relative input filename with and without `preserveSymlinks`. It fails without the change in `tsc.rs`. Validation: `cargo test -p swc_ecma_transforms_module -p swc_ecma_loader --all-features`, `cargo test -p swc --test compile --test preserve_symlinks`, `cargo fmt --all` and `cargo clippy` on the two touched crates passed locally for this change. CI has not run yet. The full `cargo test -p swc` and workspace-wide `cargo clippy --all` were not run. **BREAKING CHANGE:** None. **Related issue (if exists):** Closes #12668 Co-authored-by: Adam Cozzette <1115459+acozzette@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
main
10 hours ago
chore: Add changeset
CPunisher:codex/preserve-class-self-references
10 hours ago
fix(es/parser): Propagate JSX child parse errors (#12757) **Description:** The parser silently dropped JSX children when a nested child had a parse error. For example, `const view = <A><B></C></A>;` parsed as `Ok` with no diagnostics, and `A` had no children. The fragment form `<><B></C></>` had the same problem. The issue reported this for Flow, but the same code path also affects ECMAScript + JSX and TSX on current `main`. Each syntax returned an `Ok` AST with an empty child list. Root cause: `parse_jsx_children` used `let Ok(Some(child)) = self.parse_jsx_child(..) else { break; }`. This treated an `Err` from a child (here, the inner `JSXExpectedClosingTag` for `B`) the same as the normal `Ok(None)` closing-tag sentinel. The inner closing-tag parser had already consumed `</C>`, so the outer element then consumed `</A>` successfully. Fix: - `parse_jsx_children` now returns `PResult<Vec<JSXElementChild>>` and propagates child errors with `?`. `Ok(None)` stays the only list terminator. - The element, fragment, and TSRX dynamic-element callers propagate the result. - `parse_jsx_child` now returns `Ok(None)` at EOF instead of an `unexpected!` error. Before this change, that EOF error was always discarded, and the caller's closing-tag parser reported `Expected '</', got '<eof>'`. This keeps the existing diagnostics for unclosed elements unchanged (`jsx/errors/unclosed-tag`, `jsx/errors/nested-fragment-unclosed`, `typescript-errors/issue-2173/case1`). Unclosed input still fails. Now each case reports `Expected corresponding JSX closing tag for <B>` at the mismatched closing name. Tests (new fixtures, no existing fixture changed): - `tests/flow-errors/jsx-nested-closing-tag-mismatch/`: nested element, fragment, and a multiline case with whitespace between the inner and outer closing tags (the same shape as the Hermes `JSX/mismatched_closing_tag_child.js` fixture). - `tests/flow/jsx-nested-closing-tag/`: valid controls (`<A><B></B></A>`, `<><B></B></>`) that keep `B` as a child. - `tests/jsx/errors/nested-wrong-closing-tag{,-fragment}/`: ECMAScript + JSX. - `tests/typescript-errors/jsx-nested-closing-tag-mismatch/{element,fragment}/`: TSX. All new error fixtures failed on `main` with "should fail, but parsed as", and they pass with this change. `flow-hermes/expected-errors.txt` did not change: `JSX/mismatched_closing_tag_child.js` was already expected to fail and still fails. Local validation, run on the PR head: - `cargo fmt --all`: pass - `cargo clippy --all --all-targets -- -D warnings`: pass - `cargo test -p swc_ecma_parser --features flow,tsrx,verify`: pass - `cargo test -p swc_ecma_parser`: pass - `cargo test -p swc_ecma_transforms_typescript --test flow` (reparses `tests/flow` fixtures): pass **Related issue (if exists):** Closes swc-project/swc-ai-issues#248 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
main
12 hours ago
fix(es/parser): Keep identifier-led Flow anonymous signature param types (#12761) **Description:** In a Flow function type, each parameter is either named (`name: T`, `name?: T`) or an anonymous type. The shared signature parser (`parse_ts_binding_list_for_signature`) treated a word as an anonymous type only when `<` came next. Other identifier-led types went to the binding-pattern parser. This path is used for generic signatures and for parameters after a named parameter. As a result: - `type F = <T>(T) => void;` and `type F = (x: number, T) => void;` produced a binding named `T` with `type_ann: None`. The type was lost. - `(x: number, NS.T) => void`, `(x: number, T[]) => void`, `(x: number, T | U) => void`, `<T>(NS.T) => void`, and rest forms such as `(x: number, ...T) => void` failed to parse or lost the type. The all-anonymous `(T) => void` fast path (`try_parse_flow_anon_fn_type`) already handled these cases. Only the shared path was wrong. **Fix** - Add a `FlowAnonParamWords` mode to `flow_starts_like_anon_signature_param_type` and `try_parse_flow_anon_signature_param`. - In the shared Flow signature parser (`UnlessNamed`), a word that is not followed by `:`, `?`, or `=` starts an anonymous type. The parser builds it with `make_flow_anon_fn_param` and the original parameter index, so the output matches the `(T) => void` path (`__flow_anon_param_N` with the full type annotation). - A word followed by `:`, `?`, or `=` goes directly to the named-parameter parser. This is a one-token peek. It also skips the speculative type parse that the old code did for keyword-led named params such as `string: T`. The change adds no new nested try-parse. - The other two callers keep the old behavior (`GenericOnly`): formal parameters of `declare` signatures, and the `(`-lookahead in `is_ts_unambiguously_start_of_fn_type`. In those places a bare word can still be a binding, or the operand of a contextual keyword such as `renders`. - The existing exclusions stay: the bare `renders` check and the trailing `:`/`?`/`=` checks after the type. **Tests** New fixtures in `crates/swc_ecma_parser/tests/flow/` (I checked the AST snapshots for parameter names, indexes, type annotations, type parameters, and return types): - `issue-ai-306-generic-anon-signature-param`: `<T>(T)`, `<T>(NS.T)`, `<T>(T[])`, `<T, U>(T | U)`, `<T, U>(T & U)`, `<T>(Array<T>)`, `<T, U>(T, U, NS.T)`, `<T>(...T)`, `<T>(...NS.T)`, `<T>(?T)`, `<T>(string[], typeof x)`, and the `poly_guard` source `declare function poly(x: any): x is <T>(T) => void;`. - `issue-ai-306-anon-signature-param-after-named`: the same shapes after `x: number` and `x?: number`, generic mixed signatures, `(T, x: number, U)`, keyword-led `string[]` and `typeof x` in a non-first position, and a callable object member `{ (x: number, T): void }`. - `issue-ai-306-named-signature-param-controls`: `(T)`, `(NS.T)`, `<T>(x: T)`, `<T>(Array<T>)`, `(x: T)`, `(x: number, y: NS.T)`, optional and rest named params, keyword names (`string: T`, `type: T`), and `function f(T) {}`. These keep their previous ASTs. - TypeScript control `tests/typescript/issue-ai-306-generic-bare-param`: `<T>(T) => void` keeps its named, unannotated `T` parameter. The existing `tests/flow/issue-11729-phase2` snapshot changed. Its input has `<MethodName: $Keys<…>>(() => …, MethodName) => …`. The second parameter is now `__flow_anon_param_1: MethodName` instead of an unannotated binding `MethodName`. This is the bug that this PR fixes. The input file did not change. `flow-hermes/expected-errors.txt` did not change. The Hermes parity test still passes. Not changed: `(x: number, T?) => void` still parses as an optional named parameter `T` with no type, as on `main`. Anonymous bare-word params in `declare function` / `declare class` formal parameter lists are out of scope. **Validation** (local, on the head of this branch) - `cargo fmt --all`: pass. - `cargo clippy --all --all-targets -- -D warnings`: pass. - `cargo test -p swc_ecma_parser --features flow`: pass (all suites, including `flow`, `flow_hermes`, `typescript`, `test262`). - `cargo test -p swc_ecma_parser`: pass. - `cargo test -p swc_ecma_transforms_typescript --no-fail-fast`: the `flow` and `lowering` suites pass. Some `decorators` and `semantic` exec tests fail locally because `mocha` is not on `PATH`. These tests are not related to this change. - `cargo test -p swc --features flow --test flow`: `flow_strip_correctness` passes for all fixtures. `flow_strip_projects` fails on a react-native project file (`failed to downgrade Flow type-only module to script`). It fails in the same way with the parser from `main`, so this change does not cause it. **BREAKING CHANGE:** None. **Related issue (if exists):** Closes swc-project/swc-ai-issues#306 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
main
12 hours ago
fix(es/parser): Parse Flow mapped members in any position and validate setter arity (#12763) **Description:** This PR fixes three Flow parser bugs in object type members. All three are in `crates/swc_ecma_parser/src/parser/typescript.rs`. 1. **An object type that starts with a mapped member cannot have more members** (swc-ai-issues#307). For `{ [K in keyof O]: T, foo: string }`, the parser reported `Expected("}", "ident")`. The `{` dispatch selects the TypeScript single mapped type parser, and that parser requires `}` after one member. - In Flow, the mapped type parser now continues when another member follows. It lowers the first mapped member and parses the remaining members in the same way as an ordinary object type. The result is the same `TsTypeLit` that you get when the mapped member is not first. Comma, semicolon and newline separators, spreads, indexers, the explicit inexact `...` marker and more mapped members are supported. - **A lone mapped member keeps the current `TsMappedType` AST**, including a trailing `,` or `;`. The `issue-11729-mapped-type-trailing-comma` snapshots do not change. - In Flow, the mapped-first path now parses member types outside `DisallowFlowAnonFnType`, like `parse_ts_object_type_members`. Thus unparenthesized function types work in all members, for all member orders. - TypeScript behavior does not change. `{ [K in keyof O]: T, foo: string }` is still an error in TypeScript. 2. **Signed optionality (`+?` / `-?`) on a mapped member after other members is rejected** (swc-ai-issues#305). The member-list path accepted only a bare `?`. The member-list path and the single mapped type parser now use a shared `parse_ts_mapped_type_optional`. A sign without `?` is still an error. The error is now `Expected '?', got ':'` because a mapped member is detected with a lookahead for `[name in` instead of a speculative parse. Like the mapped type parameter parser, the lookahead accepts any IdentifierName, including reserved words such as `in` (`[in in keyof O]: T`). 3. **An object type setter with two or more parameters silently drops the extra parameters** (swc-ai-issues#308). In Flow, the parser now reports the recoverable `SyntaxError::SetterParam` ("A `set` accessor must have exactly one parameter") at the key, like object literal setters. The accessor is parsed speculatively, and speculation drops recoverable errors. Thus the closure returns the key span and the error is emitted after the speculation succeeds. The AST still keeps the first parameter. TypeScript behavior does not change, as the issue requested. **AST change for mapped members in a member list:** `TsTypeElement` has no mapped member variant, so the parser represents such a member as a synthetic `__flow_mapped` property, like `__flow_spread`. Before this PR, the property annotation was only the value type, so the parser dropped the type parameter and could not store `+`/`-`. Now the property annotation is the complete `TsMappedType`, which keeps the `+?`/`-?` modifier (`Some(Plus)` / `Some(Minus)`) and the type parameter. The property `optional` flag shows the effective optionality: `?` and `+?` are optional, `-?` and no modifier are required. The `readonly` flag continues to show `+` variance. Code generation strips Flow types, so this change does not affect emitted code. No existing snapshot changed. **Tests:** - `tests/flow/object-type-mapped-members/`: mapped-first inputs (all valid inputs from swc-ai-issues#307, with `,`, `;` and newline separators, inexact marker and function types), signed optionality first and after a property (swc-ai-issues#305), ordinary-first, spread-first, exact and variance controls, standalone controls, and the Hermes `multiple_props.js` case in both orders. `keyword-type-param.js` covers reserved-word type parameters (`[in in keyof O]`, `[out in keyof O]-?`) before, after and without other members. - `tests/flow/object-type-accessors/controls.js`: one-parameter setter with and without a return annotation, getter, and two-parameter method. - `tests/flow-errors/object-type-setter-extra-params/`: two and three parameters, no return annotation, and an exact object. - `tests/flow-errors/object-type-mapped-member-sign-without-question/`: `+:` and `-:` after a property and in the first position. - `tests/typescript-errors/types/mapped-type-mixed-members/`: TypeScript still rejects mixed members. - `tests/flow-hermes/expected-errors.txt` is unchanged. The Hermes parity test passes. `migrated_0036.js` (zero-parameter setter) is still rejected. **Local validation** (on `2296ea32dc`): - `cargo fmt --all`: pass. - `cargo clippy --all --all-targets -- -D warnings`: pass. - `cargo test -p swc_ecma_parser --features flow`: pass (all suites, including `flow_hermes`). `cargo test -p swc_ecma_parser`: pass. - `cargo test -p swc_ecma_transforms_typescript --test flow`: pass (55 tests; reparses all `tests/flow` fixtures after stripping). - `cargo test -p swc --features flow --test flow`: 55 passed, 1 failed. `flow_strip_projects` fails on `react-native/.../GenerateModuleH-test.js` ("failed to downgrade Flow type-only module to script"). The same test fails in the same way with the `origin/main` parser, so this PR did not cause it. All `flow_strip_correctness` fixtures pass, including the new fixtures. **Follow-up (not in this PR):** TypeScript object type setters with extra parameters also drop them silently. swc-ai-issues#308 put the TypeScript behavior out of scope. **BREAKING CHANGE:** None. **Related issue (if exists):** Closes swc-project/swc-ai-issues#307 Closes swc-project/swc-ai-issues#305 Closes swc-project/swc-ai-issues#308 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
main
12 hours ago

Latest Branches

CodSpeed Performance Gauge
0%
docs: Require Priority and Effort fields for swc-ai-issues issues#12769
10 hours ago
c4fb462
t3/ai-issue-priority-effort
CodSpeed Performance Gauge
+8%
10 hours ago
c682b13
CPunisher:codex/preserve-class-self-references
CodSpeed Performance Gauge
-8%
fix(es/parser): Do not parse instantiation expressions in Flow#12764
1 day ago
b10de93
t3/flow-type-argument-gating
© 2026 CodSpeed Technology
Home Terms Privacy Docs