Skip to content

Commit

Permalink
Merge df713a9 into 7aa2dc4
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Mar 14, 2020
2 parents 7aa2dc4 + df713a9 commit 992d566
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 7 deletions.
31 changes: 31 additions & 0 deletions docs/rules/prefer-set-has.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence.

[`Set#has()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) is faster than [`Array#includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes).

This rule is fixable.

## Fail

```js
const array = [1, 2, 3];
function isExists(find) {
return array.includes(find);
}
```

## Pass

```js
const set = new Set([1, 2, 3]);
function isExists(find) {
return set.has(find);
}
```

```js
const array = [1, 2];
function isExists(find) {
return array.includes(find);
}
array.push(3);
```
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ module.exports = {
'unicorn/prefer-reflect-apply': 'error',
// TODO: Enable this by default when it's shipping in a Node.js LTS version.
'unicorn/prefer-replace-all': 'off',
'unicorn/prefer-set-has': 'error',
'unicorn/prefer-spread': 'error',
'unicorn/prefer-starts-ends-with': 'error',
'unicorn/prefer-string-slice': 'error',
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Configure it in `package.json`.
"unicorn/prefer-query-selector": "error",
"unicorn/prefer-reflect-apply": "error",
"unicorn/prefer-replace-all": "off",
"unicorn/prefer-set-has": "error",
"unicorn/prefer-spread": "error",
"unicorn/prefer-starts-ends-with": "error",
"unicorn/prefer-string-slice": "error",
Expand Down Expand Up @@ -126,6 +127,7 @@ Configure it in `package.json`.
- [prefer-query-selector](docs/rules/prefer-query-selector.md) - Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`. *(partly fixable)*
- [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) - Prefer `Reflect.apply()` over `Function#apply()`. *(fixable)*
- [prefer-replace-all](docs/rules/prefer-replace-all.md) - Prefer `String#replaceAll()` over regex searches with the global flag. *(fixable)*
- [prefer-set-has](docs/rules/prefer-set-has.md) - Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. *(fixable)*
- [prefer-spread](docs/rules/prefer-spread.md) - Prefer the spread operator over `Array.from()`. *(fixable)*
- [prefer-starts-ends-with](docs/rules/prefer-starts-ends-with.md) - Prefer `String#startsWith()` & `String#endsWith()` over more complex alternatives.
- [prefer-string-slice](docs/rules/prefer-string-slice.md) - Prefer `String#slice()` over `String#substr()` and `String#substring()`. *(partly fixable)*
Expand Down
5 changes: 1 addition & 4 deletions rules/consistent-function-scoping.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const getReferences = require('./utils/get-references');

const MESSAGE_ID_NAMED = 'named';
const MESSAGE_ID_ANONYMOUS = 'anonymous';

const getReferences = scope => scope.references.concat(
...scope.childScopes.map(scope => getReferences(scope))
);

const isSameScope = (scope1, scope2) =>
scope1 && scope2 && (scope1 === scope2 || scope1.block === scope2.block);

Expand Down
6 changes: 3 additions & 3 deletions rules/prefer-event-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
const getDocumentationUrl = require('./utils/get-documentation-url');
const quoteString = require('./utils/quote-string');

const keys = [
const keys = new Set([
'keyCode',
'charCode',
'which'
];
]);

// https://github.com/facebook/react/blob/b87aabd/packages/react-dom/src/events/getEventKey.js#L36
// Only meta characters which can't be deciphered from `String.fromCharCode()`
Expand Down Expand Up @@ -173,7 +173,7 @@ const create = context => {
Property(node) {
// Destructured case
const propertyName = node.value && node.value.name;
if (!keys.includes(propertyName)) {
if (!keys.has(propertyName)) {
return;
}

Expand Down
97 changes: 97 additions & 0 deletions rules/prefer-set-has.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const getReferences = require('./utils/get-references');

const selector = [
':not(ExportNamedDeclaration)',
'>',
'VariableDeclaration',
'>',
'VariableDeclarator',
'[init.type="ArrayExpression"]',
'>',
'Identifier'
].join('');

const MESSAGE_ID = 'preferSetHas';

const isIncludesCall = node => {
/* istanbul ignore next */
if (!node.parent || !node.parent.parent) {
return false;
}

const {type, optional, callee, arguments: parameters} = node.parent.parent;
return (
type === 'CallExpression' &&
!optional,
callee &&
callee.type === 'MemberExpression' &&
!callee.computed &&
callee.object === node &&
callee.property.type === 'Identifier' &&
callee.property.name === 'includes' &&
parameters.length === 1 &&
parameters[0].type !== 'SpreadElement'
);
};

const create = context => {
const scope = context.getScope();
const declarations = new Set();

return {
[selector]: node => {
declarations.add(node);
},
'Program:exit'() {
if (declarations.size === 0) {
return;
}

const references = getReferences(scope);
for (const declaration of declarations) {
declarations.delete(declaration);

const variable = references
.find(({identifier}) => identifier === declaration)
.resolved;
const nodes = variable.references
.map(({identifier}) => identifier)
.filter(node => node !== declaration);

if (
nodes.length > 0 &&
nodes.every(node => isIncludesCall(node))
) {
context.report({
node: declaration,
messageId: MESSAGE_ID,
data: {
name: declaration.name
},
fix: fixer => [
fixer.insertTextBefore(declaration.parent.init, 'new Set('),
fixer.insertTextAfter(declaration.parent.init, ')'),
...nodes.map(node => fixer.replaceText(node.parent.property, 'has'))
]
});
}
}
}
};
};

module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages: {
[MESSAGE_ID]: '`{{name}}` should be a `Set`, and use `{{name}}.has()` to check existence or non-existence.'
}
}
};
10 changes: 10 additions & 0 deletions rules/utils/get-references.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';
const {uniq} = require('lodash');

const getReferences = scope => uniq(
scope.references.concat(
...scope.childScopes.map(scope => getReferences(scope))
)
);

module.exports = getReferences;

0 comments on commit 992d566

Please sign in to comment.