oxlint-plugin-effect 0.13.0 → 0.14.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 +24 -13
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/dist/presets/recommended.d.ts +9 -0
- package/dist/presets/recommended.js +10 -1
- package/dist/rules/_anti-slop-array-method.js +70 -0
- package/dist/rules/_anti-slop-dictionary-types.js +58 -53
- package/dist/rules/_anti-slop-function-parameters.js +27 -0
- package/dist/rules/_anti-slop-lexical-type-parameters.js +33 -0
- package/dist/rules/_anti-slop-reflect-method.js +21 -0
- package/dist/rules/_anti-slop-scope.js +13 -0
- package/dist/rules/_anti-slop-type-alias-resolution.js +146 -0
- package/dist/rules/_function-metrics.js +31 -31
- package/dist/rules/_test-files.js +8 -0
- package/dist/rules/index.d.ts +10 -1
- package/dist/rules/index.js +19 -1
- package/dist/rules/no-array-filter-map.d.ts +7 -0
- package/dist/rules/no-array-filter-map.js +39 -0
- package/dist/rules/no-conditional-empty-object-spread.d.ts +1 -1
- package/dist/rules/no-conditional-empty-object-spread.js +8 -51
- package/dist/rules/no-eager-acquire.d.ts +6 -0
- package/dist/rules/no-eager-acquire.js +92 -0
- package/dist/rules/no-effect-run-in-tests.d.ts +6 -0
- package/dist/rules/no-effect-run-in-tests.js +57 -0
- package/dist/rules/no-known-value-widening.js +6 -14
- package/dist/rules/no-module-level-mutable-state.d.ts +6 -0
- package/dist/rules/no-module-level-mutable-state.js +51 -0
- package/dist/rules/no-object-parameters.js +11 -27
- package/dist/rules/no-reduce-accumulator-copy.d.ts +7 -0
- package/dist/rules/no-reduce-accumulator-copy.js +94 -0
- package/dist/rules/no-reflect-apply.d.ts +7 -0
- package/dist/rules/no-reflect-apply.js +25 -0
- package/dist/rules/no-reflect-get.d.ts +7 -0
- package/dist/rules/no-reflect-get.js +25 -0
- package/dist/rules/no-runtime-typeof.js +14 -1
- package/dist/rules/no-shape-in-symbol-names.js +7 -1
- package/dist/rules/no-unknown-parameters.d.ts +1 -1
- package/dist/rules/no-unknown-parameters.js +23 -16
- package/dist/rules/no-unknown-returns.d.ts +7 -0
- package/dist/rules/no-unknown-returns.js +73 -0
- package/dist/rules/no-unknown-type-aliases.js +17 -27
- package/dist/rules/no-unsafe-dictionary-type.js +54 -3
- package/dist/rules/no-widen-then-assert.js +1 -1
- package/dist/rules/require-suppression-reason.d.ts +6 -0
- package/dist/rules/require-suppression-reason.js +87 -0
- package/dist/vendor/effect-oxlint/Diagnostic.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { lexicalTypeParameterNames } from "./_anti-slop-lexical-type-parameters.js";
|
|
2
|
+
//#region src/rules/_anti-slop-type-alias-resolution.ts
|
|
3
|
+
const environmentsByProgram = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
function isNode(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
6
|
+
}
|
|
7
|
+
function enclosingTypeScope(node) {
|
|
8
|
+
let current = node.parent;
|
|
9
|
+
while (current !== null) {
|
|
10
|
+
if (current.type === "Program" || current.type === "BlockStatement" || current.type === "TSModuleBlock" || current.type === "StaticBlock" || current.type === "SwitchStatement") return current;
|
|
11
|
+
current = current.parent;
|
|
12
|
+
}
|
|
13
|
+
return node;
|
|
14
|
+
}
|
|
15
|
+
function declaredTypeBinding(node) {
|
|
16
|
+
if (node.type === "TSTypeAliasDeclaration") return {
|
|
17
|
+
alias: node,
|
|
18
|
+
name: node.id.name
|
|
19
|
+
};
|
|
20
|
+
if (node.type === "TSInterfaceDeclaration" || node.type === "TSEnumDeclaration" || node.type === "ClassDeclaration" || node.type === "ClassExpression") return node.id === null ? null : {
|
|
21
|
+
alias: null,
|
|
22
|
+
name: node.id.name
|
|
23
|
+
};
|
|
24
|
+
if (node.type === "ImportSpecifier" || node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier") return {
|
|
25
|
+
alias: null,
|
|
26
|
+
name: node.local.name
|
|
27
|
+
};
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function collectTypeBindings(node, visitorKeys, bindingsByName, aliases) {
|
|
31
|
+
const declared = declaredTypeBinding(node);
|
|
32
|
+
if (declared !== null) {
|
|
33
|
+
const bindings = bindingsByName.get(declared.name) ?? [];
|
|
34
|
+
bindings.push({
|
|
35
|
+
...declared,
|
|
36
|
+
scope: enclosingTypeScope(node)
|
|
37
|
+
});
|
|
38
|
+
bindingsByName.set(declared.name, bindings);
|
|
39
|
+
if (declared.alias !== null) aliases.push(declared.alias);
|
|
40
|
+
}
|
|
41
|
+
const fields = node;
|
|
42
|
+
for (const key of visitorKeys[node.type] ?? []) {
|
|
43
|
+
const value = fields[key];
|
|
44
|
+
if (isNode(value)) {
|
|
45
|
+
collectTypeBindings(value, visitorKeys, bindingsByName, aliases);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(value)) continue;
|
|
49
|
+
for (const child of value) if (isNode(child)) collectTypeBindings(child, visitorKeys, bindingsByName, aliases);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Collect every lexical type alias and competing type binding in a program. */
|
|
53
|
+
function createTypeAliasEnvironment(program, visitorKeys) {
|
|
54
|
+
const cached = environmentsByProgram.get(program);
|
|
55
|
+
if (cached !== void 0) return cached;
|
|
56
|
+
const bindingsByName = /* @__PURE__ */ new Map();
|
|
57
|
+
const aliases = [];
|
|
58
|
+
collectTypeBindings(program, visitorKeys, bindingsByName, aliases);
|
|
59
|
+
const environment = {
|
|
60
|
+
aliases,
|
|
61
|
+
bindingsByName,
|
|
62
|
+
visitorKeys
|
|
63
|
+
};
|
|
64
|
+
environmentsByProgram.set(program, environment);
|
|
65
|
+
return environment;
|
|
66
|
+
}
|
|
67
|
+
function ancestorDistance(ancestor, node) {
|
|
68
|
+
let current = node;
|
|
69
|
+
let distance = 0;
|
|
70
|
+
while (current !== null) {
|
|
71
|
+
if (current === ancestor) return distance;
|
|
72
|
+
current = current.parent;
|
|
73
|
+
distance += 1;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function nearestTypeBindings(name, use, environment) {
|
|
78
|
+
const candidates = environment.bindingsByName.get(name) ?? [];
|
|
79
|
+
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
80
|
+
let nearest = [];
|
|
81
|
+
for (const candidate of candidates) {
|
|
82
|
+
const distance = ancestorDistance(candidate.scope, use);
|
|
83
|
+
if (distance === null || distance > nearestDistance) continue;
|
|
84
|
+
if (distance === nearestDistance) {
|
|
85
|
+
nearest.push(candidate);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
nearestDistance = distance;
|
|
89
|
+
nearest = [candidate];
|
|
90
|
+
}
|
|
91
|
+
return nearest;
|
|
92
|
+
}
|
|
93
|
+
/** Resolve the nearest visible alias with this name, respecting lexical shadowing. */
|
|
94
|
+
function visibleTypeAlias(name, use, environment) {
|
|
95
|
+
if (lexicalTypeParameterNames(use, environment.visitorKeys).has(name)) return null;
|
|
96
|
+
const bindings = nearestTypeBindings(name, use, environment);
|
|
97
|
+
return bindings.length === 1 ? bindings[0]?.alias ?? null : null;
|
|
98
|
+
}
|
|
99
|
+
/** Return whether a local declaration shadows a built-in type at this use. */
|
|
100
|
+
function hasVisibleTypeBinding(name, use, environment) {
|
|
101
|
+
return lexicalTypeParameterNames(use, environment.visitorKeys).has(name) || nearestTypeBindings(name, use, environment).length > 0;
|
|
102
|
+
}
|
|
103
|
+
function typeReferenceName(type) {
|
|
104
|
+
return type.typeName.type === "Identifier" ? type.typeName.name : null;
|
|
105
|
+
}
|
|
106
|
+
function aliasSubstitutions(alias, reference, base) {
|
|
107
|
+
const parameters = alias.typeParameters?.params ?? [];
|
|
108
|
+
const typeArguments = reference.typeArguments?.params ?? [];
|
|
109
|
+
const next = new Map(base);
|
|
110
|
+
for (const [index, parameter] of parameters.entries()) {
|
|
111
|
+
const explicitArgument = typeArguments[index];
|
|
112
|
+
const argument = explicitArgument ?? parameter.default;
|
|
113
|
+
if (argument === null || argument === void 0) return null;
|
|
114
|
+
const argumentSubstitutions = explicitArgument === void 0 ? next : base;
|
|
115
|
+
next.set(parameter.name.name, {
|
|
116
|
+
type: argument,
|
|
117
|
+
substitutions: new Map(argumentSubstitutions)
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return next;
|
|
121
|
+
}
|
|
122
|
+
/** Match a type after resolving visible aliases and substituting their type parameters. */
|
|
123
|
+
function resolvedTypeMatches(type, environment, matcher) {
|
|
124
|
+
const evaluate = (current, substitutions, resolvingAliases) => {
|
|
125
|
+
if (current.type === "TSTypeReference") {
|
|
126
|
+
const name = typeReferenceName(current);
|
|
127
|
+
if (name !== null) {
|
|
128
|
+
const substitution = substitutions.get(name);
|
|
129
|
+
if (substitution !== void 0 && !current.typeArguments?.params.length) return evaluate(substitution.type, substitution.substitutions, resolvingAliases);
|
|
130
|
+
const alias = visibleTypeAlias(name, current, environment);
|
|
131
|
+
if (alias !== null && !resolvingAliases.has(alias)) {
|
|
132
|
+
const nextSubstitutions = aliasSubstitutions(alias, current, substitutions);
|
|
133
|
+
if (nextSubstitutions !== null) {
|
|
134
|
+
const nextResolving = new Set(resolvingAliases);
|
|
135
|
+
nextResolving.add(alias);
|
|
136
|
+
return evaluate(alias.typeAnnotation, nextSubstitutions, nextResolving);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return matcher(current, (child) => evaluate(child, substitutions, resolvingAliases));
|
|
142
|
+
};
|
|
143
|
+
return evaluate(type, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Set());
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
export { createTypeAliasEnvironment, hasVisibleTypeBinding, resolvedTypeMatches, visibleTypeAlias };
|
|
@@ -154,36 +154,36 @@ const templateOperand = (node) => {
|
|
|
154
154
|
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
155
155
|
};
|
|
156
156
|
/** Node types that always stand for the same operator tokens. */
|
|
157
|
-
const fixedOperators =
|
|
158
|
-
ForStatement
|
|
159
|
-
ForInStatement
|
|
160
|
-
WhileStatement
|
|
161
|
-
DoWhileStatement
|
|
162
|
-
SwitchStatement
|
|
163
|
-
ReturnStatement
|
|
164
|
-
ThrowStatement
|
|
165
|
-
CatchClause
|
|
166
|
-
BreakStatement
|
|
167
|
-
ContinueStatement
|
|
168
|
-
LabeledStatement
|
|
169
|
-
DebuggerStatement
|
|
170
|
-
WithStatement
|
|
171
|
-
AssignmentPattern
|
|
172
|
-
NewExpression
|
|
173
|
-
ArrayExpression
|
|
174
|
-
ArrayPattern
|
|
175
|
-
ObjectExpression
|
|
176
|
-
ObjectPattern
|
|
177
|
-
SpreadElement
|
|
178
|
-
RestElement
|
|
179
|
-
ConditionalExpression
|
|
180
|
-
SequenceExpression
|
|
181
|
-
AwaitExpression
|
|
182
|
-
ImportExpression
|
|
183
|
-
TemplateLiteral
|
|
184
|
-
JSXElement
|
|
185
|
-
JSXFragment
|
|
186
|
-
|
|
157
|
+
const fixedOperators = new Map([
|
|
158
|
+
["ForStatement", ["for"]],
|
|
159
|
+
["ForInStatement", ["for-in"]],
|
|
160
|
+
["WhileStatement", ["while"]],
|
|
161
|
+
["DoWhileStatement", ["do-while"]],
|
|
162
|
+
["SwitchStatement", ["switch"]],
|
|
163
|
+
["ReturnStatement", ["return"]],
|
|
164
|
+
["ThrowStatement", ["throw"]],
|
|
165
|
+
["CatchClause", ["catch"]],
|
|
166
|
+
["BreakStatement", ["break"]],
|
|
167
|
+
["ContinueStatement", ["continue"]],
|
|
168
|
+
["LabeledStatement", [":"]],
|
|
169
|
+
["DebuggerStatement", ["debugger"]],
|
|
170
|
+
["WithStatement", ["with"]],
|
|
171
|
+
["AssignmentPattern", ["="]],
|
|
172
|
+
["NewExpression", ["new"]],
|
|
173
|
+
["ArrayExpression", ["[]"]],
|
|
174
|
+
["ArrayPattern", ["[]"]],
|
|
175
|
+
["ObjectExpression", ["{}"]],
|
|
176
|
+
["ObjectPattern", ["{}"]],
|
|
177
|
+
["SpreadElement", ["..."]],
|
|
178
|
+
["RestElement", ["..."]],
|
|
179
|
+
["ConditionalExpression", ["?:"]],
|
|
180
|
+
["SequenceExpression", [","]],
|
|
181
|
+
["AwaitExpression", ["await"]],
|
|
182
|
+
["ImportExpression", ["import()"]],
|
|
183
|
+
["TemplateLiteral", ["`"]],
|
|
184
|
+
["JSXElement", ["<>"]],
|
|
185
|
+
["JSXFragment", ["<>"]]
|
|
186
|
+
]);
|
|
187
187
|
/** Node types whose operator token carries the operator itself. */
|
|
188
188
|
const operatorCarriers = new Set([
|
|
189
189
|
"BinaryExpression",
|
|
@@ -229,7 +229,7 @@ const expressionOperators = (node) => {
|
|
|
229
229
|
};
|
|
230
230
|
/** Every operator token a node stands for. */
|
|
231
231
|
const operatorsOf = (node) => [
|
|
232
|
-
...fixedOperators
|
|
232
|
+
...fixedOperators.get(node.type) ?? [],
|
|
233
233
|
...statementOperators(node),
|
|
234
234
|
...expressionOperators(node)
|
|
235
235
|
];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/rules/_test-files.ts
|
|
2
|
+
/** Test and spec modules: `*.test.*` and `*.spec.*` with a JS or TS extension. */
|
|
3
|
+
const testFilePattern = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
|
|
4
|
+
const isTestFile = (filename) => testFilePattern.test(filename);
|
|
5
|
+
/** Visitor for a file a rule does not apply to. */
|
|
6
|
+
const skipFile = {};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { isTestFile, skipFile };
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -2,15 +2,19 @@ import { noEffectBind } from "./no-effect-bind.js";
|
|
|
2
2
|
import { noEffectDo } from "./no-effect-do.js";
|
|
3
3
|
import { maxCognitiveComplexity } from "./max-cognitive-complexity.js";
|
|
4
4
|
import { maxHalsteadDifficulty } from "./max-halstead-difficulty.js";
|
|
5
|
+
import { noArrayFilterMap } from "./no-array-filter-map.js";
|
|
5
6
|
import { noAs } from "./no-as.js";
|
|
6
7
|
import { noAsyncFunction } from "./no-async-function.js";
|
|
7
8
|
import { noChainedTypeAssertions } from "./no-chained-type-assertions.js";
|
|
8
9
|
import { noConditionalEmptyObjectSpread } from "./no-conditional-empty-object-spread.js";
|
|
9
10
|
import { noDynamicImports } from "./no-dynamic-imports.js";
|
|
11
|
+
import { noEagerAcquire } from "./no-eager-acquire.js";
|
|
12
|
+
import { noEffectRunInTests } from "./no-effect-run-in-tests.js";
|
|
10
13
|
import { noGlobals } from "./no-globals.js";
|
|
11
14
|
import { noInlineProvide } from "./no-inline-provide.js";
|
|
12
15
|
import { noKnownValueWidening } from "./no-known-value-widening.js";
|
|
13
16
|
import { noManagedRuntimeInEffect } from "./no-managed-runtime-in-effect.js";
|
|
17
|
+
import { noModuleLevelMutableState } from "./no-module-level-mutable-state.js";
|
|
14
18
|
import { noModuleMocks } from "./no-module-mocks.js";
|
|
15
19
|
import { noNestedEffectGen } from "./no-nested-effect-gen.js";
|
|
16
20
|
import { noNewError } from "./no-new-error.js";
|
|
@@ -19,6 +23,9 @@ import { noNodeBuiltinImport } from "./no-node-builtin-import.js";
|
|
|
19
23
|
import { noNullish } from "./no-nullish.js";
|
|
20
24
|
import { noObjectParameters } from "./no-object-parameters.js";
|
|
21
25
|
import { noPerCallCacheConstruction } from "./no-per-call-cache-construction.js";
|
|
26
|
+
import { noReduceAccumulatorCopy } from "./no-reduce-accumulator-copy.js";
|
|
27
|
+
import { noReflectApply } from "./no-reflect-apply.js";
|
|
28
|
+
import { noReflectGet } from "./no-reflect-get.js";
|
|
22
29
|
import { noRunCollectOnUnboundedStream } from "./no-run-collect-on-unbounded-stream.js";
|
|
23
30
|
import { noRuntimeTypeof } from "./no-runtime-typeof.js";
|
|
24
31
|
import { noSequentialEffectAll } from "./no-sequential-effect-all.js";
|
|
@@ -31,6 +38,7 @@ import { noTryCatch } from "./no-try-catch.js";
|
|
|
31
38
|
import { noUnboundedConcurrency } from "./no-unbounded-concurrency.js";
|
|
32
39
|
import { noUnboundedRetry } from "./no-unbounded-retry.js";
|
|
33
40
|
import { noUnknownParameters } from "./no-unknown-parameters.js";
|
|
41
|
+
import { noUnknownReturns } from "./no-unknown-returns.js";
|
|
34
42
|
import { noUnknownTypeAliases } from "./no-unknown-type-aliases.js";
|
|
35
43
|
import { noUnsafeDictionaryType } from "./no-unsafe-dictionary-type.js";
|
|
36
44
|
import { noWidenThenAssert } from "./no-widen-then-assert.js";
|
|
@@ -40,10 +48,11 @@ import { preferMatchTagsExhaustive } from "./prefer-match-tags-exhaustive.js";
|
|
|
40
48
|
import { preferPredicateIsTagged } from "./prefer-predicate-is-tagged.js";
|
|
41
49
|
import { preferServiceOf } from "./prefer-service-of.js";
|
|
42
50
|
import { requireNamedEffectFn } from "./require-named-effect-fn.js";
|
|
51
|
+
import { requireSuppressionReason } from "./require-suppression-reason.js";
|
|
43
52
|
|
|
44
53
|
//#region src/rules/index.d.ts
|
|
45
54
|
declare namespace index_d_exports {
|
|
46
|
-
export { maxCognitiveComplexity, maxHalsteadDifficulty, noAs, noAsyncFunction, noChainedTypeAssertions, noConditionalEmptyObjectSpread, noDynamicImports, noEffectBind, noEffectDo, noGlobals, noInlineProvide, noKnownValueWidening, noManagedRuntimeInEffect, noModuleMocks, noNestedEffectGen, noNewError, noNewPromise, noNodeBuiltinImport, noNullish, noObjectParameters, noPerCallCacheConstruction, noRunCollectOnUnboundedStream, noRuntimeTypeof, noSequentialEffectAll, noShapeInSymbolNames, noSilentCatchAll, noTernary, noTestLifecycleHooks, noThrowStatement, noTryCatch, noUnboundedConcurrency, noUnboundedRetry, noUnknownParameters, noUnknownTypeAliases, noUnsafeDictionaryType, noWidenThenAssert, preferCatchTag, preferEffectFn, preferMatchTagsExhaustive, preferPredicateIsTagged, preferServiceOf, requireNamedEffectFn };
|
|
55
|
+
export { maxCognitiveComplexity, maxHalsteadDifficulty, noArrayFilterMap, noAs, noAsyncFunction, noChainedTypeAssertions, noConditionalEmptyObjectSpread, noDynamicImports, noEagerAcquire, noEffectBind, noEffectDo, noEffectRunInTests, noGlobals, noInlineProvide, noKnownValueWidening, noManagedRuntimeInEffect, noModuleLevelMutableState, noModuleMocks, noNestedEffectGen, noNewError, noNewPromise, noNodeBuiltinImport, noNullish, noObjectParameters, noPerCallCacheConstruction, noReduceAccumulatorCopy, noReflectApply, noReflectGet, noRunCollectOnUnboundedStream, noRuntimeTypeof, noSequentialEffectAll, noShapeInSymbolNames, noSilentCatchAll, noTernary, noTestLifecycleHooks, noThrowStatement, noTryCatch, noUnboundedConcurrency, noUnboundedRetry, noUnknownParameters, noUnknownReturns, noUnknownTypeAliases, noUnsafeDictionaryType, noWidenThenAssert, preferCatchTag, preferEffectFn, preferMatchTagsExhaustive, preferPredicateIsTagged, preferServiceOf, requireNamedEffectFn, requireSuppressionReason };
|
|
47
56
|
}
|
|
48
57
|
//#endregion
|
|
49
58
|
export { index_d_exports };
|
package/dist/rules/index.js
CHANGED
|
@@ -3,15 +3,19 @@ import { noEffectBind } from "./no-effect-bind.js";
|
|
|
3
3
|
import { noEffectDo } from "./no-effect-do.js";
|
|
4
4
|
import { maxCognitiveComplexity } from "./max-cognitive-complexity.js";
|
|
5
5
|
import { maxHalsteadDifficulty } from "./max-halstead-difficulty.js";
|
|
6
|
+
import { noArrayFilterMap } from "./no-array-filter-map.js";
|
|
6
7
|
import { noAs } from "./no-as.js";
|
|
7
8
|
import { noAsyncFunction } from "./no-async-function.js";
|
|
8
9
|
import { noChainedTypeAssertions } from "./no-chained-type-assertions.js";
|
|
9
10
|
import { noConditionalEmptyObjectSpread } from "./no-conditional-empty-object-spread.js";
|
|
10
11
|
import { noDynamicImports } from "./no-dynamic-imports.js";
|
|
12
|
+
import { noEagerAcquire } from "./no-eager-acquire.js";
|
|
13
|
+
import { noEffectRunInTests } from "./no-effect-run-in-tests.js";
|
|
11
14
|
import { noGlobals } from "./no-globals.js";
|
|
12
15
|
import { noInlineProvide } from "./no-inline-provide.js";
|
|
13
16
|
import { noKnownValueWidening } from "./no-known-value-widening.js";
|
|
14
17
|
import { noManagedRuntimeInEffect } from "./no-managed-runtime-in-effect.js";
|
|
18
|
+
import { noModuleLevelMutableState } from "./no-module-level-mutable-state.js";
|
|
15
19
|
import { noModuleMocks } from "./no-module-mocks.js";
|
|
16
20
|
import { noNestedEffectGen } from "./no-nested-effect-gen.js";
|
|
17
21
|
import { noNewError } from "./no-new-error.js";
|
|
@@ -20,6 +24,9 @@ import { noNodeBuiltinImport } from "./no-node-builtin-import.js";
|
|
|
20
24
|
import { noNullish } from "./no-nullish.js";
|
|
21
25
|
import { noObjectParameters } from "./no-object-parameters.js";
|
|
22
26
|
import { noPerCallCacheConstruction } from "./no-per-call-cache-construction.js";
|
|
27
|
+
import { noReduceAccumulatorCopy } from "./no-reduce-accumulator-copy.js";
|
|
28
|
+
import { noReflectApply } from "./no-reflect-apply.js";
|
|
29
|
+
import { noReflectGet } from "./no-reflect-get.js";
|
|
23
30
|
import { noRunCollectOnUnboundedStream } from "./no-run-collect-on-unbounded-stream.js";
|
|
24
31
|
import { noRuntimeTypeof } from "./no-runtime-typeof.js";
|
|
25
32
|
import { noSequentialEffectAll } from "./no-sequential-effect-all.js";
|
|
@@ -32,6 +39,7 @@ import { noTryCatch } from "./no-try-catch.js";
|
|
|
32
39
|
import { noUnboundedConcurrency } from "./no-unbounded-concurrency.js";
|
|
33
40
|
import { noUnboundedRetry } from "./no-unbounded-retry.js";
|
|
34
41
|
import { noUnknownParameters } from "./no-unknown-parameters.js";
|
|
42
|
+
import { noUnknownReturns } from "./no-unknown-returns.js";
|
|
35
43
|
import { noUnknownTypeAliases } from "./no-unknown-type-aliases.js";
|
|
36
44
|
import { noUnsafeDictionaryType } from "./no-unsafe-dictionary-type.js";
|
|
37
45
|
import { noWidenThenAssert } from "./no-widen-then-assert.js";
|
|
@@ -41,21 +49,26 @@ import { preferMatchTagsExhaustive } from "./prefer-match-tags-exhaustive.js";
|
|
|
41
49
|
import { preferPredicateIsTagged } from "./prefer-predicate-is-tagged.js";
|
|
42
50
|
import { preferServiceOf } from "./prefer-service-of.js";
|
|
43
51
|
import { requireNamedEffectFn } from "./require-named-effect-fn.js";
|
|
52
|
+
import { requireSuppressionReason } from "./require-suppression-reason.js";
|
|
44
53
|
//#region src/rules/index.ts
|
|
45
54
|
var rules_exports = /* @__PURE__ */ __exportAll({
|
|
46
55
|
maxCognitiveComplexity: () => maxCognitiveComplexity,
|
|
47
56
|
maxHalsteadDifficulty: () => maxHalsteadDifficulty,
|
|
57
|
+
noArrayFilterMap: () => noArrayFilterMap,
|
|
48
58
|
noAs: () => noAs,
|
|
49
59
|
noAsyncFunction: () => noAsyncFunction,
|
|
50
60
|
noChainedTypeAssertions: () => noChainedTypeAssertions,
|
|
51
61
|
noConditionalEmptyObjectSpread: () => noConditionalEmptyObjectSpread,
|
|
52
62
|
noDynamicImports: () => noDynamicImports,
|
|
63
|
+
noEagerAcquire: () => noEagerAcquire,
|
|
53
64
|
noEffectBind: () => noEffectBind,
|
|
54
65
|
noEffectDo: () => noEffectDo,
|
|
66
|
+
noEffectRunInTests: () => noEffectRunInTests,
|
|
55
67
|
noGlobals: () => noGlobals,
|
|
56
68
|
noInlineProvide: () => noInlineProvide,
|
|
57
69
|
noKnownValueWidening: () => noKnownValueWidening,
|
|
58
70
|
noManagedRuntimeInEffect: () => noManagedRuntimeInEffect,
|
|
71
|
+
noModuleLevelMutableState: () => noModuleLevelMutableState,
|
|
59
72
|
noModuleMocks: () => noModuleMocks,
|
|
60
73
|
noNestedEffectGen: () => noNestedEffectGen,
|
|
61
74
|
noNewError: () => noNewError,
|
|
@@ -64,6 +77,9 @@ var rules_exports = /* @__PURE__ */ __exportAll({
|
|
|
64
77
|
noNullish: () => noNullish,
|
|
65
78
|
noObjectParameters: () => noObjectParameters,
|
|
66
79
|
noPerCallCacheConstruction: () => noPerCallCacheConstruction,
|
|
80
|
+
noReduceAccumulatorCopy: () => noReduceAccumulatorCopy,
|
|
81
|
+
noReflectApply: () => noReflectApply,
|
|
82
|
+
noReflectGet: () => noReflectGet,
|
|
67
83
|
noRunCollectOnUnboundedStream: () => noRunCollectOnUnboundedStream,
|
|
68
84
|
noRuntimeTypeof: () => noRuntimeTypeof,
|
|
69
85
|
noSequentialEffectAll: () => noSequentialEffectAll,
|
|
@@ -76,6 +92,7 @@ var rules_exports = /* @__PURE__ */ __exportAll({
|
|
|
76
92
|
noUnboundedConcurrency: () => noUnboundedConcurrency,
|
|
77
93
|
noUnboundedRetry: () => noUnboundedRetry,
|
|
78
94
|
noUnknownParameters: () => noUnknownParameters,
|
|
95
|
+
noUnknownReturns: () => noUnknownReturns,
|
|
79
96
|
noUnknownTypeAliases: () => noUnknownTypeAliases,
|
|
80
97
|
noUnsafeDictionaryType: () => noUnsafeDictionaryType,
|
|
81
98
|
noWidenThenAssert: () => noWidenThenAssert,
|
|
@@ -84,7 +101,8 @@ var rules_exports = /* @__PURE__ */ __exportAll({
|
|
|
84
101
|
preferMatchTagsExhaustive: () => preferMatchTagsExhaustive,
|
|
85
102
|
preferPredicateIsTagged: () => preferPredicateIsTagged,
|
|
86
103
|
preferServiceOf: () => preferServiceOf,
|
|
87
|
-
requireNamedEffectFn: () => requireNamedEffectFn
|
|
104
|
+
requireNamedEffectFn: () => requireNamedEffectFn,
|
|
105
|
+
requireSuppressionReason: () => requireSuppressionReason
|
|
88
106
|
});
|
|
89
107
|
//#endregion
|
|
90
108
|
export { rules_exports };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as _$_oxlint_plugins0 from "@oxlint/plugins";
|
|
2
|
+
|
|
3
|
+
//#region src/rules/no-array-filter-map.d.ts
|
|
4
|
+
/** Reject eager array filter/map pipelines; lazy iterator helpers remain allowed. */
|
|
5
|
+
declare const noArrayFilterMap: _$_oxlint_plugins0.CreateRule;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { noArrayFilterMap };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { fromId } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
2
|
+
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
3
|
+
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
4
|
+
import "../vendor/effect-oxlint/index.js";
|
|
5
|
+
import { arrayMethodTarget, isKnownArrayExpression, unwrapArrayExpression } from "./_anti-slop-array-method.js";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
//#region src/rules/no-array-filter-map.ts
|
|
8
|
+
/** Reject eager array filter/map pipelines; lazy iterator helpers remain allowed. */
|
|
9
|
+
const noArrayFilterMap = define({
|
|
10
|
+
name: "no-array-filter-map",
|
|
11
|
+
meta: meta({
|
|
12
|
+
type: "suggestion",
|
|
13
|
+
description: "Disallow adjacent array filter/map passes in favor of lazy iterator helpers or a single transformation.",
|
|
14
|
+
messages: { 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." }
|
|
15
|
+
}),
|
|
16
|
+
create: function* () {
|
|
17
|
+
const context = yield* RuleContext;
|
|
18
|
+
return { CallExpression: (node) => {
|
|
19
|
+
const outer = arrayMethodTarget(node.callee);
|
|
20
|
+
if (outer === null || outer.name !== "map" && outer.name !== "filter") return Effect.void;
|
|
21
|
+
const innerCall = unwrapArrayExpression(outer.object);
|
|
22
|
+
if (innerCall.type !== "CallExpression") return Effect.void;
|
|
23
|
+
const inner = arrayMethodTarget(innerCall.callee);
|
|
24
|
+
const pairedName = outer.name === "map" ? "filter" : "map";
|
|
25
|
+
if (inner === null || inner.name !== pairedName) return Effect.void;
|
|
26
|
+
if (!isKnownArrayExpression(context.sourceCode, inner.object)) return Effect.void;
|
|
27
|
+
return context.report(fromId({
|
|
28
|
+
node,
|
|
29
|
+
messageId: "arrayFilterMap",
|
|
30
|
+
data: {
|
|
31
|
+
first: inner.name,
|
|
32
|
+
second: outer.name
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
} };
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
//#endregion
|
|
39
|
+
export { noArrayFilterMap };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as _$_oxlint_plugins0 from "@oxlint/plugins";
|
|
2
2
|
|
|
3
3
|
//#region src/rules/no-conditional-empty-object-spread.d.ts
|
|
4
|
-
/** Ban conditional empty-object spreads
|
|
4
|
+
/** Ban conditional empty-object spreads without changing their omission semantics. */
|
|
5
5
|
declare const noConditionalEmptyObjectSpread: _$_oxlint_plugins0.CreateRule;
|
|
6
6
|
//#endregion
|
|
7
7
|
export { noConditionalEmptyObjectSpread };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fromId
|
|
1
|
+
import { fromId } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
2
2
|
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
3
3
|
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
4
4
|
import "../vendor/effect-oxlint/index.js";
|
|
@@ -12,67 +12,24 @@ function unwrapParentheses(node) {
|
|
|
12
12
|
function isEmptyObjectExpression(node) {
|
|
13
13
|
return node.type === "ObjectExpression" && node.properties.length === 0;
|
|
14
14
|
}
|
|
15
|
-
function
|
|
16
|
-
if (node.type !== "ObjectExpression" || node.properties.length !== 1) return null;
|
|
17
|
-
const [property] = node.properties;
|
|
18
|
-
if (property?.type !== "Property" || property.kind !== "init" || property.method || property.computed) return null;
|
|
19
|
-
return property;
|
|
20
|
-
}
|
|
21
|
-
function conditionalEmptyObjectSpread(node) {
|
|
15
|
+
function isConditionalEmptyObjectSpread(node) {
|
|
22
16
|
const conditional = unwrapParentheses(node);
|
|
23
|
-
|
|
24
|
-
if (isEmptyObjectExpression(conditional.consequent)) return {
|
|
25
|
-
conditional,
|
|
26
|
-
property: singleObjectProperty(conditional.alternate)
|
|
27
|
-
};
|
|
28
|
-
if (isEmptyObjectExpression(conditional.alternate)) return {
|
|
29
|
-
conditional,
|
|
30
|
-
property: singleObjectProperty(conditional.consequent)
|
|
31
|
-
};
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
function undefinedCheckedExpression(test) {
|
|
35
|
-
const binary = unwrapParentheses(test);
|
|
36
|
-
if (binary.type !== "BinaryExpression") return null;
|
|
37
|
-
if (binary.operator !== "===" && binary.operator !== "!==") return null;
|
|
38
|
-
const left = unwrapParentheses(binary.left);
|
|
39
|
-
const right = unwrapParentheses(binary.right);
|
|
40
|
-
const leftIsUndefined = left.type === "Identifier" && left.name === "undefined";
|
|
41
|
-
if (leftIsUndefined === (right.type === "Identifier" && right.name === "undefined")) return null;
|
|
42
|
-
return {
|
|
43
|
-
expression: leftIsUndefined ? right : left,
|
|
44
|
-
isDefinedWhenTrue: binary.operator === "!=="
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
function canAutofixConditionalEmptyObjectSpread(sourceCode, conditional, property) {
|
|
48
|
-
const checked = undefinedCheckedExpression(conditional.test);
|
|
49
|
-
if (checked === null) return false;
|
|
50
|
-
if (conditional.consequent === property.parent !== checked.isDefinedWhenTrue) return false;
|
|
51
|
-
return sourceCode.getText(unwrapParentheses(checked.expression)) === sourceCode.getText(property.value);
|
|
17
|
+
return conditional.type === "ConditionalExpression" && (isEmptyObjectExpression(conditional.consequent) || isEmptyObjectExpression(conditional.alternate));
|
|
52
18
|
}
|
|
53
|
-
/** Ban conditional empty-object spreads
|
|
19
|
+
/** Ban conditional empty-object spreads without changing their omission semantics. */
|
|
54
20
|
const noConditionalEmptyObjectSpread = define({
|
|
55
21
|
name: "no-conditional-empty-object-spread",
|
|
56
22
|
meta: meta({
|
|
57
23
|
type: "suggestion",
|
|
58
|
-
fixable: "code",
|
|
59
24
|
description: "Disallow object spreads that conditionally spread an empty object to omit fields.",
|
|
60
25
|
messages: { avoid: "Do not use conditional empty-object spreads. Prefer a direct property or build the object in separate statements." }
|
|
61
26
|
}),
|
|
62
27
|
create: function* () {
|
|
63
28
|
const context = yield* RuleContext;
|
|
64
|
-
return { SpreadElement: (node) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const { conditional, property } = match;
|
|
69
|
-
const diagnostic = fromId({
|
|
70
|
-
node,
|
|
71
|
-
messageId: "avoid"
|
|
72
|
-
});
|
|
73
|
-
if (property === null || !canAutofixConditionalEmptyObjectSpread(context.sourceCode, conditional, property)) return context.report(diagnostic);
|
|
74
|
-
return context.report(withFix(diagnostic, replaceText(node, context.sourceCode.getText(property))));
|
|
75
|
-
} };
|
|
29
|
+
return { SpreadElement: (node) => node.parent?.type === "ObjectExpression" && isConditionalEmptyObjectSpread(node.argument) ? context.report(fromId({
|
|
30
|
+
node,
|
|
31
|
+
messageId: "avoid"
|
|
32
|
+
})) : Effect.void };
|
|
76
33
|
}
|
|
77
34
|
});
|
|
78
35
|
//#endregion
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
2
|
+
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
3
|
+
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
4
|
+
import { findVariableUp } from "../vendor/effect-oxlint/Scope.js";
|
|
5
|
+
import "../vendor/effect-oxlint/index.js";
|
|
6
|
+
import { importedNamespaces, isStaticCall, visibleNamespaces } from "./_effect-namespaces.js";
|
|
7
|
+
import * as Option from "effect/Option";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
//#region src/rules/no-eager-acquire.ts
|
|
10
|
+
const unwrapExpression = (node) => {
|
|
11
|
+
let current = node;
|
|
12
|
+
while (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSNonNullExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion") current = current.expression;
|
|
13
|
+
return current;
|
|
14
|
+
};
|
|
15
|
+
const isWithin = (node, ancestor) => {
|
|
16
|
+
let current = node;
|
|
17
|
+
while (current !== null) {
|
|
18
|
+
if (current === ancestor) return true;
|
|
19
|
+
current = current.parent;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
const singleReturnedValue = (thunk) => {
|
|
24
|
+
const body = thunk.body;
|
|
25
|
+
if (body === null) return void 0;
|
|
26
|
+
if (body.type !== "BlockStatement") return body;
|
|
27
|
+
const returns = body.body.filter((statement) => statement.type === "ReturnStatement");
|
|
28
|
+
const [returned] = returns;
|
|
29
|
+
if (returns.length !== 1 || returned === void 0) return void 0;
|
|
30
|
+
return returned.argument ?? void 0;
|
|
31
|
+
};
|
|
32
|
+
const noEagerAcquire = define({
|
|
33
|
+
name: "no-eager-acquire",
|
|
34
|
+
meta: meta({
|
|
35
|
+
type: "problem",
|
|
36
|
+
description: "Construct a resource inside the Effect.acquireRelease acquire Effect, not before it."
|
|
37
|
+
}),
|
|
38
|
+
create: function* () {
|
|
39
|
+
const ctx = yield* RuleContext;
|
|
40
|
+
const effectNamespaces = new Set(["Effect"]);
|
|
41
|
+
/** Whether the value was bound outside the thunk, so it exists before acquire runs. */
|
|
42
|
+
const isCaptured = (expression, thunk, seen) => {
|
|
43
|
+
const value = unwrapExpression(expression);
|
|
44
|
+
if (value.type === "ThisExpression") return true;
|
|
45
|
+
if (value.type === "MemberExpression") return value.object.type !== "Super" && isCaptured(value.object, thunk, seen);
|
|
46
|
+
if (value.type !== "Identifier") return false;
|
|
47
|
+
return Option.match(findVariableUp(ctx.sourceCode.getScope(value), value.name), {
|
|
48
|
+
onNone: () => true,
|
|
49
|
+
onSome: (variable) => {
|
|
50
|
+
const [definition] = variable.defs;
|
|
51
|
+
if (definition === void 0) return true;
|
|
52
|
+
if (seen.has(definition.node)) return false;
|
|
53
|
+
seen.add(definition.node);
|
|
54
|
+
if (!isWithin(definition.node, thunk)) return true;
|
|
55
|
+
if (definition.node.type === "VariableDeclarator") return definition.node.init !== null && isCaptured(definition.node.init, thunk, seen);
|
|
56
|
+
const defaulted = thunk.params.find((parameter) => parameter.type === "AssignmentPattern" && parameter.left.type === "Identifier" && parameter.left.name === value.name);
|
|
57
|
+
return defaulted !== void 0 && isCaptured(defaulted.right, thunk, seen);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const returnsCapturedHandle = (expression, effects) => {
|
|
62
|
+
if (!isStaticCall(expression, effects, "sync")) return false;
|
|
63
|
+
const [argument] = expression.arguments;
|
|
64
|
+
if (argument === void 0 || argument.type === "SpreadElement") return false;
|
|
65
|
+
const thunk = unwrapExpression(argument);
|
|
66
|
+
if (thunk.type !== "ArrowFunctionExpression" && thunk.type !== "FunctionExpression") return false;
|
|
67
|
+
const returned = singleReturnedValue(thunk);
|
|
68
|
+
return returned !== void 0 && isCaptured(returned, thunk, /* @__PURE__ */ new Set());
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
ImportDeclaration: (node) => {
|
|
72
|
+
for (const name of importedNamespaces(node, "Effect", "effect/Effect")) effectNamespaces.add(name);
|
|
73
|
+
return Effect.void;
|
|
74
|
+
},
|
|
75
|
+
CallExpression: (node) => {
|
|
76
|
+
if (node.callee.type === "Super") return Effect.void;
|
|
77
|
+
const effects = visibleNamespaces(ctx, node, effectNamespaces);
|
|
78
|
+
if (!isStaticCall(node, effects, "acquireRelease")) return Effect.void;
|
|
79
|
+
const [acquire] = node.arguments;
|
|
80
|
+
if (acquire === void 0 || acquire.type === "SpreadElement") return Effect.void;
|
|
81
|
+
const expression = unwrapExpression(acquire);
|
|
82
|
+
if (!isStaticCall(expression, effects, "succeed") && !returnsCapturedHandle(expression, effects)) return Effect.void;
|
|
83
|
+
return ctx.report(make({
|
|
84
|
+
node: acquire,
|
|
85
|
+
message: "Build the resource inside acquire (Effect.sync(() => new Resource()), Effect.tryPromise, or another lazy Effect). Effect.succeed(handle) or Effect.sync(() => handle) creates it before acquire runs, so an interruption in between leaks it."
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
//#endregion
|
|
92
|
+
export { noEagerAcquire };
|