diff --git a/acorn/src/tokenize.js b/acorn/src/tokenize.js index c5840431e..29e56b6be 100644 --- a/acorn/src/tokenize.js +++ b/acorn/src/tokenize.js @@ -233,7 +233,13 @@ pp.readToken_mult_modulo_exp = function(code) { // '%*' pp.readToken_pipe_amp = function(code) { // '|&' let next = this.input.charCodeAt(this.pos + 1) - if (next === code) return this.finishOp(code === 124 ? tt.logicalOR : tt.logicalAND, 2) + if (next === code) { + if (this.options.ecmaVersion >= 12) { + let next2 = this.input.charCodeAt(this.pos + 2) + if (next2 === 61) return this.finishOp(tt.assign, 3) + } + return this.finishOp(code === 124 ? tt.logicalOR : tt.logicalAND, 2) + } if (next === 61) return this.finishOp(tt.assign, 2) return this.finishOp(code === 124 ? tt.bitwiseOR : tt.bitwiseAND, 1) } @@ -290,13 +296,20 @@ pp.readToken_eq_excl = function(code) { // '=!' } pp.readToken_question = function() { // '?' - if (this.options.ecmaVersion >= 11) { + const ecmaVersion = this.options.ecmaVersion + if (ecmaVersion >= 11) { let next = this.input.charCodeAt(this.pos + 1) if (next === 46) { let next2 = this.input.charCodeAt(this.pos + 2) if (next2 < 48 || next2 > 57) return this.finishOp(tt.questionDot, 2) } - if (next === 63) return this.finishOp(tt.coalesce, 2) + if (next === 63) { + if (ecmaVersion >= 12) { + let next2 = this.input.charCodeAt(this.pos + 2) + if (next2 === 61) return this.finishOp(tt.assign, 3) + } + return this.finishOp(tt.coalesce, 2) + } } return this.finishOp(tt.question, 1) } diff --git a/bin/run_test262.js b/bin/run_test262.js index c5892f29b..92a2394da 100644 --- a/bin/run_test262.js +++ b/bin/run_test262.js @@ -11,11 +11,10 @@ const unsupportedFeatures = [ "class-static-fields-public", "class-static-methods-private", "numeric-separator-literal", - "logical-assignment-operators", ]; run( - (content, {sourceType}) => parse(content, {sourceType, ecmaVersion: 11, allowHashBang: true, allowAwaitOutsideFunction: true}), + (content, {sourceType}) => parse(content, {sourceType, ecmaVersion: 12, allowHashBang: true, allowAwaitOutsideFunction: true}), { testsDirectory: path.dirname(require.resolve("test262/package.json")), skip: test => (test.attrs.features && unsupportedFeatures.some(f => test.attrs.features.includes(f))), diff --git a/test/run.js b/test/run.js index 5485e6916..bd7cebd58 100644 --- a/test/run.js +++ b/test/run.js @@ -21,6 +21,7 @@ require("./tests-import-meta.js"); require("./tests-nullish-coalescing.js"); require("./tests-optional-chaining.js"); + require("./tests-logical-assignment-operators.js"); var acorn = require("../acorn") var acorn_loose = require("../acorn-loose") diff --git a/test/tests-logical-assignment-operators.js b/test/tests-logical-assignment-operators.js new file mode 100644 index 000000000..2e157f019 --- /dev/null +++ b/test/tests-logical-assignment-operators.js @@ -0,0 +1,182 @@ +// Tests for ECMAScript 2021 `&&=`, `||=`, `??=` + +if (typeof exports != 'undefined') { + var test = require('./driver.js').test; + var testFail = require('./driver.js').testFail; +} + +test( + "a &&= b", + { + "type": "Program", + "start": 0, + "end": 7, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 7, + "expression": { + "type": "AssignmentExpression", + "start": 0, + "end": 7, + "operator": "&&=", + "left": { + "type": "Identifier", + "start": 0, + "end": 1, + "name": "a" + }, + "right": { + "type": "Identifier", + "start": 6, + "end": 7, + "name": "b" + } + } + } + ], + "sourceType": "script" + }, + { ecmaVersion: 12 } +); + +test( + "a ||= b", + { + "type": "Program", + "start": 0, + "end": 7, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 7, + "expression": { + "type": "AssignmentExpression", + "start": 0, + "end": 7, + "operator": "||=", + "left": { + "type": "Identifier", + "start": 0, + "end": 1, + "name": "a" + }, + "right": { + "type": "Identifier", + "start": 6, + "end": 7, + "name": "b" + } + } + } + ], + "sourceType": "script" + }, + { ecmaVersion: 12 } +); + +test( + "a ??= b", + { + "type": "Program", + "start": 0, + "end": 7, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 7, + "expression": { + "type": "AssignmentExpression", + "start": 0, + "end": 7, + "operator": "??=", + "left": { + "type": "Identifier", + "start": 0, + "end": 1, + "name": "a" + }, + "right": { + "type": "Identifier", + "start": 6, + "end": 7, + "name": "b" + } + } + } + ], + "sourceType": "script" + }, + { ecmaVersion: 12 } +); + +test( + "a &&= b ||= c ??= d", + { + "type": "Program", + "start": 0, + "end": 19, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 19, + "expression": { + "type": "AssignmentExpression", + "start": 0, + "end": 19, + "operator": "&&=", + "left": { + "type": "Identifier", + "start": 0, + "end": 1, + "name": "a" + }, + "right": { + "type": "AssignmentExpression", + "start": 6, + "end": 19, + "operator": "||=", + "left": { + "type": "Identifier", + "start": 6, + "end": 7, + "name": "b" + }, + "right": { + "type": "AssignmentExpression", + "start": 12, + "end": 19, + "operator": "??=", + "left": { + "type": "Identifier", + "start": 12, + "end": 13, + "name": "c" + }, + "right": { + "type": "Identifier", + "start": 18, + "end": 19, + "name": "d" + } + } + } + } + } + ], + "sourceType": "script" + }, + { ecmaVersion: 12 } +); + +testFail("a &&= b", "Unexpected token (1:4)", { ecmaVersion: 11 }); +testFail("a ||= b", "Unexpected token (1:4)", { ecmaVersion: 11 }); +testFail("a ??= b", "Unexpected token (1:4)", { ecmaVersion: 11 }); + +testFail("({a} &&= b)", "Assigning to rvalue (1:1)", { ecmaVersion: 12 }); +testFail("({a} ||= b)", "Assigning to rvalue (1:1)", { ecmaVersion: 12 }); +testFail("({a} ??= b)", "Assigning to rvalue (1:1)", { ecmaVersion: 12 });