eslint-plugin-unicorn
Version:
More than 300 powerful ESLint rules
200 lines (170 loc) • 4.77 kB
JavaScript
import {findVariable} from '@eslint-community/eslint-utils';
import {isMethodCall} from '../ast/index.js';
import {
isArray,
isTypeScriptExpressionWrapper,
} from '../utils/index.js';
import {createTypeCheckers} from '../utils/type-helpers.js';
const iteratorMethods = [
'entries',
'keys',
'values',
];
const iteratorHelperMethods = [
'drop',
'filter',
'flatMap',
'map',
'take',
];
const iteratorStaticMethods = [
'concat',
'from',
'zip',
'zipKeyed',
];
const iteratorTypeNames = new Set([
'ArrayIterator',
'Generator',
'IterableIterator',
'Iterator',
'IteratorObject',
'MapIterator',
'RegExpStringIterator',
'SetIterator',
'StringIterator',
]);
const {
isTarget: isIteratorType,
} = createTypeCheckers({
allowNullishInMixedUnion: true,
preferTypeReferenceDefinitions: true,
targetTypeNames: iteratorTypeNames,
});
export const unwrapExpression = node => {
while (
isTypeScriptExpressionWrapper(node)
|| node.type === 'TSInstantiationExpression'
|| node.type === 'ChainExpression'
|| node.type === 'ParenthesizedExpression'
) {
node = node.expression;
}
return node;
};
const isGlobalIteratorReference = (node, context) => {
node = unwrapExpression(node);
if (node.type === 'Identifier') {
return node.name === 'Iterator' && context.sourceCode.isGlobalReference(node);
}
if (
node.type !== 'MemberExpression'
|| node.optional
|| node.computed
|| node.property.type !== 'Identifier'
|| node.property.name !== 'Iterator'
) {
return false;
}
const object = unwrapExpression(node.object);
return object.type === 'Identifier'
&& object.name === 'globalThis'
&& context.sourceCode.isGlobalReference(object);
};
const isGlobalIteratorMethodCall = (node, context) =>
isMethodCall(node, {
methods: iteratorStaticMethods,
computed: false,
})
&& isGlobalIteratorReference(node.callee.object, context);
const isIteratorMethodCall = node =>
isMethodCall(node, {
methods: iteratorMethods,
argumentsLength: 0,
computed: false,
})
|| isMethodCall(node, {
method: 'matchAll',
maximumArguments: 1,
computed: false,
});
export const isLazyIteratorHelperCall = (node, context, visitedNodes) =>
isMethodCall(node, {
methods: iteratorHelperMethods,
minimumArguments: 1,
computed: false,
})
&& isIteratorExpression(node.callee.object, context, visitedNodes);
// Only follow plain, unannotated const bindings and unchanged function declarations. Properties, destructuring, mutable bindings, and function return values are intentionally not inferred.
const getImmutableValue = (node, context) => {
if (node.type !== 'Identifier') {
return;
}
const variable = findVariable(context.sourceCode.getScope(node), node);
if (
variable?.defs.length !== 1
|| variable.references.some(reference => reference.isWrite() && !reference.init)
) {
return;
}
const [definition] = variable.defs;
if (definition.type === 'FunctionName' && definition.node.type === 'FunctionDeclaration') {
return definition.node;
}
if (
definition.type === 'Variable'
&& definition.parent.kind === 'const'
&& definition.node.id.type === 'Identifier'
&& !definition.node.id.typeAnnotation
) {
return definition.node.init;
}
};
const isSynchronousGeneratorFunction = (node, context, visitedNodes) => {
while (node) {
node = unwrapExpression(node);
if (visitedNodes.has(node)) {
return false;
}
visitedNodes.add(node);
if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
return node.generator && !node.async;
}
node = getImmutableValue(node, context);
}
return false;
};
const isKnownIteratorTypeExpression = (node, context) => {
if (isArray(node, context)) {
return false;
}
const targetTypeNames = new Set(iteratorTypeNames);
for (let scope = context.sourceCode.getScope(node); scope; scope = scope.upper) {
for (const typeName of targetTypeNames) {
if (scope.set.get(typeName)?.defs.length > 0) {
targetTypeNames.delete(typeName);
}
}
}
return isIteratorType(node, context, {targetTypeNames});
};
export function isIteratorExpression(expression, context, visitedNodes = new Set()) {
const node = unwrapExpression(expression);
if (visitedNodes.has(node)) {
return false;
}
visitedNodes.add(node);
const immutableValue = getImmutableValue(node, context);
return (
isGlobalIteratorMethodCall(node, context)
|| isIteratorMethodCall(node)
|| isLazyIteratorHelperCall(node, context, visitedNodes)
|| (Boolean(immutableValue) && isIteratorExpression(immutableValue, context, visitedNodes))
|| (
node.type === 'CallExpression'
&& !node.optional
&& isSynchronousGeneratorFunction(node.callee, context, visitedNodes)
)
|| isKnownIteratorTypeExpression(expression, context)
);
}