Urara-Blog/node_modules/.pnpm-store/v3/files/6b/a5f31367a548be9db2ec82d54dee4ffb40fc24841e974f2ee26ea5ff8c1a5c2beb798e4b7b098e7d5e25f8f5f543b59ee9422e00094c5395181f08c2ff8e72
2022-08-14 01:14:53 +08:00

360 lines
No EOL
17 KiB
Text

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const ts = __importStar(require("typescript"));
const util = __importStar(require("../util"));
const utils_1 = require("@typescript-eslint/utils");
const tsutils_1 = require("tsutils");
/*
The AST is always constructed such the first element is always the deepest element.
I.e. for this code: `foo && foo.bar && foo.bar.baz && foo.bar.baz.buzz`
The AST will look like this:
{
left: {
left: {
left: foo
right: foo.bar
}
right: foo.bar.baz
}
right: foo.bar.baz.buzz
}
*/
exports.default = util.createRule({
name: 'prefer-optional-chain',
meta: {
type: 'suggestion',
docs: {
description: 'Enforce using concise optional chain expressions instead of chained logical ands',
recommended: 'strict',
suggestion: true,
},
hasSuggestions: true,
messages: {
preferOptionalChain: "Prefer using an optional chain expression instead, as it's more concise and easier to read.",
optionalChainSuggest: 'Change to an optional chain.',
},
schema: [],
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();
const parserServices = util.getParserServices(context, true);
return {
'LogicalExpression[operator="||"], LogicalExpression[operator="??"]'(node) {
const leftNode = node.left;
const rightNode = node.right;
const parentNode = node.parent;
const isRightNodeAnEmptyObjectLiteral = rightNode.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
rightNode.properties.length === 0;
if (!isRightNodeAnEmptyObjectLiteral ||
!parentNode ||
parentNode.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
parentNode.optional) {
return;
}
function isLeftSideLowerPrecedence() {
const logicalTsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
const leftTsNode = parserServices.esTreeNodeToTSNodeMap.get(leftNode);
const operator = (0, tsutils_1.isBinaryExpression)(logicalTsNode)
? logicalTsNode.operatorToken.kind
: ts.SyntaxKind.Unknown;
const leftPrecedence = util.getOperatorPrecedence(leftTsNode.kind, operator);
return leftPrecedence < util.OperatorPrecedence.LeftHandSide;
}
context.report({
node: parentNode,
messageId: 'optionalChainSuggest',
suggest: [
{
messageId: 'optionalChainSuggest',
fix: (fixer) => {
const leftNodeText = sourceCode.getText(leftNode);
// Any node that is made of an operator with higher or equal precedence,
const maybeWrappedLeftNode = isLeftSideLowerPrecedence()
? `(${leftNodeText})`
: leftNodeText;
const propertyToBeOptionalText = sourceCode.getText(parentNode.property);
const maybeWrappedProperty = parentNode.computed
? `[${propertyToBeOptionalText}]`
: propertyToBeOptionalText;
return fixer.replaceTextRange(parentNode.range, `${maybeWrappedLeftNode}?.${maybeWrappedProperty}`);
},
},
],
});
},
[[
'LogicalExpression[operator="&&"] > Identifier',
'LogicalExpression[operator="&&"] > MemberExpression',
'LogicalExpression[operator="&&"] > ChainExpression > MemberExpression',
'LogicalExpression[operator="&&"] > BinaryExpression[operator="!=="]',
'LogicalExpression[operator="&&"] > BinaryExpression[operator="!="]',
].join(',')](initialIdentifierOrNotEqualsExpr) {
var _a;
// selector guarantees this cast
const initialExpression = (((_a = initialIdentifierOrNotEqualsExpr.parent) === null || _a === void 0 ? void 0 : _a.type) ===
utils_1.AST_NODE_TYPES.ChainExpression
? initialIdentifierOrNotEqualsExpr.parent.parent
: initialIdentifierOrNotEqualsExpr.parent);
if (initialExpression.left !== initialIdentifierOrNotEqualsExpr) {
// the node(identifier or member expression) is not the deepest left node
return;
}
if (!isValidChainTarget(initialIdentifierOrNotEqualsExpr, true)) {
return;
}
// walk up the tree to figure out how many logical expressions we can include
let previous = initialExpression;
let current = initialExpression;
let previousLeftText = getText(initialIdentifierOrNotEqualsExpr);
let optionallyChainedCode = previousLeftText;
let expressionCount = 1;
while (current.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
if (!isValidChainTarget(current.right,
// only allow identifiers for the first chain - foo && foo()
expressionCount === 1)) {
break;
}
const leftText = previousLeftText;
const rightText = getText(current.right);
// can't just use startsWith because of cases like foo && fooBar.baz;
const matchRegex = new RegExp(`^${
// escape regex characters
leftText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[^a-zA-Z0-9_$]`);
if (!matchRegex.test(rightText) &&
// handle redundant cases like foo.bar && foo.bar
leftText !== rightText) {
break;
}
// omit weird doubled up expression that make no sense like foo.bar && foo.bar
if (rightText !== leftText) {
expressionCount += 1;
previousLeftText = rightText;
/*
Diff the left and right text to construct the fix string
There are the following cases:
1)
rightText === 'foo.bar.baz.buzz'
leftText === 'foo.bar.baz'
diff === '.buzz'
2)
rightText === 'foo.bar.baz.buzz()'
leftText === 'foo.bar.baz'
diff === '.buzz()'
3)
rightText === 'foo.bar.baz.buzz()'
leftText === 'foo.bar.baz.buzz'
diff === '()'
4)
rightText === 'foo.bar.baz[buzz]'
leftText === 'foo.bar.baz'
diff === '[buzz]'
5)
rightText === 'foo.bar.baz?.buzz'
leftText === 'foo.bar.baz'
diff === '?.buzz'
*/
const diff = rightText.replace(leftText, '');
if (diff.startsWith('?')) {
// item was "pre optional chained"
optionallyChainedCode += diff;
}
else {
const needsDot = diff.startsWith('(') || diff.startsWith('[');
optionallyChainedCode += `?${needsDot ? '.' : ''}${diff}`;
}
}
previous = current;
current = util.nullThrows(current.parent, util.NullThrowsReasons.MissingParent);
}
if (expressionCount > 1) {
if (previous.right.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
// case like foo && foo.bar !== someValue
optionallyChainedCode += ` ${previous.right.operator} ${sourceCode.getText(previous.right.right)}`;
}
context.report({
node: previous,
messageId: 'preferOptionalChain',
suggest: [
{
messageId: 'optionalChainSuggest',
fix: (fixer) => [
fixer.replaceText(previous, optionallyChainedCode),
],
},
],
});
}
},
};
function getText(node) {
if (node.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
return getText(
// isValidChainTarget ensures this is type safe
node.left);
}
if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
const calleeText = getText(
// isValidChainTarget ensures this is type safe
node.callee);
// ensure that the call arguments are left untouched, or else we can break cases that _need_ whitespace:
// - JSX: <Foo Needs Space Between Attrs />
// - Unary Operators: typeof foo, await bar, delete baz
const closingParenToken = util.nullThrows(sourceCode.getLastToken(node), util.NullThrowsReasons.MissingToken('closing parenthesis', node.type));
const openingParenToken = util.nullThrows(sourceCode.getFirstTokenBetween(node.callee, closingParenToken, util.isOpeningParenToken), util.NullThrowsReasons.MissingToken('opening parenthesis', node.type));
const argumentsText = sourceCode.text.substring(openingParenToken.range[0], closingParenToken.range[1]);
return `${calleeText}${argumentsText}`;
}
if (node.type === utils_1.AST_NODE_TYPES.Identifier) {
return node.name;
}
if (node.type === utils_1.AST_NODE_TYPES.ThisExpression) {
return 'this';
}
if (node.type === utils_1.AST_NODE_TYPES.ChainExpression) {
/* istanbul ignore if */ if (node.expression.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
// this shouldn't happen
return '';
}
return getText(node.expression);
}
return getMemberExpressionText(node);
}
/**
* Gets a normalized representation of the given MemberExpression
*/
function getMemberExpressionText(node) {
let objectText;
// cases should match the list in ALLOWED_MEMBER_OBJECT_TYPES
switch (node.object.type) {
case utils_1.AST_NODE_TYPES.CallExpression:
case utils_1.AST_NODE_TYPES.Identifier:
objectText = getText(node.object);
break;
case utils_1.AST_NODE_TYPES.MemberExpression:
objectText = getMemberExpressionText(node.object);
break;
case utils_1.AST_NODE_TYPES.ThisExpression:
objectText = getText(node.object);
break;
/* istanbul ignore next */
default:
throw new Error(`Unexpected member object type: ${node.object.type}`);
}
let propertyText;
if (node.computed) {
// cases should match the list in ALLOWED_COMPUTED_PROP_TYPES
switch (node.property.type) {
case utils_1.AST_NODE_TYPES.Identifier:
propertyText = getText(node.property);
break;
case utils_1.AST_NODE_TYPES.Literal:
case utils_1.AST_NODE_TYPES.TemplateLiteral:
propertyText = sourceCode.getText(node.property);
break;
case utils_1.AST_NODE_TYPES.MemberExpression:
propertyText = getMemberExpressionText(node.property);
break;
/* istanbul ignore next */
default:
throw new Error(`Unexpected member property type: ${node.object.type}`);
}
return `${objectText}${node.optional ? '?.' : ''}[${propertyText}]`;
}
else {
// cases should match the list in ALLOWED_NON_COMPUTED_PROP_TYPES
switch (node.property.type) {
case utils_1.AST_NODE_TYPES.Identifier:
propertyText = getText(node.property);
break;
/* istanbul ignore next */
default:
throw new Error(`Unexpected member property type: ${node.object.type}`);
}
return `${objectText}${node.optional ? '?.' : '.'}${propertyText}`;
}
}
},
});
const ALLOWED_MEMBER_OBJECT_TYPES = new Set([
utils_1.AST_NODE_TYPES.CallExpression,
utils_1.AST_NODE_TYPES.Identifier,
utils_1.AST_NODE_TYPES.MemberExpression,
utils_1.AST_NODE_TYPES.ThisExpression,
]);
const ALLOWED_COMPUTED_PROP_TYPES = new Set([
utils_1.AST_NODE_TYPES.Identifier,
utils_1.AST_NODE_TYPES.Literal,
utils_1.AST_NODE_TYPES.MemberExpression,
utils_1.AST_NODE_TYPES.TemplateLiteral,
]);
const ALLOWED_NON_COMPUTED_PROP_TYPES = new Set([
utils_1.AST_NODE_TYPES.Identifier,
]);
function isValidChainTarget(node, allowIdentifier) {
if (node.type === utils_1.AST_NODE_TYPES.ChainExpression) {
return isValidChainTarget(node.expression, allowIdentifier);
}
if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
const isObjectValid = ALLOWED_MEMBER_OBJECT_TYPES.has(node.object.type) &&
// make sure to validate the expression is of our expected structure
isValidChainTarget(node.object, true);
const isPropertyValid = node.computed
? ALLOWED_COMPUTED_PROP_TYPES.has(node.property.type) &&
// make sure to validate the member expression is of our expected structure
(node.property.type === utils_1.AST_NODE_TYPES.MemberExpression
? isValidChainTarget(node.property, allowIdentifier)
: true)
: ALLOWED_NON_COMPUTED_PROP_TYPES.has(node.property.type);
return isObjectValid && isPropertyValid;
}
if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
return isValidChainTarget(node.callee, allowIdentifier);
}
if (allowIdentifier &&
(node.type === utils_1.AST_NODE_TYPES.Identifier ||
node.type === utils_1.AST_NODE_TYPES.ThisExpression)) {
return true;
}
/*
special case for the following, where we only want the left
- foo !== null
- foo != null
- foo !== undefined
- foo != undefined
*/
return (node.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
['!==', '!='].includes(node.operator) &&
isValidChainTarget(node.left, allowIdentifier) &&
(util.isUndefinedIdentifier(node.right) || util.isNullLiteral(node.right)));
}
//# sourceMappingURL=prefer-optional-chain.js.map