@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 +12 -5
- package/dist/cli.js +184 -77
- package/dist/configs/effect.js +144 -72
- package/dist/configs/recommended.js +74 -2
- package/dist/generic/index.js +70 -0
- package/package.json +4 -1
- package/skills/add-oxlint-rule/SKILL.md +2 -1
- package/skills/install-timmo-oxlint-rules/SKILL.md +8 -8
- package/src/generic/index.ts +12 -0
- package/src/generic/rules/prefer-event-parameter-type.ts +83 -0
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
|
-
|
|
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
|
|
53
|
-
|
|
54
|
-
It excludes tests and repository metadata, and refuses to
|
|
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-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
1091
|
-
var noObjectParametersRule =
|
|
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
|
|
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 =
|
|
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
|
|
1200
|
-
var noReflectGetRule =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
1371
|
-
var noUnknownReturnsRule =
|
|
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
|
|
1425
|
-
var noUnknownTypeAliasesRule =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
//
|
|
1943
|
-
import { eslintCompatPlugin as
|
|
1998
|
+
// src/generic/index.ts
|
|
1999
|
+
import { eslintCompatPlugin as eslintCompatPlugin4 } from "@oxlint/plugins";
|
|
1944
2000
|
|
|
1945
|
-
//
|
|
1946
|
-
import { defineRule as
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
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
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
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
|
|
2029
|
+
var preferEventParameterTypeRule = defineRule18({
|
|
1958
2030
|
meta: {
|
|
1959
|
-
type: "
|
|
2031
|
+
type: "suggestion",
|
|
1960
2032
|
docs: {
|
|
1961
|
-
description: "
|
|
2033
|
+
description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
|
|
1962
2034
|
},
|
|
1963
2035
|
messages: {
|
|
1964
|
-
|
|
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
|
|
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
|
-
|
|
1971
|
-
|
|
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
|
-
//
|
|
1991
|
-
var
|
|
1992
|
-
meta: { name: "
|
|
2057
|
+
// src/generic/index.ts
|
|
2058
|
+
var timmoPlugin = eslintCompatPlugin4({
|
|
2059
|
+
meta: { name: "timmo" },
|
|
1993
2060
|
rules: {
|
|
1994
|
-
"
|
|
2061
|
+
"prefer-event-parameter-type": preferEventParameterTypeRule
|
|
1995
2062
|
}
|
|
1996
2063
|
});
|
|
1997
|
-
var
|
|
1998
|
-
|
|
1999
|
-
|
|
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:",
|
|
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;
|
package/dist/configs/effect.js
CHANGED
|
@@ -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-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
1089
|
-
var noObjectParametersRule =
|
|
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
|
|
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 =
|
|
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
|
|
1198
|
-
var noReflectGetRule =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
1369
|
-
var noUnknownReturnsRule =
|
|
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
|
|
1423
|
-
var noUnknownTypeAliasesRule =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
//
|
|
1941
|
-
import { eslintCompatPlugin as
|
|
1996
|
+
// src/generic/index.ts
|
|
1997
|
+
import { eslintCompatPlugin as eslintCompatPlugin4 } from "@oxlint/plugins";
|
|
1942
1998
|
|
|
1943
|
-
//
|
|
1944
|
-
import { defineRule as
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
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
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
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
|
|
2027
|
+
var preferEventParameterTypeRule = defineRule18({
|
|
1956
2028
|
meta: {
|
|
1957
|
-
type: "
|
|
2029
|
+
type: "suggestion",
|
|
1958
2030
|
docs: {
|
|
1959
|
-
description: "
|
|
2031
|
+
description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
|
|
1960
2032
|
},
|
|
1961
2033
|
messages: {
|
|
1962
|
-
|
|
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
|
|
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
|
-
|
|
1969
|
-
|
|
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
|
-
//
|
|
1989
|
-
var
|
|
1990
|
-
meta: { name: "
|
|
2055
|
+
// src/generic/index.ts
|
|
2056
|
+
var timmoPlugin = eslintCompatPlugin4({
|
|
2057
|
+
meta: { name: "timmo" },
|
|
1991
2058
|
rules: {
|
|
1992
|
-
"
|
|
2059
|
+
"prefer-event-parameter-type": preferEventParameterTypeRule
|
|
1993
2060
|
}
|
|
1994
2061
|
});
|
|
1995
|
-
var
|
|
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:
|
|
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:
|
|
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.
|
|
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.
|
|
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`.
|
|
27
|
-
|
|
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
|
|
40
|
-
|
|
41
|
-
setting
|
|
42
|
-
|
|
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
|
+
});
|