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:
@@ -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"),
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user