From f6d32412b338f52dc824dc76040a3ad6ea02dc0d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 08:49:04 +0000 Subject: [PATCH 1/2] fix(babel-plugin): keep evaluated negative/non-finite numbers static #4309 switched evaluateAndInline to t.valueToNode for numbers, which builds -1 / 0/0 expressions; the SSR and universal transforms then treated them as dynamic, diverging from the oxc compiler (parity-probes "exotic literal holes"). Build NumericLiteral nodes directly where @babel/types 8 rejects the value. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0117BoWMdD4K7882vob4J9vq --- .changeset/babel-evaluated-number-literals.md | 5 +++++ packages/babel-plugin/src/shared/utils.ts | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .changeset/babel-evaluated-number-literals.md diff --git a/.changeset/babel-evaluated-number-literals.md b/.changeset/babel-evaluated-number-literals.md new file mode 100644 index 000000000..168139cb1 --- /dev/null +++ b/.changeset/babel-evaluated-number-literals.md @@ -0,0 +1,5 @@ +--- +"@solidjs/babel-plugin": patch +--- + +Evaluated negative and non-finite numbers (`c={-1}`, `d={NaN}`) are static again. Since Babel 8 support, `evaluateAndInline` built them with `t.valueToNode`, which gives `-1` / `0 / 0` expressions rather than numeric literals, so they stopped being inlined into templates (SSR emitted `ssrAttribute("c", -1)` instead of `c="-1"`) and diverged from `@solidjs/compiler` (the parity probes' "exotic literal holes"). They are now NumericLiteral nodes again, built directly where @babel/types 8's builder would reject the value; both Babel majors print them as before. diff --git a/packages/babel-plugin/src/shared/utils.ts b/packages/babel-plugin/src/shared/utils.ts index ae3953e41..66b38080a 100644 --- a/packages/babel-plugin/src/shared/utils.ts +++ b/packages/babel-plugin/src/shared/utils.ts @@ -759,6 +759,16 @@ const templateEscapes = new Map([ ["\u2029", "\\u2029"] ]); +// A NumericLiteral for any evaluated number, so `-1` and `NaN` stay static +// literals downstream. @babel/types 8's builder rejects negative and +// non-finite values (`t.valueToNode` builds `-1` / `0 / 0` expressions +// instead), so those are built as plain nodes; generators print them as +// `-1` / `NaN` / `Infinity`, as Babel 7's builder did. +function numberLiteral(value: number): t.NumericLiteral { + if (value >= 0 && Number.isFinite(value) && !Object.is(value, -0)) return t.numericLiteral(value); + return { type: "NumericLiteral", value } as t.NumericLiteral; +} + export function evaluateAndInline( value: EvaluateAndInlineNode | null | undefined, valueNode: NodePath @@ -786,7 +796,7 @@ export function evaluateAndInline( if (typeof r.value === "string") { valueNode.replaceWith(t.stringLiteral(r.value)); } else if (typeof r.value === "number") { - valueNode.replaceWith(t.valueToNode(r.value)); + valueNode.replaceWith(numberLiteral(r.value)); } else if (typeof r.value === "boolean") { valueNode.replaceWith(t.booleanLiteral(r.value)); } From 5ba6b9869abdaad186993c6c5be8fce0ba28bdb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 15:38:01 +0000 Subject: [PATCH 2/2] babel-plugin: keep -0 and prop: numbers as plain literals Export numberLiteral and use it for evaluated prop: numbers, keep -0's sign, and test the static attribute paths. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018LY7F9czoRqdMEvk2QwPr1 --- packages/babel-plugin/src/dom/element.ts | 3 +- packages/babel-plugin/src/shared/utils.ts | 6 ++-- .../test/numeric-literals.spec.js | 29 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/babel-plugin/src/dom/element.ts b/packages/babel-plugin/src/dom/element.ts index 148e2b10b..53f0d002c 100644 --- a/packages/babel-plugin/src/dom/element.ts +++ b/packages/babel-plugin/src/dom/element.ts @@ -11,6 +11,7 @@ import { VoidElements } from "../../../web/src/constants.js"; import { + numberLiteral, evaluateAndInline, getAttributeNamed, getTagName, @@ -997,7 +998,7 @@ function transformAttributes( ((type = typeof evaluated) === "string" || type === "number" || type === "boolean") ) { if (type === "number" && key.startsWith("prop:")) { - value = t.jsxExpressionContainer(t.valueToNode(evaluated) as babelTypes.Expression); + value = t.jsxExpressionContainer(numberLiteral(evaluated)); } else if (type === "boolean") { value = t.jsxExpressionContainer(t.booleanLiteral(evaluated)); } else value = t.stringLiteral(String(evaluated)); diff --git a/packages/babel-plugin/src/shared/utils.ts b/packages/babel-plugin/src/shared/utils.ts index 66b38080a..c4f92ece4 100644 --- a/packages/babel-plugin/src/shared/utils.ts +++ b/packages/babel-plugin/src/shared/utils.ts @@ -764,9 +764,11 @@ const templateEscapes = new Map([ // non-finite values (`t.valueToNode` builds `-1` / `0 / 0` expressions // instead), so those are built as plain nodes; generators print them as // `-1` / `NaN` / `Infinity`, as Babel 7's builder did. -function numberLiteral(value: number): t.NumericLiteral { +export function numberLiteral(value: number): t.NumericLiteral { if (value >= 0 && Number.isFinite(value) && !Object.is(value, -0)) return t.numericLiteral(value); - return { type: "NumericLiteral", value } as t.NumericLiteral; + const node = { type: "NumericLiteral", value } as t.NumericLiteral; + if (Object.is(value, -0)) node.extra = { raw: "-0", rawValue: -0 }; + return node; } export function evaluateAndInline( diff --git a/packages/babel-plugin/test/numeric-literals.spec.js b/packages/babel-plugin/test/numeric-literals.spec.js index 57067a3ef..f785c13b8 100644 --- a/packages/babel-plugin/test/numeric-literals.spec.js +++ b/packages/babel-plugin/test/numeric-literals.spec.js @@ -26,3 +26,32 @@ describe("evaluated numeric literals", () => { } }); }); + +describe("evaluated numeric attributes stay static", () => { + const attrs = `const a =
; +const b = ;`; + const compile = generate => + babel.transformSync(attrs, { + plugins: [[plugin, { generate, moduleName: "r-dom" }]], + configFile: false, + babelrc: false, + filename: "numeric.jsx" + }).code; + + test("dom", () => { + const code = compile("dom"); + expect(code).toContain("
"); + expect(code).toContain("_el$2.value = -1;"); + }); + + test("ssr", () => { + expect(compile("ssr")).toContain('c=\\"-1\\" d=\\"NaN\\" e=\\"0\\"'); + }); + + test("universal", () => { + const code = compile("universal"); + expect(code).toContain("c: -1"); + expect(code).toContain("d: NaN"); + expect(code).toContain("e: -0"); + }); +});