From db56261414d4ec0265a604d2eacaf47c8ac459dd Mon Sep 17 00:00:00 2001 From: Oliver Dunk Date: Thu, 30 Jul 2020 19:17:37 +0100 Subject: [PATCH] Skip TSAsExpression when transforming spread in CallExpression (#11404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .../.npmignore | 3 + .../README.md | 17 ++++++ .../package.json | 25 ++++++++ .../src/index.js | 27 +++++++++ .../package.json | 3 +- .../src/index.js | 60 ++++++++++++------- .../output.js | 4 +- .../output.js | 4 +- .../transparent-expr-wrappers/options.json | 3 + .../ts-as-call-context/input.ts | 1 + .../ts-as-call-context/options.json | 10 ++++ .../ts-as-call-context/output.js | 3 + .../ts-as-function-call-loose/input.ts | 1 + .../ts-as-function-call-loose/options.json | 13 ++++ .../ts-as-function-call-loose/output.js | 3 + .../ts-as-member-expression/input.ts | 1 + .../ts-as-member-expression/options.json | 10 ++++ .../ts-as-member-expression/output.js | 3 + .../input.ts | 1 + .../options.json | 10 ++++ .../output.js | 3 + .../package.json | 3 +- .../src/index.js | 12 ++-- .../call-context/flow-type-cast/input.ts | 1 + .../call-context/flow-type-cast/options.json | 3 + .../call-context/flow-type-cast/output.js | 3 + .../test/fixtures/call-context/options.json | 3 + .../parenthesized-expressions/input.js | 1 + .../parenthesized-expressions/options.json | 5 ++ .../parenthesized-expressions/output.js | 3 + .../call-context/ts-type-cast/input.ts | 1 + .../call-context/ts-type-cast/options.json | 3 + .../call-context/ts-type-cast/output.js | 3 + .../test/fixtures/regression/11400/input.ts | 1 + .../fixtures/regression/11400/options.json | 7 +++ .../test/fixtures/regression/11400/output.js | 3 + 36 files changed, 224 insertions(+), 33 deletions(-) create mode 100644 packages/babel-helper-skip-transparent-expression-wrappers/.npmignore create mode 100644 packages/babel-helper-skip-transparent-expression-wrappers/README.md create mode 100644 packages/babel-helper-skip-transparent-expression-wrappers/package.json create mode 100644 packages/babel-helper-skip-transparent-expression-wrappers/src/index.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/input.ts create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/input.ts create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/input.ts create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/output.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/input.ts create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/options.json create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/output.js create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/input.ts create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/options.json create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/output.js create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/call-context/options.json create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/input.js create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/options.json create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/output.js create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/input.ts create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/options.json create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/output.js create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/regression/11400/input.ts create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/regression/11400/options.json create mode 100644 packages/babel-plugin-transform-spread/test/fixtures/regression/11400/output.js diff --git a/packages/babel-helper-skip-transparent-expression-wrappers/.npmignore b/packages/babel-helper-skip-transparent-expression-wrappers/.npmignore new file mode 100644 index 000000000000..f9806945836e --- /dev/null +++ b/packages/babel-helper-skip-transparent-expression-wrappers/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-helper-skip-transparent-expression-wrappers/README.md b/packages/babel-helper-skip-transparent-expression-wrappers/README.md new file mode 100644 index 000000000000..ac89e026937c --- /dev/null +++ b/packages/babel-helper-skip-transparent-expression-wrappers/README.md @@ -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 +``` diff --git a/packages/babel-helper-skip-transparent-expression-wrappers/package.json b/packages/babel-helper-skip-transparent-expression-wrappers/package.json new file mode 100644 index 000000000000..428d428020fb --- /dev/null +++ b/packages/babel-helper-skip-transparent-expression-wrappers/package.json @@ -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" + } +} \ No newline at end of file diff --git a/packages/babel-helper-skip-transparent-expression-wrappers/src/index.js b/packages/babel-helper-skip-transparent-expression-wrappers/src/index.js new file mode 100644 index 000000000000..c218ac6c8fde --- /dev/null +++ b/packages/babel-helper-skip-transparent-expression-wrappers/src/index.js @@ -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; +} diff --git a/packages/babel-plugin-proposal-optional-chaining/package.json b/packages/babel-plugin-proposal-optional-chaining/package.json index 48a60898096c..a35674c09544 100644 --- a/packages/babel-plugin-proposal-optional-chaining/package.json +++ b/packages/babel-plugin-proposal-optional-chaining/package.json @@ -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" diff --git a/packages/babel-plugin-proposal-optional-chaining/src/index.js b/packages/babel-plugin-proposal-optional-chaining/src/index.js index 3c80d4238c7a..f3d32be4338a 100644 --- a/packages/babel-plugin-proposal-optional-chaining/src/index.js +++ b/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"; @@ -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"), + ); } }, }, diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/regression/10959-transform-optional-chaining/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/regression/10959-transform-optional-chaining/output.js index 5bcddf99c677..06ce1ed5c674 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/regression/10959-transform-optional-chaining/output.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/regression/10959-transform-optional-chaining/output.js @@ -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; diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/regression/10959-transform-ts-and-optional-chaining/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/regression/10959-transform-ts-and-optional-chaining/output.js index 8af9f8dc0f23..56db11f31615 100644 --- a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/regression/10959-transform-ts-and-optional-chaining/output.js +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/regression/10959-transform-ts-and-optional-chaining/output.js @@ -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; diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/options.json new file mode 100644 index 000000000000..43d8f4d114cc --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["proposal-optional-chaining"] +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/input.ts b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/input.ts new file mode 100644 index 000000000000..45e75935cbab --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/input.ts @@ -0,0 +1 @@ +(a.b as any)?.() diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/options.json new file mode 100644 index 000000000000..3d509031920b --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/options.json @@ -0,0 +1,10 @@ +{ + "plugins": [ + [ + "syntax-typescript" + ], + [ + "proposal-optional-chaining" + ] + ] +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/output.js new file mode 100644 index 000000000000..bbb88cd031aa --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-call-context/output.js @@ -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); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/input.ts b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/input.ts new file mode 100644 index 000000000000..bbb299ce47a9 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/input.ts @@ -0,0 +1 @@ +(((foo as A).bar) as B)?.(foo.bar, false) diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/options.json new file mode 100644 index 000000000000..873ed97e39f7 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/options.json @@ -0,0 +1,13 @@ +{ + "plugins": [ + [ + "syntax-typescript" + ], + [ + "proposal-optional-chaining", + { + "loose": true + } + ] + ] +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/output.js new file mode 100644 index 000000000000..93a8b5c45d88 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-function-call-loose/output.js @@ -0,0 +1,3 @@ +var _bar, _ref; + +(_bar = ((_ref = (foo as A)).bar as B)) == null ? void 0 : _bar.call(_ref, foo.bar, false); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/input.ts b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/input.ts new file mode 100644 index 000000000000..fe8740240481 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/input.ts @@ -0,0 +1 @@ +(a?.b as ExampleType)?.c as ExampleType2 diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/options.json new file mode 100644 index 000000000000..3d509031920b --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/options.json @@ -0,0 +1,10 @@ +{ + "plugins": [ + [ + "syntax-typescript" + ], + [ + "proposal-optional-chaining" + ] + ] +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/output.js new file mode 100644 index 000000000000..8839b068263f --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-as-member-expression/output.js @@ -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); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/input.ts b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/input.ts new file mode 100644 index 000000000000..db9e53a2762a --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/input.ts @@ -0,0 +1 @@ +((o?.Foo.m) as ExampleType)() diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/options.json new file mode 100644 index 000000000000..00511863bbd0 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/options.json @@ -0,0 +1,10 @@ +{ + "plugins": [ + [ + "syntax-typescript" + ], + [ + "proposal-optional-chaining" + ] + ] +} diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/output.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/output.js new file mode 100644 index 000000000000..ca86081c2b97 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/transparent-expr-wrappers/ts-parenthesized-expression-member-call/output.js @@ -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)(); diff --git a/packages/babel-plugin-transform-spread/package.json b/packages/babel-plugin-transform-spread/package.json index ce7dbb08a6fc..1f7be0623450 100644 --- a/packages/babel-plugin-transform-spread/package.json +++ b/packages/babel-plugin-transform-spread/package.json @@ -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" diff --git a/packages/babel-plugin-transform-spread/src/index.js b/packages/babel-plugin-transform-spread/src/index.js index f320d50884d3..dcd933a1fa7e 100644 --- a/packages/babel-plugin-transform-spread/src/index.js +++ b/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) => { @@ -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(); } diff --git a/packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/input.ts b/packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/input.ts new file mode 100644 index 000000000000..8874581acdfa --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/input.ts @@ -0,0 +1 @@ +(a.b: any)(...args) diff --git a/packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/options.json b/packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/options.json new file mode 100644 index 000000000000..34154ac6a6a5 --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["external-helpers", "transform-spread", "syntax-flow"] +} diff --git a/packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/output.js b/packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/output.js new file mode 100644 index 000000000000..c912da78ec7d --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/call-context/flow-type-cast/output.js @@ -0,0 +1,3 @@ +var _a; + +((_a = a).b: any).apply(_a, babelHelpers.toConsumableArray(args)); diff --git a/packages/babel-plugin-transform-spread/test/fixtures/call-context/options.json b/packages/babel-plugin-transform-spread/test/fixtures/call-context/options.json new file mode 100644 index 000000000000..e9f2daf0be9f --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/call-context/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["external-helpers", "transform-spread", "transform-parameters"] +} diff --git a/packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/input.js b/packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/input.js new file mode 100644 index 000000000000..9467f61f0957 --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/input.js @@ -0,0 +1 @@ +(a.b)(...args) diff --git a/packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/options.json b/packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/options.json new file mode 100644 index 000000000000..2931a52b5b3f --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/options.json @@ -0,0 +1,5 @@ +{ + "parserOpts": { + "createParenthesizedExpressions": true + } +} diff --git a/packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/output.js b/packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/output.js new file mode 100644 index 000000000000..f0a0a9b7819f --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/call-context/parenthesized-expressions/output.js @@ -0,0 +1,3 @@ +var _a; + +((_a = a).b).apply(_a, babelHelpers.toConsumableArray(args)); diff --git a/packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/input.ts b/packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/input.ts new file mode 100644 index 000000000000..354578c62526 --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/input.ts @@ -0,0 +1 @@ +( a.b)(...args) diff --git a/packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/options.json b/packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/options.json new file mode 100644 index 000000000000..32fc1d8b1569 --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["external-helpers", "transform-spread", "syntax-typescript"] +} diff --git a/packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/output.js b/packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/output.js new file mode 100644 index 000000000000..8644e7401390 --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/call-context/ts-type-cast/output.js @@ -0,0 +1,3 @@ +var _a; + +( (_a = a).b).apply(_a, babelHelpers.toConsumableArray(args)); diff --git a/packages/babel-plugin-transform-spread/test/fixtures/regression/11400/input.ts b/packages/babel-plugin-transform-spread/test/fixtures/regression/11400/input.ts new file mode 100644 index 000000000000..2bf46926aef3 --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/regression/11400/input.ts @@ -0,0 +1 @@ +(dog.bark as any)(...args) diff --git a/packages/babel-plugin-transform-spread/test/fixtures/regression/11400/options.json b/packages/babel-plugin-transform-spread/test/fixtures/regression/11400/options.json new file mode 100644 index 000000000000..52dd0fd384e3 --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/regression/11400/options.json @@ -0,0 +1,7 @@ +{ + "presets": [ + [ + "typescript" + ] + ] +} diff --git a/packages/babel-plugin-transform-spread/test/fixtures/regression/11400/output.js b/packages/babel-plugin-transform-spread/test/fixtures/regression/11400/output.js new file mode 100644 index 000000000000..d3814197145a --- /dev/null +++ b/packages/babel-plugin-transform-spread/test/fixtures/regression/11400/output.js @@ -0,0 +1,3 @@ +var _dog; + +(_dog = dog).bark.apply(_dog, babelHelpers.toConsumableArray(args));