β¬ οΈ Back to Table of Contents
π prefer-optional-chain¶
π Analysis Summary¶
| Metric | Count |
|---|---|
| π§ Functions | 3 |
| π¦ Imports | 14 |
π Table of Contents¶
π οΈ File Location:¶
π packages/eslint-plugin/src/rules/prefer-optional-chain.ts
π€ Default Export¶
| Property | Value |
|---|---|
name |
'prefer-optional-chain' |
meta.type |
'suggestion' |
meta.docs.description |
'Enforce using concise optional chain expressions instead of chained logical ands, negated logical ors, or empty obje... |
meta.docs.recommended |
'stylistic' |
meta.docs.requiresTypeChecking |
true |
meta.fixable |
'code' |
meta.hasSuggestions |
true |
meta.messages.optionalChainSuggest |
'Change to an optional chain.' |
meta.messages.preferOptionalChain |
"Prefer using an optional chain expression instead, as it's more concise and easier to read." |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKno... |
defaultOptions |
[ { allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing: false, checkAny: true, checkBigInt: true, che... |
Entry point: create β documented under Functions.
π¦ Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
RuleFix |
@typescript-eslint/utils/ts-eslint |
AST_NODE_TYPES |
@typescript-eslint/utils |
ValidOperand |
./prefer-optional-chain-utils/gatherLogicalOperands |
PreferOptionalChainMessageIds |
./prefer-optional-chain-utils/PreferOptionalChainOptions |
PreferOptionalChainOptions |
./prefer-optional-chain-utils/PreferOptionalChainOptions |
createRule |
../util |
getOperatorPrecedence |
../util |
getParserServices |
../util |
OperatorPrecedence |
../util |
analyzeChain |
./prefer-optional-chain-utils/analyzeChain |
checkNullishAndReport |
./prefer-optional-chain-utils/checkNullishAndReport |
gatherLogicalOperands |
./prefer-optional-chain-utils/gatherLogicalOperands |
OperandValidity |
./prefer-optional-chain-utils/gatherLogicalOperands |
Functions¶
create(context: any, [options]: any): { 'LogicalExpression[operator!="??"]'(node: TSESTree.Logica⦶
Parameters:
contextany[options]any
Returns: { 'LogicalExpression[operator!="??"]'(node: TSESTree.LogicalExpression): void; 'LogicalExpression[operator="||"], LogicalExpression[operator="??"]'(node: TSESTree.LogicalExpression): void; }
Calls:
getParserServices (from ../util)seenLogicals.hasgatherLogicalOperands (from ./prefer-optional-chain-utils/gatherLogicalOperands)seenLogicals.addanalyzeChain (from ./prefer-optional-chain-utils/analyzeChain)currentChain.pushparserServices.esTreeNodeToTSNodeMap.getgetOperatorPrecedence (from ../util)checkNullishAndReport (from ./prefer-optional-chain-utils/checkNullishAndReport)context.sourceCode.getTextisLeftSideLowerPrecedencefixer.replaceTextRange
Internal Comments:
// make sure to check whatever's left
// specific handling for `(foo ?? {}).bar` / `(foo || {}).bar` (x2)
// Any node that is made of an operator with higher or equal precedence, (x2)
Code
create(context, [options]) {
const parserServices = getParserServices(context);
const seenLogicals = new Set<TSESTree.LogicalExpression>();
return {
'LogicalExpression[operator!="??"]'(
node: TSESTree.LogicalExpression,
): void {
if (seenLogicals.has(node)) {
return;
}
const { newlySeenLogicals, operands } = gatherLogicalOperands(
node,
parserServices,
context.sourceCode,
options,
);
for (const logical of newlySeenLogicals) {
seenLogicals.add(logical);
}
let currentChain: ValidOperand[] = [];
for (const operand of operands) {
if (operand.type === OperandValidity.Invalid) {
analyzeChain(
context,
parserServices,
options,
node,
node.operator,
currentChain,
);
currentChain = [];
} else if (operand.type === OperandValidity.Last) {
analyzeChain(
context,
parserServices,
options,
node,
node.operator,
currentChain,
operand,
);
currentChain = [];
} else {
currentChain.push(operand);
}
}
// make sure to check whatever's left
if (currentChain.length > 0) {
analyzeChain(
context,
parserServices,
options,
node,
node.operator,
currentChain,
);
}
},
// specific handling for `(foo ?? {}).bar` / `(foo || {}).bar`
'LogicalExpression[operator="||"], LogicalExpression[operator="??"]'(
node: TSESTree.LogicalExpression,
): void {
const leftNode = node.left;
const rightNode = node.right;
const parentNode = node.parent;
const isRightNodeAnEmptyObjectLiteral =
rightNode.type === AST_NODE_TYPES.ObjectExpression &&
rightNode.properties.length === 0;
if (
!isRightNodeAnEmptyObjectLiteral ||
parentNode.type !== AST_NODE_TYPES.MemberExpression ||
parentNode.optional
) {
return;
}
seenLogicals.add(node);
function isLeftSideLowerPrecedence(): boolean {
const logicalTsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
const leftTsNode = parserServices.esTreeNodeToTSNodeMap.get(leftNode);
const leftPrecedence = getOperatorPrecedence(
leftTsNode.kind,
logicalTsNode.operatorToken.kind,
);
return leftPrecedence < OperatorPrecedence.LeftHandSide;
}
checkNullishAndReport(context, parserServices, options, [leftNode], {
node: parentNode,
messageId: 'preferOptionalChain',
suggest: [
{
messageId: 'optionalChainSuggest',
fix: (fixer): RuleFix => {
const leftNodeText = context.sourceCode.getText(leftNode);
// Any node that is made of an operator with higher or equal precedence,
const maybeWrappedLeftNode = isLeftSideLowerPrecedence()
? `(${leftNodeText})`
: leftNodeText;
const propertyToBeOptionalText = context.sourceCode.getText(
parentNode.property,
);
const maybeWrappedProperty = parentNode.computed
? `[${propertyToBeOptionalText}]`
: propertyToBeOptionalText;
return fixer.replaceTextRange(
parentNode.range,
`${maybeWrappedLeftNode}?.${maybeWrappedProperty}`,
);
},
},
],
});
},
};
}
Internal helpers¶
Declared inside another function in this file.
isLeftSideLowerPrecedence(): boolean¶
Returns: boolean
Calls:
parserServices.esTreeNodeToTSNodeMap.getgetOperatorPrecedence (from ../util)
Code
function isLeftSideLowerPrecedence(): boolean {
const logicalTsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
const leftTsNode = parserServices.esTreeNodeToTSNodeMap.get(leftNode);
const leftPrecedence = getOperatorPrecedence(
leftTsNode.kind,
logicalTsNode.operatorToken.kind,
);
return leftPrecedence < OperatorPrecedence.LeftHandSide;
}
fix(fixer: any): RuleFix¶
Parameters:
fixerany
Returns: RuleFix
Calls:
context.sourceCode.getTextisLeftSideLowerPrecedencefixer.replaceTextRange
Internal Comments:
Code
(fixer): RuleFix => {
const leftNodeText = context.sourceCode.getText(leftNode);
// Any node that is made of an operator with higher or equal precedence,
const maybeWrappedLeftNode = isLeftSideLowerPrecedence()
? `(${leftNodeText})`
: leftNodeText;
const propertyToBeOptionalText = context.sourceCode.getText(
parentNode.property,
);
const maybeWrappedProperty = parentNode.computed
? `[${propertyToBeOptionalText}]`
: propertyToBeOptionalText;
return fixer.replaceTextRange(
parentNode.range,
`${maybeWrappedLeftNode}?.${maybeWrappedProperty}`,
);
}
Generated by Syntax Scribe