routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+211
@@ -0,0 +1,211 @@
|
||||
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);
|
||||
|
||||
context.on('BinaryExpression', /** @param {import('estree').BinaryExpression} node */ node => {
|
||||
const {right, operator} = node;
|
||||
|
||||
if ((operator !== 'instanceof') || (right.type !== 'Identifier') || 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;
|
||||
Reference in New Issue
Block a user