routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
Generated
Vendored
+194
@@ -0,0 +1,194 @@
|
||||
import {getFunctionHeadLocation, getFunctionNameWithKind} from '@eslint-community/eslint-utils';
|
||||
import {functionTypes} from './ast/index.js';
|
||||
|
||||
const MESSAGE_ID = 'prefer-native-coercion-functions';
|
||||
const messages = {
|
||||
[MESSAGE_ID]: '{{functionNameWithKind}} is equivalent to `{{replacementFunction}}`. Use `{{replacementFunction}}` directly.',
|
||||
};
|
||||
|
||||
const nativeCoercionFunctionNames = new Set(['String', 'Number', 'BigInt', 'Boolean', 'Symbol']);
|
||||
const arrayMethodsWithBooleanCallback = new Set(['every', 'filter', 'find', 'findLast', 'findIndex', 'findLastIndex', 'some']);
|
||||
|
||||
const isNativeCoercionFunctionCall = (node, firstArgumentName) =>
|
||||
node?.type === 'CallExpression'
|
||||
&& !node.optional
|
||||
&& node.callee.type === 'Identifier'
|
||||
&& nativeCoercionFunctionNames.has(node.callee.name)
|
||||
&& node.arguments[0]?.type === 'Identifier'
|
||||
&& node.arguments[0].name === firstArgumentName;
|
||||
|
||||
const isIdentityFunction = node =>
|
||||
(
|
||||
// `v => v`
|
||||
node.type === 'ArrowFunctionExpression'
|
||||
&& node.body.type === 'Identifier'
|
||||
&& node.body.name === node.params[0].name
|
||||
)
|
||||
|| (
|
||||
// `(v) => {return v;}`
|
||||
// `function (v) {return v;}`
|
||||
node.body.type === 'BlockStatement'
|
||||
&& node.body.body.length === 1
|
||||
&& node.body.body[0].type === 'ReturnStatement'
|
||||
&& node.body.body[0].argument?.type === 'Identifier'
|
||||
&& node.body.body[0].argument.name === node.params[0].name
|
||||
);
|
||||
|
||||
const isArrayIdentityCallback = node =>
|
||||
isIdentityFunction(node)
|
||||
&& node.parent.type === 'CallExpression'
|
||||
&& !node.parent.optional
|
||||
&& node.parent.arguments[0] === node
|
||||
&& node.parent.callee.type === 'MemberExpression'
|
||||
&& !node.parent.callee.computed
|
||||
&& !node.parent.callee.optional
|
||||
&& node.parent.callee.property.type === 'Identifier'
|
||||
&& arrayMethodsWithBooleanCallback.has(node.parent.callee.property.name);
|
||||
|
||||
const isTypeScriptTypePredicateFunction = node =>
|
||||
node.returnType?.type === 'TSTypeAnnotation'
|
||||
&& node.returnType.typeAnnotation.type === 'TSTypePredicate';
|
||||
|
||||
function getCallExpression(node) {
|
||||
const firstParameterName = node.params[0].name;
|
||||
|
||||
// `(v) => String(v)`
|
||||
if (
|
||||
node.type === 'ArrowFunctionExpression'
|
||||
&& isNativeCoercionFunctionCall(node.body, firstParameterName)
|
||||
) {
|
||||
return node.body;
|
||||
}
|
||||
|
||||
// `(v) => {return String(v);}`
|
||||
// `function (v) {return String(v);}`
|
||||
if (
|
||||
node.body.type === 'BlockStatement'
|
||||
&& node.body.body.length === 1
|
||||
&& node.body.body[0].type === 'ReturnStatement'
|
||||
&& isNativeCoercionFunctionCall(node.body.body[0].argument, firstParameterName)
|
||||
) {
|
||||
return node.body.body[0].argument;
|
||||
}
|
||||
}
|
||||
|
||||
function getArrayCallbackProblem(node) {
|
||||
if (
|
||||
!isArrayIdentityCallback(node)
|
||||
|| isTypeScriptTypePredicateFunction(node)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
replacementFunction: 'Boolean',
|
||||
fix: fixer => fixer.replaceText(node, 'Boolean'),
|
||||
};
|
||||
}
|
||||
|
||||
function getCoercionFunctionProblem(node) {
|
||||
const callExpression = getCallExpression(node);
|
||||
|
||||
if (!callExpression) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {name} = callExpression.callee;
|
||||
|
||||
const problem = {replacementFunction: name};
|
||||
|
||||
if (node.type === 'FunctionDeclaration' || callExpression.arguments.length !== 1) {
|
||||
return problem;
|
||||
}
|
||||
|
||||
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
||||
problem.fix = fixer => {
|
||||
let text = name;
|
||||
|
||||
if (
|
||||
node.parent.type === 'Property'
|
||||
&& node.parent.method
|
||||
&& node.parent.value === node
|
||||
) {
|
||||
text = `: ${text}`;
|
||||
} else if (node.parent.type === 'MethodDefinition') {
|
||||
text = ` = ${text};`;
|
||||
}
|
||||
|
||||
return fixer.replaceText(node, text);
|
||||
};
|
||||
|
||||
return problem;
|
||||
}
|
||||
|
||||
/** @param {import('eslint').Rule.RuleContext} context */
|
||||
const create = context => {
|
||||
context.on(functionTypes, node => {
|
||||
if (
|
||||
node.async
|
||||
|| node.generator
|
||||
|| node.params.length === 0
|
||||
|| node.params[0].type !== 'Identifier'
|
||||
|| (
|
||||
(
|
||||
(
|
||||
node.parent.type === 'MethodDefinition'
|
||||
&& (node.parent.kind === 'constructor' || node.parent.kind === 'set')
|
||||
)
|
||||
|| (node.parent.type === 'Property' && node.parent.kind === 'set')
|
||||
)
|
||||
&& node.parent.value === node
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let problem = getArrayCallbackProblem(node) || getCoercionFunctionProblem(node);
|
||||
|
||||
if (!problem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {sourceCode} = context;
|
||||
const {replacementFunction, fix} = problem;
|
||||
|
||||
problem = {
|
||||
node,
|
||||
loc: getFunctionHeadLocation(node, sourceCode),
|
||||
messageId: MESSAGE_ID,
|
||||
data: {
|
||||
functionNameWithKind: getFunctionNameWithKind(node, sourceCode),
|
||||
replacementFunction,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
We do not fix if there are:
|
||||
- Comments: No proper place to put them.
|
||||
- Extra parameters: Removing them may break types.
|
||||
*/
|
||||
if (!fix || node.params.length !== 1 || sourceCode.getCommentsInside(node).length > 0) {
|
||||
return problem;
|
||||
}
|
||||
|
||||
problem.fix = fix;
|
||||
|
||||
return problem;
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
const config = {
|
||||
create,
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly.',
|
||||
recommended: 'unopinionated',
|
||||
},
|
||||
fixable: 'code',
|
||||
messages,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user