Remove two redundant shapes from the JavaScript output - #14
Merged
Conversation
MungedCodeGenerator emitted more bytes than it needed to in two places. Both
are recorded in JsGoldenFileTest's jQuery gap table as Release 2 work; this is
that work.
A ";" immediately before a "}"
function f(){var a=1;return a;} -> function f(){var b=1;return b}
if(a)b();else c(); -> if(a){b()}else{c()}
"}" ends the statement on its own, and automatic semicolon insertion covers a
reader that wants one. Upstream YUI removes it too ("Remove ';' when followed
by a '}'", CHANGELOG 1.1) and the golden files carry no ";}" at all.
Every site that emits a block now goes through visitStatementList, which skips
the separator after the last statement. The synthetic braces that if/else and
the loop bodies wrap a single statement in never needed one, since the ";" was
emitted immediately before the "}" in the same expression. Switch cases are the
one place the position matters: only the last case's last statement is followed
by the switch's "}", so every other one keeps its ";" to separate it from the
next "case".
Line breaks are unaffected. A statement that gave up its ";" is still a
statement boundary, but nothing in the output says so, so markSafeBreak is no
longer offered there - the "}" that follows immediately offers one anyway, and
every recorded break offset stays verifiable as following a ";" or a "}".
Parentheses around a conditional on an assignment's right-hand side
x=y?z:w; -> x=y?z:w; (was "x=(y?z:w);")
An assignment's right-hand side is an AssignmentExpression, which a conditional
already is. needsParentheses returned true for every ConditionalExpression
under any InfixExpression; it now excludes an Assignment's right operand only.
Everything else keeps its parentheses, because every other operator binds
tighter than "?:" - "x=a+(b?c:d)" without them would re-parse as "(a+b)?c:d".
Measurements
- jquery-1.6.4.js: 104,815 -> 103,468 bytes (-1.3%). The gap against the golden
narrows from 2,823 bytes to 1,476, and ";}" drops from 1,259 to 0, matching
the golden exactly. The gap table and both size pins are updated; what is
left of the gap is almost entirely short-name allocation.
- 576 real-world scripts (jQuery, Bootstrap, Vue/webpack and Next.js bundles,
FullCalendar, DOMPurify, marked and the Fess/CodeLibs front ends):
11,436,756 -> 11,357,307 bytes, -0.69%. No file grew.
- Every one of those outputs was checked with "node --check". The 39 that do
not parse fail identically before this change; none is new.
- No new parse failure and no new second-pass instability across the same
corpus.
Test expectations that pinned the old spelling are updated in place - the
replacement was driven from the measured (expected, actual) pairs rather than
by hand - and RedundantOutputTest pins both new shapes together with the cases
that must keep their separator or their parentheses. issue71.js.min is
regenerated; it was written for this fork rather than taken from upstream,
which drops the "use strict" the fixture exists to test.
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
MungedCodeGeneratoremits more bytes than it needs to in two places. Both are already recorded inJsGoldenFileTest's jQuery gap table as Release 2 work — this is that work.1. A
;immediately before a}}ends the statement on its own. Upstream YUI removes it too ("Remove ';' when followed by a '}'", CHANGELOG 1.1), and every golden file insrc/test/resourcescarries zero;}.2. Parentheses around a conditional on an assignment's right-hand side
An assignment's right-hand side is an
AssignmentExpression, which a conditional already is. Upstream emits this without parentheses.Fix
Every site that emits a block now goes through one
visitStatementList, which skips the separator after the last statement. The synthetic braces thatif/elseand the loop bodies wrap a single statement in never needed a;at all — it was appended immediately before the}in the same expression.Switch cases are the one place the position matters: only the last case's last statement is followed by the switch's
}. Every other one keeps its;, otherwisecase 1:b()case 2:is a syntax error. Pinned inRedundantOutputTest.needsParenthesesreturnedtruefor everyConditionalExpressionunder anyInfixExpression; it now excludes anAssignment's right operand only. Everything else keeps its parentheses, because every other operator binds tighter than?:—x=a+(b?c:d)without them re-parses as(a+b)?c:d.Line breaks are unaffected. A statement that gave up its
;is still a statement boundary, but nothing in the output says so, somarkSafeBreakis no longer offered at that position — the}that follows immediately offers one anyway. Every recorded break offset stays verifiable as following a;or a}, which is whatModernJsTest.assertEveryLineBreakIsAtAStatementBoundarychecks.Measurements
jquery-1.6.4.js: 104,815 → 103,468 bytes (−1.3%).
;before};}now matches the golden exactly (0). What is left is almost entirely short-name allocation, which stays Release 2 work.Corpus: 576 real-world scripts (jQuery, Bootstrap, Vue/webpack and Next.js bundles, FullCalendar, DOMPurify, marked, and the Fess/CodeLibs front ends) — 11,436,756 → 11,357,307 bytes, −0.69%. No file grew.
Safety: every one of those 576 outputs was run through
node --check. The 39 that do not parse fail identically before this change — none is new, and they are a separate pre-existing issue. No new parse failure and no new second-pass instability across the same corpus.Test changes
(expected, actual)pairs reported by the failing run rather than by hand, so only exact prior expectations were rewritten.RedundantOutputTest(new, 11 cases) pins both new shapes together with the cases that must keep their separator or their parentheses.JsGoldenFileTestandIdempotencyTest, and the gap table, are updated with re-measured numbers.issue71.js.minis regenerated. That golden was written for this fork rather than taken from upstream — upstream 2.4.8 drops the"use strict"the fixture exists to test.Full suite: 775 tests, 0 failures, 3 skipped.