123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- 'use strict';
- const {getFunctionHeadLocation, getFunctionNameWithKind} = require('@eslint-community/eslint-utils');
- const {not} = require('./selectors/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);
- 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;
- }
- }
- const functionsSelector = [
- ':function',
- '[async!=true]',
- '[generator!=true]',
- '[params.length>0]',
- '[params.0.type="Identifier"]',
- not([
- 'MethodDefinition[kind="constructor"] > .value',
- 'MethodDefinition[kind="set"] > .value',
- 'Property[kind="set"] > .value',
- ]),
- ].join('');
- function getArrayCallbackProblem(node) {
- if (!isArrayIdentityCallback(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 => ({
- [functionsSelector](node) {
- let problem = getArrayCallbackProblem(node) || getCoercionFunctionProblem(node);
- if (!problem) {
- return;
- }
- const sourceCode = context.getSourceCode();
- 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} */
- module.exports = {
- create,
- meta: {
- type: 'suggestion',
- docs: {
- description: 'Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly.',
- },
- fixable: 'code',
- messages,
- },
- };
|