eslint-plugin-unicorn-ts 0.0.1-security → 50.0.1

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.

Potentially problematic release.


This version of eslint-plugin-unicorn-ts might be problematic. Click here for more details.

Files changed (214) hide show
  1. package/configs/all.js +6 -0
  2. package/configs/flat-config-base.js +10 -0
  3. package/configs/legacy-config-base.js +10 -0
  4. package/configs/recommended.js +117 -0
  5. package/index.js +91 -0
  6. package/license +9 -0
  7. package/package.json +186 -4
  8. package/readme.md +356 -0
  9. package/rules/ast/call-or-new-expression.js +127 -0
  10. package/rules/ast/function-types.js +5 -0
  11. package/rules/ast/index.js +39 -0
  12. package/rules/ast/is-arrow-function-body.js +7 -0
  13. package/rules/ast/is-empty-node.js +20 -0
  14. package/rules/ast/is-expression-statement.js +11 -0
  15. package/rules/ast/is-function.js +8 -0
  16. package/rules/ast/is-member-expression.js +101 -0
  17. package/rules/ast/is-method-call.js +65 -0
  18. package/rules/ast/is-reference-identifier.js +156 -0
  19. package/rules/ast/is-static-require.js +14 -0
  20. package/rules/ast/is-undefined.js +7 -0
  21. package/rules/ast/literal.js +29 -0
  22. package/rules/better-regex.js +144 -0
  23. package/rules/catch-error-name.js +136 -0
  24. package/rules/consistent-destructuring.js +168 -0
  25. package/rules/consistent-function-scoping.js +223 -0
  26. package/rules/custom-error-definition.js +215 -0
  27. package/rules/empty-brace-spaces.js +72 -0
  28. package/rules/error-message.js +104 -0
  29. package/rules/escape-case.js +63 -0
  30. package/rules/expiring-todo-comments.js +580 -0
  31. package/rules/explicit-length-check.js +229 -0
  32. package/rules/filename-case.js +258 -0
  33. package/rules/fix/add-parenthesizes-to-return-or-throw-expression.js +21 -0
  34. package/rules/fix/append-argument.js +20 -0
  35. package/rules/fix/extend-fix-range.js +15 -0
  36. package/rules/fix/fix-space-around-keywords.js +35 -0
  37. package/rules/fix/index.js +23 -0
  38. package/rules/fix/remove-argument.js +32 -0
  39. package/rules/fix/remove-member-expression-property.js +11 -0
  40. package/rules/fix/remove-method-call.js +20 -0
  41. package/rules/fix/remove-parentheses.js +11 -0
  42. package/rules/fix/remove-spaces-after.js +14 -0
  43. package/rules/fix/rename-variable.js +9 -0
  44. package/rules/fix/replace-argument.js +8 -0
  45. package/rules/fix/replace-node-or-token-and-spaces-before.js +21 -0
  46. package/rules/fix/replace-reference-identifier.js +35 -0
  47. package/rules/fix/replace-string-literal.js +11 -0
  48. package/rules/fix/replace-string-raw.js +14 -0
  49. package/rules/fix/replace-template-element.js +11 -0
  50. package/rules/fix/switch-call-expression-to-new-expression.js +18 -0
  51. package/rules/fix/switch-new-expression-to-call-expression.js +34 -0
  52. package/rules/import-style.js +364 -0
  53. package/rules/new-for-builtins.js +85 -0
  54. package/rules/no-abusive-eslint-disable.js +48 -0
  55. package/rules/no-array-callback-reference.js +256 -0
  56. package/rules/no-array-for-each.js +473 -0
  57. package/rules/no-array-method-this-argument.js +188 -0
  58. package/rules/no-array-push-push.js +144 -0
  59. package/rules/no-array-reduce.js +126 -0
  60. package/rules/no-await-expression-member.js +90 -0
  61. package/rules/no-console-spaces.js +86 -0
  62. package/rules/no-document-cookie.js +25 -0
  63. package/rules/no-empty-file.js +57 -0
  64. package/rules/no-for-loop.js +427 -0
  65. package/rules/no-hex-escape.js +46 -0
  66. package/rules/no-instanceof-array.js +65 -0
  67. package/rules/no-invalid-remove-event-listener.js +60 -0
  68. package/rules/no-keyword-prefix.js +199 -0
  69. package/rules/no-lonely-if.js +151 -0
  70. package/rules/no-negated-condition.js +144 -0
  71. package/rules/no-nested-ternary.js +58 -0
  72. package/rules/no-new-array.js +104 -0
  73. package/rules/no-new-buffer.js +98 -0
  74. package/rules/no-null.js +153 -0
  75. package/rules/no-object-as-default-parameter.js +50 -0
  76. package/rules/no-process-exit.js +104 -0
  77. package/rules/no-static-only-class.js +224 -0
  78. package/rules/no-thenable.js +198 -0
  79. package/rules/no-this-assignment.js +38 -0
  80. package/rules/no-typeof-undefined.js +143 -0
  81. package/rules/no-unnecessary-await.js +107 -0
  82. package/rules/no-unnecessary-polyfills.js +176 -0
  83. package/rules/no-unreadable-array-destructuring.js +83 -0
  84. package/rules/no-unreadable-iife.js +45 -0
  85. package/rules/no-unused-properties.js +238 -0
  86. package/rules/no-useless-fallback-in-spread.js +68 -0
  87. package/rules/no-useless-length-check.js +152 -0
  88. package/rules/no-useless-promise-resolve-reject.js +212 -0
  89. package/rules/no-useless-spread.js +381 -0
  90. package/rules/no-useless-switch-case.js +71 -0
  91. package/rules/no-useless-undefined.js +301 -0
  92. package/rules/no-zero-fractions.js +79 -0
  93. package/rules/number-literal-case.js +52 -0
  94. package/rules/numeric-separators-style.js +181 -0
  95. package/rules/prefer-add-event-listener.js +188 -0
  96. package/rules/prefer-array-find.js +423 -0
  97. package/rules/prefer-array-flat-map.js +82 -0
  98. package/rules/prefer-array-flat.js +279 -0
  99. package/rules/prefer-array-index-of.js +32 -0
  100. package/rules/prefer-array-some.js +157 -0
  101. package/rules/prefer-at.js +374 -0
  102. package/rules/prefer-blob-reading-methods.js +45 -0
  103. package/rules/prefer-code-point.js +67 -0
  104. package/rules/prefer-date-now.js +135 -0
  105. package/rules/prefer-default-parameters.js +219 -0
  106. package/rules/prefer-dom-node-append.js +48 -0
  107. package/rules/prefer-dom-node-dataset.js +120 -0
  108. package/rules/prefer-dom-node-remove.js +122 -0
  109. package/rules/prefer-dom-node-text-content.js +75 -0
  110. package/rules/prefer-event-target.js +117 -0
  111. package/rules/prefer-export-from.js +413 -0
  112. package/rules/prefer-includes.js +98 -0
  113. package/rules/prefer-json-parse-buffer.js +159 -0
  114. package/rules/prefer-keyboard-event-key.js +186 -0
  115. package/rules/prefer-logical-operator-over-ternary.js +159 -0
  116. package/rules/prefer-math-trunc.js +109 -0
  117. package/rules/prefer-modern-dom-apis.js +141 -0
  118. package/rules/prefer-modern-math-apis.js +212 -0
  119. package/rules/prefer-module.js +349 -0
  120. package/rules/prefer-native-coercion-functions.js +185 -0
  121. package/rules/prefer-negative-index.js +213 -0
  122. package/rules/prefer-node-protocol.js +61 -0
  123. package/rules/prefer-number-properties.js +126 -0
  124. package/rules/prefer-object-from-entries.js +252 -0
  125. package/rules/prefer-optional-catch-binding.js +75 -0
  126. package/rules/prefer-prototype-methods.js +88 -0
  127. package/rules/prefer-query-selector.js +135 -0
  128. package/rules/prefer-reflect-apply.js +97 -0
  129. package/rules/prefer-regexp-test.js +156 -0
  130. package/rules/prefer-set-has.js +186 -0
  131. package/rules/prefer-set-size.js +103 -0
  132. package/rules/prefer-spread.js +529 -0
  133. package/rules/prefer-string-replace-all.js +145 -0
  134. package/rules/prefer-string-slice.js +182 -0
  135. package/rules/prefer-string-starts-ends-with.js +199 -0
  136. package/rules/prefer-string-trim-start-end.js +44 -0
  137. package/rules/prefer-switch.js +344 -0
  138. package/rules/prefer-ternary.js +282 -0
  139. package/rules/prefer-top-level-await.js +152 -0
  140. package/rules/prefer-type-error.js +151 -0
  141. package/rules/prevent-abbreviations.js +645 -0
  142. package/rules/relative-url-style.js +168 -0
  143. package/rules/require-array-join-separator.js +63 -0
  144. package/rules/require-number-to-fixed-digits-argument.js +54 -0
  145. package/rules/require-post-message-target-origin.js +71 -0
  146. package/rules/shared/abbreviations.js +262 -0
  147. package/rules/shared/dom-events.js +275 -0
  148. package/rules/shared/event-keys.js +52 -0
  149. package/rules/shared/negative-index.js +46 -0
  150. package/rules/shared/simple-array-search-rule.js +128 -0
  151. package/rules/shared/typed-array.js +16 -0
  152. package/rules/string-content.js +187 -0
  153. package/rules/switch-case-braces.js +109 -0
  154. package/rules/template-indent.js +219 -0
  155. package/rules/text-encoding-identifier-case.js +108 -0
  156. package/rules/throw-new-error.js +53 -0
  157. package/rules/utils/array-or-object-prototype-property.js +63 -0
  158. package/rules/utils/assert-token.js +32 -0
  159. package/rules/utils/avoid-capture.js +146 -0
  160. package/rules/utils/boolean.js +92 -0
  161. package/rules/utils/builtins.js +36 -0
  162. package/rules/utils/cartesian-product-samples.js +24 -0
  163. package/rules/utils/create-deprecated-rules.js +25 -0
  164. package/rules/utils/escape-string.js +26 -0
  165. package/rules/utils/escape-template-element-raw.js +6 -0
  166. package/rules/utils/get-ancestor.js +20 -0
  167. package/rules/utils/get-builtin-rule.js +7 -0
  168. package/rules/utils/get-call-expression-arguments-text.js +21 -0
  169. package/rules/utils/get-class-head-location.js +22 -0
  170. package/rules/utils/get-documentation-url.js +10 -0
  171. package/rules/utils/get-indent-string.js +11 -0
  172. package/rules/utils/get-previous-node.js +24 -0
  173. package/rules/utils/get-references.js +9 -0
  174. package/rules/utils/get-scopes.js +14 -0
  175. package/rules/utils/get-switch-case-head-location.js +21 -0
  176. package/rules/utils/get-variable-identifiers.js +7 -0
  177. package/rules/utils/global-reference-tracker.js +72 -0
  178. package/rules/utils/has-optional-chain-element.js +21 -0
  179. package/rules/utils/has-same-range.js +7 -0
  180. package/rules/utils/index.js +53 -0
  181. package/rules/utils/is-function-self-used-inside.js +43 -0
  182. package/rules/utils/is-left-hand-side.js +22 -0
  183. package/rules/utils/is-logical-expression.js +16 -0
  184. package/rules/utils/is-method-named.js +9 -0
  185. package/rules/utils/is-new-expression-with-parentheses.js +26 -0
  186. package/rules/utils/is-node-matches.js +53 -0
  187. package/rules/utils/is-node-value-not-dom-node.js +21 -0
  188. package/rules/utils/is-node-value-not-function.js +42 -0
  189. package/rules/utils/is-number.js +224 -0
  190. package/rules/utils/is-object-method.js +11 -0
  191. package/rules/utils/is-on-same-line.js +7 -0
  192. package/rules/utils/is-same-identifier.js +8 -0
  193. package/rules/utils/is-same-reference.js +173 -0
  194. package/rules/utils/is-shadowed.js +33 -0
  195. package/rules/utils/is-shorthand-export-local.js +9 -0
  196. package/rules/utils/is-shorthand-import-local.js +9 -0
  197. package/rules/utils/is-shorthand-property-assignment-pattern-left.js +10 -0
  198. package/rules/utils/is-shorthand-property-value.js +8 -0
  199. package/rules/utils/is-value-not-usable.js +5 -0
  200. package/rules/utils/lodash.js +1589 -0
  201. package/rules/utils/needs-semicolon.js +114 -0
  202. package/rules/utils/numeric.js +53 -0
  203. package/rules/utils/parentheses.js +73 -0
  204. package/rules/utils/resolve-variable-name.js +20 -0
  205. package/rules/utils/rule.js +190 -0
  206. package/rules/utils/should-add-parentheses-to-conditional-expression-child.js +17 -0
  207. package/rules/utils/should-add-parentheses-to-expression-statement-expression.js +26 -0
  208. package/rules/utils/should-add-parentheses-to-logical-expression-child.js +47 -0
  209. package/rules/utils/should-add-parentheses-to-member-expression-object.js +47 -0
  210. package/rules/utils/should-add-parentheses-to-new-expression-callee.js +32 -0
  211. package/rules/utils/should-add-parentheses-to-spread-element-argument.js +22 -0
  212. package/rules/utils/singular.js +18 -0
  213. package/rules/utils/to-location.js +21 -0
  214. package/README.md +0 -5
@@ -0,0 +1,529 @@
1
+ 'use strict';
2
+ const {isParenthesized, getStaticValue, isCommaToken, hasSideEffect} = require('@eslint-community/eslint-utils');
3
+ const {
4
+ getParenthesizedRange,
5
+ getParenthesizedText,
6
+ needsSemicolon,
7
+ shouldAddParenthesesToSpreadElementArgument,
8
+ isNodeMatches,
9
+ isMethodNamed,
10
+ } = require('./utils/index.js');
11
+ const {removeMethodCall} = require('./fix/index.js');
12
+ const {isLiteral, isMethodCall} = require('./ast/index.js');
13
+
14
+ const ERROR_ARRAY_FROM = 'array-from';
15
+ const ERROR_ARRAY_CONCAT = 'array-concat';
16
+ const ERROR_ARRAY_SLICE = 'array-slice';
17
+ const ERROR_ARRAY_TO_SPLICED = 'array-to-spliced';
18
+ const ERROR_STRING_SPLIT = 'string-split';
19
+ const SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE = 'argument-is-spreadable';
20
+ const SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE = 'argument-is-not-spreadable';
21
+ const SUGGESTION_CONCAT_TEST_ARGUMENT = 'test-argument';
22
+ const SUGGESTION_CONCAT_SPREAD_ALL_ARGUMENTS = 'spread-all-arguments';
23
+ const SUGGESTION_USE_SPREAD = 'use-spread';
24
+ const messages = {
25
+ [ERROR_ARRAY_FROM]: 'Prefer the spread operator over `Array.from(…)`.',
26
+ [ERROR_ARRAY_CONCAT]: 'Prefer the spread operator over `Array#concat(…)`.',
27
+ [ERROR_ARRAY_SLICE]: 'Prefer the spread operator over `Array#slice()`.',
28
+ [ERROR_ARRAY_TO_SPLICED]: 'Prefer the spread operator over `Array#toSpliced()`.',
29
+ [ERROR_STRING_SPLIT]: 'Prefer the spread operator over `String#split(\'\')`.',
30
+ [SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE]: 'First argument is an `array`.',
31
+ [SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE]: 'First argument is not an `array`.',
32
+ [SUGGESTION_CONCAT_TEST_ARGUMENT]: 'Test first argument with `Array.isArray(…)`.',
33
+ [SUGGESTION_CONCAT_SPREAD_ALL_ARGUMENTS]: 'Spread all unknown arguments`.',
34
+ [SUGGESTION_USE_SPREAD]: 'Use `...` operator.',
35
+ };
36
+
37
+ const ignoredSliceCallee = [
38
+ 'arrayBuffer',
39
+ 'blob',
40
+ 'buffer',
41
+ 'file',
42
+ 'this',
43
+ ];
44
+
45
+ const isArrayLiteral = node => node.type === 'ArrayExpression';
46
+ const isArrayLiteralHasTrailingComma = (node, sourceCode) => {
47
+ if (node.elements.length === 0) {
48
+ return false;
49
+ }
50
+
51
+ return isCommaToken(sourceCode.getLastToken(node, 1));
52
+ };
53
+
54
+ function fixConcat(node, sourceCode, fixableArguments) {
55
+ const array = node.callee.object;
56
+ const concatCallArguments = node.arguments;
57
+ const arrayParenthesizedRange = getParenthesizedRange(array, sourceCode);
58
+ const arrayIsArrayLiteral = isArrayLiteral(array);
59
+ const arrayHasTrailingComma = arrayIsArrayLiteral && isArrayLiteralHasTrailingComma(array, sourceCode);
60
+
61
+ const getArrayLiteralElementsText = (node, keepTrailingComma) => {
62
+ if (
63
+ !keepTrailingComma
64
+ && isArrayLiteralHasTrailingComma(node, sourceCode)
65
+ ) {
66
+ const start = node.range[0] + 1;
67
+ const end = sourceCode.getLastToken(node, 1).range[0];
68
+ return sourceCode.text.slice(start, end);
69
+ }
70
+
71
+ return sourceCode.getText(node, -1, -1);
72
+ };
73
+
74
+ const getFixedText = () => {
75
+ const nonEmptyArguments = fixableArguments
76
+ .filter(({node, isArrayLiteral}) => (!isArrayLiteral || node.elements.length > 0));
77
+ const lastArgument = nonEmptyArguments.at(-1);
78
+
79
+ let text = nonEmptyArguments
80
+ .map(({node, isArrayLiteral, isSpreadable, testArgument}) => {
81
+ if (isArrayLiteral) {
82
+ return getArrayLiteralElementsText(node, node === lastArgument.node);
83
+ }
84
+
85
+ let text = getParenthesizedText(node, sourceCode);
86
+
87
+ if (testArgument) {
88
+ return `...(Array.isArray(${text}) ? ${text} : [${text}])`;
89
+ }
90
+
91
+ if (isSpreadable) {
92
+ if (
93
+ !isParenthesized(node, sourceCode)
94
+ && shouldAddParenthesesToSpreadElementArgument(node)
95
+ ) {
96
+ text = `(${text})`;
97
+ }
98
+
99
+ text = `...${text}`;
100
+ }
101
+
102
+ return text || ' ';
103
+ })
104
+ .join(', ');
105
+
106
+ if (!text) {
107
+ return '';
108
+ }
109
+
110
+ if (arrayIsArrayLiteral) {
111
+ if (array.elements.length > 0) {
112
+ text = ` ${text}`;
113
+
114
+ if (!arrayHasTrailingComma) {
115
+ text = `,${text}`;
116
+ }
117
+
118
+ if (
119
+ arrayHasTrailingComma
120
+ && (!lastArgument.isArrayLiteral || !isArrayLiteralHasTrailingComma(lastArgument.node, sourceCode))
121
+ ) {
122
+ text = `${text},`;
123
+ }
124
+ }
125
+ } else {
126
+ text = `, ${text}`;
127
+ }
128
+
129
+ return text;
130
+ };
131
+
132
+ function removeArguments(fixer) {
133
+ const [firstArgument] = concatCallArguments;
134
+ const lastArgument = concatCallArguments[fixableArguments.length - 1];
135
+
136
+ const [start] = getParenthesizedRange(firstArgument, sourceCode);
137
+ let [, end] = sourceCode.getTokenAfter(lastArgument, isCommaToken).range;
138
+
139
+ const textAfter = sourceCode.text.slice(end);
140
+ const [leadingSpaces] = textAfter.match(/^\s*/);
141
+ end += leadingSpaces.length;
142
+
143
+ return fixer.replaceTextRange([start, end], '');
144
+ }
145
+
146
+ return function * (fixer) {
147
+ // Fixed code always starts with `[`
148
+ if (
149
+ !arrayIsArrayLiteral
150
+ && needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, '[')
151
+ ) {
152
+ yield fixer.insertTextBefore(node, ';');
153
+ }
154
+
155
+ if (concatCallArguments.length - fixableArguments.length === 0) {
156
+ yield * removeMethodCall(fixer, node, sourceCode);
157
+ } else {
158
+ yield removeArguments(fixer);
159
+ }
160
+
161
+ const text = getFixedText();
162
+
163
+ if (arrayIsArrayLiteral) {
164
+ const closingBracketToken = sourceCode.getLastToken(array);
165
+ yield fixer.insertTextBefore(closingBracketToken, text);
166
+ } else {
167
+ // The array is already accessing `.concat`, there should not any case need add extra `()`
168
+ yield fixer.insertTextBeforeRange(arrayParenthesizedRange, '[...');
169
+ yield fixer.insertTextAfterRange(arrayParenthesizedRange, text);
170
+ yield fixer.insertTextAfterRange(arrayParenthesizedRange, ']');
171
+ }
172
+ };
173
+ }
174
+
175
+ const getConcatArgumentSpreadable = (node, scope) => {
176
+ if (node.type === 'SpreadElement') {
177
+ return;
178
+ }
179
+
180
+ if (isArrayLiteral(node)) {
181
+ return {node, isArrayLiteral: true};
182
+ }
183
+
184
+ const result = getStaticValue(node, scope);
185
+
186
+ if (!result) {
187
+ return;
188
+ }
189
+
190
+ const isSpreadable = Array.isArray(result.value);
191
+
192
+ return {node, isSpreadable};
193
+ };
194
+
195
+ function getConcatFixableArguments(argumentsList, scope) {
196
+ const fixableArguments = [];
197
+
198
+ for (const node of argumentsList) {
199
+ const result = getConcatArgumentSpreadable(node, scope);
200
+
201
+ if (result) {
202
+ fixableArguments.push(result);
203
+ } else {
204
+ break;
205
+ }
206
+ }
207
+
208
+ return fixableArguments;
209
+ }
210
+
211
+ function fixArrayFrom(node, sourceCode) {
212
+ const [object] = node.arguments;
213
+
214
+ function getObjectText() {
215
+ if (isArrayLiteral(object)) {
216
+ return sourceCode.getText(object);
217
+ }
218
+
219
+ const [start, end] = getParenthesizedRange(object, sourceCode);
220
+ let text = sourceCode.text.slice(start, end);
221
+
222
+ if (
223
+ !isParenthesized(object, sourceCode)
224
+ && shouldAddParenthesesToSpreadElementArgument(object)
225
+ ) {
226
+ text = `(${text})`;
227
+ }
228
+
229
+ return `[...${text}]`;
230
+ }
231
+
232
+ return function * (fixer) {
233
+ // Fixed code always starts with `[`
234
+ if (needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, '[')) {
235
+ yield fixer.insertTextBefore(node, ';');
236
+ }
237
+
238
+ const objectText = getObjectText();
239
+
240
+ yield fixer.replaceText(node, objectText);
241
+ };
242
+ }
243
+
244
+ function methodCallToSpread(node, sourceCode) {
245
+ return function * (fixer) {
246
+ // Fixed code always starts with `[`
247
+ if (needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, '[')) {
248
+ yield fixer.insertTextBefore(node, ';');
249
+ }
250
+
251
+ yield fixer.insertTextBefore(node, '[...');
252
+ yield fixer.insertTextAfter(node, ']');
253
+
254
+ // The array is already accessing `.slice` or `.split`, there should not any case need add extra `()`
255
+
256
+ yield * removeMethodCall(fixer, node, sourceCode);
257
+ };
258
+ }
259
+
260
+ function isClassName(node) {
261
+ if (node.type === 'MemberExpression') {
262
+ node = node.property;
263
+ }
264
+
265
+ if (node.type !== 'Identifier') {
266
+ return false;
267
+ }
268
+
269
+ const {name} = node;
270
+
271
+ return /^[A-Z]./.test(name) && name.toUpperCase() !== name;
272
+ }
273
+
274
+ function isNotArray(node, scope) {
275
+ if (
276
+ node.type === 'TemplateLiteral'
277
+ || node.type === 'Literal'
278
+ || node.type === 'BinaryExpression'
279
+ || isClassName(node)
280
+ // `foo.join()`
281
+ || (isMethodNamed(node, 'join') && node.arguments.length <= 1)
282
+ ) {
283
+ return true;
284
+ }
285
+
286
+ const staticValue = getStaticValue(node, scope);
287
+ if (staticValue && !Array.isArray(staticValue.value)) {
288
+ return true;
289
+ }
290
+
291
+ return false;
292
+ }
293
+
294
+ /** @param {import('eslint').Rule.RuleContext} context */
295
+ const create = context => {
296
+ const {sourceCode} = context;
297
+
298
+ // `Array.from()`
299
+ context.on('CallExpression', node => {
300
+ if (
301
+ isMethodCall(node, {
302
+ object: 'Array',
303
+ method: 'from',
304
+ argumentsLength: 1,
305
+ optionalCall: false,
306
+ optionalMember: false,
307
+ })
308
+ // Allow `Array.from({length})`
309
+ && node.arguments[0].type !== 'ObjectExpression'
310
+ ) {
311
+ return {
312
+ node,
313
+ messageId: ERROR_ARRAY_FROM,
314
+ fix: fixArrayFrom(node, sourceCode),
315
+ };
316
+ }
317
+ });
318
+
319
+ // `array.concat()`
320
+ context.on('CallExpression', node => {
321
+ if (!isMethodCall(node, {
322
+ method: 'concat',
323
+ optionalCall: false,
324
+ optionalMember: false,
325
+ })) {
326
+ return;
327
+ }
328
+
329
+ const {object} = node.callee;
330
+ const scope = sourceCode.getScope(object);
331
+
332
+ if (isNotArray(object, scope)) {
333
+ return;
334
+ }
335
+
336
+ const staticResult = getStaticValue(object, scope);
337
+ if (staticResult && !Array.isArray(staticResult.value)) {
338
+ return;
339
+ }
340
+
341
+ const problem = {
342
+ node: node.callee.property,
343
+ messageId: ERROR_ARRAY_CONCAT,
344
+ };
345
+
346
+ const fixableArguments = getConcatFixableArguments(node.arguments, scope);
347
+
348
+ if (fixableArguments.length > 0 || node.arguments.length === 0) {
349
+ problem.fix = fixConcat(node, sourceCode, fixableArguments);
350
+ return problem;
351
+ }
352
+
353
+ const [firstArgument, ...restArguments] = node.arguments;
354
+ if (firstArgument.type === 'SpreadElement') {
355
+ return problem;
356
+ }
357
+
358
+ const fixableArgumentsAfterFirstArgument = getConcatFixableArguments(restArguments, scope);
359
+ const suggestions = [
360
+ {
361
+ messageId: SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE,
362
+ isSpreadable: true,
363
+ },
364
+ {
365
+ messageId: SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE,
366
+ isSpreadable: false,
367
+ },
368
+ ];
369
+
370
+ if (!hasSideEffect(firstArgument, sourceCode)) {
371
+ suggestions.push({
372
+ messageId: SUGGESTION_CONCAT_TEST_ARGUMENT,
373
+ testArgument: true,
374
+ });
375
+ }
376
+
377
+ problem.suggest = suggestions.map(({messageId, isSpreadable, testArgument}) => ({
378
+ messageId,
379
+ fix: fixConcat(
380
+ node,
381
+ sourceCode,
382
+ // When apply suggestion, we also merge fixable arguments after the first one
383
+ [
384
+ {
385
+ node: firstArgument,
386
+ isSpreadable,
387
+ testArgument,
388
+ },
389
+ ...fixableArgumentsAfterFirstArgument,
390
+ ],
391
+ ),
392
+ }));
393
+
394
+ if (
395
+ fixableArgumentsAfterFirstArgument.length < restArguments.length
396
+ && restArguments.every(({type}) => type !== 'SpreadElement')
397
+ ) {
398
+ problem.suggest.push({
399
+ messageId: SUGGESTION_CONCAT_SPREAD_ALL_ARGUMENTS,
400
+ fix: fixConcat(
401
+ node,
402
+ sourceCode,
403
+ node.arguments.map(node => getConcatArgumentSpreadable(node, scope) || {node, isSpreadable: true}),
404
+ ),
405
+ });
406
+ }
407
+
408
+ return problem;
409
+ });
410
+
411
+ // `array.slice()`
412
+ context.on('CallExpression', node => {
413
+ if (!(
414
+ isMethodCall(node, {
415
+ method: 'slice',
416
+ minimumArguments: 0,
417
+ maximumArguments: 1,
418
+ optionalCall: false,
419
+ optionalMember: false,
420
+ })
421
+ && node.callee.object.type !== 'ArrayExpression'
422
+ )) {
423
+ return;
424
+ }
425
+
426
+ if (isNodeMatches(node.callee.object, ignoredSliceCallee)) {
427
+ return;
428
+ }
429
+
430
+ const [firstArgument] = node.arguments;
431
+ if (firstArgument && !isLiteral(firstArgument, 0)) {
432
+ return;
433
+ }
434
+
435
+ return {
436
+ node: node.callee.property,
437
+ messageId: ERROR_ARRAY_SLICE,
438
+ fix: methodCallToSpread(node, sourceCode),
439
+ };
440
+ });
441
+
442
+ // `array.toSpliced()`
443
+ context.on('CallExpression', node => {
444
+ if (!(
445
+ isMethodCall(node, {
446
+ method: 'toSpliced',
447
+ argumentsLength: 0,
448
+ optionalCall: false,
449
+ optionalMember: false,
450
+ })
451
+ && node.callee.object.type !== 'ArrayExpression'
452
+ )) {
453
+ return;
454
+ }
455
+
456
+ return {
457
+ node: node.callee.property,
458
+ messageId: ERROR_ARRAY_TO_SPLICED,
459
+ fix: methodCallToSpread(node, sourceCode),
460
+ };
461
+ });
462
+
463
+ // `string.split()`
464
+ context.on('CallExpression', node => {
465
+ if (!isMethodCall(node, {
466
+ method: 'split',
467
+ argumentsLength: 1,
468
+ optionalCall: false,
469
+ optionalMember: false,
470
+ })) {
471
+ return;
472
+ }
473
+
474
+ const [separator] = node.arguments;
475
+ if (!isLiteral(separator, '')) {
476
+ return;
477
+ }
478
+
479
+ const string = node.callee.object;
480
+ const staticValue = getStaticValue(string, sourceCode.getScope(string));
481
+ let hasSameResult = false;
482
+ if (staticValue) {
483
+ const {value} = staticValue;
484
+
485
+ if (typeof value !== 'string') {
486
+ return;
487
+ }
488
+
489
+ // eslint-disable-next-line unicorn/prefer-spread
490
+ const resultBySplit = value.split('');
491
+ const resultBySpread = [...value];
492
+
493
+ hasSameResult = resultBySplit.length === resultBySpread.length
494
+ && resultBySplit.every((character, index) => character === resultBySpread[index]);
495
+ }
496
+
497
+ const problem = {
498
+ node: node.callee.property,
499
+ messageId: ERROR_STRING_SPLIT,
500
+ };
501
+
502
+ if (hasSameResult) {
503
+ problem.fix = methodCallToSpread(node, sourceCode);
504
+ } else {
505
+ problem.suggest = [
506
+ {
507
+ messageId: SUGGESTION_USE_SPREAD,
508
+ fix: methodCallToSpread(node, sourceCode),
509
+ },
510
+ ];
511
+ }
512
+
513
+ return problem;
514
+ });
515
+ };
516
+
517
+ /** @type {import('eslint').Rule.RuleModule} */
518
+ module.exports = {
519
+ create,
520
+ meta: {
521
+ type: 'suggestion',
522
+ docs: {
523
+ description: 'Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split(\'\')`.',
524
+ },
525
+ fixable: 'code',
526
+ hasSuggestions: true,
527
+ messages,
528
+ },
529
+ };
@@ -0,0 +1,145 @@
1
+ 'use strict';
2
+ const {getStaticValue} = require('@eslint-community/eslint-utils');
3
+ const {parse: parseRegExp} = require('regjsparser');
4
+ const escapeString = require('./utils/escape-string.js');
5
+ const {isRegexLiteral, isNewExpression, isMethodCall} = require('./ast/index.js');
6
+
7
+ const MESSAGE_ID_USE_REPLACE_ALL = 'method';
8
+ const MESSAGE_ID_USE_STRING = 'pattern';
9
+ const messages = {
10
+ [MESSAGE_ID_USE_REPLACE_ALL]: 'Prefer `String#replaceAll()` over `String#replace()`.',
11
+ [MESSAGE_ID_USE_STRING]: 'This pattern can be replaced with {{replacement}}.',
12
+ };
13
+
14
+ function getPatternReplacement(node) {
15
+ if (!isRegexLiteral(node)) {
16
+ return;
17
+ }
18
+
19
+ const {pattern, flags} = node.regex;
20
+ if (flags.replace('u', '').replace('v', '') !== 'g') {
21
+ return;
22
+ }
23
+
24
+ let tree;
25
+
26
+ try {
27
+ tree = parseRegExp(pattern, flags, {
28
+ unicodePropertyEscape: flags.includes('u'),
29
+ unicodeSet: flags.includes('v'),
30
+ namedGroups: true,
31
+ lookbehind: true,
32
+ });
33
+ } catch {
34
+ return;
35
+ }
36
+
37
+ const parts = tree.type === 'alternative' ? tree.body : [tree];
38
+ if (parts.some(part => part.type !== 'value')) {
39
+ return;
40
+ }
41
+
42
+ // TODO: Preserve escape
43
+ const string = String.fromCodePoint(...parts.map(part => part.codePoint));
44
+
45
+ return escapeString(string);
46
+ }
47
+
48
+ const isRegExpWithGlobalFlag = (node, scope) => {
49
+ if (isRegexLiteral(node)) {
50
+ return node.regex.flags.includes('g');
51
+ }
52
+
53
+ if (
54
+ isNewExpression(node, {name: 'RegExp'})
55
+ && node.arguments[0]?.type !== 'SpreadElement'
56
+ && node.arguments[1]?.type === 'Literal'
57
+ && typeof node.arguments[1].value === 'string'
58
+ ) {
59
+ return node.arguments[1].value.includes('g');
60
+ }
61
+
62
+ const staticResult = getStaticValue(node, scope);
63
+
64
+ // Don't know if there is `g` flag
65
+ if (!staticResult) {
66
+ return false;
67
+ }
68
+
69
+ const {value} = staticResult;
70
+ return (
71
+ Object.prototype.toString.call(value) === '[object RegExp]'
72
+ && value.global
73
+ );
74
+ };
75
+
76
+ /** @param {import('eslint').Rule.RuleContext} context */
77
+ const create = context => ({
78
+ CallExpression(node) {
79
+ if (!isMethodCall(node, {
80
+ methods: ['replace', 'replaceAll'],
81
+ argumentsLength: 2,
82
+ optionalCall: false,
83
+ optionalMember: false,
84
+ })) {
85
+ return;
86
+ }
87
+
88
+ const {
89
+ arguments: [pattern],
90
+ callee: {property},
91
+ } = node;
92
+
93
+ if (!isRegExpWithGlobalFlag(pattern, context.sourceCode.getScope(pattern))) {
94
+ return;
95
+ }
96
+
97
+ const methodName = property.name;
98
+ const patternReplacement = getPatternReplacement(pattern);
99
+
100
+ if (methodName === 'replaceAll') {
101
+ if (!patternReplacement) {
102
+ return;
103
+ }
104
+
105
+ return {
106
+ node: pattern,
107
+ messageId: MESSAGE_ID_USE_STRING,
108
+ data: {
109
+ // Show `This pattern can be replaced with a string literal.` for long strings
110
+ replacement: patternReplacement.length < 20 ? patternReplacement : 'a string literal',
111
+ },
112
+ /** @param {import('eslint').Rule.RuleFixer} fixer */
113
+ fix: fixer => fixer.replaceText(pattern, patternReplacement),
114
+ };
115
+ }
116
+
117
+ return {
118
+ node: property,
119
+ messageId: MESSAGE_ID_USE_REPLACE_ALL,
120
+ /** @param {import('eslint').Rule.RuleFixer} fixer */
121
+ * fix(fixer) {
122
+ yield fixer.insertTextAfter(property, 'All');
123
+
124
+ if (!patternReplacement) {
125
+ return;
126
+ }
127
+
128
+ yield fixer.replaceText(pattern, patternReplacement);
129
+ },
130
+ };
131
+ },
132
+ });
133
+
134
+ /** @type {import('eslint').Rule.RuleModule} */
135
+ module.exports = {
136
+ create,
137
+ meta: {
138
+ type: 'suggestion',
139
+ docs: {
140
+ description: 'Prefer `String#replaceAll()` over regex searches with the global flag.',
141
+ },
142
+ fixable: 'code',
143
+ messages,
144
+ },
145
+ };