eslint-plugin-unicorn 64.0.0 → 65.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +14 -0
- package/package.json +7 -3
- package/readme.md +183 -151
- package/rules/better-dom-traversing.js +291 -0
- package/rules/consistent-compound-words.js +452 -0
- package/rules/consistent-destructuring.js +97 -9
- package/rules/consistent-function-scoping.js +20 -0
- package/rules/consistent-json-file-read.js +245 -0
- package/rules/custom-error-definition.js +89 -4
- package/rules/dom-node-dataset.js +431 -0
- package/rules/escape-case.js +1 -1
- package/rules/expiring-todo-comments.js +42 -81
- package/rules/explicit-length-check.js +139 -6
- package/rules/filename-case.js +95 -21
- package/rules/import-style.js +21 -1
- package/rules/index.js +33 -3
- package/rules/isolated-functions.js +7 -5
- package/rules/no-anonymous-default-export.js +6 -6
- package/rules/no-array-callback-reference.js +89 -23
- package/rules/no-array-fill-with-reference-type.js +127 -0
- package/rules/no-array-for-each.js +17 -1
- package/rules/no-array-from-fill.js +76 -0
- package/rules/no-array-reduce.js +508 -2
- package/rules/no-blob-to-file.js +299 -0
- package/rules/no-canvas-to-image.js +152 -0
- package/rules/no-confusing-array-splice.js +164 -0
- package/rules/no-duplicate-set-values.js +57 -0
- package/rules/no-empty-file.js +31 -2
- package/rules/no-exports-in-scripts.js +34 -0
- package/rules/no-hex-escape.js +1 -1
- package/rules/no-incorrect-query-selector.js +347 -0
- package/rules/no-invalid-file-input-accept.js +475 -0
- package/rules/no-keyword-prefix.js +20 -5
- package/rules/no-late-current-target-access.js +166 -0
- package/rules/no-lonely-if.js +151 -50
- package/rules/no-manually-wrapped-comments.js +151 -0
- package/rules/no-null.js +14 -3
- package/rules/no-this-outside-of-class.js +75 -0
- package/rules/no-typeof-undefined.js +6 -4
- package/rules/no-unnecessary-nested-ternary.js +132 -0
- package/rules/no-unnecessary-polyfills.js +66 -20
- package/rules/no-unreadable-iife.js +33 -3
- package/rules/no-unused-array-method-return.js +301 -0
- package/rules/no-useless-promise-resolve-reject.js +5 -5
- package/rules/no-useless-spread.js +53 -4
- package/rules/no-useless-undefined.js +52 -45
- package/rules/numeric-separators-style.js +13 -4
- package/rules/prefer-array-last-methods.js +93 -0
- package/rules/prefer-array-some.js +66 -5
- package/rules/prefer-at.js +114 -55
- package/rules/prefer-dom-node-append.js +2 -2
- package/rules/prefer-dom-node-remove.js +24 -18
- package/rules/prefer-export-from.js +6 -6
- package/rules/prefer-get-or-insert-computed.js +159 -0
- package/rules/prefer-global-this.js +7 -46
- package/rules/prefer-https.js +83 -0
- package/rules/prefer-includes-over-repeated-comparisons.js +199 -0
- package/rules/prefer-iterator-concat.js +174 -0
- package/rules/prefer-iterator-to-array-at-end.js +123 -0
- package/rules/prefer-keyboard-event-key.js +202 -76
- package/rules/prefer-math-abs.js +397 -0
- package/rules/prefer-math-min-max.js +5 -3
- package/rules/prefer-node-protocol.js +6 -4
- package/rules/prefer-query-selector.js +119 -7
- package/rules/prefer-queue-microtask.js +204 -0
- package/rules/prefer-regexp-test.js +164 -18
- package/rules/prefer-set-has.js +411 -22
- package/rules/prefer-single-call.js +128 -38
- package/rules/prefer-split-limit.js +107 -0
- package/rules/prefer-spread.js +34 -4
- package/rules/prefer-string-match-all.js +352 -0
- package/rules/prefer-string-pad-start-end.js +380 -0
- package/rules/prefer-string-raw.js +90 -19
- package/rules/prefer-string-repeat.js +173 -0
- package/rules/prefer-string-replace-all.js +180 -12
- package/rules/prefer-string-slice.js +54 -3
- package/rules/prefer-top-level-await.js +129 -1
- package/rules/prevent-abbreviations.js +126 -134
- package/rules/require-css-escape.js +141 -0
- package/rules/require-module-specifiers.js +62 -0
- package/rules/require-passive-events.js +292 -0
- package/rules/shared/dom-events.js +7 -7
- package/rules/shared/identifier-checks.js +205 -0
- package/rules/shared/package-json.js +1 -1
- package/rules/string-content.js +34 -5
- package/rules/template-indent.js +52 -3
- package/rules/throw-new-error.js +4 -0
- package/rules/try-complexity.js +184 -0
- package/rules/utils/boolean.js +12 -12
- package/rules/utils/get-duplicate-array-elements.js +77 -0
- package/rules/utils/index.js +1 -0
- package/rules/utils/is-same-reference.js +6 -4
- package/rules/utils/should-add-parentheses-to-await-expression-argument.js +9 -9
- package/rules/utils/should-add-parentheses-to-call-expression-callee.js +12 -10
- package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +9 -7
- package/rules/utils/should-add-parentheses-to-logical-expression-child.js +12 -8
- package/rules/utils/should-add-parentheses-to-unary-expression.js +10 -10
- package/rules/better-regex.js +0 -155
- package/rules/prefer-dom-node-dataset.js +0 -135
- package/rules/prefer-json-parse-buffer.js +0 -161
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import helperValidatorIdentifier from '@babel/helper-validator-identifier';
|
|
2
|
+
import {
|
|
3
|
+
getAvailableVariableName,
|
|
4
|
+
getScopes,
|
|
5
|
+
isShorthandPropertyValue,
|
|
6
|
+
lowerFirst,
|
|
7
|
+
upperFirst,
|
|
8
|
+
} from './utils/index.js';
|
|
9
|
+
import {
|
|
10
|
+
isClassVariable,
|
|
11
|
+
shouldCheckDefaultOrNamespaceImportName,
|
|
12
|
+
shouldCheckShorthandImportName,
|
|
13
|
+
shouldRenameVariable,
|
|
14
|
+
shouldReportIdentifierAsProperty,
|
|
15
|
+
} from './shared/identifier-checks.js';
|
|
16
|
+
import {renameVariable} from './fix/index.js';
|
|
17
|
+
|
|
18
|
+
const MESSAGE_ID_ERROR = 'consistent-compound-words/error';
|
|
19
|
+
const MESSAGE_ID_RENAME = 'consistent-compound-words/rename';
|
|
20
|
+
const {isIdentifierName} = helperValidatorIdentifier;
|
|
21
|
+
|
|
22
|
+
const messages = {
|
|
23
|
+
[MESSAGE_ID_ERROR]: 'Prefer `{{replacement}}` over `{{name}}`.',
|
|
24
|
+
[MESSAGE_ID_RENAME]: 'Rename to `{{replacement}}`.',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const defaultReplacements = {
|
|
28
|
+
// Ambiguous/common API spellings like `fileName`, `setUp`, `lookUp`, `onLine`, `offLine`, `styleSheet`, and `superClass` are intentionally excluded.
|
|
29
|
+
backGround: 'background',
|
|
30
|
+
callBack: 'callback',
|
|
31
|
+
checkBox: 'checkbox',
|
|
32
|
+
clipBoard: 'clipboard',
|
|
33
|
+
codeBase: 'codebase',
|
|
34
|
+
dataBase: 'database',
|
|
35
|
+
downLoad: 'download',
|
|
36
|
+
feedBack: 'feedback',
|
|
37
|
+
foreGround: 'foreground',
|
|
38
|
+
frameWork: 'framework',
|
|
39
|
+
headLine: 'headline',
|
|
40
|
+
keyBoard: 'keyboard',
|
|
41
|
+
keyFrame: 'keyframe',
|
|
42
|
+
lifeCycle: 'lifecycle',
|
|
43
|
+
metaData: 'metadata',
|
|
44
|
+
midPoint: 'midpoint',
|
|
45
|
+
nameSpace: 'namespace',
|
|
46
|
+
newLine: 'newline',
|
|
47
|
+
overRide: 'override',
|
|
48
|
+
passWord: 'password',
|
|
49
|
+
payLoad: 'payload',
|
|
50
|
+
placeHolder: 'placeholder',
|
|
51
|
+
preView: 'preview',
|
|
52
|
+
screenShot: 'screenshot',
|
|
53
|
+
sideBar: 'sidebar',
|
|
54
|
+
subClass: 'subclass',
|
|
55
|
+
subDirectory: 'subdirectory',
|
|
56
|
+
subDomain: 'subdomain',
|
|
57
|
+
subMenu: 'submenu',
|
|
58
|
+
subProcess: 'subprocess',
|
|
59
|
+
subString: 'substring',
|
|
60
|
+
subTree: 'subtree',
|
|
61
|
+
subType: 'subtype',
|
|
62
|
+
subTitle: 'subtitle',
|
|
63
|
+
timeOut: 'timeout',
|
|
64
|
+
timeStamp: 'timestamp',
|
|
65
|
+
toolBar: 'toolbar',
|
|
66
|
+
toolKit: 'toolkit',
|
|
67
|
+
toolTip: 'tooltip',
|
|
68
|
+
touchScreen: 'touchscreen',
|
|
69
|
+
unSubscribe: 'unsubscribe',
|
|
70
|
+
underScore: 'underscore',
|
|
71
|
+
upLoad: 'upload',
|
|
72
|
+
userName: 'username',
|
|
73
|
+
viewPort: 'viewport',
|
|
74
|
+
webCam: 'webcam',
|
|
75
|
+
webHook: 'webhook',
|
|
76
|
+
webSite: 'website',
|
|
77
|
+
weekEnd: 'weekend',
|
|
78
|
+
whiteSpace: 'whitespace',
|
|
79
|
+
wildCard: 'wildcard',
|
|
80
|
+
workFlow: 'workflow',
|
|
81
|
+
workSpace: 'workspace',
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const isUpperCase = string => string === string.toUpperCase();
|
|
85
|
+
const isUpperFirst = string => isUpperCase(string[0]);
|
|
86
|
+
const regExpSyntaxCharacters = new Set(String.raw`\^$.*+?()[]{}|`);
|
|
87
|
+
const escapeRegExp = string => {
|
|
88
|
+
let result = '';
|
|
89
|
+
|
|
90
|
+
for (const character of string) {
|
|
91
|
+
result += regExpSyntaxCharacters.has(character) ? `\\${character}` : character;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const prepareOptions = ({
|
|
98
|
+
checkProperties = false,
|
|
99
|
+
checkVariables = true,
|
|
100
|
+
|
|
101
|
+
checkDefaultAndNamespaceImports = 'internal',
|
|
102
|
+
checkShorthandImports = 'internal',
|
|
103
|
+
checkShorthandProperties = false,
|
|
104
|
+
|
|
105
|
+
extendDefaultReplacements = true,
|
|
106
|
+
replacements = {},
|
|
107
|
+
|
|
108
|
+
allowList = {},
|
|
109
|
+
} = {}) => {
|
|
110
|
+
const mergedReplacements = extendDefaultReplacements
|
|
111
|
+
? {...defaultReplacements, ...replacements}
|
|
112
|
+
: replacements;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
checkProperties,
|
|
116
|
+
checkVariables,
|
|
117
|
+
|
|
118
|
+
checkDefaultAndNamespaceImports,
|
|
119
|
+
checkShorthandImports,
|
|
120
|
+
checkShorthandProperties,
|
|
121
|
+
|
|
122
|
+
replacements: new Map(Object.entries(mergedReplacements).filter(([, replacement]) => replacement !== false)),
|
|
123
|
+
allowList: new Set(Object.keys(allowList)),
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const getReplacementForPart = (part, replacements) => {
|
|
128
|
+
const replacement = replacements.get(part) ?? replacements.get(lowerFirst(part));
|
|
129
|
+
if (!replacement) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return isUpperFirst(part) ? upperFirst(replacement) : lowerFirst(replacement);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const getNameReplacement = (name, {replacements, allowList}) => {
|
|
137
|
+
if (isUpperCase(name) || allowList.has(name)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const boundary = String.raw`(?=$|[\d_$]|\p{Uppercase_Letter})`;
|
|
142
|
+
let replacement = name;
|
|
143
|
+
for (const discouragedName of replacements.keys()) {
|
|
144
|
+
const pattern = new RegExp(`^${escapeRegExp(discouragedName)}${boundary}|${escapeRegExp(upperFirst(discouragedName))}${boundary}`, 'gv');
|
|
145
|
+
replacement = replacement.replaceAll(pattern, part => getReplacementForPart(part, replacements));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (replacement === name) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return replacement;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const createProblem = (node, replacement) => ({
|
|
156
|
+
node,
|
|
157
|
+
messageId: MESSAGE_ID_ERROR,
|
|
158
|
+
data: {
|
|
159
|
+
name: node.name,
|
|
160
|
+
replacement,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
165
|
+
const create = context => {
|
|
166
|
+
const options = prepareOptions(context.options[0]);
|
|
167
|
+
|
|
168
|
+
const identifierToOuterClassVariable = new WeakMap();
|
|
169
|
+
const scopeToNamesGeneratedByFixer = new WeakMap();
|
|
170
|
+
const isSafeName = (name, scopes) => scopes.every(scope => {
|
|
171
|
+
const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
|
|
172
|
+
return !generatedNames || !generatedNames.has(name);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const checkPossiblyWeirdClassVariable = variable => {
|
|
176
|
+
if (isClassVariable(variable)) {
|
|
177
|
+
if (variable.scope.type === 'class') {
|
|
178
|
+
const [definition] = variable.defs;
|
|
179
|
+
const outerClassVariable = identifierToOuterClassVariable.get(definition.name);
|
|
180
|
+
|
|
181
|
+
if (!outerClassVariable) {
|
|
182
|
+
return checkVariable(variable);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const combinedReferencesVariable = {
|
|
186
|
+
name: variable.name,
|
|
187
|
+
scope: variable.scope,
|
|
188
|
+
defs: variable.defs,
|
|
189
|
+
identifiers: variable.identifiers,
|
|
190
|
+
references: [...variable.references, ...outerClassVariable.references],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return checkVariable(combinedReferencesVariable);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const [definition] = variable.defs;
|
|
197
|
+
identifierToOuterClassVariable.set(definition.name, variable);
|
|
198
|
+
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return checkVariable(variable);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const checkVariable = variable => {
|
|
206
|
+
if (variable.defs.length === 0) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const [definition] = variable.defs;
|
|
211
|
+
|
|
212
|
+
if (!shouldCheckDefaultOrNamespaceImportName(definition, options)) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!shouldCheckShorthandImportName(definition, options, context)) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
!options.checkShorthandProperties
|
|
222
|
+
&& isShorthandPropertyValue(definition.name)
|
|
223
|
+
) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const replacement = getNameReplacement(variable.name, options);
|
|
228
|
+
if (!replacement) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const scopes = [
|
|
233
|
+
...variable.references.map(reference => reference.from),
|
|
234
|
+
variable.scope,
|
|
235
|
+
];
|
|
236
|
+
const safeReplacement = isIdentifierName(replacement)
|
|
237
|
+
? getAvailableVariableName(replacement, scopes, isSafeName)
|
|
238
|
+
: undefined;
|
|
239
|
+
const problem = createProblem(definition.name, safeReplacement ?? replacement);
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
safeReplacement
|
|
243
|
+
&& shouldRenameVariable(variable)
|
|
244
|
+
&& !variable.references.some(reference => reference.vueUsedInTemplate)
|
|
245
|
+
) {
|
|
246
|
+
for (const scope of scopes) {
|
|
247
|
+
if (!scopeToNamesGeneratedByFixer.has(scope)) {
|
|
248
|
+
scopeToNamesGeneratedByFixer.set(scope, new Set());
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
|
|
252
|
+
generatedNames.add(safeReplacement);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
problem.suggest = [
|
|
256
|
+
{
|
|
257
|
+
messageId: MESSAGE_ID_RENAME,
|
|
258
|
+
data: {replacement: safeReplacement},
|
|
259
|
+
fix: fixer => renameVariable(variable, safeReplacement, context, fixer),
|
|
260
|
+
},
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
context.report(problem);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const checkVariables = scope => {
|
|
268
|
+
for (const variable of scope.variables) {
|
|
269
|
+
checkPossiblyWeirdClassVariable(variable);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const checkScope = scope => {
|
|
274
|
+
const scopes = getScopes(scope);
|
|
275
|
+
for (const scope of scopes) {
|
|
276
|
+
checkVariables(scope);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const isTSParameterPropertyName = node => {
|
|
281
|
+
if (options.checkVariables) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (node.parent.type === 'TSParameterProperty') {
|
|
286
|
+
return node.parent.parameter === node;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
node.parent.type === 'AssignmentPattern'
|
|
291
|
+
&& node.parent.left === node
|
|
292
|
+
&& node.parent.parent.type === 'TSParameterProperty'
|
|
293
|
+
&& node.parent.parent.parameter === node.parent
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const checkProperty = node => {
|
|
298
|
+
if (!options.checkProperties) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (node.name === '__proto__') {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const replacement = getNameReplacement(node.name, options);
|
|
307
|
+
if (!replacement) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (node.parent.type === 'ExportSpecifier') {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (
|
|
316
|
+
!isTSParameterPropertyName(node)
|
|
317
|
+
&& !shouldReportIdentifierAsProperty(node)
|
|
318
|
+
) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const problem = createProblem(node, replacement);
|
|
323
|
+
|
|
324
|
+
context.report(problem);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
context.on('Identifier', checkProperty);
|
|
328
|
+
context.on('PrivateIdentifier', checkProperty);
|
|
329
|
+
// eslint-disable-next-line no-warning-comments
|
|
330
|
+
// TODO: Consider expanding beyond JavaScript identifiers after this rule has proven itself.
|
|
331
|
+
|
|
332
|
+
context.on('Program:exit', program => {
|
|
333
|
+
if (!options.checkVariables) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
checkScope(context.sourceCode.getScope(program));
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const schema = {
|
|
342
|
+
type: 'array',
|
|
343
|
+
additionalItems: false,
|
|
344
|
+
items: [
|
|
345
|
+
{
|
|
346
|
+
type: 'object',
|
|
347
|
+
additionalProperties: false,
|
|
348
|
+
properties: {
|
|
349
|
+
checkProperties: {
|
|
350
|
+
type: 'boolean',
|
|
351
|
+
description: 'Whether to check property names.',
|
|
352
|
+
},
|
|
353
|
+
checkVariables: {
|
|
354
|
+
type: 'boolean',
|
|
355
|
+
description: 'Whether to check variable names.',
|
|
356
|
+
},
|
|
357
|
+
checkDefaultAndNamespaceImports: {
|
|
358
|
+
anyOf: [
|
|
359
|
+
{
|
|
360
|
+
type: 'boolean',
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
enum: [
|
|
364
|
+
'internal',
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
description: 'Whether to check default and namespace import names.',
|
|
369
|
+
},
|
|
370
|
+
checkShorthandImports: {
|
|
371
|
+
anyOf: [
|
|
372
|
+
{
|
|
373
|
+
type: 'boolean',
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
enum: [
|
|
377
|
+
'internal',
|
|
378
|
+
],
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
description: 'Whether to check shorthand import names.',
|
|
382
|
+
},
|
|
383
|
+
checkShorthandProperties: {
|
|
384
|
+
type: 'boolean',
|
|
385
|
+
description: 'Whether to check shorthand property names.',
|
|
386
|
+
},
|
|
387
|
+
extendDefaultReplacements: {
|
|
388
|
+
type: 'boolean',
|
|
389
|
+
description: 'Whether to extend the default replacements.',
|
|
390
|
+
},
|
|
391
|
+
replacements: {
|
|
392
|
+
$ref: '#/definitions/replacements',
|
|
393
|
+
description: 'Custom compound word replacements.',
|
|
394
|
+
},
|
|
395
|
+
allowList: {
|
|
396
|
+
$ref: '#/definitions/trueObject',
|
|
397
|
+
description: 'Custom allow list of names.',
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
definitions: {
|
|
403
|
+
replacements: {
|
|
404
|
+
type: 'object',
|
|
405
|
+
propertyNames: {
|
|
406
|
+
minLength: 1,
|
|
407
|
+
},
|
|
408
|
+
additionalProperties: {
|
|
409
|
+
anyOf: [
|
|
410
|
+
{
|
|
411
|
+
enum: [
|
|
412
|
+
false,
|
|
413
|
+
],
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
type: 'string',
|
|
417
|
+
minLength: 1,
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
trueObject: {
|
|
423
|
+
type: 'object',
|
|
424
|
+
propertyNames: {
|
|
425
|
+
minLength: 1,
|
|
426
|
+
},
|
|
427
|
+
additionalProperties: {
|
|
428
|
+
enum: [
|
|
429
|
+
true,
|
|
430
|
+
],
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
437
|
+
const config = {
|
|
438
|
+
create,
|
|
439
|
+
meta: {
|
|
440
|
+
type: 'suggestion',
|
|
441
|
+
docs: {
|
|
442
|
+
description: 'Enforce consistent spelling of compound words in identifiers.',
|
|
443
|
+
recommended: 'unopinionated',
|
|
444
|
+
},
|
|
445
|
+
hasSuggestions: true,
|
|
446
|
+
schema,
|
|
447
|
+
defaultOptions: [{}],
|
|
448
|
+
messages,
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
export default config;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {findVariable} from '@eslint-community/eslint-utils';
|
|
2
2
|
import {getAvailableVariableName, isLeftHandSide} from './utils/index.js';
|
|
3
|
-
import {isCallOrNewExpression} from './ast/index.js';
|
|
3
|
+
import {isCallOrNewExpression, isStringLiteral} from './ast/index.js';
|
|
4
4
|
|
|
5
5
|
const MESSAGE_ID = 'consistentDestructuring';
|
|
6
6
|
const MESSAGE_ID_SUGGEST = 'consistentDestructuringSuggest';
|
|
@@ -111,6 +111,14 @@ const isMemberDestructuredInNestedPatternWithRest = (objectPattern, memberName)
|
|
|
111
111
|
&& property.value.type !== 'Identifier'
|
|
112
112
|
&& hasNestedRestElement(property.value));
|
|
113
113
|
|
|
114
|
+
const shouldIgnoreMemberExpression = node =>
|
|
115
|
+
node.computed
|
|
116
|
+
|| (
|
|
117
|
+
isCallOrNewExpression(node.parent)
|
|
118
|
+
&& node.parent.callee === node
|
|
119
|
+
)
|
|
120
|
+
|| isLeftHandSide(node);
|
|
121
|
+
|
|
114
122
|
const isRootVariableReassigned = (declaration, memberExpressionNode, memberScope, sourceCode) => {
|
|
115
123
|
if (!declaration.rootVariable) {
|
|
116
124
|
return false;
|
|
@@ -138,6 +146,88 @@ const isRootVariableReassigned = (declaration, memberExpressionNode, memberScope
|
|
|
138
146
|
});
|
|
139
147
|
};
|
|
140
148
|
|
|
149
|
+
const isMatchingInExpression = (node, memberExpression, sourceCode) => {
|
|
150
|
+
if (!(
|
|
151
|
+
node.type === 'BinaryExpression'
|
|
152
|
+
&& node.operator === 'in'
|
|
153
|
+
&& isStringLiteral(node.left)
|
|
154
|
+
&& memberExpression.property.type === 'Identifier'
|
|
155
|
+
&& node.left.value === memberExpression.property.name
|
|
156
|
+
)) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return sourceCode.getText(node.right) === sourceCode.getText(memberExpression.object);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const hasMatchingInExpression = (node, memberExpression, sourceCode) => {
|
|
164
|
+
if (isMatchingInExpression(node, memberExpression, sourceCode)) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (
|
|
169
|
+
node.type !== 'LogicalExpression'
|
|
170
|
+
|| node.operator !== '&&'
|
|
171
|
+
) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return hasMatchingInExpression(node.left, memberExpression, sourceCode)
|
|
176
|
+
|| hasMatchingInExpression(node.right, memberExpression, sourceCode);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const isInPositiveGuardBranch = (parent, child, memberExpression, sourceCode) => {
|
|
180
|
+
switch (parent.type) {
|
|
181
|
+
case 'ConditionalExpression': {
|
|
182
|
+
return parent.consequent === child
|
|
183
|
+
&& hasMatchingInExpression(parent.test, memberExpression, sourceCode);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
case 'LogicalExpression': {
|
|
187
|
+
return parent.operator === '&&'
|
|
188
|
+
&& parent.right === child
|
|
189
|
+
&& hasMatchingInExpression(parent.left, memberExpression, sourceCode);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case 'IfStatement': {
|
|
193
|
+
return parent.consequent === child
|
|
194
|
+
&& hasMatchingInExpression(parent.test, memberExpression, sourceCode);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
default: {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const isInTypeGuardBoundary = node =>
|
|
204
|
+
[
|
|
205
|
+
'AccessorProperty',
|
|
206
|
+
'FunctionDeclaration',
|
|
207
|
+
'MethodDefinition',
|
|
208
|
+
'PropertyDefinition',
|
|
209
|
+
].includes(node.type);
|
|
210
|
+
|
|
211
|
+
const isInTypeGuardedBranch = (node, sourceCode) => {
|
|
212
|
+
let child = node;
|
|
213
|
+
let {parent} = node;
|
|
214
|
+
|
|
215
|
+
while (parent) {
|
|
216
|
+
if (isInTypeGuardBoundary(parent)) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (isInPositiveGuardBranch(parent, child, node, sourceCode)) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
child = parent;
|
|
225
|
+
parent = parent.parent;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return false;
|
|
229
|
+
};
|
|
230
|
+
|
|
141
231
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
142
232
|
const create = context => {
|
|
143
233
|
const {sourceCode} = context;
|
|
@@ -165,14 +255,7 @@ const create = context => {
|
|
|
165
255
|
});
|
|
166
256
|
|
|
167
257
|
context.on('MemberExpression', node => {
|
|
168
|
-
if (
|
|
169
|
-
node.computed
|
|
170
|
-
|| (
|
|
171
|
-
isCallOrNewExpression(node.parent)
|
|
172
|
-
&& node.parent.callee === node
|
|
173
|
-
)
|
|
174
|
-
|| isLeftHandSide(node)
|
|
175
|
-
) {
|
|
258
|
+
if (shouldIgnoreMemberExpression(node)) {
|
|
176
259
|
return;
|
|
177
260
|
}
|
|
178
261
|
|
|
@@ -205,6 +288,11 @@ const create = context => {
|
|
|
205
288
|
}
|
|
206
289
|
|
|
207
290
|
const member = sourceCode.getText(node.property);
|
|
291
|
+
|
|
292
|
+
if (isInTypeGuardedBranch(node, sourceCode)) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
208
296
|
const memberDestructuredInNestedPattern = isMemberDestructuredInNestedPatternWithRest(objectPattern, member);
|
|
209
297
|
|
|
210
298
|
const destructuredProperties = objectPattern.properties.filter(property =>
|
|
@@ -99,6 +99,25 @@ const isArrowFunctionNodeWithThis = (node, visitorKeys) =>
|
|
|
99
99
|
// Include both params and body, because parameter defaults can reference lexical `this`.
|
|
100
100
|
&& isNodeContainsLexicalThis(node, visitorKeys);
|
|
101
101
|
|
|
102
|
+
function isInsideJestMockFactory(node) {
|
|
103
|
+
let current = node;
|
|
104
|
+
while (current.parent) {
|
|
105
|
+
const {parent} = current;
|
|
106
|
+
if (
|
|
107
|
+
parent.type === 'CallExpression'
|
|
108
|
+
&& parent.arguments[1] === current
|
|
109
|
+
&& functionTypes.includes(current.type)
|
|
110
|
+
&& isNodeMatches(parent.callee, ['jest.mock'])
|
|
111
|
+
) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
current = parent;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
102
121
|
const iifeFunctionTypes = new Set([
|
|
103
122
|
'FunctionExpression',
|
|
104
123
|
'ArrowFunctionExpression',
|
|
@@ -151,6 +170,7 @@ function checkNode(node, scopeManager, sourceCode) {
|
|
|
151
170
|
if (
|
|
152
171
|
!scope
|
|
153
172
|
|| isArrowFunctionNodeWithThis(node, sourceCode.visitorKeys)
|
|
173
|
+
|| isInsideJestMockFactory(node)
|
|
154
174
|
) {
|
|
155
175
|
return true;
|
|
156
176
|
}
|