Skip to content

Avoid uninitialized let in nested for-loop headers for ES5 compat#18

Merged
davidje13 merged 3 commits into
davidje13:mainfrom
journey-ad:patch-1
Jun 20, 2026
Merged

Avoid uninitialized let in nested for-loop headers for ES5 compat#18
davidje13 merged 3 commits into
davidje13:mainfrom
journey-ad:patch-1

Conversation

@journey-ad

Copy link
Copy Markdown
Contributor

Live Example: https://gh.ovo.re/lean-qr-verify/

Two for-loop headers declare variables without an initializer:

for (let i = 0, prev; size !== prev; ++i)
for (let j = 0, state = 0, consec = 0, last; j < size; ++j)

Under ES6 this is fine. let prev evaluates to undefined on each outer iteration, so size !== undefined is true and the loop enters correctly.

The problem appears when Babel transpiles to ES5. @babel/plugin-transform-block-scoping converts let to var. Since neither loop body references the variable in a closure, no per-iteration _loop wrapper is generated. The hoisted var keeps its value from the previous outer iteration instead of resetting to undefined.

In errorCorrection.mjs, this causes the interleave loop to skip the second data group. In score.mjs, it skews the penalty score. The result is broken QR codes with chessboard or stripe artifacts.

This patch gives the variables explicit initial values:

  • errorCorrection.mjs: let prev becomes let prev = -1. size starts at 0 and only increases, so size !== -1 is always true on first pass, same as size !== undefined.
  • score.mjs: last becomes last = 0. consec already starts at 0, so whether the first cur !== last check triggers the reset or not makes no difference.

Both changes are one character, behavior-preserving under ES6, and produce correct output through Babel ES5.

@davidje13

Copy link
Copy Markdown
Owner

Seems reasonable. Note that in general this project doesn't guarantee ES5 compatibility (nor more generally compatibility with unmaintained browsers), and since there's a big focus on small code size, it's likely there will be other issues with converting it.

I've pushed a couple of commits to update the statistics files (these are auto-generated when running the tests), and made a couple of small "golfing" changes to reduce the code size (with those, this actually saves a byte on the uncompressed size, and is approximately the same size compressed). I'll merge and cut a new release.

I'd recommend raising the var-hoisting issue to Babel - when they hoist an uninitialised variable, they clearly need to retrofit an = undefined in cases like this. I imagine it could cause issues with a number of projects.

@davidje13 davidje13 merged commit a6aa332 into davidje13:main Jun 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants