oxclippy 0.1.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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +140 -0
  3. package/dist/oxclippy.js +2428 -0
  4. package/package.json +53 -0
  5. package/src/index.ts +1 -0
  6. package/src/plugin.ts +134 -0
  7. package/src/rules/almost-swapped.ts +52 -0
  8. package/src/rules/bool-comparison.ts +38 -0
  9. package/src/rules/bool-to-int-with-if.ts +30 -0
  10. package/src/rules/cognitive-complexity.ts +137 -0
  11. package/src/rules/collapsible-if.ts +38 -0
  12. package/src/rules/enum-variant-names.ts +45 -0
  13. package/src/rules/excessive-nesting.ts +84 -0
  14. package/src/rules/explicit-counter-loop.ts +66 -0
  15. package/src/rules/filter-then-first.ts +26 -0
  16. package/src/rules/float-comparison.ts +52 -0
  17. package/src/rules/float-equality-without-abs.ts +43 -0
  18. package/src/rules/fn-params-excessive-bools.ts +49 -0
  19. package/src/rules/identity-op.ts +46 -0
  20. package/src/rules/if-same-then-else.ts +34 -0
  21. package/src/rules/int-plus-one.ts +74 -0
  22. package/src/rules/let-and-return.ts +43 -0
  23. package/src/rules/manual-clamp.ts +63 -0
  24. package/src/rules/manual-every.ts +47 -0
  25. package/src/rules/manual-find.ts +59 -0
  26. package/src/rules/manual-includes.ts +65 -0
  27. package/src/rules/manual-is-finite.ts +57 -0
  28. package/src/rules/manual-some.ts +46 -0
  29. package/src/rules/manual-strip.ts +96 -0
  30. package/src/rules/manual-swap.ts +73 -0
  31. package/src/rules/map-identity.ts +67 -0
  32. package/src/rules/map-void-return.ts +23 -0
  33. package/src/rules/match-same-arms.ts +46 -0
  34. package/src/rules/needless-bool.ts +67 -0
  35. package/src/rules/needless-continue.ts +47 -0
  36. package/src/rules/needless-late-init.ts +44 -0
  37. package/src/rules/needless-range-loop.ts +127 -0
  38. package/src/rules/neg-multiply.ts +36 -0
  39. package/src/rules/never-loop.ts +49 -0
  40. package/src/rules/object-keys-values.ts +67 -0
  41. package/src/rules/prefer-structured-clone.ts +30 -0
  42. package/src/rules/promise-new-resolve.ts +59 -0
  43. package/src/rules/redundant-closure-call.ts +61 -0
  44. package/src/rules/redundant-closure.ts +81 -0
  45. package/src/rules/search-is-some.ts +45 -0
  46. package/src/rules/similar-names.ts +155 -0
  47. package/src/rules/single-case-switch.ts +27 -0
  48. package/src/rules/single-element-loop.ts +21 -0
  49. package/src/rules/struct-field-names.ts +108 -0
  50. package/src/rules/too-many-arguments.ts +37 -0
  51. package/src/rules/too-many-lines.ts +45 -0
  52. package/src/rules/unnecessary-fold.ts +65 -0
  53. package/src/rules/unnecessary-reduce-collect.ts +109 -0
  54. package/src/rules/unreadable-literal.ts +59 -0
  55. package/src/rules/used-underscore-binding.ts +41 -0
  56. package/src/rules/useless-conversion.ts +115 -0
  57. package/src/rules/xor-used-as-pow.ts +34 -0
  58. package/src/rules/zero-divided-by-zero.ts +24 -0
  59. package/src/types.ts +75 -0
@@ -0,0 +1,2428 @@
1
+ // src/types.ts
2
+ function isLiteral(node, value) {
3
+ if (!node || node.type !== "Literal")
4
+ return false;
5
+ return value === undefined ? true : node.value === value;
6
+ }
7
+ function isBoolLiteral(node) {
8
+ return isLiteral(node) && typeof node.value === "boolean";
9
+ }
10
+ function isIdentifier(node, name) {
11
+ if (!node || node.type !== "Identifier")
12
+ return false;
13
+ return name === undefined ? true : node.name === name;
14
+ }
15
+ function isCallOf(node, object, method) {
16
+ if (node.type !== "CallExpression")
17
+ return false;
18
+ const callee = node.callee;
19
+ if (callee.type !== "MemberExpression")
20
+ return false;
21
+ return isIdentifier(callee.object, object) && isIdentifier(callee.property, method);
22
+ }
23
+ function isMethodCall(node, method) {
24
+ if (node.type !== "CallExpression")
25
+ return false;
26
+ const callee = node.callee;
27
+ return callee.type === "MemberExpression" && !callee.computed && isIdentifier(callee.property, method);
28
+ }
29
+ function unwrapBlock(node) {
30
+ if (!node)
31
+ return null;
32
+ if (node.type === "BlockStatement") {
33
+ return node.body.length === 1 ? node.body[0] : null;
34
+ }
35
+ return node;
36
+ }
37
+ function getFunctionBody(node) {
38
+ const body = node.body;
39
+ if (!body)
40
+ return null;
41
+ if (body.type === "BlockStatement")
42
+ return body.body;
43
+ return null;
44
+ }
45
+ function getForOfVar(node) {
46
+ const left = node.left;
47
+ if (left.type === "VariableDeclaration" && left.declarations.length === 1) {
48
+ const decl = left.declarations[0];
49
+ if (decl.id && decl.id.type === "Identifier")
50
+ return decl.id.name;
51
+ }
52
+ return null;
53
+ }
54
+
55
+ // src/rules/needless-bool.ts
56
+ var needless_bool_default = {
57
+ create(context) {
58
+ return {
59
+ IfStatement(node) {
60
+ if (!node.alternate)
61
+ return;
62
+ const consequent = unwrapBlock(node.consequent);
63
+ const alternate = unwrapBlock(node.alternate);
64
+ if (!consequent || !alternate)
65
+ return;
66
+ if (consequent.type === "ReturnStatement" && alternate.type === "ReturnStatement") {
67
+ const cArg = consequent.argument;
68
+ const aArg = alternate.argument;
69
+ if (isBoolLiteral(cArg) && isBoolLiteral(aArg) && cArg.value !== aArg.value) {
70
+ const suggestion = cArg.value ? "return <condition>" : "return !<condition>";
71
+ context.report({
72
+ message: `Needless bool: this if/else returns opposing booleans. Simplify to \`${suggestion}\`. (clippy::needless_bool)`,
73
+ node
74
+ });
75
+ }
76
+ return;
77
+ }
78
+ if (consequent.type === "ExpressionStatement" && alternate.type === "ExpressionStatement" && consequent.expression.type === "AssignmentExpression" && alternate.expression.type === "AssignmentExpression" && consequent.expression.operator === "=" && alternate.expression.operator === "=") {
79
+ const cExpr = consequent.expression;
80
+ const aExpr = alternate.expression;
81
+ if (cExpr.left.type === "Identifier" && aExpr.left.type === "Identifier" && cExpr.left.name === aExpr.left.name && isBoolLiteral(cExpr.right) && isBoolLiteral(aExpr.right) && cExpr.right.value !== aExpr.right.value) {
82
+ const varName = cExpr.left.name;
83
+ const suggestion = cExpr.right.value ? `${varName} = <condition>` : `${varName} = !<condition>`;
84
+ context.report({
85
+ message: `Needless bool: this if/else assigns opposing booleans. Simplify to \`${suggestion}\`. (clippy::needless_bool)`,
86
+ node
87
+ });
88
+ }
89
+ }
90
+ }
91
+ };
92
+ }
93
+ };
94
+
95
+ // src/rules/collapsible-if.ts
96
+ var collapsible_if_default = {
97
+ create(context) {
98
+ return {
99
+ IfStatement(node) {
100
+ if (node.alternate)
101
+ return;
102
+ const body = node.consequent;
103
+ if (!body)
104
+ return;
105
+ let inner = null;
106
+ if (body.type === "BlockStatement" && body.body.length === 1) {
107
+ inner = body.body[0];
108
+ } else if (body.type === "IfStatement") {
109
+ inner = body;
110
+ }
111
+ if (!inner || inner.type !== "IfStatement")
112
+ return;
113
+ if (inner.alternate)
114
+ return;
115
+ context.report({
116
+ message: "Collapsible if: these nested ifs can be combined with `&&`. (clippy::collapsible_if)",
117
+ node
118
+ });
119
+ }
120
+ };
121
+ }
122
+ };
123
+
124
+ // src/rules/neg-multiply.ts
125
+ function isNegOne(node) {
126
+ if (node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "Literal" && node.argument.value === 1) {
127
+ return true;
128
+ }
129
+ if (node.type === "Literal" && node.value === -1)
130
+ return true;
131
+ return false;
132
+ }
133
+ var neg_multiply_default = {
134
+ create(context) {
135
+ return {
136
+ BinaryExpression(node) {
137
+ if (node.operator !== "*")
138
+ return;
139
+ if (isNegOne(node.left) || isNegOne(node.right)) {
140
+ context.report({
141
+ message: "Neg multiply: multiplying by -1 is less readable than negation. Use `-x` instead of `x * -1`. (clippy::neg_multiply)",
142
+ node
143
+ });
144
+ }
145
+ }
146
+ };
147
+ }
148
+ };
149
+
150
+ // src/rules/bool-comparison.ts
151
+ var bool_comparison_default = {
152
+ create(context) {
153
+ return {
154
+ BinaryExpression(node) {
155
+ if (node.operator !== "===" && node.operator !== "!==")
156
+ return;
157
+ const leftBool = isBoolLiteral(node.left);
158
+ const rightBool = isBoolLiteral(node.right);
159
+ if (!leftBool && !rightBool)
160
+ return;
161
+ if (leftBool && rightBool)
162
+ return;
163
+ const boolVal = leftBool ? node.left.value : node.right.value;
164
+ const isEqual = node.operator === "===";
165
+ let suggestion;
166
+ if (boolVal && isEqual || !boolVal && !isEqual) {
167
+ suggestion = "use the expression directly";
168
+ } else {
169
+ suggestion = "negate the expression with `!`";
170
+ }
171
+ context.report({
172
+ message: `Bool comparison: comparison to \`${boolVal}\` is needless. Instead, ${suggestion}. (clippy::bool_comparison)`,
173
+ node
174
+ });
175
+ }
176
+ };
177
+ }
178
+ };
179
+
180
+ // src/rules/identity-op.ts
181
+ var identity_op_default = {
182
+ create(context) {
183
+ return {
184
+ BinaryExpression(node) {
185
+ const { operator, left, right } = node;
186
+ let match = false;
187
+ switch (operator) {
188
+ case "+":
189
+ if (isLiteral(left) && typeof left.value === "string")
190
+ break;
191
+ if (isLiteral(right) && typeof right.value === "string")
192
+ break;
193
+ match = isLiteral(left, 0) || isLiteral(right, 0);
194
+ break;
195
+ case "-":
196
+ match = isLiteral(right, 0);
197
+ break;
198
+ case "*":
199
+ match = isLiteral(left, 1) || isLiteral(right, 1);
200
+ break;
201
+ case "/":
202
+ match = isLiteral(right, 1);
203
+ break;
204
+ case "**":
205
+ match = isLiteral(right, 1);
206
+ break;
207
+ }
208
+ if (match) {
209
+ context.report({
210
+ message: `Identity op: \`${operator} ${operator === "+" || operator === "-" ? "0" : "1"}\` has no effect. (clippy::identity_op)`,
211
+ node
212
+ });
213
+ }
214
+ }
215
+ };
216
+ }
217
+ };
218
+
219
+ // src/rules/single-case-switch.ts
220
+ var single_case_switch_default = {
221
+ create(context) {
222
+ return {
223
+ SwitchStatement(node) {
224
+ const cases = node.cases;
225
+ if (!cases)
226
+ return;
227
+ const nonDefaultCases = cases.filter((c) => c.test !== null);
228
+ const defaultCase = cases.find((c) => c.test === null);
229
+ if (nonDefaultCases.length !== 1)
230
+ return;
231
+ const suggestion = defaultCase ? "if/else" : "if";
232
+ context.report({
233
+ message: `Single-case switch: this switch has only one case. Use an \`${suggestion}\` statement instead. (clippy::single_match)`,
234
+ node
235
+ });
236
+ }
237
+ };
238
+ }
239
+ };
240
+
241
+ // src/rules/too-many-arguments.ts
242
+ var THRESHOLD = 5;
243
+ function checkParams(node, context) {
244
+ const params = node.params;
245
+ if (!params || params.length <= THRESHOLD)
246
+ return;
247
+ const name = node.id?.name ?? (node.parent?.type === "VariableDeclarator" ? node.parent.id?.name : null) ?? "<anonymous>";
248
+ context.report({
249
+ message: `Too many arguments: \`${name}\` has ${params.length} parameters (max ${THRESHOLD}). Consider using an options object. (clippy::too_many_arguments)`,
250
+ node
251
+ });
252
+ }
253
+ var too_many_arguments_default = {
254
+ create(context) {
255
+ return {
256
+ FunctionDeclaration(node) {
257
+ checkParams(node, context);
258
+ },
259
+ FunctionExpression(node) {
260
+ checkParams(node, context);
261
+ },
262
+ ArrowFunctionExpression(node) {
263
+ checkParams(node, context);
264
+ }
265
+ };
266
+ }
267
+ };
268
+
269
+ // src/rules/too-many-lines.ts
270
+ var THRESHOLD2 = 100;
271
+ function countLines(node, sourceText) {
272
+ if (node.loc)
273
+ return node.loc.end.line - node.loc.start.line + 1;
274
+ if (node.start != null && node.end != null) {
275
+ return sourceText.substring(node.start, node.end).split(`
276
+ `).length;
277
+ }
278
+ return 0;
279
+ }
280
+ function checkLines(node, context) {
281
+ const lines = countLines(node, context.sourceCode.text);
282
+ if (lines <= THRESHOLD2)
283
+ return;
284
+ const name = node.id?.name ?? (node.parent?.type === "VariableDeclarator" ? node.parent.id?.name : null) ?? "<anonymous>";
285
+ context.report({
286
+ message: `Too many lines: \`${name}\` is ${lines} lines long (max ${THRESHOLD2}). Consider breaking it into smaller functions. (clippy::too_many_lines)`,
287
+ node
288
+ });
289
+ }
290
+ var too_many_lines_default = {
291
+ create(context) {
292
+ return {
293
+ FunctionDeclaration(node) {
294
+ checkLines(node, context);
295
+ },
296
+ FunctionExpression(node) {
297
+ checkLines(node, context);
298
+ },
299
+ ArrowFunctionExpression(node) {
300
+ checkLines(node, context);
301
+ }
302
+ };
303
+ }
304
+ };
305
+
306
+ // src/rules/filter-then-first.ts
307
+ var filter_then_first_default = {
308
+ create(context) {
309
+ return {
310
+ MemberExpression(node) {
311
+ if (!node.computed || !isLiteral(node.property, 0))
312
+ return;
313
+ const obj = node.object;
314
+ if (!isMethodCall(obj, "filter"))
315
+ return;
316
+ context.report({
317
+ message: "Filter then first: `.filter(fn)[0]` is less efficient and readable than `.find(fn)`. (clippy::filter_next)",
318
+ node
319
+ });
320
+ }
321
+ };
322
+ }
323
+ };
324
+
325
+ // src/rules/map-void-return.ts
326
+ var map_void_return_default = {
327
+ create(context) {
328
+ return {
329
+ ExpressionStatement(node) {
330
+ const expr = node.expression;
331
+ if (!isMethodCall(expr, "map"))
332
+ return;
333
+ context.report({
334
+ message: "Map void return: `.map()` result is unused. Use `.forEach()` for side effects. (clippy::map_unit_fn)",
335
+ node
336
+ });
337
+ }
338
+ };
339
+ }
340
+ };
341
+
342
+ // src/rules/useless-conversion.ts
343
+ var useless_conversion_default = {
344
+ create(context) {
345
+ return {
346
+ CallExpression(node) {
347
+ const { callee } = node;
348
+ const args = node.arguments;
349
+ if (!args || args.length !== 1)
350
+ return;
351
+ const arg = args[0];
352
+ if (isIdentifier(callee, "String") && arg.type === "Literal" && typeof arg.value === "string") {
353
+ context.report({
354
+ message: "Useless conversion: `String()` called on a string literal. (clippy::useless_conversion)",
355
+ node
356
+ });
357
+ return;
358
+ }
359
+ if (isIdentifier(callee, "Number") && arg.type === "Literal" && typeof arg.value === "number") {
360
+ context.report({
361
+ message: "Useless conversion: `Number()` called on a number literal. (clippy::useless_conversion)",
362
+ node
363
+ });
364
+ return;
365
+ }
366
+ if (isIdentifier(callee, "Boolean") && arg.type === "Literal" && typeof arg.value === "boolean") {
367
+ context.report({
368
+ message: "Useless conversion: `Boolean()` called on a boolean literal. (clippy::useless_conversion)",
369
+ node
370
+ });
371
+ return;
372
+ }
373
+ if (isCallOf(node, "Array", "from") && arg.type === "ArrayExpression") {
374
+ context.report({
375
+ message: "Useless conversion: `Array.from()` called on an array literal. Use the array directly. (clippy::useless_conversion)",
376
+ node
377
+ });
378
+ return;
379
+ }
380
+ if (callee.type === "MemberExpression" && isIdentifier(callee.property, "toString") && callee.object.type === "Literal" && typeof callee.object.value === "string" && args.length === 0) {}
381
+ },
382
+ "CallExpression:exit"(node) {
383
+ const { callee } = node;
384
+ const args = node.arguments;
385
+ if (!args || args.length !== 0)
386
+ return;
387
+ if (callee.type !== "MemberExpression")
388
+ return;
389
+ const obj = callee.object;
390
+ const method = callee.property;
391
+ if (method.type !== "Identifier")
392
+ return;
393
+ if (obj.type === "Literal" && typeof obj.value === "string" && method.name === "toString") {
394
+ context.report({
395
+ message: "Useless conversion: `.toString()` called on a string literal. (clippy::useless_conversion)",
396
+ node
397
+ });
398
+ return;
399
+ }
400
+ if (obj.type === "Literal" && typeof obj.value === "number" && method.name === "valueOf") {
401
+ context.report({
402
+ message: "Useless conversion: `.valueOf()` called on a number literal. (clippy::useless_conversion)",
403
+ node
404
+ });
405
+ }
406
+ }
407
+ };
408
+ }
409
+ };
410
+
411
+ // src/rules/manual-clamp.ts
412
+ function isMathCall(node, method) {
413
+ if (node.type !== "CallExpression")
414
+ return false;
415
+ const callee = node.callee;
416
+ return callee.type === "MemberExpression" && isIdentifier(callee.object, "Math") && isIdentifier(callee.property, method);
417
+ }
418
+ var manual_clamp_default = {
419
+ create(context) {
420
+ return {
421
+ CallExpression(node) {
422
+ const args = node.arguments;
423
+ if (!args || args.length !== 2)
424
+ return;
425
+ if (isMathCall(node, "max") && isMathCall(args[0], "min")) {
426
+ context.report({
427
+ message: "Manual clamp: this `Math.max(min, Math.min(val, max))` pattern is a clamp. Consider extracting a `clamp(val, min, max)` helper. (clippy::manual_clamp)",
428
+ node
429
+ });
430
+ return;
431
+ }
432
+ if (isMathCall(node, "max") && isMathCall(args[1], "min")) {
433
+ context.report({
434
+ message: "Manual clamp: this `Math.max(Math.min(val, max), min)` pattern is a clamp. Consider extracting a `clamp(val, min, max)` helper. (clippy::manual_clamp)",
435
+ node
436
+ });
437
+ return;
438
+ }
439
+ if (isMathCall(node, "min") && isMathCall(args[0], "max")) {
440
+ context.report({
441
+ message: "Manual clamp: this `Math.min(max, Math.max(val, min))` pattern is a clamp. Consider extracting a `clamp(val, min, max)` helper. (clippy::manual_clamp)",
442
+ node
443
+ });
444
+ return;
445
+ }
446
+ if (isMathCall(node, "min") && isMathCall(args[1], "max")) {
447
+ context.report({
448
+ message: "Manual clamp: this `Math.min(Math.max(val, min), max)` pattern is a clamp. Consider extracting a `clamp(val, min, max)` helper. (clippy::manual_clamp)",
449
+ node
450
+ });
451
+ return;
452
+ }
453
+ }
454
+ };
455
+ }
456
+ };
457
+
458
+ // src/rules/manual-strip.ts
459
+ function getStringMethodTarget(testExpr, methodName) {
460
+ if (!isMethodCall(testExpr, methodName))
461
+ return null;
462
+ const callee = testExpr.callee;
463
+ if (callee.object?.type !== "Identifier")
464
+ return null;
465
+ if (!testExpr.arguments || testExpr.arguments.length !== 1)
466
+ return null;
467
+ const arg = testExpr.arguments[0];
468
+ if (arg.type !== "Identifier" && arg.type !== "Literal")
469
+ return null;
470
+ return {
471
+ str: callee.object.name,
472
+ arg: arg.type === "Identifier" ? arg.name : JSON.stringify(arg.value)
473
+ };
474
+ }
475
+ function bodyContainsSlice(node, strName) {
476
+ if (!node || typeof node !== "object")
477
+ return false;
478
+ if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.type === "Identifier" && node.callee.object.name === strName && node.callee.property?.type === "Identifier" && node.callee.property.name === "slice") {
479
+ return true;
480
+ }
481
+ for (const key of Object.keys(node)) {
482
+ if (key === "type" || key === "loc" || key === "range" || key === "parent" || key === "start" || key === "end")
483
+ continue;
484
+ const val = node[key];
485
+ if (Array.isArray(val)) {
486
+ for (const child of val) {
487
+ if (child && typeof child === "object" && child.type && bodyContainsSlice(child, strName))
488
+ return true;
489
+ }
490
+ } else if (val && typeof val === "object" && val.type && bodyContainsSlice(val, strName)) {
491
+ return true;
492
+ }
493
+ }
494
+ return false;
495
+ }
496
+ var manual_strip_default = {
497
+ create(context) {
498
+ return {
499
+ IfStatement(node) {
500
+ const test = node.test;
501
+ if (!test)
502
+ return;
503
+ const startsInfo = getStringMethodTarget(test, "startsWith");
504
+ if (startsInfo && bodyContainsSlice(node.consequent, startsInfo.str)) {
505
+ context.report({
506
+ message: `Manual strip: checking \`startsWith()\` then slicing is a manual prefix strip. Consider extracting a \`stripPrefix\` helper. (clippy::manual_strip)`,
507
+ node
508
+ });
509
+ return;
510
+ }
511
+ const endsInfo = getStringMethodTarget(test, "endsWith");
512
+ if (endsInfo && bodyContainsSlice(node.consequent, endsInfo.str)) {
513
+ context.report({
514
+ message: `Manual strip: checking \`endsWith()\` then slicing is a manual suffix strip. Consider extracting a \`stripSuffix\` helper. (clippy::manual_strip)`,
515
+ node
516
+ });
517
+ }
518
+ }
519
+ };
520
+ }
521
+ };
522
+
523
+ // src/rules/manual-find.ts
524
+ function isReturnOfVar(stmt, varName) {
525
+ if (stmt.type !== "ReturnStatement")
526
+ return false;
527
+ return stmt.argument?.type === "Identifier" && stmt.argument.name === varName;
528
+ }
529
+ function isReturnNullish(stmt) {
530
+ if (stmt.type !== "ReturnStatement")
531
+ return false;
532
+ if (!stmt.argument)
533
+ return true;
534
+ if (stmt.argument.type === "Identifier" && stmt.argument.name === "undefined")
535
+ return true;
536
+ if (stmt.argument.type === "Literal" && stmt.argument.value === null)
537
+ return true;
538
+ return false;
539
+ }
540
+ function checkBody(body, context) {
541
+ for (let i = 0;i < body.length; i++) {
542
+ const stmt = body[i];
543
+ if (stmt.type !== "ForOfStatement")
544
+ continue;
545
+ const loopVar = getForOfVar(stmt);
546
+ if (!loopVar)
547
+ continue;
548
+ const inner = unwrapBlock(stmt.body);
549
+ if (!inner)
550
+ continue;
551
+ if (inner.type === "IfStatement" && !inner.alternate) {
552
+ const consequent = unwrapBlock(inner.consequent);
553
+ if (consequent && isReturnOfVar(consequent, loopVar)) {
554
+ const next = body[i + 1];
555
+ if (next && isReturnNullish(next)) {
556
+ context.report({
557
+ message: "Manual find: this loop can be replaced with `.find()`. (clippy::manual_find)",
558
+ node: stmt
559
+ });
560
+ }
561
+ }
562
+ }
563
+ }
564
+ }
565
+ var manual_find_default = {
566
+ create(context) {
567
+ function checkFunction(node) {
568
+ const body = getFunctionBody(node);
569
+ if (body)
570
+ checkBody(body, context);
571
+ }
572
+ return {
573
+ FunctionDeclaration: checkFunction,
574
+ FunctionExpression: checkFunction,
575
+ ArrowFunctionExpression: checkFunction
576
+ };
577
+ }
578
+ };
579
+
580
+ // src/rules/manual-some.ts
581
+ function checkBody2(body, context) {
582
+ for (let i = 0;i < body.length; i++) {
583
+ const stmt = body[i];
584
+ if (stmt.type !== "ForOfStatement")
585
+ continue;
586
+ const inner = unwrapBlock(stmt.body);
587
+ if (!inner || inner.type !== "IfStatement" || inner.alternate)
588
+ continue;
589
+ const consequent = unwrapBlock(inner.consequent);
590
+ if (!consequent || consequent.type !== "ReturnStatement" || !isLiteral(consequent.argument, true))
591
+ continue;
592
+ const next = body[i + 1];
593
+ if (next && next.type === "ReturnStatement" && isLiteral(next.argument, false)) {
594
+ context.report({
595
+ message: "Manual some: this loop can be replaced with `.some()`. (clippy::manual_find)",
596
+ node: stmt
597
+ });
598
+ }
599
+ }
600
+ }
601
+ var manual_some_default = {
602
+ create(context) {
603
+ function checkFunction(node) {
604
+ const body = getFunctionBody(node);
605
+ if (body)
606
+ checkBody2(body, context);
607
+ }
608
+ return {
609
+ FunctionDeclaration: checkFunction,
610
+ FunctionExpression: checkFunction,
611
+ ArrowFunctionExpression: checkFunction
612
+ };
613
+ }
614
+ };
615
+
616
+ // src/rules/manual-every.ts
617
+ function checkBody3(body, context) {
618
+ for (let i = 0;i < body.length; i++) {
619
+ const stmt = body[i];
620
+ if (stmt.type !== "ForOfStatement")
621
+ continue;
622
+ const inner = unwrapBlock(stmt.body);
623
+ if (!inner || inner.type !== "IfStatement" || inner.alternate)
624
+ continue;
625
+ const consequent = unwrapBlock(inner.consequent);
626
+ if (!consequent || consequent.type !== "ReturnStatement" || !isLiteral(consequent.argument, false))
627
+ continue;
628
+ const next = body[i + 1];
629
+ if (next && next.type === "ReturnStatement" && isLiteral(next.argument, true)) {
630
+ context.report({
631
+ message: "Manual every: this loop can be replaced with `.every()`. (clippy::manual_find)",
632
+ node: stmt
633
+ });
634
+ }
635
+ }
636
+ }
637
+ var manual_every_default = {
638
+ create(context) {
639
+ function checkFunction(node) {
640
+ const body = getFunctionBody(node);
641
+ if (body)
642
+ checkBody3(body, context);
643
+ }
644
+ return {
645
+ FunctionDeclaration: checkFunction,
646
+ FunctionExpression: checkFunction,
647
+ ArrowFunctionExpression: checkFunction
648
+ };
649
+ }
650
+ };
651
+
652
+ // src/rules/manual-includes.ts
653
+ function isEqualityCheck(test, varName) {
654
+ if (test.type !== "BinaryExpression")
655
+ return false;
656
+ if (test.operator !== "===" && test.operator !== "==")
657
+ return false;
658
+ const left = test.left;
659
+ const right = test.right;
660
+ return left.type === "Identifier" && left.name === varName || right.type === "Identifier" && right.name === varName;
661
+ }
662
+ function checkBody4(body, context) {
663
+ for (let i = 0;i < body.length; i++) {
664
+ const stmt = body[i];
665
+ if (stmt.type !== "ForOfStatement")
666
+ continue;
667
+ const loopVar = getForOfVar(stmt);
668
+ if (!loopVar)
669
+ continue;
670
+ const inner = unwrapBlock(stmt.body);
671
+ if (!inner || inner.type !== "IfStatement" || inner.alternate)
672
+ continue;
673
+ if (!isEqualityCheck(inner.test, loopVar))
674
+ continue;
675
+ const consequent = unwrapBlock(inner.consequent);
676
+ if (!consequent || consequent.type !== "ReturnStatement" || !isLiteral(consequent.argument, true))
677
+ continue;
678
+ const next = body[i + 1];
679
+ if (next && next.type === "ReturnStatement" && isLiteral(next.argument, false)) {
680
+ context.report({
681
+ message: "Manual includes: this loop can be replaced with `.includes()`. (clippy::manual_find)",
682
+ node: stmt
683
+ });
684
+ }
685
+ }
686
+ }
687
+ var manual_includes_default = {
688
+ create(context) {
689
+ function checkFunction(node) {
690
+ const body = getFunctionBody(node);
691
+ if (body)
692
+ checkBody4(body, context);
693
+ }
694
+ return {
695
+ FunctionDeclaration: checkFunction,
696
+ FunctionExpression: checkFunction,
697
+ ArrowFunctionExpression: checkFunction
698
+ };
699
+ }
700
+ };
701
+
702
+ // src/rules/cognitive-complexity.ts
703
+ var THRESHOLD3 = 25;
704
+ function calculateComplexity(node) {
705
+ let complexity = 0;
706
+ function walk(n, nesting) {
707
+ if (!n || typeof n !== "object")
708
+ return;
709
+ switch (n.type) {
710
+ case "IfStatement":
711
+ complexity += 1 + nesting;
712
+ walk(n.test, nesting);
713
+ walk(n.consequent, nesting + 1);
714
+ if (n.alternate) {
715
+ if (n.alternate.type === "IfStatement") {
716
+ complexity += 1;
717
+ walk(n.alternate.test, nesting);
718
+ walk(n.alternate.consequent, nesting + 1);
719
+ if (n.alternate.alternate) {
720
+ walk(n.alternate.alternate, nesting);
721
+ }
722
+ } else {
723
+ complexity += 1;
724
+ walk(n.alternate, nesting + 1);
725
+ }
726
+ }
727
+ return;
728
+ case "ForStatement":
729
+ case "ForInStatement":
730
+ case "ForOfStatement":
731
+ case "WhileStatement":
732
+ case "DoWhileStatement":
733
+ complexity += 1 + nesting;
734
+ walkChildren(n, nesting + 1);
735
+ return;
736
+ case "CatchClause":
737
+ complexity += 1 + nesting;
738
+ walkChildren(n, nesting + 1);
739
+ return;
740
+ case "SwitchStatement":
741
+ complexity += 1 + nesting;
742
+ walkChildren(n, nesting + 1);
743
+ return;
744
+ case "ConditionalExpression":
745
+ complexity += 1 + nesting;
746
+ walk(n.test, nesting);
747
+ walk(n.consequent, nesting + 1);
748
+ walk(n.alternate, nesting + 1);
749
+ return;
750
+ case "LogicalExpression":
751
+ complexity += 1;
752
+ walk(n.left, nesting);
753
+ walk(n.right, nesting);
754
+ return;
755
+ case "FunctionDeclaration":
756
+ case "FunctionExpression":
757
+ case "ArrowFunctionExpression":
758
+ return;
759
+ }
760
+ walkChildren(n, nesting);
761
+ }
762
+ function walkChildren(n, nesting) {
763
+ for (const key of Object.keys(n)) {
764
+ if (key === "type" || key === "loc" || key === "range" || key === "parent")
765
+ continue;
766
+ const val = n[key];
767
+ if (Array.isArray(val)) {
768
+ for (const child of val) {
769
+ if (child && typeof child === "object" && child.type) {
770
+ walk(child, nesting);
771
+ }
772
+ }
773
+ } else if (val && typeof val === "object" && val.type) {
774
+ walk(val, nesting);
775
+ }
776
+ }
777
+ }
778
+ if (node.body) {
779
+ if (node.body.type === "BlockStatement") {
780
+ for (const stmt of node.body.body) {
781
+ walk(stmt, 0);
782
+ }
783
+ } else {
784
+ walk(node.body, 0);
785
+ }
786
+ }
787
+ return complexity;
788
+ }
789
+ var cognitive_complexity_default = {
790
+ create(context) {
791
+ function checkFunction(node) {
792
+ const complexity = calculateComplexity(node);
793
+ if (complexity <= THRESHOLD3)
794
+ return;
795
+ const name = node.id?.name ?? (node.parent?.type === "VariableDeclarator" ? node.parent.id?.name : null) ?? "<anonymous>";
796
+ context.report({
797
+ message: `Cognitive complexity: \`${name}\` has a complexity of ${complexity} (max ${THRESHOLD3}). Consider refactoring into smaller functions. (clippy::cognitive_complexity)`,
798
+ node
799
+ });
800
+ }
801
+ return {
802
+ FunctionDeclaration: checkFunction,
803
+ FunctionExpression: checkFunction,
804
+ ArrowFunctionExpression: checkFunction
805
+ };
806
+ }
807
+ };
808
+
809
+ // src/rules/float-comparison.ts
810
+ function isFloatLiteral(node) {
811
+ return node.type === "Literal" && typeof node.value === "number" && !Number.isInteger(node.value);
812
+ }
813
+ function isArithmetic(node) {
814
+ return node.type === "BinaryExpression" && (node.operator === "+" || node.operator === "-" || node.operator === "*" || node.operator === "/");
815
+ }
816
+ function involvesFloat(node) {
817
+ if (isFloatLiteral(node))
818
+ return true;
819
+ if (isArithmetic(node)) {
820
+ return involvesFloat(node.left) || involvesFloat(node.right);
821
+ }
822
+ return false;
823
+ }
824
+ var float_comparison_default = {
825
+ create(context) {
826
+ return {
827
+ BinaryExpression(node) {
828
+ if (node.operator !== "===" && node.operator !== "==" && node.operator !== "!==" && node.operator !== "!=")
829
+ return;
830
+ if (involvesFloat(node.left) || involvesFloat(node.right)) {
831
+ context.report({
832
+ message: "Float comparison: direct equality comparison of floating-point numbers is unreliable. Use `Math.abs(a - b) < Number.EPSILON` instead. (clippy::float_cmp)",
833
+ node
834
+ });
835
+ }
836
+ }
837
+ };
838
+ }
839
+ };
840
+
841
+ // src/rules/needless-range-loop.ts
842
+ function isZeroInit(init) {
843
+ if (init.type !== "VariableDeclaration" || init.declarations.length !== 1)
844
+ return null;
845
+ const decl = init.declarations[0];
846
+ if (decl.id?.type !== "Identifier")
847
+ return null;
848
+ if (decl.init?.type !== "Literal" || decl.init.value !== 0)
849
+ return null;
850
+ return decl.id.name;
851
+ }
852
+ function isLengthTest(test, indexVar) {
853
+ if (test.type !== "BinaryExpression" || test.operator !== "<")
854
+ return null;
855
+ if (test.left?.type !== "Identifier" || test.left.name !== indexVar)
856
+ return null;
857
+ if (test.right?.type === "MemberExpression" && test.right.object?.type === "Identifier" && test.right.property?.type === "Identifier" && test.right.property.name === "length") {
858
+ return test.right.object.name;
859
+ }
860
+ return null;
861
+ }
862
+ function isIncrement(update, indexVar) {
863
+ if (update.type === "UpdateExpression" && update.operator === "++" && update.argument?.type === "Identifier" && update.argument.name === indexVar)
864
+ return true;
865
+ if (update.type === "AssignmentExpression" && update.operator === "+=" && update.left?.type === "Identifier" && update.left.name === indexVar && update.right?.type === "Literal" && update.right.value === 1)
866
+ return true;
867
+ return false;
868
+ }
869
+ function onlyUsedAsIndex(body, indexVar, arrName) {
870
+ let onlyIndexAccess = true;
871
+ function walk(n) {
872
+ if (!n || typeof n !== "object" || !n.type)
873
+ return;
874
+ if (!onlyIndexAccess)
875
+ return;
876
+ if (n.type === "Identifier" && n.name === indexVar) {
877
+ onlyIndexAccess = false;
878
+ return;
879
+ }
880
+ if (n.type === "MemberExpression" && n.computed && n.object?.type === "Identifier" && n.object.name === arrName && n.property?.type === "Identifier" && n.property.name === indexVar) {
881
+ walk(n.object);
882
+ return;
883
+ }
884
+ for (const key of Object.keys(n)) {
885
+ if (key === "type" || key === "loc" || key === "range" || key === "parent" || key === "start" || key === "end")
886
+ continue;
887
+ const val = n[key];
888
+ if (Array.isArray(val)) {
889
+ for (const child of val) {
890
+ if (child && typeof child === "object" && child.type)
891
+ walk(child);
892
+ }
893
+ } else if (val && typeof val === "object" && val.type) {
894
+ walk(val);
895
+ }
896
+ }
897
+ }
898
+ walk(body);
899
+ return onlyIndexAccess;
900
+ }
901
+ var needless_range_loop_default = {
902
+ create(context) {
903
+ return {
904
+ ForStatement(node) {
905
+ if (!node.init || !node.test || !node.update)
906
+ return;
907
+ const indexVar = isZeroInit(node.init);
908
+ if (!indexVar)
909
+ return;
910
+ const arrName = isLengthTest(node.test, indexVar);
911
+ if (!arrName)
912
+ return;
913
+ if (!isIncrement(node.update, indexVar))
914
+ return;
915
+ if (onlyUsedAsIndex(node.body, indexVar, arrName)) {
916
+ context.report({
917
+ message: `Needless range loop: \`${indexVar}\` is only used to index \`${arrName}\`. Use \`for (const item of ${arrName})\` instead. (clippy::needless_range_loop)`,
918
+ node
919
+ });
920
+ }
921
+ }
922
+ };
923
+ }
924
+ };
925
+
926
+ // src/rules/manual-swap.ts
927
+ function exprName(node) {
928
+ if (node.type === "Identifier")
929
+ return node.name;
930
+ if (node.type === "MemberExpression" && !node.computed && node.object?.type === "Identifier" && node.property?.type === "Identifier") {
931
+ return `${node.object.name}.${node.property.name}`;
932
+ }
933
+ return null;
934
+ }
935
+ function checkBody5(body, context) {
936
+ for (let i = 0;i < body.length - 2; i++) {
937
+ const s1 = body[i];
938
+ const s2 = body[i + 1];
939
+ const s3 = body[i + 2];
940
+ if (s1.type !== "VariableDeclaration" || s1.declarations.length !== 1)
941
+ continue;
942
+ const decl = s1.declarations[0];
943
+ if (decl.id?.type !== "Identifier" || !decl.init)
944
+ continue;
945
+ const tmpName = decl.id.name;
946
+ const aName = exprName(decl.init);
947
+ if (!aName)
948
+ continue;
949
+ if (s2.type !== "ExpressionStatement" || s2.expression?.type !== "AssignmentExpression" || s2.expression.operator !== "=")
950
+ continue;
951
+ const assignLeft = exprName(s2.expression.left);
952
+ const bName = exprName(s2.expression.right);
953
+ if (assignLeft !== aName || !bName)
954
+ continue;
955
+ if (s3.type !== "ExpressionStatement" || s3.expression?.type !== "AssignmentExpression" || s3.expression.operator !== "=")
956
+ continue;
957
+ const assign2Left = exprName(s3.expression.left);
958
+ const assign2Right = exprName(s3.expression.right);
959
+ if (assign2Left !== bName || assign2Right !== tmpName)
960
+ continue;
961
+ context.report({
962
+ message: `Manual swap: use destructuring \`[${aName}, ${bName}] = [${bName}, ${aName}]\` instead of a temp variable. (clippy::manual_swap)`,
963
+ node: s1
964
+ });
965
+ }
966
+ }
967
+ var manual_swap_default = {
968
+ create(context) {
969
+ return {
970
+ BlockStatement(node) {
971
+ if (node.body)
972
+ checkBody5(node.body, context);
973
+ },
974
+ Program(node) {
975
+ if (node.body)
976
+ checkBody5(node.body, context);
977
+ }
978
+ };
979
+ }
980
+ };
981
+
982
+ // src/rules/search-is-some.ts
983
+ function isNullish(node) {
984
+ if (node.type === "Identifier" && node.name === "undefined")
985
+ return true;
986
+ if (node.type === "Literal" && node.value === null)
987
+ return true;
988
+ return false;
989
+ }
990
+ function isFindCall(node) {
991
+ return isMethodCall(node, "find");
992
+ }
993
+ function matchesFindNullish(left, right) {
994
+ return isFindCall(left) && isNullish(right) || isNullish(left) && isFindCall(right);
995
+ }
996
+ var search_is_some_default = {
997
+ create(context) {
998
+ return {
999
+ BinaryExpression(node) {
1000
+ const { operator, left, right } = node;
1001
+ if (!matchesFindNullish(left, right))
1002
+ return;
1003
+ if (operator === "!==" || operator === "!=") {
1004
+ context.report({
1005
+ message: "Search is some: `.find(fn) !== undefined` can be simplified to `.some(fn)` when you don't need the found value. (clippy::search_is_some)",
1006
+ node
1007
+ });
1008
+ } else if (operator === "===" || operator === "==") {
1009
+ context.report({
1010
+ message: "Search is some: `.find(fn) === undefined` can be simplified to `!.some(fn)` when you don't need the found value. (clippy::search_is_some)",
1011
+ node
1012
+ });
1013
+ }
1014
+ }
1015
+ };
1016
+ }
1017
+ };
1018
+
1019
+ // src/rules/let-and-return.ts
1020
+ function checkBody6(body, context) {
1021
+ if (body.length < 2)
1022
+ return;
1023
+ const last = body[body.length - 1];
1024
+ const secondLast = body[body.length - 2];
1025
+ if (last.type !== "ReturnStatement" || !last.argument || last.argument.type !== "Identifier")
1026
+ return;
1027
+ const returnedName = last.argument.name;
1028
+ if (secondLast.type !== "VariableDeclaration" || secondLast.declarations.length !== 1)
1029
+ return;
1030
+ const decl = secondLast.declarations[0];
1031
+ if (decl.id?.type !== "Identifier" || decl.id.name !== returnedName || !decl.init)
1032
+ return;
1033
+ context.report({
1034
+ message: `Let and return: \`${returnedName}\` is immediately returned. Return the expression directly. (clippy::let_and_return)`,
1035
+ node: secondLast
1036
+ });
1037
+ }
1038
+ var let_and_return_default = {
1039
+ create(context) {
1040
+ function checkFunction(node) {
1041
+ const body = getFunctionBody(node);
1042
+ if (body)
1043
+ checkBody6(body, context);
1044
+ }
1045
+ return {
1046
+ FunctionDeclaration: checkFunction,
1047
+ FunctionExpression: checkFunction,
1048
+ ArrowFunctionExpression: checkFunction
1049
+ };
1050
+ }
1051
+ };
1052
+
1053
+ // src/rules/xor-used-as-pow.ts
1054
+ function isIntLiteral(node) {
1055
+ return node.type === "Literal" && typeof node.value === "number" && Number.isInteger(node.value);
1056
+ }
1057
+ var xor_used_as_pow_default = {
1058
+ create(context) {
1059
+ return {
1060
+ BinaryExpression(node) {
1061
+ if (node.operator !== "^")
1062
+ return;
1063
+ if (!isIntLiteral(node.left) || !isIntLiteral(node.right))
1064
+ return;
1065
+ const base = node.left.value;
1066
+ const exp = node.right.value;
1067
+ if (base >= 2 && exp >= 2) {
1068
+ context.report({
1069
+ message: `XOR used as pow: \`${base} ^ ${exp}\` is bitwise XOR (= ${base ^ exp}), not exponentiation. Use \`${base} ** ${exp}\` (= ${base ** exp}) or \`Math.pow(${base}, ${exp})\` instead. (clippy::suspicious_xor_used_as_pow)`,
1070
+ node
1071
+ });
1072
+ }
1073
+ }
1074
+ };
1075
+ }
1076
+ };
1077
+
1078
+ // src/rules/map-identity.ts
1079
+ function isIdentityClosure(node) {
1080
+ if (node.type === "ArrowFunctionExpression" && node.params.length === 1) {
1081
+ const param = node.params[0];
1082
+ const body = node.body;
1083
+ if (param.type === "Identifier" && body.type === "Identifier" && body.name === param.name) {
1084
+ return true;
1085
+ }
1086
+ if (body.type === "BlockStatement" && body.body.length === 1) {
1087
+ const stmt = body.body[0];
1088
+ if (stmt.type === "ReturnStatement" && stmt.argument?.type === "Identifier" && stmt.argument.name === param.name) {
1089
+ return true;
1090
+ }
1091
+ }
1092
+ }
1093
+ if (node.type === "FunctionExpression" && node.params.length === 1) {
1094
+ const param = node.params[0];
1095
+ if (param.type === "Identifier" && node.body.type === "BlockStatement" && node.body.body.length === 1) {
1096
+ const stmt = node.body.body[0];
1097
+ if (stmt.type === "ReturnStatement" && stmt.argument?.type === "Identifier" && stmt.argument.name === param.name) {
1098
+ return true;
1099
+ }
1100
+ }
1101
+ }
1102
+ return false;
1103
+ }
1104
+ var map_identity_default = {
1105
+ create(context) {
1106
+ return {
1107
+ CallExpression(node) {
1108
+ if (!isMethodCall(node, "map"))
1109
+ return;
1110
+ const args = node.arguments;
1111
+ if (!args || args.length !== 1)
1112
+ return;
1113
+ if (isIdentityClosure(args[0])) {
1114
+ context.report({
1115
+ message: "Map identity: `.map(x => x)` is a no-op. Remove it, or use `.slice()` / `[...arr]` to copy. (clippy::map_identity)",
1116
+ node
1117
+ });
1118
+ }
1119
+ }
1120
+ };
1121
+ }
1122
+ };
1123
+
1124
+ // src/rules/redundant-closure-call.ts
1125
+ var redundant_closure_call_default = {
1126
+ create(context) {
1127
+ return {
1128
+ CallExpression(node) {
1129
+ const { callee } = node;
1130
+ if (node.arguments.length !== 0)
1131
+ return;
1132
+ let fn = callee;
1133
+ if (fn.type === "ArrowFunctionExpression" && fn.params.length === 0) {
1134
+ if (fn.body.type !== "BlockStatement") {
1135
+ context.report({
1136
+ message: "Redundant closure call: this IIFE wraps a single expression. Use the expression directly. (clippy::redundant_closure_call)",
1137
+ node
1138
+ });
1139
+ return;
1140
+ }
1141
+ if (fn.body.body.length === 1) {
1142
+ const stmt = fn.body.body[0];
1143
+ if (stmt.type === "ReturnStatement" && stmt.argument) {
1144
+ context.report({
1145
+ message: "Redundant closure call: this IIFE wraps a single return. Use the expression directly. (clippy::redundant_closure_call)",
1146
+ node
1147
+ });
1148
+ }
1149
+ }
1150
+ return;
1151
+ }
1152
+ if (fn.type === "FunctionExpression" && fn.params.length === 0 && fn.body.type === "BlockStatement" && fn.body.body.length === 1) {
1153
+ const stmt = fn.body.body[0];
1154
+ if (stmt.type === "ReturnStatement" && stmt.argument) {
1155
+ context.report({
1156
+ message: "Redundant closure call: this IIFE wraps a single return. Use the expression directly. (clippy::redundant_closure_call)",
1157
+ node
1158
+ });
1159
+ }
1160
+ }
1161
+ }
1162
+ };
1163
+ }
1164
+ };
1165
+
1166
+ // src/rules/almost-swapped.ts
1167
+ function assignTarget(node) {
1168
+ if (node.type !== "ExpressionStatement")
1169
+ return null;
1170
+ const expr = node.expression;
1171
+ if (expr.type !== "AssignmentExpression" || expr.operator !== "=")
1172
+ return null;
1173
+ if (expr.left.type === "Identifier")
1174
+ return expr.left.name;
1175
+ return null;
1176
+ }
1177
+ function assignSource(node) {
1178
+ if (node.type !== "ExpressionStatement")
1179
+ return null;
1180
+ const expr = node.expression;
1181
+ if (expr.type !== "AssignmentExpression" || expr.operator !== "=")
1182
+ return null;
1183
+ if (expr.right.type === "Identifier")
1184
+ return expr.right.name;
1185
+ return null;
1186
+ }
1187
+ function checkBody7(body, context) {
1188
+ for (let i = 0;i < body.length - 1; i++) {
1189
+ const s1 = body[i];
1190
+ const s2 = body[i + 1];
1191
+ const t1 = assignTarget(s1);
1192
+ const src1 = assignSource(s1);
1193
+ const t2 = assignTarget(s2);
1194
+ const src2 = assignSource(s2);
1195
+ if (t1 && src1 && t2 && src2 && t1 === src2 && t2 === src1 && t1 !== t2) {
1196
+ context.report({
1197
+ message: `Almost swapped: \`${t1} = ${src1}; ${t2} = ${src2};\` overwrites \`${t1}\` before saving it. Use \`[${t1}, ${t2}] = [${t2}, ${t1}]\`. (clippy::almost_swapped)`,
1198
+ node: s1
1199
+ });
1200
+ }
1201
+ }
1202
+ }
1203
+ var almost_swapped_default = {
1204
+ create(context) {
1205
+ return {
1206
+ BlockStatement(node) {
1207
+ if (node.body)
1208
+ checkBody7(node.body, context);
1209
+ },
1210
+ Program(node) {
1211
+ if (node.body)
1212
+ checkBody7(node.body, context);
1213
+ }
1214
+ };
1215
+ }
1216
+ };
1217
+
1218
+ // src/rules/if-same-then-else.ts
1219
+ function sourceRange(node, text) {
1220
+ if (node.start != null && node.end != null) {
1221
+ return text.slice(node.start, node.end);
1222
+ }
1223
+ return "";
1224
+ }
1225
+ var if_same_then_else_default = {
1226
+ create(context) {
1227
+ return {
1228
+ IfStatement(node) {
1229
+ if (!node.alternate)
1230
+ return;
1231
+ if (node.alternate.type === "IfStatement")
1232
+ return;
1233
+ const text = context.sourceCode.text;
1234
+ const consequentSrc = sourceRange(node.consequent, text);
1235
+ const alternateSrc = sourceRange(node.alternate, text);
1236
+ if (consequentSrc && consequentSrc === alternateSrc) {
1237
+ context.report({
1238
+ message: "If same then else: both branches of this if/else are identical. (clippy::if_same_then_else)",
1239
+ node
1240
+ });
1241
+ }
1242
+ }
1243
+ };
1244
+ }
1245
+ };
1246
+
1247
+ // src/rules/never-loop.ts
1248
+ function alwaysExits(node) {
1249
+ if (!node)
1250
+ return false;
1251
+ switch (node.type) {
1252
+ case "ReturnStatement":
1253
+ case "ThrowStatement":
1254
+ case "BreakStatement":
1255
+ return true;
1256
+ case "BlockStatement":
1257
+ return node.body.length > 0 && alwaysExits(node.body[node.body.length - 1]);
1258
+ case "IfStatement":
1259
+ return !!node.alternate && alwaysExits(node.consequent) && alwaysExits(node.alternate);
1260
+ case "ExpressionStatement":
1261
+ return false;
1262
+ default:
1263
+ return false;
1264
+ }
1265
+ }
1266
+ function checkLoop(node, context) {
1267
+ const body = node.body;
1268
+ if (!body)
1269
+ return;
1270
+ if (alwaysExits(body)) {
1271
+ context.report({
1272
+ message: "Never loop: this loop always exits on the first iteration. Consider replacing with a conditional. (clippy::never_loop)",
1273
+ node
1274
+ });
1275
+ }
1276
+ }
1277
+ var never_loop_default = {
1278
+ create(context) {
1279
+ return {
1280
+ ForStatement: (node) => checkLoop(node, context),
1281
+ ForOfStatement: (node) => checkLoop(node, context),
1282
+ ForInStatement: (node) => checkLoop(node, context),
1283
+ WhileStatement: (node) => checkLoop(node, context),
1284
+ DoWhileStatement: (node) => checkLoop(node, context)
1285
+ };
1286
+ }
1287
+ };
1288
+
1289
+ // src/rules/explicit-counter-loop.ts
1290
+ function checkBody8(body, context) {
1291
+ for (let i = 0;i < body.length - 1; i++) {
1292
+ const decl = body[i];
1293
+ const loop = body[i + 1];
1294
+ if (decl.type !== "VariableDeclaration" || decl.declarations.length !== 1)
1295
+ continue;
1296
+ const d = decl.declarations[0];
1297
+ if (d.id?.type !== "Identifier" || d.init?.type !== "Literal" || d.init.value !== 0)
1298
+ continue;
1299
+ const counterName = d.id.name;
1300
+ if (loop.type !== "ForOfStatement")
1301
+ continue;
1302
+ if (loop.body?.type !== "BlockStatement" || loop.body.body.length === 0)
1303
+ continue;
1304
+ const stmts = loop.body.body;
1305
+ const last = stmts[stmts.length - 1];
1306
+ const isIncrement2 = last.type === "ExpressionStatement" && last.expression?.type === "UpdateExpression" && last.expression.operator === "++" && last.expression.argument?.type === "Identifier" && last.expression.argument.name === counterName || last.type === "ExpressionStatement" && last.expression?.type === "AssignmentExpression" && last.expression.operator === "+=" && last.expression.left?.type === "Identifier" && last.expression.left.name === counterName && last.expression.right?.type === "Literal" && last.expression.right.value === 1;
1307
+ if (isIncrement2) {
1308
+ context.report({
1309
+ message: `Explicit counter loop: manual counter \`${counterName}\` can be replaced with \`for (const [${counterName}, item] of arr.entries())\`. (clippy::explicit_counter_loop)`,
1310
+ node: loop
1311
+ });
1312
+ }
1313
+ }
1314
+ }
1315
+ var explicit_counter_loop_default = {
1316
+ create(context) {
1317
+ function checkFunction(node) {
1318
+ const body = getFunctionBody(node);
1319
+ if (body)
1320
+ checkBody8(body, context);
1321
+ }
1322
+ return {
1323
+ FunctionDeclaration: checkFunction,
1324
+ FunctionExpression: checkFunction,
1325
+ ArrowFunctionExpression: checkFunction,
1326
+ Program(node) {
1327
+ if (node.body)
1328
+ checkBody8(node.body, context);
1329
+ }
1330
+ };
1331
+ }
1332
+ };
1333
+
1334
+ // src/rules/excessive-nesting.ts
1335
+ var THRESHOLD4 = 5;
1336
+ var NESTING_NODES = new Set([
1337
+ "IfStatement",
1338
+ "ForStatement",
1339
+ "ForInStatement",
1340
+ "ForOfStatement",
1341
+ "WhileStatement",
1342
+ "DoWhileStatement",
1343
+ "SwitchStatement",
1344
+ "TryStatement"
1345
+ ]);
1346
+ var excessive_nesting_default = {
1347
+ create(context) {
1348
+ let reported = false;
1349
+ function walk(node, depth) {
1350
+ if (!node || typeof node !== "object" || !node.type)
1351
+ return;
1352
+ if (reported)
1353
+ return;
1354
+ const isNesting = NESTING_NODES.has(node.type);
1355
+ const newDepth = isNesting ? depth + 1 : depth;
1356
+ if (newDepth > THRESHOLD4 && isNesting) {
1357
+ reported = true;
1358
+ context.report({
1359
+ message: `Excessive nesting: this code is nested ${newDepth} levels deep (max ${THRESHOLD4}). Consider extracting into helper functions. (clippy::excessive_nesting)`,
1360
+ node
1361
+ });
1362
+ return;
1363
+ }
1364
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
1365
+ if (depth > 0)
1366
+ return;
1367
+ }
1368
+ for (const key of Object.keys(node)) {
1369
+ if (key === "type" || key === "loc" || key === "range" || key === "parent" || key === "start" || key === "end")
1370
+ continue;
1371
+ const val = node[key];
1372
+ if (Array.isArray(val)) {
1373
+ for (const child of val) {
1374
+ if (child && typeof child === "object" && child.type)
1375
+ walk(child, newDepth);
1376
+ }
1377
+ } else if (val && typeof val === "object" && val.type) {
1378
+ walk(val, newDepth);
1379
+ }
1380
+ }
1381
+ }
1382
+ return {
1383
+ FunctionDeclaration(node) {
1384
+ reported = false;
1385
+ walk(node.body, 0);
1386
+ },
1387
+ FunctionExpression(node) {
1388
+ reported = false;
1389
+ walk(node.body, 0);
1390
+ },
1391
+ ArrowFunctionExpression(node) {
1392
+ reported = false;
1393
+ walk(node.body, 0);
1394
+ }
1395
+ };
1396
+ }
1397
+ };
1398
+
1399
+ // src/rules/fn-params-excessive-bools.ts
1400
+ var THRESHOLD5 = 3;
1401
+ function countBoolParams(node) {
1402
+ const params = node.params;
1403
+ if (!params)
1404
+ return 0;
1405
+ let count = 0;
1406
+ for (const p of params) {
1407
+ if (p.type === "Identifier" && p.typeAnnotation) {
1408
+ const ann = p.typeAnnotation.typeAnnotation ?? p.typeAnnotation;
1409
+ if (ann.type === "TSBooleanKeyword")
1410
+ count++;
1411
+ }
1412
+ if (p.type === "AssignmentPattern" && p.right?.type === "Literal" && typeof p.right.value === "boolean") {
1413
+ count++;
1414
+ }
1415
+ }
1416
+ return count;
1417
+ }
1418
+ function checkFunction(node, context) {
1419
+ const boolCount = countBoolParams(node);
1420
+ if (boolCount <= THRESHOLD5)
1421
+ return;
1422
+ const name = node.id?.name ?? "<anonymous>";
1423
+ context.report({
1424
+ message: `Excessive bools: \`${name}\` has ${boolCount} boolean parameters (max ${THRESHOLD5}). Consider using an options object. (clippy::fn_params_excessive_bools)`,
1425
+ node
1426
+ });
1427
+ }
1428
+ var fn_params_excessive_bools_default = {
1429
+ create(context) {
1430
+ return {
1431
+ FunctionDeclaration: (node) => checkFunction(node, context),
1432
+ FunctionExpression: (node) => checkFunction(node, context),
1433
+ ArrowFunctionExpression: (node) => checkFunction(node, context)
1434
+ };
1435
+ }
1436
+ };
1437
+
1438
+ // src/rules/float-equality-without-abs.ts
1439
+ function isEpsilonLike(node) {
1440
+ if (node.type === "MemberExpression" && node.object?.type === "Identifier" && node.object.name === "Number" && node.property?.type === "Identifier" && node.property.name === "EPSILON")
1441
+ return true;
1442
+ if (node.type === "Literal" && typeof node.value === "number" && node.value > 0 && node.value < 1)
1443
+ return true;
1444
+ return false;
1445
+ }
1446
+ function isSubtraction(node) {
1447
+ return node.type === "BinaryExpression" && node.operator === "-";
1448
+ }
1449
+ var float_equality_without_abs_default = {
1450
+ create(context) {
1451
+ return {
1452
+ BinaryExpression(node) {
1453
+ if (node.operator !== "<" && node.operator !== "<=")
1454
+ return;
1455
+ if (isSubtraction(node.left) && isEpsilonLike(node.right)) {
1456
+ context.report({
1457
+ message: "Float equality without abs: `(a - b) < epsilon` fails when a < b. Use `Math.abs(a - b) < epsilon`. (clippy::float_equality_without_abs)",
1458
+ node
1459
+ });
1460
+ }
1461
+ }
1462
+ };
1463
+ }
1464
+ };
1465
+
1466
+ // src/rules/manual-is-finite.ts
1467
+ function isInfinityCheck(node, operator) {
1468
+ if (node.type !== "BinaryExpression" || node.operator !== operator)
1469
+ return false;
1470
+ return isInfinityLiteral(node.right) || isInfinityLiteral(node.left);
1471
+ }
1472
+ function isInfinityLiteral(node) {
1473
+ if (node.type === "Identifier" && node.name === "Infinity")
1474
+ return true;
1475
+ if (node.type === "UnaryExpression" && node.operator === "-" && node.argument?.type === "Identifier" && node.argument.name === "Infinity")
1476
+ return true;
1477
+ return false;
1478
+ }
1479
+ var manual_is_finite_default = {
1480
+ create(context) {
1481
+ return {
1482
+ LogicalExpression(node) {
1483
+ if (node.operator === "&&" && isInfinityCheck(node.left, "!==") && isInfinityCheck(node.right, "!==")) {
1484
+ context.report({
1485
+ message: "Manual isFinite: this pattern checks for finiteness manually. Use `Number.isFinite(x)` instead. (clippy::manual_is_finite)",
1486
+ node
1487
+ });
1488
+ }
1489
+ if (node.operator === "||" && isInfinityCheck(node.left, "===") && isInfinityCheck(node.right, "===")) {
1490
+ context.report({
1491
+ message: "Manual isInfinite: this pattern checks for infinity manually. Use `!Number.isFinite(x)` instead. (clippy::manual_is_infinite)",
1492
+ node
1493
+ });
1494
+ }
1495
+ }
1496
+ };
1497
+ }
1498
+ };
1499
+
1500
+ // src/rules/unnecessary-fold.ts
1501
+ var unnecessary_fold_default = {
1502
+ create(context) {
1503
+ return {
1504
+ CallExpression(node) {
1505
+ if (!isMethodCall(node, "reduce"))
1506
+ return;
1507
+ const args = node.arguments;
1508
+ if (!args || args.length !== 2)
1509
+ return;
1510
+ const callback = args[0];
1511
+ const initial = args[1];
1512
+ if (callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression")
1513
+ return;
1514
+ if (callback.params.length !== 2)
1515
+ return;
1516
+ const accParam = callback.params[0];
1517
+ if (accParam.type !== "Identifier")
1518
+ return;
1519
+ const accName = accParam.name;
1520
+ let bodyExpr = null;
1521
+ if (callback.body.type === "BlockStatement") {
1522
+ if (callback.body.body.length === 1 && callback.body.body[0].type === "ReturnStatement") {
1523
+ bodyExpr = callback.body.body[0].argument;
1524
+ }
1525
+ } else {
1526
+ bodyExpr = callback.body;
1527
+ }
1528
+ if (!bodyExpr || bodyExpr.type !== "LogicalExpression")
1529
+ return;
1530
+ if (bodyExpr.operator === "||" && isLiteral(initial, false)) {
1531
+ if (bodyExpr.left.type === "Identifier" && bodyExpr.left.name === accName) {
1532
+ context.report({
1533
+ message: "Unnecessary fold: this `.reduce()` with `||` and initial `false` can be replaced with `.some()`. (clippy::unnecessary_fold)",
1534
+ node
1535
+ });
1536
+ }
1537
+ }
1538
+ if (bodyExpr.operator === "&&" && isLiteral(initial, true)) {
1539
+ if (bodyExpr.left.type === "Identifier" && bodyExpr.left.name === accName) {
1540
+ context.report({
1541
+ message: "Unnecessary fold: this `.reduce()` with `&&` and initial `true` can be replaced with `.every()`. (clippy::unnecessary_fold)",
1542
+ node
1543
+ });
1544
+ }
1545
+ }
1546
+ }
1547
+ };
1548
+ }
1549
+ };
1550
+
1551
+ // src/rules/needless-late-init.ts
1552
+ function checkBody9(body, context) {
1553
+ for (let i = 0;i < body.length - 1; i++) {
1554
+ const decl = body[i];
1555
+ const next = body[i + 1];
1556
+ if (decl.type !== "VariableDeclaration" || decl.declarations.length !== 1)
1557
+ continue;
1558
+ const d = decl.declarations[0];
1559
+ if (d.id?.type !== "Identifier" || d.init !== null)
1560
+ continue;
1561
+ const varName = d.id.name;
1562
+ if (next.type === "ExpressionStatement" && next.expression?.type === "AssignmentExpression" && next.expression.operator === "=" && next.expression.left?.type === "Identifier" && next.expression.left.name === varName) {
1563
+ context.report({
1564
+ message: `Needless late init: \`${varName}\` is declared then immediately assigned. Initialize on declaration with \`const\`. (clippy::needless_late_init)`,
1565
+ node: decl
1566
+ });
1567
+ }
1568
+ }
1569
+ }
1570
+ var needless_late_init_default = {
1571
+ create(context) {
1572
+ return {
1573
+ BlockStatement(node) {
1574
+ if (node.body)
1575
+ checkBody9(node.body, context);
1576
+ },
1577
+ Program(node) {
1578
+ if (node.body)
1579
+ checkBody9(node.body, context);
1580
+ }
1581
+ };
1582
+ }
1583
+ };
1584
+
1585
+ // src/rules/single-element-loop.ts
1586
+ var single_element_loop_default = {
1587
+ create(context) {
1588
+ return {
1589
+ ForOfStatement(node) {
1590
+ const right = node.right;
1591
+ if (right?.type === "ArrayExpression" && right.elements?.length === 1) {
1592
+ context.report({
1593
+ message: "Single element loop: this loop iterates over a single-element array. Use the value directly. (clippy::single_element_loop)",
1594
+ node
1595
+ });
1596
+ }
1597
+ }
1598
+ };
1599
+ }
1600
+ };
1601
+
1602
+ // src/rules/int-plus-one.ts
1603
+ var int_plus_one_default = {
1604
+ create(context) {
1605
+ return {
1606
+ BinaryExpression(node) {
1607
+ const { operator, left, right } = node;
1608
+ if (operator === ">=" && right.type === "BinaryExpression" && right.operator === "+" && isLiteral(right.right, 1)) {
1609
+ context.report({
1610
+ message: "Int plus one: `x >= y + 1` can be simplified to `x > y`. (clippy::int_plus_one)",
1611
+ node
1612
+ });
1613
+ return;
1614
+ }
1615
+ if (operator === "<=" && left.type === "BinaryExpression" && left.operator === "+" && isLiteral(left.right, 1)) {
1616
+ context.report({
1617
+ message: "Int plus one: `x + 1 <= y` can be simplified to `x < y`. (clippy::int_plus_one)",
1618
+ node
1619
+ });
1620
+ return;
1621
+ }
1622
+ if (operator === ">=" && left.type === "BinaryExpression" && left.operator === "-" && isLiteral(left.right, 1)) {
1623
+ context.report({
1624
+ message: "Int plus one: `x - 1 >= y` can be simplified to `x > y`. (clippy::int_plus_one)",
1625
+ node
1626
+ });
1627
+ return;
1628
+ }
1629
+ if (operator === "<=" && right.type === "BinaryExpression" && right.operator === "-" && isLiteral(right.right, 1)) {
1630
+ context.report({
1631
+ message: "Int plus one: `y <= x - 1` can be simplified to `y < x`. (clippy::int_plus_one)",
1632
+ node
1633
+ });
1634
+ }
1635
+ }
1636
+ };
1637
+ }
1638
+ };
1639
+
1640
+ // src/rules/zero-divided-by-zero.ts
1641
+ function isZero(node) {
1642
+ return node.type === "Literal" && (node.value === 0 || node.value === 0);
1643
+ }
1644
+ var zero_divided_by_zero_default = {
1645
+ create(context) {
1646
+ return {
1647
+ BinaryExpression(node) {
1648
+ if (node.operator === "/" && isZero(node.left) && isZero(node.right)) {
1649
+ context.report({
1650
+ message: "Zero divided by zero: `0 / 0` is `NaN`. Use `NaN` directly if intentional. (clippy::zero_divided_by_zero)",
1651
+ node
1652
+ });
1653
+ }
1654
+ }
1655
+ };
1656
+ }
1657
+ };
1658
+
1659
+ // src/rules/redundant-closure.ts
1660
+ var SAFE_SINGLE_ARG = new Set([
1661
+ "String",
1662
+ "Number",
1663
+ "Boolean",
1664
+ "BigInt",
1665
+ "parseFloat",
1666
+ "isNaN",
1667
+ "isFinite",
1668
+ "encodeURIComponent",
1669
+ "decodeURIComponent",
1670
+ "encodeURI",
1671
+ "decodeURI"
1672
+ ]);
1673
+ var redundant_closure_default = {
1674
+ create(context) {
1675
+ return {
1676
+ CallExpression(node) {
1677
+ const callee = node.callee;
1678
+ if (callee.type !== "MemberExpression")
1679
+ return;
1680
+ const args = node.arguments;
1681
+ if (!args || args.length !== 1)
1682
+ return;
1683
+ const callback = args[0];
1684
+ let paramName = null;
1685
+ let bodyExpr = null;
1686
+ if (callback.type === "ArrowFunctionExpression" && callback.params.length === 1) {
1687
+ const param = callback.params[0];
1688
+ if (param.type !== "Identifier")
1689
+ return;
1690
+ paramName = param.name;
1691
+ if (callback.body.type === "BlockStatement" && callback.body.body.length === 1) {
1692
+ const stmt = callback.body.body[0];
1693
+ if (stmt.type === "ReturnStatement")
1694
+ bodyExpr = stmt.argument;
1695
+ } else if (callback.body.type !== "BlockStatement") {
1696
+ bodyExpr = callback.body;
1697
+ }
1698
+ } else if (callback.type === "FunctionExpression" && callback.params.length === 1) {
1699
+ const param = callback.params[0];
1700
+ if (param.type !== "Identifier")
1701
+ return;
1702
+ paramName = param.name;
1703
+ if (callback.body.type === "BlockStatement" && callback.body.body.length === 1) {
1704
+ const stmt = callback.body.body[0];
1705
+ if (stmt.type === "ReturnStatement")
1706
+ bodyExpr = stmt.argument;
1707
+ }
1708
+ }
1709
+ if (!paramName || !bodyExpr)
1710
+ return;
1711
+ if (bodyExpr.type !== "CallExpression")
1712
+ return;
1713
+ if (bodyExpr.arguments.length !== 1)
1714
+ return;
1715
+ const innerArg = bodyExpr.arguments[0];
1716
+ if (!isIdentifier(innerArg, paramName))
1717
+ return;
1718
+ const fnCallee = bodyExpr.callee;
1719
+ if (isIdentifier(fnCallee) && SAFE_SINGLE_ARG.has(fnCallee.name)) {
1720
+ context.report({
1721
+ message: `Redundant closure: \`x => ${fnCallee.name}(x)\` can be simplified to \`${fnCallee.name}\`. (clippy::redundant_closure)`,
1722
+ node: callback
1723
+ });
1724
+ }
1725
+ }
1726
+ };
1727
+ }
1728
+ };
1729
+
1730
+ // src/rules/unnecessary-reduce-collect.ts
1731
+ function isEmptyArray(node) {
1732
+ return node.type === "ArrayExpression" && (!node.elements || node.elements.length === 0);
1733
+ }
1734
+ var unnecessary_reduce_collect_default = {
1735
+ create(context) {
1736
+ return {
1737
+ CallExpression(node) {
1738
+ if (!isMethodCall(node, "reduce"))
1739
+ return;
1740
+ const args = node.arguments;
1741
+ if (!args || args.length !== 2)
1742
+ return;
1743
+ const callback = args[0];
1744
+ const init = args[1];
1745
+ if (!isEmptyArray(init))
1746
+ return;
1747
+ if (callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression")
1748
+ return;
1749
+ if (callback.params.length !== 2)
1750
+ return;
1751
+ const accParam = callback.params[0];
1752
+ if (accParam.type !== "Identifier")
1753
+ return;
1754
+ const accName = accParam.name;
1755
+ let bodyStmts = null;
1756
+ if (callback.body.type === "BlockStatement") {
1757
+ bodyStmts = callback.body.body;
1758
+ }
1759
+ if (!bodyStmts)
1760
+ return;
1761
+ if (bodyStmts.length === 2) {
1762
+ const pushStmt = bodyStmts[0];
1763
+ const returnStmt = bodyStmts[1];
1764
+ if (returnStmt.type !== "ReturnStatement")
1765
+ return;
1766
+ if (returnStmt.argument?.type !== "Identifier" || returnStmt.argument.name !== accName)
1767
+ return;
1768
+ if (pushStmt.type === "ExpressionStatement" && pushStmt.expression?.type === "CallExpression" && pushStmt.expression.callee?.type === "MemberExpression" && pushStmt.expression.callee.object?.type === "Identifier" && pushStmt.expression.callee.object.name === accName && pushStmt.expression.callee.property?.type === "Identifier" && pushStmt.expression.callee.property.name === "push" && pushStmt.expression.arguments?.length === 1) {
1769
+ context.report({
1770
+ message: "Unnecessary reduce: this `.reduce()` builds an array with `.push()`. Use `.map()` instead. (clippy: prefer combinators)",
1771
+ node
1772
+ });
1773
+ return;
1774
+ }
1775
+ }
1776
+ if (bodyStmts.length === 2) {
1777
+ const ifStmt = bodyStmts[0];
1778
+ const returnStmt = bodyStmts[1];
1779
+ if (returnStmt.type !== "ReturnStatement")
1780
+ return;
1781
+ if (returnStmt.argument?.type !== "Identifier" || returnStmt.argument.name !== accName)
1782
+ return;
1783
+ if (ifStmt.type === "IfStatement" && !ifStmt.alternate) {
1784
+ const consequent = ifStmt.consequent;
1785
+ let pushExpr = null;
1786
+ if (consequent.type === "BlockStatement" && consequent.body.length === 1) {
1787
+ pushExpr = consequent.body[0];
1788
+ } else if (consequent.type === "ExpressionStatement") {
1789
+ pushExpr = consequent;
1790
+ }
1791
+ if (pushExpr?.type === "ExpressionStatement" && pushExpr.expression?.type === "CallExpression" && pushExpr.expression.callee?.type === "MemberExpression" && pushExpr.expression.callee.object?.type === "Identifier" && pushExpr.expression.callee.object.name === accName && pushExpr.expression.callee.property?.type === "Identifier" && pushExpr.expression.callee.property.name === "push") {
1792
+ context.report({
1793
+ message: "Unnecessary reduce: this `.reduce()` conditionally pushes items. Use `.filter()` instead. (clippy: prefer combinators)",
1794
+ node
1795
+ });
1796
+ }
1797
+ }
1798
+ }
1799
+ }
1800
+ };
1801
+ }
1802
+ };
1803
+
1804
+ // src/rules/prefer-structured-clone.ts
1805
+ var prefer_structured_clone_default = {
1806
+ create(context) {
1807
+ return {
1808
+ CallExpression(node) {
1809
+ if (!isCallOf(node, "JSON", "parse"))
1810
+ return;
1811
+ const args = node.arguments;
1812
+ if (!args || args.length !== 1)
1813
+ return;
1814
+ const inner = args[0];
1815
+ if (isCallOf(inner, "JSON", "stringify") && inner.arguments?.length === 1) {
1816
+ context.report({
1817
+ message: "Prefer structuredClone: `JSON.parse(JSON.stringify(x))` can be replaced with `structuredClone(x)`, which handles more types and is more correct. (clippy: don't reinvent stdlib)",
1818
+ node
1819
+ });
1820
+ }
1821
+ }
1822
+ };
1823
+ }
1824
+ };
1825
+
1826
+ // src/rules/object-keys-values.ts
1827
+ var object_keys_values_default = {
1828
+ create(context) {
1829
+ return {
1830
+ CallExpression(node) {
1831
+ const callee = node.callee;
1832
+ if (callee.type !== "MemberExpression")
1833
+ return;
1834
+ const methodName = callee.property;
1835
+ if (methodName?.type !== "Identifier")
1836
+ return;
1837
+ if (methodName.name !== "map" && methodName.name !== "forEach")
1838
+ return;
1839
+ const receiver = callee.object;
1840
+ if (!isCallOf(receiver, "Object", "keys"))
1841
+ return;
1842
+ if (!receiver.arguments || receiver.arguments.length !== 1)
1843
+ return;
1844
+ const objArg = receiver.arguments[0];
1845
+ if (objArg.type !== "Identifier")
1846
+ return;
1847
+ const objName = objArg.name;
1848
+ const args = node.arguments;
1849
+ if (!args || args.length !== 1)
1850
+ return;
1851
+ const callback = args[0];
1852
+ if (callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression")
1853
+ return;
1854
+ if (callback.params.length !== 1)
1855
+ return;
1856
+ const param = callback.params[0];
1857
+ if (param.type !== "Identifier")
1858
+ return;
1859
+ const keyName = param.name;
1860
+ const bodyStr = context.sourceCode.text.slice(callback.body.start, callback.body.end);
1861
+ const indexPattern = `${objName}[${keyName}]`;
1862
+ if (bodyStr.includes(indexPattern)) {
1863
+ const stripped = bodyStr.replace(new RegExp(`${objName}\\[${keyName}\\]`, "g"), "");
1864
+ const keyStillUsed = new RegExp(`\\b${keyName}\\b`).test(stripped);
1865
+ if (keyStillUsed) {
1866
+ context.report({
1867
+ message: `Object keys+values: \`Object.keys(${objName}).${methodName.name}(${keyName} => ... ${objName}[${keyName}] ...)\` can use \`Object.entries(${objName})\` for clearer access to both key and value. (clippy: for_kv_map)`,
1868
+ node
1869
+ });
1870
+ } else {
1871
+ context.report({
1872
+ message: `Object keys→values: \`Object.keys(${objName}).${methodName.name}(${keyName} => ... ${objName}[${keyName}] ...)\` can be simplified to \`Object.values(${objName}).${methodName.name}(...)\`. (clippy: for_kv_map)`,
1873
+ node
1874
+ });
1875
+ }
1876
+ }
1877
+ }
1878
+ };
1879
+ }
1880
+ };
1881
+
1882
+ // src/rules/promise-new-resolve.ts
1883
+ var promise_new_resolve_default = {
1884
+ create(context) {
1885
+ return {
1886
+ NewExpression(node) {
1887
+ if (!isIdentifier(node.callee, "Promise"))
1888
+ return;
1889
+ if (!node.arguments || node.arguments.length !== 1)
1890
+ return;
1891
+ const callback = node.arguments[0];
1892
+ if (callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression")
1893
+ return;
1894
+ if (callback.params.length < 1)
1895
+ return;
1896
+ const resolveParam = callback.params[0];
1897
+ const rejectParam = callback.params[1];
1898
+ let bodyExpr = null;
1899
+ if (callback.body.type === "BlockStatement" && callback.body.body.length === 1) {
1900
+ const stmt = callback.body.body[0];
1901
+ if (stmt.type === "ExpressionStatement")
1902
+ bodyExpr = stmt.expression;
1903
+ } else if (callback.body.type !== "BlockStatement") {
1904
+ bodyExpr = callback.body;
1905
+ }
1906
+ if (!bodyExpr || bodyExpr.type !== "CallExpression")
1907
+ return;
1908
+ if (bodyExpr.arguments.length > 1)
1909
+ return;
1910
+ const calledFn = bodyExpr.callee;
1911
+ if (calledFn.type !== "Identifier")
1912
+ return;
1913
+ if (resolveParam.type === "Identifier" && calledFn.name === resolveParam.name) {
1914
+ context.report({
1915
+ message: "Promise constructor wrapping sync value: `new Promise(resolve => resolve(x))` can be simplified to `Promise.resolve(x)`. (clippy: avoid error-prone constructors)",
1916
+ node
1917
+ });
1918
+ return;
1919
+ }
1920
+ if (rejectParam?.type === "Identifier" && calledFn.name === rejectParam.name) {
1921
+ context.report({
1922
+ message: "Promise constructor wrapping sync rejection: `new Promise((_, reject) => reject(x))` can be simplified to `Promise.reject(x)`. (clippy: avoid error-prone constructors)",
1923
+ node
1924
+ });
1925
+ }
1926
+ }
1927
+ };
1928
+ }
1929
+ };
1930
+
1931
+ // src/rules/similar-names.ts
1932
+ var ALLOWED_PAIRS = new Set([
1933
+ "i,j",
1934
+ "j,k",
1935
+ "i,k",
1936
+ "x,y",
1937
+ "y,z",
1938
+ "x,z",
1939
+ "a,b",
1940
+ "b,c",
1941
+ "a,c",
1942
+ "n,m",
1943
+ "r,s"
1944
+ ]);
1945
+ function areSimilar(a, b) {
1946
+ if (Math.abs(a.length - b.length) > 1)
1947
+ return false;
1948
+ if (a.length === b.length) {
1949
+ let diffs2 = 0;
1950
+ let firstDiff = -1;
1951
+ for (let i = 0;i < a.length; i++) {
1952
+ if (a[i] !== b[i]) {
1953
+ if (diffs2 === 0)
1954
+ firstDiff = i;
1955
+ diffs2++;
1956
+ if (diffs2 > 2)
1957
+ return false;
1958
+ }
1959
+ }
1960
+ if (diffs2 <= 1)
1961
+ return diffs2 === 1;
1962
+ if (diffs2 === 2 && a[firstDiff] === b[firstDiff + 1] && a[firstDiff + 1] === b[firstDiff])
1963
+ return true;
1964
+ return false;
1965
+ }
1966
+ const [shorter, longer] = a.length < b.length ? [a, b] : [b, a];
1967
+ let diffs = 0;
1968
+ let si = 0;
1969
+ for (let li = 0;li < longer.length; li++) {
1970
+ if (shorter[si] === longer[li]) {
1971
+ si++;
1972
+ } else {
1973
+ diffs++;
1974
+ if (diffs > 1)
1975
+ return false;
1976
+ }
1977
+ }
1978
+ return true;
1979
+ }
1980
+ function collectNames(node, names) {
1981
+ if (!node || typeof node !== "object" || !node.type)
1982
+ return;
1983
+ if (node.type === "VariableDeclarator" && node.id?.type === "Identifier") {
1984
+ names.set(node.id.name, node.id);
1985
+ }
1986
+ if (node.type === "FunctionDeclaration" && node.id?.type === "Identifier") {
1987
+ names.set(node.id.name, node.id);
1988
+ }
1989
+ if (node.params) {
1990
+ for (const p of node.params) {
1991
+ if (p.type === "Identifier")
1992
+ names.set(p.name, p);
1993
+ if (p.type === "AssignmentPattern" && p.left?.type === "Identifier")
1994
+ names.set(p.left.name, p.left);
1995
+ }
1996
+ }
1997
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression")
1998
+ return;
1999
+ for (const key of Object.keys(node)) {
2000
+ if (key === "type" || key === "loc" || key === "range" || key === "parent" || key === "start" || key === "end")
2001
+ continue;
2002
+ const val = node[key];
2003
+ if (Array.isArray(val)) {
2004
+ for (const child of val) {
2005
+ if (child && typeof child === "object" && child.type)
2006
+ collectNames(child, names);
2007
+ }
2008
+ } else if (val && typeof val === "object" && val.type) {
2009
+ collectNames(val, names);
2010
+ }
2011
+ }
2012
+ }
2013
+ var similar_names_default = {
2014
+ create(context) {
2015
+ function checkScope(node) {
2016
+ const names = new Map;
2017
+ if (node.params) {
2018
+ for (const p of node.params) {
2019
+ if (p.type === "Identifier")
2020
+ names.set(p.name, p);
2021
+ if (p.type === "AssignmentPattern" && p.left?.type === "Identifier")
2022
+ names.set(p.left.name, p.left);
2023
+ }
2024
+ }
2025
+ if (node.body?.type === "BlockStatement") {
2026
+ for (const stmt of node.body.body) {
2027
+ collectNames(stmt, names);
2028
+ }
2029
+ }
2030
+ const nameList = [...names.entries()];
2031
+ const reported = new Set;
2032
+ for (let i = 0;i < nameList.length; i++) {
2033
+ for (let j = i + 1;j < nameList.length; j++) {
2034
+ const [nameA] = nameList[i];
2035
+ const [nameB, nodeB] = nameList[j];
2036
+ if (nameA.length < 3 || nameB.length < 3)
2037
+ continue;
2038
+ const pair = [nameA, nameB].sort().join(",");
2039
+ if (ALLOWED_PAIRS.has(pair))
2040
+ continue;
2041
+ if (reported.has(pair))
2042
+ continue;
2043
+ if (areSimilar(nameA.toLowerCase(), nameB.toLowerCase())) {
2044
+ reported.add(pair);
2045
+ context.report({
2046
+ message: `Similar names: \`${nameA}\` and \`${nameB}\` differ by only one character, which is easy to confuse. (clippy::similar_names)`,
2047
+ node: nodeB
2048
+ });
2049
+ }
2050
+ }
2051
+ }
2052
+ }
2053
+ return {
2054
+ FunctionDeclaration: checkScope,
2055
+ FunctionExpression: checkScope,
2056
+ ArrowFunctionExpression: checkScope
2057
+ };
2058
+ }
2059
+ };
2060
+
2061
+ // src/rules/match-same-arms.ts
2062
+ function caseBodySource(caseNode, sourceText) {
2063
+ const stmts = caseNode.consequent;
2064
+ if (!stmts || stmts.length === 0)
2065
+ return "";
2066
+ const meaningful = stmts.filter((s) => s.type !== "BreakStatement");
2067
+ if (meaningful.length === 0)
2068
+ return "";
2069
+ const first = meaningful[0];
2070
+ const last = meaningful[meaningful.length - 1];
2071
+ if (first.start == null || last.end == null)
2072
+ return "";
2073
+ return sourceText.slice(first.start, last.end);
2074
+ }
2075
+ var match_same_arms_default = {
2076
+ create(context) {
2077
+ return {
2078
+ SwitchStatement(node) {
2079
+ const cases = node.cases;
2080
+ if (!cases || cases.length < 2)
2081
+ return;
2082
+ const seen = new Map;
2083
+ for (const c of cases) {
2084
+ if (c.test === null)
2085
+ continue;
2086
+ const body = caseBodySource(c, context.sourceCode.text);
2087
+ if (!body)
2088
+ continue;
2089
+ const existing = seen.get(body);
2090
+ if (existing) {
2091
+ context.report({
2092
+ message: "Match same arms: multiple switch cases have identical bodies. Consider combining them. (clippy::match_same_arms)",
2093
+ node: c
2094
+ });
2095
+ } else {
2096
+ seen.set(body, c);
2097
+ }
2098
+ }
2099
+ }
2100
+ };
2101
+ }
2102
+ };
2103
+
2104
+ // src/rules/used-underscore-binding.ts
2105
+ var used_underscore_binding_default = {
2106
+ create(context) {
2107
+ const declared = new Map;
2108
+ const used = new Set;
2109
+ return {
2110
+ VariableDeclarator(node) {
2111
+ if (node.id?.type === "Identifier" && node.id.name.startsWith("_") && node.id.name !== "_") {
2112
+ declared.set(node.id.name, node.id);
2113
+ }
2114
+ },
2115
+ Identifier(node) {
2116
+ if (node.name?.startsWith("_") && node.name !== "_") {
2117
+ if (node.parent?.type !== "VariableDeclarator" || node.parent.id !== node) {
2118
+ used.add(node.name);
2119
+ }
2120
+ }
2121
+ },
2122
+ "Program:exit"() {
2123
+ for (const [name, declNode] of declared) {
2124
+ if (used.has(name)) {
2125
+ context.report({
2126
+ message: `Used underscore binding: \`${name}\` starts with \`_\` (conventionally unused) but is actually used. Remove the underscore prefix. (clippy::used_underscore_binding)`,
2127
+ node: declNode
2128
+ });
2129
+ }
2130
+ }
2131
+ }
2132
+ };
2133
+ }
2134
+ };
2135
+
2136
+ // src/rules/needless-continue.ts
2137
+ function checkLoopBody(body, context) {
2138
+ if (body.type !== "BlockStatement" || body.body.length === 0)
2139
+ return;
2140
+ const stmts = body.body;
2141
+ const last = stmts[stmts.length - 1];
2142
+ if (last.type === "ContinueStatement" && !last.label) {
2143
+ context.report({
2144
+ message: "Needless continue: `continue` at the end of a loop body has no effect. (clippy::needless_continue)",
2145
+ node: last
2146
+ });
2147
+ return;
2148
+ }
2149
+ if (last.type === "IfStatement" && last.alternate) {
2150
+ const alt = last.alternate;
2151
+ const altBody = alt.type === "BlockStatement" ? alt.body : [alt];
2152
+ if (altBody.length === 1 && altBody[0].type === "ContinueStatement" && !altBody[0].label) {
2153
+ context.report({
2154
+ message: "Needless continue: `else { continue; }` at the end of a loop is redundant. Remove the else branch. (clippy::needless_continue)",
2155
+ node: altBody[0]
2156
+ });
2157
+ }
2158
+ }
2159
+ }
2160
+ var needless_continue_default = {
2161
+ create(context) {
2162
+ return {
2163
+ ForStatement: (node) => checkLoopBody(node.body, context),
2164
+ ForOfStatement: (node) => checkLoopBody(node.body, context),
2165
+ ForInStatement: (node) => checkLoopBody(node.body, context),
2166
+ WhileStatement: (node) => checkLoopBody(node.body, context),
2167
+ DoWhileStatement: (node) => checkLoopBody(node.body, context)
2168
+ };
2169
+ }
2170
+ };
2171
+
2172
+ // src/rules/enum-variant-names.ts
2173
+ var enum_variant_names_default = {
2174
+ create(context) {
2175
+ return {
2176
+ TSEnumDeclaration(node) {
2177
+ const enumName = node.id?.name;
2178
+ if (!enumName)
2179
+ return;
2180
+ const members = node.members ?? node.body?.members;
2181
+ if (!members || members.length < 2)
2182
+ return;
2183
+ const names = members.map((m) => m.id?.type === "Identifier" ? m.id.name : null).filter((n) => n !== null);
2184
+ if (names.length < 2)
2185
+ return;
2186
+ const lowerEnum = enumName.toLowerCase();
2187
+ const allPrefixed = names.every((n) => n.toLowerCase().startsWith(lowerEnum));
2188
+ if (allPrefixed) {
2189
+ context.report({
2190
+ message: `Enum variant names: all members of \`${enumName}\` are prefixed with \`${enumName}\`. Remove the redundant prefix. (clippy::enum_variant_names)`,
2191
+ node
2192
+ });
2193
+ return;
2194
+ }
2195
+ const allSuffixed = names.every((n) => n.toLowerCase().endsWith(lowerEnum));
2196
+ if (allSuffixed) {
2197
+ context.report({
2198
+ message: `Enum variant names: all members of \`${enumName}\` are suffixed with \`${enumName}\`. Remove the redundant suffix. (clippy::enum_variant_names)`,
2199
+ node
2200
+ });
2201
+ }
2202
+ }
2203
+ };
2204
+ }
2205
+ };
2206
+
2207
+ // src/rules/struct-field-names.ts
2208
+ function getFieldNames(node) {
2209
+ const body = node.body?.body ?? node.body?.members ?? node.members;
2210
+ if (!Array.isArray(body))
2211
+ return [];
2212
+ return body.map((m) => {
2213
+ if (m.type === "TSPropertySignature" || m.type === "PropertyDefinition") {
2214
+ return m.key?.type === "Identifier" ? m.key.name : null;
2215
+ }
2216
+ return null;
2217
+ }).filter((n) => n !== null);
2218
+ }
2219
+ function commonPrefix(names) {
2220
+ if (names.length < 2)
2221
+ return "";
2222
+ let prefix = names[0];
2223
+ for (let i = 1;i < names.length; i++) {
2224
+ const name = names[i];
2225
+ let j = 0;
2226
+ while (j < prefix.length && j < name.length && prefix[j].toLowerCase() === name[j].toLowerCase())
2227
+ j++;
2228
+ prefix = prefix.slice(0, j);
2229
+ }
2230
+ if (!prefix)
2231
+ return "";
2232
+ if (prefix.endsWith("_"))
2233
+ return prefix;
2234
+ for (const name of names) {
2235
+ if (name.length > prefix.length) {
2236
+ const nextChar = name[prefix.length];
2237
+ if (nextChar >= "A" && nextChar <= "Z")
2238
+ return prefix;
2239
+ return "";
2240
+ }
2241
+ }
2242
+ return prefix;
2243
+ }
2244
+ function commonSuffix(names) {
2245
+ if (names.length < 2)
2246
+ return "";
2247
+ let suffix = names[0];
2248
+ for (let i = 1;i < names.length; i++) {
2249
+ const name = names[i];
2250
+ let j = 0;
2251
+ while (j < suffix.length && j < name.length && suffix[suffix.length - 1 - j] === name[name.length - 1 - j])
2252
+ j++;
2253
+ suffix = suffix.slice(suffix.length - j);
2254
+ }
2255
+ if (!suffix)
2256
+ return "";
2257
+ if (suffix[0] >= "A" && suffix[0] <= "Z")
2258
+ return suffix;
2259
+ if (suffix.startsWith("_"))
2260
+ return suffix;
2261
+ return "";
2262
+ }
2263
+ var struct_field_names_default = {
2264
+ create(context) {
2265
+ function check(node, typeName) {
2266
+ if (!typeName)
2267
+ return;
2268
+ const names = getFieldNames(node);
2269
+ if (names.length < 2)
2270
+ return;
2271
+ const prefix = commonPrefix(names);
2272
+ if (prefix.length >= 3) {
2273
+ context.report({
2274
+ message: `Struct field names: all fields of \`${typeName}\` share the prefix \`${prefix}\`. Remove it — the type name provides context. (clippy::struct_field_names)`,
2275
+ node
2276
+ });
2277
+ return;
2278
+ }
2279
+ const suffix = commonSuffix(names);
2280
+ if (suffix.length >= 3) {
2281
+ context.report({
2282
+ message: `Struct field names: all fields of \`${typeName}\` share the suffix \`${suffix}\`. Consider removing it. (clippy::struct_field_names)`,
2283
+ node
2284
+ });
2285
+ }
2286
+ }
2287
+ return {
2288
+ TSInterfaceDeclaration(node) {
2289
+ check(node, node.id?.name);
2290
+ },
2291
+ TSTypeAliasDeclaration(node) {
2292
+ if (node.typeAnnotation?.type === "TSTypeLiteral") {
2293
+ check(node.typeAnnotation, node.id?.name);
2294
+ }
2295
+ }
2296
+ };
2297
+ }
2298
+ };
2299
+
2300
+ // src/rules/unreadable-literal.ts
2301
+ var INT_THRESHOLD = 1e4;
2302
+ var unreadable_literal_default = {
2303
+ create(context) {
2304
+ return {
2305
+ Literal(node) {
2306
+ if (typeof node.value !== "number")
2307
+ return;
2308
+ if (!Number.isFinite(node.value))
2309
+ return;
2310
+ const raw = node.raw;
2311
+ if (!raw)
2312
+ return;
2313
+ if (raw.includes("_"))
2314
+ return;
2315
+ if (raw.startsWith("0x") || raw.startsWith("0o") || raw.startsWith("0b") || raw.startsWith("0X") || raw.startsWith("0O") || raw.startsWith("0B"))
2316
+ return;
2317
+ if (raw.includes("."))
2318
+ return;
2319
+ if (raw.includes("e") || raw.includes("E"))
2320
+ return;
2321
+ const absVal = Math.abs(node.value);
2322
+ if (absVal >= INT_THRESHOLD && Number.isInteger(absVal)) {
2323
+ context.report({
2324
+ message: `Unreadable literal: \`${raw}\` is hard to read. Use numeric separators: \`${formatWithSeparators(raw)}\`. (clippy::unreadable_literal)`,
2325
+ node
2326
+ });
2327
+ }
2328
+ }
2329
+ };
2330
+ }
2331
+ };
2332
+ function formatWithSeparators(raw) {
2333
+ const negative = raw.startsWith("-");
2334
+ const digits = negative ? raw.slice(1) : raw;
2335
+ const parts = [];
2336
+ for (let i = digits.length;i > 0; i -= 3) {
2337
+ parts.unshift(digits.slice(Math.max(0, i - 3), i));
2338
+ }
2339
+ return (negative ? "-" : "") + parts.join("_");
2340
+ }
2341
+
2342
+ // src/rules/bool-to-int-with-if.ts
2343
+ var bool_to_int_with_if_default = {
2344
+ create(context) {
2345
+ return {
2346
+ ConditionalExpression(node) {
2347
+ const { consequent, alternate } = node;
2348
+ if (isLiteral(consequent, 1) && isLiteral(alternate, 0)) {
2349
+ context.report({
2350
+ message: "Bool to int with if: `cond ? 1 : 0` can be simplified to `Number(cond)` or `+cond`. (clippy::bool_to_int_with_if)",
2351
+ node
2352
+ });
2353
+ } else if (isLiteral(consequent, 0) && isLiteral(alternate, 1)) {
2354
+ context.report({
2355
+ message: "Bool to int with if: `cond ? 0 : 1` can be simplified to `Number(!cond)` or `+!cond`. (clippy::bool_to_int_with_if)",
2356
+ node
2357
+ });
2358
+ }
2359
+ }
2360
+ };
2361
+ }
2362
+ };
2363
+
2364
+ // src/plugin.ts
2365
+ var plugin = {
2366
+ meta: {
2367
+ name: "oxclippy",
2368
+ version: "0.1.0"
2369
+ },
2370
+ rules: {
2371
+ "needless-bool": needless_bool_default,
2372
+ "collapsible-if": collapsible_if_default,
2373
+ "neg-multiply": neg_multiply_default,
2374
+ "bool-comparison": bool_comparison_default,
2375
+ "single-case-switch": single_case_switch_default,
2376
+ "let-and-return": let_and_return_default,
2377
+ "int-plus-one": int_plus_one_default,
2378
+ "needless-late-init": needless_late_init_default,
2379
+ "identity-op": identity_op_default,
2380
+ "manual-clamp": manual_clamp_default,
2381
+ "manual-strip": manual_strip_default,
2382
+ "useless-conversion": useless_conversion_default,
2383
+ "manual-swap": manual_swap_default,
2384
+ "manual-is-finite": manual_is_finite_default,
2385
+ "float-comparison": float_comparison_default,
2386
+ "xor-used-as-pow": xor_used_as_pow_default,
2387
+ "almost-swapped": almost_swapped_default,
2388
+ "if-same-then-else": if_same_then_else_default,
2389
+ "never-loop": never_loop_default,
2390
+ "float-equality-without-abs": float_equality_without_abs_default,
2391
+ "zero-divided-by-zero": zero_divided_by_zero_default,
2392
+ "filter-then-first": filter_then_first_default,
2393
+ "map-void-return": map_void_return_default,
2394
+ "map-identity": map_identity_default,
2395
+ "manual-find": manual_find_default,
2396
+ "manual-some": manual_some_default,
2397
+ "manual-every": manual_every_default,
2398
+ "manual-includes": manual_includes_default,
2399
+ "search-is-some": search_is_some_default,
2400
+ "needless-range-loop": needless_range_loop_default,
2401
+ "redundant-closure-call": redundant_closure_call_default,
2402
+ "explicit-counter-loop": explicit_counter_loop_default,
2403
+ "unnecessary-fold": unnecessary_fold_default,
2404
+ "single-element-loop": single_element_loop_default,
2405
+ "too-many-arguments": too_many_arguments_default,
2406
+ "too-many-lines": too_many_lines_default,
2407
+ "cognitive-complexity": cognitive_complexity_default,
2408
+ "excessive-nesting": excessive_nesting_default,
2409
+ "fn-params-excessive-bools": fn_params_excessive_bools_default,
2410
+ "redundant-closure": redundant_closure_default,
2411
+ "unnecessary-reduce-collect": unnecessary_reduce_collect_default,
2412
+ "prefer-structured-clone": prefer_structured_clone_default,
2413
+ "object-keys-values": object_keys_values_default,
2414
+ "promise-new-resolve": promise_new_resolve_default,
2415
+ "similar-names": similar_names_default,
2416
+ "match-same-arms": match_same_arms_default,
2417
+ "used-underscore-binding": used_underscore_binding_default,
2418
+ "needless-continue": needless_continue_default,
2419
+ "enum-variant-names": enum_variant_names_default,
2420
+ "struct-field-names": struct_field_names_default,
2421
+ "unreadable-literal": unreadable_literal_default,
2422
+ "bool-to-int-with-if": bool_to_int_with_if_default
2423
+ }
2424
+ };
2425
+ var plugin_default = plugin;
2426
+ export {
2427
+ plugin_default as default
2428
+ };