@timmo001/oxlint-rules 0.4.0 → 0.5.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.
@@ -8,16 +8,8 @@ import { eslintCompatPlugin } from "@oxlint/plugins";
8
8
 
9
9
  // src/effect/rules/no-try-catch-in-effect-generators.ts
10
10
  import { defineRule } from "@oxlint/plugins";
11
- function resolveVariable(sourceCode, identifier) {
12
- let scope = sourceCode.getScope(identifier);
13
- while (scope) {
14
- const variable = scope.set.get(identifier.name);
15
- if (variable)
16
- return variable;
17
- scope = scope.upper;
18
- }
19
- return null;
20
- }
11
+
12
+ // src/shared/ast.ts
21
13
  function nearestEnclosingFunction(node) {
22
14
  let current = node.parent;
23
15
  while (current) {
@@ -33,6 +25,19 @@ function staticMemberName(node) {
33
25
  return null;
34
26
  return node.property.type === "Identifier" ? node.property.name : null;
35
27
  }
28
+
29
+ // vendor/anti-slop/src/shared/scope.ts
30
+ function resolveVariable(sourceCode, identifier) {
31
+ let scope = sourceCode.getScope(identifier);
32
+ while (scope !== null) {
33
+ const variable = scope.set.get(identifier.name);
34
+ if (variable !== undefined)
35
+ return variable;
36
+ scope = scope.upper;
37
+ }
38
+ return null;
39
+ }
40
+ // src/shared/effect.ts
36
41
  function isEffectImport(sourceCode, identifier, namespace) {
37
42
  const variable = resolveVariable(sourceCode, identifier);
38
43
  return variable?.defs.some((definition) => {
@@ -60,6 +65,11 @@ function isEffectMethod(sourceCode, node, method) {
60
65
  }
61
66
  return isEffectImport(sourceCode, object.object, true);
62
67
  }
68
+ function importsEffect(program) {
69
+ return program.body.some((statement) => statement.type === "ImportDeclaration" && statement.importKind !== "type" && (statement.source.value === "effect" || statement.source.value.startsWith("effect/") || statement.source.value.startsWith("@effect/")));
70
+ }
71
+
72
+ // src/effect/rules/no-try-catch-in-effect-generators.ts
63
73
  function isDirectArgument(owner, call) {
64
74
  return call.arguments.some((argument) => argument === owner);
65
75
  }
@@ -98,11 +108,85 @@ var noTryCatchInEffectGeneratorsRule = defineRule({
98
108
  }
99
109
  });
100
110
 
111
+ // src/effect/rules/prefer-platform-services.ts
112
+ import { defineRule as defineRule2 } from "@oxlint/plugins";
113
+ var moduleServices = new Map([
114
+ ["child_process", "childProcess"],
115
+ ["fs", "fileSystem"],
116
+ ["fs/promises", "fileSystem"]
117
+ ]);
118
+ var bunServices = new Map([
119
+ ["$", "childProcess"],
120
+ ["file", "fileSystem"],
121
+ ["spawn", "childProcess"],
122
+ ["spawnSync", "childProcess"],
123
+ ["write", "fileSystem"]
124
+ ]);
125
+ var preferPlatformServicesRule = defineRule2({
126
+ meta: {
127
+ type: "suggestion",
128
+ docs: {
129
+ description: "Prefer Effect's ChildProcessSpawner and FileSystem services over Node or Bun process and file system APIs in modules that use Effect."
130
+ },
131
+ messages: {
132
+ childProcess: "Use Effect's `ChildProcessSpawner` service (`effect/process`) instead of `{{api}}`, so scope cleanup, interruption, and test layers apply.",
133
+ fileSystem: "Use Effect's `FileSystem` service instead of `{{api}}`, so failures stay typed and tests can provide a layer."
134
+ }
135
+ },
136
+ create(context) {
137
+ let enabled = false;
138
+ return {
139
+ Program(node) {
140
+ enabled = importsEffect(node);
141
+ },
142
+ ImportDeclaration(node) {
143
+ if (!enabled || node.importKind === "type")
144
+ return;
145
+ const source = node.source.value;
146
+ const service = moduleServices.get(source.replace(/^node:/u, ""));
147
+ if (service) {
148
+ context.report({ node, messageId: service, data: { api: source } });
149
+ return;
150
+ }
151
+ if (source !== "bun")
152
+ return;
153
+ for (const specifier of node.specifiers) {
154
+ if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type") {
155
+ continue;
156
+ }
157
+ const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
158
+ const bunService = bunServices.get(imported);
159
+ if (bunService) {
160
+ context.report({
161
+ node: specifier,
162
+ messageId: bunService,
163
+ data: { api: `Bun.${imported}` }
164
+ });
165
+ }
166
+ }
167
+ },
168
+ MemberExpression(node) {
169
+ const name = staticMemberName(node);
170
+ const service = name ? bunServices.get(name) : undefined;
171
+ if (!enabled || !service || node.object.type !== "Identifier" || node.object.name !== "Bun" || (resolveVariable(context.sourceCode, node.object)?.defs.length ?? 0) > 0) {
172
+ return;
173
+ }
174
+ context.report({
175
+ node,
176
+ messageId: service,
177
+ data: { api: `Bun.${name}` }
178
+ });
179
+ }
180
+ };
181
+ }
182
+ });
183
+
101
184
  // src/effect/index.ts
102
185
  var timmoEffectPlugin = eslintCompatPlugin({
103
186
  meta: { name: "timmo-effect" },
104
187
  rules: {
105
- "no-try-catch-in-effect-generators": noTryCatchInEffectGeneratorsRule
188
+ "no-try-catch-in-effect-generators": noTryCatchInEffectGeneratorsRule,
189
+ "prefer-platform-services": preferPlatformServicesRule
106
190
  }
107
191
  });
108
192
  var effect_default = timmoEffectPlugin;
@@ -111,7 +195,7 @@ var effect_default = timmoEffectPlugin;
111
195
  import { eslintCompatPlugin as eslintCompatPlugin2 } from "@oxlint/plugins";
112
196
 
113
197
  // vendor/anti-slop/src/effect/rules/no-manual-effect-error-tag.ts
114
- import { defineRule as defineRule2 } from "@oxlint/plugins";
198
+ import { defineRule as defineRule3 } from "@oxlint/plugins";
115
199
 
116
200
  // vendor/anti-slop/src/effect/shared/tagged-values.ts
117
201
  var equalityOperators = new Set(["==", "===", "!=", "!=="]);
@@ -158,7 +242,7 @@ var isMatchPatternObject = (node) => {
158
242
  };
159
243
 
160
244
  // vendor/anti-slop/src/effect/rules/no-manual-effect-error-tag.ts
161
- var noManualEffectErrorTagRule = defineRule2({
245
+ var noManualEffectErrorTagRule = defineRule3({
162
246
  meta: {
163
247
  type: "problem",
164
248
  docs: {
@@ -195,8 +279,8 @@ var noManualEffectErrorTagRule = defineRule2({
195
279
  });
196
280
 
197
281
  // vendor/anti-slop/src/effect/rules/no-manual-tag-comparison.ts
198
- import { defineRule as defineRule3 } from "@oxlint/plugins";
199
- var noManualTagComparisonRule = defineRule3({
282
+ import { defineRule as defineRule4 } from "@oxlint/plugins";
283
+ var noManualTagComparisonRule = defineRule4({
200
284
  meta: {
201
285
  type: "problem",
202
286
  docs: {
@@ -226,8 +310,8 @@ var noManualTagComparisonRule = defineRule3({
226
310
  });
227
311
 
228
312
  // vendor/anti-slop/src/effect/rules/no-manual-tagged-construction.ts
229
- import { defineRule as defineRule4 } from "@oxlint/plugins";
230
- var noManualTaggedConstructionRule = defineRule4({
313
+ import { defineRule as defineRule5 } from "@oxlint/plugins";
314
+ var noManualTaggedConstructionRule = defineRule5({
231
315
  meta: {
232
316
  type: "problem",
233
317
  docs: {
@@ -252,7 +336,7 @@ var noManualTaggedConstructionRule = defineRule4({
252
336
  });
253
337
 
254
338
  // vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
255
- import { defineRule as defineRule5 } from "@oxlint/plugins";
339
+ import { defineRule as defineRule6 } from "@oxlint/plugins";
256
340
  var SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
257
341
  var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
258
342
  function isProjectLocalImport(source) {
@@ -263,7 +347,7 @@ function getImportedName(specifier) {
263
347
  return specifier.imported.name;
264
348
  return specifier.imported.value;
265
349
  }
266
- var noServiceConstructorImportsRule = defineRule5({
350
+ var noServiceConstructorImportsRule = defineRule6({
267
351
  meta: {
268
352
  type: "problem",
269
353
  docs: {
@@ -297,9 +381,9 @@ var noServiceConstructorImportsRule = defineRule5({
297
381
  });
298
382
 
299
383
  // vendor/anti-slop/src/effect/rules/prefer-effect-match.ts
300
- import { defineRule as defineRule6 } from "@oxlint/plugins";
384
+ import { defineRule as defineRule7 } from "@oxlint/plugins";
301
385
  var equalityOperators2 = new Set(["==", "===", "!=", "!=="]);
302
- var preferEffectMatchRule = defineRule6({
386
+ var preferEffectMatchRule = defineRule7({
303
387
  meta: {
304
388
  type: "problem",
305
389
  docs: {
@@ -365,7 +449,7 @@ function enablePluginRules(namespace, plugin) {
365
449
  import { eslintCompatPlugin as eslintCompatPlugin3 } from "@oxlint/plugins";
366
450
 
367
451
  // vendor/anti-slop/src/rules/no-array-filter-map.ts
368
- import { defineRule as defineRule7 } from "@oxlint/plugins";
452
+ import { defineRule as defineRule8 } from "@oxlint/plugins";
369
453
 
370
454
  // vendor/anti-slop/src/shared/array-method.ts
371
455
  function unwrapArrayExpression(node) {
@@ -440,7 +524,7 @@ function isKnownArrayExpression(sourceCode, node, visited = new Set) {
440
524
  }
441
525
 
442
526
  // vendor/anti-slop/src/rules/no-array-filter-map.ts
443
- var noArrayFilterMapRule = defineRule7({
527
+ var noArrayFilterMapRule = defineRule8({
444
528
  meta: {
445
529
  type: "suggestion",
446
530
  docs: { description: "Disallow adjacent array filter/map passes in favor of lazy iterator helpers or a single transformation." },
@@ -469,7 +553,7 @@ var noArrayFilterMapRule = defineRule7({
469
553
  });
470
554
 
471
555
  // vendor/anti-slop/src/rules/no-reduce-accumulator-copy.ts
472
- import { defineRule as defineRule8 } from "@oxlint/plugins";
556
+ import { defineRule as defineRule9 } from "@oxlint/plugins";
473
557
  function enclosingReducer(node) {
474
558
  let parent = node.parent;
475
559
  while (parent !== null) {
@@ -519,7 +603,7 @@ function isGlobalCopyOwner(sourceCode, node, name) {
519
603
  const variable = resolveArrayBinding(sourceCode, node);
520
604
  return variable === null || variable.defs.length === 0;
521
605
  }
522
- var noReduceAccumulatorCopyRule = defineRule8({
606
+ var noReduceAccumulatorCopyRule = defineRule9({
523
607
  meta: {
524
608
  type: "problem",
525
609
  docs: { description: "Disallow copying growing reducer accumulators with Object.assign, Array.from, or array copy methods." },
@@ -560,7 +644,7 @@ var noReduceAccumulatorCopyRule = defineRule8({
560
644
  });
561
645
 
562
646
  // vendor/anti-slop/src/rules/no-chained-type-assertions.ts
563
- import { defineRule as defineRule9 } from "@oxlint/plugins";
647
+ import { defineRule as defineRule10 } from "@oxlint/plugins";
564
648
  function isTypeAssertionExpression(node) {
565
649
  return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
566
650
  }
@@ -595,7 +679,7 @@ function isForbiddenAssertionChain(node) {
595
679
  }
596
680
  return assertionCount > 1 && hasNonConstAssertion;
597
681
  }
598
- var noChainedTypeAssertionsRule = defineRule9({
682
+ var noChainedTypeAssertionsRule = defineRule10({
599
683
  meta: {
600
684
  type: "problem",
601
685
  docs: {
@@ -619,7 +703,7 @@ var noChainedTypeAssertionsRule = defineRule9({
619
703
  });
620
704
 
621
705
  // vendor/anti-slop/src/rules/no-conditional-empty-object-spread.ts
622
- import { defineRule as defineRule10 } from "@oxlint/plugins";
706
+ import { defineRule as defineRule11 } from "@oxlint/plugins";
623
707
  function unwrapParentheses(node) {
624
708
  let current = node;
625
709
  while (current.type === "ParenthesizedExpression") {
@@ -634,7 +718,7 @@ function isConditionalEmptyObjectSpread(node) {
634
718
  const conditional = unwrapParentheses(node);
635
719
  return conditional.type === "ConditionalExpression" && (isEmptyObjectExpression(conditional.consequent) || isEmptyObjectExpression(conditional.alternate));
636
720
  }
637
- var noConditionalEmptyObjectSpreadRule = defineRule10({
721
+ var noConditionalEmptyObjectSpreadRule = defineRule11({
638
722
  meta: {
639
723
  type: "suggestion",
640
724
  docs: {
@@ -658,7 +742,7 @@ var noConditionalEmptyObjectSpreadRule = defineRule10({
658
742
  });
659
743
 
660
744
  // vendor/anti-slop/src/rules/no-known-value-widening.ts
661
- import { defineRule as defineRule11 } from "@oxlint/plugins";
745
+ import { defineRule as defineRule12 } from "@oxlint/plugins";
662
746
 
663
747
  // vendor/anti-slop/src/shared/lexical-type-parameters.ts
664
748
  function isNode(value) {
@@ -1187,18 +1271,6 @@ function functionParameterBindingName(parameter, sourceCode) {
1187
1271
  return annotationStart === undefined ? sourceText : sourceText.slice(0, annotationStart - parameter.start).trimEnd();
1188
1272
  }
1189
1273
 
1190
- // vendor/anti-slop/src/shared/scope.ts
1191
- function resolveVariable2(sourceCode, identifier) {
1192
- let scope = sourceCode.getScope(identifier);
1193
- while (scope !== null) {
1194
- const variable = scope.set.get(identifier.name);
1195
- if (variable !== undefined)
1196
- return variable;
1197
- scope = scope.upper;
1198
- }
1199
- return null;
1200
- }
1201
-
1202
1274
  // vendor/anti-slop/src/rules/no-known-value-widening.ts
1203
1275
  function unwrapExpression(expression) {
1204
1276
  let current = expression;
@@ -1222,7 +1294,7 @@ function hasKnownEvidence(sourceCode, expression, visitedVariables = new Set) {
1222
1294
  const unwrapped = unwrapExpression(expression);
1223
1295
  if (unwrapped.type !== "Identifier")
1224
1296
  return false;
1225
- const variable = resolveVariable2(sourceCode, unwrapped);
1297
+ const variable = resolveVariable(sourceCode, unwrapped);
1226
1298
  if (variable === null || visitedVariables.has(variable))
1227
1299
  return false;
1228
1300
  const declarator = variableDeclarator(variable);
@@ -1241,7 +1313,7 @@ function localFunctionForCall(sourceCode, callee) {
1241
1313
  return unwrapped;
1242
1314
  if (unwrapped.type !== "Identifier")
1243
1315
  return null;
1244
- const variable = resolveVariable2(sourceCode, unwrapped);
1316
+ const variable = resolveVariable(sourceCode, unwrapped);
1245
1317
  if (variable === null || variable.defs.length !== 1)
1246
1318
  return null;
1247
1319
  const [definition] = variable.defs;
@@ -1294,7 +1366,7 @@ function hasKnownCallArgumentEvidence(sourceCode, expression, environment, visit
1294
1366
  }
1295
1367
  if (expression.type !== "Identifier")
1296
1368
  return isKnownEvidenceExpression(expression);
1297
- const variable = resolveVariable2(sourceCode, expression);
1369
+ const variable = resolveVariable(sourceCode, expression);
1298
1370
  if (variable === null || visitedVariables.has(variable))
1299
1371
  return false;
1300
1372
  const annotation = variableTypeAnnotation(sourceCode, variable);
@@ -1359,7 +1431,7 @@ function isDictionaryAccumulatorTarget(destination) {
1359
1431
  function hasParentAssertion(node) {
1360
1432
  return node.parent?.type === "TSAsExpression" || node.parent?.type === "TSTypeAssertion";
1361
1433
  }
1362
- var noKnownValueWideningRule = defineRule11({
1434
+ var noKnownValueWideningRule = defineRule12({
1363
1435
  meta: {
1364
1436
  type: "problem",
1365
1437
  docs: {
@@ -1408,7 +1480,7 @@ var noKnownValueWideningRule = defineRule11({
1408
1480
  AssignmentExpression(node) {
1409
1481
  if (node.operator !== "=" || node.left.type !== "Identifier")
1410
1482
  return;
1411
- const variable = resolveVariable2(context.sourceCode, node.left);
1483
+ const variable = resolveVariable(context.sourceCode, node.left);
1412
1484
  if (variable === null)
1413
1485
  return;
1414
1486
  const declarator = variableDeclarator(variable);
@@ -1472,7 +1544,7 @@ var noKnownValueWideningRule = defineRule11({
1472
1544
  });
1473
1545
 
1474
1546
  // vendor/anti-slop/src/rules/no-module-mocking.ts
1475
- import { defineRule as defineRule12 } from "@oxlint/plugins";
1547
+ import { defineRule as defineRule13 } from "@oxlint/plugins";
1476
1548
  var moduleMockMethods = new Set(["doMock", "mock", "unstable_mockModule"]);
1477
1549
  function importedName(node) {
1478
1550
  if (node.type !== "ImportSpecifier")
@@ -1485,7 +1557,7 @@ function isTestFrameworkObject(sourceCode, expression) {
1485
1557
  if ((expression.name === "vi" || expression.name === "jest") && sourceCode.isGlobalReference(expression)) {
1486
1558
  return true;
1487
1559
  }
1488
- const variable = resolveVariable2(sourceCode, expression);
1560
+ const variable = resolveVariable(sourceCode, expression);
1489
1561
  if (variable === null || variable.defs.length === 0) {
1490
1562
  return expression.name === "vi" || expression.name === "jest";
1491
1563
  }
@@ -1507,7 +1579,7 @@ function moduleMockCall(sourceCode, callee) {
1507
1579
  const method = callee.computed ? property.type === "Literal" && (property.value === "doMock" || property.value === "mock" || property.value === "unstable_mockModule") ? property.value : null : property.type === "Identifier" ? property.name : null;
1508
1580
  return method !== null && moduleMockMethods.has(method);
1509
1581
  }
1510
- var noModuleMockingRule = defineRule12({
1582
+ var noModuleMockingRule = defineRule13({
1511
1583
  meta: {
1512
1584
  type: "problem",
1513
1585
  docs: {
@@ -1531,8 +1603,8 @@ var noModuleMockingRule = defineRule12({
1531
1603
  });
1532
1604
 
1533
1605
  // vendor/anti-slop/src/rules/no-object-parameters.ts
1534
- import { defineRule as defineRule13 } from "@oxlint/plugins";
1535
- var noObjectParametersRule = defineRule13({
1606
+ import { defineRule as defineRule14 } from "@oxlint/plugins";
1607
+ var noObjectParametersRule = defineRule14({
1536
1608
  meta: {
1537
1609
  type: "problem",
1538
1610
  docs: {
@@ -1585,7 +1657,7 @@ var noObjectParametersRule = defineRule13({
1585
1657
  });
1586
1658
 
1587
1659
  // vendor/anti-slop/src/rules/no-reflect-apply.ts
1588
- import { defineRule as defineRule14 } from "@oxlint/plugins";
1660
+ import { defineRule as defineRule15 } from "@oxlint/plugins";
1589
1661
 
1590
1662
  // vendor/anti-slop/src/shared/reflect-method.ts
1591
1663
  function isGlobalReflect(sourceCode, expression) {
@@ -1593,7 +1665,7 @@ function isGlobalReflect(sourceCode, expression) {
1593
1665
  return false;
1594
1666
  if (sourceCode.isGlobalReference(expression))
1595
1667
  return true;
1596
- const variable = resolveVariable2(sourceCode, expression);
1668
+ const variable = resolveVariable(sourceCode, expression);
1597
1669
  return variable === null || variable.defs.length === 0;
1598
1670
  }
1599
1671
  function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
@@ -1606,7 +1678,7 @@ function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
1606
1678
  }
1607
1679
 
1608
1680
  // vendor/anti-slop/src/rules/no-reflect-apply.ts
1609
- var noReflectApplyRule = defineRule14({
1681
+ var noReflectApplyRule = defineRule15({
1610
1682
  meta: {
1611
1683
  type: "problem",
1612
1684
  docs: {
@@ -1630,8 +1702,8 @@ var noReflectApplyRule = defineRule14({
1630
1702
  });
1631
1703
 
1632
1704
  // vendor/anti-slop/src/rules/no-reflect-get.ts
1633
- import { defineRule as defineRule15 } from "@oxlint/plugins";
1634
- var noReflectGetRule = defineRule15({
1705
+ import { defineRule as defineRule16 } from "@oxlint/plugins";
1706
+ var noReflectGetRule = defineRule16({
1635
1707
  meta: {
1636
1708
  type: "problem",
1637
1709
  docs: {
@@ -1655,7 +1727,7 @@ var noReflectGetRule = defineRule15({
1655
1727
  });
1656
1728
 
1657
1729
  // vendor/anti-slop/src/rules/no-runtime-typeof.ts
1658
- import { defineRule as defineRule16 } from "@oxlint/plugins";
1730
+ import { defineRule as defineRule17 } from "@oxlint/plugins";
1659
1731
  function isRuntimeFunction(node) {
1660
1732
  return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
1661
1733
  }
@@ -1678,7 +1750,7 @@ function isExistenceProbe(node) {
1678
1750
  const other = parent.left === node ? parent.right : parent.left;
1679
1751
  return other.type === "Literal" && other.value === "undefined";
1680
1752
  }
1681
- var noRuntimeTypeofRule = defineRule16({
1753
+ var noRuntimeTypeofRule = defineRule17({
1682
1754
  meta: {
1683
1755
  type: "problem",
1684
1756
  docs: {
@@ -1712,7 +1784,7 @@ var noRuntimeTypeofRule = defineRule16({
1712
1784
  });
1713
1785
 
1714
1786
  // vendor/anti-slop/src/rules/no-shape-in-symbol-names.ts
1715
- import { defineRule as defineRule17 } from "@oxlint/plugins";
1787
+ import { defineRule as defineRule18 } from "@oxlint/plugins";
1716
1788
  var FORBIDDEN_SYMBOL_NAME = "shape";
1717
1789
  function containsForbiddenSymbolName(name) {
1718
1790
  return name.toLowerCase().includes(FORBIDDEN_SYMBOL_NAME);
@@ -1723,7 +1795,7 @@ function isBorrowedMemberName(node) {
1723
1795
  return false;
1724
1796
  return parent.property === node && parent.computed === false;
1725
1797
  }
1726
- var noForbiddenTermInSymbolNamesRule = defineRule17({
1798
+ var noForbiddenTermInSymbolNamesRule = defineRule18({
1727
1799
  meta: {
1728
1800
  type: "problem",
1729
1801
  docs: {
@@ -1752,12 +1824,12 @@ var noForbiddenTermInSymbolNamesRule = defineRule17({
1752
1824
  });
1753
1825
 
1754
1826
  // vendor/anti-slop/src/rules/no-unknown-parameters.ts
1755
- import { defineRule as defineRule18 } from "@oxlint/plugins";
1827
+ import { defineRule as defineRule19 } from "@oxlint/plugins";
1756
1828
  function isTypePredicateSubject(owner, parameterName) {
1757
1829
  const predicate = owner.returnType?.typeAnnotation;
1758
1830
  return predicate?.type === "TSTypePredicate" && predicate.parameterName.type === "Identifier" && predicate.parameterName.name === parameterName;
1759
1831
  }
1760
- var noUnknownParametersRule = defineRule18({
1832
+ var noUnknownParametersRule = defineRule19({
1761
1833
  meta: {
1762
1834
  type: "problem",
1763
1835
  docs: {
@@ -1801,8 +1873,8 @@ var noUnknownParametersRule = defineRule18({
1801
1873
  });
1802
1874
 
1803
1875
  // vendor/anti-slop/src/rules/no-unknown-returns.ts
1804
- import { defineRule as defineRule19 } from "@oxlint/plugins";
1805
- var noUnknownReturnsRule = defineRule19({
1876
+ import { defineRule as defineRule20 } from "@oxlint/plugins";
1877
+ var noUnknownReturnsRule = defineRule20({
1806
1878
  meta: {
1807
1879
  type: "problem",
1808
1880
  docs: {
@@ -1855,8 +1927,8 @@ var noUnknownReturnsRule = defineRule19({
1855
1927
  });
1856
1928
 
1857
1929
  // vendor/anti-slop/src/rules/no-unknown-type-aliases.ts
1858
- import { defineRule as defineRule20 } from "@oxlint/plugins";
1859
- var noUnknownTypeAliasesRule = defineRule20({
1930
+ import { defineRule as defineRule21 } from "@oxlint/plugins";
1931
+ var noUnknownTypeAliasesRule = defineRule21({
1860
1932
  meta: {
1861
1933
  type: "problem",
1862
1934
  docs: {
@@ -1894,7 +1966,7 @@ var noUnknownTypeAliasesRule = defineRule20({
1894
1966
  });
1895
1967
 
1896
1968
  // vendor/anti-slop/src/rules/no-unsafe-dictionary-type.ts
1897
- import { defineRule as defineRule21 } from "@oxlint/plugins";
1969
+ import { defineRule as defineRule22 } from "@oxlint/plugins";
1898
1970
  var typeNodeKinds = new Set([
1899
1971
  "JSDocNonNullableType",
1900
1972
  "JSDocNullableType",
@@ -1981,7 +2053,7 @@ function shouldReportType(node, environment) {
1981
2053
  }
1982
2054
  return true;
1983
2055
  }
1984
- var noUnsafeDictionaryTypeRule = defineRule21({
2056
+ var noUnsafeDictionaryTypeRule = defineRule22({
1985
2057
  meta: {
1986
2058
  type: "problem",
1987
2059
  docs: {
@@ -2023,7 +2095,7 @@ var noUnsafeDictionaryTypeRule = defineRule21({
2023
2095
  });
2024
2096
 
2025
2097
  // vendor/anti-slop/src/rules/no-widen-then-assert.ts
2026
- import { defineRule as defineRule22 } from "@oxlint/plugins";
2098
+ import { defineRule as defineRule23 } from "@oxlint/plugins";
2027
2099
  var functionBoundaryTypes = new Set([
2028
2100
  "ArrowFunctionExpression",
2029
2101
  "FunctionDeclaration",
@@ -2219,7 +2291,7 @@ function assertionIsNarrower(sourceText, broadKind, evidence, assertedType) {
2219
2291
  return isDefinitelyObjectType(assertedType);
2220
2292
  return isDefinitelyNarrowerRecordType(assertedType);
2221
2293
  }
2222
- var noWidenThenAssertRule = defineRule22({
2294
+ var noWidenThenAssertRule = defineRule23({
2223
2295
  meta: {
2224
2296
  type: "problem",
2225
2297
  docs: {
@@ -2781,7 +2853,7 @@ var requireReadableSpacingRule = {
2781
2853
  };
2782
2854
 
2783
2855
  // vendor/anti-slop/src/rules/require-safety-comment-for-type-assertion.ts
2784
- import { defineRule as defineRule23 } from "@oxlint/plugins";
2856
+ import { defineRule as defineRule24 } from "@oxlint/plugins";
2785
2857
  var DEFAULT_SAFETY_MARKERS = ["SAFETY"];
2786
2858
  var commentOwnerKinds = new Set([
2787
2859
  "ExpressionStatement",
@@ -2824,7 +2896,7 @@ function hasSafetyComment(sourceCode, node, pattern) {
2824
2896
  current = current.parent;
2825
2897
  }
2826
2898
  }
2827
- var requireSafetyCommentForTypeAssertionRule = defineRule23({
2899
+ var requireSafetyCommentForTypeAssertionRule = defineRule24({
2828
2900
  meta: {
2829
2901
  type: "problem",
2830
2902
  docs: {
@@ -2901,42 +2973,277 @@ var src_default = antiSlopPlugin;
2901
2973
  // src/generic/index.ts
2902
2974
  import { eslintCompatPlugin as eslintCompatPlugin4 } from "@oxlint/plugins";
2903
2975
 
2904
- // src/generic/rules/prefer-event-parameter-type.ts
2905
- import { defineRule as defineRule24 } from "@oxlint/plugins";
2906
- function nearestEnclosingFunction2(node) {
2907
- let current = node.parent;
2908
- while (current) {
2909
- if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
2910
- return current;
2976
+ // src/generic/rules/no-explicit-inferable-type-predicate.ts
2977
+ import { defineRule as defineRule25 } from "@oxlint/plugins";
2978
+ function isNamed(node, name) {
2979
+ return node.type === "Identifier" && node.name === name;
2980
+ }
2981
+ function isNullish(node) {
2982
+ return node.type === "Literal" && node.value === null || isNamed(node, "undefined");
2983
+ }
2984
+ function isNullishExclusion(node, name) {
2985
+ return node.type === "BinaryExpression" && (node.operator === "!==" || node.operator === "!=") && (isNamed(node.left, name) && isNullish(node.right) || isNullish(node.left) && isNamed(node.right, name));
2986
+ }
2987
+ function isTypeofCheck(node, name) {
2988
+ if (node.type !== "BinaryExpression" || node.operator !== "===" && node.operator !== "==") {
2989
+ return false;
2990
+ }
2991
+ const isTypeof = (side) => side.type === "UnaryExpression" && side.operator === "typeof" && isNamed(side.argument, name);
2992
+ const isLiteral = (side) => side.type === "Literal";
2993
+ return isTypeof(node.left) && isLiteral(node.right) || isLiteral(node.left) && isTypeof(node.right);
2994
+ }
2995
+ function isInferableCheck(node, name) {
2996
+ if (node.type === "LogicalExpression") {
2997
+ return node.operator === "&&" && isNullishExclusion(node.left, name) && isNullishExclusion(node.right, name);
2998
+ }
2999
+ return isNullishExclusion(node, name) || isTypeofCheck(node, name) || node.type === "BinaryExpression" && node.operator === "instanceof" && isNamed(node.left, name);
3000
+ }
3001
+ function returnedExpression(owner) {
3002
+ const body = owner.body;
3003
+ if (!body)
3004
+ return null;
3005
+ if (body.type !== "BlockStatement")
3006
+ return body;
3007
+ const [statement] = body.body;
3008
+ return body.body.length === 1 && statement?.type === "ReturnStatement" ? statement.argument : null;
3009
+ }
3010
+ function isPublicSignature(owner) {
3011
+ const parent = owner.parent;
3012
+ if (parent.type === "MethodDefinition" || parent.type === "ExportNamedDeclaration" || parent.type === "ExportDefaultDeclaration") {
3013
+ return true;
3014
+ }
3015
+ return parent.type === "VariableDeclarator" && parent.parent.parent?.type === "ExportNamedDeclaration";
3016
+ }
3017
+ var noExplicitInferableTypePredicateRule = defineRule25({
3018
+ meta: {
3019
+ type: "suggestion",
3020
+ docs: {
3021
+ description: "Disallow explicit type predicates on simple null, typeof, or instanceof checks that TypeScript 5.5+ infers."
3022
+ },
3023
+ messages: {
3024
+ removeTypePredicate: "TypeScript infers the type predicate for `{{parameter}}` from this check, while an explicit predicate is never checked against the body. Remove the annotation and rely on the inferred predicate."
2911
3025
  }
2912
- current = current.parent;
3026
+ },
3027
+ create(context) {
3028
+ const check = (owner) => {
3029
+ const annotation = owner.returnType?.typeAnnotation;
3030
+ if (annotation?.type !== "TSTypePredicate" || annotation.asserts || annotation.parameterName.type !== "Identifier" || isPublicSignature(owner)) {
3031
+ return;
3032
+ }
3033
+ const parameter = annotation.parameterName.name;
3034
+ const expression = returnedExpression(owner);
3035
+ if (!owner.params.some((param) => isNamed(param, parameter)) || !expression || !isInferableCheck(expression, parameter)) {
3036
+ return;
3037
+ }
3038
+ context.report({
3039
+ node: annotation,
3040
+ messageId: "removeTypePredicate",
3041
+ data: { parameter }
3042
+ });
3043
+ };
3044
+ return {
3045
+ ArrowFunctionExpression: check,
3046
+ FunctionDeclaration: check,
3047
+ FunctionExpression: check
3048
+ };
3049
+ }
3050
+ });
3051
+
3052
+ // src/generic/rules/no-unchecked-query-selector-type.ts
3053
+ import { defineRule as defineRule26 } from "@oxlint/plugins";
3054
+
3055
+ // src/shared/json.ts
3056
+ function jsonObject(value) {
3057
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
3058
+ }
3059
+
3060
+ // src/shared/lit.ts
3061
+ var renderRoots = new Set(["renderRoot", "shadowRoot"]);
3062
+ function litElementBaseClasses(settings) {
3063
+ const configured = jsonObject(settings.lit)?.elementBaseClasses;
3064
+ return new Set([
3065
+ "LitElement",
3066
+ ...Array.isArray(configured) ? configured.map(String) : []
3067
+ ]);
3068
+ }
3069
+ function isWrapperExpression(node) {
3070
+ return node?.type === "ChainExpression" || node?.type === "ParenthesizedExpression" || node?.type === "TSAsExpression" || node?.type === "TSNonNullExpression" || node?.type === "TSSatisfiesExpression" || node?.type === "TSTypeAssertion";
3071
+ }
3072
+ function unwrapExpression2(node) {
3073
+ let current = node;
3074
+ while (isWrapperExpression(current))
3075
+ current = current.expression;
3076
+ return current;
3077
+ }
3078
+ function extendsLitBase(node, bases) {
3079
+ if (node.type === "Identifier")
3080
+ return bases.has(node.name);
3081
+ return node.type === "CallExpression" && node.arguments.some((argument) => extendsLitBase(argument, bases));
3082
+ }
3083
+ function isLitClass(node, bases) {
3084
+ const hasCustomElementDecorator = node.decorators.some((decorator) => decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier" && decorator.expression.callee.name === "customElement");
3085
+ return hasCustomElementDecorator || node.superClass !== null && extendsLitBase(node.superClass, bases);
3086
+ }
3087
+ function classOfBody(body) {
3088
+ const owner = body.parent;
3089
+ return owner?.type === "ClassDeclaration" || owner?.type === "ClassExpression" ? owner : null;
3090
+ }
3091
+ function thisClass(node) {
3092
+ let current = node;
3093
+ while (current.parent) {
3094
+ const parent = current.parent;
3095
+ if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression") {
3096
+ const owner = parent.parent;
3097
+ return (owner?.type === "MethodDefinition" || owner?.type === "TSAbstractMethodDefinition") && !owner.static && owner.value === parent ? classOfBody(owner.parent) : null;
3098
+ }
3099
+ if ((parent.type === "PropertyDefinition" || parent.type === "AccessorProperty") && parent.value === current) {
3100
+ return parent.static ? null : classOfBody(parent.parent);
3101
+ }
3102
+ if (parent.type === "StaticBlock" || parent.type === "ClassBody" || parent.type === "Program") {
3103
+ return null;
3104
+ }
3105
+ current = parent;
2913
3106
  }
2914
3107
  return null;
2915
3108
  }
2916
- function assertedEventParameter(node) {
2917
- const expression = node.expression;
2918
- if (expression.type !== "MemberExpression" || expression.computed || expression.object.type !== "Identifier" || expression.property.type !== "Identifier" || expression.property.name !== "currentTarget" && expression.property.name !== "target") {
3109
+ function isStaticSelector(node) {
3110
+ return node?.type === "Literal" || node?.type === "TemplateLiteral" && node.expressions.length === 0;
3111
+ }
3112
+ function renderRootQuery(node, bases) {
3113
+ const method = staticMemberName(node.callee);
3114
+ if (method !== "querySelector" && method !== "querySelectorAll" || node.callee.type !== "MemberExpression" || !isStaticSelector(node.arguments[0])) {
2919
3115
  return null;
2920
3116
  }
2921
- const parameterName = expression.object.name;
2922
- const property = expression.property.name;
2923
- const owner = nearestEnclosingFunction2(node);
2924
- if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === parameterName)) {
3117
+ const receiver = unwrapExpression2(node.callee.object);
3118
+ const root = staticMemberName(receiver);
3119
+ if (!root || !renderRoots.has(root) || receiver.type !== "MemberExpression" || receiver.object.type !== "ThisExpression") {
3120
+ return null;
3121
+ }
3122
+ const owner = thisClass(receiver.object);
3123
+ return owner && isLitClass(owner, bases) ? { method, root } : null;
3124
+ }
3125
+ function isAssignedMethod(name) {
3126
+ return name === "assignedElements" || name === "assignedNodes";
3127
+ }
3128
+ function feedsAssignedCall(node) {
3129
+ let current = node;
3130
+ while (isWrapperExpression(current.parent))
3131
+ current = current.parent;
3132
+ const member = current.parent;
3133
+ return member?.type === "MemberExpression" && member.object === current && isAssignedMethod(staticMemberName(member));
3134
+ }
3135
+ function litQueryDecoratorCall(node, bases) {
3136
+ const method = staticMemberName(node.callee);
3137
+ if (!isAssignedMethod(method)) {
3138
+ return feedsAssignedCall(node) ? null : renderRootQuery(node, bases);
3139
+ }
3140
+ if (node.callee.type !== "MemberExpression")
3141
+ return null;
3142
+ const inner = unwrapExpression2(node.callee.object);
3143
+ if (inner.type !== "CallExpression")
3144
+ return null;
3145
+ const query = renderRootQuery(inner, bases);
3146
+ return query?.method === "querySelector" ? { method, root: query.root } : null;
3147
+ }
3148
+ function isLitRenderRootQuery(node, bases) {
3149
+ return renderRootQuery(node, bases) !== null;
3150
+ }
3151
+
3152
+ // src/generic/rules/no-unchecked-query-selector-type.ts
3153
+ var queryMethods = new Set(["closest", "querySelector", "querySelectorAll"]);
3154
+ var noUncheckedQuerySelectorTypeRule = defineRule26({
3155
+ meta: {
3156
+ type: "problem",
3157
+ docs: {
3158
+ description: "Disallow type arguments on `querySelector`, `querySelectorAll`, and `closest`, which cast the result without checking it."
3159
+ },
3160
+ messages: {
3161
+ checkQueryResult: "`{{method}}<T>` casts the result without checking it. Check it with `instanceof`, use a checked lookup helper, or query a tag name so TypeScript infers the element type."
3162
+ }
3163
+ },
3164
+ create(context) {
3165
+ const bases = litElementBaseClasses(context.settings);
3166
+ return {
3167
+ CallExpression(node) {
3168
+ const method = staticMemberName(node.callee);
3169
+ if (!node.typeArguments || !method || !queryMethods.has(method) || isLitRenderRootQuery(node, bases)) {
3170
+ return;
3171
+ }
3172
+ context.report({
3173
+ node: node.typeArguments,
3174
+ messageId: "checkQueryResult",
3175
+ data: { method }
3176
+ });
3177
+ }
3178
+ };
3179
+ }
3180
+ });
3181
+
3182
+ // src/generic/rules/no-untyped-custom-event.ts
3183
+ import { defineRule as defineRule27 } from "@oxlint/plugins";
3184
+ var noUntypedCustomEventRule = defineRule27({
3185
+ meta: {
3186
+ type: "problem",
3187
+ docs: {
3188
+ description: "Disallow `CustomEvent` type references without a detail type, which leave `detail` as `any`."
3189
+ },
3190
+ messages: {
3191
+ typeCustomEventDetail: "`CustomEvent` without a type argument leaves `detail` as `any`. Reuse the project's typed event helper if one fits, or add one, instead of a bare `CustomEvent`."
3192
+ }
3193
+ },
3194
+ create(context) {
3195
+ return {
3196
+ TSTypeReference(node) {
3197
+ if (node.typeName.type !== "Identifier" || node.typeName.name !== "CustomEvent" || node.typeArguments) {
3198
+ return;
3199
+ }
3200
+ context.report({ node, messageId: "typeCustomEventDetail" });
3201
+ }
3202
+ };
3203
+ }
3204
+ });
3205
+
3206
+ // src/generic/rules/prefer-event-parameter-type.ts
3207
+ import { defineRule as defineRule28 } from "@oxlint/plugins";
3208
+ function staticMemberPath(node) {
3209
+ const path = [];
3210
+ let current = node.type === "ChainExpression" ? node.expression : node;
3211
+ while (current.type === "MemberExpression") {
3212
+ if (current.computed || current.property.type !== "Identifier") {
3213
+ return null;
3214
+ }
3215
+ path.unshift(current.property.name);
3216
+ current = current.object;
3217
+ }
3218
+ return current.type === "Identifier" && path.length > 0 ? { root: current.name, path } : null;
3219
+ }
3220
+ function isEventMemberPath(path) {
3221
+ const [first] = path;
3222
+ if (first === "detail")
3223
+ return true;
3224
+ return path.length === 1 && (first === "currentTarget" || first === "target");
3225
+ }
3226
+ function assertedEventParameter(node) {
3227
+ const member = staticMemberPath(node.expression);
3228
+ if (!member || !isEventMemberPath(member.path))
3229
+ return null;
3230
+ const owner = nearestEnclosingFunction(node);
3231
+ if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === member.root)) {
2925
3232
  return null;
2926
3233
  }
2927
3234
  return {
2928
- parameter: parameterName,
2929
- property
3235
+ parameter: member.root,
3236
+ property: member.path.join(".")
2930
3237
  };
2931
3238
  }
2932
- var preferEventParameterTypeRule = defineRule24({
3239
+ var preferEventParameterTypeRule = defineRule28({
2933
3240
  meta: {
2934
3241
  type: "suggestion",
2935
3242
  docs: {
2936
- description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
3243
+ description: "Prefer typing event target and detail properties in the handler parameter instead of asserting them at use sites."
2937
3244
  },
2938
3245
  messages: {
2939
- typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site."
3246
+ typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site. Reuse the project's typed event helper if one fits, or add one."
2940
3247
  }
2941
3248
  },
2942
3249
  create(context) {
@@ -2957,9 +3264,69 @@ var preferEventParameterTypeRule = defineRule24({
2957
3264
  }
2958
3265
  });
2959
3266
 
3267
+ // src/generic/rules/prefer-lit-query-decorators.ts
3268
+ import { defineRule as defineRule29 } from "@oxlint/plugins";
3269
+ var messageIds = {
3270
+ assignedElements: "preferQueryAssignedElements",
3271
+ assignedNodes: "preferQueryAssignedNodes",
3272
+ querySelector: "preferQuery",
3273
+ querySelectorAll: "preferQueryAll"
3274
+ };
3275
+ var preferLitQueryDecoratorsRule = defineRule29({
3276
+ meta: {
3277
+ type: "suggestion",
3278
+ docs: {
3279
+ description: "Require Lit query decorators instead of static render-root queries in Lit elements."
3280
+ },
3281
+ schema: [
3282
+ {
3283
+ type: "object",
3284
+ properties: {
3285
+ querySelector: { type: "boolean" },
3286
+ querySelectorAll: { type: "boolean" },
3287
+ assignedElements: { type: "boolean" },
3288
+ assignedNodes: { type: "boolean" }
3289
+ },
3290
+ additionalProperties: false
3291
+ }
3292
+ ],
3293
+ defaultOptions: [
3294
+ {
3295
+ querySelector: true,
3296
+ querySelectorAll: true,
3297
+ assignedElements: true,
3298
+ assignedNodes: true
3299
+ }
3300
+ ],
3301
+ messages: {
3302
+ preferQuery: "Use the @query decorator instead of this.{{root}}.querySelector().",
3303
+ preferQueryAll: "Use the @queryAll decorator instead of this.{{root}}.querySelectorAll().",
3304
+ preferQueryAssignedElements: "Use the @queryAssignedElements decorator instead of this.{{root}}.querySelector().assignedElements().",
3305
+ preferQueryAssignedNodes: "Use the @queryAssignedNodes decorator instead of this.{{root}}.querySelector().assignedNodes()."
3306
+ }
3307
+ },
3308
+ create(context) {
3309
+ const bases = litElementBaseClasses(context.settings);
3310
+ const options = jsonObject(context.options[0]);
3311
+ const disabled = new Set(options ? Object.keys(options).filter((method) => options[method] === false) : []);
3312
+ return {
3313
+ CallExpression(node) {
3314
+ const query = litQueryDecoratorCall(node, bases);
3315
+ if (!query || disabled.has(query.method))
3316
+ return;
3317
+ context.report({
3318
+ node,
3319
+ messageId: messageIds[query.method],
3320
+ data: { root: query.root }
3321
+ });
3322
+ }
3323
+ };
3324
+ }
3325
+ });
3326
+
2960
3327
  // src/generic/rules/prefer-types-over-assertions.ts
2961
- import { defineRule as defineRule25 } from "@oxlint/plugins";
2962
- var preferTypesOverAssertionsRule = defineRule25({
3328
+ import { defineRule as defineRule30 } from "@oxlint/plugins";
3329
+ var preferTypesOverAssertionsRule = defineRule30({
2963
3330
  meta: {
2964
3331
  type: "problem",
2965
3332
  docs: {
@@ -2971,7 +3338,7 @@ var preferTypesOverAssertionsRule = defineRule25({
2971
3338
  },
2972
3339
  create(context) {
2973
3340
  const checkAssertion = (node) => {
2974
- if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const") {
3341
+ if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const" || assertedEventParameter(node)) {
2975
3342
  return;
2976
3343
  }
2977
3344
  context.report({ node, messageId: "avoidAssertion" });
@@ -2987,7 +3354,11 @@ var preferTypesOverAssertionsRule = defineRule25({
2987
3354
  var timmoPlugin = eslintCompatPlugin4({
2988
3355
  meta: { name: "timmo" },
2989
3356
  rules: {
3357
+ "no-explicit-inferable-type-predicate": noExplicitInferableTypePredicateRule,
3358
+ "no-unchecked-query-selector-type": noUncheckedQuerySelectorTypeRule,
3359
+ "no-untyped-custom-event": noUntypedCustomEventRule,
2990
3360
  "prefer-event-parameter-type": preferEventParameterTypeRule,
3361
+ "prefer-lit-query-decorators": preferLitQueryDecoratorsRule,
2991
3362
  "prefer-types-over-assertions": preferTypesOverAssertionsRule
2992
3363
  }
2993
3364
  });
@@ -3005,7 +3376,11 @@ var recommended = defineConfig({
3005
3376
  rules: {
3006
3377
  ...enablePluginRules("anti-slop", src_default),
3007
3378
  ...enablePluginRules("timmo", generic_default),
3008
- "anti-slop/require-safety-comment-for-type-assertion": "off"
3379
+ "anti-slop/require-safety-comment-for-type-assertion": "off",
3380
+ "typescript/no-non-null-assertion": "error",
3381
+ "typescript/no-unnecessary-type-arguments": "error",
3382
+ "typescript/no-unnecessary-type-assertion": "error",
3383
+ "typescript/no-unnecessary-type-parameters": "error"
3009
3384
  }
3010
3385
  });
3011
3386
  var recommended_default = recommended;