@timmo001/oxlint-rules 0.2.1 → 0.3.1
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 +2 -2
- package/dist/cli.js +344 -147
- package/dist/configs/{effect.js → recommended-effect.js} +246 -49
- package/dist/configs/recommended.js +237 -40
- package/dist/upstream/anti-slop.js +230 -33
- package/package.json +6 -6
- package/skills/install-timmo-oxlint-rules/SKILL.md +3 -2
- package/vendor/anti-slop/src/index.ts +4 -0
- package/vendor/anti-slop/src/rules/no-array-filter-map.ts +28 -0
- package/vendor/anti-slop/src/rules/no-reduce-accumulator-copy.ts +109 -0
- package/vendor/anti-slop/src/shared/array-method.ts +94 -0
package/dist/cli.js
CHANGED
|
@@ -1,168 +1,211 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { resolve as resolve2 } from "node:path";
|
|
5
|
+
|
|
6
|
+
// node_modules/oxlint/dist/index.js
|
|
7
|
+
function defineConfig(config) {
|
|
8
|
+
return config;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// vendor/anti-slop/src/index.ts
|
|
4
12
|
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
5
13
|
|
|
6
|
-
// src/
|
|
14
|
+
// vendor/anti-slop/src/rules/no-array-filter-map.ts
|
|
7
15
|
import { defineRule } from "@oxlint/plugins";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
|
|
17
|
+
// vendor/anti-slop/src/shared/array-method.ts
|
|
18
|
+
function unwrapArrayExpression(node) {
|
|
19
|
+
while (node.type === "ParenthesizedExpression" || node.type === "ChainExpression" || node.type === "TSAsExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression" || node.type === "TSSatisfiesExpression") {
|
|
20
|
+
node = node.expression;
|
|
21
|
+
}
|
|
22
|
+
return node;
|
|
23
|
+
}
|
|
24
|
+
function resolveArrayBinding(sourceCode, node) {
|
|
25
|
+
node = unwrapArrayExpression(node);
|
|
26
|
+
if (node.type !== "Identifier")
|
|
27
|
+
return null;
|
|
28
|
+
let scope = sourceCode.getScope(node);
|
|
29
|
+
while (scope !== null) {
|
|
30
|
+
const variable = scope.set.get(node.name);
|
|
31
|
+
if (variable !== undefined)
|
|
13
32
|
return variable;
|
|
14
33
|
scope = scope.upper;
|
|
15
34
|
}
|
|
16
35
|
return null;
|
|
17
36
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
function arrayMethodTarget(node) {
|
|
38
|
+
node = unwrapArrayExpression(node);
|
|
39
|
+
if (node.type !== "MemberExpression")
|
|
40
|
+
return null;
|
|
41
|
+
const property = node.property;
|
|
42
|
+
if (!node.computed && property.type === "Identifier") {
|
|
43
|
+
return { name: property.name, object: node.object };
|
|
44
|
+
}
|
|
45
|
+
if (node.computed && property.type === "Literal" && typeof property.value === "string") {
|
|
46
|
+
return { name: property.value, object: node.object };
|
|
25
47
|
}
|
|
26
48
|
return null;
|
|
27
49
|
}
|
|
28
|
-
function
|
|
29
|
-
if (
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return variable?.defs.some((definition) => {
|
|
36
|
-
if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration" || definition.parent.source.value !== "effect") {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
if (namespace)
|
|
40
|
-
return definition.node.type === "ImportNamespaceSpecifier";
|
|
41
|
-
if (definition.node.type !== "ImportSpecifier")
|
|
42
|
-
return false;
|
|
43
|
-
const imported = definition.node.imported;
|
|
44
|
-
return (imported.type === "Identifier" ? imported.name : imported.value) === "Effect";
|
|
45
|
-
}) ?? false;
|
|
46
|
-
}
|
|
47
|
-
function isEffectMethod(sourceCode, node, method) {
|
|
48
|
-
if (staticMemberName(node) !== method || node.type !== "MemberExpression") {
|
|
49
|
-
return false;
|
|
50
|
+
function isArrayAnnotation(type) {
|
|
51
|
+
if (type.type === "TSArrayType" || type.type === "TSTupleType")
|
|
52
|
+
return true;
|
|
53
|
+
if (type.type === "TSParenthesizedType")
|
|
54
|
+
return isArrayAnnotation(type.typeAnnotation);
|
|
55
|
+
if (type.type === "TSTypeOperator" && type.operator === "readonly") {
|
|
56
|
+
return isArrayAnnotation(type.typeAnnotation);
|
|
50
57
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
return type.type === "TSTypeReference" && type.typeName.type === "Identifier" && (type.typeName.name === "Array" || type.typeName.name === "ReadonlyArray");
|
|
59
|
+
}
|
|
60
|
+
function isKnownArrayExpression(sourceCode, node, visited = new Set) {
|
|
61
|
+
node = unwrapArrayExpression(node);
|
|
62
|
+
if (node.type === "ArrayExpression")
|
|
63
|
+
return true;
|
|
64
|
+
if (node.type === "CallExpression") {
|
|
65
|
+
const method = arrayMethodTarget(node.callee);
|
|
66
|
+
return method !== null && ["map", "filter", "flatMap", "slice", "concat", "toSorted", "toReversed", "toSpliced"].includes(method.name) && isKnownArrayExpression(sourceCode, method.object, visited);
|
|
54
67
|
}
|
|
55
|
-
if (
|
|
68
|
+
if (node.type !== "Identifier")
|
|
56
69
|
return false;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
function isDirectArgument(owner, call) {
|
|
61
|
-
return call.arguments.some((argument) => argument === owner);
|
|
62
|
-
}
|
|
63
|
-
function isRecognisedEffectGenerator(sourceCode, owner) {
|
|
64
|
-
const parent = owner.parent;
|
|
65
|
-
if (parent.type !== "CallExpression" || !isDirectArgument(owner, parent)) {
|
|
70
|
+
const variable = resolveArrayBinding(sourceCode, node);
|
|
71
|
+
if (variable === null || visited.has(variable))
|
|
66
72
|
return false;
|
|
73
|
+
visited.add(variable);
|
|
74
|
+
if (variable.references.some((reference) => reference.isWrite() && !reference.init))
|
|
75
|
+
return false;
|
|
76
|
+
for (const identifier of variable.identifiers) {
|
|
77
|
+
const annotation = identifier.typeAnnotation?.typeAnnotation;
|
|
78
|
+
if (annotation !== undefined)
|
|
79
|
+
return isArrayAnnotation(annotation);
|
|
67
80
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
for (const definition of variable.defs) {
|
|
82
|
+
if (definition.type === "Variable" && definition.node.type === "VariableDeclarator" && definition.node.id.type === "Identifier" && definition.node.init !== null && definition.node.parent.type === "VariableDeclaration" && definition.node.parent.kind === "const") {
|
|
83
|
+
return isKnownArrayExpression(sourceCode, definition.node.init, visited);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
72
87
|
}
|
|
73
|
-
|
|
88
|
+
|
|
89
|
+
// vendor/anti-slop/src/rules/no-array-filter-map.ts
|
|
90
|
+
var noArrayFilterMapRule = defineRule({
|
|
74
91
|
meta: {
|
|
75
|
-
type: "
|
|
76
|
-
docs: {
|
|
77
|
-
description: "Disallow synchronous try/catch owned by recognised Effect generator callbacks."
|
|
78
|
-
},
|
|
92
|
+
type: "suggestion",
|
|
93
|
+
docs: { description: "Disallow adjacent array filter/map passes in favor of lazy iterator helpers or a single transformation." },
|
|
79
94
|
messages: {
|
|
80
|
-
|
|
95
|
+
arrayFilterMap: "Avoid consecutive array `{{first}}` and `{{second}}` passes. Prefer `.values().{{first}}(...).{{second}}(...).toArray()` where iterator helpers are supported, or a single `flatMap`/mutating reducer. Preserve callback ordering, indexes, and filtering semantics."
|
|
81
96
|
}
|
|
82
97
|
},
|
|
83
|
-
|
|
98
|
+
createOnce(context) {
|
|
84
99
|
return {
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
CallExpression(node) {
|
|
101
|
+
const outer = arrayMethodTarget(node.callee);
|
|
102
|
+
if (outer === null || outer.name !== "map" && outer.name !== "filter")
|
|
87
103
|
return;
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
104
|
+
const innerCall = unwrapArrayExpression(outer.object);
|
|
105
|
+
if (innerCall.type !== "CallExpression")
|
|
90
106
|
return;
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
const inner = arrayMethodTarget(innerCall.callee);
|
|
108
|
+
if (inner === null || inner.name !== (outer.name === "map" ? "filter" : "map"))
|
|
109
|
+
return;
|
|
110
|
+
if (!isKnownArrayExpression(context.sourceCode, inner.object))
|
|
111
|
+
return;
|
|
112
|
+
context.report({ node, messageId: "arrayFilterMap", data: { first: inner.name, second: outer.name } });
|
|
93
113
|
}
|
|
94
114
|
};
|
|
95
115
|
}
|
|
96
116
|
});
|
|
97
117
|
|
|
98
|
-
// src/
|
|
99
|
-
var timmoEffectPlugin = eslintCompatPlugin({
|
|
100
|
-
meta: { name: "timmo-effect" },
|
|
101
|
-
rules: {
|
|
102
|
-
"no-try-catch-in-effect-generators": noTryCatchInEffectGeneratorsRule
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
var effect_default = timmoEffectPlugin;
|
|
106
|
-
|
|
107
|
-
// vendor/anti-slop/src/effect/index.ts
|
|
108
|
-
import { eslintCompatPlugin as eslintCompatPlugin2 } from "@oxlint/plugins";
|
|
109
|
-
|
|
110
|
-
// vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
|
|
118
|
+
// vendor/anti-slop/src/rules/no-reduce-accumulator-copy.ts
|
|
111
119
|
import { defineRule as defineRule2 } from "@oxlint/plugins";
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
function enclosingReducer(node) {
|
|
121
|
+
let parent = node.parent;
|
|
122
|
+
while (parent !== null) {
|
|
123
|
+
if (parent.type === "FunctionDeclaration")
|
|
124
|
+
return null;
|
|
125
|
+
if (parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression") {
|
|
126
|
+
const callback = parent;
|
|
127
|
+
let owner = callback.parent;
|
|
128
|
+
while (owner !== null && unwrapArrayExpression(owner) === callback)
|
|
129
|
+
owner = owner.parent;
|
|
130
|
+
if (owner?.type !== "CallExpression")
|
|
131
|
+
return null;
|
|
132
|
+
const method = arrayMethodTarget(owner.callee);
|
|
133
|
+
const firstArgument = owner.arguments[0];
|
|
134
|
+
if (method === null || method.name !== "reduce" && method.name !== "reduceRight" || owner.arguments.length > 2 || firstArgument === undefined || unwrapArrayExpression(firstArgument) !== callback)
|
|
135
|
+
return null;
|
|
136
|
+
const firstParameter = callback.params[0];
|
|
137
|
+
const accumulator = firstParameter?.type === "AssignmentPattern" ? firstParameter.left : firstParameter;
|
|
138
|
+
if (accumulator?.type !== "Identifier")
|
|
139
|
+
return null;
|
|
140
|
+
return { callback, accumulator, initialValue: owner.arguments[1] };
|
|
141
|
+
}
|
|
142
|
+
parent = parent.parent;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
116
145
|
}
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
146
|
+
function referencesAccumulator(sourceCode, node, accumulator, visited = new Set) {
|
|
147
|
+
const variable = resolveArrayBinding(sourceCode, node);
|
|
148
|
+
if (variable === null || visited.has(variable))
|
|
149
|
+
return false;
|
|
150
|
+
if (variable === accumulator)
|
|
151
|
+
return true;
|
|
152
|
+
visited.add(variable);
|
|
153
|
+
if (variable.references.some((reference) => reference.isWrite() && !reference.init))
|
|
154
|
+
return false;
|
|
155
|
+
for (const definition of variable.defs) {
|
|
156
|
+
if (definition.type === "Variable" && definition.node.type === "VariableDeclarator" && definition.node.id.type === "Identifier" && definition.node.init !== null && definition.node.parent.type === "VariableDeclaration" && definition.node.parent.kind === "const") {
|
|
157
|
+
return referencesAccumulator(sourceCode, definition.node.init, accumulator, visited);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
function isGlobalCopyOwner(sourceCode, node, name) {
|
|
163
|
+
node = unwrapArrayExpression(node);
|
|
164
|
+
if (node.type !== "Identifier" || node.name !== name)
|
|
165
|
+
return false;
|
|
166
|
+
const variable = resolveArrayBinding(sourceCode, node);
|
|
167
|
+
return variable === null || variable.defs.length === 0;
|
|
121
168
|
}
|
|
122
|
-
var
|
|
169
|
+
var noReduceAccumulatorCopyRule = defineRule2({
|
|
123
170
|
meta: {
|
|
124
171
|
type: "problem",
|
|
125
|
-
docs: {
|
|
126
|
-
description: "Disallow project-local make<CapabilityName> imports outside test and spec files."
|
|
127
|
-
},
|
|
172
|
+
docs: { description: "Disallow copying growing reducer accumulators with Object.assign, Array.from, or array copy methods." },
|
|
128
173
|
messages: {
|
|
129
|
-
|
|
174
|
+
accumulatorCopy: "Do not copy the reducer accumulator on every iteration; growing copies can cause quadratic work. Mutate a fresh, locally owned accumulator and return it, or use an iterator pipeline/flatMap."
|
|
130
175
|
}
|
|
131
176
|
},
|
|
132
|
-
|
|
133
|
-
const isTestFile = TEST_FILE.test(context.filename.replaceAll("\\", "/"));
|
|
177
|
+
createOnce(context) {
|
|
134
178
|
return {
|
|
135
|
-
|
|
136
|
-
|
|
179
|
+
CallExpression(node) {
|
|
180
|
+
const method = arrayMethodTarget(node.callee);
|
|
181
|
+
if (method === null)
|
|
137
182
|
return;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
183
|
+
const reducer = enclosingReducer(node);
|
|
184
|
+
if (reducer === null)
|
|
185
|
+
return;
|
|
186
|
+
const accumulator = context.sourceCode.getDeclaredVariables(reducer.callback).find((variable) => variable.identifiers.some((identifier) => identifier.start === reducer.accumulator.start));
|
|
187
|
+
if (accumulator === undefined)
|
|
188
|
+
return;
|
|
189
|
+
const isAccumulator = (expression) => referencesAccumulator(context.sourceCode, expression, accumulator);
|
|
190
|
+
let copiesAccumulator = false;
|
|
191
|
+
if (method.name === "assign" && isGlobalCopyOwner(context.sourceCode, method.object, "Object")) {
|
|
192
|
+
const target = node.arguments[0];
|
|
193
|
+
copiesAccumulator = target !== undefined && unwrapArrayExpression(target).type === "ObjectExpression" && node.arguments.slice(1).some(isAccumulator);
|
|
194
|
+
} else if (method.name === "from" && isGlobalCopyOwner(context.sourceCode, method.object, "Array")) {
|
|
195
|
+
const source = node.arguments[0];
|
|
196
|
+
copiesAccumulator = source !== undefined && isAccumulator(source);
|
|
197
|
+
} else if (["concat", "slice", "toSpliced", "toSorted", "toReversed", "with"].includes(method.name)) {
|
|
198
|
+
const initialValue = reducer.initialValue;
|
|
199
|
+
const arrayAccumulator = initialValue !== undefined && isKnownArrayExpression(context.sourceCode, initialValue);
|
|
200
|
+
copiesAccumulator = arrayAccumulator && isAccumulator(method.object);
|
|
149
201
|
}
|
|
202
|
+
if (copiesAccumulator)
|
|
203
|
+
context.report({ node, messageId: "accumulatorCopy" });
|
|
150
204
|
}
|
|
151
205
|
};
|
|
152
206
|
}
|
|
153
207
|
});
|
|
154
208
|
|
|
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
209
|
// vendor/anti-slop/src/rules/no-chained-type-assertions.ts
|
|
167
210
|
import { defineRule as defineRule3 } from "@oxlint/plugins";
|
|
168
211
|
function isTypeAssertionExpression(node) {
|
|
@@ -665,9 +708,9 @@ function classifyWideningTarget(type, environment) {
|
|
|
665
708
|
if (alias === null)
|
|
666
709
|
return null;
|
|
667
710
|
if ((alias.typeParameters?.params.length ?? 0) > 0) {
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
return
|
|
711
|
+
const substitutions = aliasSubstitution(alias, unwrapped, new Map);
|
|
712
|
+
const resolved = substitutions === null ? null : classifyAliasBroadTarget(alias.typeAnnotation, environment, substitutions, new Set([name]));
|
|
713
|
+
return resolved?.kind === "open dictionary" ? { kind: "generic container" } : null;
|
|
671
714
|
}
|
|
672
715
|
const substitutions = aliasSubstitution(alias, unwrapped, new Map);
|
|
673
716
|
if (substitutions === null)
|
|
@@ -799,7 +842,7 @@ function unwrapExpression(expression) {
|
|
|
799
842
|
}
|
|
800
843
|
return current;
|
|
801
844
|
}
|
|
802
|
-
function
|
|
845
|
+
function resolveVariable(sourceCode, identifier) {
|
|
803
846
|
let scope = sourceCode.getScope(identifier);
|
|
804
847
|
while (scope !== null) {
|
|
805
848
|
const variable = scope.set.get(identifier.name);
|
|
@@ -824,7 +867,7 @@ function hasKnownEvidence(sourceCode, expression, visitedVariables = new Set) {
|
|
|
824
867
|
const unwrapped = unwrapExpression(expression);
|
|
825
868
|
if (unwrapped.type !== "Identifier")
|
|
826
869
|
return false;
|
|
827
|
-
const variable =
|
|
870
|
+
const variable = resolveVariable(sourceCode, unwrapped);
|
|
828
871
|
if (variable === null || visitedVariables.has(variable))
|
|
829
872
|
return false;
|
|
830
873
|
const declarator = variableDeclarator(variable);
|
|
@@ -843,7 +886,7 @@ function localFunctionForCall(sourceCode, callee) {
|
|
|
843
886
|
return unwrapped;
|
|
844
887
|
if (unwrapped.type !== "Identifier")
|
|
845
888
|
return null;
|
|
846
|
-
const variable =
|
|
889
|
+
const variable = resolveVariable(sourceCode, unwrapped);
|
|
847
890
|
if (variable === null || variable.defs.length !== 1)
|
|
848
891
|
return null;
|
|
849
892
|
const [definition] = variable.defs;
|
|
@@ -896,7 +939,7 @@ function hasKnownCallArgumentEvidence(sourceCode, expression, environment, visit
|
|
|
896
939
|
}
|
|
897
940
|
if (expression.type !== "Identifier")
|
|
898
941
|
return isKnownEvidenceExpression(expression);
|
|
899
|
-
const variable =
|
|
942
|
+
const variable = resolveVariable(sourceCode, expression);
|
|
900
943
|
if (variable === null || visitedVariables.has(variable))
|
|
901
944
|
return false;
|
|
902
945
|
const annotation = variableTypeAnnotation(sourceCode, variable);
|
|
@@ -1010,7 +1053,7 @@ var noKnownValueWideningRule = defineRule5({
|
|
|
1010
1053
|
AssignmentExpression(node) {
|
|
1011
1054
|
if (node.operator !== "=" || node.left.type !== "Identifier")
|
|
1012
1055
|
return;
|
|
1013
|
-
const variable =
|
|
1056
|
+
const variable = resolveVariable(context.sourceCode, node.left);
|
|
1014
1057
|
if (variable === null)
|
|
1015
1058
|
return;
|
|
1016
1059
|
const declarator = variableDeclarator(variable);
|
|
@@ -1076,7 +1119,7 @@ var noKnownValueWideningRule = defineRule5({
|
|
|
1076
1119
|
// vendor/anti-slop/src/rules/no-module-mocking.ts
|
|
1077
1120
|
import { defineRule as defineRule6 } from "@oxlint/plugins";
|
|
1078
1121
|
var moduleMockMethods = new Set(["doMock", "mock", "unstable_mockModule"]);
|
|
1079
|
-
function
|
|
1122
|
+
function resolveVariable2(sourceCode, identifier) {
|
|
1080
1123
|
let scope = sourceCode.getScope(identifier);
|
|
1081
1124
|
while (scope !== null) {
|
|
1082
1125
|
const variable = scope.set.get(identifier.name);
|
|
@@ -1097,7 +1140,7 @@ function isTestFrameworkObject(sourceCode, expression) {
|
|
|
1097
1140
|
if ((expression.name === "vi" || expression.name === "jest") && sourceCode.isGlobalReference(expression)) {
|
|
1098
1141
|
return true;
|
|
1099
1142
|
}
|
|
1100
|
-
const variable =
|
|
1143
|
+
const variable = resolveVariable2(sourceCode, expression);
|
|
1101
1144
|
if (variable === null || variable.defs.length === 0) {
|
|
1102
1145
|
return expression.name === "vi" || expression.name === "jest";
|
|
1103
1146
|
}
|
|
@@ -1200,7 +1243,7 @@ var noObjectParametersRule = defineRule7({
|
|
|
1200
1243
|
import { defineRule as defineRule8 } from "@oxlint/plugins";
|
|
1201
1244
|
|
|
1202
1245
|
// vendor/anti-slop/src/shared/reflect-method.ts
|
|
1203
|
-
function
|
|
1246
|
+
function resolveVariable3(sourceCode, identifier) {
|
|
1204
1247
|
let scope = sourceCode.getScope(identifier);
|
|
1205
1248
|
while (scope !== null) {
|
|
1206
1249
|
const variable = scope.set.get(identifier.name);
|
|
@@ -1215,7 +1258,7 @@ function isGlobalReflect(sourceCode, expression) {
|
|
|
1215
1258
|
return false;
|
|
1216
1259
|
if (sourceCode.isGlobalReference(expression))
|
|
1217
1260
|
return true;
|
|
1218
|
-
const variable =
|
|
1261
|
+
const variable = resolveVariable3(sourceCode, expression);
|
|
1219
1262
|
return variable === null || variable.defs.length === 0;
|
|
1220
1263
|
}
|
|
1221
1264
|
function isGlobalReflectMethodCall(sourceCode, callee, methodName) {
|
|
@@ -1974,9 +2017,11 @@ var requireSafetyCommentForTypeAssertionRule = defineRule17({
|
|
|
1974
2017
|
});
|
|
1975
2018
|
|
|
1976
2019
|
// vendor/anti-slop/src/index.ts
|
|
1977
|
-
var antiSlopPlugin =
|
|
2020
|
+
var antiSlopPlugin = eslintCompatPlugin({
|
|
1978
2021
|
meta: { name: "anti-slop" },
|
|
1979
2022
|
rules: {
|
|
2023
|
+
"no-array-filter-map": noArrayFilterMapRule,
|
|
2024
|
+
"no-reduce-accumulator-copy": noReduceAccumulatorCopyRule,
|
|
1980
2025
|
"no-chained-type-assertions": noChainedTypeAssertionsRule,
|
|
1981
2026
|
"no-conditional-empty-object-spread": noConditionalEmptyObjectSpreadRule,
|
|
1982
2027
|
"no-known-value-widening": noKnownValueWideningRule,
|
|
@@ -1996,11 +2041,11 @@ var antiSlopPlugin = eslintCompatPlugin3({
|
|
|
1996
2041
|
});
|
|
1997
2042
|
var src_default = antiSlopPlugin;
|
|
1998
2043
|
// src/generic/index.ts
|
|
1999
|
-
import { eslintCompatPlugin as
|
|
2044
|
+
import { eslintCompatPlugin as eslintCompatPlugin2 } from "@oxlint/plugins";
|
|
2000
2045
|
|
|
2001
2046
|
// src/generic/rules/prefer-event-parameter-type.ts
|
|
2002
2047
|
import { defineRule as defineRule18 } from "@oxlint/plugins";
|
|
2003
|
-
function
|
|
2048
|
+
function nearestEnclosingFunction(node) {
|
|
2004
2049
|
let current = node.parent;
|
|
2005
2050
|
while (current) {
|
|
2006
2051
|
if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
|
|
@@ -2017,7 +2062,7 @@ function assertedEventParameter(node) {
|
|
|
2017
2062
|
}
|
|
2018
2063
|
const parameterName = expression.object.name;
|
|
2019
2064
|
const property = expression.property.name;
|
|
2020
|
-
const owner =
|
|
2065
|
+
const owner = nearestEnclosingFunction(node);
|
|
2021
2066
|
if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === parameterName)) {
|
|
2022
2067
|
return null;
|
|
2023
2068
|
}
|
|
@@ -2055,7 +2100,7 @@ var preferEventParameterTypeRule = defineRule18({
|
|
|
2055
2100
|
});
|
|
2056
2101
|
|
|
2057
2102
|
// src/generic/index.ts
|
|
2058
|
-
var timmoPlugin =
|
|
2103
|
+
var timmoPlugin = eslintCompatPlugin2({
|
|
2059
2104
|
meta: { name: "timmo" },
|
|
2060
2105
|
rules: {
|
|
2061
2106
|
"prefer-event-parameter-type": preferEventParameterTypeRule
|
|
@@ -2063,11 +2108,6 @@ var timmoPlugin = eslintCompatPlugin4({
|
|
|
2063
2108
|
});
|
|
2064
2109
|
var generic_default = timmoPlugin;
|
|
2065
2110
|
|
|
2066
|
-
// node_modules/oxlint/dist/index.js
|
|
2067
|
-
function defineConfig(config) {
|
|
2068
|
-
return config;
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
2111
|
// src/configs/enable-plugin-rules.ts
|
|
2072
2112
|
function enablePluginRules(namespace, plugin) {
|
|
2073
2113
|
return Object.fromEntries(Object.keys(plugin.rules).sort().map((rule) => [`${namespace}/${rule}`, "error"]));
|
|
@@ -2089,8 +2129,168 @@ var recommended = defineConfig({
|
|
|
2089
2129
|
});
|
|
2090
2130
|
var recommended_default = recommended;
|
|
2091
2131
|
|
|
2092
|
-
// src/
|
|
2093
|
-
|
|
2132
|
+
// src/effect/index.ts
|
|
2133
|
+
import { eslintCompatPlugin as eslintCompatPlugin3 } from "@oxlint/plugins";
|
|
2134
|
+
|
|
2135
|
+
// src/effect/rules/no-try-catch-in-effect-generators.ts
|
|
2136
|
+
import { defineRule as defineRule19 } from "@oxlint/plugins";
|
|
2137
|
+
function resolveVariable4(sourceCode, identifier) {
|
|
2138
|
+
let scope = sourceCode.getScope(identifier);
|
|
2139
|
+
while (scope) {
|
|
2140
|
+
const variable = scope.set.get(identifier.name);
|
|
2141
|
+
if (variable)
|
|
2142
|
+
return variable;
|
|
2143
|
+
scope = scope.upper;
|
|
2144
|
+
}
|
|
2145
|
+
return null;
|
|
2146
|
+
}
|
|
2147
|
+
function nearestEnclosingFunction2(node) {
|
|
2148
|
+
let current = node.parent;
|
|
2149
|
+
while (current) {
|
|
2150
|
+
if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
|
|
2151
|
+
return current;
|
|
2152
|
+
}
|
|
2153
|
+
current = current.parent;
|
|
2154
|
+
}
|
|
2155
|
+
return null;
|
|
2156
|
+
}
|
|
2157
|
+
function staticMemberName(node) {
|
|
2158
|
+
if (node.type !== "MemberExpression" || node.computed)
|
|
2159
|
+
return null;
|
|
2160
|
+
return node.property.type === "Identifier" ? node.property.name : null;
|
|
2161
|
+
}
|
|
2162
|
+
function isEffectImport(sourceCode, identifier, namespace) {
|
|
2163
|
+
const variable = resolveVariable4(sourceCode, identifier);
|
|
2164
|
+
return variable?.defs.some((definition) => {
|
|
2165
|
+
if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration" || definition.parent.source.value !== "effect") {
|
|
2166
|
+
return false;
|
|
2167
|
+
}
|
|
2168
|
+
if (namespace)
|
|
2169
|
+
return definition.node.type === "ImportNamespaceSpecifier";
|
|
2170
|
+
if (definition.node.type !== "ImportSpecifier")
|
|
2171
|
+
return false;
|
|
2172
|
+
const imported = definition.node.imported;
|
|
2173
|
+
return (imported.type === "Identifier" ? imported.name : imported.value) === "Effect";
|
|
2174
|
+
}) ?? false;
|
|
2175
|
+
}
|
|
2176
|
+
function isEffectMethod(sourceCode, node, method) {
|
|
2177
|
+
if (staticMemberName(node) !== method || node.type !== "MemberExpression") {
|
|
2178
|
+
return false;
|
|
2179
|
+
}
|
|
2180
|
+
const object = node.object;
|
|
2181
|
+
if (object.type === "Identifier") {
|
|
2182
|
+
return isEffectImport(sourceCode, object, false);
|
|
2183
|
+
}
|
|
2184
|
+
if (staticMemberName(object) !== "Effect" || object.type !== "MemberExpression" || object.object.type !== "Identifier") {
|
|
2185
|
+
return false;
|
|
2186
|
+
}
|
|
2187
|
+
return isEffectImport(sourceCode, object.object, true);
|
|
2188
|
+
}
|
|
2189
|
+
function isDirectArgument(owner, call) {
|
|
2190
|
+
return call.arguments.some((argument) => argument === owner);
|
|
2191
|
+
}
|
|
2192
|
+
function isRecognisedEffectGenerator(sourceCode, owner) {
|
|
2193
|
+
const parent = owner.parent;
|
|
2194
|
+
if (parent.type !== "CallExpression" || !isDirectArgument(owner, parent)) {
|
|
2195
|
+
return false;
|
|
2196
|
+
}
|
|
2197
|
+
if (isEffectMethod(sourceCode, parent.callee, "gen"))
|
|
2198
|
+
return true;
|
|
2199
|
+
const factoryCall = parent.callee;
|
|
2200
|
+
return factoryCall.type === "CallExpression" && isEffectMethod(sourceCode, factoryCall.callee, "fn");
|
|
2201
|
+
}
|
|
2202
|
+
var noTryCatchInEffectGeneratorsRule = defineRule19({
|
|
2203
|
+
meta: {
|
|
2204
|
+
type: "problem",
|
|
2205
|
+
docs: {
|
|
2206
|
+
description: "Disallow synchronous try/catch owned by recognised Effect generator callbacks."
|
|
2207
|
+
},
|
|
2208
|
+
messages: {
|
|
2209
|
+
useEffectErrorChannel: "Keep expected failures in the Effect error channel. Use Effect.try for synchronous throwing work, Effect.tryPromise for asynchronous throwing work, Effect-returning schema APIs for decoding, and Effect recovery combinators for recovery."
|
|
2210
|
+
}
|
|
2211
|
+
},
|
|
2212
|
+
create(context) {
|
|
2213
|
+
return {
|
|
2214
|
+
TryStatement(node) {
|
|
2215
|
+
if (!node.handler)
|
|
2216
|
+
return;
|
|
2217
|
+
const owner = nearestEnclosingFunction2(node);
|
|
2218
|
+
if (!owner?.generator || !isRecognisedEffectGenerator(context.sourceCode, owner)) {
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
context.report({ node, messageId: "useEffectErrorChannel" });
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
});
|
|
2226
|
+
|
|
2227
|
+
// src/effect/index.ts
|
|
2228
|
+
var timmoEffectPlugin = eslintCompatPlugin3({
|
|
2229
|
+
meta: { name: "timmo-effect" },
|
|
2230
|
+
rules: {
|
|
2231
|
+
"no-try-catch-in-effect-generators": noTryCatchInEffectGeneratorsRule
|
|
2232
|
+
}
|
|
2233
|
+
});
|
|
2234
|
+
var effect_default = timmoEffectPlugin;
|
|
2235
|
+
|
|
2236
|
+
// vendor/anti-slop/src/effect/index.ts
|
|
2237
|
+
import { eslintCompatPlugin as eslintCompatPlugin4 } from "@oxlint/plugins";
|
|
2238
|
+
|
|
2239
|
+
// vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
|
|
2240
|
+
import { defineRule as defineRule20 } from "@oxlint/plugins";
|
|
2241
|
+
var SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
|
|
2242
|
+
var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
|
|
2243
|
+
function isProjectLocalImport(source) {
|
|
2244
|
+
return source.startsWith("./") || source.startsWith("../");
|
|
2245
|
+
}
|
|
2246
|
+
function getImportedName(specifier) {
|
|
2247
|
+
if (specifier.imported.type === "Identifier")
|
|
2248
|
+
return specifier.imported.name;
|
|
2249
|
+
return specifier.imported.value;
|
|
2250
|
+
}
|
|
2251
|
+
var noServiceConstructorImportsRule = defineRule20({
|
|
2252
|
+
meta: {
|
|
2253
|
+
type: "problem",
|
|
2254
|
+
docs: {
|
|
2255
|
+
description: "Disallow project-local make<CapabilityName> imports outside test and spec files."
|
|
2256
|
+
},
|
|
2257
|
+
messages: {
|
|
2258
|
+
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.'
|
|
2259
|
+
}
|
|
2260
|
+
},
|
|
2261
|
+
create(context) {
|
|
2262
|
+
const isTestFile = TEST_FILE.test(context.filename.replaceAll("\\", "/"));
|
|
2263
|
+
return {
|
|
2264
|
+
ImportDeclaration(node) {
|
|
2265
|
+
if (isTestFile || !isProjectLocalImport(node.source.value))
|
|
2266
|
+
return;
|
|
2267
|
+
for (const specifier of node.specifiers) {
|
|
2268
|
+
if (specifier.type !== "ImportSpecifier")
|
|
2269
|
+
continue;
|
|
2270
|
+
const importedName = getImportedName(specifier);
|
|
2271
|
+
if (!SERVICE_CONSTRUCTOR_NAME.test(importedName))
|
|
2272
|
+
continue;
|
|
2273
|
+
context.report({
|
|
2274
|
+
node: specifier,
|
|
2275
|
+
messageId: "serviceConstructorImport",
|
|
2276
|
+
data: { name: importedName }
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
});
|
|
2283
|
+
|
|
2284
|
+
// vendor/anti-slop/src/effect/index.ts
|
|
2285
|
+
var antiSlopEffectPlugin = eslintCompatPlugin4({
|
|
2286
|
+
meta: { name: "anti-slop-effect" },
|
|
2287
|
+
rules: {
|
|
2288
|
+
"no-service-constructor-imports": noServiceConstructorImportsRule
|
|
2289
|
+
}
|
|
2290
|
+
});
|
|
2291
|
+
var effect_default2 = antiSlopEffectPlugin;
|
|
2292
|
+
// src/configs/recommended-effect.ts
|
|
2293
|
+
var recommendedEffect = defineConfig({
|
|
2094
2294
|
extends: [recommended_default],
|
|
2095
2295
|
jsPlugins: [
|
|
2096
2296
|
{
|
|
@@ -2104,10 +2304,7 @@ var effect = defineConfig({
|
|
|
2104
2304
|
...enablePluginRules("timmo-effect", effect_default)
|
|
2105
2305
|
}
|
|
2106
2306
|
});
|
|
2107
|
-
var
|
|
2108
|
-
|
|
2109
|
-
// src/cli.ts
|
|
2110
|
-
import { resolve as resolve2 } from "node:path";
|
|
2307
|
+
var recommended_effect_default = recommendedEffect;
|
|
2111
2308
|
|
|
2112
2309
|
// src/install/copy.ts
|
|
2113
2310
|
import { cp, mkdir, readdir, rm } from "node:fs/promises";
|
|
@@ -2189,7 +2386,7 @@ if (command === "--help" || command === "-h") {
|
|
|
2189
2386
|
console.log(` timmo: ${entries.timmo}`);
|
|
2190
2387
|
console.log(` timmo-effect: ${entries.timmoEffect}`);
|
|
2191
2388
|
printRuleSettings("General rule settings:", recommended_default.rules ?? {});
|
|
2192
|
-
printRuleSettings("Effect rule settings (opt-in):",
|
|
2389
|
+
printRuleSettings("Effect rule settings (opt-in):", recommended_effect_default.rules ?? {});
|
|
2193
2390
|
} catch (error) {
|
|
2194
2391
|
console.error(error instanceof Error ? error.message : String(error));
|
|
2195
2392
|
process.exitCode = 1;
|