routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+230
@@ -0,0 +1,230 @@
|
||||
/* eslint-disable complexity */
|
||||
import {isBigIntLiteral, isCallExpression, isNewExpression} from './ast/index.js';
|
||||
import {fixSpaceAroundKeyword} from './fix/index.js';
|
||||
|
||||
const MESSAGE_ID = 'prefer-math-min-max';
|
||||
const messages = {
|
||||
[MESSAGE_ID]: 'Prefer `Math.{{method}}()` to simplify ternary expressions.',
|
||||
};
|
||||
|
||||
const isNumberTypeAnnotation = typeAnnotation => {
|
||||
if (typeAnnotation.type === 'TSNumberKeyword') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeAnnotation.type === 'TSTypeAnnotation' && typeAnnotation.typeAnnotation.type === 'TSNumberKeyword') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeAnnotation.type === 'TSTypeReference' && typeAnnotation.typeName.name === 'Number') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
function unwrapNode(node) {
|
||||
if (
|
||||
node.type === 'TSAsExpression'
|
||||
|| node.type === 'TSTypeAssertion'
|
||||
|| node.type === 'TSNonNullExpression'
|
||||
) {
|
||||
return unwrapNode(node.expression);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function getTypeAnnotation(node) {
|
||||
if (node.type === 'TSNonNullExpression') {
|
||||
return getTypeAnnotation(node.expression);
|
||||
}
|
||||
|
||||
if (node.type === 'TSAsExpression' || node.type === 'TSTypeAssertion') {
|
||||
return node.typeAnnotation;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {import('eslint').Rule.RuleContext} context */
|
||||
const create = context => {
|
||||
context.on('ConditionalExpression', /** @param {import('estree').ConditionalExpression} conditionalExpression */ conditionalExpression => {
|
||||
const {test, consequent, alternate} = conditionalExpression;
|
||||
|
||||
if (test.type !== 'BinaryExpression') {
|
||||
return;
|
||||
}
|
||||
|
||||
const {operator, left, right} = test;
|
||||
|
||||
const hasBigInt = [left, right].some(node =>
|
||||
isBigIntLiteral(node)
|
||||
|| isCallExpression(node, {
|
||||
name: 'BigInt',
|
||||
argumentsLength: 1,
|
||||
optional: false,
|
||||
}));
|
||||
|
||||
if (hasBigInt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasDate = [left, right].some(node => isNewExpression(node, {name: 'Date'}));
|
||||
|
||||
if (hasDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [leftText, rightText, alternateText, consequentText] = [left, right, alternate, consequent].map(node => context.sourceCode.getText(unwrapNode(node)));
|
||||
|
||||
const isGreaterOrEqual = operator === '>' || operator === '>=';
|
||||
const isLessOrEqual = operator === '<' || operator === '<=';
|
||||
|
||||
let method;
|
||||
|
||||
// Prefer `Math.min()`
|
||||
if (
|
||||
// `height > 50 ? 50 : height`
|
||||
(isGreaterOrEqual && leftText === alternateText && rightText === consequentText)
|
||||
// `height < 50 ? height : 50`
|
||||
|| (isLessOrEqual && leftText === consequentText && rightText === alternateText)
|
||||
) {
|
||||
method = 'min';
|
||||
} else if (
|
||||
// `height > 50 ? height : 50`
|
||||
(isGreaterOrEqual && leftText === consequentText && rightText === alternateText)
|
||||
// `height < 50 ? 50 : height`
|
||||
|| (isLessOrEqual && leftText === alternateText && rightText === consequentText)
|
||||
) {
|
||||
method = 'max';
|
||||
}
|
||||
|
||||
if (!method) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const node of [left, right]) {
|
||||
const expressionNode = unwrapNode(node);
|
||||
const typeAnnotation = getTypeAnnotation(node);
|
||||
if (
|
||||
node !== expressionNode
|
||||
&& typeAnnotation
|
||||
&& !isNumberTypeAnnotation(typeAnnotation)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find variable declaration
|
||||
if (expressionNode.type === 'Identifier') {
|
||||
const variable = context.sourceCode.getScope(expressionNode).variables.find(variable => variable.name === expressionNode.name);
|
||||
|
||||
for (const definition of variable?.defs ?? []) {
|
||||
switch (definition.type) {
|
||||
case 'Parameter': {
|
||||
const identifier = definition.name;
|
||||
|
||||
/**
|
||||
Capture the following statement
|
||||
|
||||
```js
|
||||
function foo(a: number) {}
|
||||
```
|
||||
*/
|
||||
if (identifier.typeAnnotation?.type === 'TSTypeAnnotation' && !isNumberTypeAnnotation(identifier.typeAnnotation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
Capture the following statement
|
||||
|
||||
```js
|
||||
function foo(a = 10) {}
|
||||
```
|
||||
*/
|
||||
if (identifier.parent.type === 'AssignmentPattern' && identifier.parent.right.type === 'Literal' && typeof identifier.parent.right.value !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Variable': {
|
||||
/** @type {import('estree').VariableDeclarator} */
|
||||
const variableDeclarator = definition.node;
|
||||
|
||||
/**
|
||||
Capture the following statement
|
||||
|
||||
```js
|
||||
var foo: number
|
||||
```
|
||||
*/
|
||||
if (variableDeclarator.id.typeAnnotation?.type === 'TSTypeAnnotation' && !isNumberTypeAnnotation(variableDeclarator.id.typeAnnotation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
Capture the following statement
|
||||
|
||||
```js
|
||||
var foo = 10
|
||||
```
|
||||
*/
|
||||
if (variableDeclarator.init?.type === 'Literal' && typeof variableDeclarator.init.value !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
Capture the following statement
|
||||
|
||||
```js
|
||||
var foo = new Date()
|
||||
```
|
||||
*/
|
||||
if (isNewExpression(variableDeclarator.init, {name: 'Date'})) {
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
node: conditionalExpression,
|
||||
messageId: MESSAGE_ID,
|
||||
data: {method},
|
||||
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
||||
* fix(fixer) {
|
||||
const {sourceCode} = context;
|
||||
|
||||
yield fixSpaceAroundKeyword(fixer, conditionalExpression, context);
|
||||
|
||||
const argumentsText = [left, right]
|
||||
.map(node => node.type === 'SequenceExpression' ? `(${sourceCode.getText(node)})` : sourceCode.getText(node))
|
||||
.join(', ');
|
||||
|
||||
yield fixer.replaceText(conditionalExpression, `Math.${method}(${argumentsText})`);
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
const config = {
|
||||
create,
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'Prefer `Math.min()` and `Math.max()` over ternaries for simple comparisons.',
|
||||
recommended: 'unopinionated',
|
||||
},
|
||||
fixable: 'code',
|
||||
messages,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user