mirror of
https://github.com/nodejs/node.git
synced 2024-11-30 23:43:09 +01:00
574f2dd517
PR-URL: https://github.com/nodejs/node/pull/55045 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
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),
|
|
);
|
|
},
|
|
});
|
|
},
|
|
};
|
|
},
|
|
};
|