eslint-plugin-unicorn 63.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.
Files changed (146) hide show
  1. package/index.js +26 -18
  2. package/package.json +32 -30
  3. package/readme.md +183 -147
  4. package/rules/ast/call-or-new-expression.js +3 -5
  5. package/rules/ast/literal.js +1 -6
  6. package/rules/better-dom-traversing.js +291 -0
  7. package/rules/catch-error-name.js +3 -3
  8. package/rules/consistent-compound-words.js +452 -0
  9. package/rules/consistent-destructuring.js +234 -22
  10. package/rules/consistent-function-scoping.js +35 -11
  11. package/rules/consistent-json-file-read.js +245 -0
  12. package/rules/consistent-template-literal-escape.js +52 -0
  13. package/rules/custom-error-definition.js +176 -71
  14. package/rules/dom-node-dataset.js +431 -0
  15. package/rules/escape-case.js +2 -1
  16. package/rules/expiring-todo-comments.js +56 -102
  17. package/rules/explicit-length-check.js +145 -33
  18. package/rules/filename-case.js +97 -13
  19. package/rules/fix/append-argument.js +1 -1
  20. package/rules/fix/remove-expression-statement.js +1 -0
  21. package/rules/import-style.js +30 -10
  22. package/rules/index.js +37 -3
  23. package/rules/isolated-functions.js +11 -5
  24. package/rules/no-accessor-recursion.js +1 -2
  25. package/rules/no-anonymous-default-export.js +9 -11
  26. package/rules/no-array-callback-reference.js +148 -22
  27. package/rules/no-array-fill-with-reference-type.js +127 -0
  28. package/rules/no-array-for-each.js +18 -3
  29. package/rules/no-array-from-fill.js +76 -0
  30. package/rules/no-array-method-this-argument.js +1 -1
  31. package/rules/no-array-reduce.js +509 -2
  32. package/rules/no-blob-to-file.js +299 -0
  33. package/rules/no-canvas-to-image.js +152 -0
  34. package/rules/no-confusing-array-splice.js +164 -0
  35. package/rules/no-duplicate-set-values.js +57 -0
  36. package/rules/no-empty-file.js +31 -2
  37. package/rules/no-exports-in-scripts.js +34 -0
  38. package/rules/no-for-loop.js +1 -5
  39. package/rules/no-hex-escape.js +1 -1
  40. package/rules/no-incorrect-query-selector.js +347 -0
  41. package/rules/no-instanceof-builtins.js +4 -6
  42. package/rules/no-invalid-fetch-options.js +1 -1
  43. package/rules/no-invalid-file-input-accept.js +475 -0
  44. package/rules/no-keyword-prefix.js +23 -5
  45. package/rules/no-late-current-target-access.js +166 -0
  46. package/rules/no-lonely-if.js +152 -52
  47. package/rules/no-manually-wrapped-comments.js +151 -0
  48. package/rules/no-null.js +16 -5
  49. package/rules/no-process-exit.js +1 -1
  50. package/rules/no-this-outside-of-class.js +75 -0
  51. package/rules/no-typeof-undefined.js +7 -4
  52. package/rules/no-unnecessary-nested-ternary.js +132 -0
  53. package/rules/no-unnecessary-polyfills.js +253 -24
  54. package/rules/no-unreadable-iife.js +33 -3
  55. package/rules/no-unused-array-method-return.js +301 -0
  56. package/rules/no-unused-properties.js +13 -19
  57. package/rules/no-useless-iterator-to-array.js +249 -0
  58. package/rules/no-useless-length-check.js +5 -11
  59. package/rules/no-useless-promise-resolve-reject.js +5 -5
  60. package/rules/no-useless-spread.js +53 -4
  61. package/rules/no-useless-undefined.js +62 -46
  62. package/rules/numeric-separators-style.js +17 -6
  63. package/rules/prefer-add-event-listener.js +1 -0
  64. package/rules/prefer-array-find.js +1 -0
  65. package/rules/prefer-array-flat.js +76 -8
  66. package/rules/prefer-array-last-methods.js +93 -0
  67. package/rules/prefer-array-some.js +74 -8
  68. package/rules/prefer-at.js +118 -57
  69. package/rules/prefer-class-fields.js +2 -4
  70. package/rules/prefer-default-parameters.js +1 -2
  71. package/rules/prefer-dom-node-append.js +2 -2
  72. package/rules/prefer-dom-node-remove.js +24 -18
  73. package/rules/prefer-export-from.js +8 -7
  74. package/rules/prefer-get-or-insert-computed.js +159 -0
  75. package/rules/prefer-global-this.js +33 -29
  76. package/rules/prefer-https.js +83 -0
  77. package/rules/prefer-includes-over-repeated-comparisons.js +199 -0
  78. package/rules/prefer-iterator-concat.js +174 -0
  79. package/rules/prefer-iterator-to-array-at-end.js +123 -0
  80. package/rules/prefer-keyboard-event-key.js +202 -76
  81. package/rules/prefer-logical-operator-over-ternary.js +1 -1
  82. package/rules/prefer-math-abs.js +397 -0
  83. package/rules/prefer-math-min-max.js +54 -26
  84. package/rules/prefer-module.js +5 -7
  85. package/rules/prefer-native-coercion-functions.js +8 -1
  86. package/rules/prefer-node-protocol.js +7 -4
  87. package/rules/prefer-number-properties.js +2 -0
  88. package/rules/prefer-object-from-entries.js +12 -7
  89. package/rules/prefer-prototype-methods.js +1 -3
  90. package/rules/prefer-query-selector.js +119 -7
  91. package/rules/prefer-queue-microtask.js +204 -0
  92. package/rules/prefer-regexp-test.js +166 -20
  93. package/rules/prefer-set-has.js +474 -47
  94. package/rules/prefer-simple-condition-first.js +282 -0
  95. package/rules/prefer-single-call.js +129 -38
  96. package/rules/prefer-split-limit.js +107 -0
  97. package/rules/prefer-spread.js +150 -19
  98. package/rules/prefer-string-match-all.js +352 -0
  99. package/rules/prefer-string-pad-start-end.js +380 -0
  100. package/rules/prefer-string-raw.js +90 -19
  101. package/rules/prefer-string-repeat.js +173 -0
  102. package/rules/prefer-string-replace-all.js +180 -12
  103. package/rules/prefer-string-slice.js +55 -4
  104. package/rules/prefer-string-starts-ends-with.js +2 -2
  105. package/rules/prefer-structured-clone.js +1 -0
  106. package/rules/prefer-switch.js +2 -0
  107. package/rules/prefer-ternary.js +3 -2
  108. package/rules/prefer-top-level-await.js +168 -6
  109. package/rules/prevent-abbreviations.js +144 -158
  110. package/rules/relative-url-style.js +1 -0
  111. package/rules/require-css-escape.js +141 -0
  112. package/rules/require-module-attributes.js +16 -18
  113. package/rules/require-module-specifiers.js +62 -0
  114. package/rules/require-passive-events.js +292 -0
  115. package/rules/rule/to-eslint-rule.js +1 -1
  116. package/rules/rule/to-eslint-rules.js +4 -6
  117. package/rules/shared/abbreviations.js +1 -0
  118. package/rules/shared/dom-events.js +7 -7
  119. package/rules/shared/identifier-checks.js +205 -0
  120. package/rules/shared/package-json.js +1 -1
  121. package/rules/string-content.js +35 -5
  122. package/rules/switch-case-braces.js +2 -3
  123. package/rules/switch-case-break-position.js +138 -0
  124. package/rules/template-indent.js +59 -3
  125. package/rules/text-encoding-identifier-case.js +23 -5
  126. package/rules/throw-new-error.js +4 -0
  127. package/rules/try-complexity.js +184 -0
  128. package/rules/utils/assert-token.js +3 -5
  129. package/rules/utils/boolean.js +37 -28
  130. package/rules/utils/create-deprecated-rules.js +25 -27
  131. package/rules/utils/get-call-expression-arguments-text.js +3 -5
  132. package/rules/utils/get-duplicate-array-elements.js +77 -0
  133. package/rules/utils/index.js +4 -1
  134. package/rules/utils/is-node-contains-lexical-this.js +65 -0
  135. package/rules/utils/is-number.js +2 -2
  136. package/rules/utils/is-same-reference.js +25 -17
  137. package/rules/utils/should-add-parentheses-to-await-expression-argument.js +9 -9
  138. package/rules/utils/should-add-parentheses-to-call-expression-callee.js +12 -10
  139. package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +9 -5
  140. package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +1 -1
  141. package/rules/utils/should-add-parentheses-to-logical-expression-child.js +12 -8
  142. package/rules/utils/should-add-parentheses-to-member-expression-object.js +1 -1
  143. package/rules/utils/should-add-parentheses-to-unary-expression.js +10 -10
  144. package/rules/better-regex.js +0 -145
  145. package/rules/prefer-dom-node-dataset.js +0 -135
  146. 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;