Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prefer-math-min-max #2432

Merged
merged 26 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bd6f6cc
Add `prefer-math-min-max`
axetroy Aug 21, 2024
fbefd9a
Update prefer-math-min-max.js
sindresorhus Aug 21, 2024
dc5aedc
simplify the switch case
axetroy Aug 21, 2024
7948804
Remove unnecessary placeholder
axetroy Aug 21, 2024
92babf1
update description
axetroy Aug 21, 2024
87815e9
fix edge case
axetroy Aug 21, 2024
48de8a4
Fix edge case in `prefer-math-min-max` rule
axetroy Aug 21, 2024
5164a5e
update docs
axetroy Aug 21, 2024
7cd3d39
remove context.report() with return statement
axetroy Aug 21, 2024
616e3d0
update docs
axetroy Aug 21, 2024
f0cf4bc
refactor code to improve readability and performance
axetroy Aug 21, 2024
9b27968
support edge case `(0,foo) > 10 ? 10 : (0,foo)`
axetroy Aug 21, 2024
82130fd
refactor code to improve readability and performance
axetroy Aug 21, 2024
6b7c587
add more test case
axetroy Aug 21, 2024
1dd9b4c
add more edge test case
axetroy Aug 22, 2024
63b9d31
add more edge case
axetroy Aug 22, 2024
f5afe1e
refactor code to use fixSpaceAroundKeyword in prefer-math-min-max rule
axetroy Aug 22, 2024
f88e226
improve codecov
axetroy Aug 22, 2024
49d6e0e
Simplify the code
axetroy Aug 22, 2024
2d8b9aa
rename `reportPreferMathMinOrMax` to `getProblem`
axetroy Aug 22, 2024
9feae75
refactor prefer-math-min-max rule to improve readability and performance
axetroy Aug 22, 2024
9bdef77
refactor prefer-math-min-max rule for improved readability and perfor…
axetroy Aug 22, 2024
9544a1d
update format
axetroy Aug 22, 2024
6ae06c6
Add test
fisker Aug 23, 2024
8d08770
Fix tests / Simplify rule
fisker Aug 23, 2024
a58b6c8
Linting
fisker Aug 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions docs/rules/prefer-math-min-max.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Prefer `Math.min()` and `Math.max()` over ternary expressions for simple comparisons
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Prefer `Math.min()` and `Math.max()` over ternary expressions for simple comparisons
# Prefer `Math.min()` and `Math.max()` over ternaries for simple comparisons


💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs).

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

<!-- end auto-generated rule header -->
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->

Enforce the use of `Math.min()` and `Math.max()` instead of ternary expressions for simple comparisons. This makes the code more readable.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is repeating the title.


## Examples

<!-- Math.min() -->

```js
height > 50 ? 50 : height; // ❌
Math.min(height, 50); // ✅
```

```js
height >= 50 ? 50 : height; // ❌
Math.min(height, 50); // ✅
```

```js
height < 50 ? height : 50; // ❌
Math.min(height, 50); // ✅
```

```js
height <= 50 ? height : 50; // ❌
Math.min(height, 50); // ✅
```

<!-- Math.max() -->

```js
height > 50 ? height : 50; // ❌
Math.max(height, 50); // ✅
```

```js
height >= 50 ? height : 50; // ❌
Math.max(height, 50); // ✅
```

```js
height < 50 ? 50 : height; // ❌
Math.max(height, 50); // ✅
```

```js
height <= 50 ? 50 : height; // ❌
Math.max(height, 50); // ✅
```
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
| [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | 🔧 | |
| [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | ✅ | 🔧 | |
| [prefer-logical-operator-over-ternary](docs/rules/prefer-logical-operator-over-ternary.md) | Prefer using a logical operator over a ternary. | ✅ | | 💡 |
| [prefer-math-min-max](docs/rules/prefer-math-min-max.md) | Prefer `Math.min()` and `Math.max()` over ternary expressions for simple comparisons. | ✅ | 🔧 | |
| [prefer-math-trunc](docs/rules/prefer-math-trunc.md) | Enforce the use of `Math.trunc` instead of bitwise operators. | ✅ | 🔧 | 💡 |
| [prefer-modern-dom-apis](docs/rules/prefer-modern-dom-apis.md) | Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`. | ✅ | 🔧 | |
| [prefer-modern-math-apis](docs/rules/prefer-modern-math-apis.md) | Prefer modern `Math` APIs over legacy patterns. | ✅ | 🔧 | |
Expand Down
96 changes: 96 additions & 0 deletions rules/prefer-math-min-max.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
'use strict';

const MESSAGE_ID = 'prefer-math-min-max';
const messages = {
[MESSAGE_ID]: 'Prefer `{{replacement}}` to simplify ternary expressions.',
};

/**

@param {import('eslint').Rule.RuleContext} context
@param {import('estree').ConditionalExpression} node
@param {string} method
*/
function reportPreferMathMinOrMax(context, node, left, right, method) {
const {sourceCode} = context;

context.report({
fisker marked this conversation as resolved.
Show resolved Hide resolved
node,
messageId: MESSAGE_ID,
data: {
replacement: `${method}()`,
value: sourceCode.getText(node),
fisker marked this conversation as resolved.
Show resolved Hide resolved
},
fix: fixer => fixer.replaceText(node, `${method}(${sourceCode.getText(left)}, ${sourceCode.getText(right)})`),
});
}

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
/** @param {import('estree').ConditionalExpression} node */
ConditionalExpression(node) {
const {test, consequent, alternate} = node;

if (test.type !== 'BinaryExpression') {
return;
}

const {sourceCode} = context;
const {operator, left, right} = test;

const leftCode = sourceCode.getText(left);
const rightCode = sourceCode.getText(right);
const alternateCode = sourceCode.getText(alternate);
const consequentCode = sourceCode.getText(consequent);

switch (operator) {
fisker marked this conversation as resolved.
Show resolved Hide resolved
case '>':
case '>=': {
if (leftCode === alternateCode && rightCode === consequentCode) {
// Example `height > 50 ? 50 : height`
// Prefer `Math.min()`
reportPreferMathMinOrMax(context, node, left, right, 'Math.min');
} else if (leftCode === consequentCode && rightCode === alternateCode) {
// Example `height > 50 ? height : 50`
// Prefer `Math.max()`
reportPreferMathMinOrMax(context, node, left, right, 'Math.max');
}

break;
}

case '<':
case '<=': {
if (leftCode === consequentCode && rightCode === alternateCode) {
// Example `height < 50 ? height : 50`
// Prefer `Math.min()`
reportPreferMathMinOrMax(context, node, left, right, 'Math.min');
} else if (leftCode === alternateCode && rightCode === consequentCode) {
// Example `height < 50 ? 50 : height`
// Prefer `Math.max()`
reportPreferMathMinOrMax(context, node, left, right, 'Math.max');
}

break;
}

default: {
break;
}
}
},
});

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
meta: {
type: 'problem',
docs: {
description: 'Prefer `Math.min()` and `Math.max()` over ternary expressions for simple comparisons.',
recommended: true,
},
fixable: 'code',
messages,
},
};
1 change: 1 addition & 0 deletions test/package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const RULES_WITHOUT_PASS_FAIL_SECTIONS = new Set([
'filename-case',
// Intended to not use `pass`/`fail` section in this rule.
'prefer-modern-math-apis',
'prefer-math-min-max',
]);

test('Every rule is defined in index file in alphabetical order', t => {
Expand Down
35 changes: 35 additions & 0 deletions test/prefer-math-min-max.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {getTester} from './utils/test.mjs';

const {test} = getTester(import.meta);

test.snapshot({
valid: [
'height > 10 ? height : 20',
'height > 50 ? Math.min(50, height) : height',
],
invalid: [
// Prefer `Math.min()`
'height > 50 ? 50 : height',
'height >= 50 ? 50 : height',
'height < 50 ? height : 50',
'height <= 50 ? height : 50',

// Prefer `Math.min()`
'height > maxHeight ? maxHeight : height',
'height < maxHeight ? height : maxHeight',

// Prefer `Math.min()`
'window.height > 50 ? 50 : window.height',
'window.height < 50 ? window.height : 50',

// Prefer `Math.max()`
'height > 50 ? height : 50',
'height >= 50 ? height : 50',
'height < 50 ? 50 : height',
'height <= 50 ? 50 : height',

// Prefer `Math.max()`
'height > maxHeight ? height : maxHeight',
'height < maxHeight ? maxHeight : height',
],
});
Loading