From fe7cd9e312ac1263961c919fbfefb7c14ecb33dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 27 May 2020 10:07:58 -0400 Subject: [PATCH] fix: unwrap parthenthesizedExpression --- .../src/index.js | 24 ++++++---- .../exec.js | 45 +++++++++++++++++++ .../options.json | 6 +++ 3 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/exec.js create mode 100644 packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/options.json diff --git a/packages/babel-plugin-proposal-optional-chaining/src/index.js b/packages/babel-plugin-proposal-optional-chaining/src/index.js index 76a9b1ba438f..46b53685e7ca 100644 --- a/packages/babel-plugin-proposal-optional-chaining/src/index.js +++ b/packages/babel-plugin-proposal-optional-chaining/src/index.js @@ -23,14 +23,23 @@ export default declare((api, options) => { visitor: { "OptionalCallExpression|OptionalMemberExpression"(path) { - const { parentPath, scope } = path; + const { scope } = path; + let parentPath; + // unwrap parenthesis around parent path + for ( + { parentPath } = path; + parentPath.type === "ParenthesizedExpression"; + { parentPath } = parentPath + ); let isDeleteOperation = false; const optionals = []; let optionalPath = path; while ( optionalPath.isOptionalMemberExpression() || - optionalPath.isOptionalCallExpression() + optionalPath.isOptionalCallExpression() || + optionalPath.isParenthesizedExpression() || + optionalPath.isTSNonNullExpression() ) { const { node } = optionalPath; if (node.optional) { @@ -43,10 +52,8 @@ export default declare((api, options) => { } else if (optionalPath.isOptionalCallExpression()) { optionalPath.node.type = "CallExpression"; optionalPath = optionalPath.get("callee"); - } - - // unwrap a TSNonNullExpression if need - if (optionalPath.isTSNonNullExpression()) { + } else { + // unwrap TSNonNullExpression/ParenthesizedExpression if needed optionalPath = optionalPath.get("expression"); } } @@ -117,8 +124,9 @@ export default declare((api, options) => { // Ensure (a?.b)() has proper `this` if ( t.isMemberExpression(replacement) && - replacement.extra?.parenthesized && - replacementPath.parentPath.isCallExpression() + (replacement.extra?.parenthesized || + replacementPath.parentPath !== parentPath) && + parentPath.isCallExpression() ) { // `(a?.b)()` to `(a == null ? undefined : a.b.bind(a))()` const { object } = replacement; diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/exec.js b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/exec.js new file mode 100644 index 000000000000..bfb53e693b08 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/exec.js @@ -0,0 +1,45 @@ +class Foo { + constructor() { + this.x = 1; + this.self = this; + } + m() { return this.x; }; + getSelf() { return this } + + test() { + const Foo = this; + const o = { Foo: Foo }; + const deep = { very: { o } }; + function fn() { + return o; + } + function fnDeep() { + return deep; + } + + expect((Foo?.["m"])()).toEqual(1); + expect((Foo?.["m"])().toString).toEqual(1..toString); + expect((Foo?.["m"])().toString()).toEqual('1'); + + expect((o?.Foo.m)()).toEqual(1); + expect((o?.Foo.m)().toString).toEqual(1..toString); + expect((o?.Foo.m)().toString()).toEqual('1'); + + expect((((o.Foo?.self.getSelf)())?.m)()).toEqual(1); + expect((((o.Foo.self?.getSelf)())?.m)()).toEqual(1); + } + + testNull() { + const o = null; + + expect(() => { (o?.Foo.m)() }).toThrow(); + expect(() => { (o?.Foo.m)().toString }).toThrow(); + expect(() => { (o?.Foo.m)().toString() }).toThrow(); + + expect(() => { (((o.Foo?.self.getSelf)())?.m)() }).toThrow(); + expect(() => { (((o.Foo.self?.getSelf)())?.m)() }).toThrow(); + } +} + +(new Foo).test(); +(new Foo).testNull(); diff --git a/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/options.json b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/options.json new file mode 100644 index 000000000000..eae39736acf4 --- /dev/null +++ b/packages/babel-plugin-proposal-optional-chaining/test/fixtures/general/parenthesized-expression-member-call/options.json @@ -0,0 +1,6 @@ +{ + "plugins": ["proposal-optional-chaining"], + "parserOpts": { + "createParenthesizedExpressions": true + } +}