139 lines
3.6 KiB
JavaScript
139 lines
3.6 KiB
JavaScript
'use strict';
|
|
const {getParenthesizedText} = require('./utils/parentheses.js');
|
|
|
|
const MESSAGE_ID = 'prefer-modern-math-apis';
|
|
const messages = {
|
|
[MESSAGE_ID]: 'Prefer `{{replacement}}` over `{{description}}`.',
|
|
};
|
|
|
|
const isMathProperty = (node, property) =>
|
|
node.type === 'MemberExpression'
|
|
&& !node.optional
|
|
&& !node.computed
|
|
&& node.object.type === 'Identifier'
|
|
&& node.object.name === 'Math'
|
|
&& node.property.type === 'Identifier'
|
|
&& node.property.name === property;
|
|
|
|
const isMathMethodCall = (node, method) =>
|
|
node.type === 'CallExpression'
|
|
&& !node.optional
|
|
&& isMathProperty(node.callee, method)
|
|
&& node.arguments.length === 1
|
|
&& node.arguments[0].type !== 'SpreadElement';
|
|
|
|
// `Math.log(x) * Math.LOG10E` -> `Math.log10(x)`
|
|
// `Math.LOG10E * Math.log(x)` -> `Math.log10(x)`
|
|
// `Math.log(x) * Math.LOG2E` -> `Math.log2(x)`
|
|
// `Math.LOG2E * Math.log(x)` -> `Math.log2(x)`
|
|
function createLogCallTimesConstantCheck({constantName, replacementMethod}) {
|
|
const replacement = `Math.${replacementMethod}(…)`;
|
|
|
|
return function (node, context) {
|
|
if (!(node.type === 'BinaryExpression' && node.operator === '*')) {
|
|
return;
|
|
}
|
|
|
|
let mathLogCall;
|
|
let description;
|
|
if (isMathMethodCall(node.left, 'log') && isMathProperty(node.right, constantName)) {
|
|
mathLogCall = node.left;
|
|
description = `Math.log(…) * Math.${constantName}`;
|
|
} else if (isMathMethodCall(node.right, 'log') && isMathProperty(node.left, constantName)) {
|
|
mathLogCall = node.right;
|
|
description = `Math.${constantName} * Math.log(…)`;
|
|
}
|
|
|
|
if (!mathLogCall) {
|
|
return;
|
|
}
|
|
|
|
const [valueNode] = mathLogCall.arguments;
|
|
|
|
return {
|
|
node,
|
|
messageId: MESSAGE_ID,
|
|
data: {
|
|
replacement,
|
|
description,
|
|
},
|
|
fix: fixer => fixer.replaceText(node, `Math.${replacementMethod}(${getParenthesizedText(valueNode, context.getSourceCode())})`),
|
|
};
|
|
};
|
|
}
|
|
|
|
// `Math.log(x) / Math.LN10` -> `Math.log10(x)`
|
|
// `Math.log(x) / Math.LN2` -> `Math.log2(x)`
|
|
function createLogCallDivideConstantCheck({constantName, replacementMethod}) {
|
|
const message = {
|
|
messageId: MESSAGE_ID,
|
|
data: {
|
|
replacement: `Math.${replacementMethod}(…)`,
|
|
description: `Math.log(…) / Math.${constantName}`,
|
|
},
|
|
};
|
|
|
|
return function (node, context) {
|
|
if (
|
|
!(
|
|
node.type === 'BinaryExpression'
|
|
&& node.operator === '/'
|
|
&& isMathMethodCall(node.left, 'log')
|
|
&& isMathProperty(node.right, constantName)
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const [valueNode] = node.left.arguments;
|
|
|
|
return {
|
|
...message,
|
|
node,
|
|
fix: fixer => fixer.replaceText(node, `Math.${replacementMethod}(${getParenthesizedText(valueNode, context.getSourceCode())})`),
|
|
};
|
|
};
|
|
}
|
|
|
|
const checkFunctions = [
|
|
createLogCallTimesConstantCheck({constantName: 'LOG10E', replacementMethod: 'log10'}),
|
|
createLogCallTimesConstantCheck({constantName: 'LOG2E', replacementMethod: 'log2'}),
|
|
createLogCallDivideConstantCheck({constantName: 'LN10', replacementMethod: 'log10'}),
|
|
createLogCallDivideConstantCheck({constantName: 'LN2', replacementMethod: 'log2'}),
|
|
];
|
|
|
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
const create = context => {
|
|
const nodes = [];
|
|
|
|
return {
|
|
BinaryExpression(node) {
|
|
nodes.push(node);
|
|
},
|
|
* 'Program:exit'() {
|
|
for (const node of nodes) {
|
|
for (const getProblem of checkFunctions) {
|
|
const problem = getProblem(node, context);
|
|
|
|
if (problem) {
|
|
yield problem;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
};
|
|
};
|
|
|
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
module.exports = {
|
|
create,
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
description: 'Prefer modern `Math` APIs over legacy patterns.',
|
|
},
|
|
fixable: 'code',
|
|
messages,
|
|
},
|
|
};
|