diff --git a/packages/babel-plugin-proposal-logical-assignment-operators/package.json b/packages/babel-plugin-proposal-logical-assignment-operators/package.json index d1547eb140..d423296a38 100644 --- a/packages/babel-plugin-proposal-logical-assignment-operators/package.json +++ b/packages/babel-plugin-proposal-logical-assignment-operators/package.json @@ -17,6 +17,8 @@ }, "devDependencies": { "@babel/core": "7.0.0-beta.42", - "@babel/helper-plugin-test-runner": "7.0.0-beta.42" + "@babel/helper-plugin-test-runner": "7.0.0-beta.42", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.0.0-beta.42", + "@babel/plugin-syntax-nullish-coalescing-operator": "7.0.0-beta.42" } } diff --git a/packages/babel-plugin-proposal-logical-assignment-operators/src/index.js b/packages/babel-plugin-proposal-logical-assignment-operators/src/index.js index 2ecda96b8f..cffeca9a12 100644 --- a/packages/babel-plugin-proposal-logical-assignment-operators/src/index.js +++ b/packages/babel-plugin-proposal-logical-assignment-operators/src/index.js @@ -12,7 +12,7 @@ export default declare(api => { AssignmentExpression(path) { const { node, scope } = path; const { operator, left, right } = node; - if (operator !== "||=" && operator !== "&&=") { + if (operator !== "||=" && operator !== "&&=" && operator !== "??=") { return; } diff --git a/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing-without-other/input.js b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing-without-other/input.js new file mode 100644 index 0000000000..0d6fb52770 --- /dev/null +++ b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing-without-other/input.js @@ -0,0 +1,4 @@ +let o; +o ??= {}; +o.a ??= 1; +o["b"] ??= 2; diff --git a/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing-without-other/options.json b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing-without-other/options.json new file mode 100644 index 0000000000..e6559faadb --- /dev/null +++ b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing-without-other/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["proposal-logical-assignment-operators", "syntax-nullish-coalescing-operator"] +} diff --git a/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing-without-other/output.js b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing-without-other/output.js new file mode 100644 index 0000000000..6263aa5e19 --- /dev/null +++ b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing-without-other/output.js @@ -0,0 +1,6 @@ +var _o, _o2, _b; + +let o; +o ?? (o = {}); +(_o = o).a ?? (_o.a = 1); +(_o2 = o)[_b = "b"] ?? (_o2[_b] = 2); diff --git a/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/exec.js b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/exec.js new file mode 100644 index 0000000000..cee70f2406 --- /dev/null +++ b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/exec.js @@ -0,0 +1,49 @@ +var x = undefined; +var sets = 0; +var obj = { + get x() { + return x; + }, + + set x(value) { + sets++; + x = value; + }, +}; + +assert.equal(obj.x ??= 1, 1); +assert.equal(sets, 1); +assert.equal(obj.x ??= 2, 1); +assert.equal(sets, 1); + +var gets = 0; +var deep = { + get obj() { + gets++; + return obj; + }, +}; + +obj.x = undefined; +assert.equal(deep.obj.x ??= 1, 1); +assert.equal(gets, 1); +assert.equal(deep.obj.x ??= 2, 1); +assert.equal(gets, 2); + +var key = 0; +obj.x = undefined; +assert.equal(obj[++key] ??= 1, 1); +assert.equal(key, 1); +key = 0; +assert.equal(obj[++key] ??= 2, 1); +assert.equal(key, 1); + +obj.x = undefined; +key = 0; +assert.equal(deep.obj[++key] ??= 1, 1); +assert.equal(gets, 3); +assert.equal(key, 1); +key = 0; +assert.equal(deep.obj[++key] ??= 2, 1); +assert.equal(gets, 4); +assert.equal(key, 1); diff --git a/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js new file mode 100644 index 0000000000..cee70f2406 --- /dev/null +++ b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js @@ -0,0 +1,49 @@ +var x = undefined; +var sets = 0; +var obj = { + get x() { + return x; + }, + + set x(value) { + sets++; + x = value; + }, +}; + +assert.equal(obj.x ??= 1, 1); +assert.equal(sets, 1); +assert.equal(obj.x ??= 2, 1); +assert.equal(sets, 1); + +var gets = 0; +var deep = { + get obj() { + gets++; + return obj; + }, +}; + +obj.x = undefined; +assert.equal(deep.obj.x ??= 1, 1); +assert.equal(gets, 1); +assert.equal(deep.obj.x ??= 2, 1); +assert.equal(gets, 2); + +var key = 0; +obj.x = undefined; +assert.equal(obj[++key] ??= 1, 1); +assert.equal(key, 1); +key = 0; +assert.equal(obj[++key] ??= 2, 1); +assert.equal(key, 1); + +obj.x = undefined; +key = 0; +assert.equal(deep.obj[++key] ??= 1, 1); +assert.equal(gets, 3); +assert.equal(key, 1); +key = 0; +assert.equal(deep.obj[++key] ??= 2, 1); +assert.equal(gets, 4); +assert.equal(key, 1); diff --git a/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/options.json b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/options.json new file mode 100644 index 0000000000..e7a7bc2afe --- /dev/null +++ b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["proposal-logical-assignment-operators", "proposal-nullish-coalescing-operator"] +} diff --git a/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/output.js b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/output.js new file mode 100644 index 0000000000..7640ba1ba6 --- /dev/null +++ b/packages/babel-plugin-proposal-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/output.js @@ -0,0 +1,48 @@ +var _obj$x, _obj$x2, _deep$obj, _x, _deep$obj2, _x2, _ref, _obj, _ref2, _obj2, _deep$obj3, _ref3, _ref4, _deep$obj4, _ref5, _ref6; + +var x = undefined; +var sets = 0; +var obj = { + get x() { + return x; + }, + + set x(value) { + sets++; + x = value; + } + +}; +assert.equal((_obj$x = obj.x) !== null && _obj$x !== void 0 ? _obj$x : obj.x = 1, 1); +assert.equal(sets, 1); +assert.equal((_obj$x2 = obj.x) !== null && _obj$x2 !== void 0 ? _obj$x2 : obj.x = 2, 1); +assert.equal(sets, 1); +var gets = 0; +var deep = { + get obj() { + gets++; + return obj; + } + +}; +obj.x = undefined; +assert.equal((_x = (_deep$obj = deep.obj).x) !== null && _x !== void 0 ? _x : _deep$obj.x = 1, 1); +assert.equal(gets, 1); +assert.equal((_x2 = (_deep$obj2 = deep.obj).x) !== null && _x2 !== void 0 ? _x2 : _deep$obj2.x = 2, 1); +assert.equal(gets, 2); +var key = 0; +obj.x = undefined; +assert.equal((_obj = obj[_ref = ++key]) !== null && _obj !== void 0 ? _obj : obj[_ref] = 1, 1); +assert.equal(key, 1); +key = 0; +assert.equal((_obj2 = obj[_ref2 = ++key]) !== null && _obj2 !== void 0 ? _obj2 : obj[_ref2] = 2, 1); +assert.equal(key, 1); +obj.x = undefined; +key = 0; +assert.equal((_ref4 = (_deep$obj3 = deep.obj)[_ref3 = ++key]) !== null && _ref4 !== void 0 ? _ref4 : _deep$obj3[_ref3] = 1, 1); +assert.equal(gets, 3); +assert.equal(key, 1); +key = 0; +assert.equal((_ref6 = (_deep$obj4 = deep.obj)[_ref5 = ++key]) !== null && _ref6 !== void 0 ? _ref6 : _deep$obj4[_ref5] = 2, 1); +assert.equal(gets, 4); +assert.equal(key, 1); diff --git a/packages/babylon/src/parser/expression.js b/packages/babylon/src/parser/expression.js index 001ea6d897..75178532ea 100644 --- a/packages/babylon/src/parser/expression.js +++ b/packages/babylon/src/parser/expression.js @@ -299,10 +299,6 @@ export default class ExpressionParser extends LValParser { this.state.potentialArrowAt = startPos; } - if (node.operator === "??") { - this.expectPlugin("nullishCoalescingOperator"); - } - node.right = this.parseExprOp( this.parseMaybeUnary(), startPos, diff --git a/packages/babylon/src/tokenizer/index.js b/packages/babylon/src/tokenizer/index.js index 1396bf7d0b..6a7ec8f9f4 100644 --- a/packages/babylon/src/tokenizer/index.js +++ b/packages/babylon/src/tokenizer/index.js @@ -607,8 +607,16 @@ export default class Tokenizer extends LocationParser { const next = this.input.charCodeAt(this.state.pos + 1); const next2 = this.input.charCodeAt(this.state.pos + 2); if (next === charCodes.questionMark) { - // '??' - this.finishOp(tt.nullishCoalescing, 2); + this.expectPlugin("nullishCoalescingOperator"); + + if (next2 === charCodes.equalsTo) { + // '??=' + this.expectPlugin("logicalAssignment"); + this.finishOp(tt.assign, 3); + } else { + // '??' + this.finishOp(tt.nullishCoalescing, 2); + } } else if ( next === charCodes.dot && !(next2 >= charCodes.digit0 && next2 <= charCodes.digit9) diff --git a/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-null-coalesce-plugin/input.js b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-null-coalesce-plugin/input.js new file mode 100644 index 0000000000..dcf876b174 --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-null-coalesce-plugin/input.js @@ -0,0 +1,2 @@ +a ??= b; +obj.a ??= b; diff --git a/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-null-coalesce-plugin/options.json b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-null-coalesce-plugin/options.json new file mode 100644 index 0000000000..3cb238ee4a --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-null-coalesce-plugin/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["logicalAssignment"], + "throws": "This experimental syntax requires enabling the parser plugin: 'nullishCoalescingOperator' (1:2)" +} diff --git a/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-plugin/input.js b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-plugin/input.js new file mode 100644 index 0000000000..dcf876b174 --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-plugin/input.js @@ -0,0 +1,2 @@ +a ??= b; +obj.a ??= b; diff --git a/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-plugin/options.json b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-plugin/options.json new file mode 100644 index 0000000000..f5e977775d --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals-no-plugin/options.json @@ -0,0 +1,4 @@ +{ + "plugins": ["nullishCoalescingOperator"], + "throws": "This experimental syntax requires enabling the parser plugin: 'logicalAssignment' (1:2)" +} diff --git a/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals/input.js b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals/input.js new file mode 100644 index 0000000000..dcf876b174 --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals/input.js @@ -0,0 +1,2 @@ +a ??= b; +obj.a ??= b; diff --git a/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals/options.json b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals/options.json new file mode 100644 index 0000000000..6b07062568 --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["logicalAssignment", "nullishCoalescingOperator"] +} diff --git a/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals/output.json b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals/output.json new file mode 100644 index 0000000000..7b7eded332 --- /dev/null +++ b/packages/babylon/test/fixtures/experimental/logical-assignment-operator/qq-equals/output.json @@ -0,0 +1,197 @@ +{ + "type": "File", + "start": 0, + "end": 21, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 12 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 21, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 12 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "expression": { + "type": "AssignmentExpression", + "start": 0, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 7 + } + }, + "operator": "??=", + "left": { + "type": "Identifier", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + }, + "identifierName": "a" + }, + "name": "a" + }, + "right": { + "type": "Identifier", + "start": 6, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 7 + }, + "identifierName": "b" + }, + "name": "b" + } + } + }, + { + "type": "ExpressionStatement", + "start": 9, + "end": 21, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 12 + } + }, + "expression": { + "type": "AssignmentExpression", + "start": 9, + "end": 20, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 11 + } + }, + "operator": "??=", + "left": { + "type": "MemberExpression", + "start": 9, + "end": 14, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 5 + } + }, + "object": { + "type": "Identifier", + "start": 9, + "end": 12, + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 3 + }, + "identifierName": "obj" + }, + "name": "obj" + }, + "property": { + "type": "Identifier", + "start": 13, + "end": 14, + "loc": { + "start": { + "line": 2, + "column": 4 + }, + "end": { + "line": 2, + "column": 5 + }, + "identifierName": "a" + }, + "name": "a" + }, + "computed": false + }, + "right": { + "type": "Identifier", + "start": 19, + "end": 20, + "loc": { + "start": { + "line": 2, + "column": 10 + }, + "end": { + "line": 2, + "column": 11 + }, + "identifierName": "b" + }, + "name": "b" + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babylon/test/fixtures/experimental/nullish-coalescing-operator/no-plugin-error/options.json b/packages/babylon/test/fixtures/experimental/nullish-coalescing-operator/no-plugin-error/options.json index a7057a230a..9ae9a3d8a5 100644 --- a/packages/babylon/test/fixtures/experimental/nullish-coalescing-operator/no-plugin-error/options.json +++ b/packages/babylon/test/fixtures/experimental/nullish-coalescing-operator/no-plugin-error/options.json @@ -1,3 +1,3 @@ { - "throws": "This experimental syntax requires enabling the parser plugin: 'nullishCoalescingOperator' (1:7)" + "throws": "This experimental syntax requires enabling the parser plugin: 'nullishCoalescingOperator' (1:4)" }