1 | 'use strict';
|
2 | const {findVariable} = require('eslint-utils');
|
3 | const getDocumentationUrl = require('./utils/get-documentation-url');
|
4 | const getVariableIdentifiers = require('./utils/get-variable-identifiers');
|
5 | const methodSelector = require('./utils/method-selector');
|
6 |
|
7 |
|
8 | const arrayExpressionSelector = [
|
9 | '[init.type="ArrayExpression"]'
|
10 | ].join('');
|
11 |
|
12 |
|
13 | const ArraySelector = [
|
14 | '[init.type="CallExpression"]',
|
15 | '[init.callee.type="Identifier"]',
|
16 | '[init.callee.name="Array"]'
|
17 | ].join('');
|
18 |
|
19 |
|
20 | const newArraySelector = [
|
21 | '[init.type="NewExpression"]',
|
22 | '[init.callee.type="Identifier"]',
|
23 | '[init.callee.name="Array"]'
|
24 | ].join('');
|
25 |
|
26 |
|
27 |
|
28 | const arrayStaticMethodSelector = methodSelector({
|
29 | object: 'Array',
|
30 | names: ['from', 'of'],
|
31 | property: 'init'
|
32 | });
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | const arrayMethodSelector = methodSelector({
|
46 | names: [
|
47 | 'concat',
|
48 | 'copyWithin',
|
49 | 'fill',
|
50 | 'filter',
|
51 | 'flat',
|
52 | 'flatMap',
|
53 | 'map',
|
54 | 'reverse',
|
55 | 'slice',
|
56 | 'sort',
|
57 | 'splice'
|
58 | ],
|
59 | property: 'init'
|
60 | });
|
61 |
|
62 | const selector = [
|
63 | 'VariableDeclaration',
|
64 |
|
65 | `:not(${
|
66 | [
|
67 | 'ExportNamedDeclaration',
|
68 | '>',
|
69 | 'VariableDeclaration.declaration'
|
70 | ].join('')
|
71 | })`,
|
72 | '>',
|
73 | 'VariableDeclarator.declarations',
|
74 | `:matches(${
|
75 | [
|
76 | arrayExpressionSelector,
|
77 | ArraySelector,
|
78 | newArraySelector,
|
79 | arrayStaticMethodSelector,
|
80 | arrayMethodSelector
|
81 | ].join(',')
|
82 | })`,
|
83 | '>',
|
84 | 'Identifier.id'
|
85 | ].join('');
|
86 |
|
87 | const MESSAGE_ID = 'preferSetHas';
|
88 |
|
89 | const isIncludesCall = node => {
|
90 |
|
91 | if (!node.parent || !node.parent.parent) {
|
92 | return false;
|
93 | }
|
94 |
|
95 | const {type, optional, callee, arguments: parameters} = node.parent.parent;
|
96 | return (
|
97 | type === 'CallExpression' &&
|
98 | !optional,
|
99 | callee &&
|
100 | callee.type === 'MemberExpression' &&
|
101 | !callee.computed &&
|
102 | callee.object === node &&
|
103 | callee.property.type === 'Identifier' &&
|
104 | callee.property.name === 'includes' &&
|
105 | parameters.length === 1 &&
|
106 | parameters[0].type !== 'SpreadElement'
|
107 | );
|
108 | };
|
109 |
|
110 | const create = context => {
|
111 | return {
|
112 | [selector]: node => {
|
113 | const variable = findVariable(context.getScope(), node);
|
114 | const identifiers = getVariableIdentifiers(variable).filter(identifier => identifier !== node);
|
115 |
|
116 | if (
|
117 | identifiers.length === 0 ||
|
118 | identifiers.some(node => !isIncludesCall(node))
|
119 | ) {
|
120 | return;
|
121 | }
|
122 |
|
123 | context.report({
|
124 | node,
|
125 | messageId: MESSAGE_ID,
|
126 | data: {
|
127 | name: node.name
|
128 | },
|
129 | fix: fixer => [
|
130 | fixer.insertTextBefore(node.parent.init, 'new Set('),
|
131 | fixer.insertTextAfter(node.parent.init, ')'),
|
132 | ...identifiers.map(identifier => fixer.replaceText(identifier.parent.property, 'has'))
|
133 | ]
|
134 | });
|
135 | }
|
136 | };
|
137 | };
|
138 |
|
139 | module.exports = {
|
140 | create,
|
141 | meta: {
|
142 | type: 'suggestion',
|
143 | docs: {
|
144 | url: getDocumentationUrl(__filename)
|
145 | },
|
146 | fixable: 'code',
|
147 | messages: {
|
148 | [MESSAGE_ID]: '`{{name}}` should be a `Set`, and use `{{name}}.has()` to check existence or non-existence.'
|
149 | }
|
150 | }
|
151 | };
|