elearning/Frontend-Learner/node_modules/eslint-plugin-unicorn/rules/no-instanceof-builtins.js
2026-01-13 10:48:02 +07:00

216 lines
5.1 KiB
JavaScript

import {
checkVueTemplate,
getParenthesizedRange,
getTokenStore,
} from './utils/index.js';
import {replaceNodeOrTokenAndSpacesBefore, fixSpaceAroundKeyword} from './fix/index.js';
import builtinErrors from './shared/builtin-errors.js';
import typedArray from './shared/typed-array.js';
const isInstanceofToken = token => token.value === 'instanceof' && token.type === 'Keyword';
const MESSAGE_ID = 'no-instanceof-builtins';
const MESSAGE_ID_SWITCH_TO_TYPE_OF = 'switch-to-type-of';
const messages = {
[MESSAGE_ID]: 'Avoid using `instanceof` for type checking as it can lead to unreliable results.',
[MESSAGE_ID_SWITCH_TO_TYPE_OF]: 'Switch to `typeof … === \'{{type}}\'`.',
};
const primitiveWrappers = new Set([
'String',
'Number',
'Boolean',
'BigInt',
'Symbol',
]);
const strictStrategyConstructors = [
// Error types
...builtinErrors,
// Collection types
'Map',
'Set',
'WeakMap',
'WeakRef',
'WeakSet',
// Arrays and Typed Arrays
'ArrayBuffer',
...typedArray,
// Data types
'Object',
// Regular Expressions
'RegExp',
// Async and functions
'Promise',
'Proxy',
// Other
'DataView',
'Date',
'SharedArrayBuffer',
'FinalizationRegistry',
];
const replaceWithFunctionCall = (node, context, functionName) => function * (fixer) {
const {left, right} = node;
const tokenStore = getTokenStore(context, node);
const instanceofToken = tokenStore.getTokenAfter(left, isInstanceofToken);
yield * fixSpaceAroundKeyword(fixer, node, context);
const range = getParenthesizedRange(left, {sourceCode: tokenStore});
yield fixer.insertTextBeforeRange(range, functionName + '(');
yield fixer.insertTextAfterRange(range, ')');
yield * replaceNodeOrTokenAndSpacesBefore(instanceofToken, '', fixer, context, tokenStore);
yield * replaceNodeOrTokenAndSpacesBefore(right, '', fixer, context, tokenStore);
};
const replaceWithTypeOfExpression = (node, context) => function * (fixer) {
const {left, right} = node;
const tokenStore = getTokenStore(context, node);
const instanceofToken = tokenStore.getTokenAfter(left, isInstanceofToken);
const {sourceCode} = context;
// Check if the node is in a Vue template expression
const vueExpressionContainer = sourceCode.getAncestors(node).findLast(ancestor => ancestor.type === 'VExpressionContainer');
// Get safe quote
const safeQuote = vueExpressionContainer ? (sourceCode.getText(vueExpressionContainer)[0] === '"' ? '\'' : '"') : '\'';
yield * fixSpaceAroundKeyword(fixer, node, context);
const leftRange = getParenthesizedRange(left, {sourceCode: tokenStore});
yield fixer.insertTextBeforeRange(leftRange, 'typeof ');
yield fixer.replaceText(instanceofToken, '===');
const rightRange = getParenthesizedRange(right, {sourceCode: tokenStore});
yield fixer.replaceTextRange(rightRange, safeQuote + sourceCode.getText(right).toLowerCase() + safeQuote);
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {
useErrorIsError = false,
strategy = 'loose',
include = [],
exclude = [],
} = context.options[0] ?? {};
const forbiddenConstructors = new Set(
strategy === 'strict'
? [...strictStrategyConstructors, ...include]
: include,
);
return {
/** @param {import('estree').BinaryExpression} node */
BinaryExpression(node) {
const {right, operator} = node;
if (right.type !== 'Identifier' || operator !== 'instanceof' || exclude.includes(right.name)) {
return;
}
const constructorName = right.name;
/** @type {import('eslint').Rule.ReportDescriptor} */
const problem = {
node,
messageId: MESSAGE_ID,
};
if (
constructorName === 'Array'
|| (constructorName === 'Error' && useErrorIsError)
) {
const functionName = constructorName === 'Array' ? 'Array.isArray' : 'Error.isError';
problem.fix = replaceWithFunctionCall(node, context, functionName);
return problem;
}
if (constructorName === 'Function') {
problem.fix = replaceWithTypeOfExpression(node, context);
return problem;
}
if (primitiveWrappers.has(constructorName)) {
problem.suggest = [
{
messageId: MESSAGE_ID_SWITCH_TO_TYPE_OF,
data: {type: constructorName.toLowerCase()},
fix: replaceWithTypeOfExpression(node, context),
},
];
return problem;
}
if (!forbiddenConstructors.has(constructorName)) {
return;
}
return problem;
},
};
};
const schema = [
{
type: 'object',
properties: {
useErrorIsError: {
type: 'boolean',
},
strategy: {
enum: [
'loose',
'strict',
],
},
include: {
type: 'array',
items: {
type: 'string',
},
},
exclude: {
type: 'array',
items: {
type: 'string',
},
},
},
additionalProperties: false,
},
];
/** @type {import('eslint').Rule.RuleModule} */
const config = {
create: checkVueTemplate(create),
meta: {
type: 'problem',
docs: {
description: 'Disallow `instanceof` with built-in objects',
recommended: 'unopinionated',
},
fixable: 'code',
schema,
defaultOptions: [{
useErrorIsError: false,
strategy: 'loose',
include: [],
exclude: [],
}],
hasSuggestions: true,
messages,
},
};
export default config;