Latest Results
fix(es/typescript): Fix merged namespace bindings and enum evaluation (#12455)
**Description:**
References across merged TypeScript `namespace` and `enum` declarations
could resolve to outer lexical bindings or remain unresolved, and
lowering enums without pre-erasure dependency tracking could
constant-fold write targets (such as `delete E.A`) or strip required
runtime objects.
- **Merged declaration resolution (`swc_ecma_utils`, `resolver`)**:
Index `namespace` and `enum` containers, exported members, and `import
=` aliases across merged bodies while keeping separate type, value, and
namespace lookup spaces and distinct JS binding IDs per emitted body.
Fix parameter and computed-key scoping in TypeScript signatures.
- **Pre-erasure enum and usage analysis
(`swc_ecma_transforms_typescript`)**: Evaluate enum members and
initializer dependencies before type stripping, inline eligible constant
reads, and track surviving references to retain required enums,
namespaces, `import =` aliases, and imports.
- **Container emission**: Lower each retained `namespace` and `enum`
body with its own IIFE parameter binding, allow ambient value
declarations to instantiate enclosing namespaces without emitting
statements, and skip synthetic `export {}` when `export =` is present.
- **Write-target preservation (`RefRewriter`)**: Route assignment,
update, `delete`, and destructuring targets through
`query_lhs`—including across parentheses and TS type assertions—so
member writes rewrite their container reference without folding into
literals, while still folding reads in receivers and computed keys.
**Related issue (if exists):**
- Fixes #11514
- Fixes #11607
---------
Co-authored-by: DongYun Kang <kdy.1997.dev@gmail.com> fix(es/fixer): preserve the parentheses of string literal statements so they do not become directives (#12633)
**Description:**
Prettier prints a `"use strict";` that is not the first statement of its
function or script as `("use strict");` (measured with 2.8.8 and
3.9.10). That statement is not a directive, so the code stays sloppy,
but SWC removes the parentheses and turns it into one.
The form is in published code. GitHub code search reports about 8,800
results for that line. In a sample of 195 of those JavaScript files, 108
have it as the first statement of a script or function, and 19 of those
also assign to an undeclared name (automated AST check), which only
works in sloppy mode. 11 of the 19 are the `app.js` of one admin
template, Velzon (the files name versions from 1.6.0 to 4.3.0). Reduced
from that file:
```js
(function () {
("use strict");
function hideNested(childCollapses, bootstrap) {
Array.from(childCollapses).forEach(function (childCollapse) {
childCollapseInstance = bootstrap.Collapse.getInstance(childCollapse);
childCollapseInstance.hide();
});
}
hideNested(["a", "b"], {
Collapse: { getInstance: (c) => ({ hide: () => console.log("hide", c) }) },
});
})();
```
The original prints `hide a` and `hide b`. With @swc/core 1.16.20,
`minifySync(code, { compress: {}, mangle: {} })` gives `!function(){"use
strict";var
e;e={Collapse:{getInstance:e=>({hide:()=>console.log("hide",e)})}},Array.from(["a","b"]).forEach(function(n){(childCollapseInstance=e.Collapse.getInstance(n)).hide()})}();`,
which throws a `ReferenceError`. `transformSync` without `minify` throws
it too. There is no report from a SWC user: the evidence is this code
and what SWC does to it.
`Fixer::visit_mut_expr` unwraps every `ParenExpr`, and
`handle_expr_stmt` only restores parentheses for objects, classes,
functions and object patterns. Without them, an `ExprStmt` of a `Str` is
what a directive looks like in the AST, so the compressor and the
codegen treat it as one. `paren_remover` is the same `Fixer` and runs
first in both pipelines. The pass documents its purpose as "Fixes ast
nodes before printing so semantics are preserved."
(`crates/swc_ecma_transforms_base/src/fixer.rs:16`).
`visit_mut_expr_stmt` now keeps one pair of parentheses around a string
literal statement. They have to be kept rather than restored, because
once they are gone nothing tells the two statements apart. This is not
limited to the directive prologue: `{ ("use strict"); } y = 1;` and `0;
("use strict"); y = 1;` also became strict with the default options,
because the compressor removes what comes first.
With the compressor, the statement is now dropped like any other unused
string. Without it, the output keeps the 2 bytes. Performance: one match
on the expression of each expression statement. A real directive has no
`ParenExpr`, so it takes the same path as before. The second call to
`unwrap_expr` stopped it being inlined in `visit_mut_expr`, which cost
4.4% more instructions per iteration in `es/fixer/typescript` and
`es/large/base/fixer` (`perf stat` on builds with the `cargo-codspeed`
flags, not CodSpeed's simulator). With `#[inline(always)]`, the
difference from `main` is 0.19% and 0.21%.
A bare string that reaches the prologue another way (`function f() { var
a; "use strict"; y = 1; }`, or `` `use strict`; ``) is a different path
and is not changed here.
**Tests:**
`tests/fixture/issues/12587/` in `swc_ecma_minifier`, each with
`expected.stdout` and `expected.repeat-stdout`: the snippet above and
the other parenthesized forms (`defaults: true`, and `no-compress/` with
`defaults: false`), the snippet from the issue at the top level
(`top-level/`), and three real directives as controls. New inline tests
in `fixer.rs` cover `paren_remover` and `fixer`. On `main`, the 6
fixture tests and 6 of the 7 inline tests fail; the seventh checks that
a real directive is unchanged.
Existing snapshots that change, 6 files:
-
`crates/swc_ecma_transforms_proposal/tests/decorators/2023-11-misc{,--to-es2015}/init{Proto,Field}-existing-derived-constructor/output.js`
(4 files): the strings inside blocks keep the parentheses they have in
the input.
-
`crates/swc_ecma_minifier/tests/full/size/ecbeb82a70cef8575c7f48355f383c6c4cc1bca4/output.js`:
`[]({b(){a;"use strict"}});` becomes `[]({b(){a}});`.
-
`crates/swc_ts_fast_strip/tests/fixture/asi/erasure-after-ended-directive-prologue.transform.js`:
`afterParenthesizedString` printed `"custom"; "use strict";`, which made
the function strict, while the strip output next to it keeps
`("custom");`. Now both keep it.
- `cargo test -p swc_ecma_minifier`, `-p swc_ecma_transforms_base` (with
`fixer_test262`: 1915 passed, the same as on `main`), `-p swc --test
tsc` (4580 passed, no snapshot changes), `-p swc_ecma_codegen`, `-p
swc_ts_fast_strip` and `-p swc_html_minifier`: 0 failed.
- `cargo test -p swc_ecma_transforms_proposal`: the 419 tests that need
`mocha` could not run here; everything else passes, including the 4
decorator snapshots above, and the same 419 are the only failures on
`main`.
- `./scripts/exec.sh` (2620 passed), `cargo clippy -p
swc_ecma_transforms_base --all-targets -- -D warnings` and `cargo fmt -p
swc_ecma_transforms_base -p swc_ecma_minifier -- --check`: clean.
**Related issue (if exists):**
Closes #12587
---
Found with a differential fuzzer. Drafted with AI assistance; I
confirmed locally that the 6 new fixture tests and the 6 new fixer tests
above fail on `main` and pass with this change, that the snippet above
throws a `ReferenceError` with @swc/core 1.16.20 and on `main` and
prints `hide a` and `hide b` with this change, and that Prettier 2.8.8
and 3.9.10 print `("use strict");` for a `"use strict";` that is not the
first statement. Latest Branches
0%
kdy1/fix-ci-failure-pr-12455 +7%
fix/ts-namespace-enum-bindings 0%
kdy1/remove-windows-x86-node-changeset © 2026 CodSpeed Technology