Skip to content

Commit

Permalink
Add prefer-array-some rule (#887)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Dec 24, 2020
1 parent 72e0390 commit b6a5a50
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 67 deletions.
27 changes: 27 additions & 0 deletions docs/rules/prefer-array-some.md
@@ -0,0 +1,27 @@
# Prefer `.some(…)` over `.find(…)`.

Prefer using [`Array#some`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) over [`Array#find`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) when ensuring at least one element in the array passes a given check.

## Fail

```js
if (array.find(element => element === '🦄')) {
//
}
```

```js
const foo = array.find(element => element === '🦄') ? bar : baz;
```

## Pass

```js
if (array.some(element => element === '🦄')) {
//
}
```

```js
const foo = array.find(element => element === '🦄') || bar;
```
1 change: 1 addition & 0 deletions index.js
Expand Up @@ -65,6 +65,7 @@ module.exports = {
'unicorn/numeric-separators-style': 'off',
'unicorn/prefer-add-event-listener': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-some': 'error',
'unicorn/prefer-dataset': 'error',
'unicorn/prefer-date-now': 'error',
'unicorn/prefer-default-parameters': 'error',
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Expand Up @@ -70,6 +70,7 @@ Configure it in `package.json`.
"unicorn/numeric-separators-style": "off",
"unicorn/prefer-add-event-listener": "error",
"unicorn/prefer-array-find": "error",
"unicorn/prefer-array-some": "error",
"unicorn/prefer-dataset": "error",
"unicorn/prefer-date-now": "error",
"unicorn/prefer-default-parameters": "error",
Expand Down Expand Up @@ -140,6 +141,7 @@ Configure it in `package.json`.
- [numeric-separators-style](docs/rules/numeric-separators-style.md) - Enforce the style of numeric separators by correctly grouping digits. *(fixable)*
- [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) - Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. *(partly fixable)*
- [prefer-array-find](docs/rules/prefer-array-find.md) - Prefer `.find(…)` over the first element from `.filter(…)`. *(partly fixable)*
- [prefer-array-some](docs/rules/prefer-array-some.md) - Prefer `.some(…)` over `.find(…)`.
- [prefer-dataset](docs/rules/prefer-dataset.md) - Prefer using `.dataset` on DOM elements over `.setAttribute(…)`. *(fixable)*
- [prefer-date-now](docs/rules/prefer-date-now.md) - Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch. *(fixable)*
- [prefer-default-parameters](docs/rules/prefer-default-parameters.md) - Prefer default parameters over reassignment. *(fixable)*
Expand Down
66 changes: 1 addition & 65 deletions rules/explicit-length-check.js
Expand Up @@ -2,6 +2,7 @@
const {isParenthesized} = require('eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url');
const isLiteralValue = require('./utils/is-literal-value');
const {isBooleanNode, getBooleanAncestor} = require('./utils/boolean');

const TYPE_NON_ZERO = 'non-zero';
const TYPE_ZERO = 'zero';
Expand All @@ -12,23 +13,6 @@ const messages = {
[MESSAGE_ID_SUGGESTION]: 'Replace `.length` with `.length {{code}}`.'
};

const isLogicNot = node =>
node &&
node.type === 'UnaryExpression' &&
node.operator === '!';
const isLogicNotArgument = node =>
isLogicNot(node.parent) &&
node.parent.argument === node;
const isBooleanCall = node =>
node &&
node.type === 'CallExpression' &&
node.callee &&
node.callee.type === 'Identifier' &&
node.callee.name === 'Boolean' &&
node.arguments.length === 1;
const isBooleanCallArgument = node =>
isBooleanCall(node.parent) &&
node.parent.arguments[0] === node;
const isCompareRight = (node, operator, value) =>
node.type === 'BinaryExpression' &&
node.operator === operator &&
Expand Down Expand Up @@ -72,23 +56,6 @@ const lengthSelector = [
'[property.name="length"]'
].join('');

function getBooleanAncestor(node) {
let isNegative = false;
// eslint-disable-next-line no-constant-condition
while (true) {
if (isLogicNotArgument(node)) {
isNegative = !isNegative;
node = node.parent;
} else if (isBooleanCallArgument(node)) {
node = node.parent;
} else {
break;
}
}

return {node, isNegative};
}

function getLengthCheckNode(node) {
node = node.parent;

Expand Down Expand Up @@ -135,37 +102,6 @@ function getLengthCheckNode(node) {
return {};
}

function isBooleanNode(node) {
if (
isLogicNot(node) ||
isLogicNotArgument(node) ||
isBooleanCall(node) ||
isBooleanCallArgument(node)
) {
return true;
}

const {parent} = node;
if (
(
parent.type === 'IfStatement' ||
parent.type === 'ConditionalExpression' ||
parent.type === 'WhileStatement' ||
parent.type === 'DoWhileStatement' ||
parent.type === 'ForStatement'
) &&
parent.test === node
) {
return true;
}

if (parent.type === 'LogicalExpression') {
return isBooleanNode(parent);
}

return false;
}

function create(context) {
const options = {
'non-zero': 'greater-than',
Expand Down
48 changes: 48 additions & 0 deletions rules/prefer-array-some.js
@@ -0,0 +1,48 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const methodSelector = require('./utils/method-selector');
const {isBooleanNode} = require('./utils/boolean');

const MESSAGE_ID_ERROR = 'error';
const MESSAGE_ID_SUGGESTION = 'suggestion';
const messages = {
[MESSAGE_ID_ERROR]: 'Prefer `.some(…)` over `.find(…)`.',
[MESSAGE_ID_SUGGESTION]: 'Replace `.find(…)` with `.some(…)`.'
};

const arrayFindCallSelector = methodSelector({
name: 'find',
min: 1,
max: 2
});

const create = context => {
return {
[arrayFindCallSelector](node) {
if (isBooleanNode(node)) {
node = node.callee.property;
context.report({
node,
messageId: MESSAGE_ID_ERROR,
suggest: [
{
messageId: MESSAGE_ID_SUGGESTION,
fix: fixer => fixer.replaceText(node, 'some')
}
]
});
}
}
};
};

module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
messages
}
};
4 changes: 2 additions & 2 deletions rules/prefer-event-key.js
Expand Up @@ -171,7 +171,7 @@ const create = context => {

if (
references &&
references.find(reference => isPropertyOf(node, reference.identifier))
references.some(reference => isPropertyOf(node, reference.identifier))
) {
report(node);
}
Expand Down Expand Up @@ -201,7 +201,7 @@ const create = context => {
// Make sure initObject is a reference of eventVariable
if (
references &&
references.find(reference => reference.identifier === initObject)
references.some(reference => reference.identifier === initObject)
) {
report(node.value);
return;
Expand Down
83 changes: 83 additions & 0 deletions rules/utils/boolean.js
@@ -0,0 +1,83 @@
'use strict';

const isLogicNot = node =>
node &&
node.type === 'UnaryExpression' &&
node.operator === '!';
const isLogicNotArgument = node =>
isLogicNot(node.parent) &&
node.parent.argument === node;
const isBooleanCallArgument = node =>
isBooleanCall(node.parent) &&
node.parent.arguments[0] === node;
const isBooleanCall = node =>
node &&
node.type === 'CallExpression' &&
node.callee &&
node.callee.type === 'Identifier' &&
node.callee.name === 'Boolean' &&
node.arguments.length === 1;

/**
Check if the value of node is a `boolean`.
@param {Node} node
@returns {boolean}
*/
function isBooleanNode(node) {
if (
isLogicNot(node) ||
isLogicNotArgument(node) ||
isBooleanCall(node) ||
isBooleanCallArgument(node)
) {
return true;
}

const {parent} = node;
if (
(
parent.type === 'IfStatement' ||
parent.type === 'ConditionalExpression' ||
parent.type === 'WhileStatement' ||
parent.type === 'DoWhileStatement' ||
parent.type === 'ForStatement'
) &&
parent.test === node
) {
return true;
}

if (parent.type === 'LogicalExpression') {
return isBooleanNode(parent);
}

return false;
}

/**
Get the boolean type-casting ancestor.
@typedef {{ node: Node, isNegative: boolean }} Result
@param {Node} node
@returns {Result}
*/
function getBooleanAncestor(node) {
let isNegative = false;
// eslint-disable-next-line no-constant-condition
while (true) {
if (isLogicNotArgument(node)) {
isNegative = !isNegative;
node = node.parent;
} else if (isBooleanCallArgument(node)) {
node = node.parent;
} else {
break;
}
}

return {node, isNegative};
}

module.exports = {isBooleanNode, getBooleanAncestor};

0 comments on commit b6a5a50

Please sign in to comment.