Skip TSAsExpression when transforming spread in CallExpression (#11404)
* Skip TSAsExpression when transforming spread in CallExpression * Create @babel/helper-get-call-context package * Support OptionalCallExpressions * Use helper in optional chaining plugin, and move tests * Update package.json files * Use dot notation to access property * Remove private method tests until future MR * Update packages/babel-plugin-transform-spread/package.json * Rename @babel/helper-get-call-context to @babel/helper-skip-transparent-expr-wrappers * Handle typed OptionalMemberExpressions * Make @babel/helper-skip-transparent-expr-wrappers a dependency * Support TSNonNullExpressions * Use named import instead of default * Add test for call context when parenthesized call expression has type * Improve handling of member expressions inside transparent expression wrappers * Add comment explaining what a transparent expression wrapper is * Add newlines to test fixtures * Pass correct parameter type to skipTransparentExprWrappers * Rename to babel-helper-skip-transparent-expression-wrappers * Remove getCallContext helper * Fixed exports key * Preserve types in babel-plugin-transform-spread tests * Use external-helpers to avoid inlining helper functions in tests Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
This commit is contained in:
parent
32e7bb4027
commit
db56261414
@ -0,0 +1,3 @@
|
||||
src
|
||||
test
|
||||
*.log
|
||||
@ -0,0 +1,17 @@
|
||||
# @babel/helper-skip-transparent-expression-wrappers
|
||||
|
||||
> Helper which skips types and parentheses
|
||||
|
||||
## Install
|
||||
|
||||
Using npm:
|
||||
|
||||
```sh
|
||||
npm install --save-dev @babel/helper-skip-transparent-expression-wrappers
|
||||
```
|
||||
|
||||
or using yarn:
|
||||
|
||||
```sh
|
||||
yarn add @babel/helper-skip-transparent-expression-wrappers --dev
|
||||
```
|
||||
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@babel/helper-skip-transparent-expression-wrappers",
|
||||
"version": "7.9.6",
|
||||
"description": "Helper which skips types and parentheses",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/babel/babel.git",
|
||||
"directory": "packages/babel-helper-skip-transparent-expression-wrappers"
|
||||
},
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "./lib/index.js",
|
||||
"exports": {
|
||||
".": "./lib/index.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.9.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/traverse": "^7.9.6"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
// @flow
|
||||
|
||||
import * as t from "@babel/types";
|
||||
import type { NodePath } from "@babel/traverse";
|
||||
|
||||
// A transparent expression wrapper is an AST node that most plugins will wish
|
||||
// to skip, as its presence does not affect the behaviour of the code. This
|
||||
// includes expressions used for types, and extra parenthesis. For example, in
|
||||
// (a as any)(), this helper can be used to skip the TSAsExpression when
|
||||
// determining the callee.
|
||||
export function isTransparentExprWrapper(node: Node) {
|
||||
return (
|
||||
t.isTSAsExpression(node) ||
|
||||
t.isTSTypeAssertion(node) ||
|
||||
t.isTSNonNullExpression(node) ||
|
||||
t.isTypeCastExpression(node) ||
|
||||
t.isParenthesizedExpression(node)
|
||||
);
|
||||
}
|
||||
|
||||
export function skipTransparentExprWrappers(path: NodePath): NodePath {
|
||||
while (isTransparentExprWrapper(path.node)) {
|
||||
path = path.get("expression");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
@ -17,7 +17,8 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.10.4",
|
||||
"@babel/plugin-syntax-optional-chaining": "^7.8.0"
|
||||
"@babel/plugin-syntax-optional-chaining": "^7.8.0",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.9.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import { declare } from "@babel/helper-plugin-utils";
|
||||
import {
|
||||
isTransparentExprWrapper,
|
||||
skipTransparentExprWrappers,
|
||||
} from "@babel/helper-skip-transparent-expression-wrappers";
|
||||
import syntaxOptionalChaining from "@babel/plugin-syntax-optional-chaining";
|
||||
import { types as t } from "@babel/core";
|
||||
|
||||
@ -8,6 +12,7 @@ export default declare((api, options) => {
|
||||
const { loose = false } = options;
|
||||
|
||||
function isSimpleMemberExpression(expression) {
|
||||
expression = skipTransparentExprWrappers(expression);
|
||||
return (
|
||||
t.isIdentifier(expression) ||
|
||||
t.isSuper(expression) ||
|
||||
@ -24,16 +29,16 @@ export default declare((api, options) => {
|
||||
visitor: {
|
||||
"OptionalCallExpression|OptionalMemberExpression"(path) {
|
||||
const { scope } = path;
|
||||
// maybeParenthesized points to the outermost parenthesizedExpression
|
||||
// maybeWrapped points to the outermost transparent expression wrapper
|
||||
// or the path itself
|
||||
let maybeParenthesized = path;
|
||||
let maybeWrapped = path;
|
||||
const parentPath = path.findParent(p => {
|
||||
if (!p.isParenthesizedExpression()) return true;
|
||||
maybeParenthesized = p;
|
||||
if (!isTransparentExprWrapper(p)) return true;
|
||||
maybeWrapped = p;
|
||||
});
|
||||
let isDeleteOperation = false;
|
||||
const parentIsCall =
|
||||
parentPath.isCallExpression({ callee: maybeParenthesized.node }) &&
|
||||
parentPath.isCallExpression({ callee: maybeWrapped.node }) &&
|
||||
// note that the first condition must implies that `path.optional` is `true`,
|
||||
// otherwise the parentPath should be an OptionalCallExpressioin
|
||||
path.isOptionalMemberExpression();
|
||||
@ -43,9 +48,7 @@ export default declare((api, options) => {
|
||||
let optionalPath = path;
|
||||
while (
|
||||
optionalPath.isOptionalMemberExpression() ||
|
||||
optionalPath.isOptionalCallExpression() ||
|
||||
optionalPath.isParenthesizedExpression() ||
|
||||
optionalPath.isTSNonNullExpression()
|
||||
optionalPath.isOptionalCallExpression()
|
||||
) {
|
||||
const { node } = optionalPath;
|
||||
if (node.optional) {
|
||||
@ -54,13 +57,14 @@ export default declare((api, options) => {
|
||||
|
||||
if (optionalPath.isOptionalMemberExpression()) {
|
||||
optionalPath.node.type = "MemberExpression";
|
||||
optionalPath = optionalPath.get("object");
|
||||
optionalPath = skipTransparentExprWrappers(
|
||||
optionalPath.get("object"),
|
||||
);
|
||||
} else if (optionalPath.isOptionalCallExpression()) {
|
||||
optionalPath.node.type = "CallExpression";
|
||||
optionalPath = optionalPath.get("callee");
|
||||
} else {
|
||||
// unwrap TSNonNullExpression/ParenthesizedExpression if needed
|
||||
optionalPath = optionalPath.get("expression");
|
||||
optionalPath = skipTransparentExprWrappers(
|
||||
optionalPath.get("callee"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,7 +78,13 @@ export default declare((api, options) => {
|
||||
|
||||
const isCall = t.isCallExpression(node);
|
||||
const replaceKey = isCall ? "callee" : "object";
|
||||
const chain = node[replaceKey];
|
||||
|
||||
const chainWithTypes = node[replaceKey];
|
||||
let chain = chainWithTypes;
|
||||
|
||||
while (isTransparentExprWrapper(chain)) {
|
||||
chain = chain.expression;
|
||||
}
|
||||
|
||||
let ref;
|
||||
let check;
|
||||
@ -86,20 +96,22 @@ export default declare((api, options) => {
|
||||
// If we are using a loose transform (avoiding a Function#call) and we are at the call,
|
||||
// we can avoid a needless memoize. We only do this if the callee is a simple member
|
||||
// expression, to avoid multiple calls to nested call expressions.
|
||||
check = ref = chain;
|
||||
check = ref = chainWithTypes;
|
||||
} else {
|
||||
ref = scope.maybeGenerateMemoised(chain);
|
||||
if (ref) {
|
||||
check = t.assignmentExpression(
|
||||
"=",
|
||||
t.cloneNode(ref),
|
||||
// Here `chain` MUST NOT be cloned because it could be updated
|
||||
// when generating the memoised context of a call espression
|
||||
chain,
|
||||
// Here `chainWithTypes` MUST NOT be cloned because it could be
|
||||
// updated when generating the memoised context of a call
|
||||
// expression
|
||||
chainWithTypes,
|
||||
);
|
||||
|
||||
node[replaceKey] = ref;
|
||||
} else {
|
||||
check = ref = chain;
|
||||
check = ref = chainWithTypes;
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +121,7 @@ export default declare((api, options) => {
|
||||
if (loose && isSimpleMemberExpression(chain)) {
|
||||
// To avoid a Function#call, we can instead re-grab the property from the context object.
|
||||
// `a.?b.?()` translates roughly to `_a.b != null && _a.b()`
|
||||
node.callee = chain;
|
||||
node.callee = chainWithTypes;
|
||||
} else {
|
||||
// Otherwise, we need to memoize the context object, and change the call into a Function#call.
|
||||
// `a.?b.?()` translates roughly to `(_b = _a.b) != null && _b.call(_a)`
|
||||
@ -137,7 +149,9 @@ export default declare((api, options) => {
|
||||
// i.e. `?.b` in `(a?.b.c)()`
|
||||
if (i === 0 && parentIsCall) {
|
||||
// `(a?.b)()` to `(a == null ? undefined : a.b.bind(a))()`
|
||||
const { object } = replacement;
|
||||
const object = skipTransparentExprWrappers(
|
||||
replacementPath.get("object"),
|
||||
).node;
|
||||
let baseRef;
|
||||
if (!loose || !isSimpleMemberExpression(object)) {
|
||||
// memoize the context object in non-loose mode
|
||||
@ -180,7 +194,9 @@ export default declare((api, options) => {
|
||||
),
|
||||
);
|
||||
|
||||
replacementPath = replacementPath.get("alternate");
|
||||
replacementPath = skipTransparentExprWrappers(
|
||||
replacementPath.get("alternate"),
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
var _a, _a2, _a3, _b, _a4, _ref, _a5, _c, _a6, _a7;
|
||||
var _a, _a2, _a3, _b, _a4, _a4$b, _a5, _c, _a6, _a7;
|
||||
|
||||
(_a = a) === null || _a === void 0 ? void 0 : _a.b!.c;
|
||||
(_a2 = a) === null || _a2 === void 0 ? void 0 : _a2.b!.c.d;
|
||||
(_a3 = a) === null || _a3 === void 0 ? void 0 : _a3.b.c!.d;
|
||||
(_b = a!.b) === null || _b === void 0 ? void 0 : _b.c;
|
||||
(_a4 = a) === null || _a4 === void 0 ? void 0 : (_ref = _a4.b!) === null || _ref === void 0 ? void 0 : _ref.c;
|
||||
(_a4 = a) === null || _a4 === void 0 ? void 0 : (_a4$b = _a4.b!) === null || _a4$b === void 0 ? void 0 : _a4$b.c;
|
||||
(_a5 = a) === null || _a5 === void 0 ? void 0 : (_c = _a5.b!.c) === null || _c === void 0 ? void 0 : _c.c;
|
||||
((_a6 = a) === null || _a6 === void 0 ? void 0 : _a6.b)!.c;
|
||||
((_a7 = a) === null || _a7 === void 0 ? void 0 : _a7.b)!.c;
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
var _a, _a2, _a3, _b, _a4, _ref, _a5, _c, _a6, _a7;
|
||||
var _a, _a2, _a3, _b, _a4, _a4$b, _a5, _c, _a6, _a7;
|
||||
|
||||
(_a = a) === null || _a === void 0 ? void 0 : _a.b.c;
|
||||
(_a2 = a) === null || _a2 === void 0 ? void 0 : _a2.b.c.d;
|
||||
(_a3 = a) === null || _a3 === void 0 ? void 0 : _a3.b.c.d;
|
||||
(_b = a.b) === null || _b === void 0 ? void 0 : _b.c;
|
||||
(_a4 = a) === null || _a4 === void 0 ? void 0 : (_ref = _a4.b) === null || _ref === void 0 ? void 0 : _ref.c;
|
||||
(_a4 = a) === null || _a4 === void 0 ? void 0 : (_a4$b = _a4.b) === null || _a4$b === void 0 ? void 0 : _a4$b.c;
|
||||
(_a5 = a) === null || _a5 === void 0 ? void 0 : (_c = _a5.b.c) === null || _c === void 0 ? void 0 : _c.c;
|
||||
((_a6 = a) === null || _a6 === void 0 ? void 0 : _a6.b).c;
|
||||
((_a7 = a) === null || _a7 === void 0 ? void 0 : _a7.b).c;
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["proposal-optional-chaining"]
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
(a.b as any)?.()
|
||||
@ -0,0 +1,10 @@
|
||||
{
|
||||
"plugins": [
|
||||
[
|
||||
"syntax-typescript"
|
||||
],
|
||||
[
|
||||
"proposal-optional-chaining"
|
||||
]
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
var _a$b, _a;
|
||||
|
||||
(_a$b = ((_a = a).b as any)) === null || _a$b === void 0 ? void 0 : _a$b.call(_a);
|
||||
@ -0,0 +1 @@
|
||||
(((foo as A).bar) as B)?.(foo.bar, false)
|
||||
@ -0,0 +1,13 @@
|
||||
{
|
||||
"plugins": [
|
||||
[
|
||||
"syntax-typescript"
|
||||
],
|
||||
[
|
||||
"proposal-optional-chaining",
|
||||
{
|
||||
"loose": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
var _bar, _ref;
|
||||
|
||||
(_bar = ((_ref = (foo as A)).bar as B)) == null ? void 0 : _bar.call(_ref, foo.bar, false);
|
||||
@ -0,0 +1 @@
|
||||
(a?.b as ExampleType)?.c as ExampleType2
|
||||
@ -0,0 +1,10 @@
|
||||
{
|
||||
"plugins": [
|
||||
[
|
||||
"syntax-typescript"
|
||||
],
|
||||
[
|
||||
"proposal-optional-chaining"
|
||||
]
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
var _a, _a$b;
|
||||
|
||||
(((_a = a) === null || _a === void 0 ? void 0 : (_a$b = (_a.b as ExampleType)) === null || _a$b === void 0 ? void 0 : _a$b.c) as ExampleType2);
|
||||
@ -0,0 +1 @@
|
||||
((o?.Foo.m) as ExampleType)()
|
||||
@ -0,0 +1,10 @@
|
||||
{
|
||||
"plugins": [
|
||||
[
|
||||
"syntax-typescript"
|
||||
],
|
||||
[
|
||||
"proposal-optional-chaining"
|
||||
]
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
var _o, _o$Foo;
|
||||
|
||||
(((_o = o) === null || _o === void 0 ? void 0 : (_o$Foo = _o.Foo).m.bind(_o$Foo)) as ExampleType)();
|
||||
@ -16,7 +16,8 @@
|
||||
"babel-plugin"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.10.4"
|
||||
"@babel/helper-plugin-utils": "^7.10.4",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "7.9.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { declare } from "@babel/helper-plugin-utils";
|
||||
import { skipTransparentExprWrappers } from "@babel/helper-skip-transparent-expression-wrappers";
|
||||
import { types as t } from "@babel/core";
|
||||
|
||||
export default declare((api, options) => {
|
||||
@ -94,7 +95,8 @@ export default declare((api, options) => {
|
||||
const args = node.arguments;
|
||||
if (!hasSpread(args)) return;
|
||||
|
||||
const calleePath = path.get("callee");
|
||||
const calleePath = skipTransparentExprWrappers(path.get("callee"));
|
||||
|
||||
if (calleePath.isSuper()) return;
|
||||
|
||||
let contextLiteral = scope.buildUndefinedNode();
|
||||
@ -120,7 +122,7 @@ export default declare((api, options) => {
|
||||
node.arguments.push(first);
|
||||
}
|
||||
|
||||
const callee = node.callee;
|
||||
const callee = calleePath.node;
|
||||
|
||||
if (calleePath.isMemberExpression()) {
|
||||
const temp = scope.maybeGenerateMemoised(callee.object);
|
||||
@ -130,11 +132,11 @@ export default declare((api, options) => {
|
||||
} else {
|
||||
contextLiteral = t.cloneNode(callee.object);
|
||||
}
|
||||
t.appendToMemberExpression(callee, t.identifier("apply"));
|
||||
} else {
|
||||
node.callee = t.memberExpression(node.callee, t.identifier("apply"));
|
||||
}
|
||||
|
||||
// We use the original callee here, to preserve any types/parentheses
|
||||
node.callee = t.memberExpression(node.callee, t.identifier("apply"));
|
||||
|
||||
if (t.isSuper(contextLiteral)) {
|
||||
contextLiteral = t.thisExpression();
|
||||
}
|
||||
|
||||
1
packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/input.ts
vendored
Normal file
1
packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/input.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
(a.b: any)(...args)
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["external-helpers", "transform-spread", "syntax-flow"]
|
||||
}
|
||||
3
packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/output.js
vendored
Normal file
3
packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/output.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
var _a;
|
||||
|
||||
((_a = a).b: any).apply(_a, babelHelpers.toConsumableArray(args));
|
||||
3
packages/babel-plugin-transform-spread/test/fixtures/call-context/options.json
vendored
Normal file
3
packages/babel-plugin-transform-spread/test/fixtures/call-context/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["external-helpers", "transform-spread", "transform-parameters"]
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
(a.b)(...args)
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"parserOpts": {
|
||||
"createParenthesizedExpressions": true
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
var _a;
|
||||
|
||||
((_a = a).b).apply(_a, babelHelpers.toConsumableArray(args));
|
||||
1
packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/input.ts
vendored
Normal file
1
packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/input.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
(<any> a.b)(...args)
|
||||
3
packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/options.json
vendored
Normal file
3
packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["external-helpers", "transform-spread", "syntax-typescript"]
|
||||
}
|
||||
3
packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/output.js
vendored
Normal file
3
packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/output.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
var _a;
|
||||
|
||||
(<any> (_a = a).b).apply(_a, babelHelpers.toConsumableArray(args));
|
||||
1
packages/babel-plugin-transform-spread/test/fixtures/regression/11400/input.ts
vendored
Normal file
1
packages/babel-plugin-transform-spread/test/fixtures/regression/11400/input.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
(dog.bark as any)(...args)
|
||||
7
packages/babel-plugin-transform-spread/test/fixtures/regression/11400/options.json
vendored
Normal file
7
packages/babel-plugin-transform-spread/test/fixtures/regression/11400/options.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"typescript"
|
||||
]
|
||||
]
|
||||
}
|
||||
3
packages/babel-plugin-transform-spread/test/fixtures/regression/11400/output.js
vendored
Normal file
3
packages/babel-plugin-transform-spread/test/fixtures/regression/11400/output.js
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
var _dog;
|
||||
|
||||
(_dog = dog).bark.apply(_dog, babelHelpers.toConsumableArray(args));
|
||||
Loading…
x
Reference in New Issue
Block a user