mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
94 lines
2.9 KiB
JavaScript
94 lines
2.9 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Rule Definition
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
module.exports = {
|
||
|
meta: {
|
||
|
docs: {
|
||
|
description: 'Prefer optional chaining',
|
||
|
category: 'suggestion',
|
||
|
},
|
||
|
fixable: 'code',
|
||
|
schema: [],
|
||
|
},
|
||
|
|
||
|
create(context) {
|
||
|
const sourceCode = context.getSourceCode();
|
||
|
|
||
|
// Helper function: Checks if two nodes have identical tokens
|
||
|
function equalTokens(left, right) {
|
||
|
const leftTokens = sourceCode.getTokens(left);
|
||
|
const rightTokens = sourceCode.getTokens(right);
|
||
|
return (
|
||
|
leftTokens.length === rightTokens.length &&
|
||
|
leftTokens.every((tokenL, i) => tokenL.type === rightTokens[i].type && tokenL.value === rightTokens[i].value)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Check if a sequence of two nodes forms a valid member expression chain
|
||
|
function isValidMemberExpressionPair(left, right) {
|
||
|
return (
|
||
|
right.type === 'MemberExpression' &&
|
||
|
equalTokens(left, right.object)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Generate the optional chaining expression
|
||
|
function generateOptionalChaining(ops, first, last) {
|
||
|
return ops.slice(first, last + 1).reduce((chain, node, i) => {
|
||
|
const property = node.computed ?
|
||
|
`[${sourceCode.getText(node.property)}]` :
|
||
|
sourceCode.getText(node.property);
|
||
|
return i === 0 ? sourceCode.getText(node) : `${chain}?.${property}`;
|
||
|
}, '');
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
'LogicalExpression[operator=&&]:exit'(node) {
|
||
|
// Early return if part of a larger `&&` chain
|
||
|
if (node.parent.type === 'LogicalExpression' && node.parent.operator === '&&') {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const ops = [];
|
||
|
let current = node;
|
||
|
|
||
|
// Collect `&&` expressions into the ops array
|
||
|
while (current.type === 'LogicalExpression' && current.operator === '&&') {
|
||
|
ops.unshift(current.right); // Add right operand
|
||
|
current = current.left;
|
||
|
}
|
||
|
ops.unshift(current); // Add the leftmost operand
|
||
|
|
||
|
// Find the first valid member expression sequence
|
||
|
let first = 0;
|
||
|
while (first < ops.length - 1 && !isValidMemberExpressionPair(ops[first], ops[first + 1])) {
|
||
|
first++;
|
||
|
}
|
||
|
|
||
|
// No valid sequence found
|
||
|
if (first === ops.length - 1) return;
|
||
|
|
||
|
context.report({
|
||
|
node,
|
||
|
message: 'Prefer optional chaining.',
|
||
|
fix(fixer) {
|
||
|
// Find the last valid member expression sequence
|
||
|
let last = first;
|
||
|
while (last < ops.length - 1 && isValidMemberExpressionPair(ops[last], ops[last + 1])) {
|
||
|
last++;
|
||
|
}
|
||
|
|
||
|
return fixer.replaceTextRange(
|
||
|
[ops[first].range[0], ops[last].range[1]],
|
||
|
generateOptionalChaining(ops, first, last),
|
||
|
);
|
||
|
},
|
||
|
});
|
||
|
},
|
||
|
};
|
||
|
},
|
||
|
};
|