diff --git a/changelog_unreleased/javascript/pr-6685.md b/changelog_unreleased/javascript/pr-6685.md new file mode 100644 index 000000000..61246ec27 --- /dev/null +++ b/changelog_unreleased/javascript/pr-6685.md @@ -0,0 +1,22 @@ +#### Method chain breaking ([#6685](https://github.com/prettier/prettier/pull/6685) by [@mmkal](https://github.com/mmkal)) + +Previously, any chained method calls of length three or longer would be automatically broken. This uses a heuristic for the complexity of the arguments in the chain, rather than just the chain length. Now, if chained method calls have arguments which aren't easy to understand at a glance (e.g. functions or deeply-nested objects), the chain is broken. Otherwise, they're allowed to remain on one line. See prior issues [#3197](https://github.com/prettier/prettier/issues/3107), [#4765](https://github.com/prettier/prettier/pull/4765), [#1565](https://github.com/prettier/prettier/issues/1565) and [#4125](https://github.com/prettier/prettier/pull/4125) for context and examples. + + +```javascript +// Input +foo.one().two().three() === bar.four().five().six(); + +// Output (Prettier stable) +foo + .one() + .two() + .three() === + bar + .four() + .five() + .six(); + +// Output (Prettier master) +foo.one().two().three() === bar.four().five().six(); +``` diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index 00b1e363b..eca7c19aa 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -76,6 +76,7 @@ const { isNumericLiteral, isObjectType, isObjectTypePropertyAFunction, + isSimpleCallArgument, isSimpleFlowType, isSimpleTemplateLiteral, isStringLiteral, @@ -5084,9 +5085,7 @@ function printMemberChain(path, options, print) { const oneLine = concat(printedGroups); const cutoff = shouldMerge ? 3 : 2; - const flatGroups = groups - .slice(0, cutoff) - .reduce((res, group) => res.concat(group), []); + const flatGroups = groups.reduce((res, group) => res.concat(group), []); const hasComment = flatGroups.slice(1, -1).some(node => hasLeadingComment(node.node)) || @@ -5123,14 +5122,17 @@ function printMemberChain(path, options, print) { .map(({ node }) => node) .filter(isCallOrOptionalCallExpression); - // We don't want to print in one line if there's: + // We don't want to print in one line if the chain has: // * A comment. - // * 3 or more chained calls. + // * Non-trivial arguments. // * Any group but the last one has a hard line. // If the last group is a function it's okay to inline if it fits. if ( hasComment || - callExpressions.length >= 3 || + (callExpressions.length > 2 && + callExpressions.some( + expr => !expr.arguments.every(arg => isSimpleCallArgument(arg, 0)) + )) || printedGroups.slice(0, -1).some(willBreak) || /** * scopes.filter(scope => scope.value !== '').map((scope, i) => { diff --git a/src/language-js/utils.js b/src/language-js/utils.js index fb51d4f2d..b27fa9901 100644 --- a/src/language-js/utils.js +++ b/src/language-js/utils.js @@ -885,6 +885,86 @@ function isLongCurriedCallExpression(path) { ); } +/** + * @param {import('estree').Node} node + * @param {number} depth + * @returns {boolean} + */ +function isSimpleCallArgument(node, depth) { + if (depth >= 2) { + return false; + } + const isChildSimple = child => isSimpleCallArgument(child, depth + 1); + + const regexpPattern = + (node.type === "Literal" && node.regex && node.regex.pattern) || + (node.type === "RegExpLiteral" && node.pattern); + + if (regexpPattern && regexpPattern.length > 5) { + return false; + } + + if ( + node.type === "Literal" || + node.type === "BooleanLiteral" || + node.type === "NullLiteral" || + node.type === "NumericLiteral" || + node.type === "StringLiteral" || + node.type === "Identifier" || + node.type === "ThisExpression" || + node.type === "Super" || + node.type === "BigIntLiteral" || + node.type === "PrivateName" || + node.type === "ArgumentPlaceholder" || + node.type === "RegExpLiteral" || + node.type === "Import" + ) { + return true; + } + if (node.type === "TemplateLiteral") { + return node.expressions.every(isChildSimple); + } + if (node.type === "ObjectExpression") { + return node.properties.every( + p => + p.shorthand || + (isSimpleCallArgument(p.key, depth) && isChildSimple(p.value)) + ); + } + if (node.type === "ArrayExpression") { + return node.elements.every(isChildSimple); + } + if ( + node.type === "CallExpression" || + node.type === "OptionalCallExpression" || + node.type === "NewExpression" + ) { + return ( + isSimpleCallArgument(node.callee, depth) && + node.arguments.every(isChildSimple) + ); + } + if ( + node.type === "MemberExpression" || + node.type === "OptionalMemberExpression" + ) { + return ( + isSimpleCallArgument(node.object, depth) && + isSimpleCallArgument(node.property, depth) + ); + } + if ( + node.type === "UnaryExpression" && + (node.operator === "!" || node.operator === "-") + ) { + return isSimpleCallArgument(node.argument, depth); + } + if (node.type === "TSNonNullExpression") { + return isSimpleCallArgument(node.expression, depth); + } + return false; +} + function rawText(node) { return node.extra ? node.extra.raw : node.raw; } @@ -930,6 +1010,7 @@ module.exports = { isLastStatement, isLiteral, isLongCurriedCallExpression, + isSimpleCallArgument, isMeaningfulJSXText, isMemberExpressionChain, isMemberish, diff --git a/tests/flow/node_tests/crypto/__snapshots__/jsfmt.spec.js.snap b/tests/flow/node_tests/crypto/__snapshots__/jsfmt.spec.js.snap index ad81f6583..401323dec 100644 --- a/tests/flow/node_tests/crypto/__snapshots__/jsfmt.spec.js.snap +++ b/tests/flow/node_tests/crypto/__snapshots__/jsfmt.spec.js.snap @@ -76,10 +76,7 @@ let tests = [ hmac.update(buf, "utf8"); // 1 error: no encoding when passing a buffer // it's also chainable - (hmac - .update("some data to hash") - .update(buf) - .digest(): Buffer); + (hmac.update("some data to hash").update(buf).digest(): Buffer); (hmac.digest("hex"): string); (hmac.digest(): Buffer); diff --git a/tests/method-chain/__snapshots__/jsfmt.spec.js.snap b/tests/method-chain/__snapshots__/jsfmt.spec.js.snap index fa915e143..94378d98e 100644 --- a/tests/method-chain/__snapshots__/jsfmt.spec.js.snap +++ b/tests/method-chain/__snapshots__/jsfmt.spec.js.snap @@ -239,14 +239,9 @@ foo().bar().baz(); foo().bar.baz(); =====================================output===================================== -object - .foo() - .bar() - .baz(); +object.foo().bar().baz(); -foo() - .bar() - .baz(); +foo().bar().baz(); foo().bar.baz(); @@ -510,10 +505,7 @@ object[valid (a ? b : c).d().e(); -(a ? b : c) - .d() - .e() - .f(); +(a ? b : c).d().e().f(); (valid ? helper.responseBody(this.currentUser) @@ -571,9 +563,7 @@ d3.select("body") .at({ width: 30, fill: "#f0f" }) .st({ fontWeight: 600 }); -d3.scaleLinear() - .domain([1950, 1980]) - .range([0, width]); +d3.scaleLinear().domain([1950, 1980]).range([0, width]); not.d3 .select("body") @@ -581,10 +571,7 @@ not.d3 .at({ width: 30, fill: "#f0f" }) .st({ fontWeight: 600 }); -not.d3 - .scaleLinear() - .domain([1950, 1980]) - .range([0, width]); +not.d3.scaleLinear().domain([1950, 1980]).range([0, width]); ================================================================================ `; @@ -708,9 +695,335 @@ this.layoutPartsToHide = this.utils.hashset( .filter(Boolean) ); -var jqxhr = $.ajax("example.php") - .done(doneFn) - .fail(failFn); +var jqxhr = $.ajax("example.php").done(doneFn).fail(failFn); + +================================================================================ +`; + +exports[`issue-4125.js 1`] = ` +====================================options===================================== +parsers: ["flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +// examples from https://github.com/prettier/prettier/issues/4125 + +const sha256 = (data) => crypto.createHash("sha256").update(data).digest("hex"); + +req.checkBody('id').isInt().optional(); +req.checkBody('name').notEmpty().optional(); + +const x = moment().add(1, 'day').valueOf() + +// should stay on one line: +obj.foo(1).foo(2).foo(3); +obj.foo(-1).foo(import('2')).foo(!x).check(/[A-Z]/); + +// better on multiple lines: +somePromise.then(format).then((val)=>doSomething(val)).catch((err)=>handleError(err)) + +// you can still force multi-line chaining with a comment: +const sha256 = (data) => + crypto // breakme + .createHash("sha256") + .update(data) + .digest("hex"); + +// examples from https://github.com/prettier/prettier/pull/4765 + +if ($(el).attr("href").includes("/wiki/")) { +} + +if ($(el).attr("href").includes("/wiki/")) { + if ($(el).attr("xyz").includes("/whatever/")) { + if ($(el).attr("hello").includes("/world/")) { + } + } +} + +const parseNumbers = s => s.split('').map(Number).sort() + +function palindrome(a, b) { + return a.slice().reverse().join(',') === b.slice().sort().join(','); +} + +// examples from https://github.com/prettier/prettier/issues/1565 + +d3.select("body").selectAll("p").data([1, 2, 3]).enter().style("color", "white"); + +Object.keys(props).filter(key => key in own === false).reduce((a, key) => { + a[key] = props[key]; + return a; +}, {}) + +point().x(4).y(3).z(6).plot(); + +assert.equal(this.$().text().trim(), '1000'); + +something().then(() => doSomethingElse()).then(result => dontForgetThisAsWell(result)) + +db.branch( + db.table('users').filter({ email }).count(), + db.table('users').filter({ email: 'a@b.com' }).count(), + db.table('users').insert({ email }), + db.table('users').filter({ email }), +) + +sandbox.stub(config, 'get').withArgs('env').returns('dev') + +moment.utc(userInput).hour(0).minute(0).second(0) + +fetchUser(id) + .then(fetchAccountForUser) + .catch(handleFetchError) + +fetchUser(id) // + .then(fetchAccountForUser) + .catch(handleFetchError) + +// examples from https://github.com/prettier/prettier/issues/3107 + +function HelloWorld() { + window.FooClient.setVars({ + locale: getFooLocale({ page }), + authorizationToken: data.token, + }).initVerify('foo_container'); + + fejax.ajax({ + url: '/verification/', + dataType: 'json', + }).then( + (data) => { + this.setState({ isLoading: false }); + this.initWidget(data); + }, + (data) => { + this.logImpression('foo_fetch_error', data); + Flash.error(I18n.t('offline_identity.foo_issue')); + }, + ); +} + +action$.ofType(ActionTypes.SEARCHED_USERS) + .map(action => action.payload.query) + .filter(q => !!q) + .switchMap(q => + Observable.timer(800) // debounce + .takeUntil(action$.ofType(ActionTypes.CLEARED_SEARCH_RESULTS)) + .mergeMap(() => + Observable.merge( + Observable.of(replace(\`?q=\${q}\`)), + ajax + .getJSON(\`https://api.github.com/search/users?q=\${q}\`) + .map(res => res.items) + .map(receiveUsers) + ) + ) + ); + +window.FooClient + .setVars({ + locale: getFooLocale({ page }), + authorizationToken: data.token, + }) + .initVerify('foo_container'); + +it('gets triggered by mouseenter', () => { + const wrapper = shallow(); + wrapper.dive().find(Button).prop(); +}); + +x.a(true).b(null).c(123) +x.d('').e(\`\`).f(g) +x.d('').e(\`\${123}\`).f(g) +x.h(i.j).k(l()).m([n, o]) +class X { + y() { + x.a(this).b(super.cde()).f(/g/).h(new i()).j() + } +} + +// should break when call expressions get complex +x.a().b([c, [d, [e]]]).f() +x.a().b(c(d(e()))).f() +x.a().b(\`\${c(d())}\`).f() + +xyz.a().b().c(a(a(b(c(d().p).p).p).p)) + +var l = base + .replace(/^\\w*:\\/\\//, '') + .replace(/\\/$/, '') + .split('/').length + + +=====================================output===================================== +// examples from https://github.com/prettier/prettier/issues/4125 + +const sha256 = data => crypto.createHash("sha256").update(data).digest("hex"); + +req.checkBody("id").isInt().optional(); +req.checkBody("name").notEmpty().optional(); + +const x = moment().add(1, "day").valueOf(); + +// should stay on one line: +obj.foo(1).foo(2).foo(3); +obj.foo(-1).foo(import("2")).foo(!x).check(/[A-Z]/); + +// better on multiple lines: +somePromise + .then(format) + .then(val => doSomething(val)) + .catch(err => handleError(err)); + +// you can still force multi-line chaining with a comment: +const sha256 = data => + crypto // breakme + .createHash("sha256") + .update(data) + .digest("hex"); + +// examples from https://github.com/prettier/prettier/pull/4765 + +if ($(el).attr("href").includes("/wiki/")) { +} + +if ($(el).attr("href").includes("/wiki/")) { + if ($(el).attr("xyz").includes("/whatever/")) { + if ($(el).attr("hello").includes("/world/")) { + } + } +} + +const parseNumbers = s => s.split("").map(Number).sort(); + +function palindrome(a, b) { + return a.slice().reverse().join(",") === b.slice().sort().join(","); +} + +// examples from https://github.com/prettier/prettier/issues/1565 + +d3.select("body") + .selectAll("p") + .data([1, 2, 3]) + .enter() + .style("color", "white"); + +Object.keys(props) + .filter(key => key in own === false) + .reduce((a, key) => { + a[key] = props[key]; + return a; + }, {}); + +point().x(4).y(3).z(6).plot(); + +assert.equal(this.$().text().trim(), "1000"); + +something() + .then(() => doSomethingElse()) + .then(result => dontForgetThisAsWell(result)); + +db.branch( + db.table("users").filter({ email }).count(), + db.table("users").filter({ email: "a@b.com" }).count(), + db.table("users").insert({ email }), + db.table("users").filter({ email }) +); + +sandbox.stub(config, "get").withArgs("env").returns("dev"); + +moment.utc(userInput).hour(0).minute(0).second(0); + +fetchUser(id).then(fetchAccountForUser).catch(handleFetchError); + +fetchUser(id) // + .then(fetchAccountForUser) + .catch(handleFetchError); + +// examples from https://github.com/prettier/prettier/issues/3107 + +function HelloWorld() { + window.FooClient.setVars({ + locale: getFooLocale({ page }), + authorizationToken: data.token, + }).initVerify("foo_container"); + + fejax + .ajax({ + url: "/verification/", + dataType: "json", + }) + .then( + data => { + this.setState({ isLoading: false }); + this.initWidget(data); + }, + data => { + this.logImpression("foo_fetch_error", data); + Flash.error(I18n.t("offline_identity.foo_issue")); + } + ); +} + +action$ + .ofType(ActionTypes.SEARCHED_USERS) + .map(action => action.payload.query) + .filter(q => !!q) + .switchMap(q => + Observable.timer(800) // debounce + .takeUntil(action$.ofType(ActionTypes.CLEARED_SEARCH_RESULTS)) + .mergeMap(() => + Observable.merge( + Observable.of(replace(\`?q=\${q}\`)), + ajax + .getJSON(\`https://api.github.com/search/users?q=\${q}\`) + .map(res => res.items) + .map(receiveUsers) + ) + ) + ); + +window.FooClient.setVars({ + locale: getFooLocale({ page }), + authorizationToken: data.token, +}).initVerify("foo_container"); + +it("gets triggered by mouseenter", () => { + const wrapper = shallow(); + wrapper.dive().find(Button).prop(); +}); + +x.a(true).b(null).c(123); +x.d("").e(\`\`).f(g); +x.d("").e(\`\${123}\`).f(g); +x.h(i.j).k(l()).m([n, o]); +class X { + y() { + x.a(this).b(super.cde()).f(/g/).h(new i()).j(); + } +} + +// should break when call expressions get complex +x.a() + .b([c, [d, [e]]]) + .f(); +x.a() + .b(c(d(e()))) + .f(); +x.a() + .b(\`\${c(d())}\`) + .f(); + +xyz + .a() + .b() + .c(a(a(b(c(d().p).p).p).p)); + +var l = base + .replace(/^\\w*:\\/\\//, "") + .replace(/\\/$/, "") + .split("/").length; ================================================================================ `; @@ -813,16 +1126,14 @@ if (testConfig.ENABLE_ONLINE_TESTS === "true") { describe("POST /users/me/pet", function() { it("saves pet", function() { function assert(pet) { - expect(pet) - .to.have.property("OwnerAddress") - .that.deep.equals({ - AddressLine1: "Alexanderstrasse", - AddressLine2: "", - PostalCode: "10999", - Region: "Berlin", - City: "Berlin", - Country: "DE", - }); + expect(pet).to.have.property("OwnerAddress").that.deep.equals({ + AddressLine1: "Alexanderstrasse", + AddressLine2: "", + PostalCode: "10999", + Region: "Berlin", + City: "Berlin", + Country: "DE", + }); } }); }); diff --git a/tests/method-chain/issue-4125.js b/tests/method-chain/issue-4125.js new file mode 100644 index 000000000..0f755ba12 --- /dev/null +++ b/tests/method-chain/issue-4125.js @@ -0,0 +1,149 @@ +// examples from https://github.com/prettier/prettier/issues/4125 + +const sha256 = (data) => crypto.createHash("sha256").update(data).digest("hex"); + +req.checkBody('id').isInt().optional(); +req.checkBody('name').notEmpty().optional(); + +const x = moment().add(1, 'day').valueOf() + +// should stay on one line: +obj.foo(1).foo(2).foo(3); +obj.foo(-1).foo(import('2')).foo(!x).check(/[A-Z]/); + +// better on multiple lines: +somePromise.then(format).then((val)=>doSomething(val)).catch((err)=>handleError(err)) + +// you can still force multi-line chaining with a comment: +const sha256 = (data) => + crypto // breakme + .createHash("sha256") + .update(data) + .digest("hex"); + +// examples from https://github.com/prettier/prettier/pull/4765 + +if ($(el).attr("href").includes("/wiki/")) { +} + +if ($(el).attr("href").includes("/wiki/")) { + if ($(el).attr("xyz").includes("/whatever/")) { + if ($(el).attr("hello").includes("/world/")) { + } + } +} + +const parseNumbers = s => s.split('').map(Number).sort() + +function palindrome(a, b) { + return a.slice().reverse().join(',') === b.slice().sort().join(','); +} + +// examples from https://github.com/prettier/prettier/issues/1565 + +d3.select("body").selectAll("p").data([1, 2, 3]).enter().style("color", "white"); + +Object.keys(props).filter(key => key in own === false).reduce((a, key) => { + a[key] = props[key]; + return a; +}, {}) + +point().x(4).y(3).z(6).plot(); + +assert.equal(this.$().text().trim(), '1000'); + +something().then(() => doSomethingElse()).then(result => dontForgetThisAsWell(result)) + +db.branch( + db.table('users').filter({ email }).count(), + db.table('users').filter({ email: 'a@b.com' }).count(), + db.table('users').insert({ email }), + db.table('users').filter({ email }), +) + +sandbox.stub(config, 'get').withArgs('env').returns('dev') + +moment.utc(userInput).hour(0).minute(0).second(0) + +fetchUser(id) + .then(fetchAccountForUser) + .catch(handleFetchError) + +fetchUser(id) // + .then(fetchAccountForUser) + .catch(handleFetchError) + +// examples from https://github.com/prettier/prettier/issues/3107 + +function HelloWorld() { + window.FooClient.setVars({ + locale: getFooLocale({ page }), + authorizationToken: data.token, + }).initVerify('foo_container'); + + fejax.ajax({ + url: '/verification/', + dataType: 'json', + }).then( + (data) => { + this.setState({ isLoading: false }); + this.initWidget(data); + }, + (data) => { + this.logImpression('foo_fetch_error', data); + Flash.error(I18n.t('offline_identity.foo_issue')); + }, + ); +} + +action$.ofType(ActionTypes.SEARCHED_USERS) + .map(action => action.payload.query) + .filter(q => !!q) + .switchMap(q => + Observable.timer(800) // debounce + .takeUntil(action$.ofType(ActionTypes.CLEARED_SEARCH_RESULTS)) + .mergeMap(() => + Observable.merge( + Observable.of(replace(`?q=${q}`)), + ajax + .getJSON(`https://api.github.com/search/users?q=${q}`) + .map(res => res.items) + .map(receiveUsers) + ) + ) + ); + +window.FooClient + .setVars({ + locale: getFooLocale({ page }), + authorizationToken: data.token, + }) + .initVerify('foo_container'); + +it('gets triggered by mouseenter', () => { + const wrapper = shallow(); + wrapper.dive().find(Button).prop(); +}); + +x.a(true).b(null).c(123) +x.d('').e(``).f(g) +x.d('').e(`${123}`).f(g) +x.h(i.j).k(l()).m([n, o]) +class X { + y() { + x.a(this).b(super.cde()).f(/g/).h(new i()).j() + } +} + +// should break when call expressions get complex +x.a().b([c, [d, [e]]]).f() +x.a().b(c(d(e()))).f() +x.a().b(`${c(d())}`).f() + +xyz.a().b().c(a(a(b(c(d().p).p).p).p)) + +var l = base + .replace(/^\w*:\/\//, '') + .replace(/\/$/, '') + .split('/').length + diff --git a/tests/test_declarations/__snapshots__/jsfmt.spec.js.snap b/tests/test_declarations/__snapshots__/jsfmt.spec.js.snap index 5fc1e5621..c3a88f597 100644 --- a/tests/test_declarations/__snapshots__/jsfmt.spec.js.snap +++ b/tests/test_declarations/__snapshots__/jsfmt.spec.js.snap @@ -46,21 +46,13 @@ beforeEach(async(() => { // code })); -beforeEach(done => - foo() - .bar() - .bar() -); +beforeEach(done => foo().bar().bar()); afterAll(async(() => { console.log("Hello"); })); -afterAll(done => - foo() - .bar() - .bar() -); +afterAll(done => foo().bar().bar()); it("should create the app", async(() => { //code @@ -129,21 +121,13 @@ beforeEach(async(() => { // code })); -beforeEach((done) => - foo() - .bar() - .bar() -); +beforeEach((done) => foo().bar().bar()); afterAll(async(() => { console.log("Hello"); })); -afterAll((done) => - foo() - .bar() - .bar() -); +afterAll((done) => foo().bar().bar()); it("should create the app", async(() => { //code @@ -477,7 +461,7 @@ describe.each\`a | b | expected // an example to demo multiline quasi describe.each\`a | b | expected -\${11111111111} | \${a().b().c().d()} | \${2} +\${11111111111} | \${a().b(x => x).c().d()} | \${2} \${1} | \${2} | \${3} \${2} | \${1} | \${3}\` @@ -545,7 +529,7 @@ describe.each\` describe.each\` a | b | expected \${11111111111} | \${a() - .b() + .b(x => x) .c() .d()} | \${2} \${1} | \${2} | \${3} @@ -622,7 +606,7 @@ describe.each\`a | b | expected // an example to demo multiline quasi describe.each\`a | b | expected -\${11111111111} | \${a().b().c().d()} | \${2} +\${11111111111} | \${a().b(x => x).c().d()} | \${2} \${1} | \${2} | \${3} \${2} | \${1} | \${3}\` @@ -690,7 +674,7 @@ describe.each\` describe.each\` a | b | expected \${11111111111} | \${a() - .b() + .b((x) => x) .c() .d()} | \${2} \${1} | \${2} | \${3} diff --git a/tests/test_declarations/jest-each.js b/tests/test_declarations/jest-each.js index 8bcdf7fb4..246b25bd9 100644 --- a/tests/test_declarations/jest-each.js +++ b/tests/test_declarations/jest-each.js @@ -38,7 +38,7 @@ ${2} | ${1} | ${3}` // an example to demo multiline quasi describe.each`a | b | expected -${11111111111} | ${a().b().c().d()} | ${2} +${11111111111} | ${a().b(x => x).c().d()} | ${2} ${1} | ${2} | ${3} ${2} | ${1} | ${3}` diff --git a/tests/typescript_non_null/__snapshots__/jsfmt.spec.js.snap b/tests/typescript_non_null/__snapshots__/jsfmt.spec.js.snap index b0fc7499a..0442353ca 100644 --- a/tests/typescript_non_null/__snapshots__/jsfmt.spec.js.snap +++ b/tests/typescript_non_null/__snapshots__/jsfmt.spec.js.snap @@ -25,15 +25,9 @@ const { anotherPropThatHasALongName2, } = this.props.imReallySureAboutThis!.anotherObject; -this.foo - .get("bar")! - .doThings() - .more(); +this.foo.get("bar")!.doThings().more(); -foo! - .bar() - .baz() - .what(); +foo!.bar().baz().what(); ================================================================================ `; @@ -68,6 +62,8 @@ new (x()\`\`!.y)(); new (x()!\`\`.y)(); new (x!()\`\`.y)(); +xyz.a(b!).a(b!).a(b!) + =====================================output===================================== (a ? b : c)![tokenKey]; (a || b)![tokenKey]; @@ -93,5 +89,7 @@ new (x()\`\`!.y)(); new (x()!\`\`.y)(); new (x!()\`\`.y)(); +xyz.a(b!).a(b!).a(b!); + ================================================================================ `; diff --git a/tests/typescript_non_null/parens.ts b/tests/typescript_non_null/parens.ts index 3f064920e..ef13c0346 100644 --- a/tests/typescript_non_null/parens.ts +++ b/tests/typescript_non_null/parens.ts @@ -21,3 +21,5 @@ new (x()``.y!)(); new (x()``!.y)(); new (x()!``.y)(); new (x!()``.y)(); + +xyz.a(b!).a(b!).a(b!)