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,413 @@
1
+ 'use strict';
2
+ const {
3
+ isCommaToken,
4
+ isOpeningBraceToken,
5
+ isClosingBraceToken,
6
+ } = require('@eslint-community/eslint-utils');
7
+ const {
8
+ isStringLiteral,
9
+ } = require('./ast/index.js');
10
+
11
+ const MESSAGE_ID_ERROR = 'error';
12
+ const MESSAGE_ID_SUGGESTION = 'suggestion';
13
+ const messages = {
14
+ [MESSAGE_ID_ERROR]: 'Use `export…from` to re-export `{{exported}}`.',
15
+ [MESSAGE_ID_SUGGESTION]: 'Switch to `export…from`.',
16
+ };
17
+
18
+ // Default import/export can be `Identifier`, have to use `Symbol.for`
19
+ const DEFAULT_SPECIFIER_NAME = Symbol.for('default');
20
+ const NAMESPACE_SPECIFIER_NAME = Symbol('NAMESPACE_SPECIFIER_NAME');
21
+
22
+ const getSpecifierName = node => {
23
+ switch (node.type) {
24
+ case 'Identifier': {
25
+ return Symbol.for(node.name);
26
+ }
27
+
28
+ case 'Literal': {
29
+ return node.value;
30
+ }
31
+ // No default
32
+ }
33
+ };
34
+
35
+ const isTypeExport = specifier => specifier.exportKind === 'type' || specifier.parent.exportKind === 'type';
36
+
37
+ const isTypeImport = specifier => specifier.importKind === 'type' || specifier.parent.importKind === 'type';
38
+
39
+ function * removeSpecifier(node, fixer, sourceCode) {
40
+ const {parent} = node;
41
+ const {specifiers} = parent;
42
+
43
+ if (specifiers.length === 1) {
44
+ yield * removeImportOrExport(parent, fixer, sourceCode);
45
+ return;
46
+ }
47
+
48
+ switch (node.type) {
49
+ case 'ImportSpecifier': {
50
+ const hasOtherSpecifiers = specifiers.some(specifier => specifier !== node && specifier.type === node.type);
51
+ if (!hasOtherSpecifiers) {
52
+ const closingBraceToken = sourceCode.getTokenAfter(node, isClosingBraceToken);
53
+
54
+ // If there are other specifiers, they have to be the default import specifier
55
+ // And the default import has to write before the named import specifiers
56
+ // So there must be a comma before
57
+ const commaToken = sourceCode.getTokenBefore(node, isCommaToken);
58
+ yield fixer.replaceTextRange([commaToken.range[0], closingBraceToken.range[1]], '');
59
+ return;
60
+ }
61
+ // Fallthrough
62
+ }
63
+
64
+ case 'ExportSpecifier':
65
+ case 'ImportNamespaceSpecifier':
66
+ case 'ImportDefaultSpecifier': {
67
+ yield fixer.remove(node);
68
+
69
+ const tokenAfter = sourceCode.getTokenAfter(node);
70
+ if (isCommaToken(tokenAfter)) {
71
+ yield fixer.remove(tokenAfter);
72
+ }
73
+
74
+ break;
75
+ }
76
+
77
+ // No default
78
+ }
79
+ }
80
+
81
+ function * removeImportOrExport(node, fixer, sourceCode) {
82
+ switch (node.type) {
83
+ case 'ImportSpecifier':
84
+ case 'ExportSpecifier':
85
+ case 'ImportDefaultSpecifier':
86
+ case 'ImportNamespaceSpecifier': {
87
+ yield * removeSpecifier(node, fixer, sourceCode);
88
+ return;
89
+ }
90
+
91
+ case 'ImportDeclaration':
92
+ case 'ExportDefaultDeclaration':
93
+ case 'ExportNamedDeclaration': {
94
+ yield fixer.remove(node);
95
+ }
96
+
97
+ // No default
98
+ }
99
+ }
100
+
101
+ function getSourceAndAssertionsText(declaration, sourceCode) {
102
+ const keywordFromToken = sourceCode.getTokenBefore(
103
+ declaration.source,
104
+ token => token.type === 'Identifier' && token.value === 'from',
105
+ );
106
+ const [start] = keywordFromToken.range;
107
+ const [, end] = declaration.range;
108
+ return sourceCode.text.slice(start, end);
109
+ }
110
+
111
+ function getFixFunction({
112
+ sourceCode,
113
+ imported,
114
+ exported,
115
+ exportDeclarations,
116
+ program,
117
+ }) {
118
+ const importDeclaration = imported.declaration;
119
+ const sourceNode = importDeclaration.source;
120
+ const sourceValue = sourceNode.value;
121
+ const shouldExportAsType = imported.isTypeImport || exported.isTypeExport;
122
+
123
+ let exportDeclaration;
124
+ if (shouldExportAsType) {
125
+ // If a type export declaration already exists, reuse it, else use a value export declaration with an inline type specifier.
126
+ exportDeclaration = exportDeclarations.find(({source, exportKind}) => source.value === sourceValue && exportKind === 'type');
127
+ }
128
+
129
+ if (!exportDeclaration) {
130
+ exportDeclaration = exportDeclarations.find(({source, exportKind}) => source.value === sourceValue && exportKind !== 'type');
131
+ }
132
+
133
+ /** @param {import('eslint').Rule.RuleFixer} fixer */
134
+ return function * (fixer) {
135
+ if (imported.name === NAMESPACE_SPECIFIER_NAME) {
136
+ yield fixer.insertTextAfter(
137
+ program,
138
+ `\nexport * as ${exported.text} ${getSourceAndAssertionsText(importDeclaration, sourceCode)}`,
139
+ );
140
+ } else {
141
+ let specifierText = exported.name === imported.name
142
+ ? exported.text
143
+ : `${imported.text} as ${exported.text}`;
144
+
145
+ // Add an inline type specifier if the value is a type and the export deceleration is a value deceleration
146
+ if (shouldExportAsType && (!exportDeclaration || exportDeclaration.exportKind !== 'type')) {
147
+ specifierText = `type ${specifierText}`;
148
+ }
149
+
150
+ if (exportDeclaration) {
151
+ const lastSpecifier = exportDeclaration.specifiers.at(-1);
152
+
153
+ // `export {} from 'foo';`
154
+ if (lastSpecifier) {
155
+ yield fixer.insertTextAfter(lastSpecifier, `, ${specifierText}`);
156
+ } else {
157
+ const openingBraceToken = sourceCode.getFirstToken(exportDeclaration, isOpeningBraceToken);
158
+ yield fixer.insertTextAfter(openingBraceToken, specifierText);
159
+ }
160
+ } else {
161
+ yield fixer.insertTextAfter(
162
+ program,
163
+ `\nexport {${specifierText}} ${getSourceAndAssertionsText(importDeclaration, sourceCode)}`,
164
+ );
165
+ }
166
+ }
167
+
168
+ if (imported.variable.references.length === 1) {
169
+ yield * removeImportOrExport(imported.node, fixer, sourceCode);
170
+ }
171
+
172
+ yield * removeImportOrExport(exported.node, fixer, sourceCode);
173
+ };
174
+ }
175
+
176
+ function getExported(identifier, sourceCode) {
177
+ const {parent} = identifier;
178
+ switch (parent.type) {
179
+ case 'ExportDefaultDeclaration': {
180
+ return {
181
+ node: parent,
182
+ name: DEFAULT_SPECIFIER_NAME,
183
+ text: 'default',
184
+ isTypeExport: isTypeExport(parent),
185
+ };
186
+ }
187
+
188
+ case 'ExportSpecifier': {
189
+ return {
190
+ node: parent,
191
+ name: getSpecifierName(parent.exported),
192
+ text: sourceCode.getText(parent.exported),
193
+ isTypeExport: isTypeExport(parent),
194
+ };
195
+ }
196
+
197
+ case 'VariableDeclarator': {
198
+ if (
199
+ parent.init === identifier
200
+ && parent.id.type === 'Identifier'
201
+ && !parent.id.typeAnnotation
202
+ && parent.parent.type === 'VariableDeclaration'
203
+ && parent.parent.kind === 'const'
204
+ && parent.parent.declarations.length === 1
205
+ && parent.parent.declarations[0] === parent
206
+ && parent.parent.parent.type === 'ExportNamedDeclaration'
207
+ && isVariableUnused(parent, sourceCode)
208
+ ) {
209
+ return {
210
+ node: parent.parent.parent,
211
+ name: Symbol.for(parent.id.name),
212
+ text: sourceCode.getText(parent.id),
213
+ };
214
+ }
215
+
216
+ break;
217
+ }
218
+
219
+ // No default
220
+ }
221
+ }
222
+
223
+ function isVariableUnused(node, sourceCode) {
224
+ const variables = sourceCode.getDeclaredVariables(node);
225
+
226
+ /* c8 ignore next 3 */
227
+ if (variables.length !== 1) {
228
+ return false;
229
+ }
230
+
231
+ const [{identifiers, references}] = variables;
232
+ return identifiers.length === 1
233
+ && identifiers[0] === node.id
234
+ && references.length === 1
235
+ && references[0].identifier === node.id;
236
+ }
237
+
238
+ function getImported(variable, sourceCode) {
239
+ const specifier = variable.defs[0].node;
240
+ const result = {
241
+ node: specifier,
242
+ declaration: specifier.parent,
243
+ variable,
244
+ isTypeImport: isTypeImport(specifier),
245
+ };
246
+
247
+ switch (specifier.type) {
248
+ case 'ImportDefaultSpecifier': {
249
+ return {
250
+ name: DEFAULT_SPECIFIER_NAME,
251
+ text: 'default',
252
+ ...result,
253
+ };
254
+ }
255
+
256
+ case 'ImportSpecifier': {
257
+ return {
258
+ name: getSpecifierName(specifier.imported),
259
+ text: sourceCode.getText(specifier.imported),
260
+ ...result,
261
+ };
262
+ }
263
+
264
+ case 'ImportNamespaceSpecifier': {
265
+ return {
266
+ name: NAMESPACE_SPECIFIER_NAME,
267
+ text: '*',
268
+ ...result,
269
+ };
270
+ }
271
+
272
+ // No default
273
+ }
274
+ }
275
+
276
+ function getExports(imported, sourceCode) {
277
+ const exports = [];
278
+ for (const {identifier} of imported.variable.references) {
279
+ const exported = getExported(identifier, sourceCode);
280
+
281
+ if (!exported) {
282
+ continue;
283
+ }
284
+
285
+ /*
286
+ There is no substitution for:
287
+
288
+ ```js
289
+ import * as foo from 'foo';
290
+ export default foo;
291
+ ```
292
+ */
293
+ if (imported.name === NAMESPACE_SPECIFIER_NAME && exported.name === DEFAULT_SPECIFIER_NAME) {
294
+ continue;
295
+ }
296
+
297
+ exports.push(exported);
298
+ }
299
+
300
+ return exports;
301
+ }
302
+
303
+ const schema = [
304
+ {
305
+ type: 'object',
306
+ additionalProperties: false,
307
+ properties: {
308
+ ignoreUsedVariables: {
309
+ type: 'boolean',
310
+ default: false,
311
+ },
312
+ },
313
+ },
314
+ ];
315
+
316
+ /** @param {import('eslint').Rule.RuleContext} context */
317
+ function create(context) {
318
+ const {sourceCode} = context;
319
+ const {ignoreUsedVariables} = {ignoreUsedVariables: false, ...context.options[0]};
320
+ const importDeclarations = new Set();
321
+ const exportDeclarations = [];
322
+
323
+ return {
324
+ ImportDeclaration(node) {
325
+ if (node.specifiers.length > 0) {
326
+ importDeclarations.add(node);
327
+ }
328
+ },
329
+ // `ExportAllDeclaration` and `ExportDefaultDeclaration` can't be reused
330
+ ExportNamedDeclaration(node) {
331
+ if (isStringLiteral(node.source)) {
332
+ exportDeclarations.push(node);
333
+ }
334
+ },
335
+ * 'Program:exit'(program) {
336
+ for (const importDeclaration of importDeclarations) {
337
+ let variables = sourceCode.getDeclaredVariables(importDeclaration);
338
+
339
+ if (variables.some(variable => variable.defs.length !== 1 || variable.defs[0].parent !== importDeclaration)) {
340
+ continue;
341
+ }
342
+
343
+ variables = variables.map(variable => {
344
+ const imported = getImported(variable, sourceCode);
345
+ const exports = getExports(imported, sourceCode);
346
+
347
+ return {
348
+ variable,
349
+ imported,
350
+ exports,
351
+ };
352
+ });
353
+
354
+ if (
355
+ ignoreUsedVariables
356
+ && variables.some(({variable, exports}) => variable.references.length !== exports.length)
357
+ ) {
358
+ continue;
359
+ }
360
+
361
+ const shouldUseSuggestion = ignoreUsedVariables
362
+ && variables.some(({variable}) => variable.references.length === 0);
363
+
364
+ for (const {imported, exports} of variables) {
365
+ for (const exported of exports) {
366
+ const problem = {
367
+ node: exported.node,
368
+ messageId: MESSAGE_ID_ERROR,
369
+ data: {
370
+ exported: exported.text,
371
+ },
372
+ };
373
+ const fix = getFixFunction({
374
+ sourceCode,
375
+ imported,
376
+ exported,
377
+ exportDeclarations,
378
+ program,
379
+ });
380
+
381
+ if (shouldUseSuggestion) {
382
+ problem.suggest = [
383
+ {
384
+ messageId: MESSAGE_ID_SUGGESTION,
385
+ fix,
386
+ },
387
+ ];
388
+ } else {
389
+ problem.fix = fix;
390
+ }
391
+
392
+ yield problem;
393
+ }
394
+ }
395
+ }
396
+ },
397
+ };
398
+ }
399
+
400
+ /** @type {import('eslint').Rule.RuleModule} */
401
+ module.exports = {
402
+ create,
403
+ meta: {
404
+ type: 'suggestion',
405
+ docs: {
406
+ description: 'Prefer `export…from` when re-exporting.',
407
+ },
408
+ fixable: 'code',
409
+ hasSuggestions: true,
410
+ schema,
411
+ messages,
412
+ },
413
+ };
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+ const isMethodNamed = require('./utils/is-method-named.js');
3
+ const simpleArraySearchRule = require('./shared/simple-array-search-rule.js');
4
+ const {isLiteral} = require('./ast/index.js');
5
+
6
+ const MESSAGE_ID = 'prefer-includes';
7
+ const messages = {
8
+ [MESSAGE_ID]: 'Use `.includes()`, rather than `.indexOf()`, when checking for existence.',
9
+ };
10
+ // Ignore {_,lodash,underscore}.indexOf
11
+ const ignoredVariables = new Set(['_', 'lodash', 'underscore']);
12
+ const isIgnoredTarget = node => node.type === 'Identifier' && ignoredVariables.has(node.name);
13
+ const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1;
14
+ const isLiteralZero = node => isLiteral(node, 0);
15
+ const isNegativeResult = node => ['===', '==', '<'].includes(node.operator);
16
+
17
+ const getProblem = (context, node, target, argumentsNodes) => {
18
+ const {sourceCode} = context;
19
+ const memberExpressionNode = target.parent;
20
+ const dotToken = sourceCode.getTokenBefore(memberExpressionNode.property);
21
+ const targetSource = sourceCode.getText().slice(memberExpressionNode.range[0], dotToken.range[0]);
22
+
23
+ // Strip default `fromIndex`
24
+ if (isLiteralZero(argumentsNodes[1])) {
25
+ argumentsNodes = argumentsNodes.slice(0, 1);
26
+ }
27
+
28
+ const argumentsSource = argumentsNodes.map(argument => sourceCode.getText(argument));
29
+
30
+ return {
31
+ node: memberExpressionNode.property,
32
+ messageId: MESSAGE_ID,
33
+ fix(fixer) {
34
+ const replacement = `${isNegativeResult(node) ? '!' : ''}${targetSource}.includes(${argumentsSource.join(', ')})`;
35
+ return fixer.replaceText(node, replacement);
36
+ },
37
+ };
38
+ };
39
+
40
+ const includesOverSomeRule = simpleArraySearchRule({
41
+ method: 'some',
42
+ replacement: 'includes',
43
+ });
44
+
45
+ /** @param {import('eslint').Rule.RuleContext} context */
46
+ const create = context => {
47
+ includesOverSomeRule.listen(context);
48
+
49
+ context.on('BinaryExpression', node => {
50
+ const {left, right, operator} = node;
51
+
52
+ if (!isMethodNamed(left, 'indexOf')) {
53
+ return;
54
+ }
55
+
56
+ const target = left.callee.object;
57
+
58
+ if (isIgnoredTarget(target)) {
59
+ return;
60
+ }
61
+
62
+ const {arguments: argumentsNodes} = left;
63
+
64
+ // Ignore something.indexOf(foo, 0, another)
65
+ if (argumentsNodes.length > 2) {
66
+ return;
67
+ }
68
+
69
+ if (
70
+ (['!==', '!=', '>', '===', '=='].includes(operator) && isNegativeOne(right))
71
+ || (['>=', '<'].includes(operator) && isLiteralZero(right))
72
+ ) {
73
+ return getProblem(
74
+ context,
75
+ node,
76
+ target,
77
+ argumentsNodes,
78
+ );
79
+ }
80
+ });
81
+ };
82
+
83
+ /** @type {import('eslint').Rule.RuleModule} */
84
+ module.exports = {
85
+ create,
86
+ meta: {
87
+ type: 'suggestion',
88
+ docs: {
89
+ description: 'Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence.',
90
+ },
91
+ fixable: 'code',
92
+ hasSuggestions: true,
93
+ messages: {
94
+ ...messages,
95
+ ...includesOverSomeRule.messages,
96
+ },
97
+ },
98
+ };
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+ const {findVariable, getStaticValue, getPropertyName} = require('@eslint-community/eslint-utils');
3
+ const {isMethodCall} = require('./ast/index.js');
4
+ const {removeArgument} = require('./fix/index.js');
5
+
6
+ const MESSAGE_ID = 'prefer-json-parse-buffer';
7
+ const messages = {
8
+ [MESSAGE_ID]: 'Prefer reading the JSON file as a buffer.',
9
+ };
10
+
11
+ const getAwaitExpressionArgument = node => {
12
+ while (node.type === 'AwaitExpression') {
13
+ node = node.argument;
14
+ }
15
+
16
+ return node;
17
+ };
18
+
19
+ function getIdentifierDeclaration(node, scope) {
20
+ if (!node) {
21
+ return;
22
+ }
23
+
24
+ node = getAwaitExpressionArgument(node);
25
+
26
+ if (!node || node.type !== 'Identifier') {
27
+ return node;
28
+ }
29
+
30
+ const variable = findVariable(scope, node);
31
+ if (!variable) {
32
+ return;
33
+ }
34
+
35
+ const {identifiers, references} = variable;
36
+
37
+ if (identifiers.length !== 1 || references.length !== 2) {
38
+ return;
39
+ }
40
+
41
+ const [identifier] = identifiers;
42
+
43
+ if (
44
+ identifier.parent.type !== 'VariableDeclarator'
45
+ || identifier.parent.id !== identifier
46
+ ) {
47
+ return;
48
+ }
49
+
50
+ return getIdentifierDeclaration(identifier.parent.init, variable.scope);
51
+ }
52
+
53
+ const isUtf8EncodingStringNode = (node, scope) =>
54
+ isUtf8EncodingString(getStaticValue(node, scope)?.value);
55
+
56
+ const isUtf8EncodingString = value => {
57
+ if (typeof value !== 'string') {
58
+ return false;
59
+ }
60
+
61
+ value = value.toLowerCase();
62
+
63
+ // eslint-disable-next-line unicorn/text-encoding-identifier-case
64
+ return value === 'utf8' || value === 'utf-8';
65
+ };
66
+
67
+ function isUtf8Encoding(node, scope) {
68
+ if (
69
+ node.type === 'ObjectExpression'
70
+ && node.properties.length === 1
71
+ && node.properties[0].type === 'Property'
72
+ && getPropertyName(node.properties[0], scope) === 'encoding'
73
+ && isUtf8EncodingStringNode(node.properties[0].value, scope)
74
+ ) {
75
+ return true;
76
+ }
77
+
78
+ if (isUtf8EncodingStringNode(node, scope)) {
79
+ return true;
80
+ }
81
+
82
+ const staticValue = getStaticValue(node, scope);
83
+ if (!staticValue) {
84
+ return false;
85
+ }
86
+
87
+ const {value} = staticValue;
88
+ if (
89
+ typeof value === 'object'
90
+ && Object.keys(value).length === 1
91
+ && isUtf8EncodingString(value.encoding)
92
+ ) {
93
+ return true;
94
+ }
95
+
96
+ return false;
97
+ }
98
+
99
+ /** @param {import('eslint').Rule.RuleContext} context */
100
+ const create = context => ({
101
+ CallExpression(callExpression) {
102
+ if (!(isMethodCall(callExpression, {
103
+ object: 'JSON',
104
+ method: 'parse',
105
+ argumentsLength: 1,
106
+ optionalCall: false,
107
+ optionalMember: false,
108
+ }))) {
109
+ return;
110
+ }
111
+
112
+ let [node] = callExpression.arguments;
113
+ const {sourceCode} = context;
114
+ const scope = sourceCode.getScope(node);
115
+ node = getIdentifierDeclaration(node, scope);
116
+ if (
117
+ !(
118
+ node
119
+ && node.type === 'CallExpression'
120
+ && !node.optional
121
+ && node.arguments.length === 2
122
+ && !node.arguments.some(node => node.type === 'SpreadElement')
123
+ && node.callee.type === 'MemberExpression'
124
+ && !node.callee.optional
125
+ )
126
+ ) {
127
+ return;
128
+ }
129
+
130
+ const method = getPropertyName(node.callee, scope);
131
+ if (method !== 'readFile' && method !== 'readFileSync') {
132
+ return;
133
+ }
134
+
135
+ const [, charsetNode] = node.arguments;
136
+ if (!isUtf8Encoding(charsetNode, scope)) {
137
+ return;
138
+ }
139
+
140
+ return {
141
+ node: charsetNode,
142
+ messageId: MESSAGE_ID,
143
+ fix: fixer => removeArgument(fixer, charsetNode, sourceCode),
144
+ };
145
+ },
146
+ });
147
+
148
+ /** @type {import('eslint').Rule.RuleModule} */
149
+ module.exports = {
150
+ create,
151
+ meta: {
152
+ type: 'suggestion',
153
+ docs: {
154
+ description: 'Prefer reading a JSON file as a buffer.',
155
+ },
156
+ fixable: 'code',
157
+ messages,
158
+ },
159
+ };