📄 getWrappingFixer¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 7 |
| 📦 Imports | 5 |
| 📐 Interfaces | 1 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/util/getWrappingFixer.ts
📦 Imports¶
| Name | Source |
|---|---|
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
ASTUtils |
@typescript-eslint/utils |
ESLintUtils |
@typescript-eslint/utils |
Functions¶
getWrappingFixer(params: WrappingFixerParams): (fixer: TSESLint.RuleFixer) => TSESLint.RuleFix¶
Wraps node with some code. Adds parentheses as necessary.
Returns: undefined
Fixer which adds the specified code and parens if necessary.
Raw JSDoc
Calls:
Array.isArrayinnerNodes.mapsourceCode.getTextisStrongPrecedenceNodeisObjectExpressionInOneLineReturnfixer.replaceTextinnerCodes.joinwrapisWeakPrecedenceParentASTUtils.isParenthesized/^[([]/.test`isMissingSemicolonBefore
Internal Comments:
/**
* Wrap our node in parens to prevent the following cases:
* - It has a weaker precedence than the code we are wrapping it in
* - It's gotten mistaken as block statement instead of object expression
*/
// do the wrapping (x2)
// check the outer expression's precedence
// we wrapped the node in some expression which very likely has a different precedence than original wrapped node
// let's wrap the whole expression in parens just in case
// check if we need to insert semicolon
Code
export function getWrappingFixer(
params: WrappingFixerParams,
): (fixer: TSESLint.RuleFixer) => TSESLint.RuleFix {
const { node, innerNode = node, sourceCode, wrap } = params;
const innerNodes = Array.isArray(innerNode) ? innerNode : [innerNode];
return (fixer): TSESLint.RuleFix => {
const innerCodes = innerNodes.map(innerNode => {
let code = sourceCode.getText(innerNode);
/**
* Wrap our node in parens to prevent the following cases:
* - It has a weaker precedence than the code we are wrapping it in
* - It's gotten mistaken as block statement instead of object expression
*/
if (
!isStrongPrecedenceNode(innerNode) ||
isObjectExpressionInOneLineReturn(node, innerNode)
) {
code = `(${code})`;
}
return code;
});
if (!wrap) {
return fixer.replaceText(node, innerCodes.join(''));
}
// do the wrapping
let code = wrap(...innerCodes);
// check the outer expression's precedence
if (
isWeakPrecedenceParent(node) &&
// we wrapped the node in some expression which very likely has a different precedence than original wrapped node
// let's wrap the whole expression in parens just in case
!ASTUtils.isParenthesized(node, sourceCode)
) {
code = `(${code})`;
}
// check if we need to insert semicolon
if (/^[`([]/.test(code) && isMissingSemicolonBefore(node, sourceCode)) {
code = `;${code}`;
}
return fixer.replaceText(node, code);
};
}
getMovedNodeCode(params: { destinationNode: TSESTree.Node; nodeT…): string¶
If the node to be moved and the destination node require parentheses, include parentheses in the node to be moved.
Parameters:
sourceCodeany: Source code of current filenodeToMoveany: Nodes that need to be moveddestinationNodeany: Final destination node with nodeToMove
Returns: undefined
If parentheses are required, code for the nodeToMove node is returned with parentheses at both ends of the code.
Raw JSDoc
/**
* If the node to be moved and the destination node require parentheses, include parentheses in the node to be moved.
* @param sourceCode Source code of current file
* @param nodeToMove Nodes that need to be moved
* @param destinationNode Final destination node with nodeToMove
* @returns If parentheses are required, code for the nodeToMove node is returned with parentheses at both ends of the code.
*/
Calls:
sourceCode.getTextisStrongPrecedenceNodeisWeakPrecedenceParent
Internal Comments:
// Moved node never needs parens
// Destination would never needs parens, regardless what node moves there
// Parens may be necessary
Code
export function getMovedNodeCode(params: {
destinationNode: TSESTree.Node;
nodeToMove: TSESTree.Node;
sourceCode: Readonly<TSESLint.SourceCode>;
}): string {
const { destinationNode, nodeToMove: existingNode, sourceCode } = params;
const code = sourceCode.getText(existingNode);
if (isStrongPrecedenceNode(existingNode)) {
// Moved node never needs parens
return code;
}
if (!isWeakPrecedenceParent(destinationNode)) {
// Destination would never needs parens, regardless what node moves there
return code;
}
// Parens may be necessary
return `(${code})`;
}
isStrongPrecedenceNode(innerNode: TSESTree.Node): boolean¶
Check if a node will always have the same precedence if its parent changes.
Code
export function isStrongPrecedenceNode(innerNode: TSESTree.Node): boolean {
return (
innerNode.type === AST_NODE_TYPES.Literal ||
innerNode.type === AST_NODE_TYPES.Identifier ||
innerNode.type === AST_NODE_TYPES.TSTypeReference ||
innerNode.type === AST_NODE_TYPES.TSTypeOperator ||
innerNode.type === AST_NODE_TYPES.ArrayExpression ||
innerNode.type === AST_NODE_TYPES.ObjectExpression ||
innerNode.type === AST_NODE_TYPES.MemberExpression ||
innerNode.type === AST_NODE_TYPES.CallExpression ||
innerNode.type === AST_NODE_TYPES.NewExpression ||
innerNode.type === AST_NODE_TYPES.TaggedTemplateExpression ||
innerNode.type === AST_NODE_TYPES.TSInstantiationExpression
);
}
isWeakPrecedenceParent(node: TSESTree.Node): boolean¶
Check if a node's parent could have different precedence if the node changes.
Code
export function isWeakPrecedenceParent(node: TSESTree.Node): boolean {
const parent = node.parent;
if (!parent) {
return false;
}
if (
parent.type === AST_NODE_TYPES.UpdateExpression ||
parent.type === AST_NODE_TYPES.UnaryExpression ||
parent.type === AST_NODE_TYPES.BinaryExpression ||
parent.type === AST_NODE_TYPES.LogicalExpression ||
parent.type === AST_NODE_TYPES.ConditionalExpression ||
parent.type === AST_NODE_TYPES.AwaitExpression
) {
return true;
}
if (
parent.type === AST_NODE_TYPES.MemberExpression &&
parent.object === node
) {
return true;
}
if (
(parent.type === AST_NODE_TYPES.CallExpression ||
parent.type === AST_NODE_TYPES.NewExpression) &&
parent.callee === node
) {
return true;
}
if (
parent.type === AST_NODE_TYPES.TaggedTemplateExpression &&
parent.tag === node
) {
return true;
}
return false;
}
isMissingSemicolonBefore(node: TSESTree.Node, sourceCode: TSESLint.SourceCode): boolean¶
Returns true if a node is at the beginning of expression statement and the statement above doesn't end with semicolon.
Doesn't check if the node begins with (, [ or `.
Raw JSDoc
Calls:
block.body.indexOfESLintUtils.nullThrowssourceCode.getLastTokenisLeftHandSide
Internal Comments:
// https://github.com/typescript-eslint/typescript-eslint/issues/6225 (x2)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2)
// parent is an expression statement in a block (x2)
Code
function isMissingSemicolonBefore(
node: TSESTree.Node,
sourceCode: TSESLint.SourceCode,
): boolean {
for (;;) {
// https://github.com/typescript-eslint/typescript-eslint/issues/6225
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const parent = node.parent!;
if (parent.type === AST_NODE_TYPES.ExpressionStatement) {
const block = parent.parent;
if (
block.type === AST_NODE_TYPES.Program ||
block.type === AST_NODE_TYPES.BlockStatement
) {
// parent is an expression statement in a block
const statementIndex = block.body.indexOf(parent);
const previousStatement = block.body[statementIndex - 1];
if (
statementIndex > 0 &&
ESLintUtils.nullThrows(
sourceCode.getLastToken(previousStatement),
'Mismatched semicolon and block',
).value !== ';'
) {
return true;
}
}
}
if (!isLeftHandSide(node)) {
return false;
}
node = parent;
}
}
isLeftHandSide(node: TSESTree.Node): boolean¶
Checks if a node is LHS of an operator.
Internal Comments:
// https://github.com/typescript-eslint/typescript-eslint/issues/6225 (x2)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2)
// a++
// a + b
// a ? b : c
// a(b)
// a`b`
Code
function isLeftHandSide(node: TSESTree.Node): boolean {
// https://github.com/typescript-eslint/typescript-eslint/issues/6225
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const parent = node.parent!;
// a++
if (parent.type === AST_NODE_TYPES.UpdateExpression) {
return true;
}
// a + b
if (
(parent.type === AST_NODE_TYPES.BinaryExpression ||
parent.type === AST_NODE_TYPES.LogicalExpression ||
parent.type === AST_NODE_TYPES.AssignmentExpression) &&
node === parent.left
) {
return true;
}
// a ? b : c
if (
parent.type === AST_NODE_TYPES.ConditionalExpression &&
node === parent.test
) {
return true;
}
// a(b)
if (parent.type === AST_NODE_TYPES.CallExpression && node === parent.callee) {
return true;
}
// a`b`
if (
parent.type === AST_NODE_TYPES.TaggedTemplateExpression &&
node === parent.tag
) {
return true;
}
return false;
}
isObjectExpressionInOneLineReturn(node: TSESTree.Node, innerNode: TSESTree.Node): boolean¶
Checks if a node's parent is arrow function expression and a inner node is object expression
Raw JSDoc
Code
Interfaces¶
WrappingFixerParams¶
Interface Code
interface WrappingFixerParams {
/**
* Descendant of `node` we want to preserve.
* Use this to replace some code with another.
* By default it's the node we are modifying (so nothing is removed).
* You can pass multiple nodes as an array.
*/
innerNode?: TSESTree.Node | TSESTree.Node[];
/** The node we want to modify. */
node: TSESTree.Node;
/** Source code. */
sourceCode: Readonly<TSESLint.SourceCode>;
/**
* The function which gets the code of the `innerNode` and returns some code around it.
* Receives multiple arguments if there are multiple innerNodes.
* E.g. ``code => `${code} != null` ``
*/
wrap?: (...code: string[]) => string;
}
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
innerNode |
TSESTree.Node \| TSESTree.Node[] |
✓ | not shown |
node |
TSESTree.Node |
✗ | not shown |
sourceCode |
Readonly<TSESLint.SourceCode> |
✗ | not shown |
wrap |
(...code: string[]) => string |
✓ | not shown |
Generated by Syntax Scribe