Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip TSAsExpression when transforming spread in CallExpression #11404

Merged
merged 30 commits into from Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
087405e
Skip TSAsExpression when transforming spread in CallExpression
oliverdunk Apr 10, 2020
dc5316e
Merge branch 'master' into od/11400-typescript-spread-as
oliverdunk Apr 10, 2020
e090927
Create @babel/helper-get-call-context package
oliverdunk May 3, 2020
b41cc10
Support OptionalCallExpressions
oliverdunk May 4, 2020
f406e41
Merge branch 'master' into od/11400-typescript-spread-as
oliverdunk May 4, 2020
a0bb643
Merge branch 'master' into od/11400-typescript-spread-as
oliverdunk May 16, 2020
2bdb62d
Use helper in optional chaining plugin, and move tests
oliverdunk May 17, 2020
06d092f
Update package.json files
oliverdunk May 23, 2020
ed10970
Use dot notation to access property
oliverdunk May 23, 2020
1d957b3
Remove private method tests until future MR
oliverdunk May 23, 2020
5d7c306
Update packages/babel-plugin-transform-spread/package.json
nicolo-ribaudo May 23, 2020
defdad1
Rename @babel/helper-get-call-context to @babel/helper-skip-transpare…
oliverdunk May 23, 2020
8c3b44b
Handle typed OptionalMemberExpressions
oliverdunk May 24, 2020
db8f8c3
Make @babel/helper-skip-transparent-expr-wrappers a dependency
oliverdunk May 24, 2020
b4948cf
Merge branch 'master' into od/11400-typescript-spread-as
oliverdunk May 31, 2020
c69c921
Support TSNonNullExpressions
oliverdunk May 31, 2020
db8f57a
Use named import instead of default
oliverdunk May 31, 2020
117bb67
Merge branch 'master' into od/11400-typescript-spread-as
oliverdunk Jun 1, 2020
4e49c03
Add test for call context when parenthesized call expression has type
oliverdunk Jun 1, 2020
c0ecd48
Merge branch 'main' into od/11400-typescript-spread-as
oliverdunk Jun 29, 2020
e40676b
Improve handling of member expressions inside transparent expression …
oliverdunk Jun 29, 2020
dd1b57d
Add comment explaining what a transparent expression wrapper is
oliverdunk Jun 29, 2020
4250471
Add newlines to test fixtures
oliverdunk Jun 29, 2020
24d550a
Merge branch 'main' into od/11400-typescript-spread-as
oliverdunk Jun 30, 2020
7069abe
Pass correct parameter type to skipTransparentExprWrappers
oliverdunk Jun 30, 2020
0704742
Rename to babel-helper-skip-transparent-expression-wrappers
oliverdunk Jul 3, 2020
f6cc6e2
Remove getCallContext helper
oliverdunk Jul 3, 2020
cea347f
Fixed exports key
oliverdunk Jul 3, 2020
d654efc
Preserve types in babel-plugin-transform-spread tests
oliverdunk Jul 3, 2020
e6c3a4c
Use external-helpers to avoid inlining helper functions in tests
oliverdunk Jul 22, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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";
oliverdunk marked this conversation as resolved.
Show resolved Hide resolved
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;
}
Expand Up @@ -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"
Expand Down
60 changes: 38 additions & 22 deletions packages/babel-plugin-proposal-optional-chaining/src/index.js
@@ -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";

Expand All @@ -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) ||
Expand All @@ -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();
Expand All @@ -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) {
Expand All @@ -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"),
);
}
}

Expand All @@ -74,28 +78,36 @@ export default declare((api, options) => {

const isCall = t.isCallExpression(node);
const replaceKey = isCall ? "callee" : "object";
const chain = node[replaceKey];

const chainWithTypes = node[replaceKey];
JLHwung marked this conversation as resolved.
Show resolved Hide resolved
let chain = chainWithTypes;

while (isTransparentExprWrapper(chain)) {
chain = chain.expression;
}

let ref;
let check;
if (loose && isCall && isSimpleMemberExpression(chain)) {
// 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;
}
}

Expand All @@ -105,7 +117,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)`
Expand Down Expand Up @@ -133,7 +145,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
Expand Down Expand Up @@ -176,7 +190,9 @@ export default declare((api, options) => {
),
);

replacementPath = replacementPath.get("alternate");
replacementPath = skipTransparentExprWrappers(
replacementPath.get("alternate"),
);
}
},
},
Expand Down
@@ -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);
JLHwung marked this conversation as resolved.
Show resolved Hide resolved
@@ -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)();
3 changes: 2 additions & 1 deletion packages/babel-plugin-transform-spread/package.json
Expand Up @@ -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"
Expand Down
12 changes: 7 additions & 5 deletions packages/babel-plugin-transform-spread/src/index.js
@@ -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) => {
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -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();
}
Expand Down
@@ -0,0 +1 @@
(a.b: any)(...args)
@@ -0,0 +1,3 @@
{
"plugins": ["external-helpers", "transform-spread", "syntax-flow"]
}
@@ -0,0 +1,3 @@
var _a;

((_a = a).b: any).apply(_a, babelHelpers.toConsumableArray(args));
@@ -0,0 +1,3 @@
{
"plugins": ["external-helpers", "transform-spread", "transform-parameters"]
}
@@ -0,0 +1 @@
(a.b)(...args)