oxlint-plugin-effect 0.3.0 → 0.4.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 +79 -38
- package/dist/presets/index.d.ts +3 -9
- package/dist/presets/index.js +3 -17
- package/dist/presets/recommended.d.ts +17 -0
- package/dist/presets/recommended.js +17 -0
- package/dist/rules/index.d.ts +4 -61
- package/dist/rules/index.js +4 -118
- package/dist/rules/no-async-function.js +8 -13
- package/dist/rules/no-dynamic-imports.js +89 -60
- package/dist/rules/no-globals.js +164 -127
- package/dist/rules/no-new-error.js +26 -13
- package/dist/rules/no-new-promise.js +22 -21
- package/dist/rules/no-node-builtin-import.js +98 -41
- package/dist/rules/no-throw-statement.js +4 -16
- package/dist/rules/no-try-catch.js +4 -16
- package/package.json +2 -2
- package/dist/presets/core.d.ts +0 -50
- package/dist/presets/core.js +0 -50
- package/dist/presets/effect-native.d.ts +0 -29
- package/dist/presets/effect-native.js +0 -29
- package/dist/presets/full.d.ts +0 -68
- package/dist/presets/full.js +0 -68
- package/dist/presets/functional.d.ts +0 -22
- package/dist/presets/functional.js +0 -22
- package/dist/presets/strict.d.ts +0 -73
- package/dist/presets/strict.js +0 -73
- package/dist/presets/v3-strict.d.ts +0 -16
- package/dist/presets/v3-strict.js +0 -16
- package/dist/presets/v3.d.ts +0 -18
- package/dist/presets/v3.js +0 -18
- package/dist/rules/_effect-context.d.ts +0 -19
- package/dist/rules/_effect-context.js +0 -22
- package/dist/rules/no-arrow-ladder.d.ts +0 -6
- package/dist/rules/no-arrow-ladder.js +0 -61
- package/dist/rules/no-catch-all-to-map-error.d.ts +0 -6
- package/dist/rules/no-catch-all-to-map-error.js +0 -67
- package/dist/rules/no-effect-all-step-sequencing.d.ts +0 -6
- package/dist/rules/no-effect-all-step-sequencing.js +0 -63
- package/dist/rules/no-effect-as.d.ts +0 -6
- package/dist/rules/no-effect-as.js +0 -16
- package/dist/rules/no-effect-async.d.ts +0 -6
- package/dist/rules/no-effect-async.js +0 -16
- package/dist/rules/no-effect-fn-generator.d.ts +0 -6
- package/dist/rules/no-effect-fn-generator.js +0 -30
- package/dist/rules/no-effect-gen-adapter.d.ts +0 -6
- package/dist/rules/no-effect-gen-adapter.js +0 -30
- package/dist/rules/no-effect-map-flatten.d.ts +0 -6
- package/dist/rules/no-effect-map-flatten.js +0 -53
- package/dist/rules/no-effect-map-void.d.ts +0 -6
- package/dist/rules/no-effect-map-void.js +0 -40
- package/dist/rules/no-effect-never.d.ts +0 -6
- package/dist/rules/no-effect-never.js +0 -16
- package/dist/rules/no-effect-orElse-ladder.d.ts +0 -6
- package/dist/rules/no-effect-orElse-ladder.js +0 -53
- package/dist/rules/no-effect-succeed-string.d.ts +0 -6
- package/dist/rules/no-effect-succeed-string.js +0 -30
- package/dist/rules/no-effect-succeed-variable.d.ts +0 -6
- package/dist/rules/no-effect-succeed-variable.js +0 -30
- package/dist/rules/no-effect-succeed-void.d.ts +0 -6
- package/dist/rules/no-effect-succeed-void.js +0 -33
- package/dist/rules/no-effect-sync-console.d.ts +0 -6
- package/dist/rules/no-effect-sync-console.js +0 -63
- package/dist/rules/no-effect-sync-wrapper.d.ts +0 -6
- package/dist/rules/no-effect-sync-wrapper.js +0 -37
- package/dist/rules/no-effect-type-alias.d.ts +0 -6
- package/dist/rules/no-effect-type-alias.js +0 -52
- package/dist/rules/no-effect-wrapper-alias.d.ts +0 -6
- package/dist/rules/no-effect-wrapper-alias.js +0 -69
- package/dist/rules/no-extends-native-error.d.ts +0 -6
- package/dist/rules/no-extends-native-error.js +0 -41
- package/dist/rules/no-flatmap-ladder.d.ts +0 -6
- package/dist/rules/no-flatmap-ladder.js +0 -48
- package/dist/rules/no-fromnullable-coalesce.d.ts +0 -6
- package/dist/rules/no-fromnullable-coalesce.js +0 -36
- package/dist/rules/no-global-error-in-catch.d.ts +0 -6
- package/dist/rules/no-global-error-in-catch.js +0 -75
- package/dist/rules/no-global-error-in-failure.d.ts +0 -6
- package/dist/rules/no-global-error-in-failure.js +0 -54
- package/dist/rules/no-hand-rolled-tagged-union.d.ts +0 -6
- package/dist/rules/no-hand-rolled-tagged-union.js +0 -70
- package/dist/rules/no-if-statement.d.ts +0 -6
- package/dist/rules/no-if-statement.js +0 -18
- package/dist/rules/no-iife-wrapper.d.ts +0 -6
- package/dist/rules/no-iife-wrapper.js +0 -37
- package/dist/rules/no-inline-provide.d.ts +0 -6
- package/dist/rules/no-inline-provide.js +0 -31
- package/dist/rules/no-instanceof-schema.d.ts +0 -6
- package/dist/rules/no-instanceof-schema.js +0 -35
- package/dist/rules/no-make-unsafe.d.ts +0 -6
- package/dist/rules/no-make-unsafe.js +0 -30
- package/dist/rules/no-manual-data-guard.d.ts +0 -6
- package/dist/rules/no-manual-data-guard.js +0 -56
- package/dist/rules/no-manual-effect-channels.d.ts +0 -6
- package/dist/rules/no-manual-effect-channels.js +0 -40
- package/dist/rules/no-match-effect-branch.d.ts +0 -6
- package/dist/rules/no-match-effect-branch.js +0 -64
- package/dist/rules/no-match-void-branch.d.ts +0 -6
- package/dist/rules/no-match-void-branch.js +0 -50
- package/dist/rules/no-multiple-effect-provide.d.ts +0 -6
- package/dist/rules/no-multiple-effect-provide.js +0 -45
- package/dist/rules/no-nested-effect-call.d.ts +0 -6
- package/dist/rules/no-nested-effect-call.js +0 -97
- package/dist/rules/no-nested-effect-gen.d.ts +0 -6
- package/dist/rules/no-nested-effect-gen.js +0 -46
- package/dist/rules/no-nested-pipe.d.ts +0 -6
- package/dist/rules/no-nested-pipe.js +0 -38
- package/dist/rules/no-option-as.d.ts +0 -6
- package/dist/rules/no-option-as.js +0 -16
- package/dist/rules/no-option-boolean-normalization.d.ts +0 -6
- package/dist/rules/no-option-boolean-normalization.js +0 -50
- package/dist/rules/no-positional-log-error.d.ts +0 -6
- package/dist/rules/no-positional-log-error.js +0 -52
- package/dist/rules/no-promise-control-flow-in-tests.d.ts +0 -6
- package/dist/rules/no-promise-control-flow-in-tests.js +0 -94
- package/dist/rules/no-redundant-schema-tag-identifier.d.ts +0 -6
- package/dist/rules/no-redundant-schema-tag-identifier.js +0 -46
- package/dist/rules/no-return-in-arrow.d.ts +0 -6
- package/dist/rules/no-return-in-arrow.js +0 -41
- package/dist/rules/no-return-nullish.d.ts +0 -6
- package/dist/rules/no-return-nullish.js +0 -47
- package/dist/rules/no-run-in-effect-gen.d.ts +0 -6
- package/dist/rules/no-run-in-effect-gen.js +0 -48
- package/dist/rules/no-runtime-run-fork.d.ts +0 -6
- package/dist/rules/no-runtime-run-fork.js +0 -19
- package/dist/rules/no-schema-struct-with-tag.d.ts +0 -6
- package/dist/rules/no-schema-struct-with-tag.js +0 -48
- package/dist/rules/no-schema-struct.d.ts +0 -6
- package/dist/rules/no-schema-struct.js +0 -28
- package/dist/rules/no-schema-union-of-literals.d.ts +0 -6
- package/dist/rules/no-schema-union-of-literals.js +0 -35
- package/dist/rules/no-sleep-in-tests.d.ts +0 -6
- package/dist/rules/no-sleep-in-tests.js +0 -55
- package/dist/rules/no-spread.d.ts +0 -6
- package/dist/rules/no-spread.js +0 -26
- package/dist/rules/no-string-sentinel-const.d.ts +0 -6
- package/dist/rules/no-string-sentinel-const.js +0 -38
- package/dist/rules/no-switch-statement.d.ts +0 -6
- package/dist/rules/no-switch-statement.js +0 -16
- package/dist/rules/no-unnecessary-arrow-block.d.ts +0 -6
- package/dist/rules/no-unnecessary-arrow-block.js +0 -34
- package/dist/rules/no-unnecessary-effect-gen.d.ts +0 -6
- package/dist/rules/no-unnecessary-effect-gen.js +0 -35
- package/dist/rules/no-unnecessary-pipe-chain.d.ts +0 -6
- package/dist/rules/no-unnecessary-pipe-chain.js +0 -36
- package/dist/rules/no-unnecessary-pipe.d.ts +0 -6
- package/dist/rules/no-unnecessary-pipe.js +0 -29
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { isCallOf } from "../vendor/effect-oxlint/AST.js";
|
|
2
|
-
import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
3
|
-
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
4
|
-
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
5
|
-
import "../vendor/effect-oxlint/index.js";
|
|
6
|
-
import * as Effect from "effect/Effect";
|
|
7
|
-
//#region src/rules/no-match-effect-branch.ts
|
|
8
|
-
const sequencingMethods = new Set([
|
|
9
|
-
"flatMap",
|
|
10
|
-
"map",
|
|
11
|
-
"andThen",
|
|
12
|
-
"tap",
|
|
13
|
-
"zipRight",
|
|
14
|
-
"pipe"
|
|
15
|
-
]);
|
|
16
|
-
const containsEffectSequencing = (node) => {
|
|
17
|
-
if (node == null || typeof node !== "object") return false;
|
|
18
|
-
const n = node;
|
|
19
|
-
if (n["type"] === "CallExpression" && "callee" in n) {
|
|
20
|
-
const callee = n["callee"];
|
|
21
|
-
if (callee != null && callee["type"] === "MemberExpression") {
|
|
22
|
-
const obj = callee["object"];
|
|
23
|
-
const prop = callee["property"];
|
|
24
|
-
if (obj != null && obj["type"] === "Identifier" && obj["name"] === "Effect" && prop != null && prop["type"] === "Identifier" && typeof prop["name"] === "string" && sequencingMethods.has(prop["name"])) return true;
|
|
25
|
-
}
|
|
26
|
-
if ("arguments" in n && Array.isArray(n["arguments"])) {
|
|
27
|
-
for (const arg of n["arguments"]) if (containsEffectSequencing(arg)) return true;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if ("body" in n) {
|
|
31
|
-
if (Array.isArray(n["body"])) {
|
|
32
|
-
for (const child of n["body"]) if (containsEffectSequencing(child)) return true;
|
|
33
|
-
} else if (containsEffectSequencing(n["body"])) return true;
|
|
34
|
-
}
|
|
35
|
-
if ("argument" in n && containsEffectSequencing(n["argument"])) return true;
|
|
36
|
-
if ("expression" in n && containsEffectSequencing(n["expression"])) return true;
|
|
37
|
-
return false;
|
|
38
|
-
};
|
|
39
|
-
const noMatchEffectBranch = define({
|
|
40
|
-
name: "no-match-effect-branch",
|
|
41
|
-
meta: meta({
|
|
42
|
-
type: "suggestion",
|
|
43
|
-
description: "Avoid Effect sequencing inside Match branches. Select value in Match, run Effect outside."
|
|
44
|
-
}),
|
|
45
|
-
create: function* () {
|
|
46
|
-
const ctx = yield* RuleContext;
|
|
47
|
-
return { CallExpression: (node) => {
|
|
48
|
-
const ce = node;
|
|
49
|
-
const isMatchWhen = isCallOf(ce, "Match", "when");
|
|
50
|
-
const isMatchOrElse = isCallOf(ce, "Match", "orElse");
|
|
51
|
-
if (!isMatchWhen && !isMatchOrElse) return Effect.void;
|
|
52
|
-
if (!("arguments" in node) || !Array.isArray(node.arguments)) return Effect.void;
|
|
53
|
-
const callbackIdx = isMatchWhen ? 1 : 0;
|
|
54
|
-
const callback = node.arguments[callbackIdx];
|
|
55
|
-
if (callback != null && containsEffectSequencing(callback)) return ctx.report(make({
|
|
56
|
-
node,
|
|
57
|
-
message: "Avoid Effect sequencing inside Match branches. Select value in Match, then run one Effect pipeline outside."
|
|
58
|
-
}));
|
|
59
|
-
return Effect.void;
|
|
60
|
-
} };
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
//#endregion
|
|
64
|
-
export { noMatchEffectBranch };
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { isCallOf } from "../vendor/effect-oxlint/AST.js";
|
|
2
|
-
import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
3
|
-
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
4
|
-
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
5
|
-
import "../vendor/effect-oxlint/index.js";
|
|
6
|
-
import * as Effect from "effect/Effect";
|
|
7
|
-
//#region src/rules/no-match-void-branch.ts
|
|
8
|
-
const isEffectVoid = (node) => {
|
|
9
|
-
if (node == null || typeof node !== "object") return false;
|
|
10
|
-
const n = node;
|
|
11
|
-
if (n["type"] !== "MemberExpression") return false;
|
|
12
|
-
const obj = n["object"];
|
|
13
|
-
const prop = n["property"];
|
|
14
|
-
return obj != null && obj["type"] === "Identifier" && obj["name"] === "Effect" && prop != null && prop["type"] === "Identifier" && prop["name"] === "void";
|
|
15
|
-
};
|
|
16
|
-
const isVoidCallback = (node) => {
|
|
17
|
-
if (node == null || typeof node !== "object") return false;
|
|
18
|
-
const n = node;
|
|
19
|
-
if (n["type"] !== "ArrowFunctionExpression" && n["type"] !== "FunctionExpression") return false;
|
|
20
|
-
if (!("body" in n)) return false;
|
|
21
|
-
return isEffectVoid(n["body"]);
|
|
22
|
-
};
|
|
23
|
-
const noMatchVoidBranch = define({
|
|
24
|
-
name: "no-match-void-branch",
|
|
25
|
-
meta: meta({
|
|
26
|
-
type: "suggestion",
|
|
27
|
-
description: "Avoid no-op Match branches returning Effect.void. Remove the branch or restructure."
|
|
28
|
-
}),
|
|
29
|
-
create: function* () {
|
|
30
|
-
const ctx = yield* RuleContext;
|
|
31
|
-
return { CallExpression: (node) => {
|
|
32
|
-
const ce = node;
|
|
33
|
-
const isMatchWhen = isCallOf(ce, "Match", "when");
|
|
34
|
-
const isMatchOrElse = isCallOf(ce, "Match", "orElse");
|
|
35
|
-
if (!isMatchWhen && !isMatchOrElse) return Effect.void;
|
|
36
|
-
if (!("arguments" in node) || !Array.isArray(node.arguments)) return Effect.void;
|
|
37
|
-
if (isMatchOrElse && node.arguments.length >= 1 && isVoidCallback(node.arguments[0])) return ctx.report(make({
|
|
38
|
-
node,
|
|
39
|
-
message: "Match.orElse returning Effect.void is a no-op. Remove the branch or restructure."
|
|
40
|
-
}));
|
|
41
|
-
if (isMatchWhen && node.arguments.length >= 2 && isVoidCallback(node.arguments[1])) return ctx.report(make({
|
|
42
|
-
node,
|
|
43
|
-
message: "Match.when branch returning Effect.void is a no-op. Remove the branch or restructure."
|
|
44
|
-
}));
|
|
45
|
-
return Effect.void;
|
|
46
|
-
} };
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
//#endregion
|
|
50
|
-
export { noMatchVoidBranch };
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { calleeName, matchCallOf } from "../vendor/effect-oxlint/AST.js";
|
|
2
|
-
import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
3
|
-
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
4
|
-
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
5
|
-
import "../vendor/effect-oxlint/index.js";
|
|
6
|
-
import * as Option from "effect/Option";
|
|
7
|
-
import * as Effect from "effect/Effect";
|
|
8
|
-
//#region src/rules/no-multiple-effect-provide.ts
|
|
9
|
-
const isEffectProvide = (node) => {
|
|
10
|
-
if (node.type !== "CallExpression") return false;
|
|
11
|
-
return Option.isSome(matchCallOf(node, "Effect", "provide"));
|
|
12
|
-
};
|
|
13
|
-
const countProvides = (args) => {
|
|
14
|
-
let count = 0;
|
|
15
|
-
for (const arg of args) if (isEffectProvide(arg)) count++;
|
|
16
|
-
return count;
|
|
17
|
-
};
|
|
18
|
-
const noMultipleEffectProvide = define({
|
|
19
|
-
name: "no-multiple-effect-provide",
|
|
20
|
-
meta: meta({
|
|
21
|
-
type: "suggestion",
|
|
22
|
-
description: "Avoid consecutive Effect.provide() calls. Merge into Effect.provide(Layer.mergeAll(...))."
|
|
23
|
-
}),
|
|
24
|
-
create: function* () {
|
|
25
|
-
const ctx = yield* RuleContext;
|
|
26
|
-
return { CallExpression: (node) => {
|
|
27
|
-
const call = node;
|
|
28
|
-
const args = call.arguments;
|
|
29
|
-
if (args.length < 3) return Effect.void;
|
|
30
|
-
const calleeName$1 = Option.getOrUndefined(calleeName(call));
|
|
31
|
-
const callee = call.callee;
|
|
32
|
-
if (!(calleeName$1 === "pipe" || callee.type === "MemberExpression" && "property" in callee && callee["computed"] !== true && (() => {
|
|
33
|
-
const prop = callee["property"];
|
|
34
|
-
return prop != null && typeof prop === "object" && "name" in prop && prop.name === "pipe";
|
|
35
|
-
})())) return Effect.void;
|
|
36
|
-
if (countProvides(args) >= 2) return ctx.report(make({
|
|
37
|
-
node,
|
|
38
|
-
message: "Multiple Effect.provide() in pipe. Merge into Effect.provide(Layer.mergeAll(...))."
|
|
39
|
-
}));
|
|
40
|
-
return Effect.void;
|
|
41
|
-
} };
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
//#endregion
|
|
45
|
-
export { noMultipleEffectProvide };
|
|
@@ -1,97 +0,0 @@
|
|
|
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 "../vendor/effect-oxlint/index.js";
|
|
5
|
-
import * as Effect from "effect/Effect";
|
|
6
|
-
//#region src/rules/no-nested-effect-call.ts
|
|
7
|
-
const pipelineCombinators = new Set([
|
|
8
|
-
"flatMap",
|
|
9
|
-
"map",
|
|
10
|
-
"mapBoth",
|
|
11
|
-
"mapError",
|
|
12
|
-
"andThen",
|
|
13
|
-
"tap",
|
|
14
|
-
"tapError",
|
|
15
|
-
"tapBoth",
|
|
16
|
-
"zipRight",
|
|
17
|
-
"zipLeft",
|
|
18
|
-
"zip",
|
|
19
|
-
"as",
|
|
20
|
-
"asVoid",
|
|
21
|
-
"catchAll",
|
|
22
|
-
"catchAllCause",
|
|
23
|
-
"catch",
|
|
24
|
-
"catchCause",
|
|
25
|
-
"catchTag",
|
|
26
|
-
"catchTags"
|
|
27
|
-
]);
|
|
28
|
-
const effectAcceptingCombinators = new Set([
|
|
29
|
-
"andThen",
|
|
30
|
-
"tap",
|
|
31
|
-
"zipRight",
|
|
32
|
-
"zipLeft"
|
|
33
|
-
]);
|
|
34
|
-
const effectProducers = new Set([
|
|
35
|
-
"gen",
|
|
36
|
-
"fn",
|
|
37
|
-
"succeed",
|
|
38
|
-
"fail",
|
|
39
|
-
"failCause",
|
|
40
|
-
"sync",
|
|
41
|
-
"promise",
|
|
42
|
-
"tryPromise",
|
|
43
|
-
"try",
|
|
44
|
-
"async",
|
|
45
|
-
"void",
|
|
46
|
-
"die",
|
|
47
|
-
"dieMessage"
|
|
48
|
-
]);
|
|
49
|
-
const getEffectMethodName = (node) => {
|
|
50
|
-
if (node == null || typeof node !== "object") return void 0;
|
|
51
|
-
const n = node;
|
|
52
|
-
if (n["type"] !== "CallExpression") return void 0;
|
|
53
|
-
const callee = n["callee"];
|
|
54
|
-
if (callee == null || typeof callee !== "object") return void 0;
|
|
55
|
-
const c = callee;
|
|
56
|
-
if (c["type"] !== "MemberExpression") return void 0;
|
|
57
|
-
const obj = c["object"];
|
|
58
|
-
const prop = c["property"];
|
|
59
|
-
if (obj == null || typeof obj !== "object") return void 0;
|
|
60
|
-
if (prop == null || typeof prop !== "object") return void 0;
|
|
61
|
-
const o = obj;
|
|
62
|
-
const p = prop;
|
|
63
|
-
if (o["type"] !== "Identifier" || o["name"] !== "Effect") return void 0;
|
|
64
|
-
if (p["type"] !== "Identifier" || typeof p["name"] !== "string") return void 0;
|
|
65
|
-
return p["name"];
|
|
66
|
-
};
|
|
67
|
-
const noNestedEffectCall = define({
|
|
68
|
-
name: "no-nested-effect-call",
|
|
69
|
-
meta: meta({
|
|
70
|
-
type: "suggestion",
|
|
71
|
-
description: "Avoid nested Effect pipeline calls. Compose with .pipe() instead of nesting."
|
|
72
|
-
}),
|
|
73
|
-
create: function* () {
|
|
74
|
-
const ctx = yield* RuleContext;
|
|
75
|
-
return { CallExpression: (node) => {
|
|
76
|
-
const outerName = getEffectMethodName(node);
|
|
77
|
-
if (outerName === void 0 || !pipelineCombinators.has(outerName)) return Effect.void;
|
|
78
|
-
const ce = node;
|
|
79
|
-
if (!("arguments" in ce) || !Array.isArray(ce.arguments)) return Effect.void;
|
|
80
|
-
if (ce.arguments.length < 2) return Effect.void;
|
|
81
|
-
const outerTakesEffect = effectAcceptingCombinators.has(outerName);
|
|
82
|
-
for (const arg of ce.arguments) {
|
|
83
|
-
const innerName = getEffectMethodName(arg);
|
|
84
|
-
if (innerName === void 0) continue;
|
|
85
|
-
const isCallTower = pipelineCombinators.has(innerName);
|
|
86
|
-
const isProducerArg = outerTakesEffect && effectProducers.has(innerName);
|
|
87
|
-
if (isCallTower || isProducerArg) return ctx.report(make({
|
|
88
|
-
node,
|
|
89
|
-
message: `Nested Effect.${outerName}(Effect.${innerName}(...)). Use .pipe(Effect.${innerName}(...), Effect.${outerName}(...)) instead.`
|
|
90
|
-
}));
|
|
91
|
-
}
|
|
92
|
-
return Effect.void;
|
|
93
|
-
} };
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
//#endregion
|
|
97
|
-
export { noNestedEffectCall };
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { isCallOf } from "../vendor/effect-oxlint/AST.js";
|
|
2
|
-
import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
3
|
-
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
4
|
-
import { merge, tracked } from "../vendor/effect-oxlint/Visitor.js";
|
|
5
|
-
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
6
|
-
import "../vendor/effect-oxlint/index.js";
|
|
7
|
-
import * as Effect from "effect/Effect";
|
|
8
|
-
import * as Ref from "effect/Ref";
|
|
9
|
-
//#region src/rules/no-nested-effect-gen.ts
|
|
10
|
-
const parentOf = (node) => node.parent;
|
|
11
|
-
const isDirectlyNestedGen = (node) => {
|
|
12
|
-
const yieldExpr = parentOf(node);
|
|
13
|
-
if (yieldExpr?.type !== "YieldExpression") return false;
|
|
14
|
-
if (yieldExpr.delegate !== true) return false;
|
|
15
|
-
const stmt = parentOf(yieldExpr);
|
|
16
|
-
if (stmt?.type !== "ExpressionStatement") return false;
|
|
17
|
-
const block = parentOf(stmt);
|
|
18
|
-
if (block?.type !== "BlockStatement") return false;
|
|
19
|
-
const fn = parentOf(block);
|
|
20
|
-
if (fn?.type !== "FunctionExpression" && fn?.type !== "ArrowFunctionExpression") return false;
|
|
21
|
-
const call = parentOf(fn);
|
|
22
|
-
if (call?.type !== "CallExpression") return false;
|
|
23
|
-
return isCallOf(call, "Effect", "gen") || isCallOf(call, "Effect", "fn");
|
|
24
|
-
};
|
|
25
|
-
const noNestedEffectGen = define({
|
|
26
|
-
name: "no-nested-effect-gen",
|
|
27
|
-
meta: meta({
|
|
28
|
-
type: "suggestion",
|
|
29
|
-
description: "Avoid nested Effect.gen. Flatten to a single Effect.gen per method."
|
|
30
|
-
}),
|
|
31
|
-
create: function* () {
|
|
32
|
-
const ctx = yield* RuleContext;
|
|
33
|
-
const depth = yield* Ref.make(0);
|
|
34
|
-
return merge(tracked("CallExpression", (node) => isCallOf(node, "Effect", "gen"), depth), { CallExpression: (node) => Effect.flatMap(Ref.get(depth), (d) => {
|
|
35
|
-
if (d <= 1) return Effect.void;
|
|
36
|
-
if (!isCallOf(node, "Effect", "gen")) return Effect.void;
|
|
37
|
-
if (!isDirectlyNestedGen(node)) return Effect.void;
|
|
38
|
-
return ctx.report(make({
|
|
39
|
-
node,
|
|
40
|
-
message: "Nested Effect.gen detected. Flatten to a single Effect.gen."
|
|
41
|
-
}));
|
|
42
|
-
}) });
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
//#endregion
|
|
46
|
-
export { noNestedEffectGen };
|
|
@@ -1,38 +0,0 @@
|
|
|
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 "../vendor/effect-oxlint/index.js";
|
|
5
|
-
import * as Effect from "effect/Effect";
|
|
6
|
-
//#region src/rules/no-nested-pipe.ts
|
|
7
|
-
const isPipeCall = (node) => {
|
|
8
|
-
if (node.type === "CallExpression" && "callee" in node) {
|
|
9
|
-
const callee = node["callee"];
|
|
10
|
-
if (callee != null && typeof callee === "object" && "type" in callee) {
|
|
11
|
-
if (callee.type === "Identifier" && "name" in callee && callee.name === "pipe") return true;
|
|
12
|
-
if (callee.type === "MemberExpression" && "property" in callee && callee.property != null && typeof callee.property === "object" && "type" in callee.property && callee.property.type === "Identifier" && "name" in callee.property && callee.property.name === "pipe") return true;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return false;
|
|
16
|
-
};
|
|
17
|
-
const noNestedPipe = define({
|
|
18
|
-
name: "no-nested-pipe",
|
|
19
|
-
meta: meta({
|
|
20
|
-
type: "suggestion",
|
|
21
|
-
description: "Avoid nested pipe() calls. Flatten into a single pipeline."
|
|
22
|
-
}),
|
|
23
|
-
create: function* () {
|
|
24
|
-
const ctx = yield* RuleContext;
|
|
25
|
-
return { CallExpression: (node) => {
|
|
26
|
-
if (!isPipeCall(node)) return Effect.void;
|
|
27
|
-
if ("arguments" in node && Array.isArray(node.arguments)) {
|
|
28
|
-
for (const arg of node.arguments) if (arg != null && typeof arg === "object" && "type" in arg && isPipeCall(arg)) return ctx.report(make({
|
|
29
|
-
node: arg,
|
|
30
|
-
message: "Nested pipe() detected. Flatten into a single pipeline."
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
|
-
return Effect.void;
|
|
34
|
-
} };
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
//#endregion
|
|
38
|
-
export { noNestedPipe };
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { banMember } from "../vendor/effect-oxlint/Rule.js";
|
|
2
|
-
import "../vendor/effect-oxlint/index.js";
|
|
3
|
-
//#region src/rules/no-option-as.ts
|
|
4
|
-
/**
|
|
5
|
-
* Ban `Option.as`.
|
|
6
|
-
*
|
|
7
|
-
* Use `Option.map` or `Option.match` with explicit value return.
|
|
8
|
-
*
|
|
9
|
-
* Source: biome-effect-linting-rules/no-option-as
|
|
10
|
-
*/
|
|
11
|
-
const noOptionAs = banMember("Option", "as", {
|
|
12
|
-
message: "Avoid Option.as. Use Option.map or Option.match instead.",
|
|
13
|
-
meta: { type: "suggestion" }
|
|
14
|
-
});
|
|
15
|
-
//#endregion
|
|
16
|
-
export { noOptionAs };
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { isCallOf } from "../vendor/effect-oxlint/AST.js";
|
|
2
|
-
import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
3
|
-
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
4
|
-
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
5
|
-
import "../vendor/effect-oxlint/index.js";
|
|
6
|
-
import * as Effect from "effect/Effect";
|
|
7
|
-
//#region src/rules/no-option-boolean-normalization.ts
|
|
8
|
-
const noOptionBooleanNormalization = define({
|
|
9
|
-
name: "no-option-boolean-normalization",
|
|
10
|
-
meta: meta({
|
|
11
|
-
type: "suggestion",
|
|
12
|
-
description: "Avoid Option.match for boolean normalization. Normalize at schema boundary."
|
|
13
|
-
}),
|
|
14
|
-
create: function* () {
|
|
15
|
-
const ctx = yield* RuleContext;
|
|
16
|
-
return { CallExpression: (node) => {
|
|
17
|
-
if (!isCallOf(node, "Option", "match")) return Effect.void;
|
|
18
|
-
if (!("arguments" in node) || !Array.isArray(node.arguments) || node.arguments.length < 2) return Effect.void;
|
|
19
|
-
const opts = node.arguments[1];
|
|
20
|
-
if (opts == null || typeof opts !== "object" || !("type" in opts)) return Effect.void;
|
|
21
|
-
if (opts.type !== "ObjectExpression" || !("properties" in opts) || !Array.isArray(opts.properties)) return Effect.void;
|
|
22
|
-
let hasTripleEqualTrue = false;
|
|
23
|
-
let hasReturnFalse = false;
|
|
24
|
-
for (const prop of opts.properties) {
|
|
25
|
-
if (prop == null || typeof prop !== "object" || !("type" in prop) || prop.type !== "Property") continue;
|
|
26
|
-
const key = prop["key"];
|
|
27
|
-
const value = prop["value"];
|
|
28
|
-
if (key == null || value == null) continue;
|
|
29
|
-
if (key["type"] === "Identifier" && key["name"] === "onSome" && value["type"] === "ArrowFunctionExpression" && "body" in value) {
|
|
30
|
-
const body = value["body"];
|
|
31
|
-
if (body != null && body["type"] === "BinaryExpression" && body["operator"] === "===" && "right" in body) {
|
|
32
|
-
const right = body["right"];
|
|
33
|
-
if (right != null && right["type"] === "Literal" && right["value"] === true) hasTripleEqualTrue = true;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (key["type"] === "Identifier" && key["name"] === "onNone" && value["type"] === "ArrowFunctionExpression" && "body" in value) {
|
|
37
|
-
const body = value["body"];
|
|
38
|
-
if (body != null && body["type"] === "Literal" && body["value"] === false) hasReturnFalse = true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (hasTripleEqualTrue && hasReturnFalse) return ctx.report(make({
|
|
42
|
-
node,
|
|
43
|
-
message: "Avoid Option.match for boolean normalization. Normalize at schema boundary instead."
|
|
44
|
-
}));
|
|
45
|
-
return Effect.void;
|
|
46
|
-
} };
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
//#endregion
|
|
50
|
-
export { noOptionBooleanNormalization };
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { memberNames } from "../vendor/effect-oxlint/AST.js";
|
|
2
|
-
import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
3
|
-
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
4
|
-
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
5
|
-
import "../vendor/effect-oxlint/index.js";
|
|
6
|
-
import * as Option from "effect/Option";
|
|
7
|
-
import * as Effect from "effect/Effect";
|
|
8
|
-
//#region src/rules/no-positional-log-error.ts
|
|
9
|
-
const logMethods = new Set([
|
|
10
|
-
"log",
|
|
11
|
-
"logDebug",
|
|
12
|
-
"logInfo",
|
|
13
|
-
"logWarning",
|
|
14
|
-
"logError",
|
|
15
|
-
"logFatal",
|
|
16
|
-
"logTrace"
|
|
17
|
-
]);
|
|
18
|
-
const noPositionalLogError = define({
|
|
19
|
-
name: "no-positional-log-error",
|
|
20
|
-
meta: meta({
|
|
21
|
-
type: "suggestion",
|
|
22
|
-
description: "Avoid passing errors as positional arguments to Effect.log*. Use Effect.annotateLogs instead."
|
|
23
|
-
}),
|
|
24
|
-
create: function* () {
|
|
25
|
-
const ctx = yield* RuleContext;
|
|
26
|
-
return { CallExpression: (node) => {
|
|
27
|
-
const call = node;
|
|
28
|
-
const names = Option.getOrUndefined(memberNames(call.callee));
|
|
29
|
-
if (names === void 0) return Effect.void;
|
|
30
|
-
const [obj, prop] = names;
|
|
31
|
-
if (obj !== "Effect") return Effect.void;
|
|
32
|
-
if (!logMethods.has(prop)) return Effect.void;
|
|
33
|
-
const args = call.arguments;
|
|
34
|
-
if (args.length < 2) return Effect.void;
|
|
35
|
-
const secondArg = args[1];
|
|
36
|
-
if (secondArg === void 0) return Effect.void;
|
|
37
|
-
if (secondArg.type === "CallExpression") {
|
|
38
|
-
const secondNames = Option.getOrUndefined(memberNames(secondArg.callee));
|
|
39
|
-
if (secondNames !== void 0) {
|
|
40
|
-
const [sObj] = secondNames;
|
|
41
|
-
if (sObj === "Cause") return Effect.void;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return ctx.report(make({
|
|
45
|
-
node: secondArg,
|
|
46
|
-
message: `Avoid passing errors as positional args to Effect.${prop}. Use .pipe(Effect.annotateLogs({ error: String(err) })) or pass a Cause.`
|
|
47
|
-
}));
|
|
48
|
-
} };
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
//#endregion
|
|
52
|
-
export { noPositionalLogError };
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { memberNames } from "../vendor/effect-oxlint/AST.js";
|
|
2
|
-
import { make } from "../vendor/effect-oxlint/Diagnostic.js";
|
|
3
|
-
import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
|
|
4
|
-
import { define, meta } from "../vendor/effect-oxlint/Rule.js";
|
|
5
|
-
import "../vendor/effect-oxlint/index.js";
|
|
6
|
-
import * as Option from "effect/Option";
|
|
7
|
-
import * as Effect from "effect/Effect";
|
|
8
|
-
//#region src/rules/no-promise-control-flow-in-tests.ts
|
|
9
|
-
const isTestFilename = (filename) => /\.test\.tsx?$/.test(filename) || /\/tests\/.*\.[cm]?tsx?$/.test(filename);
|
|
10
|
-
const promiseChainMethods = new Set([
|
|
11
|
-
"then",
|
|
12
|
-
"catch",
|
|
13
|
-
"finally"
|
|
14
|
-
]);
|
|
15
|
-
const promiseStaticMethods = new Set([
|
|
16
|
-
"all",
|
|
17
|
-
"allSettled",
|
|
18
|
-
"any",
|
|
19
|
-
"race",
|
|
20
|
-
"resolve",
|
|
21
|
-
"reject"
|
|
22
|
-
]);
|
|
23
|
-
const isAsyncFunction = (node) => "async" in node && node.async === true;
|
|
24
|
-
const promiseChainMethodName = (node) => {
|
|
25
|
-
if (node.callee.type !== "MemberExpression") return void 0;
|
|
26
|
-
const names = Option.getOrUndefined(memberNames(node.callee));
|
|
27
|
-
if (names === void 0) return void 0;
|
|
28
|
-
const [object, property] = names;
|
|
29
|
-
if (object === "Effect") return void 0;
|
|
30
|
-
return promiseChainMethods.has(property) ? property : void 0;
|
|
31
|
-
};
|
|
32
|
-
const noPromiseControlFlowInTests = define({
|
|
33
|
-
name: "no-promise-control-flow-in-tests",
|
|
34
|
-
meta: meta({
|
|
35
|
-
type: "problem",
|
|
36
|
-
description: "Keep test control flow in Effect instead of async/await, try/finally, or Promise chains."
|
|
37
|
-
}),
|
|
38
|
-
create: function* () {
|
|
39
|
-
const ctx = yield* RuleContext;
|
|
40
|
-
if (!isTestFilename(ctx.filename)) return {};
|
|
41
|
-
return {
|
|
42
|
-
TryStatement: (node) => {
|
|
43
|
-
if (!("finalizer" in node) || node.finalizer == null) return Effect.void;
|
|
44
|
-
return ctx.report(make({
|
|
45
|
-
node,
|
|
46
|
-
message: "Do not use `try/finally` cleanup in tests. Use Effect scopes."
|
|
47
|
-
}));
|
|
48
|
-
},
|
|
49
|
-
FunctionDeclaration: (node) => isAsyncFunction(node) ? ctx.report(make({
|
|
50
|
-
node,
|
|
51
|
-
message: "Do not use async test functions. Return an Effect from the test body."
|
|
52
|
-
})) : Effect.void,
|
|
53
|
-
FunctionExpression: (node) => isAsyncFunction(node) ? ctx.report(make({
|
|
54
|
-
node,
|
|
55
|
-
message: "Do not use async test functions. Return an Effect from the test body."
|
|
56
|
-
})) : Effect.void,
|
|
57
|
-
ArrowFunctionExpression: (node) => isAsyncFunction(node) ? ctx.report(make({
|
|
58
|
-
node,
|
|
59
|
-
message: "Do not use async test functions. Return an Effect from the test body."
|
|
60
|
-
})) : Effect.void,
|
|
61
|
-
AwaitExpression: (node) => ctx.report(make({
|
|
62
|
-
node,
|
|
63
|
-
message: "Do not use `await` in tests. Use `yield*` inside Effect.gen."
|
|
64
|
-
})),
|
|
65
|
-
NewExpression: (node) => {
|
|
66
|
-
const expression = node;
|
|
67
|
-
if (expression.callee.type !== "Identifier" || expression.callee.name !== "Promise") return Effect.void;
|
|
68
|
-
return ctx.report(make({
|
|
69
|
-
node,
|
|
70
|
-
message: "Do not construct Promise directly in tests. Use Effect.async/Effect.promise at real boundaries."
|
|
71
|
-
}));
|
|
72
|
-
},
|
|
73
|
-
CallExpression: (node) => {
|
|
74
|
-
const call = node;
|
|
75
|
-
const chainMethod = promiseChainMethodName(call);
|
|
76
|
-
if (chainMethod !== void 0) return ctx.report(make({
|
|
77
|
-
node,
|
|
78
|
-
message: `Do not use Promise.${chainMethod} style chains in tests. Keep control flow in Effect.`
|
|
79
|
-
}));
|
|
80
|
-
if (call.callee.type !== "MemberExpression") return Effect.void;
|
|
81
|
-
const names = Option.getOrUndefined(memberNames(call.callee));
|
|
82
|
-
if (names === void 0) return Effect.void;
|
|
83
|
-
const [object, property] = names;
|
|
84
|
-
if (object !== "Promise" || !promiseStaticMethods.has(property)) return Effect.void;
|
|
85
|
-
return ctx.report(make({
|
|
86
|
-
node,
|
|
87
|
-
message: `Do not use Promise.${property} in tests. Use Effect concurrency primitives.`
|
|
88
|
-
}));
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
//#endregion
|
|
94
|
-
export { noPromiseControlFlowInTests };
|