From 29c3f97decdb5736174b3217ada25edab08b7662 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 21 Jan 2026 08:26:02 +0800 Subject: [PATCH] Keep `ChainExpression` AST unchanged (#18661) --- changelog_unreleased/typescript/18661.md | 19 +++ src/language-js/clean.js | 10 -- .../parentheses/chain-expression.js | 70 ++++++++ .../parentheses/needs-parentheses.js | 160 +----------------- src/language-js/print/member-chain.js | 19 +-- .../html/__snapshots__/format.test.js.snap | 2 +- .../__snapshots__/format.test.js.snap | 13 +- .../format/typescript/assignment/lone-arg.ts | 5 +- .../__snapshots__/format.test.js.snap | 82 ++++----- .../__snapshots__/format.test.js.snap | 10 +- .../typescript/non-null/optional-chain.ts | 4 +- .../__snapshots__/format.test.js.snap | 4 +- .../tsx/__snapshots__/format.test.js.snap | 2 +- ...al-chainning.tsx => optional-chaining.tsx} | 0 14 files changed, 163 insertions(+), 237 deletions(-) create mode 100644 changelog_unreleased/typescript/18661.md create mode 100644 src/language-js/parentheses/chain-expression.js rename tests/format/typescript/tsx/{optional-chainning.tsx => optional-chaining.tsx} (100%) diff --git a/changelog_unreleased/typescript/18661.md b/changelog_unreleased/typescript/18661.md new file mode 100644 index 000000000..a9429d175 --- /dev/null +++ b/changelog_unreleased/typescript/18661.md @@ -0,0 +1,19 @@ +#### Preserve non-null assertion around optional chaining (#18661 by @fisker) + + +```tsx +// Input +(a?.b!).c; +(a?.b)!.c; +(a?.b!)!.c; + +// Prettier stable +(a?.b)!.c; +(a?.b)!.c; +a?.b!!.c; + +// Prettier main +(a?.b!).c; +(a?.b)!.c; +(a?.b!)!.c; +``` diff --git a/src/language-js/clean.js b/src/language-js/clean.js index 5e64738ed..65dcb73e2 100644 --- a/src/language-js/clean.js +++ b/src/language-js/clean.js @@ -176,16 +176,6 @@ function clean(original, cloned, parent) { if (original.type === "TemplateLiteral") { removeTemplateElementsValue(cloned); } - - // We print `(a?.b!).c` as `(a?.b)!.c`, but `typescript` parse them differently - if ( - original.type === "ChainExpression" && - original.expression.type === "TSNonNullExpression" - ) { - // Ideally, we should swap these two nodes, but `type` is the only difference - cloned.type = "TSNonNullExpression"; - cloned.expression.type = "ChainExpression"; - } } clean.ignoredProperties = ignoredProperties; diff --git a/src/language-js/parentheses/chain-expression.js b/src/language-js/parentheses/chain-expression.js new file mode 100644 index 000000000..a1828a68f --- /dev/null +++ b/src/language-js/parentheses/chain-expression.js @@ -0,0 +1,70 @@ +import { createTypeCheckFunction } from "../utilities/index.js"; + +/** +@import AstPath from "../../common/ast-path.js" +*/ + +function shouldAddParenthesesToChainExpression(path) { + const { key, parent } = path; + + return ( + (key === "expression" && parent.type === "TSNonNullExpression") || + (key === "object" && + parent.type === "MemberExpression" && + !parent.optional) || + (key === "callee" && + parent.type === "CallExpression" && + !parent.optional) || + (key === "callee" && parent.type === "NewExpression") || + (key === "tag" && parent.type === "TaggedTemplateExpression") + ); +} + +const isParenthesized = (node) => node.extra?.parenthesized; +const isBabelOptionalChainElement = createTypeCheckFunction([ + "OptionalCallExpression", + "OptionalMemberExpression", +]); + +/** +@param {AstPath} path +*/ +function isBabelOptionalChainRoot(path) { + const { node } = path; + + let child = node; + while (child.type === "TSNonNullExpression") { + child = child.expression; + + if (isParenthesized(child)) { + return false; + } + } + + if (!isBabelOptionalChainElement(child)) { + return false; + } + + if (isParenthesized(node)) { + return true; + } + + return !( + (path.key === "expression" && path.parent.type === "TSNonNullExpression") + // We should exclude if it's in `OptionalCallExpression` or `OptionalMemberExpression` + // But in these cases it should not matter, since we don't need parentheses anyway + ); +} + +/** +@param {AstPath} path +@returns {boolean} +*/ +function shouldAddParenthesesToChainElement(path) { + return ( + (path.node.type === "ChainExpression" || isBabelOptionalChainRoot(path)) && + shouldAddParenthesesToChainExpression(path) + ); +} + +export { shouldAddParenthesesToChainElement }; diff --git a/src/language-js/parentheses/needs-parentheses.js b/src/language-js/parentheses/needs-parentheses.js index 6b7d8118f..4e42d44b5 100644 --- a/src/language-js/parentheses/needs-parentheses.js +++ b/src/language-js/parentheses/needs-parentheses.js @@ -26,6 +26,7 @@ import { stripChainElementWrappers, } from "../utilities/index.js"; import { returnArgumentHasLeadingComment } from "../utilities/return-statement-has-leading-comment.js"; +import { shouldAddParenthesesToChainElement } from "./chain-expression.js"; import { shouldAddParenthesesToIdentifier } from "./identifier.js"; /** @@ -829,15 +830,16 @@ function needsParentheses(path, options) { } case "OptionalMemberExpression": case "OptionalCallExpression": - case "CallExpression": - case "MemberExpression": + case "ChainExpression": + case "TSNonNullExpression": if (shouldAddParenthesesToChainElement(path)) { return true; } // fallthrough + case "CallExpression": + case "MemberExpression": case "TaggedTemplateExpression": - case "TSNonNullExpression": if ( key === "callee" && (parent.type === "BindExpression" || parent.type === "NewExpression") @@ -1086,158 +1088,6 @@ function shouldWrapFunctionForExportDefault(path, options) { ); } -/* -Matches following cases: - -```js -(a?.b).c; -(a?.()).b; -(a?.b!).c; -(a?.()!).b; -(a?.b)!.c; -(a?.())!.b; - -(a?.b)(); -(a?.())(); - -new (a?.b)(); -new (a?.())(); - -(a?.b)``; -(a?.())``; -``` -*/ -/** - * @param {AstPath} path - * @returns {boolean} - */ -function shouldAddParenthesesToChainElement(path) { - if ( - // ESTree - path.match( - undefined, - (node, name) => name === "expression" && node.type === "ChainExpression", - (node, name) => - name === "tag" && node.type === "TaggedTemplateExpression", - ) || - // Babel - path.match( - (node) => - node.type === "OptionalCallExpression" || - node.type === "OptionalMemberExpression", - (node, name) => - name === "tag" && node.type === "TaggedTemplateExpression", - ) || - // Babel-ts - // (a?.b)!``; - // (a?.b!)``; - path.match( - (node) => - node.type === "OptionalCallExpression" || - node.type === "OptionalMemberExpression", - (node, name) => - name === "expression" && node.type === "TSNonNullExpression", - (node, name) => - name === "tag" && node.type === "TaggedTemplateExpression", - ) || - // case (a?.b)!``; in Typescript - path.match( - undefined, - (node, name) => name === "expression" && node.type === "ChainExpression", - (node, name) => - name === "expression" && node.type === "TSNonNullExpression", - (node, name) => - name === "tag" && node.type === "TaggedTemplateExpression", - ) || - // case (a?.b!)``; in Typescript - path.match( - undefined, - (node, name) => - name === "expression" && node.type === "TSNonNullExpression", - (node, name) => name === "expression" && node.type === "ChainExpression", - (node, name) => - name === "tag" && node.type === "TaggedTemplateExpression", - ) - ) { - return true; - } - - if ( - path.match( - (node) => - node.type === "OptionalMemberExpression" || - node.type === "OptionalCallExpression", - (node, name) => - (name === "object" && node.type === "MemberExpression") || - (name === "callee" && - (node.type === "CallExpression" || node.type === "NewExpression")), - ) || - path.match( - (node) => - node.type === "OptionalMemberExpression" || - node.type === "OptionalCallExpression", - (node, name) => - name === "expression" && node.type === "TSNonNullExpression", - (node, name) => - (name === "object" && node.type === "MemberExpression") || - (name === "callee" && node.type === "CallExpression"), - ) - ) { - return true; - } - - // ESTree, same logic as babel - if ( - path.match( - (node) => - node.type === "CallExpression" || node.type === "MemberExpression", - (node, name) => name === "expression" && node.type === "ChainExpression", - ) && - (path.match( - undefined, - undefined, - (node, name) => - (name === "callee" && - ((node.type === "CallExpression" && !node.optional) || - node.type === "NewExpression")) || - (name === "object" && - node.type === "MemberExpression" && - !node.optional), - ) || - path.match( - undefined, - undefined, - (node, name) => - name === "expression" && node.type === "TSNonNullExpression", - (node, name) => - (name === "object" && node.type === "MemberExpression") || - (name === "callee" && node.type === "CallExpression"), - )) - ) { - return true; - } - - // Babel treat `(a?.b!).c` and `(a?.b)!.c` the same, https://github.com/babel/babel/discussions/15077 - // Use this to align with babel - if ( - path.match( - (node) => - node.type === "CallExpression" || node.type === "MemberExpression", - (node, name) => - name === "expression" && node.type === "TSNonNullExpression", - (node, name) => name === "expression" && node.type === "ChainExpression", - (node, name) => - (name === "object" && node.type === "MemberExpression") || - (name === "callee" && node.type === "CallExpression"), - ) - ) { - return true; - } - - // This function only handle cases above - return false; -} - function isDecoratorMemberExpression(node) { if (node.type === "Identifier") { return true; diff --git a/src/language-js/print/member-chain.js b/src/language-js/print/member-chain.js index 357e238f2..26b0d2eae 100644 --- a/src/language-js/print/member-chain.js +++ b/src/language-js/print/member-chain.js @@ -46,16 +46,12 @@ import { printOptionalToken } from "./miscellaneous.js"; // The way it is structured in the AST is via a nested sequence of // MemberExpression and CallExpression. We need to traverse the AST // and make groups out of it to print it in the desired way. +/* +- `BindExpression` +- `MemberExpression` +- `OptionalMemberExpression` +*/ function printMemberChain(path, options, print) { - // TODO[@fisker]: skip `TSNonNullExpression` - /* c8 ignore next 6 */ - if (path.node.type === "ChainExpression") { - return path.call( - () => printMemberChain(path, options, print), - "expression", - ); - } - const isExpressionStatement = (path.parent.type === "ChainExpression" ? path.grandparent : path.parent) .type === "ExpressionStatement"; @@ -95,10 +91,6 @@ function printMemberChain(path, options, print) { function rec() { const { node } = path; - if (node.type === "ChainExpression") { - return path.call(rec, "expression"); - } - if ( isCallExpression(node) && (isMemberish(node.callee) || isCallExpression(node.callee)) @@ -137,6 +129,7 @@ function printMemberChain(path, options, print) { } else if (node.type === "TSNonNullExpression") { printedNodes.unshift({ node, + needsParens: needsParentheses(path, options), printed: printComments(path, "!", options), }); path.call(rec, "expression"); diff --git a/tests/format/angular/interpolation/html/__snapshots__/format.test.js.snap b/tests/format/angular/interpolation/html/__snapshots__/format.test.js.snap index 9a1410857..9113ec15c 100644 --- a/tests/format/angular/interpolation/html/__snapshots__/format.test.js.snap +++ b/tests/format/angular/interpolation/html/__snapshots__/format.test.js.snap @@ -68,7 +68,7 @@ printWidth: 80 =====================================output===================================== {{ foo?.bar!.bam }} {{ (foo?.bar)!.bam }} -{{ (foo?.bar)!.bam }} +{{ (foo?.bar!).bam }} ================================================================================ `; diff --git a/tests/format/typescript/assignment/__snapshots__/format.test.js.snap b/tests/format/typescript/assignment/__snapshots__/format.test.js.snap index c9005f0de..58e546c34 100644 --- a/tests/format/typescript/assignment/__snapshots__/format.test.js.snap +++ b/tests/format/typescript/assignment/__snapshots__/format.test.js.snap @@ -506,9 +506,8 @@ if (true) { .type; const secondType3 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!) .type; - // TODO[@fisker]: Fix - // const secondType4 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!)! - // .type; + const secondType4 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!)! + .type; const secondType5 = sourceCode.getNodeByRangeIndex1234(second.range[0])!! .type; } @@ -525,10 +524,10 @@ if (true) { )).type; const secondType3 = (sourceCode?.getNodeByRangeIndex1234( second.range[0], - ))!.type; - // TODO[@fisker]: Fix - // const secondType4 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!)! - // .type; + )!).type; + const secondType4 = (sourceCode?.getNodeByRangeIndex1234( + second.range[0], + )!)!.type; const secondType5 = sourceCode.getNodeByRangeIndex1234( second.range[0], )!!.type; diff --git a/tests/format/typescript/assignment/lone-arg.ts b/tests/format/typescript/assignment/lone-arg.ts index 1cdd8628d..2a0b12278 100644 --- a/tests/format/typescript/assignment/lone-arg.ts +++ b/tests/format/typescript/assignment/lone-arg.ts @@ -6,9 +6,8 @@ if (true) { .type; const secondType3 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!) .type; - // TODO[@fisker]: Fix - // const secondType4 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!)! - // .type; + const secondType4 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!)! + .type; const secondType5 = sourceCode.getNodeByRangeIndex1234(second.range[0])!! .type; } diff --git a/tests/format/typescript/chain-expression/__snapshots__/format.test.js.snap b/tests/format/typescript/chain-expression/__snapshots__/format.test.js.snap index 72df887c2..196513c4d 100644 --- a/tests/format/typescript/chain-expression/__snapshots__/format.test.js.snap +++ b/tests/format/typescript/chain-expression/__snapshots__/format.test.js.snap @@ -74,14 +74,14 @@ foo((a?.b)!) =====================================output===================================== // Member expressions (a?.b)!(); -(a?.b)!(); +(a?.b!)(); (a!?.b)(); (a.b?.c)!(); -(a.b?.c)!(); +(a.b?.c!)(); (a.b!?.c)(); (a!.b?.c)(); (a?.b.c)!(); -(a?.b.c)!(); +(a?.b.c!)(); (a?.b!.c)(); (a!?.b.c)(); a[b?.c]!(); @@ -105,21 +105,21 @@ a![b?.()](); (a?.()).b!(); (a?.()).b!(); (a?.())!.b(); -(a?.())!.b(); +(a?.()!).b(); (a!?.()).b(); // Call expressions (a?.())!(); -(a?.())!(); +(a?.()!)(); (a!?.())(); (a.b.c?.())!(); -(a.b.c?.())!(); +(a.b.c?.()!)(); (a.b.c!?.())(); (a.b?.c())!(); -(a.b?.c())!(); +(a.b?.c()!)(); (a.b!?.c())(); (a?.b.c())!(); -(a?.b.c())!(); +(a?.b.c()!)(); (a?.b!.c())(); a(b?.c)!(); a(b?.c)!(); @@ -127,15 +127,15 @@ a(b?.c!)(); (a?.b)()!(); (a?.b)()!(); (a?.b)!()(); -(a?.b)!()(); +(a?.b!)()(); (a?.())()!(); (a?.())()!(); (a?.())!()(); -(a?.())!()(); +(a?.()!)()(); (a!?.())()(); // Not \`.callee\` -foo(a?.b!); +foo((a?.b)!); ================================================================================ `; @@ -285,14 +285,14 @@ _[a?.b!] =====================================output===================================== // Member expressions (a?.b)!.foo; -(a?.b)!.foo; +(a?.b!).foo; (a!?.b).foo; (a.b?.c)!.foo; -(a.b?.c)!.foo; +(a.b?.c!).foo; (a.b!?.c).foo; (a!.b?.c).foo; (a?.b.c)!.foo; -(a?.b.c)!.foo; +(a?.b.c!).foo; (a?.b!.c).foo; (a!?.b.c).foo; a[b?.c]!.foo; @@ -301,7 +301,7 @@ a[b?.c!].foo; a[b!?.c].foo; (a?.b).c!.foo; (a?.b).c!.foo; -(a?.b)!.c.foo; +(a?.b!).c.foo; (a!?.b).c.foo; a[b?.()]!.foo; a[b?.()]!.foo; @@ -311,26 +311,26 @@ a![b?.()].foo; (a?.b).c!.foo; (a?.b).c!.foo; (a?.b)!.c.foo; -(a?.b)!.c.foo; +(a?.b!).c.foo; (a!?.b).c.foo; (a?.()).b!.foo; (a?.()).b!.foo; (a?.())!.b.foo; -(a?.())!.b.foo; +(a?.()!).b.foo; (a!?.()).b.foo; // Call expressions (a?.())!.foo; -(a?.())!.foo; +(a?.()!).foo; (a!?.()).foo; (a.b.c?.())!.foo; -(a.b.c?.())!.foo; +(a.b.c?.()!).foo; (a.b.c!?.()).foo; (a.b?.c())!.foo; -(a.b?.c())!.foo; +(a.b?.c()!).foo; (a.b!?.c()).foo; (a?.b.c())!.foo; -(a?.b.c())!.foo; +(a?.b.c()!).foo; (a?.b!.c()).foo; a(b?.c)!.foo; a(b?.c)!.foo; @@ -338,11 +338,11 @@ a(b?.c!).foo; (a?.b)()!.foo; (a?.b)()!.foo; (a?.b)!().foo; -(a?.b)!().foo; +(a?.b!)().foo; (a?.())()!.foo; (a?.())()!.foo; (a?.())!().foo; -(a?.())!().foo; +(a?.()!)().foo; (a!?.())().foo; // Not \`.object\` @@ -444,7 +444,7 @@ new a[b?.c!](); new a[b!?.c](); new (a?.b).c!(); new (a?.b).c!(); -new (a?.b)!.c(); +new (a?.b!).c(); new (a!?.b).c(); new a[b?.()]!(); new a[b?.()]!(); @@ -454,7 +454,7 @@ new a![b?.()](); new (a?.b).c!(); new (a?.b).c!(); new (a?.b)!.c(); -new (a?.b)!.c(); +new (a?.b!).c(); new (a!?.b).c(); // FIXME: new ((a?.()).b)! (); // FIXME: new ((a?.()).b!) (); @@ -481,11 +481,11 @@ new (a(b?.c!))(); new ((a?.b)()!)(); new ((a?.b)()!)(); new ((a?.b)!())(); -new ((a?.b)!())(); +new ((a?.b!)())(); new ((a?.())()!)(); new ((a?.())()!)(); new ((a?.())!())(); -new ((a?.())!())(); +new ((a?.()!)())(); new ((a!?.())())(); // Not \`.callee\` @@ -565,14 +565,14 @@ printWidth: 80 =====================================output===================================== // Member expressions (a?.b)!\`\`; -(a?.b)!\`\`; +(a?.b!)\`\`; (a!?.b)\`\`; (a.b?.c)!\`\`; -(a.b?.c)!\`\`; +(a.b?.c!)\`\`; (a.b!?.c)\`\`; (a!.b?.c)\`\`; (a?.b.c)!\`\`; -(a?.b.c)!\`\`; +(a?.b.c!)\`\`; (a?.b!.c)\`\`; (a!?.b.c)\`\`; a[b?.c]!\`\`; @@ -581,7 +581,7 @@ a[b?.c!]\`\`; a[b!?.c]\`\`; (a?.b).c!\`\`; (a?.b).c!\`\`; -(a?.b)!.c\`\`; +(a?.b!).c\`\`; (a!?.b).c\`\`; a[b?.()]!\`\`; a[b?.()]!\`\`; @@ -591,26 +591,26 @@ a![b?.()]\`\`; (a?.b).c!\`\`; (a?.b).c!\`\`; (a?.b)!.c\`\`; -(a?.b)!.c\`\`; +(a?.b!).c\`\`; (a!?.b).c\`\`; (a?.()).b!\`\`; (a?.()).b!\`\`; (a?.())!.b\`\`; -(a?.())!.b\`\`; +(a?.()!).b\`\`; (a!?.()).b\`\`; // Call expressions (a?.())!\`\`; -(a?.())!\`\`; +(a?.()!)\`\`; (a!?.())\`\`; (a.b.c?.())!\`\`; -(a.b.c?.())!\`\`; +(a.b.c?.()!)\`\`; (a.b.c!?.())\`\`; (a.b?.c())!\`\`; -(a.b?.c())!\`\`; +(a.b?.c()!)\`\`; (a.b!?.c())\`\`; (a?.b.c())!\`\`; -(a?.b.c())!\`\`; +(a?.b.c()!)\`\`; (a?.b!.c())\`\`; a(b?.c)!\`\`; a(b?.c)!\`\`; @@ -618,11 +618,11 @@ a(b?.c!)\`\`; (a?.b)()!\`\`; (a?.b)()!\`\`; (a?.b)!()\`\`; -(a?.b)!()\`\`; +(a?.b!)()\`\`; (a?.())()!\`\`; (a?.())()!\`\`; (a?.())!()\`\`; -(a?.())!()\`\`; +(a?.()!)()\`\`; (a!?.())()\`\`; ================================================================================ @@ -640,8 +640,8 @@ printWidth: 80 (a?.())!.b; =====================================output===================================== -(a?.b)!.c; -(a?.())!.b; +(a?.b!).c; +(a?.()!).b; (a?.b)!.c; (a?.())!.b; diff --git a/tests/format/typescript/non-null/__snapshots__/format.test.js.snap b/tests/format/typescript/non-null/__snapshots__/format.test.js.snap index 69f69d452..2a8a1f36a 100644 --- a/tests/format/typescript/non-null/__snapshots__/format.test.js.snap +++ b/tests/format/typescript/non-null/__snapshots__/format.test.js.snap @@ -105,7 +105,9 @@ a?.().b!.c?.c; (a?.().b!).c; (a?.().b)!.c; -(a?.b)![c?.d!] +(a?.b)![c?.d!]; + +(a?.b!!)!!.c; =====================================output===================================== a?.b!.c; @@ -114,7 +116,7 @@ a?.b.c!.d; a!.b?.c; a?.b!?.c; a?.b!.c?.c; -(a?.b)!.c; +(a?.b!).c; (a?.b)!.c; a?.().b!.c; @@ -122,11 +124,13 @@ a?.().b!.c.d; a?.().b.c!.d; a?.().b!?.c; a?.().b!.c?.c; -(a?.().b)!.c; +(a?.().b!).c; (a?.().b)!.c; (a?.b)![c?.d!]; +(a?.b!!)!!.c; + ================================================================================ `; diff --git a/tests/format/typescript/non-null/optional-chain.ts b/tests/format/typescript/non-null/optional-chain.ts index d344ab39a..a1bb90fd5 100644 --- a/tests/format/typescript/non-null/optional-chain.ts +++ b/tests/format/typescript/non-null/optional-chain.ts @@ -15,4 +15,6 @@ a?.().b!.c?.c; (a?.().b!).c; (a?.().b)!.c; -(a?.b)![c?.d!] +(a?.b)![c?.d!]; + +(a?.b!!)!!.c; diff --git a/tests/format/typescript/template-literals/__snapshots__/format.test.js.snap b/tests/format/typescript/template-literals/__snapshots__/format.test.js.snap index 98c9c78e2..997832df9 100644 --- a/tests/format/typescript/template-literals/__snapshots__/format.test.js.snap +++ b/tests/format/typescript/template-literals/__snapshots__/format.test.js.snap @@ -78,8 +78,8 @@ long1 = \`long \${ } long longlong \${a.b.c.d.e} long longlong \${a.b.c.d.e} long longlong \${a.b.c.d.e} long long\`; long1 = \`long \${ - a //comment - ?.b! + (a //comment + ?.b)! } long longlong \${a.b.c.d.e} long longlong \${a.b.c.d.e} long longlong \${a.b.c.d.e} long long\`; ================================================================================ diff --git a/tests/format/typescript/tsx/__snapshots__/format.test.js.snap b/tests/format/typescript/tsx/__snapshots__/format.test.js.snap index 9f4af6c71..a926692a2 100644 --- a/tests/format/typescript/tsx/__snapshots__/format.test.js.snap +++ b/tests/format/typescript/tsx/__snapshots__/format.test.js.snap @@ -126,7 +126,7 @@ const footer = ""; ================================================================================ `; -exports[`optional-chainning.tsx format 1`] = ` +exports[`optional-chaining.tsx format 1`] = ` ====================================options===================================== parsers: ["typescript"] printWidth: 80 diff --git a/tests/format/typescript/tsx/optional-chainning.tsx b/tests/format/typescript/tsx/optional-chaining.tsx similarity index 100% rename from tests/format/typescript/tsx/optional-chainning.tsx rename to tests/format/typescript/tsx/optional-chaining.tsx