@timmo001/oxlint-rules 0.4.0 → 0.6.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 +59 -5
- package/dist/cli.js +498 -55
- package/dist/configs/recommended-effect.js +539 -99
- package/dist/configs/recommended.js +336 -16
- package/dist/effect/index.js +162 -13
- package/dist/generic/index.js +331 -15
- package/package.json +12 -9
- package/skills/install-timmo-oxlint-rules/SKILL.md +18 -5
- package/src/effect/index.ts +4 -0
- package/src/effect/rules/no-raw-gh.ts +126 -0
- package/src/effect/rules/no-try-catch-in-effect-generators.ts +4 -98
- package/src/effect/rules/prefer-platform-services.ts +105 -0
- package/src/generic/index.ts +9 -0
- package/src/generic/rules/no-explicit-inferable-type-predicate.ts +146 -0
- package/src/generic/rules/no-unchecked-query-selector-type.ts +48 -0
- package/src/generic/rules/no-untyped-custom-event.ts +31 -0
- package/src/generic/rules/prefer-event-parameter-type.ts +36 -35
- package/src/generic/rules/prefer-lit-query-decorators.ts +84 -0
- package/src/generic/rules/prefer-types-over-assertions.ts +6 -5
- package/src/shared/ast.ts +31 -0
- package/src/shared/effect.ts +72 -0
- package/src/shared/json.ts +11 -0
- package/src/shared/lit.ts +219 -0
- package/src/shared/scope.ts +1 -0
|
@@ -6,18 +6,10 @@ function defineConfig(config) {
|
|
|
6
6
|
// src/effect/index.ts
|
|
7
7
|
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
8
8
|
|
|
9
|
-
// src/effect/rules/no-
|
|
9
|
+
// src/effect/rules/no-raw-gh.ts
|
|
10
10
|
import { defineRule } from "@oxlint/plugins";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
while (scope) {
|
|
14
|
-
const variable = scope.set.get(identifier.name);
|
|
15
|
-
if (variable)
|
|
16
|
-
return variable;
|
|
17
|
-
scope = scope.upper;
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
11
|
+
|
|
12
|
+
// src/shared/ast.ts
|
|
21
13
|
function nearestEnclosingFunction(node) {
|
|
22
14
|
let current = node.parent;
|
|
23
15
|
while (current) {
|
|
@@ -33,6 +25,83 @@ function staticMemberName(node) {
|
|
|
33
25
|
return null;
|
|
34
26
|
return node.property.type === "Identifier" ? node.property.name : null;
|
|
35
27
|
}
|
|
28
|
+
|
|
29
|
+
// vendor/anti-slop/src/shared/scope.ts
|
|
30
|
+
function resolveVariable(sourceCode, identifier) {
|
|
31
|
+
let scope = sourceCode.getScope(identifier);
|
|
32
|
+
while (scope !== null) {
|
|
33
|
+
const variable = scope.set.get(identifier.name);
|
|
34
|
+
if (variable !== undefined)
|
|
35
|
+
return variable;
|
|
36
|
+
scope = scope.upper;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// src/effect/rules/no-raw-gh.ts
|
|
41
|
+
var PACKAGE = "@timmo001/effect-gh";
|
|
42
|
+
var rawMethods = new Set(["execute", "json", "stream"]);
|
|
43
|
+
function isEffectGhImport(sourceCode, identifier, name) {
|
|
44
|
+
return resolveVariable(sourceCode, identifier)?.defs.some((definition) => {
|
|
45
|
+
if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration" || definition.parent.source.value !== PACKAGE || definition.node.type !== "ImportSpecifier") {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const imported = definition.node.imported;
|
|
49
|
+
return (imported.type === "Identifier" ? imported.name : imported.value) === name;
|
|
50
|
+
}) ?? false;
|
|
51
|
+
}
|
|
52
|
+
function isYieldGh(sourceCode, node) {
|
|
53
|
+
return node?.type === "YieldExpression" && node.delegate && node.argument?.type === "Identifier" && isEffectGhImport(sourceCode, node.argument, "Gh");
|
|
54
|
+
}
|
|
55
|
+
function isGhBinding(sourceCode, identifier) {
|
|
56
|
+
return resolveVariable(sourceCode, identifier)?.defs.some((definition) => definition.type === "Variable" && definition.node.type === "VariableDeclarator" && isYieldGh(sourceCode, definition.node.init)) ?? false;
|
|
57
|
+
}
|
|
58
|
+
function isGhArgument(node) {
|
|
59
|
+
if (node?.type === "Literal")
|
|
60
|
+
return node.value === "gh";
|
|
61
|
+
if (node?.type !== "ArrayExpression")
|
|
62
|
+
return false;
|
|
63
|
+
const first = node.elements[0];
|
|
64
|
+
return first?.type === "Literal" && first.value === "gh";
|
|
65
|
+
}
|
|
66
|
+
var noRawGhRule = defineRule({
|
|
67
|
+
meta: {
|
|
68
|
+
type: "problem",
|
|
69
|
+
docs: {
|
|
70
|
+
description: "Use @timmo001/effect-gh typed operations instead of the raw Gh methods, Api.raw, or spawning gh directly."
|
|
71
|
+
},
|
|
72
|
+
messages: {
|
|
73
|
+
rawGh: "Use a typed effect-gh operation instead of `Gh.{{method}}` with raw argv. Add one to effect-gh if the command has none.",
|
|
74
|
+
rawApi: "Use `Api.json`, `Api.pages`, `Api.empty`, `Api.text` or `Api.headers` instead of `Api.raw`, so the response is typed.",
|
|
75
|
+
spawnGh: "Run gh through a typed @timmo001/effect-gh operation instead of spawning it directly."
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
create(context) {
|
|
79
|
+
return {
|
|
80
|
+
CallExpression(node) {
|
|
81
|
+
const callee = node.callee;
|
|
82
|
+
const method = staticMemberName(callee);
|
|
83
|
+
if (callee.type === "MemberExpression" && method !== null) {
|
|
84
|
+
const object = callee.object;
|
|
85
|
+
if (rawMethods.has(method) && (isYieldGh(context.sourceCode, object) || object.type === "Identifier" && isGhBinding(context.sourceCode, object))) {
|
|
86
|
+
context.report({ node, messageId: "rawGh", data: { method } });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (method === "raw" && object.type === "Identifier" && isEffectGhImport(context.sourceCode, object, "Api")) {
|
|
90
|
+
context.report({ node, messageId: "rawApi" });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (isGhArgument(node.arguments[0]))
|
|
95
|
+
context.report({ node, messageId: "spawnGh" });
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// src/effect/rules/no-try-catch-in-effect-generators.ts
|
|
102
|
+
import { defineRule as defineRule2 } from "@oxlint/plugins";
|
|
103
|
+
|
|
104
|
+
// src/shared/effect.ts
|
|
36
105
|
function isEffectImport(sourceCode, identifier, namespace) {
|
|
37
106
|
const variable = resolveVariable(sourceCode, identifier);
|
|
38
107
|
return variable?.defs.some((definition) => {
|
|
@@ -60,6 +129,11 @@ function isEffectMethod(sourceCode, node, method) {
|
|
|
60
129
|
}
|
|
61
130
|
return isEffectImport(sourceCode, object.object, true);
|
|
62
131
|
}
|
|
132
|
+
function importsEffect(program) {
|
|
133
|
+
return program.body.some((statement) => statement.type === "ImportDeclaration" && statement.importKind !== "type" && (statement.source.value === "effect" || statement.source.value.startsWith("effect/") || statement.source.value.startsWith("@effect/")));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/effect/rules/no-try-catch-in-effect-generators.ts
|
|
63
137
|
function isDirectArgument(owner, call) {
|
|
64
138
|
return call.arguments.some((argument) => argument === owner);
|
|
65
139
|
}
|
|
@@ -73,7 +147,7 @@ function isRecognisedEffectGenerator(sourceCode, owner) {
|
|
|
73
147
|
const factoryCall = parent.callee;
|
|
74
148
|
return factoryCall.type === "CallExpression" && isEffectMethod(sourceCode, factoryCall.callee, "fn");
|
|
75
149
|
}
|
|
76
|
-
var noTryCatchInEffectGeneratorsRule =
|
|
150
|
+
var noTryCatchInEffectGeneratorsRule = defineRule2({
|
|
77
151
|
meta: {
|
|
78
152
|
type: "problem",
|
|
79
153
|
docs: {
|
|
@@ -98,11 +172,86 @@ var noTryCatchInEffectGeneratorsRule = defineRule({
|
|
|
98
172
|
}
|
|
99
173
|
});
|
|
100
174
|
|
|
175
|
+
// src/effect/rules/prefer-platform-services.ts
|
|
176
|
+
import { defineRule as defineRule3 } from "@oxlint/plugins";
|
|
177
|
+
var moduleServices = new Map([
|
|
178
|
+
["child_process", "childProcess"],
|
|
179
|
+
["fs", "fileSystem"],
|
|
180
|
+
["fs/promises", "fileSystem"]
|
|
181
|
+
]);
|
|
182
|
+
var bunServices = new Map([
|
|
183
|
+
["$", "childProcess"],
|
|
184
|
+
["file", "fileSystem"],
|
|
185
|
+
["spawn", "childProcess"],
|
|
186
|
+
["spawnSync", "childProcess"],
|
|
187
|
+
["write", "fileSystem"]
|
|
188
|
+
]);
|
|
189
|
+
var preferPlatformServicesRule = defineRule3({
|
|
190
|
+
meta: {
|
|
191
|
+
type: "suggestion",
|
|
192
|
+
docs: {
|
|
193
|
+
description: "Prefer Effect's ChildProcessSpawner and FileSystem services over Node or Bun process and file system APIs in modules that use Effect."
|
|
194
|
+
},
|
|
195
|
+
messages: {
|
|
196
|
+
childProcess: "Use Effect's `ChildProcessSpawner` service (`effect/process`) instead of `{{api}}`, so scope cleanup, interruption, and test layers apply.",
|
|
197
|
+
fileSystem: "Use Effect's `FileSystem` service instead of `{{api}}`, so failures stay typed and tests can provide a layer."
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
create(context) {
|
|
201
|
+
let enabled = false;
|
|
202
|
+
return {
|
|
203
|
+
Program(node) {
|
|
204
|
+
enabled = importsEffect(node);
|
|
205
|
+
},
|
|
206
|
+
ImportDeclaration(node) {
|
|
207
|
+
if (!enabled || node.importKind === "type")
|
|
208
|
+
return;
|
|
209
|
+
const source = node.source.value;
|
|
210
|
+
const service = moduleServices.get(source.replace(/^node:/u, ""));
|
|
211
|
+
if (service) {
|
|
212
|
+
context.report({ node, messageId: service, data: { api: source } });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (source !== "bun")
|
|
216
|
+
return;
|
|
217
|
+
for (const specifier of node.specifiers) {
|
|
218
|
+
if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type") {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
|
|
222
|
+
const bunService = bunServices.get(imported);
|
|
223
|
+
if (bunService) {
|
|
224
|
+
context.report({
|
|
225
|
+
node: specifier,
|
|
226
|
+
messageId: bunService,
|
|
227
|
+
data: { api: `Bun.${imported}` }
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
MemberExpression(node) {
|
|
233
|
+
const name = staticMemberName(node);
|
|
234
|
+
const service = name ? bunServices.get(name) : undefined;
|
|
235
|
+
if (!enabled || !service || node.object.type !== "Identifier" || node.object.name !== "Bun" || (resolveVariable(context.sourceCode, node.object)?.defs.length ?? 0) > 0) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
context.report({
|
|
239
|
+
node,
|
|
240
|
+
messageId: service,
|
|
241
|
+
data: { api: `Bun.${name}` }
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
101
248
|
// src/effect/index.ts
|
|
102
249
|
var timmoEffectPlugin = eslintCompatPlugin({
|
|
103
250
|
meta: { name: "timmo-effect" },
|
|
104
251
|
rules: {
|
|
105
|
-
"no-
|
|
252
|
+
"no-raw-gh": noRawGhRule,
|
|
253
|
+
"no-try-catch-in-effect-generators": noTryCatchInEffectGeneratorsRule,
|
|
254
|
+
"prefer-platform-services": preferPlatformServicesRule
|
|
106
255
|
}
|
|
107
256
|
});
|
|
108
257
|
var effect_default = timmoEffectPlugin;
|
|
@@ -111,7 +260,7 @@ var effect_default = timmoEffectPlugin;
|
|
|
111
260
|
import { eslintCompatPlugin as eslintCompatPlugin2 } from "@oxlint/plugins";
|
|
112
261
|
|
|
113
262
|
// vendor/anti-slop/src/effect/rules/no-manual-effect-error-tag.ts
|
|
114
|
-
import { defineRule as
|
|
263
|
+
import { defineRule as defineRule4 } from "@oxlint/plugins";
|
|
115
264
|
|
|
116
265
|
// vendor/anti-slop/src/effect/shared/tagged-values.ts
|
|
117
266
|
var equalityOperators = new Set(["==", "===", "!=", "!=="]);
|
|
@@ -158,7 +307,7 @@ var isMatchPatternObject = (node) => {
|
|
|
158
307
|
};
|
|
159
308
|
|
|
160
309
|
// vendor/anti-slop/src/effect/rules/no-manual-effect-error-tag.ts
|
|
161
|
-
var noManualEffectErrorTagRule =
|
|
310
|
+
var noManualEffectErrorTagRule = defineRule4({
|
|
162
311
|
meta: {
|
|
163
312
|
type: "problem",
|
|
164
313
|
docs: {
|
|
@@ -195,8 +344,8 @@ var noManualEffectErrorTagRule = defineRule2({
|
|
|
195
344
|
});
|
|
196
345
|
|
|
197
346
|
// vendor/anti-slop/src/effect/rules/no-manual-tag-comparison.ts
|
|
198
|
-
import { defineRule as
|
|
199
|
-
var noManualTagComparisonRule =
|
|
347
|
+
import { defineRule as defineRule5 } from "@oxlint/plugins";
|
|
348
|
+
var noManualTagComparisonRule = defineRule5({
|
|
200
349
|
meta: {
|
|
201
350
|
type: "problem",
|
|
202
351
|
docs: {
|
|
@@ -226,8 +375,8 @@ var noManualTagComparisonRule = defineRule3({
|
|
|
226
375
|
});
|
|
227
376
|
|
|
228
377
|
// vendor/anti-slop/src/effect/rules/no-manual-tagged-construction.ts
|
|
229
|
-
import { defineRule as
|
|
230
|
-
var noManualTaggedConstructionRule =
|
|
378
|
+
import { defineRule as defineRule6 } from "@oxlint/plugins";
|
|
379
|
+
var noManualTaggedConstructionRule = defineRule6({
|
|
231
380
|
meta: {
|
|
232
381
|
type: "problem",
|
|
233
382
|
docs: {
|
|
@@ -252,7 +401,7 @@ var noManualTaggedConstructionRule = defineRule4({
|
|
|
252
401
|
});
|
|
253
402
|
|
|
254
403
|
// vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
|
|
255
|
-
import { defineRule as
|
|
404
|
+
import { defineRule as defineRule7 } from "@oxlint/plugins";
|
|
256
405
|
var SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
|
|
257
406
|
var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
|
|
258
407
|
function isProjectLocalImport(source) {
|
|
@@ -263,7 +412,7 @@ function getImportedName(specifier) {
|
|
|
263
412
|
return specifier.imported.name;
|
|
264
413
|
return specifier.imported.value;
|
|
265
414
|
}
|
|
266
|
-
var noServiceConstructorImportsRule =
|
|
415
|
+
var noServiceConstructorImportsRule = defineRule7({
|
|
267
416
|
meta: {
|
|
268
417
|
type: "problem",
|
|
269
418
|
docs: {
|
|
@@ -297,9 +446,9 @@ var noServiceConstructorImportsRule = defineRule5({
|
|
|
297
446
|
});
|
|
298
447
|
|
|
299
448
|
// vendor/anti-slop/src/effect/rules/prefer-effect-match.ts
|
|
300
|
-
import { defineRule as
|
|
449
|
+
import { defineRule as defineRule8 } from "@oxlint/plugins";
|
|
301
450
|
var equalityOperators2 = new Set(["==", "===", "!=", "!=="]);
|
|
302
|
-
var preferEffectMatchRule =
|
|
451
|
+
var preferEffectMatchRule = defineRule8({
|
|
303
452
|
meta: {
|
|
304
453
|
type: "problem",
|
|
305
454
|
docs: {
|
|
@@ -365,7 +514,7 @@ function enablePluginRules(namespace, plugin) {
|
|
|
365
514
|
import { eslintCompatPlugin as eslintCompatPlugin3 } from "@oxlint/plugins";
|
|
366
515
|
|
|
367
516
|
// vendor/anti-slop/src/rules/no-array-filter-map.ts
|
|
368
|
-
import { defineRule as
|
|
517
|
+
import { defineRule as defineRule9 } from "@oxlint/plugins";
|
|
369
518
|
|
|
370
519
|
// vendor/anti-slop/src/shared/array-method.ts
|
|
371
520
|
function unwrapArrayExpression(node) {
|
|
@@ -440,7 +589,7 @@ function isKnownArrayExpression(sourceCode, node, visited = new Set) {
|
|
|
440
589
|
}
|
|
441
590
|
|
|
442
591
|
// vendor/anti-slop/src/rules/no-array-filter-map.ts
|
|
443
|
-
var noArrayFilterMapRule =
|
|
592
|
+
var noArrayFilterMapRule = defineRule9({
|
|
444
593
|
meta: {
|
|
445
594
|
type: "suggestion",
|
|
446
595
|
docs: { description: "Disallow adjacent array filter/map passes in favor of lazy iterator helpers or a single transformation." },
|
|
@@ -469,7 +618,7 @@ var noArrayFilterMapRule = defineRule7({
|
|
|
469
618
|
});
|
|
470
619
|
|
|
471
620
|
// vendor/anti-slop/src/rules/no-reduce-accumulator-copy.ts
|
|
472
|
-
import { defineRule as
|
|
621
|
+
import { defineRule as defineRule10 } from "@oxlint/plugins";
|
|
473
622
|
function enclosingReducer(node) {
|
|
474
623
|
let parent = node.parent;
|
|
475
624
|
while (parent !== null) {
|
|
@@ -519,7 +668,7 @@ function isGlobalCopyOwner(sourceCode, node, name) {
|
|
|
519
668
|
const variable = resolveArrayBinding(sourceCode, node);
|
|
520
669
|
return variable === null || variable.defs.length === 0;
|
|
521
670
|
}
|
|
522
|
-
var noReduceAccumulatorCopyRule =
|
|
671
|
+
var noReduceAccumulatorCopyRule = defineRule10({
|
|
523
672
|
meta: {
|
|
524
673
|
type: "problem",
|
|
525
674
|
docs: { description: "Disallow copying growing reducer accumulators with Object.assign, Array.from, or array copy methods." },
|
|
@@ -560,7 +709,7 @@ var noReduceAccumulatorCopyRule = defineRule8({
|
|
|
560
709
|
});
|
|
561
710
|
|
|
562
711
|
// vendor/anti-slop/src/rules/no-chained-type-assertions.ts
|
|
563
|
-
import { defineRule as
|
|
712
|
+
import { defineRule as defineRule11 } from "@oxlint/plugins";
|
|
564
713
|
function isTypeAssertionExpression(node) {
|
|
565
714
|
return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
|
|
566
715
|
}
|
|
@@ -595,7 +744,7 @@ function isForbiddenAssertionChain(node) {
|
|
|
595
744
|
}
|
|
596
745
|
return assertionCount > 1 && hasNonConstAssertion;
|
|
597
746
|
}
|
|
598
|
-
var noChainedTypeAssertionsRule =
|
|
747
|
+
var noChainedTypeAssertionsRule = defineRule11({
|
|
599
748
|
meta: {
|
|
600
749
|
type: "problem",
|
|
601
750
|
docs: {
|
|
@@ -619,7 +768,7 @@ var noChainedTypeAssertionsRule = defineRule9({
|
|
|
619
768
|
});
|
|
620
769
|
|
|
621
770
|
// vendor/anti-slop/src/rules/no-conditional-empty-object-spread.ts
|
|
622
|
-
import { defineRule as
|
|
771
|
+
import { defineRule as defineRule12 } from "@oxlint/plugins";
|
|
623
772
|
function unwrapParentheses(node) {
|
|
624
773
|
let current = node;
|
|
625
774
|
while (current.type === "ParenthesizedExpression") {
|
|
@@ -634,7 +783,7 @@ function isConditionalEmptyObjectSpread(node) {
|
|
|
634
783
|
const conditional = unwrapParentheses(node);
|
|
635
784
|
return conditional.type === "ConditionalExpression" && (isEmptyObjectExpression(conditional.consequent) || isEmptyObjectExpression(conditional.alternate));
|
|
636
785
|
}
|
|
637
|
-
var noConditionalEmptyObjectSpreadRule =
|
|
786
|
+
var noConditionalEmptyObjectSpreadRule = defineRule12({
|
|
638
787
|
meta: {
|
|
639
788
|
type: "suggestion",
|
|
640
789
|
docs: {
|
|
@@ -658,7 +807,7 @@ var noConditionalEmptyObjectSpreadRule = defineRule10({
|
|
|
658
807
|
});
|
|
659
808
|
|
|
660
809
|
// vendor/anti-slop/src/rules/no-known-value-widening.ts
|
|
661
|
-
import { defineRule as
|
|
810
|
+
import { defineRule as defineRule13 } from "@oxlint/plugins";
|
|
662
811
|
|
|
663
812
|
// vendor/anti-slop/src/shared/lexical-type-parameters.ts
|
|
664
813
|
function isNode(value) {
|
|
@@ -1187,18 +1336,6 @@ function functionParameterBindingName(parameter, sourceCode) {
|
|
|
1187
1336
|
return annotationStart === undefined ? sourceText : sourceText.slice(0, annotationStart - parameter.start).trimEnd();
|
|
1188
1337
|
}
|
|
1189
1338
|
|
|
1190
|
-
// vendor/anti-slop/src/shared/scope.ts
|
|
1191
|
-
function resolveVariable2(sourceCode, identifier) {
|
|
1192
|
-
let scope = sourceCode.getScope(identifier);
|
|
1193
|
-
while (scope !== null) {
|
|
1194
|
-
const variable = scope.set.get(identifier.name);
|
|
1195
|
-
if (variable !== undefined)
|
|
1196
|
-
return variable;
|
|
1197
|
-
scope = scope.upper;
|
|
1198
|
-
}
|
|
1199
|
-
return null;
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
1339
|
// vendor/anti-slop/src/rules/no-known-value-widening.ts
|
|
1203
1340
|
function unwrapExpression(expression) {
|
|
1204
1341
|
let current = expression;
|
|
@@ -1222,7 +1359,7 @@ function hasKnownEvidence(sourceCode, expression, visitedVariables = new Set) {
|
|
|
1222
1359
|
const unwrapped = unwrapExpression(expression);
|
|
1223
1360
|
if (unwrapped.type !== "Identifier")
|
|
1224
1361
|
return false;
|
|
1225
|
-
const variable =
|
|
1362
|
+
const variable = resolveVariable(sourceCode, unwrapped);
|
|
1226
1363
|
if (variable === null || visitedVariables.has(variable))
|
|
1227
1364
|
return false;
|
|
1228
1365
|
const declarator = variableDeclarator(variable);
|
|
@@ -1241,7 +1378,7 @@ function localFunctionForCall(sourceCode, callee) {
|
|
|
1241
1378
|
return unwrapped;
|
|
1242
1379
|
if (unwrapped.type !== "Identifier")
|
|
1243
1380
|
return null;
|
|
1244
|
-
const variable =
|
|
1381
|
+
const variable = resolveVariable(sourceCode, unwrapped);
|
|
1245
1382
|
if (variable === null || variable.defs.length !== 1)
|
|
1246
1383
|
return null;
|
|
1247
1384
|
const [definition] = variable.defs;
|
|
@@ -1294,7 +1431,7 @@ function hasKnownCallArgumentEvidence(sourceCode, expression, environment, visit
|
|
|
1294
1431
|
}
|
|
1295
1432
|
if (expression.type !== "Identifier")
|
|
1296
1433
|
return isKnownEvidenceExpression(expression);
|
|
1297
|
-
const variable =
|
|
1434
|
+
const variable = resolveVariable(sourceCode, expression);
|
|
1298
1435
|
if (variable === null || visitedVariables.has(variable))
|
|
1299
1436
|
return false;
|
|
1300
1437
|
const annotation = variableTypeAnnotation(sourceCode, variable);
|
|
@@ -1359,7 +1496,7 @@ function isDictionaryAccumulatorTarget(destination) {
|
|
|
1359
1496
|
function hasParentAssertion(node) {
|
|
1360
1497
|
return node.parent?.type === "TSAsExpression" || node.parent?.type === "TSTypeAssertion";
|
|
1361
1498
|
}
|
|
1362
|
-
var noKnownValueWideningRule =
|
|
1499
|
+
var noKnownValueWideningRule = defineRule13({
|
|
1363
1500
|
meta: {
|
|
1364
1501
|
type: "problem",
|
|
1365
1502
|
docs: {
|
|
@@ -1408,7 +1545,7 @@ var noKnownValueWideningRule = defineRule11({
|
|
|
1408
1545
|
AssignmentExpression(node) {
|
|
1409
1546
|
if (node.operator !== "=" || node.left.type !== "Identifier")
|
|
1410
1547
|
return;
|
|
1411
|
-
const variable =
|
|
1548
|
+
const variable = resolveVariable(context.sourceCode, node.left);
|
|
1412
1549
|
if (variable === null)
|
|
1413
1550
|
return;
|
|
1414
1551
|
const declarator = variableDeclarator(variable);
|
|
@@ -1472,7 +1609,7 @@ var noKnownValueWideningRule = defineRule11({
|
|
|
1472
1609
|
});
|
|
1473
1610
|
|
|
1474
1611
|
// vendor/anti-slop/src/rules/no-module-mocking.ts
|
|
1475
|
-
import { defineRule as
|
|
1612
|
+
import { defineRule as defineRule14 } from "@oxlint/plugins";
|
|
1476
1613
|
var moduleMockMethods = new Set(["doMock", "mock", "unstable_mockModule"]);
|
|
1477
1614
|
function importedName(node) {
|
|
1478
1615
|
if (node.type !== "ImportSpecifier")
|
|
@@ -1485,7 +1622,7 @@ function isTestFrameworkObject(sourceCode, expression) {
|
|
|
1485
1622
|
if ((expression.name === "vi" || expression.name === "jest") && sourceCode.isGlobalReference(expression)) {
|
|
1486
1623
|
return true;
|
|
1487
1624
|
}
|
|
1488
|
-
const variable =
|
|
1625
|
+
const variable = resolveVariable(sourceCode, expression);
|
|
1489
1626
|
if (variable === null || variable.defs.length === 0) {
|
|
1490
1627
|
return expression.name === "vi" || expression.name === "jest";
|
|
1491
1628
|
}
|
|
@@ -1507,7 +1644,7 @@ function moduleMockCall(sourceCode, callee) {
|
|
|
1507
1644
|
const method = callee.computed ? property.type === "Literal" && (property.value === "doMock" || property.value === "mock" || property.value === "unstable_mockModule") ? property.value : null : property.type === "Identifier" ? property.name : null;
|
|
1508
1645
|
return method !== null && moduleMockMethods.has(method);
|
|
1509
1646
|
}
|
|
1510
|
-
var noModuleMockingRule =
|
|
1647
|
+
var noModuleMockingRule = defineRule14({
|
|
1511
1648
|
meta: {
|
|
1512
1649
|
type: "problem",
|
|
1513
1650
|
docs: {
|
|
@@ -1531,8 +1668,8 @@ var noModuleMockingRule = defineRule12({
|
|
|
1531
1668
|
});
|
|
1532
1669
|
|
|
1533
1670
|
// vendor/anti-slop/src/rules/no-object-parameters.ts
|
|
1534
|
-
import { defineRule as
|
|
1535
|
-
var noObjectParametersRule =
|
|
1671
|
+
import { defineRule as defineRule15 } from "@oxlint/plugins";
|
|
1672
|
+
var noObjectParametersRule = defineRule15({
|
|
1536
1673
|
meta: {
|
|
1537
1674
|
type: "problem",
|
|
1538
1675
|
docs: {
|
|
@@ -1585,7 +1722,7 @@ var noObjectParametersRule = defineRule13({
|
|
|
1585
1722
|
});
|
|
1586
1723
|
|
|
1587
1724
|
// vendor/anti-slop/src/rules/no-reflect-apply.ts
|
|
1588
|
-
import { defineRule as
|
|
1725
|
+
import { defineRule as defineRule16 } from "@oxlint/plugins";
|
|
1589
1726
|
|
|
1590
1727
|
// vendor/anti-slop/src/shared/reflect-method.ts
|
|
1591
1728
|
function isGlobalReflect(sourceCode, expression) {
|
|
@@ -1593,7 +1730,7 @@ function isGlobalReflect(sourceCode, expression) {
|
|
|
1593
1730
|
return false;
|
|
1594
1731
|
if (sourceCode.isGlobalReference(expression))
|
|
1595
1732
|
return true;
|
|
1596
|
-
const variable =
|
|
1733
|
+
const variable = resolveVariable(sourceCode, expression);
|
|
1597
1734
|
return variable === null || variable.defs.length === 0;
|
|
1598
1735
|
}
|
|
1599
1736
|
function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
|
|
@@ -1606,7 +1743,7 @@ function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
|
|
|
1606
1743
|
}
|
|
1607
1744
|
|
|
1608
1745
|
// vendor/anti-slop/src/rules/no-reflect-apply.ts
|
|
1609
|
-
var noReflectApplyRule =
|
|
1746
|
+
var noReflectApplyRule = defineRule16({
|
|
1610
1747
|
meta: {
|
|
1611
1748
|
type: "problem",
|
|
1612
1749
|
docs: {
|
|
@@ -1630,8 +1767,8 @@ var noReflectApplyRule = defineRule14({
|
|
|
1630
1767
|
});
|
|
1631
1768
|
|
|
1632
1769
|
// vendor/anti-slop/src/rules/no-reflect-get.ts
|
|
1633
|
-
import { defineRule as
|
|
1634
|
-
var noReflectGetRule =
|
|
1770
|
+
import { defineRule as defineRule17 } from "@oxlint/plugins";
|
|
1771
|
+
var noReflectGetRule = defineRule17({
|
|
1635
1772
|
meta: {
|
|
1636
1773
|
type: "problem",
|
|
1637
1774
|
docs: {
|
|
@@ -1655,7 +1792,7 @@ var noReflectGetRule = defineRule15({
|
|
|
1655
1792
|
});
|
|
1656
1793
|
|
|
1657
1794
|
// vendor/anti-slop/src/rules/no-runtime-typeof.ts
|
|
1658
|
-
import { defineRule as
|
|
1795
|
+
import { defineRule as defineRule18 } from "@oxlint/plugins";
|
|
1659
1796
|
function isRuntimeFunction(node) {
|
|
1660
1797
|
return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
|
|
1661
1798
|
}
|
|
@@ -1678,7 +1815,7 @@ function isExistenceProbe(node) {
|
|
|
1678
1815
|
const other = parent.left === node ? parent.right : parent.left;
|
|
1679
1816
|
return other.type === "Literal" && other.value === "undefined";
|
|
1680
1817
|
}
|
|
1681
|
-
var noRuntimeTypeofRule =
|
|
1818
|
+
var noRuntimeTypeofRule = defineRule18({
|
|
1682
1819
|
meta: {
|
|
1683
1820
|
type: "problem",
|
|
1684
1821
|
docs: {
|
|
@@ -1712,7 +1849,7 @@ var noRuntimeTypeofRule = defineRule16({
|
|
|
1712
1849
|
});
|
|
1713
1850
|
|
|
1714
1851
|
// vendor/anti-slop/src/rules/no-shape-in-symbol-names.ts
|
|
1715
|
-
import { defineRule as
|
|
1852
|
+
import { defineRule as defineRule19 } from "@oxlint/plugins";
|
|
1716
1853
|
var FORBIDDEN_SYMBOL_NAME = "shape";
|
|
1717
1854
|
function containsForbiddenSymbolName(name) {
|
|
1718
1855
|
return name.toLowerCase().includes(FORBIDDEN_SYMBOL_NAME);
|
|
@@ -1723,7 +1860,7 @@ function isBorrowedMemberName(node) {
|
|
|
1723
1860
|
return false;
|
|
1724
1861
|
return parent.property === node && parent.computed === false;
|
|
1725
1862
|
}
|
|
1726
|
-
var noForbiddenTermInSymbolNamesRule =
|
|
1863
|
+
var noForbiddenTermInSymbolNamesRule = defineRule19({
|
|
1727
1864
|
meta: {
|
|
1728
1865
|
type: "problem",
|
|
1729
1866
|
docs: {
|
|
@@ -1752,12 +1889,12 @@ var noForbiddenTermInSymbolNamesRule = defineRule17({
|
|
|
1752
1889
|
});
|
|
1753
1890
|
|
|
1754
1891
|
// vendor/anti-slop/src/rules/no-unknown-parameters.ts
|
|
1755
|
-
import { defineRule as
|
|
1892
|
+
import { defineRule as defineRule20 } from "@oxlint/plugins";
|
|
1756
1893
|
function isTypePredicateSubject(owner, parameterName) {
|
|
1757
1894
|
const predicate = owner.returnType?.typeAnnotation;
|
|
1758
1895
|
return predicate?.type === "TSTypePredicate" && predicate.parameterName.type === "Identifier" && predicate.parameterName.name === parameterName;
|
|
1759
1896
|
}
|
|
1760
|
-
var noUnknownParametersRule =
|
|
1897
|
+
var noUnknownParametersRule = defineRule20({
|
|
1761
1898
|
meta: {
|
|
1762
1899
|
type: "problem",
|
|
1763
1900
|
docs: {
|
|
@@ -1801,8 +1938,8 @@ var noUnknownParametersRule = defineRule18({
|
|
|
1801
1938
|
});
|
|
1802
1939
|
|
|
1803
1940
|
// vendor/anti-slop/src/rules/no-unknown-returns.ts
|
|
1804
|
-
import { defineRule as
|
|
1805
|
-
var noUnknownReturnsRule =
|
|
1941
|
+
import { defineRule as defineRule21 } from "@oxlint/plugins";
|
|
1942
|
+
var noUnknownReturnsRule = defineRule21({
|
|
1806
1943
|
meta: {
|
|
1807
1944
|
type: "problem",
|
|
1808
1945
|
docs: {
|
|
@@ -1855,8 +1992,8 @@ var noUnknownReturnsRule = defineRule19({
|
|
|
1855
1992
|
});
|
|
1856
1993
|
|
|
1857
1994
|
// vendor/anti-slop/src/rules/no-unknown-type-aliases.ts
|
|
1858
|
-
import { defineRule as
|
|
1859
|
-
var noUnknownTypeAliasesRule =
|
|
1995
|
+
import { defineRule as defineRule22 } from "@oxlint/plugins";
|
|
1996
|
+
var noUnknownTypeAliasesRule = defineRule22({
|
|
1860
1997
|
meta: {
|
|
1861
1998
|
type: "problem",
|
|
1862
1999
|
docs: {
|
|
@@ -1894,7 +2031,7 @@ var noUnknownTypeAliasesRule = defineRule20({
|
|
|
1894
2031
|
});
|
|
1895
2032
|
|
|
1896
2033
|
// vendor/anti-slop/src/rules/no-unsafe-dictionary-type.ts
|
|
1897
|
-
import { defineRule as
|
|
2034
|
+
import { defineRule as defineRule23 } from "@oxlint/plugins";
|
|
1898
2035
|
var typeNodeKinds = new Set([
|
|
1899
2036
|
"JSDocNonNullableType",
|
|
1900
2037
|
"JSDocNullableType",
|
|
@@ -1981,7 +2118,7 @@ function shouldReportType(node, environment) {
|
|
|
1981
2118
|
}
|
|
1982
2119
|
return true;
|
|
1983
2120
|
}
|
|
1984
|
-
var noUnsafeDictionaryTypeRule =
|
|
2121
|
+
var noUnsafeDictionaryTypeRule = defineRule23({
|
|
1985
2122
|
meta: {
|
|
1986
2123
|
type: "problem",
|
|
1987
2124
|
docs: {
|
|
@@ -2023,7 +2160,7 @@ var noUnsafeDictionaryTypeRule = defineRule21({
|
|
|
2023
2160
|
});
|
|
2024
2161
|
|
|
2025
2162
|
// vendor/anti-slop/src/rules/no-widen-then-assert.ts
|
|
2026
|
-
import { defineRule as
|
|
2163
|
+
import { defineRule as defineRule24 } from "@oxlint/plugins";
|
|
2027
2164
|
var functionBoundaryTypes = new Set([
|
|
2028
2165
|
"ArrowFunctionExpression",
|
|
2029
2166
|
"FunctionDeclaration",
|
|
@@ -2219,7 +2356,7 @@ function assertionIsNarrower(sourceText, broadKind, evidence, assertedType) {
|
|
|
2219
2356
|
return isDefinitelyObjectType(assertedType);
|
|
2220
2357
|
return isDefinitelyNarrowerRecordType(assertedType);
|
|
2221
2358
|
}
|
|
2222
|
-
var noWidenThenAssertRule =
|
|
2359
|
+
var noWidenThenAssertRule = defineRule24({
|
|
2223
2360
|
meta: {
|
|
2224
2361
|
type: "problem",
|
|
2225
2362
|
docs: {
|
|
@@ -2781,7 +2918,7 @@ var requireReadableSpacingRule = {
|
|
|
2781
2918
|
};
|
|
2782
2919
|
|
|
2783
2920
|
// vendor/anti-slop/src/rules/require-safety-comment-for-type-assertion.ts
|
|
2784
|
-
import { defineRule as
|
|
2921
|
+
import { defineRule as defineRule25 } from "@oxlint/plugins";
|
|
2785
2922
|
var DEFAULT_SAFETY_MARKERS = ["SAFETY"];
|
|
2786
2923
|
var commentOwnerKinds = new Set([
|
|
2787
2924
|
"ExpressionStatement",
|
|
@@ -2824,7 +2961,7 @@ function hasSafetyComment(sourceCode, node, pattern) {
|
|
|
2824
2961
|
current = current.parent;
|
|
2825
2962
|
}
|
|
2826
2963
|
}
|
|
2827
|
-
var requireSafetyCommentForTypeAssertionRule =
|
|
2964
|
+
var requireSafetyCommentForTypeAssertionRule = defineRule25({
|
|
2828
2965
|
meta: {
|
|
2829
2966
|
type: "problem",
|
|
2830
2967
|
docs: {
|
|
@@ -2901,42 +3038,277 @@ var src_default = antiSlopPlugin;
|
|
|
2901
3038
|
// src/generic/index.ts
|
|
2902
3039
|
import { eslintCompatPlugin as eslintCompatPlugin4 } from "@oxlint/plugins";
|
|
2903
3040
|
|
|
2904
|
-
// src/generic/rules/
|
|
2905
|
-
import { defineRule as
|
|
2906
|
-
function
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
3041
|
+
// src/generic/rules/no-explicit-inferable-type-predicate.ts
|
|
3042
|
+
import { defineRule as defineRule26 } from "@oxlint/plugins";
|
|
3043
|
+
function isNamed(node, name) {
|
|
3044
|
+
return node.type === "Identifier" && node.name === name;
|
|
3045
|
+
}
|
|
3046
|
+
function isNullish(node) {
|
|
3047
|
+
return node.type === "Literal" && node.value === null || isNamed(node, "undefined");
|
|
3048
|
+
}
|
|
3049
|
+
function isNullishExclusion(node, name) {
|
|
3050
|
+
return node.type === "BinaryExpression" && (node.operator === "!==" || node.operator === "!=") && (isNamed(node.left, name) && isNullish(node.right) || isNullish(node.left) && isNamed(node.right, name));
|
|
3051
|
+
}
|
|
3052
|
+
function isTypeofCheck(node, name) {
|
|
3053
|
+
if (node.type !== "BinaryExpression" || node.operator !== "===" && node.operator !== "==") {
|
|
3054
|
+
return false;
|
|
3055
|
+
}
|
|
3056
|
+
const isTypeof = (side) => side.type === "UnaryExpression" && side.operator === "typeof" && isNamed(side.argument, name);
|
|
3057
|
+
const isLiteral = (side) => side.type === "Literal";
|
|
3058
|
+
return isTypeof(node.left) && isLiteral(node.right) || isLiteral(node.left) && isTypeof(node.right);
|
|
3059
|
+
}
|
|
3060
|
+
function isInferableCheck(node, name) {
|
|
3061
|
+
if (node.type === "LogicalExpression") {
|
|
3062
|
+
return node.operator === "&&" && isNullishExclusion(node.left, name) && isNullishExclusion(node.right, name);
|
|
3063
|
+
}
|
|
3064
|
+
return isNullishExclusion(node, name) || isTypeofCheck(node, name) || node.type === "BinaryExpression" && node.operator === "instanceof" && isNamed(node.left, name);
|
|
3065
|
+
}
|
|
3066
|
+
function returnedExpression(owner) {
|
|
3067
|
+
const body = owner.body;
|
|
3068
|
+
if (!body)
|
|
3069
|
+
return null;
|
|
3070
|
+
if (body.type !== "BlockStatement")
|
|
3071
|
+
return body;
|
|
3072
|
+
const [statement] = body.body;
|
|
3073
|
+
return body.body.length === 1 && statement?.type === "ReturnStatement" ? statement.argument : null;
|
|
3074
|
+
}
|
|
3075
|
+
function isPublicSignature(owner) {
|
|
3076
|
+
const parent = owner.parent;
|
|
3077
|
+
if (parent.type === "MethodDefinition" || parent.type === "ExportNamedDeclaration" || parent.type === "ExportDefaultDeclaration") {
|
|
3078
|
+
return true;
|
|
3079
|
+
}
|
|
3080
|
+
return parent.type === "VariableDeclarator" && parent.parent.parent?.type === "ExportNamedDeclaration";
|
|
3081
|
+
}
|
|
3082
|
+
var noExplicitInferableTypePredicateRule = defineRule26({
|
|
3083
|
+
meta: {
|
|
3084
|
+
type: "suggestion",
|
|
3085
|
+
docs: {
|
|
3086
|
+
description: "Disallow explicit type predicates on simple null, typeof, or instanceof checks that TypeScript 5.5+ infers."
|
|
3087
|
+
},
|
|
3088
|
+
messages: {
|
|
3089
|
+
removeTypePredicate: "TypeScript infers the type predicate for `{{parameter}}` from this check, while an explicit predicate is never checked against the body. Remove the annotation and rely on the inferred predicate."
|
|
2911
3090
|
}
|
|
2912
|
-
|
|
3091
|
+
},
|
|
3092
|
+
create(context) {
|
|
3093
|
+
const check = (owner) => {
|
|
3094
|
+
const annotation = owner.returnType?.typeAnnotation;
|
|
3095
|
+
if (annotation?.type !== "TSTypePredicate" || annotation.asserts || annotation.parameterName.type !== "Identifier" || isPublicSignature(owner)) {
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3098
|
+
const parameter = annotation.parameterName.name;
|
|
3099
|
+
const expression = returnedExpression(owner);
|
|
3100
|
+
if (!owner.params.some((param) => isNamed(param, parameter)) || !expression || !isInferableCheck(expression, parameter)) {
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
context.report({
|
|
3104
|
+
node: annotation,
|
|
3105
|
+
messageId: "removeTypePredicate",
|
|
3106
|
+
data: { parameter }
|
|
3107
|
+
});
|
|
3108
|
+
};
|
|
3109
|
+
return {
|
|
3110
|
+
ArrowFunctionExpression: check,
|
|
3111
|
+
FunctionDeclaration: check,
|
|
3112
|
+
FunctionExpression: check
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
});
|
|
3116
|
+
|
|
3117
|
+
// src/generic/rules/no-unchecked-query-selector-type.ts
|
|
3118
|
+
import { defineRule as defineRule27 } from "@oxlint/plugins";
|
|
3119
|
+
|
|
3120
|
+
// src/shared/json.ts
|
|
3121
|
+
function jsonObject(value) {
|
|
3122
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
// src/shared/lit.ts
|
|
3126
|
+
var renderRoots = new Set(["renderRoot", "shadowRoot"]);
|
|
3127
|
+
function litElementBaseClasses(settings) {
|
|
3128
|
+
const configured = jsonObject(settings.lit)?.elementBaseClasses;
|
|
3129
|
+
return new Set([
|
|
3130
|
+
"LitElement",
|
|
3131
|
+
...Array.isArray(configured) ? configured.map(String) : []
|
|
3132
|
+
]);
|
|
3133
|
+
}
|
|
3134
|
+
function isWrapperExpression(node) {
|
|
3135
|
+
return node?.type === "ChainExpression" || node?.type === "ParenthesizedExpression" || node?.type === "TSAsExpression" || node?.type === "TSNonNullExpression" || node?.type === "TSSatisfiesExpression" || node?.type === "TSTypeAssertion";
|
|
3136
|
+
}
|
|
3137
|
+
function unwrapExpression2(node) {
|
|
3138
|
+
let current = node;
|
|
3139
|
+
while (isWrapperExpression(current))
|
|
3140
|
+
current = current.expression;
|
|
3141
|
+
return current;
|
|
3142
|
+
}
|
|
3143
|
+
function extendsLitBase(node, bases) {
|
|
3144
|
+
if (node.type === "Identifier")
|
|
3145
|
+
return bases.has(node.name);
|
|
3146
|
+
return node.type === "CallExpression" && node.arguments.some((argument) => extendsLitBase(argument, bases));
|
|
3147
|
+
}
|
|
3148
|
+
function isLitClass(node, bases) {
|
|
3149
|
+
const hasCustomElementDecorator = node.decorators.some((decorator) => decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier" && decorator.expression.callee.name === "customElement");
|
|
3150
|
+
return hasCustomElementDecorator || node.superClass !== null && extendsLitBase(node.superClass, bases);
|
|
3151
|
+
}
|
|
3152
|
+
function classOfBody(body) {
|
|
3153
|
+
const owner = body.parent;
|
|
3154
|
+
return owner?.type === "ClassDeclaration" || owner?.type === "ClassExpression" ? owner : null;
|
|
3155
|
+
}
|
|
3156
|
+
function thisClass(node) {
|
|
3157
|
+
let current = node;
|
|
3158
|
+
while (current.parent) {
|
|
3159
|
+
const parent = current.parent;
|
|
3160
|
+
if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression") {
|
|
3161
|
+
const owner = parent.parent;
|
|
3162
|
+
return (owner?.type === "MethodDefinition" || owner?.type === "TSAbstractMethodDefinition") && !owner.static && owner.value === parent ? classOfBody(owner.parent) : null;
|
|
3163
|
+
}
|
|
3164
|
+
if ((parent.type === "PropertyDefinition" || parent.type === "AccessorProperty") && parent.value === current) {
|
|
3165
|
+
return parent.static ? null : classOfBody(parent.parent);
|
|
3166
|
+
}
|
|
3167
|
+
if (parent.type === "StaticBlock" || parent.type === "ClassBody" || parent.type === "Program") {
|
|
3168
|
+
return null;
|
|
3169
|
+
}
|
|
3170
|
+
current = parent;
|
|
2913
3171
|
}
|
|
2914
3172
|
return null;
|
|
2915
3173
|
}
|
|
2916
|
-
function
|
|
2917
|
-
|
|
2918
|
-
|
|
3174
|
+
function isStaticSelector(node) {
|
|
3175
|
+
return node?.type === "Literal" || node?.type === "TemplateLiteral" && node.expressions.length === 0;
|
|
3176
|
+
}
|
|
3177
|
+
function renderRootQuery(node, bases) {
|
|
3178
|
+
const method = staticMemberName(node.callee);
|
|
3179
|
+
if (method !== "querySelector" && method !== "querySelectorAll" || node.callee.type !== "MemberExpression" || !isStaticSelector(node.arguments[0])) {
|
|
2919
3180
|
return null;
|
|
2920
3181
|
}
|
|
2921
|
-
const
|
|
2922
|
-
const
|
|
2923
|
-
|
|
2924
|
-
|
|
3182
|
+
const receiver = unwrapExpression2(node.callee.object);
|
|
3183
|
+
const root = staticMemberName(receiver);
|
|
3184
|
+
if (!root || !renderRoots.has(root) || receiver.type !== "MemberExpression" || receiver.object.type !== "ThisExpression") {
|
|
3185
|
+
return null;
|
|
3186
|
+
}
|
|
3187
|
+
const owner = thisClass(receiver.object);
|
|
3188
|
+
return owner && isLitClass(owner, bases) ? { method, root } : null;
|
|
3189
|
+
}
|
|
3190
|
+
function isAssignedMethod(name) {
|
|
3191
|
+
return name === "assignedElements" || name === "assignedNodes";
|
|
3192
|
+
}
|
|
3193
|
+
function feedsAssignedCall(node) {
|
|
3194
|
+
let current = node;
|
|
3195
|
+
while (isWrapperExpression(current.parent))
|
|
3196
|
+
current = current.parent;
|
|
3197
|
+
const member = current.parent;
|
|
3198
|
+
return member?.type === "MemberExpression" && member.object === current && isAssignedMethod(staticMemberName(member));
|
|
3199
|
+
}
|
|
3200
|
+
function litQueryDecoratorCall(node, bases) {
|
|
3201
|
+
const method = staticMemberName(node.callee);
|
|
3202
|
+
if (!isAssignedMethod(method)) {
|
|
3203
|
+
return feedsAssignedCall(node) ? null : renderRootQuery(node, bases);
|
|
3204
|
+
}
|
|
3205
|
+
if (node.callee.type !== "MemberExpression")
|
|
3206
|
+
return null;
|
|
3207
|
+
const inner = unwrapExpression2(node.callee.object);
|
|
3208
|
+
if (inner.type !== "CallExpression")
|
|
3209
|
+
return null;
|
|
3210
|
+
const query = renderRootQuery(inner, bases);
|
|
3211
|
+
return query?.method === "querySelector" ? { method, root: query.root } : null;
|
|
3212
|
+
}
|
|
3213
|
+
function isLitRenderRootQuery(node, bases) {
|
|
3214
|
+
return renderRootQuery(node, bases) !== null;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
// src/generic/rules/no-unchecked-query-selector-type.ts
|
|
3218
|
+
var queryMethods = new Set(["closest", "querySelector", "querySelectorAll"]);
|
|
3219
|
+
var noUncheckedQuerySelectorTypeRule = defineRule27({
|
|
3220
|
+
meta: {
|
|
3221
|
+
type: "problem",
|
|
3222
|
+
docs: {
|
|
3223
|
+
description: "Disallow type arguments on `querySelector`, `querySelectorAll`, and `closest`, which cast the result without checking it."
|
|
3224
|
+
},
|
|
3225
|
+
messages: {
|
|
3226
|
+
checkQueryResult: "`{{method}}<T>` casts the result without checking it. Check it with `instanceof`, use a checked lookup helper, or query a tag name so TypeScript infers the element type."
|
|
3227
|
+
}
|
|
3228
|
+
},
|
|
3229
|
+
create(context) {
|
|
3230
|
+
const bases = litElementBaseClasses(context.settings);
|
|
3231
|
+
return {
|
|
3232
|
+
CallExpression(node) {
|
|
3233
|
+
const method = staticMemberName(node.callee);
|
|
3234
|
+
if (!node.typeArguments || !method || !queryMethods.has(method) || isLitRenderRootQuery(node, bases)) {
|
|
3235
|
+
return;
|
|
3236
|
+
}
|
|
3237
|
+
context.report({
|
|
3238
|
+
node: node.typeArguments,
|
|
3239
|
+
messageId: "checkQueryResult",
|
|
3240
|
+
data: { method }
|
|
3241
|
+
});
|
|
3242
|
+
}
|
|
3243
|
+
};
|
|
3244
|
+
}
|
|
3245
|
+
});
|
|
3246
|
+
|
|
3247
|
+
// src/generic/rules/no-untyped-custom-event.ts
|
|
3248
|
+
import { defineRule as defineRule28 } from "@oxlint/plugins";
|
|
3249
|
+
var noUntypedCustomEventRule = defineRule28({
|
|
3250
|
+
meta: {
|
|
3251
|
+
type: "problem",
|
|
3252
|
+
docs: {
|
|
3253
|
+
description: "Disallow `CustomEvent` type references without a detail type, which leave `detail` as `any`."
|
|
3254
|
+
},
|
|
3255
|
+
messages: {
|
|
3256
|
+
typeCustomEventDetail: "`CustomEvent` without a type argument leaves `detail` as `any`. Reuse the project's typed event helper if one fits, or add one, instead of a bare `CustomEvent`."
|
|
3257
|
+
}
|
|
3258
|
+
},
|
|
3259
|
+
create(context) {
|
|
3260
|
+
return {
|
|
3261
|
+
TSTypeReference(node) {
|
|
3262
|
+
if (node.typeName.type !== "Identifier" || node.typeName.name !== "CustomEvent" || node.typeArguments) {
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
context.report({ node, messageId: "typeCustomEventDetail" });
|
|
3266
|
+
}
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
});
|
|
3270
|
+
|
|
3271
|
+
// src/generic/rules/prefer-event-parameter-type.ts
|
|
3272
|
+
import { defineRule as defineRule29 } from "@oxlint/plugins";
|
|
3273
|
+
function staticMemberPath(node) {
|
|
3274
|
+
const path = [];
|
|
3275
|
+
let current = node.type === "ChainExpression" ? node.expression : node;
|
|
3276
|
+
while (current.type === "MemberExpression") {
|
|
3277
|
+
if (current.computed || current.property.type !== "Identifier") {
|
|
3278
|
+
return null;
|
|
3279
|
+
}
|
|
3280
|
+
path.unshift(current.property.name);
|
|
3281
|
+
current = current.object;
|
|
3282
|
+
}
|
|
3283
|
+
return current.type === "Identifier" && path.length > 0 ? { root: current.name, path } : null;
|
|
3284
|
+
}
|
|
3285
|
+
function isEventMemberPath(path) {
|
|
3286
|
+
const [first] = path;
|
|
3287
|
+
if (first === "detail")
|
|
3288
|
+
return true;
|
|
3289
|
+
return path.length === 1 && (first === "currentTarget" || first === "target");
|
|
3290
|
+
}
|
|
3291
|
+
function assertedEventParameter(node) {
|
|
3292
|
+
const member = staticMemberPath(node.expression);
|
|
3293
|
+
if (!member || !isEventMemberPath(member.path))
|
|
3294
|
+
return null;
|
|
3295
|
+
const owner = nearestEnclosingFunction(node);
|
|
3296
|
+
if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === member.root)) {
|
|
2925
3297
|
return null;
|
|
2926
3298
|
}
|
|
2927
3299
|
return {
|
|
2928
|
-
parameter:
|
|
2929
|
-
property
|
|
3300
|
+
parameter: member.root,
|
|
3301
|
+
property: member.path.join(".")
|
|
2930
3302
|
};
|
|
2931
3303
|
}
|
|
2932
|
-
var preferEventParameterTypeRule =
|
|
3304
|
+
var preferEventParameterTypeRule = defineRule29({
|
|
2933
3305
|
meta: {
|
|
2934
3306
|
type: "suggestion",
|
|
2935
3307
|
docs: {
|
|
2936
|
-
description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
|
|
3308
|
+
description: "Prefer typing event target and detail properties in the handler parameter instead of asserting them at use sites."
|
|
2937
3309
|
},
|
|
2938
3310
|
messages: {
|
|
2939
|
-
typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site."
|
|
3311
|
+
typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site. Reuse the project's typed event helper if one fits, or add one."
|
|
2940
3312
|
}
|
|
2941
3313
|
},
|
|
2942
3314
|
create(context) {
|
|
@@ -2957,9 +3329,69 @@ var preferEventParameterTypeRule = defineRule24({
|
|
|
2957
3329
|
}
|
|
2958
3330
|
});
|
|
2959
3331
|
|
|
3332
|
+
// src/generic/rules/prefer-lit-query-decorators.ts
|
|
3333
|
+
import { defineRule as defineRule30 } from "@oxlint/plugins";
|
|
3334
|
+
var messageIds = {
|
|
3335
|
+
assignedElements: "preferQueryAssignedElements",
|
|
3336
|
+
assignedNodes: "preferQueryAssignedNodes",
|
|
3337
|
+
querySelector: "preferQuery",
|
|
3338
|
+
querySelectorAll: "preferQueryAll"
|
|
3339
|
+
};
|
|
3340
|
+
var preferLitQueryDecoratorsRule = defineRule30({
|
|
3341
|
+
meta: {
|
|
3342
|
+
type: "suggestion",
|
|
3343
|
+
docs: {
|
|
3344
|
+
description: "Require Lit query decorators instead of static render-root queries in Lit elements."
|
|
3345
|
+
},
|
|
3346
|
+
schema: [
|
|
3347
|
+
{
|
|
3348
|
+
type: "object",
|
|
3349
|
+
properties: {
|
|
3350
|
+
querySelector: { type: "boolean" },
|
|
3351
|
+
querySelectorAll: { type: "boolean" },
|
|
3352
|
+
assignedElements: { type: "boolean" },
|
|
3353
|
+
assignedNodes: { type: "boolean" }
|
|
3354
|
+
},
|
|
3355
|
+
additionalProperties: false
|
|
3356
|
+
}
|
|
3357
|
+
],
|
|
3358
|
+
defaultOptions: [
|
|
3359
|
+
{
|
|
3360
|
+
querySelector: true,
|
|
3361
|
+
querySelectorAll: true,
|
|
3362
|
+
assignedElements: true,
|
|
3363
|
+
assignedNodes: true
|
|
3364
|
+
}
|
|
3365
|
+
],
|
|
3366
|
+
messages: {
|
|
3367
|
+
preferQuery: "Use the @query decorator instead of this.{{root}}.querySelector().",
|
|
3368
|
+
preferQueryAll: "Use the @queryAll decorator instead of this.{{root}}.querySelectorAll().",
|
|
3369
|
+
preferQueryAssignedElements: "Use the @queryAssignedElements decorator instead of this.{{root}}.querySelector().assignedElements().",
|
|
3370
|
+
preferQueryAssignedNodes: "Use the @queryAssignedNodes decorator instead of this.{{root}}.querySelector().assignedNodes()."
|
|
3371
|
+
}
|
|
3372
|
+
},
|
|
3373
|
+
create(context) {
|
|
3374
|
+
const bases = litElementBaseClasses(context.settings);
|
|
3375
|
+
const options = jsonObject(context.options[0]);
|
|
3376
|
+
const disabled = new Set(options ? Object.keys(options).filter((method) => options[method] === false) : []);
|
|
3377
|
+
return {
|
|
3378
|
+
CallExpression(node) {
|
|
3379
|
+
const query = litQueryDecoratorCall(node, bases);
|
|
3380
|
+
if (!query || disabled.has(query.method))
|
|
3381
|
+
return;
|
|
3382
|
+
context.report({
|
|
3383
|
+
node,
|
|
3384
|
+
messageId: messageIds[query.method],
|
|
3385
|
+
data: { root: query.root }
|
|
3386
|
+
});
|
|
3387
|
+
}
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
});
|
|
3391
|
+
|
|
2960
3392
|
// src/generic/rules/prefer-types-over-assertions.ts
|
|
2961
|
-
import { defineRule as
|
|
2962
|
-
var preferTypesOverAssertionsRule =
|
|
3393
|
+
import { defineRule as defineRule31 } from "@oxlint/plugins";
|
|
3394
|
+
var preferTypesOverAssertionsRule = defineRule31({
|
|
2963
3395
|
meta: {
|
|
2964
3396
|
type: "problem",
|
|
2965
3397
|
docs: {
|
|
@@ -2971,7 +3403,7 @@ var preferTypesOverAssertionsRule = defineRule25({
|
|
|
2971
3403
|
},
|
|
2972
3404
|
create(context) {
|
|
2973
3405
|
const checkAssertion = (node) => {
|
|
2974
|
-
if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const") {
|
|
3406
|
+
if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const" || assertedEventParameter(node)) {
|
|
2975
3407
|
return;
|
|
2976
3408
|
}
|
|
2977
3409
|
context.report({ node, messageId: "avoidAssertion" });
|
|
@@ -2987,7 +3419,11 @@ var preferTypesOverAssertionsRule = defineRule25({
|
|
|
2987
3419
|
var timmoPlugin = eslintCompatPlugin4({
|
|
2988
3420
|
meta: { name: "timmo" },
|
|
2989
3421
|
rules: {
|
|
3422
|
+
"no-explicit-inferable-type-predicate": noExplicitInferableTypePredicateRule,
|
|
3423
|
+
"no-unchecked-query-selector-type": noUncheckedQuerySelectorTypeRule,
|
|
3424
|
+
"no-untyped-custom-event": noUntypedCustomEventRule,
|
|
2990
3425
|
"prefer-event-parameter-type": preferEventParameterTypeRule,
|
|
3426
|
+
"prefer-lit-query-decorators": preferLitQueryDecoratorsRule,
|
|
2991
3427
|
"prefer-types-over-assertions": preferTypesOverAssertionsRule
|
|
2992
3428
|
}
|
|
2993
3429
|
});
|
|
@@ -3005,7 +3441,11 @@ var recommended = defineConfig({
|
|
|
3005
3441
|
rules: {
|
|
3006
3442
|
...enablePluginRules("anti-slop", src_default),
|
|
3007
3443
|
...enablePluginRules("timmo", generic_default),
|
|
3008
|
-
"anti-slop/require-safety-comment-for-type-assertion": "off"
|
|
3444
|
+
"anti-slop/require-safety-comment-for-type-assertion": "off",
|
|
3445
|
+
"typescript/no-non-null-assertion": "error",
|
|
3446
|
+
"typescript/no-unnecessary-type-arguments": "error",
|
|
3447
|
+
"typescript/no-unnecessary-type-assertion": "error",
|
|
3448
|
+
"typescript/no-unnecessary-type-parameters": "error"
|
|
3009
3449
|
}
|
|
3010
3450
|
});
|
|
3011
3451
|
var recommended_default = recommended;
|