diff --git a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js index c1d8ab57d..dbfc24a8f 100644 --- a/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js +++ b/packages/babel-plugin-minify-mangle-names/__tests__/mangle-names-test.js @@ -1281,4 +1281,67 @@ describe("mangle-names", () => { `); expect(transform(source, { topLevel: true }, "module")).toBe(expected); }); + + // Safari raises a syntax error for a `let` or `const` declaration in a `for` + // loop initialization that shadows a parent function's parameter. + // https://github.com/babel/babili/issues/559 + // https://bugs.webkit.org/show_bug.cgi?id=171041 + // https://trac.webkit.org/changeset/217200/webkit/trunk/Source + describe("Safari for loop lexical scope workaround", () => { + it("should not shadow params in ForStatement.init", () => { + const source = unpad(` + function a(b) { + for (let c;;); + } + `); + const expected = unpad(` + function a(a) { + for (let b;;); + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should not shadow params in ForInStatement.left", () => { + const source = unpad(` + function a(b) { + for (let c in d); + } + `); + const expected = unpad(` + function a(a) { + for (let b in d); + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should not shadow params in ForOfStatement.left", () => { + const source = unpad(` + function a(b) { + for (let c of d); + } + `); + const expected = unpad(` + function a(a) { + for (let b of d); + } + `); + expect(transform(source)).toBe(expected); + }); + + it("should not shadow params in complex ForStatement.init", () => { + const source = unpad(` + function a(b) { + for (let [c, d] of e); + } + `); + const expected = unpad(` + function a(a) { + for (let [b, c] of e); + } + `); + expect(transform(source)).toBe(expected); + }); + }); }); diff --git a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js index c6300d7f2..946696ac2 100644 --- a/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js +++ b/packages/babel-plugin-minify-mangle-names/src/scope-tracker.js @@ -121,6 +121,37 @@ module.exports = class ScopeTracker { return false; } + // Safari raises a syntax error for a `let` or `const` declaration in a + // `for` loop initialization that shadows a parent function's parameter. + // https://github.com/babel/babili/issues/559 + // https://bugs.webkit.org/show_bug.cgi?id=171041 + // https://trac.webkit.org/changeset/217200/webkit/trunk/Source + const isBlockScoped = binding.kind === "let" || binding.kind === "const"; + const isForLoopDeclaration = + (binding.path.parentPath.parent.type === "ForStatement" && + binding.path.parentPath.key === "init") || + (binding.path.parentPath.parent.type === "ForInStatement" && + binding.path.parentPath.key === "left") || + (binding.path.parentPath.parent.type === "ForOfStatement" && + binding.path.parentPath.key === "left"); + if (isBlockScoped && isForLoopDeclaration) { + const functionParentScope = binding.scope.getFunctionParent(); + const isIncorrectlyTopLevelInSafari = + binding.scope.parent === functionParentScope; + if (isIncorrectlyTopLevelInSafari) { + const parentFunctionBinding = this.bindings + .get(functionParentScope) + .get(next); + if (parentFunctionBinding) { + const parentFunctionHasParamBinding = + parentFunctionBinding.kind === "param"; + if (parentFunctionHasParamBinding) { + return false; + } + } + } + } + for (let i = 0; i < binding.constantViolations.length; i++) { const violation = binding.constantViolations[i]; if (tracker.hasBindingOrReference(violation.scope, binding, next)) {