Proposal: Logical Assignment Operators (#7385)

* Proposal: Logical Assignment Operators

https://github.com/jridgewell/proposal-logical-assignment

I'm bringing it [back](https://github.com/babel/babel/pull/516). 😉

* Use expectPlugin

* Add to stage 0 preset

* Add logicalAssignment missing plugin log stuff
This commit is contained in:
Justin Ridgewell 2018-02-18 13:56:29 -05:00 committed by GitHub
parent 3d49766f6b
commit 7e90d56024
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 753 additions and 3 deletions

View File

@ -113,6 +113,16 @@ const pluginNameMap = {
url: "https://git.io/vb4yd", url: "https://git.io/vb4yd",
}, },
}, },
logicalAssignment: {
syntax: {
name: "@babel/plugin-syntax-logical-assignment-operators",
url: "https://git.io/vAlBp",
},
transform: {
name: "@babel/plugin-proposal-logical-assignment-operators",
url: "https://git.io/vAlRe",
},
},
nullishCoalescingOperator: { nullishCoalescingOperator: {
syntax: { syntax: {
name: "@babel/plugin-syntax-nullish-coalescing-operator", name: "@babel/plugin-syntax-nullish-coalescing-operator",

View File

@ -0,0 +1,3 @@
src
test
*.log

View File

@ -0,0 +1,63 @@
# @babel/plugin-proposal-logical-assignment-operator
> Transforms logical assignment operators into short-circuited assignments
## Example
**In**
```javascript
a ||= b;
obj.a.b ||= c;
a &&= b;
obj.a.b &&= c;
```
**Out**
```javascript
var _obj$a, _obj$a2;
a || (a = b);
(_obj$a = obj.a).b || (_obj$a.b = c);
a && (a = b);
(_obj$a2 = obj.a).b && (_obj$a2.b = c);
```
## Installation
```sh
npm install --save-dev @babel/plugin-proposal-logical-assignment-operators
```
## Usage
### Via `.babelrc` (Recommended)
**.babelrc**
```json
{
"plugins": ["@babel/plugin-proposal-logical-assignment-operators"]
}
```
### Via CLI
```sh
babel --plugins @babel/plugin-proposal-logical-assignment-operators script.js
```
### Via Node API
```javascript
require("@babel/core").transform("code", {
plugins: ["@babel/plugin-proposal-logical-assignment-operators"]
});
```
## References
* [Proposal: Logical Assignment Operators](https://github.com/jridgewell/proposal-logical-assignment-operators)

View File

@ -0,0 +1,21 @@
{
"name": "@babel/plugin-proposal-logical-assignment-operators",
"version": "7.0.0-beta.40",
"description": "Transforms logical assignment operators into short-circuited assignments",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-logical-assignment-operators",
"license": "MIT",
"main": "lib",
"keywords": [
"babel-plugin"
],
"dependencies": {
"@babel/plugin-syntax-logical-assignment-operators": "7.0.0-beta.40"
},
"peerDependencies": {
"@babel/core": "7.0.0-beta.40"
},
"devDependencies": {
"@babel/core": "7.0.0-beta.40",
"@babel/helper-plugin-test-runner": "7.0.0-beta.40"
}
}

View File

@ -0,0 +1,42 @@
import syntaxLogicalAssignmentOperators from "@babel/plugin-syntax-logical-assignment-operators";
import { types as t } from "@babel/core";
export default function() {
return {
inherits: syntaxLogicalAssignmentOperators,
visitor: {
AssignmentExpression(path) {
const { node, scope } = path;
const { operator, left, right } = node;
if (operator !== "||=" && operator !== "&&=") {
return;
}
let ref;
if (t.isMemberExpression(left)) {
const { object } = left;
const memo = scope.maybeGenerateMemoised(object);
if (memo) {
path
.get("left.object")
.replaceWith(
t.assignmentExpression("=", t.cloneNode(memo), object),
);
ref = t.cloneNode(left);
ref.object = t.cloneNode(memo);
}
}
path.replaceWith(
t.logicalExpression(
operator.slice(0, -1),
left,
t.assignmentExpression("=", ref || t.cloneNode(left), right),
),
);
},
},
};
}

View File

@ -0,0 +1,40 @@
var x = 0;
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);
assert.equal(obj.x &&= 0, 0);
assert.equal(sets, 2);
assert.equal(obj.x &&= 3, 0);
assert.equal(sets, 2);
var gets = 0;
var deep = {
get obj() {
gets++;
return obj;
},
};
assert.equal(deep.obj.x ||= 1, 1);
assert.equal(gets, 1);
assert.equal(deep.obj.x ||= 2, 1);
assert.equal(gets, 2);
assert.equal(deep.obj.x &&= 0, 0);
assert.equal(gets, 3);
assert.equal(deep.obj.x &&= 3, 0);
assert.equal(gets, 4);

View File

@ -0,0 +1,40 @@
var x = 0;
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);
assert.equal(obj.x &&= 0, 0);
assert.equal(sets, 2);
assert.equal(obj.x &&= 3, 0);
assert.equal(sets, 2);
var gets = 0;
var deep = {
get obj() {
gets++;
return obj;
},
};
assert.equal(deep.obj.x ||= 1, 1);
assert.equal(gets, 1);
assert.equal(deep.obj.x ||= 2, 1);
assert.equal(gets, 2);
assert.equal(deep.obj.x &&= 0, 0);
assert.equal(gets, 3);
assert.equal(deep.obj.x &&= 3, 0);
assert.equal(gets, 4);

View File

@ -0,0 +1,3 @@
{
"plugins": ["proposal-logical-assignment-operators"]
}

View File

@ -0,0 +1,39 @@
var _deep$obj, _deep$obj2, _deep$obj3, _deep$obj4;
var x = 0;
var sets = 0;
var obj = {
get x() {
return x;
},
set x(value) {
sets++;
x = value;
}
};
assert.equal(obj.x || (obj.x = 1), 1);
assert.equal(sets, 1);
assert.equal(obj.x || (obj.x = 2), 1);
assert.equal(sets, 1);
assert.equal(obj.x && (obj.x = 0), 0);
assert.equal(sets, 2);
assert.equal(obj.x && (obj.x = 3), 0);
assert.equal(sets, 2);
var gets = 0;
var deep = {
get obj() {
gets++;
return obj;
}
};
assert.equal((_deep$obj = deep.obj).x || (_deep$obj.x = 1), 1);
assert.equal(gets, 1);
assert.equal((_deep$obj2 = deep.obj).x || (_deep$obj2.x = 2), 1);
assert.equal(gets, 2);
assert.equal((_deep$obj3 = deep.obj).x && (_deep$obj3.x = 0), 0);
assert.equal(gets, 3);
assert.equal((_deep$obj4 = deep.obj).x && (_deep$obj4.x = 3), 0);
assert.equal(gets, 4);

View File

@ -0,0 +1,3 @@
import runner from "@babel/helper-plugin-test-runner";
runner(__dirname);

View File

@ -0,0 +1,3 @@
src
test
*.log

View File

@ -0,0 +1,35 @@
# @babel/plugin-syntax-logical-assignment-operators
> Allow parsing of the logical assignment operators.
## Installation
```sh
npm install --save-dev @babel/plugin-syntax-logical-assignment-operators
```
## Usage
### Via `.babelrc` (Recommended)
**.babelrc**
```json
{
"plugins": ["@babel/plugin-syntax-logical-assignment-operators"]
}
```
### Via CLI
```sh
babel --plugins @babel/plugin-syntax-logical-assignment-operators script.js
```
### Via Node API
```javascript
require("@babel/core").transform("code", {
plugins: ["@babel/plugin-syntax-logical-assignment-operators"]
});
```

View File

@ -0,0 +1,17 @@
{
"name": "@babel/plugin-syntax-logical-assignment-operators",
"version": "7.0.0-beta.40",
"description": "Allow parsing of the logical assignment operators",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-logical-assignment-operators",
"license": "MIT",
"main": "lib",
"keywords": [
"babel-plugin"
],
"peerDependencies": {
"@babel/core": "7.0.0-beta.40"
},
"devDependencies": {
"@babel/core": "7.0.0-beta.40"
}
}

View File

@ -0,0 +1,7 @@
export default function() {
return {
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("logicalAssignment");
},
};
}

View File

@ -9,6 +9,7 @@
"main": "lib/index.js", "main": "lib/index.js",
"dependencies": { "dependencies": {
"@babel/plugin-proposal-function-bind": "7.0.0-beta.40", "@babel/plugin-proposal-function-bind": "7.0.0-beta.40",
"@babel/plugin-proposal-logical-assignment-operators": "7.0.0-beta.40",
"@babel/preset-stage-1": "7.0.0-beta.40" "@babel/preset-stage-1": "7.0.0-beta.40"
}, },
"peerDependencies": { "peerDependencies": {

View File

@ -1,6 +1,7 @@
import presetStage1 from "@babel/preset-stage-1"; import presetStage1 from "@babel/preset-stage-1";
import transformFunctionBind from "@babel/plugin-proposal-function-bind"; import transformFunctionBind from "@babel/plugin-proposal-function-bind";
import transformLogicalAssignmentOperators from "@babel/plugin-proposal-logical-assignment-operators";
export default function(context, opts = {}) { export default function(context, opts = {}) {
let loose = false; let loose = false;
@ -22,6 +23,6 @@ export default function(context, opts = {}) {
return { return {
presets: [[presetStage1, { loose, useBuiltIns }]], presets: [[presetStage1, { loose, useBuiltIns }]],
plugins: [transformFunctionBind], plugins: [transformFunctionBind, transformLogicalAssignmentOperators],
}; };
} }

View File

@ -464,9 +464,16 @@ export default class Tokenizer extends LocationParser {
const next = this.input.charCodeAt(this.state.pos + 1); const next = this.input.charCodeAt(this.state.pos + 1);
if (next === code) { if (next === code) {
const assign =
this.input.charCodeAt(this.state.pos + 2) === charCodes.equalsTo;
if (assign) {
this.expectPlugin("logicalAssignment");
}
this.finishOp( this.finishOp(
code === charCodes.verticalBar ? tt.logicalOR : tt.logicalAND, assign
2, ? tt.assign
: code === charCodes.verticalBar ? tt.logicalOR : tt.logicalAND,
assign ? 3 : 2,
); );
return; return;
} }

View File

@ -0,0 +1,2 @@
a &&= b;
obj.a &&= b;

View File

@ -0,0 +1,4 @@
{
"plugins": [],
"throws": "This experimental syntax requires enabling the parser plugin: 'logicalAssignment' (1:2)"
}

View File

@ -0,0 +1,2 @@
a &&= b;
obj.a &&= b;

View File

@ -0,0 +1,3 @@
{
"plugins": ["logicalAssignment"]
}

View File

@ -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": []
}
}

View File

@ -0,0 +1,4 @@
{
"plugins": [],
"throws": "This experimental syntax requires enabling the parser plugin: 'logicalAssignment' (1:2)"
}

View File

@ -0,0 +1,2 @@
a ||= b;
obj.a ||= b;

View File

@ -0,0 +1,3 @@
{
"plugins": ["logicalAssignment"]
}

View File

@ -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": []
}
}