ITADN

Compiler drops precedence-significant parentheses in logical expressions (`a && (b || c)` → `a && b || c`)

#18380Openepyy47 创建于 2026-06-04
E
epyy47commented
### Describe the bug When the Svelte compiler re-prints a JavaScript logical expression that contains a **parenthesized `||` (or `&&`) nested on the side that needs the parentheses**, it emits the expression **without the parentheses**. Because `&&` binds tighter than `||`, this silently changes the runtime semantics of the code. Source: ```js if (e.key === 'b' && (e.metaKey || e.ctrlKey)) { ... } ``` Compiled output: ```js if (e.key === 'b' && e.metaKey || e.ctrlKey) { ... } // JS parses this as: ((e.key === 'b' && e.metaKey) || e.ctrlKey) ``` So the guard fires whenever `e.ctrlKey` is true, regardless of `e.key`. In our app this made a `Ctrl+B` sidebar shortcut toggle on _any_ Ctrl-held key (and on Ctrl alone), which the source code clearly does not express. This affects both `compile` (component markup/script) and `compileModule` (`.svelte.js` / `.svelte.ts`), so it appears to be in the shared code printer (esrap), not the template transform. ### Cases | Source | Compiled output | Semantics | | ----------------------- | --------------------- | ------------------------------- | | `a && (b \|\| c)` | `a && b \|\| c` | ❌ changed → `(a && b) \|\| c` | | `(a \|\| b) && c` | `a \|\| b && c` | ❌ changed → `a \|\| (b && c)` | | `a === b && (c \|\| d)` | `a === b && c \|\| d` | ❌ changed (real-world case) | | `a \|\| (b && c)` | `a \|\| b && c` | ✅ unchanged (parens redundant) | The printer drops the parentheses around **every** nested logical expression. When the parentheses were redundant (last row) the result is harmless; when they were precedence-significant (first three rows) the meaning changes. ### Expected behavior The emitted code must preserve the semantics of the source. A `||` (or `&&`) whose parentheses are precedence-significant must be re-parenthesized when printed: `a && (b || c)` must stay `a && (b || c)`. ### Reproduction REPL (open the **JS Output** tab): https://svelte.dev/playground#H4sIAAAAAAAAA22SzWvbQBTE_5XHHhIJXJke0oNqG_oBheTQg-mpKni1GkWL12-X3ac4xtH_XvSRtIWeFubN_BiGvSrWJ6hSffGnYB0iNdGHREFHcCLL5PyjNdoRnkNEStazWinR1p0tN6pstUtYqdY6JFX-vCq5hJE3Cmr1Sv8UQpGe4GTUap3wP914FrAkVapNMtEG2VVcyXpNn398o2z20V1x96F4n5fkA5ikA1Xqfk_fewm9VIpE18US2_s-GlAN589kEx00bbdbqunmhjJDLy_U5IdX8zJAQ34iLTuM_GWLd7t_AHP-sFri586aju73ozshkU50yP6257OfsnP0_EghwqABG-RTg7ZnI9YzdZobh5ghp-t4qMS2lKE44jLRbuvbqT-KE0Q_4DJyURiJ7gGX_C1VCYoQ8QSWr2h17yTLP86nYXyGijfrPzPzpu5FPJNn46w5bq9LkWFnOpgjvW08LrxZz-5dNf0GPIsqJfYYfg2_AbmzQPhSAgAA Or run locally against the installed compiler: ```js import { compileModule, VERSION } from 'svelte/compiler'; console.log(VERSION); // 5.56.1 const src = `export const f = (a, b, c, d) => { if (a === b && (c || d)) return 1; };`; const { js } = compileModule(src, { filename: 'demo.svelte.js', generate: 'client' }); console.log( js.code .split('\n') .find((l) => l.trim().startsWith('if (')) .trim() ); ``` ### Logs ```shell ``` ### System Info ```shell System: OS: Windows 11 10.0.26200 CPU: (16) x64 13th Gen Intel(R) Core(TM) i7-1360P Memory: 18.47 GB / 31.68 GB Binaries: Node: 24.12.0 - C:\Program Files\nodejs\node.EXE npm: 11.6.2 - C:\Program Files\nodejs\npm.CMD pnpm: 10.33.3 - C:\Users\eric.poon\AppData\Local\pnpm\pnpm.CMD Browsers: Chrome: 148.0.7778.217 Edge: Chromium (148.0.3967.54) Internet Explorer: 11.0.26100.8115 npmPackages: svelte: ^5.56.1 => 5.56.1 ``` ### Severity annoyance
0 条评论