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-array-some rule #887

Merged
merged 16 commits into from Dec 24, 2020
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};