eslint-plugin-unicorn
Version:
Various awesome ESLint rules
155 lines (134 loc) • 3.46 kB
JavaScript
;
const isShadowed = require('./utils/is-shadowed.js');
const {
referenceIdentifierSelector,
callExpressionSelector,
} = require('./selectors/index.js');
const {replaceReferenceIdentifier} = require('./fix/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const METHOD_ERROR_MESSAGE_ID = 'method-error';
const METHOD_SUGGESTION_MESSAGE_ID = 'method-suggestion';
const PROPERTY_ERROR_MESSAGE_ID = 'property-error';
const messages = {
[METHOD_ERROR_MESSAGE_ID]: 'Prefer `Number.{{name}}()` over `{{name}}()`.',
[METHOD_SUGGESTION_MESSAGE_ID]: 'Replace `{{name}}()` with `Number.{{name}}()`.',
[PROPERTY_ERROR_MESSAGE_ID]: 'Prefer `Number.{{property}}` over `{{identifier}}`.',
};
const methods = {
// Safe
parseInt: true,
parseFloat: true,
// Unsafe
isNaN: false,
isFinite: false,
};
const methodsSelector = [
callExpressionSelector(Object.keys(methods)),
' > ',
'.callee',
].join('');
const propertiesSelector = referenceIdentifierSelector(['NaN', 'Infinity']);
const isNegative = node => {
const {parent} = node;
return parent && parent.type === 'UnaryExpression' && parent.operator === '-' && parent.argument === node;
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const sourceCode = context.getSourceCode();
const options = {
checkInfinity: true,
...context.options[0],
};
// Cache `NaN` and `Infinity` in `foo = {NaN, Infinity}`
const reported = new WeakSet();
return {
[methodsSelector]: node => {
if (isShadowed(context.getScope(), node)) {
return;
}
const {name} = node;
const isSafe = methods[name];
const problem = {
node,
messageId: METHOD_ERROR_MESSAGE_ID,
data: {
name,
},
};
const fix = fixer => replaceReferenceIdentifier(node, `Number.${name}`, fixer, sourceCode);
if (isSafe) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: METHOD_SUGGESTION_MESSAGE_ID,
data: {
name,
},
fix,
},
];
}
return problem;
},
[propertiesSelector]: node => {
if (reported.has(node) || isShadowed(context.getScope(), node)) {
return;
}
const {name, parent} = node;
if (name === 'Infinity' && !options.checkInfinity) {
return;
}
let property = name;
if (name === 'Infinity') {
property = isNegative(node) ? 'NEGATIVE_INFINITY' : 'POSITIVE_INFINITY';
}
const problem = {
node,
messageId: PROPERTY_ERROR_MESSAGE_ID,
data: {
identifier: name,
property,
},
};
if (property === 'NEGATIVE_INFINITY') {
problem.node = parent;
problem.data.identifier = '-Infinity';
problem.fix = function * (fixer) {
yield fixer.replaceText(parent, 'Number.NEGATIVE_INFINITY');
yield * fixSpaceAroundKeyword(fixer, parent, sourceCode);
};
} else {
problem.fix = fixer => replaceReferenceIdentifier(node, `Number.${property}`, fixer, sourceCode);
}
reported.add(node);
return problem;
},
};
};
const schema = [
{
type: 'object',
additionalProperties: false,
properties: {
checkInfinity: {
type: 'boolean',
default: true,
},
},
},
];
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Prefer `Number` static properties over global ones.',
},
fixable: 'code',
hasSuggestions: true,
schema,
messages,
},
};