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,344 @@
1
+ 'use strict';
2
+ const {hasSideEffect} = require('@eslint-community/eslint-utils');
3
+ const isSameReference = require('./utils/is-same-reference.js');
4
+ const getIndentString = require('./utils/get-indent-string.js');
5
+
6
+ const MESSAGE_ID = 'prefer-switch';
7
+ const messages = {
8
+ [MESSAGE_ID]: 'Use `switch` instead of multiple `else-if`.',
9
+ };
10
+
11
+ const isSame = (nodeA, nodeB) => nodeA === nodeB || isSameReference(nodeA, nodeB);
12
+
13
+ function getEqualityComparisons(node) {
14
+ const nodes = [node];
15
+ const compareExpressions = [];
16
+ while (nodes.length > 0) {
17
+ node = nodes.pop();
18
+
19
+ if (node.type === 'LogicalExpression' && node.operator === '||') {
20
+ nodes.push(node.right, node.left);
21
+ continue;
22
+ }
23
+
24
+ if (node.type !== 'BinaryExpression' || node.operator !== '===') {
25
+ return [];
26
+ }
27
+
28
+ compareExpressions.push(node);
29
+ }
30
+
31
+ return compareExpressions;
32
+ }
33
+
34
+ function getCommonReferences(expressions, candidates) {
35
+ for (const {left, right} of expressions) {
36
+ candidates = candidates.filter(node => isSame(node, left) || isSame(node, right));
37
+
38
+ if (candidates.length === 0) {
39
+ break;
40
+ }
41
+ }
42
+
43
+ return candidates;
44
+ }
45
+
46
+ function getStatements(statement) {
47
+ let discriminantCandidates;
48
+ const ifStatements = [];
49
+ for (; statement && statement.type === 'IfStatement'; statement = statement.alternate) {
50
+ const {test} = statement;
51
+ const compareExpressions = getEqualityComparisons(test);
52
+
53
+ if (compareExpressions.length === 0) {
54
+ break;
55
+ }
56
+
57
+ if (!discriminantCandidates) {
58
+ const [{left, right}] = compareExpressions;
59
+ discriminantCandidates = [left, right];
60
+ }
61
+
62
+ const candidates = getCommonReferences(
63
+ compareExpressions,
64
+ discriminantCandidates,
65
+ );
66
+
67
+ if (candidates.length === 0) {
68
+ break;
69
+ }
70
+
71
+ discriminantCandidates = candidates;
72
+
73
+ ifStatements.push({
74
+ statement,
75
+ compareExpressions,
76
+ });
77
+ }
78
+
79
+ return {
80
+ ifStatements,
81
+ discriminant: discriminantCandidates && discriminantCandidates[0],
82
+ };
83
+ }
84
+
85
+ const breakAbleNodeTypes = new Set([
86
+ 'WhileStatement',
87
+ 'DoWhileStatement',
88
+ 'ForStatement',
89
+ 'ForOfStatement',
90
+ 'ForInStatement',
91
+ 'SwitchStatement',
92
+ ]);
93
+ const getBreakTarget = node => {
94
+ for (;node.parent; node = node.parent) {
95
+ if (breakAbleNodeTypes.has(node.type)) {
96
+ return node;
97
+ }
98
+ }
99
+ };
100
+
101
+ const isNodeInsideNode = (inner, outer) =>
102
+ inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];
103
+ function hasBreakInside(breakStatements, node) {
104
+ for (const breakStatement of breakStatements) {
105
+ if (!isNodeInsideNode(breakStatement, node)) {
106
+ continue;
107
+ }
108
+
109
+ const breakTarget = getBreakTarget(breakStatement);
110
+
111
+ if (!breakTarget) {
112
+ return true;
113
+ }
114
+
115
+ if (isNodeInsideNode(node, breakTarget)) {
116
+ return true;
117
+ }
118
+ }
119
+
120
+ return false;
121
+ }
122
+
123
+ function * insertBracesIfNotBlockStatement(node, fixer, indent) {
124
+ if (!node || node.type === 'BlockStatement') {
125
+ return;
126
+ }
127
+
128
+ yield fixer.insertTextBefore(node, `{\n${indent}`);
129
+ yield fixer.insertTextAfter(node, `\n${indent}}`);
130
+ }
131
+
132
+ function * insertBreakStatement(node, fixer, sourceCode, indent) {
133
+ if (node.type === 'BlockStatement') {
134
+ const lastToken = sourceCode.getLastToken(node);
135
+ yield fixer.insertTextBefore(lastToken, `\n${indent}break;\n${indent}`);
136
+ } else {
137
+ yield fixer.insertTextAfter(node, `\n${indent}break;`);
138
+ }
139
+ }
140
+
141
+ function getBlockStatementLastNode(blockStatement) {
142
+ const {body} = blockStatement;
143
+ for (let index = body.length - 1; index >= 0; index--) {
144
+ const node = body[index];
145
+ if (node.type === 'FunctionDeclaration' || node.type === 'EmptyStatement') {
146
+ continue;
147
+ }
148
+
149
+ if (node.type === 'BlockStatement') {
150
+ const last = getBlockStatementLastNode(node);
151
+ if (last) {
152
+ return last;
153
+ }
154
+
155
+ continue;
156
+ }
157
+
158
+ return node;
159
+ }
160
+ }
161
+
162
+ function shouldInsertBreakStatement(node) {
163
+ switch (node.type) {
164
+ case 'ReturnStatement':
165
+ case 'ThrowStatement': {
166
+ return false;
167
+ }
168
+
169
+ case 'IfStatement': {
170
+ return !node.alternate
171
+ || shouldInsertBreakStatement(node.consequent)
172
+ || shouldInsertBreakStatement(node.alternate);
173
+ }
174
+
175
+ case 'BlockStatement': {
176
+ const lastNode = getBlockStatementLastNode(node);
177
+ return !lastNode || shouldInsertBreakStatement(lastNode);
178
+ }
179
+
180
+ default: {
181
+ return true;
182
+ }
183
+ }
184
+ }
185
+
186
+ function fix({discriminant, ifStatements}, sourceCode, options) {
187
+ const discriminantText = sourceCode.getText(discriminant);
188
+
189
+ return function * (fixer) {
190
+ const firstStatement = ifStatements[0].statement;
191
+ const indent = getIndentString(firstStatement, sourceCode);
192
+ yield fixer.insertTextBefore(firstStatement, `switch (${discriminantText}) {`);
193
+
194
+ const lastStatement = ifStatements.at(-1).statement;
195
+ if (lastStatement.alternate) {
196
+ const {alternate} = lastStatement;
197
+ yield fixer.insertTextBefore(alternate, `\n${indent}default: `);
198
+ /*
199
+ Technically, we should insert braces for the following case,
200
+ but who writes like this? And using `let`/`const` is invalid.
201
+
202
+ ```js
203
+ if (foo === 1) {}
204
+ else if (foo === 2) {}
205
+ else if (foo === 3) {}
206
+ else var a = 1;
207
+ ```
208
+ */
209
+ } else {
210
+ switch (options.emptyDefaultCase) {
211
+ case 'no-default-comment': {
212
+ yield fixer.insertTextAfter(firstStatement, `\n${indent}// No default`);
213
+ break;
214
+ }
215
+
216
+ case 'do-nothing-comment': {
217
+ yield fixer.insertTextAfter(firstStatement, `\n${indent}default:\n${indent}// Do nothing`);
218
+ break;
219
+ }
220
+ // No default
221
+ }
222
+ }
223
+
224
+ yield fixer.insertTextAfter(firstStatement, `\n${indent}}`);
225
+
226
+ for (const {statement, compareExpressions} of ifStatements) {
227
+ const {consequent, alternate, range} = statement;
228
+ const headRange = [range[0], consequent.range[0]];
229
+
230
+ if (alternate) {
231
+ const [, start] = consequent.range;
232
+ const [end] = alternate.range;
233
+ yield fixer.replaceTextRange([start, end], '');
234
+ }
235
+
236
+ yield fixer.replaceTextRange(headRange, '');
237
+ for (const {left, right} of compareExpressions) {
238
+ const node = isSame(left, discriminant) ? right : left;
239
+ const text = sourceCode.getText(node);
240
+ yield fixer.insertTextBefore(consequent, `\n${indent}case ${text}: `);
241
+ }
242
+
243
+ if (shouldInsertBreakStatement(consequent)) {
244
+ yield * insertBreakStatement(consequent, fixer, sourceCode, indent);
245
+ yield * insertBracesIfNotBlockStatement(consequent, fixer, indent);
246
+ }
247
+ }
248
+ };
249
+ }
250
+
251
+ /** @param {import('eslint').Rule.RuleContext} context */
252
+ const create = context => {
253
+ const options = {
254
+ minimumCases: 3,
255
+ emptyDefaultCase: 'no-default-comment',
256
+ insertBreakInDefaultCase: false,
257
+ ...context.options[0],
258
+ };
259
+ const {sourceCode} = context;
260
+ const ifStatements = new Set();
261
+ const breakStatements = [];
262
+ const checked = new Set();
263
+
264
+ return {
265
+ IfStatement(node) {
266
+ ifStatements.add(node);
267
+ },
268
+ BreakStatement(node) {
269
+ if (!node.label) {
270
+ breakStatements.push(node);
271
+ }
272
+ },
273
+ * 'Program:exit'() {
274
+ for (const node of ifStatements) {
275
+ if (checked.has(node)) {
276
+ continue;
277
+ }
278
+
279
+ const {discriminant, ifStatements} = getStatements(node);
280
+
281
+ if (!discriminant || ifStatements.length < options.minimumCases) {
282
+ continue;
283
+ }
284
+
285
+ for (const {statement} of ifStatements) {
286
+ checked.add(statement);
287
+ }
288
+
289
+ const problem = {
290
+ loc: {
291
+ start: node.loc.start,
292
+ end: node.consequent.loc.start,
293
+ },
294
+ messageId: MESSAGE_ID,
295
+ };
296
+
297
+ if (
298
+ !hasSideEffect(discriminant, sourceCode)
299
+ && !ifStatements.some(({statement}) => hasBreakInside(breakStatements, statement))
300
+ ) {
301
+ problem.fix = fix({discriminant, ifStatements}, sourceCode, options);
302
+ }
303
+
304
+ yield problem;
305
+ }
306
+ },
307
+ };
308
+ };
309
+
310
+ const schema = [
311
+ {
312
+ type: 'object',
313
+ additionalProperties: false,
314
+ properties: {
315
+ minimumCases: {
316
+ type: 'integer',
317
+ minimum: 2,
318
+ default: 3,
319
+ },
320
+ emptyDefaultCase: {
321
+ enum: [
322
+ 'no-default-comment',
323
+ 'do-nothing-comment',
324
+ 'no-default-case',
325
+ ],
326
+ default: 'no-default-comment',
327
+ },
328
+ },
329
+ },
330
+ ];
331
+
332
+ /** @type {import('eslint').Rule.RuleModule} */
333
+ module.exports = {
334
+ create,
335
+ meta: {
336
+ type: 'suggestion',
337
+ docs: {
338
+ description: 'Prefer `switch` over multiple `else-if`.',
339
+ },
340
+ fixable: 'code',
341
+ schema,
342
+ messages,
343
+ },
344
+ };
@@ -0,0 +1,282 @@
1
+ 'use strict';
2
+ const {isParenthesized} = require('@eslint-community/eslint-utils');
3
+ const avoidCapture = require('./utils/avoid-capture.js');
4
+ const needsSemicolon = require('./utils/needs-semicolon.js');
5
+ const isSameReference = require('./utils/is-same-reference.js');
6
+ const getIndentString = require('./utils/get-indent-string.js');
7
+ const {getParenthesizedText} = require('./utils/parentheses.js');
8
+ const shouldAddParenthesesToConditionalExpressionChild = require('./utils/should-add-parentheses-to-conditional-expression-child.js');
9
+ const {extendFixRange} = require('./fix/index.js');
10
+ const getScopes = require('./utils/get-scopes.js');
11
+
12
+ const messageId = 'prefer-ternary';
13
+
14
+ const isTernary = node => node?.type === 'ConditionalExpression';
15
+
16
+ function getNodeBody(node) {
17
+ /* c8 ignore next 3 */
18
+ if (!node) {
19
+ return;
20
+ }
21
+
22
+ if (node.type === 'ExpressionStatement') {
23
+ return getNodeBody(node.expression);
24
+ }
25
+
26
+ if (node.type === 'BlockStatement') {
27
+ const body = node.body.filter(({type}) => type !== 'EmptyStatement');
28
+ if (body.length === 1) {
29
+ return getNodeBody(body[0]);
30
+ }
31
+ }
32
+
33
+ return node;
34
+ }
35
+
36
+ const isSingleLineNode = node => node.loc.start.line === node.loc.end.line;
37
+
38
+ /** @param {import('eslint').Rule.RuleContext} context */
39
+ const create = context => {
40
+ const onlySingleLine = context.options[0] === 'only-single-line';
41
+ const {sourceCode} = context;
42
+ const scopeToNamesGeneratedByFixer = new WeakMap();
43
+ const isSafeName = (name, scopes) => scopes.every(scope => {
44
+ const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
45
+ return !generatedNames || !generatedNames.has(name);
46
+ });
47
+
48
+ const getText = node => {
49
+ let text = getParenthesizedText(node, sourceCode);
50
+ if (
51
+ !isParenthesized(node, sourceCode)
52
+ && shouldAddParenthesesToConditionalExpressionChild(node)
53
+ ) {
54
+ text = `(${text})`;
55
+ }
56
+
57
+ return text;
58
+ };
59
+
60
+ function merge(options, mergeOptions) {
61
+ const {
62
+ before = '',
63
+ after = ';',
64
+ consequent,
65
+ alternate,
66
+ node,
67
+ } = options;
68
+
69
+ const {
70
+ checkThrowStatement,
71
+ returnFalseIfNotMergeable,
72
+ } = {
73
+ checkThrowStatement: false,
74
+ returnFalseIfNotMergeable: false,
75
+ ...mergeOptions,
76
+ };
77
+
78
+ if (!consequent || !alternate || consequent.type !== alternate.type) {
79
+ return returnFalseIfNotMergeable ? false : options;
80
+ }
81
+
82
+ const {type, argument, delegate, left, right, operator} = consequent;
83
+
84
+ if (
85
+ type === 'ReturnStatement'
86
+ && !isTernary(argument)
87
+ && !isTernary(alternate.argument)
88
+ ) {
89
+ return merge({
90
+ before: `${before}return `,
91
+ after,
92
+ consequent: argument === null ? 'undefined' : argument,
93
+ alternate: alternate.argument === null ? 'undefined' : alternate.argument,
94
+ node,
95
+ });
96
+ }
97
+
98
+ if (
99
+ type === 'YieldExpression'
100
+ && delegate === alternate.delegate
101
+ && !isTernary(argument)
102
+ && !isTernary(alternate.argument)
103
+ ) {
104
+ return merge({
105
+ before: `${before}yield${delegate ? '*' : ''} (`,
106
+ after: `)${after}`,
107
+ consequent: argument === null ? 'undefined' : argument,
108
+ alternate: alternate.argument === null ? 'undefined' : alternate.argument,
109
+ node,
110
+ });
111
+ }
112
+
113
+ if (
114
+ type === 'AwaitExpression'
115
+ && !isTernary(argument)
116
+ && !isTernary(alternate.argument)
117
+ ) {
118
+ return merge({
119
+ before: `${before}await (`,
120
+ after: `)${after}`,
121
+ consequent: argument,
122
+ alternate: alternate.argument,
123
+ node,
124
+ });
125
+ }
126
+
127
+ if (
128
+ checkThrowStatement
129
+ && type === 'ThrowStatement'
130
+ && !isTernary(argument)
131
+ && !isTernary(alternate.argument)
132
+ ) {
133
+ // `ThrowStatement` don't check nested
134
+
135
+ // If `IfStatement` is not a `BlockStatement`, need add `{}`
136
+ const {parent} = node;
137
+ const needBraces = parent && parent.type !== 'BlockStatement';
138
+ return {
139
+ type,
140
+ before: `${before}${needBraces ? '{\n{{INDENT_STRING}}' : ''}const {{ERROR_NAME}} = `,
141
+ after: `;\n{{INDENT_STRING}}throw {{ERROR_NAME}};${needBraces ? '\n}' : ''}`,
142
+ consequent: argument,
143
+ alternate: alternate.argument,
144
+ };
145
+ }
146
+
147
+ if (
148
+ type === 'AssignmentExpression'
149
+ && operator === alternate.operator
150
+ && !isTernary(left)
151
+ && !isTernary(alternate.left)
152
+ && !isTernary(right)
153
+ && !isTernary(alternate.right)
154
+ && isSameReference(left, alternate.left)
155
+ ) {
156
+ return merge({
157
+ before: `${before}${sourceCode.getText(left)} ${operator} `,
158
+ after,
159
+ consequent: right,
160
+ alternate: alternate.right,
161
+ node,
162
+ });
163
+ }
164
+
165
+ return returnFalseIfNotMergeable ? false : options;
166
+ }
167
+
168
+ return {
169
+ IfStatement(node) {
170
+ if (
171
+ (node.parent.type === 'IfStatement' && node.parent.alternate === node)
172
+ || node.test.type === 'ConditionalExpression'
173
+ || !node.consequent
174
+ || !node.alternate
175
+ ) {
176
+ return;
177
+ }
178
+
179
+ const consequent = getNodeBody(node.consequent);
180
+ const alternate = getNodeBody(node.alternate);
181
+
182
+ if (
183
+ onlySingleLine
184
+ && [consequent, alternate, node.test].some(node => !isSingleLineNode(node))
185
+ ) {
186
+ return;
187
+ }
188
+
189
+ const result = merge({node, consequent, alternate}, {
190
+ checkThrowStatement: true,
191
+ returnFalseIfNotMergeable: true,
192
+ });
193
+
194
+ if (!result) {
195
+ return;
196
+ }
197
+
198
+ const problem = {node, messageId};
199
+
200
+ // Don't fix if there are comments
201
+ if (sourceCode.getCommentsInside(node).length > 0) {
202
+ return problem;
203
+ }
204
+
205
+ const scope = sourceCode.getScope(node);
206
+ problem.fix = function * (fixer) {
207
+ const testText = getText(node.test);
208
+ const consequentText = typeof result.consequent === 'string'
209
+ ? result.consequent
210
+ : getText(result.consequent);
211
+ const alternateText = typeof result.alternate === 'string'
212
+ ? result.alternate
213
+ : getText(result.alternate);
214
+
215
+ let {type, before, after} = result;
216
+
217
+ let generateNewVariables = false;
218
+ if (type === 'ThrowStatement') {
219
+ const scopes = getScopes(scope);
220
+ const errorName = avoidCapture('error', scopes, isSafeName);
221
+
222
+ for (const scope of scopes) {
223
+ if (!scopeToNamesGeneratedByFixer.has(scope)) {
224
+ scopeToNamesGeneratedByFixer.set(scope, new Set());
225
+ }
226
+
227
+ const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
228
+ generatedNames.add(errorName);
229
+ }
230
+
231
+ const indentString = getIndentString(node, sourceCode);
232
+
233
+ after = after
234
+ .replace('{{INDENT_STRING}}', indentString)
235
+ .replace('{{ERROR_NAME}}', errorName);
236
+ before = before
237
+ .replace('{{INDENT_STRING}}', indentString)
238
+ .replace('{{ERROR_NAME}}', errorName);
239
+ generateNewVariables = true;
240
+ }
241
+
242
+ let fixed = `${before}${testText} ? ${consequentText} : ${alternateText}${after}`;
243
+ const tokenBefore = sourceCode.getTokenBefore(node);
244
+ const shouldAddSemicolonBefore = needsSemicolon(tokenBefore, sourceCode, fixed);
245
+ if (shouldAddSemicolonBefore) {
246
+ fixed = `;${fixed}`;
247
+ }
248
+
249
+ yield fixer.replaceText(node, fixed);
250
+
251
+ if (generateNewVariables) {
252
+ yield * extendFixRange(fixer, sourceCode.ast.range);
253
+ }
254
+ };
255
+
256
+ return problem;
257
+ },
258
+ };
259
+ };
260
+
261
+ const schema = [
262
+ {
263
+ enum: ['always', 'only-single-line'],
264
+ default: 'always',
265
+ },
266
+ ];
267
+
268
+ /** @type {import('eslint').Rule.RuleModule} */
269
+ module.exports = {
270
+ create,
271
+ meta: {
272
+ type: 'suggestion',
273
+ docs: {
274
+ description: 'Prefer ternary expressions over simple `if-else` statements.',
275
+ },
276
+ fixable: 'code',
277
+ schema,
278
+ messages: {
279
+ [messageId]: 'This `if` statement can be replaced by a ternary expression.',
280
+ },
281
+ },
282
+ };