@timmo001/oxlint-rules 0.1.4 → 0.2.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.
package/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  Shared Oxlint plugins and configs built around the unchanged
4
4
  [`dmmulroy/anti-slop`](https://github.com/dmmulroy/anti-slop) rules, with
5
- separately owned Effect rules under the `timmo-effect` namespace.
5
+ locally owned generic rules under `timmo` and Effect rules under
6
+ `timmo-effect`.
6
7
 
7
8
  Oxlint's JavaScript plugin API is alpha. Consumers must keep `oxlint` and
8
9
  `@oxlint/plugins` on the exact peer versions declared by this package.
@@ -49,10 +50,10 @@ Copy a reviewed snapshot when a repository should own the rule source:
49
50
  npx --yes @timmo001/oxlint-rules copy tools/oxlint/timmo-rules
50
51
  ```
51
52
 
52
- The command prints the three local plugin entry points and every discovered rule
53
- setting to merge into the target Oxlint config. Effect settings remain opt-in.
54
- It excludes tests and repository metadata, and refuses to replace an existing
55
- destination unless `--force` is passed.
53
+ The command prints every copied plugin entry point and the rule settings from
54
+ the package configs to merge into the target Oxlint config. Effect settings
55
+ remain opt-in. It excludes tests and repository metadata, and refuses to
56
+ replace an existing destination unless `--force` is passed.
56
57
 
57
58
  ## Rules
58
59
 
@@ -74,6 +75,12 @@ destination unless `--force` is passed.
74
75
  - `no-widen-then-assert`
75
76
  - `require-safety-comment-for-type-assertion`
76
77
 
78
+ ### `timmo`
79
+
80
+ - `prefer-event-parameter-type`: reports assertions on a handler parameter's
81
+ `target` or `currentTarget`. Express the target type in the function signature
82
+ instead so every use sees the same contract.
83
+
77
84
  ### `anti-slop-effect`
78
85
 
79
86
  - `no-service-constructor-imports`
package/dist/cli.js CHANGED
@@ -104,11 +104,67 @@ var timmoEffectPlugin = eslintCompatPlugin({
104
104
  });
105
105
  var effect_default = timmoEffectPlugin;
106
106
 
107
- // vendor/anti-slop/src/index.ts
107
+ // vendor/anti-slop/src/effect/index.ts
108
108
  import { eslintCompatPlugin as eslintCompatPlugin2 } from "@oxlint/plugins";
109
109
 
110
- // vendor/anti-slop/src/rules/no-chained-type-assertions.ts
110
+ // vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
111
111
  import { defineRule as defineRule2 } from "@oxlint/plugins";
112
+ var SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
113
+ var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
114
+ function isProjectLocalImport(source) {
115
+ return source.startsWith("./") || source.startsWith("../");
116
+ }
117
+ function getImportedName(specifier) {
118
+ if (specifier.imported.type === "Identifier")
119
+ return specifier.imported.name;
120
+ return specifier.imported.value;
121
+ }
122
+ var noServiceConstructorImportsRule = defineRule2({
123
+ meta: {
124
+ type: "problem",
125
+ docs: {
126
+ description: "Disallow project-local make<CapabilityName> imports outside test and spec files."
127
+ },
128
+ messages: {
129
+ serviceConstructorImport: 'Do not import Effect service constructor "{{name}}" into runtime code. Import the owning Layer, yield the contextual service, and allow its requirements to propagate to the composition root.'
130
+ }
131
+ },
132
+ create(context) {
133
+ const isTestFile = TEST_FILE.test(context.filename.replaceAll("\\", "/"));
134
+ return {
135
+ ImportDeclaration(node) {
136
+ if (isTestFile || !isProjectLocalImport(node.source.value))
137
+ return;
138
+ for (const specifier of node.specifiers) {
139
+ if (specifier.type !== "ImportSpecifier")
140
+ continue;
141
+ const importedName = getImportedName(specifier);
142
+ if (!SERVICE_CONSTRUCTOR_NAME.test(importedName))
143
+ continue;
144
+ context.report({
145
+ node: specifier,
146
+ messageId: "serviceConstructorImport",
147
+ data: { name: importedName }
148
+ });
149
+ }
150
+ }
151
+ };
152
+ }
153
+ });
154
+
155
+ // vendor/anti-slop/src/effect/index.ts
156
+ var antiSlopEffectPlugin = eslintCompatPlugin2({
157
+ meta: { name: "anti-slop-effect" },
158
+ rules: {
159
+ "no-service-constructor-imports": noServiceConstructorImportsRule
160
+ }
161
+ });
162
+ var effect_default2 = antiSlopEffectPlugin;
163
+ // vendor/anti-slop/src/index.ts
164
+ import { eslintCompatPlugin as eslintCompatPlugin3 } from "@oxlint/plugins";
165
+
166
+ // vendor/anti-slop/src/rules/no-chained-type-assertions.ts
167
+ import { defineRule as defineRule3 } from "@oxlint/plugins";
112
168
  function isTypeAssertionExpression(node) {
113
169
  return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
114
170
  }
@@ -143,7 +199,7 @@ function isForbiddenAssertionChain(node) {
143
199
  }
144
200
  return assertionCount > 1 && hasNonConstAssertion;
145
201
  }
146
- var noChainedTypeAssertionsRule = defineRule2({
202
+ var noChainedTypeAssertionsRule = defineRule3({
147
203
  meta: {
148
204
  type: "problem",
149
205
  docs: {
@@ -167,7 +223,7 @@ var noChainedTypeAssertionsRule = defineRule2({
167
223
  });
168
224
 
169
225
  // vendor/anti-slop/src/rules/no-conditional-empty-object-spread.ts
170
- import { defineRule as defineRule3 } from "@oxlint/plugins";
226
+ import { defineRule as defineRule4 } from "@oxlint/plugins";
171
227
  function unwrapParentheses(node) {
172
228
  let current = node;
173
229
  while (current.type === "ParenthesizedExpression") {
@@ -182,7 +238,7 @@ function isConditionalEmptyObjectSpread(node) {
182
238
  const conditional = unwrapParentheses(node);
183
239
  return conditional.type === "ConditionalExpression" && (isEmptyObjectExpression(conditional.consequent) || isEmptyObjectExpression(conditional.alternate));
184
240
  }
185
- var noConditionalEmptyObjectSpreadRule = defineRule3({
241
+ var noConditionalEmptyObjectSpreadRule = defineRule4({
186
242
  meta: {
187
243
  type: "suggestion",
188
244
  docs: {
@@ -206,7 +262,7 @@ var noConditionalEmptyObjectSpreadRule = defineRule3({
206
262
  });
207
263
 
208
264
  // vendor/anti-slop/src/rules/no-known-value-widening.ts
209
- import { defineRule as defineRule4 } from "@oxlint/plugins";
265
+ import { defineRule as defineRule5 } from "@oxlint/plugins";
210
266
 
211
267
  // vendor/anti-slop/src/shared/lexical-type-parameters.ts
212
268
  function isNode(value) {
@@ -905,7 +961,7 @@ function isDictionaryAccumulatorTarget(destination) {
905
961
  function hasParentAssertion(node) {
906
962
  return node.parent?.type === "TSAsExpression" || node.parent?.type === "TSTypeAssertion";
907
963
  }
908
- var noKnownValueWideningRule = defineRule4({
964
+ var noKnownValueWideningRule = defineRule5({
909
965
  meta: {
910
966
  type: "problem",
911
967
  docs: {
@@ -1018,7 +1074,7 @@ var noKnownValueWideningRule = defineRule4({
1018
1074
  });
1019
1075
 
1020
1076
  // vendor/anti-slop/src/rules/no-module-mocking.ts
1021
- import { defineRule as defineRule5 } from "@oxlint/plugins";
1077
+ import { defineRule as defineRule6 } from "@oxlint/plugins";
1022
1078
  var moduleMockMethods = new Set(["doMock", "mock", "unstable_mockModule"]);
1023
1079
  function resolveVariable3(sourceCode, identifier) {
1024
1080
  let scope = sourceCode.getScope(identifier);
@@ -1063,7 +1119,7 @@ function moduleMockCall(sourceCode, callee) {
1063
1119
  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;
1064
1120
  return method !== null && moduleMockMethods.has(method);
1065
1121
  }
1066
- var noModuleMockingRule = defineRule5({
1122
+ var noModuleMockingRule = defineRule6({
1067
1123
  meta: {
1068
1124
  type: "problem",
1069
1125
  docs: {
@@ -1087,8 +1143,8 @@ var noModuleMockingRule = defineRule5({
1087
1143
  });
1088
1144
 
1089
1145
  // vendor/anti-slop/src/rules/no-object-parameters.ts
1090
- import { defineRule as defineRule6 } from "@oxlint/plugins";
1091
- var noObjectParametersRule = defineRule6({
1146
+ import { defineRule as defineRule7 } from "@oxlint/plugins";
1147
+ var noObjectParametersRule = defineRule7({
1092
1148
  meta: {
1093
1149
  type: "problem",
1094
1150
  docs: {
@@ -1141,7 +1197,7 @@ var noObjectParametersRule = defineRule6({
1141
1197
  });
1142
1198
 
1143
1199
  // vendor/anti-slop/src/rules/no-reflect-apply.ts
1144
- import { defineRule as defineRule7 } from "@oxlint/plugins";
1200
+ import { defineRule as defineRule8 } from "@oxlint/plugins";
1145
1201
 
1146
1202
  // vendor/anti-slop/src/shared/reflect-method.ts
1147
1203
  function resolveVariable4(sourceCode, identifier) {
@@ -1172,7 +1228,7 @@ function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
1172
1228
  }
1173
1229
 
1174
1230
  // vendor/anti-slop/src/rules/no-reflect-apply.ts
1175
- var noReflectApplyRule = defineRule7({
1231
+ var noReflectApplyRule = defineRule8({
1176
1232
  meta: {
1177
1233
  type: "problem",
1178
1234
  docs: {
@@ -1196,8 +1252,8 @@ var noReflectApplyRule = defineRule7({
1196
1252
  });
1197
1253
 
1198
1254
  // vendor/anti-slop/src/rules/no-reflect-get.ts
1199
- import { defineRule as defineRule8 } from "@oxlint/plugins";
1200
- var noReflectGetRule = defineRule8({
1255
+ import { defineRule as defineRule9 } from "@oxlint/plugins";
1256
+ var noReflectGetRule = defineRule9({
1201
1257
  meta: {
1202
1258
  type: "problem",
1203
1259
  docs: {
@@ -1221,7 +1277,7 @@ var noReflectGetRule = defineRule8({
1221
1277
  });
1222
1278
 
1223
1279
  // vendor/anti-slop/src/rules/no-runtime-typeof.ts
1224
- import { defineRule as defineRule9 } from "@oxlint/plugins";
1280
+ import { defineRule as defineRule10 } from "@oxlint/plugins";
1225
1281
  function isRuntimeFunction(node) {
1226
1282
  return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
1227
1283
  }
@@ -1244,7 +1300,7 @@ function isExistenceProbe(node) {
1244
1300
  const other = parent.left === node ? parent.right : parent.left;
1245
1301
  return other.type === "Literal" && other.value === "undefined";
1246
1302
  }
1247
- var noRuntimeTypeofRule = defineRule9({
1303
+ var noRuntimeTypeofRule = defineRule10({
1248
1304
  meta: {
1249
1305
  type: "problem",
1250
1306
  docs: {
@@ -1278,7 +1334,7 @@ var noRuntimeTypeofRule = defineRule9({
1278
1334
  });
1279
1335
 
1280
1336
  // vendor/anti-slop/src/rules/no-shape-in-symbol-names.ts
1281
- import { defineRule as defineRule10 } from "@oxlint/plugins";
1337
+ import { defineRule as defineRule11 } from "@oxlint/plugins";
1282
1338
  var FORBIDDEN_SYMBOL_NAME = "shape";
1283
1339
  function containsForbiddenSymbolName(name) {
1284
1340
  return name.toLowerCase().includes(FORBIDDEN_SYMBOL_NAME);
@@ -1289,7 +1345,7 @@ function isBorrowedMemberName(node) {
1289
1345
  return false;
1290
1346
  return parent.property === node && parent.computed === false;
1291
1347
  }
1292
- var noForbiddenTermInSymbolNamesRule = defineRule10({
1348
+ var noForbiddenTermInSymbolNamesRule = defineRule11({
1293
1349
  meta: {
1294
1350
  type: "problem",
1295
1351
  docs: {
@@ -1318,12 +1374,12 @@ var noForbiddenTermInSymbolNamesRule = defineRule10({
1318
1374
  });
1319
1375
 
1320
1376
  // vendor/anti-slop/src/rules/no-unknown-parameters.ts
1321
- import { defineRule as defineRule11 } from "@oxlint/plugins";
1377
+ import { defineRule as defineRule12 } from "@oxlint/plugins";
1322
1378
  function isTypePredicateSubject(owner, parameterName) {
1323
1379
  const predicate = owner.returnType?.typeAnnotation;
1324
1380
  return predicate?.type === "TSTypePredicate" && predicate.parameterName.type === "Identifier" && predicate.parameterName.name === parameterName;
1325
1381
  }
1326
- var noUnknownParametersRule = defineRule11({
1382
+ var noUnknownParametersRule = defineRule12({
1327
1383
  meta: {
1328
1384
  type: "problem",
1329
1385
  docs: {
@@ -1367,8 +1423,8 @@ var noUnknownParametersRule = defineRule11({
1367
1423
  });
1368
1424
 
1369
1425
  // vendor/anti-slop/src/rules/no-unknown-returns.ts
1370
- import { defineRule as defineRule12 } from "@oxlint/plugins";
1371
- var noUnknownReturnsRule = defineRule12({
1426
+ import { defineRule as defineRule13 } from "@oxlint/plugins";
1427
+ var noUnknownReturnsRule = defineRule13({
1372
1428
  meta: {
1373
1429
  type: "problem",
1374
1430
  docs: {
@@ -1421,8 +1477,8 @@ var noUnknownReturnsRule = defineRule12({
1421
1477
  });
1422
1478
 
1423
1479
  // vendor/anti-slop/src/rules/no-unknown-type-aliases.ts
1424
- import { defineRule as defineRule13 } from "@oxlint/plugins";
1425
- var noUnknownTypeAliasesRule = defineRule13({
1480
+ import { defineRule as defineRule14 } from "@oxlint/plugins";
1481
+ var noUnknownTypeAliasesRule = defineRule14({
1426
1482
  meta: {
1427
1483
  type: "problem",
1428
1484
  docs: {
@@ -1460,7 +1516,7 @@ var noUnknownTypeAliasesRule = defineRule13({
1460
1516
  });
1461
1517
 
1462
1518
  // vendor/anti-slop/src/rules/no-unsafe-dictionary-type.ts
1463
- import { defineRule as defineRule14 } from "@oxlint/plugins";
1519
+ import { defineRule as defineRule15 } from "@oxlint/plugins";
1464
1520
  var typeNodeKinds = new Set([
1465
1521
  "JSDocNonNullableType",
1466
1522
  "JSDocNullableType",
@@ -1547,7 +1603,7 @@ function shouldReportType(node, environment) {
1547
1603
  }
1548
1604
  return true;
1549
1605
  }
1550
- var noUnsafeDictionaryTypeRule = defineRule14({
1606
+ var noUnsafeDictionaryTypeRule = defineRule15({
1551
1607
  meta: {
1552
1608
  type: "problem",
1553
1609
  docs: {
@@ -1589,7 +1645,7 @@ var noUnsafeDictionaryTypeRule = defineRule14({
1589
1645
  });
1590
1646
 
1591
1647
  // vendor/anti-slop/src/rules/no-widen-then-assert.ts
1592
- import { defineRule as defineRule15 } from "@oxlint/plugins";
1648
+ import { defineRule as defineRule16 } from "@oxlint/plugins";
1593
1649
  var functionBoundaryTypes = new Set([
1594
1650
  "ArrowFunctionExpression",
1595
1651
  "FunctionDeclaration",
@@ -1785,7 +1841,7 @@ function assertionIsNarrower(sourceText, broadKind, evidence, assertedType) {
1785
1841
  return isDefinitelyObjectType(assertedType);
1786
1842
  return isDefinitelyNarrowerRecordType(assertedType);
1787
1843
  }
1788
- var noWidenThenAssertRule = defineRule15({
1844
+ var noWidenThenAssertRule = defineRule16({
1789
1845
  meta: {
1790
1846
  type: "problem",
1791
1847
  docs: {
@@ -1825,7 +1881,7 @@ var noWidenThenAssertRule = defineRule15({
1825
1881
  });
1826
1882
 
1827
1883
  // vendor/anti-slop/src/rules/require-safety-comment-for-type-assertion.ts
1828
- import { defineRule as defineRule16 } from "@oxlint/plugins";
1884
+ import { defineRule as defineRule17 } from "@oxlint/plugins";
1829
1885
  var DEFAULT_SAFETY_MARKERS = ["SAFETY"];
1830
1886
  var commentOwnerKinds = new Set([
1831
1887
  "ExpressionStatement",
@@ -1868,7 +1924,7 @@ function hasSafetyComment(sourceCode, node, pattern) {
1868
1924
  current = current.parent;
1869
1925
  }
1870
1926
  }
1871
- var requireSafetyCommentForTypeAssertionRule = defineRule16({
1927
+ var requireSafetyCommentForTypeAssertionRule = defineRule17({
1872
1928
  meta: {
1873
1929
  type: "problem",
1874
1930
  docs: {
@@ -1918,7 +1974,7 @@ var requireSafetyCommentForTypeAssertionRule = defineRule16({
1918
1974
  });
1919
1975
 
1920
1976
  // vendor/anti-slop/src/index.ts
1921
- var antiSlopPlugin = eslintCompatPlugin2({
1977
+ var antiSlopPlugin = eslintCompatPlugin3({
1922
1978
  meta: { name: "anti-slop" },
1923
1979
  rules: {
1924
1980
  "no-chained-type-assertions": noChainedTypeAssertionsRule,
@@ -1939,70 +1995,120 @@ var antiSlopPlugin = eslintCompatPlugin2({
1939
1995
  }
1940
1996
  });
1941
1997
  var src_default = antiSlopPlugin;
1942
- // vendor/anti-slop/src/effect/index.ts
1943
- import { eslintCompatPlugin as eslintCompatPlugin3 } from "@oxlint/plugins";
1998
+ // src/generic/index.ts
1999
+ import { eslintCompatPlugin as eslintCompatPlugin4 } from "@oxlint/plugins";
1944
2000
 
1945
- // vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
1946
- import { defineRule as defineRule17 } from "@oxlint/plugins";
1947
- var SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
1948
- var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
1949
- function isProjectLocalImport(source) {
1950
- return source.startsWith("./") || source.startsWith("../");
2001
+ // src/generic/rules/prefer-event-parameter-type.ts
2002
+ import { defineRule as defineRule18 } from "@oxlint/plugins";
2003
+ function nearestEnclosingFunction2(node) {
2004
+ let current = node.parent;
2005
+ while (current) {
2006
+ if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
2007
+ return current;
2008
+ }
2009
+ current = current.parent;
2010
+ }
2011
+ return null;
1951
2012
  }
1952
- function getImportedName(specifier) {
1953
- if (specifier.imported.type === "Identifier")
1954
- return specifier.imported.name;
1955
- return specifier.imported.value;
2013
+ function assertedEventParameter(node) {
2014
+ const expression = node.expression;
2015
+ if (expression.type !== "MemberExpression" || expression.computed || expression.object.type !== "Identifier" || expression.property.type !== "Identifier" || expression.property.name !== "currentTarget" && expression.property.name !== "target") {
2016
+ return null;
2017
+ }
2018
+ const parameterName = expression.object.name;
2019
+ const property = expression.property.name;
2020
+ const owner = nearestEnclosingFunction2(node);
2021
+ if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === parameterName)) {
2022
+ return null;
2023
+ }
2024
+ return {
2025
+ parameter: parameterName,
2026
+ property
2027
+ };
1956
2028
  }
1957
- var noServiceConstructorImportsRule = defineRule17({
2029
+ var preferEventParameterTypeRule = defineRule18({
1958
2030
  meta: {
1959
- type: "problem",
2031
+ type: "suggestion",
1960
2032
  docs: {
1961
- description: "Disallow project-local make<CapabilityName> imports outside test and spec files."
2033
+ description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
1962
2034
  },
1963
2035
  messages: {
1964
- serviceConstructorImport: 'Do not import Effect service constructor "{{name}}" into runtime code. Import the owning Layer, yield the contextual service, and allow its requirements to propagate to the composition root.'
2036
+ typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site."
1965
2037
  }
1966
2038
  },
1967
2039
  create(context) {
1968
- const isTestFile = TEST_FILE.test(context.filename.replaceAll("\\", "/"));
2040
+ const checkAssertion = (node) => {
2041
+ const eventParameter = assertedEventParameter(node);
2042
+ if (!eventParameter)
2043
+ return;
2044
+ context.report({
2045
+ node,
2046
+ messageId: "typeEventParameter",
2047
+ data: eventParameter
2048
+ });
2049
+ };
1969
2050
  return {
1970
- ImportDeclaration(node) {
1971
- if (isTestFile || !isProjectLocalImport(node.source.value))
1972
- return;
1973
- for (const specifier of node.specifiers) {
1974
- if (specifier.type !== "ImportSpecifier")
1975
- continue;
1976
- const importedName2 = getImportedName(specifier);
1977
- if (!SERVICE_CONSTRUCTOR_NAME.test(importedName2))
1978
- continue;
1979
- context.report({
1980
- node: specifier,
1981
- messageId: "serviceConstructorImport",
1982
- data: { name: importedName2 }
1983
- });
1984
- }
1985
- }
2051
+ TSAsExpression: checkAssertion,
2052
+ TSTypeAssertion: checkAssertion
1986
2053
  };
1987
2054
  }
1988
2055
  });
1989
2056
 
1990
- // vendor/anti-slop/src/effect/index.ts
1991
- var antiSlopEffectPlugin = eslintCompatPlugin3({
1992
- meta: { name: "anti-slop-effect" },
2057
+ // src/generic/index.ts
2058
+ var timmoPlugin = eslintCompatPlugin4({
2059
+ meta: { name: "timmo" },
1993
2060
  rules: {
1994
- "no-service-constructor-imports": noServiceConstructorImportsRule
2061
+ "prefer-event-parameter-type": preferEventParameterTypeRule
1995
2062
  }
1996
2063
  });
1997
- var effect_default2 = antiSlopEffectPlugin;
1998
- // src/cli.ts
1999
- import { resolve as resolve2 } from "node:path";
2064
+ var generic_default = timmoPlugin;
2065
+
2066
+ // node_modules/oxlint/dist/index.js
2067
+ function defineConfig(config) {
2068
+ return config;
2069
+ }
2000
2070
 
2001
2071
  // src/configs/enable-plugin-rules.ts
2002
2072
  function enablePluginRules(namespace, plugin) {
2003
2073
  return Object.fromEntries(Object.keys(plugin.rules).sort().map((rule) => [`${namespace}/${rule}`, "error"]));
2004
2074
  }
2005
2075
 
2076
+ // src/configs/recommended.ts
2077
+ var recommended = defineConfig({
2078
+ jsPlugins: [
2079
+ {
2080
+ name: "anti-slop",
2081
+ specifier: "@timmo001/oxlint-rules/upstream/anti-slop"
2082
+ },
2083
+ { name: "timmo", specifier: "@timmo001/oxlint-rules/generic" }
2084
+ ],
2085
+ rules: {
2086
+ ...enablePluginRules("anti-slop", src_default),
2087
+ ...enablePluginRules("timmo", generic_default)
2088
+ }
2089
+ });
2090
+ var recommended_default = recommended;
2091
+
2092
+ // src/configs/effect.ts
2093
+ var effect = defineConfig({
2094
+ extends: [recommended_default],
2095
+ jsPlugins: [
2096
+ {
2097
+ name: "anti-slop-effect",
2098
+ specifier: "@timmo001/oxlint-rules/upstream/effect"
2099
+ },
2100
+ { name: "timmo-effect", specifier: "@timmo001/oxlint-rules/effect" }
2101
+ ],
2102
+ rules: {
2103
+ ...enablePluginRules("anti-slop-effect", effect_default2),
2104
+ ...enablePluginRules("timmo-effect", effect_default)
2105
+ }
2106
+ });
2107
+ var effect_default3 = effect;
2108
+
2109
+ // src/cli.ts
2110
+ import { resolve as resolve2 } from "node:path";
2111
+
2006
2112
  // src/install/copy.ts
2007
2113
  import { cp, mkdir, readdir, rm } from "node:fs/promises";
2008
2114
  import { dirname, join, relative, resolve, sep } from "node:path";
@@ -2039,11 +2145,14 @@ async function copyRules(destination, options = {}) {
2039
2145
  await cp(join(root, "vendor/anti-slop/LICENSE"), join(target, "upstream/effect/LICENSE"));
2040
2146
  await copyTree(join(root, "src/effect"), join(target, "effect"));
2041
2147
  await cp(join(root, "LICENSE"), join(target, "effect/LICENSE"));
2148
+ await copyTree(join(root, "src/generic"), join(target, "generic"));
2149
+ await cp(join(root, "LICENSE"), join(target, "generic/LICENSE"));
2042
2150
  const displayRoot = relative(process.cwd(), target) || ".";
2043
2151
  const entryPoint = (path) => `./${path.split(sep).join("/")}`;
2044
2152
  return {
2045
2153
  antiSlop: entryPoint(join(displayRoot, "upstream/anti-slop/index.ts")),
2046
2154
  antiSlopEffect: entryPoint(join(displayRoot, "upstream/effect/index.ts")),
2155
+ timmo: entryPoint(join(displayRoot, "generic/index.ts")),
2047
2156
  timmoEffect: entryPoint(join(displayRoot, "effect/index.ts"))
2048
2157
  };
2049
2158
  }
@@ -2077,12 +2186,10 @@ if (command === "--help" || command === "-h") {
2077
2186
  console.log("Copied Oxlint plugins:");
2078
2187
  console.log(` anti-slop: ${entries.antiSlop}`);
2079
2188
  console.log(` anti-slop-effect: ${entries.antiSlopEffect}`);
2189
+ console.log(` timmo: ${entries.timmo}`);
2080
2190
  console.log(` timmo-effect: ${entries.timmoEffect}`);
2081
- printRuleSettings("General rule settings:", enablePluginRules("anti-slop", src_default));
2082
- printRuleSettings("Effect rule settings (opt-in):", {
2083
- ...enablePluginRules("anti-slop-effect", effect_default2),
2084
- ...enablePluginRules("timmo-effect", effect_default)
2085
- });
2191
+ printRuleSettings("General rule settings:", recommended_default.rules ?? {});
2192
+ printRuleSettings("Effect rule settings (opt-in):", effect_default3.rules ?? {});
2086
2193
  } catch (error) {
2087
2194
  console.error(error instanceof Error ? error.message : String(error));
2088
2195
  process.exitCode = 1;
@@ -102,11 +102,67 @@ var timmoEffectPlugin = eslintCompatPlugin({
102
102
  });
103
103
  var effect_default = timmoEffectPlugin;
104
104
 
105
- // vendor/anti-slop/src/index.ts
105
+ // vendor/anti-slop/src/effect/index.ts
106
106
  import { eslintCompatPlugin as eslintCompatPlugin2 } from "@oxlint/plugins";
107
107
 
108
- // vendor/anti-slop/src/rules/no-chained-type-assertions.ts
108
+ // vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
109
109
  import { defineRule as defineRule2 } from "@oxlint/plugins";
110
+ var SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
111
+ var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
112
+ function isProjectLocalImport(source) {
113
+ return source.startsWith("./") || source.startsWith("../");
114
+ }
115
+ function getImportedName(specifier) {
116
+ if (specifier.imported.type === "Identifier")
117
+ return specifier.imported.name;
118
+ return specifier.imported.value;
119
+ }
120
+ var noServiceConstructorImportsRule = defineRule2({
121
+ meta: {
122
+ type: "problem",
123
+ docs: {
124
+ description: "Disallow project-local make<CapabilityName> imports outside test and spec files."
125
+ },
126
+ messages: {
127
+ serviceConstructorImport: 'Do not import Effect service constructor "{{name}}" into runtime code. Import the owning Layer, yield the contextual service, and allow its requirements to propagate to the composition root.'
128
+ }
129
+ },
130
+ create(context) {
131
+ const isTestFile = TEST_FILE.test(context.filename.replaceAll("\\", "/"));
132
+ return {
133
+ ImportDeclaration(node) {
134
+ if (isTestFile || !isProjectLocalImport(node.source.value))
135
+ return;
136
+ for (const specifier of node.specifiers) {
137
+ if (specifier.type !== "ImportSpecifier")
138
+ continue;
139
+ const importedName = getImportedName(specifier);
140
+ if (!SERVICE_CONSTRUCTOR_NAME.test(importedName))
141
+ continue;
142
+ context.report({
143
+ node: specifier,
144
+ messageId: "serviceConstructorImport",
145
+ data: { name: importedName }
146
+ });
147
+ }
148
+ }
149
+ };
150
+ }
151
+ });
152
+
153
+ // vendor/anti-slop/src/effect/index.ts
154
+ var antiSlopEffectPlugin = eslintCompatPlugin2({
155
+ meta: { name: "anti-slop-effect" },
156
+ rules: {
157
+ "no-service-constructor-imports": noServiceConstructorImportsRule
158
+ }
159
+ });
160
+ var effect_default2 = antiSlopEffectPlugin;
161
+ // vendor/anti-slop/src/index.ts
162
+ import { eslintCompatPlugin as eslintCompatPlugin3 } from "@oxlint/plugins";
163
+
164
+ // vendor/anti-slop/src/rules/no-chained-type-assertions.ts
165
+ import { defineRule as defineRule3 } from "@oxlint/plugins";
110
166
  function isTypeAssertionExpression(node) {
111
167
  return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
112
168
  }
@@ -141,7 +197,7 @@ function isForbiddenAssertionChain(node) {
141
197
  }
142
198
  return assertionCount > 1 && hasNonConstAssertion;
143
199
  }
144
- var noChainedTypeAssertionsRule = defineRule2({
200
+ var noChainedTypeAssertionsRule = defineRule3({
145
201
  meta: {
146
202
  type: "problem",
147
203
  docs: {
@@ -165,7 +221,7 @@ var noChainedTypeAssertionsRule = defineRule2({
165
221
  });
166
222
 
167
223
  // vendor/anti-slop/src/rules/no-conditional-empty-object-spread.ts
168
- import { defineRule as defineRule3 } from "@oxlint/plugins";
224
+ import { defineRule as defineRule4 } from "@oxlint/plugins";
169
225
  function unwrapParentheses(node) {
170
226
  let current = node;
171
227
  while (current.type === "ParenthesizedExpression") {
@@ -180,7 +236,7 @@ function isConditionalEmptyObjectSpread(node) {
180
236
  const conditional = unwrapParentheses(node);
181
237
  return conditional.type === "ConditionalExpression" && (isEmptyObjectExpression(conditional.consequent) || isEmptyObjectExpression(conditional.alternate));
182
238
  }
183
- var noConditionalEmptyObjectSpreadRule = defineRule3({
239
+ var noConditionalEmptyObjectSpreadRule = defineRule4({
184
240
  meta: {
185
241
  type: "suggestion",
186
242
  docs: {
@@ -204,7 +260,7 @@ var noConditionalEmptyObjectSpreadRule = defineRule3({
204
260
  });
205
261
 
206
262
  // vendor/anti-slop/src/rules/no-known-value-widening.ts
207
- import { defineRule as defineRule4 } from "@oxlint/plugins";
263
+ import { defineRule as defineRule5 } from "@oxlint/plugins";
208
264
 
209
265
  // vendor/anti-slop/src/shared/lexical-type-parameters.ts
210
266
  function isNode(value) {
@@ -903,7 +959,7 @@ function isDictionaryAccumulatorTarget(destination) {
903
959
  function hasParentAssertion(node) {
904
960
  return node.parent?.type === "TSAsExpression" || node.parent?.type === "TSTypeAssertion";
905
961
  }
906
- var noKnownValueWideningRule = defineRule4({
962
+ var noKnownValueWideningRule = defineRule5({
907
963
  meta: {
908
964
  type: "problem",
909
965
  docs: {
@@ -1016,7 +1072,7 @@ var noKnownValueWideningRule = defineRule4({
1016
1072
  });
1017
1073
 
1018
1074
  // vendor/anti-slop/src/rules/no-module-mocking.ts
1019
- import { defineRule as defineRule5 } from "@oxlint/plugins";
1075
+ import { defineRule as defineRule6 } from "@oxlint/plugins";
1020
1076
  var moduleMockMethods = new Set(["doMock", "mock", "unstable_mockModule"]);
1021
1077
  function resolveVariable3(sourceCode, identifier) {
1022
1078
  let scope = sourceCode.getScope(identifier);
@@ -1061,7 +1117,7 @@ function moduleMockCall(sourceCode, callee) {
1061
1117
  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;
1062
1118
  return method !== null && moduleMockMethods.has(method);
1063
1119
  }
1064
- var noModuleMockingRule = defineRule5({
1120
+ var noModuleMockingRule = defineRule6({
1065
1121
  meta: {
1066
1122
  type: "problem",
1067
1123
  docs: {
@@ -1085,8 +1141,8 @@ var noModuleMockingRule = defineRule5({
1085
1141
  });
1086
1142
 
1087
1143
  // vendor/anti-slop/src/rules/no-object-parameters.ts
1088
- import { defineRule as defineRule6 } from "@oxlint/plugins";
1089
- var noObjectParametersRule = defineRule6({
1144
+ import { defineRule as defineRule7 } from "@oxlint/plugins";
1145
+ var noObjectParametersRule = defineRule7({
1090
1146
  meta: {
1091
1147
  type: "problem",
1092
1148
  docs: {
@@ -1139,7 +1195,7 @@ var noObjectParametersRule = defineRule6({
1139
1195
  });
1140
1196
 
1141
1197
  // vendor/anti-slop/src/rules/no-reflect-apply.ts
1142
- import { defineRule as defineRule7 } from "@oxlint/plugins";
1198
+ import { defineRule as defineRule8 } from "@oxlint/plugins";
1143
1199
 
1144
1200
  // vendor/anti-slop/src/shared/reflect-method.ts
1145
1201
  function resolveVariable4(sourceCode, identifier) {
@@ -1170,7 +1226,7 @@ function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
1170
1226
  }
1171
1227
 
1172
1228
  // vendor/anti-slop/src/rules/no-reflect-apply.ts
1173
- var noReflectApplyRule = defineRule7({
1229
+ var noReflectApplyRule = defineRule8({
1174
1230
  meta: {
1175
1231
  type: "problem",
1176
1232
  docs: {
@@ -1194,8 +1250,8 @@ var noReflectApplyRule = defineRule7({
1194
1250
  });
1195
1251
 
1196
1252
  // vendor/anti-slop/src/rules/no-reflect-get.ts
1197
- import { defineRule as defineRule8 } from "@oxlint/plugins";
1198
- var noReflectGetRule = defineRule8({
1253
+ import { defineRule as defineRule9 } from "@oxlint/plugins";
1254
+ var noReflectGetRule = defineRule9({
1199
1255
  meta: {
1200
1256
  type: "problem",
1201
1257
  docs: {
@@ -1219,7 +1275,7 @@ var noReflectGetRule = defineRule8({
1219
1275
  });
1220
1276
 
1221
1277
  // vendor/anti-slop/src/rules/no-runtime-typeof.ts
1222
- import { defineRule as defineRule9 } from "@oxlint/plugins";
1278
+ import { defineRule as defineRule10 } from "@oxlint/plugins";
1223
1279
  function isRuntimeFunction(node) {
1224
1280
  return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
1225
1281
  }
@@ -1242,7 +1298,7 @@ function isExistenceProbe(node) {
1242
1298
  const other = parent.left === node ? parent.right : parent.left;
1243
1299
  return other.type === "Literal" && other.value === "undefined";
1244
1300
  }
1245
- var noRuntimeTypeofRule = defineRule9({
1301
+ var noRuntimeTypeofRule = defineRule10({
1246
1302
  meta: {
1247
1303
  type: "problem",
1248
1304
  docs: {
@@ -1276,7 +1332,7 @@ var noRuntimeTypeofRule = defineRule9({
1276
1332
  });
1277
1333
 
1278
1334
  // vendor/anti-slop/src/rules/no-shape-in-symbol-names.ts
1279
- import { defineRule as defineRule10 } from "@oxlint/plugins";
1335
+ import { defineRule as defineRule11 } from "@oxlint/plugins";
1280
1336
  var FORBIDDEN_SYMBOL_NAME = "shape";
1281
1337
  function containsForbiddenSymbolName(name) {
1282
1338
  return name.toLowerCase().includes(FORBIDDEN_SYMBOL_NAME);
@@ -1287,7 +1343,7 @@ function isBorrowedMemberName(node) {
1287
1343
  return false;
1288
1344
  return parent.property === node && parent.computed === false;
1289
1345
  }
1290
- var noForbiddenTermInSymbolNamesRule = defineRule10({
1346
+ var noForbiddenTermInSymbolNamesRule = defineRule11({
1291
1347
  meta: {
1292
1348
  type: "problem",
1293
1349
  docs: {
@@ -1316,12 +1372,12 @@ var noForbiddenTermInSymbolNamesRule = defineRule10({
1316
1372
  });
1317
1373
 
1318
1374
  // vendor/anti-slop/src/rules/no-unknown-parameters.ts
1319
- import { defineRule as defineRule11 } from "@oxlint/plugins";
1375
+ import { defineRule as defineRule12 } from "@oxlint/plugins";
1320
1376
  function isTypePredicateSubject(owner, parameterName) {
1321
1377
  const predicate = owner.returnType?.typeAnnotation;
1322
1378
  return predicate?.type === "TSTypePredicate" && predicate.parameterName.type === "Identifier" && predicate.parameterName.name === parameterName;
1323
1379
  }
1324
- var noUnknownParametersRule = defineRule11({
1380
+ var noUnknownParametersRule = defineRule12({
1325
1381
  meta: {
1326
1382
  type: "problem",
1327
1383
  docs: {
@@ -1365,8 +1421,8 @@ var noUnknownParametersRule = defineRule11({
1365
1421
  });
1366
1422
 
1367
1423
  // vendor/anti-slop/src/rules/no-unknown-returns.ts
1368
- import { defineRule as defineRule12 } from "@oxlint/plugins";
1369
- var noUnknownReturnsRule = defineRule12({
1424
+ import { defineRule as defineRule13 } from "@oxlint/plugins";
1425
+ var noUnknownReturnsRule = defineRule13({
1370
1426
  meta: {
1371
1427
  type: "problem",
1372
1428
  docs: {
@@ -1419,8 +1475,8 @@ var noUnknownReturnsRule = defineRule12({
1419
1475
  });
1420
1476
 
1421
1477
  // vendor/anti-slop/src/rules/no-unknown-type-aliases.ts
1422
- import { defineRule as defineRule13 } from "@oxlint/plugins";
1423
- var noUnknownTypeAliasesRule = defineRule13({
1478
+ import { defineRule as defineRule14 } from "@oxlint/plugins";
1479
+ var noUnknownTypeAliasesRule = defineRule14({
1424
1480
  meta: {
1425
1481
  type: "problem",
1426
1482
  docs: {
@@ -1458,7 +1514,7 @@ var noUnknownTypeAliasesRule = defineRule13({
1458
1514
  });
1459
1515
 
1460
1516
  // vendor/anti-slop/src/rules/no-unsafe-dictionary-type.ts
1461
- import { defineRule as defineRule14 } from "@oxlint/plugins";
1517
+ import { defineRule as defineRule15 } from "@oxlint/plugins";
1462
1518
  var typeNodeKinds = new Set([
1463
1519
  "JSDocNonNullableType",
1464
1520
  "JSDocNullableType",
@@ -1545,7 +1601,7 @@ function shouldReportType(node, environment) {
1545
1601
  }
1546
1602
  return true;
1547
1603
  }
1548
- var noUnsafeDictionaryTypeRule = defineRule14({
1604
+ var noUnsafeDictionaryTypeRule = defineRule15({
1549
1605
  meta: {
1550
1606
  type: "problem",
1551
1607
  docs: {
@@ -1587,7 +1643,7 @@ var noUnsafeDictionaryTypeRule = defineRule14({
1587
1643
  });
1588
1644
 
1589
1645
  // vendor/anti-slop/src/rules/no-widen-then-assert.ts
1590
- import { defineRule as defineRule15 } from "@oxlint/plugins";
1646
+ import { defineRule as defineRule16 } from "@oxlint/plugins";
1591
1647
  var functionBoundaryTypes = new Set([
1592
1648
  "ArrowFunctionExpression",
1593
1649
  "FunctionDeclaration",
@@ -1783,7 +1839,7 @@ function assertionIsNarrower(sourceText, broadKind, evidence, assertedType) {
1783
1839
  return isDefinitelyObjectType(assertedType);
1784
1840
  return isDefinitelyNarrowerRecordType(assertedType);
1785
1841
  }
1786
- var noWidenThenAssertRule = defineRule15({
1842
+ var noWidenThenAssertRule = defineRule16({
1787
1843
  meta: {
1788
1844
  type: "problem",
1789
1845
  docs: {
@@ -1823,7 +1879,7 @@ var noWidenThenAssertRule = defineRule15({
1823
1879
  });
1824
1880
 
1825
1881
  // vendor/anti-slop/src/rules/require-safety-comment-for-type-assertion.ts
1826
- import { defineRule as defineRule16 } from "@oxlint/plugins";
1882
+ import { defineRule as defineRule17 } from "@oxlint/plugins";
1827
1883
  var DEFAULT_SAFETY_MARKERS = ["SAFETY"];
1828
1884
  var commentOwnerKinds = new Set([
1829
1885
  "ExpressionStatement",
@@ -1866,7 +1922,7 @@ function hasSafetyComment(sourceCode, node, pattern) {
1866
1922
  current = current.parent;
1867
1923
  }
1868
1924
  }
1869
- var requireSafetyCommentForTypeAssertionRule = defineRule16({
1925
+ var requireSafetyCommentForTypeAssertionRule = defineRule17({
1870
1926
  meta: {
1871
1927
  type: "problem",
1872
1928
  docs: {
@@ -1916,7 +1972,7 @@ var requireSafetyCommentForTypeAssertionRule = defineRule16({
1916
1972
  });
1917
1973
 
1918
1974
  // vendor/anti-slop/src/index.ts
1919
- var antiSlopPlugin = eslintCompatPlugin2({
1975
+ var antiSlopPlugin = eslintCompatPlugin3({
1920
1976
  meta: { name: "anti-slop" },
1921
1977
  rules: {
1922
1978
  "no-chained-type-assertions": noChainedTypeAssertionsRule,
@@ -1937,62 +1993,74 @@ var antiSlopPlugin = eslintCompatPlugin2({
1937
1993
  }
1938
1994
  });
1939
1995
  var src_default = antiSlopPlugin;
1940
- // vendor/anti-slop/src/effect/index.ts
1941
- import { eslintCompatPlugin as eslintCompatPlugin3 } from "@oxlint/plugins";
1996
+ // src/generic/index.ts
1997
+ import { eslintCompatPlugin as eslintCompatPlugin4 } from "@oxlint/plugins";
1942
1998
 
1943
- // vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
1944
- import { defineRule as defineRule17 } from "@oxlint/plugins";
1945
- var SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
1946
- var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
1947
- function isProjectLocalImport(source) {
1948
- return source.startsWith("./") || source.startsWith("../");
1999
+ // src/generic/rules/prefer-event-parameter-type.ts
2000
+ import { defineRule as defineRule18 } from "@oxlint/plugins";
2001
+ function nearestEnclosingFunction2(node) {
2002
+ let current = node.parent;
2003
+ while (current) {
2004
+ if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
2005
+ return current;
2006
+ }
2007
+ current = current.parent;
2008
+ }
2009
+ return null;
1949
2010
  }
1950
- function getImportedName(specifier) {
1951
- if (specifier.imported.type === "Identifier")
1952
- return specifier.imported.name;
1953
- return specifier.imported.value;
2011
+ function assertedEventParameter(node) {
2012
+ const expression = node.expression;
2013
+ if (expression.type !== "MemberExpression" || expression.computed || expression.object.type !== "Identifier" || expression.property.type !== "Identifier" || expression.property.name !== "currentTarget" && expression.property.name !== "target") {
2014
+ return null;
2015
+ }
2016
+ const parameterName = expression.object.name;
2017
+ const property = expression.property.name;
2018
+ const owner = nearestEnclosingFunction2(node);
2019
+ if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === parameterName)) {
2020
+ return null;
2021
+ }
2022
+ return {
2023
+ parameter: parameterName,
2024
+ property
2025
+ };
1954
2026
  }
1955
- var noServiceConstructorImportsRule = defineRule17({
2027
+ var preferEventParameterTypeRule = defineRule18({
1956
2028
  meta: {
1957
- type: "problem",
2029
+ type: "suggestion",
1958
2030
  docs: {
1959
- description: "Disallow project-local make<CapabilityName> imports outside test and spec files."
2031
+ description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
1960
2032
  },
1961
2033
  messages: {
1962
- serviceConstructorImport: 'Do not import Effect service constructor "{{name}}" into runtime code. Import the owning Layer, yield the contextual service, and allow its requirements to propagate to the composition root.'
2034
+ typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site."
1963
2035
  }
1964
2036
  },
1965
2037
  create(context) {
1966
- const isTestFile = TEST_FILE.test(context.filename.replaceAll("\\", "/"));
2038
+ const checkAssertion = (node) => {
2039
+ const eventParameter = assertedEventParameter(node);
2040
+ if (!eventParameter)
2041
+ return;
2042
+ context.report({
2043
+ node,
2044
+ messageId: "typeEventParameter",
2045
+ data: eventParameter
2046
+ });
2047
+ };
1967
2048
  return {
1968
- ImportDeclaration(node) {
1969
- if (isTestFile || !isProjectLocalImport(node.source.value))
1970
- return;
1971
- for (const specifier of node.specifiers) {
1972
- if (specifier.type !== "ImportSpecifier")
1973
- continue;
1974
- const importedName2 = getImportedName(specifier);
1975
- if (!SERVICE_CONSTRUCTOR_NAME.test(importedName2))
1976
- continue;
1977
- context.report({
1978
- node: specifier,
1979
- messageId: "serviceConstructorImport",
1980
- data: { name: importedName2 }
1981
- });
1982
- }
1983
- }
2049
+ TSAsExpression: checkAssertion,
2050
+ TSTypeAssertion: checkAssertion
1984
2051
  };
1985
2052
  }
1986
2053
  });
1987
2054
 
1988
- // vendor/anti-slop/src/effect/index.ts
1989
- var antiSlopEffectPlugin = eslintCompatPlugin3({
1990
- meta: { name: "anti-slop-effect" },
2055
+ // src/generic/index.ts
2056
+ var timmoPlugin = eslintCompatPlugin4({
2057
+ meta: { name: "timmo" },
1991
2058
  rules: {
1992
- "no-service-constructor-imports": noServiceConstructorImportsRule
2059
+ "prefer-event-parameter-type": preferEventParameterTypeRule
1993
2060
  }
1994
2061
  });
1995
- var effect_default2 = antiSlopEffectPlugin;
2062
+ var generic_default = timmoPlugin;
2063
+
1996
2064
  // node_modules/oxlint/dist/index.js
1997
2065
  function defineConfig(config) {
1998
2066
  return config;
@@ -2009,9 +2077,13 @@ var recommended = defineConfig({
2009
2077
  {
2010
2078
  name: "anti-slop",
2011
2079
  specifier: "@timmo001/oxlint-rules/upstream/anti-slop"
2012
- }
2080
+ },
2081
+ { name: "timmo", specifier: "@timmo001/oxlint-rules/generic" }
2013
2082
  ],
2014
- rules: enablePluginRules("anti-slop", src_default)
2083
+ rules: {
2084
+ ...enablePluginRules("anti-slop", src_default),
2085
+ ...enablePluginRules("timmo", generic_default)
2086
+ }
2015
2087
  });
2016
2088
  var recommended_default = recommended;
2017
2089
 
@@ -1833,6 +1833,74 @@ var antiSlopPlugin = eslintCompatPlugin({
1833
1833
  }
1834
1834
  });
1835
1835
  var src_default = antiSlopPlugin;
1836
+ // src/generic/index.ts
1837
+ import { eslintCompatPlugin as eslintCompatPlugin2 } from "@oxlint/plugins";
1838
+
1839
+ // src/generic/rules/prefer-event-parameter-type.ts
1840
+ import { defineRule as defineRule16 } from "@oxlint/plugins";
1841
+ function nearestEnclosingFunction(node) {
1842
+ let current = node.parent;
1843
+ while (current) {
1844
+ if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
1845
+ return current;
1846
+ }
1847
+ current = current.parent;
1848
+ }
1849
+ return null;
1850
+ }
1851
+ function assertedEventParameter(node) {
1852
+ const expression = node.expression;
1853
+ if (expression.type !== "MemberExpression" || expression.computed || expression.object.type !== "Identifier" || expression.property.type !== "Identifier" || expression.property.name !== "currentTarget" && expression.property.name !== "target") {
1854
+ return null;
1855
+ }
1856
+ const parameterName = expression.object.name;
1857
+ const property = expression.property.name;
1858
+ const owner = nearestEnclosingFunction(node);
1859
+ if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === parameterName)) {
1860
+ return null;
1861
+ }
1862
+ return {
1863
+ parameter: parameterName,
1864
+ property
1865
+ };
1866
+ }
1867
+ var preferEventParameterTypeRule = defineRule16({
1868
+ meta: {
1869
+ type: "suggestion",
1870
+ docs: {
1871
+ description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
1872
+ },
1873
+ messages: {
1874
+ typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site."
1875
+ }
1876
+ },
1877
+ create(context) {
1878
+ const checkAssertion = (node) => {
1879
+ const eventParameter = assertedEventParameter(node);
1880
+ if (!eventParameter)
1881
+ return;
1882
+ context.report({
1883
+ node,
1884
+ messageId: "typeEventParameter",
1885
+ data: eventParameter
1886
+ });
1887
+ };
1888
+ return {
1889
+ TSAsExpression: checkAssertion,
1890
+ TSTypeAssertion: checkAssertion
1891
+ };
1892
+ }
1893
+ });
1894
+
1895
+ // src/generic/index.ts
1896
+ var timmoPlugin = eslintCompatPlugin2({
1897
+ meta: { name: "timmo" },
1898
+ rules: {
1899
+ "prefer-event-parameter-type": preferEventParameterTypeRule
1900
+ }
1901
+ });
1902
+ var generic_default = timmoPlugin;
1903
+
1836
1904
  // node_modules/oxlint/dist/index.js
1837
1905
  function defineConfig(config) {
1838
1906
  return config;
@@ -1849,9 +1917,13 @@ var recommended = defineConfig({
1849
1917
  {
1850
1918
  name: "anti-slop",
1851
1919
  specifier: "@timmo001/oxlint-rules/upstream/anti-slop"
1852
- }
1920
+ },
1921
+ { name: "timmo", specifier: "@timmo001/oxlint-rules/generic" }
1853
1922
  ],
1854
- rules: enablePluginRules("anti-slop", src_default)
1923
+ rules: {
1924
+ ...enablePluginRules("anti-slop", src_default),
1925
+ ...enablePluginRules("timmo", generic_default)
1926
+ }
1855
1927
  });
1856
1928
  var recommended_default = recommended;
1857
1929
  export {
@@ -0,0 +1,70 @@
1
+ // src/generic/index.ts
2
+ import { eslintCompatPlugin } from "@oxlint/plugins";
3
+
4
+ // src/generic/rules/prefer-event-parameter-type.ts
5
+ import { defineRule } from "@oxlint/plugins";
6
+ function nearestEnclosingFunction(node) {
7
+ let current = node.parent;
8
+ while (current) {
9
+ if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
10
+ return current;
11
+ }
12
+ current = current.parent;
13
+ }
14
+ return null;
15
+ }
16
+ function assertedEventParameter(node) {
17
+ const expression = node.expression;
18
+ if (expression.type !== "MemberExpression" || expression.computed || expression.object.type !== "Identifier" || expression.property.type !== "Identifier" || expression.property.name !== "currentTarget" && expression.property.name !== "target") {
19
+ return null;
20
+ }
21
+ const parameterName = expression.object.name;
22
+ const property = expression.property.name;
23
+ const owner = nearestEnclosingFunction(node);
24
+ if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === parameterName)) {
25
+ return null;
26
+ }
27
+ return {
28
+ parameter: parameterName,
29
+ property
30
+ };
31
+ }
32
+ var preferEventParameterTypeRule = defineRule({
33
+ meta: {
34
+ type: "suggestion",
35
+ docs: {
36
+ description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
37
+ },
38
+ messages: {
39
+ typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site."
40
+ }
41
+ },
42
+ create(context) {
43
+ const checkAssertion = (node) => {
44
+ const eventParameter = assertedEventParameter(node);
45
+ if (!eventParameter)
46
+ return;
47
+ context.report({
48
+ node,
49
+ messageId: "typeEventParameter",
50
+ data: eventParameter
51
+ });
52
+ };
53
+ return {
54
+ TSAsExpression: checkAssertion,
55
+ TSTypeAssertion: checkAssertion
56
+ };
57
+ }
58
+ });
59
+
60
+ // src/generic/index.ts
61
+ var timmoPlugin = eslintCompatPlugin({
62
+ meta: { name: "timmo" },
63
+ rules: {
64
+ "prefer-event-parameter-type": preferEventParameterTypeRule
65
+ }
66
+ });
67
+ var generic_default = timmoPlugin;
68
+ export {
69
+ generic_default as default
70
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/oxlint-rules",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Shared Oxlint plugins and configs with optional Effect rules.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -14,6 +14,7 @@
14
14
  "exports": {
15
15
  "./upstream/anti-slop": "./dist/upstream/anti-slop.js",
16
16
  "./upstream/effect": "./dist/upstream/effect.js",
17
+ "./generic": "./dist/generic/index.js",
17
18
  "./effect": "./dist/effect/index.js",
18
19
  "./configs/recommended": "./dist/configs/recommended.js",
19
20
  "./configs/effect": "./dist/configs/effect.js"
@@ -21,6 +22,8 @@
21
22
  "files": [
22
23
  "dist",
23
24
  "src/effect",
25
+ "src/generic",
26
+ "!src/generic/**/*.test.ts",
24
27
  "!src/effect/**/*.test.ts",
25
28
  "vendor/anti-slop/src",
26
29
  "vendor/anti-slop/LICENSE",
@@ -23,7 +23,8 @@ description: >-
23
23
  separately when Dylan Mulroy's plugin should own the behaviour.
24
24
  6. Register the rule in its plugin. Verify the matching config and copy command
25
25
  discover it from the plugin's `rules` map, then update the README rule list
26
- and behaviour description. Do not add rule names to either skill.
26
+ and behaviour description. Keep skills workflow-only; do not duplicate rule
27
+ or plugin inventories or counts in them.
27
28
  7. Run `mise run check`, `mise run build`, `npm pack --dry-run`, and
28
29
  `bunx jsr@0.14.3 publish --dry-run` in the central checkout.
29
30
 
@@ -23,9 +23,9 @@ description: >-
23
23
  2. Detect Bun, npm, pnpm, or Yarn from the target's package manager declaration
24
24
  and lockfile. Add an exact development dependency through that package
25
25
  manager.
26
- 3. Extend `@timmo001/oxlint-rules/configs/recommended`. Use `/configs/effect`
27
- instead only when `effect` is a direct dependency or the user explicitly
28
- requests it.
26
+ 3. Extend `@timmo001/oxlint-rules/configs/recommended`. The config owns plugin
27
+ registration and recommended severities. Use `/configs/effect` instead only
28
+ when `effect` is a direct dependency or the user explicitly requests it.
29
29
  4. Keep dependency and config edits visible. Do not delegate them to a script.
30
30
 
31
31
  ## Copy rules
@@ -36,11 +36,11 @@ description: >-
36
36
  meaningful differences before asking whether replacement is intended.
37
37
  3. Run `node scripts/copy.mjs <bun|npm|pnpm|yarn> <destination>`. Add `--force`
38
38
  only after explicit replacement approval.
39
- 4. Register the three entry points printed by the command as `anti-slop`,
40
- `anti-slop-effect`, and `timmo-effect`. Merge every printed general rule
41
- setting into the target config. Merge every printed Effect rule setting only
42
- for direct Effect use or an explicit request. Do not maintain a rule list in
43
- this skill.
39
+ 4. Register every plugin entry point printed by the command. Merge every printed
40
+ general rule setting into the target config. Merge every printed Effect rule
41
+ setting only for direct Effect use or an explicit request. Treat the command
42
+ output as authoritative rather than maintaining plugin or rule inventories
43
+ in this skill.
44
44
 
45
45
  Run the target repository's normal lint, typecheck, tests, and build. Report
46
46
  package-manager changes, preserved local configuration, enabled rule groups,
@@ -0,0 +1,12 @@
1
+ import { eslintCompatPlugin } from "@oxlint/plugins";
2
+
3
+ import { preferEventParameterTypeRule } from "./rules/prefer-event-parameter-type.ts";
4
+
5
+ const timmoPlugin = eslintCompatPlugin({
6
+ meta: { name: "timmo" },
7
+ rules: {
8
+ "prefer-event-parameter-type": preferEventParameterTypeRule,
9
+ },
10
+ });
11
+
12
+ export default timmoPlugin;
@@ -0,0 +1,83 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+
3
+ import type { ESTree } from "@oxlint/plugins";
4
+
5
+ type FunctionNode = ESTree.Function | ESTree.ArrowFunctionExpression;
6
+ type TypeAssertion = ESTree.TSAsExpression | ESTree.TSTypeAssertion;
7
+
8
+ function nearestEnclosingFunction(node: ESTree.Node): FunctionNode | null {
9
+ let current: ESTree.Node | null = node.parent;
10
+ while (current) {
11
+ if (
12
+ current.type === "FunctionDeclaration" ||
13
+ current.type === "FunctionExpression" ||
14
+ current.type === "ArrowFunctionExpression"
15
+ ) {
16
+ return current;
17
+ }
18
+ current = current.parent;
19
+ }
20
+ return null;
21
+ }
22
+
23
+ function assertedEventParameter(node: TypeAssertion): {
24
+ readonly parameter: string;
25
+ readonly property: "currentTarget" | "target";
26
+ } | null {
27
+ const expression = node.expression;
28
+ if (
29
+ expression.type !== "MemberExpression" ||
30
+ expression.computed ||
31
+ expression.object.type !== "Identifier" ||
32
+ expression.property.type !== "Identifier" ||
33
+ (expression.property.name !== "currentTarget" &&
34
+ expression.property.name !== "target")
35
+ ) {
36
+ return null;
37
+ }
38
+ const parameterName = expression.object.name;
39
+ const property = expression.property.name;
40
+ const owner = nearestEnclosingFunction(node);
41
+ if (
42
+ !owner?.params.some(
43
+ (parameter) =>
44
+ parameter.type === "Identifier" && parameter.name === parameterName,
45
+ )
46
+ ) {
47
+ return null;
48
+ }
49
+ return {
50
+ parameter: parameterName,
51
+ property,
52
+ };
53
+ }
54
+
55
+ /** Prefer expressing an event target type in its handler parameter signature. */
56
+ export const preferEventParameterTypeRule = defineRule({
57
+ meta: {
58
+ type: "suggestion",
59
+ docs: {
60
+ description:
61
+ "Prefer typing event target properties in the handler parameter instead of asserting them at use sites.",
62
+ },
63
+ messages: {
64
+ typeEventParameter:
65
+ "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site.",
66
+ },
67
+ },
68
+ create(context) {
69
+ const checkAssertion = (node: TypeAssertion) => {
70
+ const eventParameter = assertedEventParameter(node);
71
+ if (!eventParameter) return;
72
+ context.report({
73
+ node,
74
+ messageId: "typeEventParameter",
75
+ data: eventParameter,
76
+ });
77
+ };
78
+ return {
79
+ TSAsExpression: checkAssertion,
80
+ TSTypeAssertion: checkAssertion,
81
+ };
82
+ },
83
+ });