Review ChainExpression check (#18659)

This commit is contained in:
fisker Cheung authored and GitHub committed 2026-01-17 04:46:43 +08:00
1 parent 5ce41ed535
commit 22032d5aa4
15 files changed
+242 -49

No files matched your search

+2 -2
View File
@@ -1,4 +1,4 @@
#### Fix inconsistent formatting of class superClass (#18652 by @fisker)
#### Fix inconsistent formatting of class superClass (#18652, #18659 by @fisker)
<!-- prettier-ignore -->
```tsx
@@ -18,5 +18,5 @@ class ALongLongLongLongLongLongLongLongLongLongLongLongClassName extends (foo
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends foo.bar.Baz {}
class ALongLongLongLongLongLongLongLongLongLongLongLongClassName
extends (foo.bar.Baz!) {}
extends foo.bar.Baz! {}
```
@@ -22,6 +22,7 @@ import {
isUnionType,
shouldFlatten,
startsWithNoLookaheadToken,
stripChainElementWrappers,
} from "../utilities/index.js";
import { returnArgumentHasLeadingComment } from "../utilities/return-statement-has-leading-comment.js";
import { shouldAddParenthesesToIdentifier } from "./identifier.js";
@@ -111,25 +112,27 @@ function needsParentheses(path, options) {
case "ClassExpression":
// Add parens around the extends clause of a class. It is needed for almost
// all expressions.
if (
key === "superClass" &&
(node.type === "ArrowFunctionExpression" ||
node.type === "AssignmentExpression" ||
node.type === "AwaitExpression" ||
node.type === "BinaryExpression" ||
node.type === "ConditionalExpression" ||
node.type === "LogicalExpression" ||
node.type === "NewExpression" ||
node.type === "ObjectExpression" ||
node.type === "SequenceExpression" ||
node.type === "TaggedTemplateExpression" ||
node.type === "UnaryExpression" ||
node.type === "UpdateExpression" ||
node.type === "YieldExpression" ||
node.type === "TSNonNullExpression" ||
(node.type === "ClassExpression" && isNonEmptyArray(node.decorators)))
) {
return true;
if (key === "superClass") {
const superClass = stripChainElementWrappers(node);
if (
superClass.type === "ArrowFunctionExpression" ||
superClass.type === "AssignmentExpression" ||
superClass.type === "AwaitExpression" ||
superClass.type === "BinaryExpression" ||
superClass.type === "ConditionalExpression" ||
superClass.type === "LogicalExpression" ||
superClass.type === "NewExpression" ||
superClass.type === "ObjectExpression" ||
superClass.type === "SequenceExpression" ||
superClass.type === "TaggedTemplateExpression" ||
superClass.type === "UnaryExpression" ||
superClass.type === "UpdateExpression" ||
superClass.type === "YieldExpression" ||
(superClass.type === "ClassExpression" &&
isNonEmptyArray(superClass.decorators))
) {
return true;
}
}
break;
-1
View File
@@ -243,7 +243,6 @@ function printBinaryishExpressions(
const shouldInline = shouldInlineLogicalExpression(node);
const rightNodeToCheckComments =
// TODO[@fisker]: Use `stripChainElementWrappers`
node.right.type === "ChainExpression" ? node.right.expression : node.right;
const lineBeforeOperator =
(node.type === "NGPipeExpression" ||
+5 -11
View File
@@ -18,8 +18,8 @@ import {
CommentCheckFlags,
createTypeCheckFunction,
hasComment,
isChainElementWrapper,
isMemberExpression,
stripChainElementWrappers,
} from "../utilities/index.js";
import { printAssignment } from "./assignment.js";
import { printClassMemberDecorators } from "./decorators.js";
@@ -169,15 +169,6 @@ function hasMultipleHeritage(node) {
return count > 1;
}
function isMemberish(node) {
// TODO[@fisker]: Should use `stripChainElementWrappers` instead, since it can be in multiple wrappers
if (isChainElementWrapper(node)) {
return isMemberish(node.expression);
}
return isMemberExpression(node);
}
/**
@returns {boolean}
*/
@@ -197,7 +188,10 @@ function shouldPrintClassInGroupModeWithoutCache(path) {
return false;
}
return !node.superTypeArguments && isMemberish(node.superClass);
return (
!node.superTypeArguments &&
isMemberExpression(stripChainElementWrappers(node.superClass))
);
}
const heritage =
+2 -4
View File
@@ -34,6 +34,7 @@ import {
isJsxElement,
isObjectExpression,
isStringLiteral,
stripChainElementWrappers,
} from "../utilities/index.js";
/**
@@ -587,10 +588,7 @@ function printJsxExpressionContainer(path, options, print) {
(node.type === "AwaitExpression" &&
(shouldInline(node.argument, node) ||
node.argument.type === "JSXElement")) ||
isCallExpression(node) ||
// TODO[@fisker]: Use `stripChainElementWrappers`
(node.type === "ChainExpression" &&
isCallExpression(node.expression)) ||
isCallExpression(stripChainElementWrappers(node)) ||
node.type === "FunctionExpression" ||
node.type === "TemplateLiteral" ||
node.type === "TaggedTemplateExpression" ||
+4 -8
View File
@@ -11,16 +11,12 @@ import {
isChainElementWrapper,
isMemberExpression,
isNumericLiteral,
stripChainElementWrappers,
} from "../utilities/index.js";
import { printOptionalToken } from "./miscellaneous.js";
const isCallExpressionWithArguments = (node) => {
// TODO[@fisker]: Use `stripChainElementWrappers`
if (isChainElementWrapper(node)) {
node = node.expression;
}
return isCallExpression(node) && getCallArguments(node).length > 0;
};
const isCallExpressionWithArguments = (node) =>
isCallExpression(node) && getCallArguments(node).length > 0;
function shouldInlineNewExpressionCallee(path) {
let { node: child, ancestors } = path;
@@ -64,7 +60,7 @@ function printMemberExpression(path, options, print) {
!isMemberExpression(firstNonChainElementWrapperParent)) ||
((firstNonChainElementWrapperParent.type === "AssignmentExpression" ||
firstNonChainElementWrapperParent.type === "VariableDeclarator") &&
(isCallExpressionWithArguments(node.object) ||
(isCallExpressionWithArguments(stripChainElementWrappers(node.object)) ||
objectDoc.label?.memberChain));
return label(objectDoc.label, [
@@ -500,7 +500,16 @@ printWidth: 80
=====================================input======================================
if (true) {
if (condition) {
const secondType = sourceCode.getNodeByRangeIndex1234(second.range[0])!
const secondType1 = sourceCode.getNodeByRangeIndex1234(second.range[0])!
.type;
const secondType2 = (sourceCode?.getNodeByRangeIndex1234(second.range[0]))
.type;
const secondType3 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!)
.type;
// TODO[@fisker]: Fix
// const secondType4 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!)!
// .type;
const secondType5 = sourceCode.getNodeByRangeIndex1234(second.range[0])!!
.type;
}
}
@@ -508,9 +517,21 @@ if (true) {
=====================================output=====================================
if (true) {
if (condition) {
const secondType = sourceCode.getNodeByRangeIndex1234(
const secondType1 = sourceCode.getNodeByRangeIndex1234(
second.range[0],
)!.type;
const secondType2 = (sourceCode?.getNodeByRangeIndex1234(
second.range[0],
)).type;
const secondType3 = (sourceCode?.getNodeByRangeIndex1234(
second.range[0],
))!.type;
// TODO[@fisker]: Fix
// const secondType4 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!)!
// .type;
const secondType5 = sourceCode.getNodeByRangeIndex1234(
second.range[0],
)!!.type;
}
}
+10 -1
View File
@@ -1,6 +1,15 @@
if (true) {
if (condition) {
const secondType = sourceCode.getNodeByRangeIndex1234(second.range[0])!
const secondType1 = sourceCode.getNodeByRangeIndex1234(second.range[0])!
.type;
const secondType2 = (sourceCode?.getNodeByRangeIndex1234(second.range[0]))
.type;
const secondType3 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!)
.type;
// TODO[@fisker]: Fix
// const secondType4 = (sourceCode?.getNodeByRangeIndex1234(second.range[0])!)!
// .type;
const secondType5 = sourceCode.getNodeByRangeIndex1234(second.range[0])!!
.type;
}
}
@@ -0,0 +1,47 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`chain-expression.ts format 1`] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
| printWidth
=====================================input======================================
if (
true
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
&& flibble.blibble.blobble?.bloo
) {
doThings();
}
if (
true
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
&& flibble.blibble.blobble?.bloo!
) {
doThings();
}
=====================================output=====================================
if (
true &&
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
flibble.blibble.blobble?.bloo
) {
doThings();
}
if (
true &&
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
flibble.blibble.blobble?.bloo!
) {
doThings();
}
================================================================================
`;
@@ -0,0 +1,17 @@
if (
true
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
&& flibble.blibble.blobble?.bloo
) {
doThings();
}
if (
true
// This is a really complicated part of the condition, so we need a big ol'
// comment here to explain it.
&& flibble.blibble.blobble?.bloo!
) {
doThings();
}
@@ -0,0 +1 @@
runFormatTest(import.meta, ["typescript"]);
@@ -17,6 +17,10 @@ class A_long_long_long_long_long_long_long_long_name12
class A_long_long_long_long_long_long_long_long_name12
extends eslint.Rule.RuleModule! {}
class A_long_long_long_long_long_long_long_long_name12
extends eslint?.Rule.RuleModule! {}
class A_long_long_long_long_long_long_long_long_name12
extends eslint.Rule.RuleModule!! {}
interface A_long_long_long_long_long_long_long_long_name2
extends eslint.Rule.RuleModule {}
@@ -38,7 +42,11 @@ class A_long_long_long_long_long_long_long_long_name12
extends eslint.Rule?.RuleModule {}
class A_long_long_long_long_long_long_long_long_name12
extends (eslint.Rule.RuleModule!) {}
extends eslint.Rule.RuleModule! {}
class A_long_long_long_long_long_long_long_long_name12
extends eslint?.Rule.RuleModule! {}
class A_long_long_long_long_long_long_long_long_name12
extends eslint.Rule.RuleModule!! {}
interface A_long_long_long_long_long_long_long_long_name2
extends eslint.Rule.RuleModule {}
@@ -9,6 +9,10 @@ class A_long_long_long_long_long_long_long_long_name12
class A_long_long_long_long_long_long_long_long_name12
extends eslint.Rule.RuleModule! {}
class A_long_long_long_long_long_long_long_long_name12
extends eslint?.Rule.RuleModule! {}
class A_long_long_long_long_long_long_long_long_name12
extends eslint.Rule.RuleModule!! {}
interface A_long_long_long_long_long_long_long_long_name2
extends eslint.Rule.RuleModule {}
@@ -126,6 +126,74 @@ const footer = "<footer></footer>";
================================================================================
`;
exports[`optional-chainning.tsx format 1`] = `
====================================options=====================================
parsers: ["typescript"]
printWidth: 80
| printWidth
=====================================input======================================
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?.bbbbbbbbbbbbbbbbbbbbbbbbb.map(
(p, i) => <p key={i}>{p}</p>
)}
</div>
);
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?.bbbbbbbbbbbbbbbbbbbbbbbbb.map(
(p, i) => <p key={i}>{p}</p>
)!}
</div>
);
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbb.map(
(p, i) => <p key={i}>{p}</p>
)}
</div>
);
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbb.map(
(p, i) => <p key={i}>{p}</p>
)!}
</div>
);
=====================================output=====================================
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?.bbbbbbbbbbbbbbbbbbbbbbbbb.map((p, i) => (
<p key={i}>{p}</p>
))}
</div>
);
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?.bbbbbbbbbbbbbbbbbbbbbbbbb.map((p, i) => (
<p key={i}>{p}</p>
))!}
</div>
);
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbb.map((p, i) => (
<p key={i}>{p}</p>
))}
</div>
);
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbb.map((p, i) => (
<p key={i}>{p}</p>
))!}
</div>
);
================================================================================
`;
exports[`react.tsx format 1`] = `
====================================options=====================================
parsers: ["typescript"]
@@ -0,0 +1,28 @@
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?.bbbbbbbbbbbbbbbbbbbbbbbbb.map(
(p, i) => <p key={i}>{p}</p>
)}
</div>
);
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa?.bbbbbbbbbbbbbbbbbbbbbbbbb.map(
(p, i) => <p key={i}>{p}</p>
)!}
</div>
);
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbb.map(
(p, i) => <p key={i}>{p}</p>
)}
</div>
);
a = (
<div>
{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbb.map(
(p, i) => <p key={i}>{p}</p>
)!}
</div>
);