From 99be60b53d39e26a761b2de3a04b677ff7c5d929 Mon Sep 17 00:00:00 2001 From: Lucas Azzola Date: Wed, 18 Oct 2017 18:10:05 +1100 Subject: [PATCH] Implement transform for nullish-coalescing operator (#6483) * Implement transform for nullish-coalescing operator * Update example output * Switch from BinaryExpression to LogicalExpression * Address review comments - Use generateUidIdentifierBasedOnNode - Inline "??" - Clone ref node - Move "??" to LogicalExpression in babel-types * Fix reference to @babel/helper-plugin-test-runner * Fix reference to @babel/plugin-syntax-nullish-coalescing-operator * Don't use parent scope * Remove .vscode from .gitignore, change 'lib/index.js' to 'lib' * Ensure `document.all ?? 0 === document.all` * Fix note and copy to an inline comment --- .../.npmignore | 3 ++ .../README.md | 35 ++++++++++++ .../package.json | 13 +++++ .../src/index.js | 7 +++ .../.npmignore | 3 ++ .../README.md | 54 +++++++++++++++++++ .../package.json | 17 ++++++ .../src/index.js | 40 ++++++++++++++ .../runtime-semantics/exec.js | 19 +++++++ .../runtime-semantics/options.json | 3 ++ .../transform-in-default/actual.js | 1 + .../transform-in-default/expected.js | 3 ++ .../transform-in-default/options.json | 3 ++ .../transform-in-function/actual.js | 3 ++ .../transform-in-function/expected.js | 5 ++ .../transform-in-function/options.json | 3 ++ .../test/index.js | 3 ++ packages/babel-preset-stage-1/package.json | 1 + packages/babel-preset-stage-1/src/index.js | 2 + packages/babel-types/src/constants.js | 2 +- 20 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 packages/babel-plugin-syntax-nullish-coalescing-operator/.npmignore create mode 100644 packages/babel-plugin-syntax-nullish-coalescing-operator/README.md create mode 100644 packages/babel-plugin-syntax-nullish-coalescing-operator/package.json create mode 100644 packages/babel-plugin-syntax-nullish-coalescing-operator/src/index.js create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/.npmignore create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/README.md create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/package.json create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/src/index.js create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/runtime-semantics/exec.js create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/runtime-semantics/options.json create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/actual.js create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/expected.js create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/options.json create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/actual.js create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/expected.js create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/options.json create mode 100644 packages/babel-plugin-transform-nullish-coalescing-operator/test/index.js diff --git a/packages/babel-plugin-syntax-nullish-coalescing-operator/.npmignore b/packages/babel-plugin-syntax-nullish-coalescing-operator/.npmignore new file mode 100644 index 0000000000..f980694583 --- /dev/null +++ b/packages/babel-plugin-syntax-nullish-coalescing-operator/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-plugin-syntax-nullish-coalescing-operator/README.md b/packages/babel-plugin-syntax-nullish-coalescing-operator/README.md new file mode 100644 index 0000000000..20c90cd5be --- /dev/null +++ b/packages/babel-plugin-syntax-nullish-coalescing-operator/README.md @@ -0,0 +1,35 @@ +# @babel/plugin-syntax-nullish-coalescing-operator + +> Allow parsing of the nullish-coalescing operator. + +## Installation + +```sh +npm install --save-dev @babel/plugin-syntax-nullish-coalescing-operator +``` + +## Usage + +### Via `.babelrc` (Recommended) + +**.babelrc** + +```json +{ + "plugins": ["syntax-nullish-coalescing-operator"] +} +``` + +### Via CLI + +```sh +babel --plugins syntax-nullish-coalescing-operator script.js +``` + +### Via Node API + +```javascript +require("babel-core").transform("code", { + plugins: ["syntax-nullish-coalescing-operator"] +}); +``` diff --git a/packages/babel-plugin-syntax-nullish-coalescing-operator/package.json b/packages/babel-plugin-syntax-nullish-coalescing-operator/package.json new file mode 100644 index 0000000000..a2971b7643 --- /dev/null +++ b/packages/babel-plugin-syntax-nullish-coalescing-operator/package.json @@ -0,0 +1,13 @@ +{ + "name": "@babel/plugin-syntax-nullish-coalescing-operator", + "version": "7.0.0-beta.3", + "description": "Allow parsing of the nullish-coalescing operator", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-nullish-coalescing-operator", + "license": "MIT", + "main": "lib", + "keywords": [ + "babel-plugin" + ], + "dependencies": {}, + "devDependencies": {} +} diff --git a/packages/babel-plugin-syntax-nullish-coalescing-operator/src/index.js b/packages/babel-plugin-syntax-nullish-coalescing-operator/src/index.js new file mode 100644 index 0000000000..d5cf5faa5a --- /dev/null +++ b/packages/babel-plugin-syntax-nullish-coalescing-operator/src/index.js @@ -0,0 +1,7 @@ +export default function() { + return { + manipulateOptions(opts, parserOpts) { + parserOpts.plugins.push("nullishCoalescingOperator"); + }, + }; +} diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/.npmignore b/packages/babel-plugin-transform-nullish-coalescing-operator/.npmignore new file mode 100644 index 0000000000..f980694583 --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/README.md b/packages/babel-plugin-transform-nullish-coalescing-operator/README.md new file mode 100644 index 0000000000..0eb9a9b681 --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/README.md @@ -0,0 +1,54 @@ +# @babel/plugin-transform-nullish-coalescing-operator + +> Replace `??` with an inline helper. + +## Example + +**In** + +```javascript +var foo = object.foo ?? "default"; +``` + +**Out** + +```javascript +var _object$foo; + +var foo = (_object$foo = object.foo, _object$foo !== null && _object$foo !== void 0 ? _object$foo : "default"); +``` + +> **NOTE:** We cannot use `!= null` here because `document.all == null` and +> `document.all` has been deemed not "nullish". + +## Installation + +```sh +npm install --save-dev @babel/plugin-transform-nullish-coalescing-operator +``` + +## Usage + +### Via `.babelrc` (Recommended) + +**.babelrc** + +```json +{ + "plugins": ["transform-nullish-coalescing-operator"] +} +``` + +### Via CLI + +```sh +babel --plugins transform-nullish-coalescing-operator script.js +``` + +### Via Node API + +```javascript +require("babel-core").transform("code", { + plugins: ["transform-nullish-coalescing-operator"] +}); +``` diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/package.json b/packages/babel-plugin-transform-nullish-coalescing-operator/package.json new file mode 100644 index 0000000000..b4bdd2eaf2 --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/package.json @@ -0,0 +1,17 @@ +{ + "name": "@babel/plugin-transform-nullish-coalescing-operator", + "version": "7.0.0-beta.3", + "description": "Remove nullish coalescing operator", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-nullish-coalescing-opearator", + "license": "MIT", + "main": "lib", + "keywords": [ + "babel-plugin" + ], + "dependencies": { + "@babel/plugin-syntax-nullish-coalescing-operator": "7.0.0-beta.3" + }, + "devDependencies": { + "@babel/helper-plugin-test-runner": "7.0.0-beta.3" + } +} diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/src/index.js b/packages/babel-plugin-transform-nullish-coalescing-operator/src/index.js new file mode 100644 index 0000000000..d61fe1af49 --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/src/index.js @@ -0,0 +1,40 @@ +import syntaxNullishCoalescingOperator from "@babel/plugin-syntax-nullish-coalescing-operator"; + +export default function({ types: t }) { + return { + inherits: syntaxNullishCoalescingOperator, + + visitor: { + LogicalExpression(path) { + const { node, scope } = path; + if (node.operator !== "??") { + return; + } + + const ref = scope.generateUidIdentifierBasedOnNode(node.left); + scope.push({ id: ref }); + + path.replaceWith( + t.sequenceExpression([ + t.assignmentExpression("=", ref, node.left), + t.conditionalExpression( + // We cannot use `!= null` here because `document.all == null` + // and `document.all` has been deemed not "nullish". + t.logicalExpression( + "&&", + t.binaryExpression("!==", t.clone(ref), t.nullLiteral()), + t.binaryExpression( + "!==", + t.clone(ref), + scope.buildUndefinedNode(), + ), + ), + t.clone(ref), + node.right, + ), + ]), + ); + }, + }, + }; +} diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/runtime-semantics/exec.js b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/runtime-semantics/exec.js new file mode 100644 index 0000000000..a066fff086 --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/runtime-semantics/exec.js @@ -0,0 +1,19 @@ +assert.equal(null ?? undefined, undefined); +assert.equal(undefined ?? null, null); +assert.equal(false ?? true, false); +assert.equal(0 ?? 1, 0); +assert.equal("" ?? "foo", ""); + +var obj = { exists: true }; +assert.equal(obj.exists ?? false, true); +assert.equal(obj.doesNotExist ?? "foo", "foo"); + +var counter = 0; +function sideEffect() { return counter++; } +assert.equal(sideEffect() ?? -1, 0); + +var counter2 = 0; +var obj2 = { + get foo() { return counter2++; } +}; +assert.equal(obj2.foo ?? -1, 0); diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/runtime-semantics/options.json b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/runtime-semantics/options.json new file mode 100644 index 0000000000..30d908eec2 --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/runtime-semantics/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["transform-nullish-coalescing-operator"] +} diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/actual.js b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/actual.js new file mode 100644 index 0000000000..7c7dcae88c --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/actual.js @@ -0,0 +1 @@ +function foo(foo, bar = foo ?? "bar") {} diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/expected.js b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/expected.js new file mode 100644 index 0000000000..236ab9c27b --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/expected.js @@ -0,0 +1,3 @@ +function foo(foo, bar = (_foo = foo, _foo !== null && _foo !== void 0 ? _foo : "bar")) { + var _foo; +} diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/options.json b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/options.json new file mode 100644 index 0000000000..30d908eec2 --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-default/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["transform-nullish-coalescing-operator"] +} diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/actual.js b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/actual.js new file mode 100644 index 0000000000..63b28b6c23 --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/actual.js @@ -0,0 +1,3 @@ +function foo(opts) { + var foo = opts.foo ?? "default"; +} diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/expected.js b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/expected.js new file mode 100644 index 0000000000..aa5ec8a47d --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/expected.js @@ -0,0 +1,5 @@ +function foo(opts) { + var _opts$foo; + + var foo = (_opts$foo = opts.foo, _opts$foo !== null && _opts$foo !== void 0 ? _opts$foo : "default"); +} diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/options.json b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/options.json new file mode 100644 index 0000000000..30d908eec2 --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/nullish-coalescing/transform-in-function/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["transform-nullish-coalescing-operator"] +} diff --git a/packages/babel-plugin-transform-nullish-coalescing-operator/test/index.js b/packages/babel-plugin-transform-nullish-coalescing-operator/test/index.js new file mode 100644 index 0000000000..1b534b8fc6 --- /dev/null +++ b/packages/babel-plugin-transform-nullish-coalescing-operator/test/index.js @@ -0,0 +1,3 @@ +import runner from "@babel/helper-plugin-test-runner"; + +runner(__dirname); diff --git a/packages/babel-preset-stage-1/package.json b/packages/babel-preset-stage-1/package.json index 36af149c3c..f419f1887a 100644 --- a/packages/babel-preset-stage-1/package.json +++ b/packages/babel-preset-stage-1/package.json @@ -12,6 +12,7 @@ "@babel/plugin-transform-export-default": "7.0.0-beta.3", "@babel/plugin-transform-optional-chaining": "7.0.0-beta.3", "@babel/plugin-transform-pipeline-operator": "7.0.0-beta.3", + "@babel/plugin-transform-nullish-coalescing-operator": "7.0.0-beta.3", "@babel/preset-stage-2": "7.0.0-beta.3" } } diff --git a/packages/babel-preset-stage-1/src/index.js b/packages/babel-preset-stage-1/src/index.js index a26c5818b2..06f634ba39 100644 --- a/packages/babel-preset-stage-1/src/index.js +++ b/packages/babel-preset-stage-1/src/index.js @@ -4,6 +4,7 @@ import transformDecorators from "@babel/plugin-transform-decorators"; import transformExportDefault from "@babel/plugin-transform-export-default"; import transformOptionalChaining from "@babel/plugin-transform-optional-chaining"; import transformPipelineOperator from "@babel/plugin-transform-pipeline-operator"; +import transformNullishCoalescingOperator from "@babel/plugin-transform-nullish-coalescing-operator"; export default function() { return { @@ -13,6 +14,7 @@ export default function() { transformExportDefault, transformOptionalChaining, transformPipelineOperator, + transformNullishCoalescingOperator, ], }; } diff --git a/packages/babel-types/src/constants.js b/packages/babel-types/src/constants.js index acacab0d59..67bf898068 100644 --- a/packages/babel-types/src/constants.js +++ b/packages/babel-types/src/constants.js @@ -9,7 +9,7 @@ export const COMMENT_KEYS = [ "innerComments", ]; -export const LOGICAL_OPERATORS = ["||", "&&"]; +export const LOGICAL_OPERATORS = ["||", "&&", "??"]; export const UPDATE_OPERATORS = ["++", "--"]; export const BOOLEAN_NUMBER_BINARY_OPERATORS = [">", "<", ">=", "<="];