diff --git a/packages/babel-plugin-transform-block-scoping/src/index.ts b/packages/babel-plugin-transform-block-scoping/src/index.ts index 42f9132e7fa6..84bd0ce3ad89 100644 --- a/packages/babel-plugin-transform-block-scoping/src/index.ts +++ b/packages/babel-plugin-transform-block-scoping/src/index.ts @@ -1,5 +1,5 @@ import { declare } from "@babel/helper-plugin-utils"; -import type { NodePath, Visitor, Scope } from "@babel/traverse"; +import type { NodePath, Visitor, Scope, Binding } from "@babel/traverse"; import { visitor as tdzVisitor } from "./tdz"; import type { TDZVisitorState } from "./tdz"; import { traverse, template, types as t } from "@babel/core"; @@ -487,13 +487,21 @@ class BlockScoping { } checkConstants() { - const scope = this.scope; - const state = this.state; + const constBindings = new Map(); + + // In some cases, there are two different scopes: for example, + // for (const x of y) {} has a scope for the loop head and one + // for the body. + for (const scope of new Set([this.scope, this.blockPath.scope])) { + for (const name of Object.keys(scope.bindings)) { + const binding = scope.bindings[name]; + if (binding.kind === "const") constBindings.set(name, binding); + } + } - for (const name of Object.keys(scope.bindings)) { - const binding = scope.bindings[name]; - if (binding.kind !== "const") continue; + const { state } = this; + for (const [name, binding] of constBindings) { for (const violation of binding.constantViolations) { const readOnlyError = state.addHelper("readOnlyError"); const throwNode = t.callExpression(readOnlyError, [ diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-loop/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-loop/exec.js new file mode 100644 index 000000000000..c90d58a395cb --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-loop/exec.js @@ -0,0 +1,11 @@ +expect(function () { + for (const element = 1; element++;) break; +}).toThrow('"element" is read-only'); + +expect(function () { + for (;;) { + const from = 50; + from--; + break; + } +}).toThrow('"from" is read-only'); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-loop/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-loop/input.js new file mode 100644 index 000000000000..49cf55d2641c --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-loop/input.js @@ -0,0 +1,4 @@ +for (const element = 1; element++;) { + const from = 50; + from--; +} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-loop/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-loop/output.js new file mode 100644 index 000000000000..ed9c31565385 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-loop/output.js @@ -0,0 +1,4 @@ +for (var element = 1; +element, babelHelpers.readOnlyError("element");) { + var from = 50; + +from, babelHelpers.readOnlyError("from"); +} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-of-loop/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-of-loop/exec.js new file mode 100644 index 000000000000..515a70b1e4bb --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-of-loop/exec.js @@ -0,0 +1,7 @@ +expect(function () { + const array = [1, 2, 3]; + for (const element of array) { + const from = 50; + from--; + } +}).toThrow('"from" is read-only'); \ No newline at end of file diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-of-loop/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-of-loop/input.js new file mode 100644 index 000000000000..0c1caa33adf0 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-of-loop/input.js @@ -0,0 +1,4 @@ +for (const element of []) { + const from = 50; + from--; +} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-of-loop/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-of-loop/output.js new file mode 100644 index 000000000000..cb1740464877 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-in-for-of-loop/output.js @@ -0,0 +1,4 @@ +for (var element of []) { + var from = 50; + +from, babelHelpers.readOnlyError("from"); +}