Define default value for vars shadowing params (#11307)

* Define default value for vars shadowing params

* Remove from package.json

* Only convert traverse the params if needed

This prevents this plugin from running after the destructuring
transform, which causes #11256

* Review

* Review

* Update packages/babel-plugin-transform-parameters/src/params.js [skip ci]

Co-Authored-By: Brian Ng <bng412@gmail.com>

Co-authored-by: Brian Ng <bng412@gmail.com>
This commit is contained in:
Nicolò Ribaudo 2020-03-22 10:45:10 +01:00 committed by GitHub
parent b0315b81c7
commit ceb54dd756
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 129 additions and 66 deletions

View File

@ -9,7 +9,6 @@
}, },
"main": "lib/index.js", "main": "lib/index.js",
"dependencies": { "dependencies": {
"@babel/helper-call-delegate": "^7.8.7",
"@babel/helper-get-function-arity": "^7.8.3", "@babel/helper-get-function-arity": "^7.8.3",
"@babel/helper-plugin-utils": "^7.8.3" "@babel/helper-plugin-utils": "^7.8.3"
}, },

View File

@ -1,4 +1,3 @@
import callDelegate from "@babel/helper-call-delegate";
import { template, types as t } from "@babel/core"; import { template, types as t } from "@babel/core";
const buildDefaultParam = template(` const buildDefaultParam = template(`
@ -23,47 +22,40 @@ const buildSafeArgumentsAccess = template(`
let $0 = arguments.length > $1 ? arguments[$1] : undefined; let $0 = arguments.length > $1 ? arguments[$1] : undefined;
`); `);
function isSafeBinding(scope, node) {
if (!scope.hasOwnBinding(node.name)) return true;
const { kind } = scope.getOwnBinding(node.name);
return kind === "param" || kind === "local";
}
const iifeVisitor = { const iifeVisitor = {
ReferencedIdentifier(path, state) { "ReferencedIdentifier|BindingIdentifier"(path, state) {
const { scope, node } = path; const { scope, node } = path;
const { name } = node;
if ( if (
node.name === "eval" || name === "eval" ||
!isSafeBinding(scope, node) || (scope.getBinding(name) === state.scope.parent.getBinding(name) &&
!isSafeBinding(state.scope, node) state.scope.hasOwnBinding(name))
) { ) {
state.iife = true; state.needsOuterBinding = true;
path.stop(); path.stop();
} }
}, },
Scope(path) {
// different bindings
path.skip();
},
}; };
export default function convertFunctionParams(path, loose) { export default function convertFunctionParams(path, loose) {
const params = path.get("params");
const isSimpleParameterList = params.every(param => param.isIdentifier());
if (isSimpleParameterList) return false;
const { node, scope } = path; const { node, scope } = path;
const state = { const state = {
iife: false, stop: false,
scope: scope, needsOuterBinding: false,
scope,
}; };
const body = []; const body = [];
const params = path.get("params"); const shadowedParams = new Set();
let firstOptionalIndex = null;
for (let i = 0; i < params.length; i++) {
const param = params[i];
for (const param of params) {
for (const name of Object.keys(param.getBindingIdentifiers())) { for (const name of Object.keys(param.getBindingIdentifiers())) {
const constantViolations = scope.bindings[name]?.constantViolations; const constantViolations = scope.bindings[name]?.constantViolations;
if (constantViolations) { if (constantViolations) {
@ -74,7 +66,7 @@ export default function convertFunctionParams(path, loose) {
// If so, we remove that declarator. // If so, we remove that declarator.
// Otherwise, we have to wrap it in an IIFE. // Otherwise, we have to wrap it in an IIFE.
switch (node.type) { switch (node.type) {
case "VariableDeclarator": case "VariableDeclarator": {
if (node.init === null) { if (node.init === null) {
const declaration = redeclarator.parentPath; const declaration = redeclarator.parentPath;
// The following uninitialized var declarators should not be removed // The following uninitialized var declarators should not be removed
@ -88,14 +80,30 @@ export default function convertFunctionParams(path, loose) {
break; break;
} }
} }
// fall through
shadowedParams.add(name);
break;
}
case "FunctionDeclaration": case "FunctionDeclaration":
state.iife = true; shadowedParams.add(name);
break; break;
} }
} }
} }
} }
}
if (shadowedParams.size === 0) {
for (const param of params) {
if (!param.isIdentifier()) param.traverse(iifeVisitor, state);
if (state.needsOuterBinding) break;
}
}
let firstOptionalIndex = null;
for (let i = 0; i < params.length; i++) {
const param = params[i];
const paramIsAssignmentPattern = param.isAssignmentPattern(); const paramIsAssignmentPattern = param.isAssignmentPattern();
if (paramIsAssignmentPattern && (loose || node.kind === "set")) { if (paramIsAssignmentPattern && (loose || node.kind === "set")) {
@ -131,15 +139,6 @@ export default function convertFunctionParams(path, loose) {
const left = param.get("left"); const left = param.get("left");
const right = param.get("right"); const right = param.get("right");
if (!state.iife) {
if (right.isIdentifier() && !isSafeBinding(scope, right.node)) {
// the right hand side references a parameter
state.iife = true;
} else {
right.traverse(iifeVisitor, state);
}
}
const defNode = buildDefaultParam({ const defNode = buildDefaultParam({
VARIABLE_NAME: left.node, VARIABLE_NAME: left.node,
DEFAULT_VALUE: right.node, DEFAULT_VALUE: right.node,
@ -162,14 +161,8 @@ export default function convertFunctionParams(path, loose) {
param.replaceWith(t.cloneNode(uid)); param.replaceWith(t.cloneNode(uid));
} }
if (!state.iife && !param.isIdentifier()) {
param.traverse(iifeVisitor, state);
}
} }
if (body.length === 0) return false;
// we need to cut off all trailing parameters // we need to cut off all trailing parameters
if (firstOptionalIndex !== null) { if (firstOptionalIndex !== null) {
node.params = node.params.slice(0, firstOptionalIndex); node.params = node.params.slice(0, firstOptionalIndex);
@ -178,13 +171,34 @@ export default function convertFunctionParams(path, loose) {
// ensure it's a block, useful for arrow functions // ensure it's a block, useful for arrow functions
path.ensureBlock(); path.ensureBlock();
if (state.iife) { if (state.needsOuterBinding || shadowedParams.size > 0) {
// we don't want to hoist the inner declarations up body.push(buildScopeIIFE(shadowedParams, path.get("body").node));
body.push(callDelegate(path, scope, false));
path.set("body", t.blockStatement(body)); path.set("body", t.blockStatement(body));
// We inject an arrow and then transform it to a normal function, to be
// sure that we correctly handle this and arguments.
const bodyPath = path.get("body.body");
const arrowPath = bodyPath[bodyPath.length - 1].get("argument.callee");
arrowPath.arrowFunctionToExpression();
} else { } else {
path.get("body").unshiftContainer("body", body); path.get("body").unshiftContainer("body", body);
} }
return true; return true;
} }
function buildScopeIIFE(shadowedParams, body) {
const args = [];
const params = [];
for (const name of shadowedParams) {
// We create them twice; the other option is to use t.cloneNode
args.push(t.identifier(name));
params.push(t.identifier(name));
}
return t.returnStatement(
t.callExpression(t.arrowFunctionExpression(params, body), params),
);
}

View File

@ -1,6 +1,6 @@
function foo() { function foo() {
var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2; var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;
return function () { return function (a) {
function a() {} function a() {}
}(); }(a);
} }

View File

@ -10,11 +10,13 @@ var Test = /*#__PURE__*/function () {
babelHelpers.createClass(Test, [{ babelHelpers.createClass(Test, [{
key: "invite", key: "invite",
value: function invite() { value: function invite() {
var _this = this;
var p = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : a; var p = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : a;
return function () { return function () {
var a; var a;
this; _this;
}.apply(this); }();
} }
}]); }]);
return Test; return Test;

View File

@ -1,5 +1,5 @@
function foo(a = 2) { function foo(a = 2) {
for (var a, i = 0; i < 1; i++); for (var a, i = 0; i < 1; i++);
expect(a).toBe(undefined); expect(a).toBe(2);
} }
foo(); foo();

View File

@ -1,8 +1,8 @@
function f() { function f() {
var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2; var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;
var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3; var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3;
return function () { return function (b) {
var b = 4; var b = 4;
return a + b; return a + b;
}(); }(b);
} }

View File

@ -1,11 +1,11 @@
function f() { function f() {
var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2; var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;
var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3; var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3;
return function () { return function (a) {
var _a = { var _a = {
a: 4 a: 4
}, },
a = _a.a; a = _a.a;
return a + b; return a + b;
}(); }(a);
} }

View File

@ -1,9 +1,9 @@
function foo(_ref) { function foo(_ref) {
var a = _ref.a, var a = _ref.a,
b = _ref.b; b = _ref.b;
return function () { return function (a) {
var a = 3; var a = 3;
var c = 2; var c = 2;
var d = a + b + c; var d = a + b + c;
}(); }(a);
} }

View File

@ -3,9 +3,9 @@ function foo(_ref) {
a = _ref2[0], a = _ref2[0],
b = _ref2[1]; b = _ref2[1];
return function () { return function (a) {
var a = 3; var a = 3;
var c = 2; var c = 2;
var d = a + b + c; var d = a + b + c;
}(); }(a);
} }

View File

@ -1,6 +1,6 @@
function foo() { function foo() {
var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2; var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;
return function () { return function (a) {
var a = 1; var a = 1;
}(); }(a);
} }

View File

@ -1,11 +1,11 @@
function f() { function f() {
var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2; var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 2;
var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3; var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3;
return function () { return function (a) {
var _a = { var _a = {
a: 4 a: 4
}, },
a = _a.a; a = _a.a;
return a + b; return a + b;
}(); }(a);
} }

View File

@ -1,6 +1,6 @@
(function () { (function () {
let i = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "__proto__"; let i = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "__proto__";
return function () { return function (i) {
for (var i in {}); for (var i in {});
}(); }(i);
}); });

View File

@ -0,0 +1,7 @@
class A {
a = b => {
{
({ b } = {});
}
};
}

View File

@ -0,0 +1,8 @@
{
"plugins": [
"proposal-class-properties",
"transform-parameters",
"transform-destructuring",
["external-helpers", { "helperVersion": "7.100.0" }]
]
}

View File

@ -0,0 +1,11 @@
class A {
constructor() {
babelHelpers.defineProperty(this, "a", b => {
{
var _ref = {};
b = _ref.b;
}
});
}
}

View File

@ -0,0 +1,6 @@
function test(a, b = 1) {
var a = a + b;
return a;
}
expect(test(2)).toBe(3);

View File

@ -0,0 +1,4 @@
function test(a, b = 1) {
var a = a + b;
return a;
}

View File

@ -0,0 +1,5 @@
{
"plugins": [
"transform-parameters"
]
}

View File

@ -0,0 +1,7 @@
function test(a) {
let b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
return function (a) {
var a = a + b;
return a;
}(a);
}