oxlint-plugin-effect 0.2.0 → 0.2.2
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/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.js +18 -0
- package/dist/presets/core.d.ts +43 -0
- package/dist/presets/core.js +43 -0
- package/dist/presets/effect-native.d.ts +28 -0
- package/dist/presets/effect-native.js +28 -0
- package/dist/presets/full.d.ts +60 -0
- package/dist/presets/full.js +60 -0
- package/dist/presets/functional.d.ts +21 -0
- package/dist/presets/functional.js +21 -0
- package/dist/presets/index.d.ts +14 -0
- package/dist/presets/index.js +20 -0
- package/dist/presets/strict.d.ts +64 -0
- package/dist/presets/strict.js +64 -0
- package/dist/presets/v3-strict.d.ts +16 -0
- package/dist/presets/v3-strict.js +16 -0
- package/dist/presets/v3.d.ts +18 -0
- package/dist/presets/v3.js +18 -0
- package/dist/rules/_effect-context.d.ts +19 -0
- package/dist/rules/_effect-context.js +22 -0
- package/dist/rules/index.d.ts +66 -0
- package/dist/rules/index.js +124 -0
- package/dist/rules/no-arrow-ladder.d.ts +6 -0
- package/dist/rules/no-arrow-ladder.js +61 -0
- package/dist/rules/no-async-function.d.ts +6 -0
- package/dist/rules/no-async-function.js +46 -0
- package/dist/rules/no-catch-all-to-map-error.d.ts +6 -0
- package/dist/rules/no-catch-all-to-map-error.js +67 -0
- package/dist/rules/no-effect-as.d.ts +6 -0
- package/dist/rules/no-effect-as.js +16 -0
- package/dist/rules/no-effect-async.d.ts +6 -0
- package/dist/rules/no-effect-async.js +16 -0
- package/dist/rules/no-effect-bind.d.ts +6 -0
- package/dist/rules/no-effect-bind.js +16 -0
- package/dist/rules/no-effect-do.d.ts +6 -0
- package/dist/rules/no-effect-do.js +16 -0
- package/dist/rules/no-effect-fn-generator.d.ts +6 -0
- package/dist/rules/no-effect-fn-generator.js +30 -0
- package/dist/rules/no-effect-gen-adapter.d.ts +6 -0
- package/dist/rules/no-effect-gen-adapter.js +30 -0
- package/dist/rules/no-effect-map-flatten.d.ts +6 -0
- package/dist/rules/no-effect-map-flatten.js +53 -0
- package/dist/rules/no-effect-map-void.d.ts +6 -0
- package/dist/rules/no-effect-map-void.js +40 -0
- package/dist/rules/no-effect-never.d.ts +6 -0
- package/dist/rules/no-effect-never.js +16 -0
- package/dist/rules/no-effect-orElse-ladder.d.ts +6 -0
- package/dist/rules/no-effect-orElse-ladder.js +53 -0
- package/dist/rules/no-effect-succeed-string.d.ts +6 -0
- package/dist/rules/no-effect-succeed-string.js +30 -0
- package/dist/rules/no-effect-succeed-variable.d.ts +6 -0
- package/dist/rules/no-effect-succeed-variable.js +30 -0
- package/dist/rules/no-effect-succeed-void.d.ts +6 -0
- package/dist/rules/no-effect-succeed-void.js +33 -0
- package/dist/rules/no-effect-sync-console.d.ts +6 -0
- package/dist/rules/no-effect-sync-console.js +63 -0
- package/dist/rules/no-effect-sync-wrapper.d.ts +6 -0
- package/dist/rules/no-effect-sync-wrapper.js +37 -0
- package/dist/rules/no-effect-type-alias.d.ts +6 -0
- package/dist/rules/no-effect-type-alias.js +52 -0
- package/dist/rules/no-effect-wrapper-alias.d.ts +6 -0
- package/dist/rules/no-effect-wrapper-alias.js +69 -0
- package/dist/rules/no-extends-native-error.d.ts +6 -0
- package/dist/rules/no-extends-native-error.js +41 -0
- package/dist/rules/no-flatmap-ladder.d.ts +6 -0
- package/dist/rules/no-flatmap-ladder.js +48 -0
- package/dist/rules/no-fromnullable-coalesce.d.ts +6 -0
- package/dist/rules/no-fromnullable-coalesce.js +36 -0
- package/dist/rules/no-global-error-in-catch.d.ts +6 -0
- package/dist/rules/no-global-error-in-catch.js +75 -0
- package/dist/rules/no-global-error-in-failure.d.ts +6 -0
- package/dist/rules/no-global-error-in-failure.js +54 -0
- package/dist/rules/no-globals.d.ts +6 -0
- package/dist/rules/no-globals.js +164 -0
- package/dist/rules/no-if-statement.d.ts +6 -0
- package/dist/rules/no-if-statement.js +18 -0
- package/dist/rules/no-iife-wrapper.d.ts +6 -0
- package/dist/rules/no-iife-wrapper.js +37 -0
- package/dist/rules/no-inline-provide.d.ts +6 -0
- package/dist/rules/no-inline-provide.js +31 -0
- package/dist/rules/no-instanceof-schema.d.ts +6 -0
- package/dist/rules/no-instanceof-schema.js +35 -0
- package/dist/rules/no-manual-effect-channels.d.ts +6 -0
- package/dist/rules/no-manual-effect-channels.js +40 -0
- package/dist/rules/no-match-effect-branch.d.ts +6 -0
- package/dist/rules/no-match-effect-branch.js +64 -0
- package/dist/rules/no-match-void-branch.d.ts +6 -0
- package/dist/rules/no-match-void-branch.js +50 -0
- package/dist/rules/no-multiple-effect-provide.d.ts +6 -0
- package/dist/rules/no-multiple-effect-provide.js +45 -0
- package/dist/rules/no-nested-effect-call.d.ts +6 -0
- package/dist/rules/no-nested-effect-call.js +97 -0
- package/dist/rules/no-nested-effect-gen.d.ts +6 -0
- package/dist/rules/no-nested-effect-gen.js +46 -0
- package/dist/rules/no-nested-pipe.d.ts +6 -0
- package/dist/rules/no-nested-pipe.js +38 -0
- package/dist/rules/no-new-error.d.ts +6 -0
- package/dist/rules/no-new-error.js +39 -0
- package/dist/rules/no-new-promise.d.ts +6 -0
- package/dist/rules/no-new-promise.js +38 -0
- package/dist/rules/no-node-builtin-import.d.ts +6 -0
- package/dist/rules/no-node-builtin-import.js +61 -0
- package/dist/rules/no-option-as.d.ts +6 -0
- package/dist/rules/no-option-as.js +16 -0
- package/dist/rules/no-option-boolean-normalization.d.ts +6 -0
- package/dist/rules/no-option-boolean-normalization.js +50 -0
- package/dist/rules/no-positional-log-error.d.ts +6 -0
- package/dist/rules/no-positional-log-error.js +52 -0
- package/dist/rules/no-redundant-schema-tag-identifier.d.ts +6 -0
- package/dist/rules/no-redundant-schema-tag-identifier.js +46 -0
- package/dist/rules/no-return-in-arrow.d.ts +6 -0
- package/dist/rules/no-return-in-arrow.js +41 -0
- package/dist/rules/no-return-nullish.d.ts +6 -0
- package/dist/rules/no-return-nullish.js +47 -0
- package/dist/rules/no-run-in-effect-gen.d.ts +6 -0
- package/dist/rules/no-run-in-effect-gen.js +48 -0
- package/dist/rules/no-runtime-run-fork.d.ts +6 -0
- package/dist/rules/no-runtime-run-fork.js +19 -0
- package/dist/rules/no-schema-struct-with-tag.d.ts +6 -0
- package/dist/rules/no-schema-struct-with-tag.js +48 -0
- package/dist/rules/no-schema-union-of-literals.d.ts +6 -0
- package/dist/rules/no-schema-union-of-literals.js +35 -0
- package/dist/rules/no-string-sentinel-const.d.ts +6 -0
- package/dist/rules/no-string-sentinel-const.js +38 -0
- package/dist/rules/no-switch-statement.d.ts +6 -0
- package/dist/rules/no-switch-statement.js +16 -0
- package/dist/rules/no-ternary.d.ts +6 -0
- package/dist/rules/no-ternary.js +30 -0
- package/dist/rules/no-throw-statement.d.ts +6 -0
- package/dist/rules/no-throw-statement.js +34 -0
- package/dist/rules/no-try-catch.d.ts +6 -0
- package/dist/rules/no-try-catch.js +34 -0
- package/dist/rules/no-unnecessary-arrow-block.d.ts +6 -0
- package/dist/rules/no-unnecessary-arrow-block.js +34 -0
- package/dist/rules/no-unnecessary-effect-gen.d.ts +6 -0
- package/dist/rules/no-unnecessary-effect-gen.js +35 -0
- package/dist/rules/no-unnecessary-pipe-chain.d.ts +6 -0
- package/dist/rules/no-unnecessary-pipe-chain.js +36 -0
- package/dist/rules/no-unnecessary-pipe.d.ts +6 -0
- package/dist/rules/no-unnecessary-pipe.js +29 -0
- package/dist/vendor/effect-oxlint/AST.d.ts +199 -0
- package/dist/vendor/effect-oxlint/AST.js +214 -0
- package/dist/vendor/effect-oxlint/Comment.d.ts +55 -0
- package/dist/vendor/effect-oxlint/Comment.js +67 -0
- package/dist/vendor/effect-oxlint/Diagnostic.d.ts +86 -0
- package/dist/vendor/effect-oxlint/Diagnostic.js +115 -0
- package/dist/vendor/effect-oxlint/Plugin.d.ts +38 -0
- package/dist/vendor/effect-oxlint/Plugin.js +45 -0
- package/dist/vendor/effect-oxlint/Rule.d.ts +218 -0
- package/dist/vendor/effect-oxlint/Rule.js +309 -0
- package/dist/vendor/effect-oxlint/RuleContext.d.ts +77 -0
- package/dist/vendor/effect-oxlint/RuleContext.js +74 -0
- package/dist/vendor/effect-oxlint/Scope.d.ts +100 -0
- package/dist/vendor/effect-oxlint/Scope.js +110 -0
- package/dist/vendor/effect-oxlint/SourceCode.d.ts +180 -0
- package/dist/vendor/effect-oxlint/SourceCode.js +203 -0
- package/dist/vendor/effect-oxlint/Testing.d.ts +510 -0
- package/dist/vendor/effect-oxlint/Testing.js +963 -0
- package/dist/vendor/effect-oxlint/Token.d.ts +98 -0
- package/dist/vendor/effect-oxlint/Token.js +103 -0
- package/dist/vendor/effect-oxlint/Visitor.d.ts +166 -0
- package/dist/vendor/effect-oxlint/Visitor.js +138 -0
- package/dist/vendor/effect-oxlint/index.d.ts +13 -0
- package/dist/vendor/effect-oxlint/index.js +12 -0
- package/package.json +24 -5
- package/.effect-lsp.json +0 -60
- package/.oxlintrc.json +0 -33
- package/CHANGELOG.md +0 -55
- package/scripts/add-rule.ts +0 -87
- package/scripts/codegen.ts +0 -108
- package/src/index.ts +0 -9
- package/src/plugin.ts +0 -16
- package/src/presets/core.ts +0 -47
- package/src/presets/effect-native.ts +0 -34
- package/src/presets/full.ts +0 -70
- package/src/presets/functional.ts +0 -18
- package/src/presets/index.ts +0 -5
- package/src/presets/strict.ts +0 -77
- package/src/rules/_effect-context.ts +0 -28
- package/src/rules/index.ts +0 -72
- package/src/rules/no-arrow-ladder.ts +0 -70
- package/src/rules/no-async-function.ts +0 -47
- package/src/rules/no-catch-all-to-map-error.ts +0 -90
- package/src/rules/no-effect-as.ts +0 -14
- package/src/rules/no-effect-async.ts +0 -14
- package/src/rules/no-effect-bind.ts +0 -13
- package/src/rules/no-effect-do.ts +0 -14
- package/src/rules/no-effect-fn-generator.ts +0 -58
- package/src/rules/no-effect-gen-adapter.ts +0 -51
- package/src/rules/no-effect-map-flatten.ts +0 -82
- package/src/rules/no-effect-map-void.ts +0 -80
- package/src/rules/no-effect-never.ts +0 -14
- package/src/rules/no-effect-orElse-ladder.ts +0 -79
- package/src/rules/no-effect-succeed-string.ts +0 -52
- package/src/rules/no-effect-succeed-variable.ts +0 -50
- package/src/rules/no-effect-succeed-void.ts +0 -63
- package/src/rules/no-effect-sync-console.ts +0 -91
- package/src/rules/no-effect-sync-wrapper.ts +0 -67
- package/src/rules/no-effect-type-alias.ts +0 -71
- package/src/rules/no-effect-wrapper-alias.ts +0 -95
- package/src/rules/no-extends-native-error.ts +0 -54
- package/src/rules/no-flatmap-ladder.ts +0 -77
- package/src/rules/no-fromnullable-coalesce.ts +0 -62
- package/src/rules/no-global-error-in-catch.ts +0 -104
- package/src/rules/no-global-error-in-failure.ts +0 -83
- package/src/rules/no-globals.ts +0 -178
- package/src/rules/no-if-statement.ts +0 -16
- package/src/rules/no-iife-wrapper.ts +0 -51
- package/src/rules/no-inline-provide.ts +0 -41
- package/src/rules/no-instanceof-schema.ts +0 -42
- package/src/rules/no-manual-effect-channels.ts +0 -58
- package/src/rules/no-match-effect-branch.ts +0 -82
- package/src/rules/no-match-void-branch.ts +0 -72
- package/src/rules/no-multiple-effect-provide.ts +0 -75
- package/src/rules/no-nested-effect-call.ts +0 -57
- package/src/rules/no-nested-effect-gen.ts +0 -43
- package/src/rules/no-nested-pipe.ts +0 -73
- package/src/rules/no-new-error.ts +0 -53
- package/src/rules/no-new-promise.ts +0 -50
- package/src/rules/no-node-builtin-import.ts +0 -68
- package/src/rules/no-option-as.ts +0 -13
- package/src/rules/no-option-boolean-normalization.ts +0 -88
- package/src/rules/no-positional-log-error.ts +0 -76
- package/src/rules/no-redundant-schema-tag-identifier.ts +0 -62
- package/src/rules/no-return-in-arrow.ts +0 -76
- package/src/rules/no-return-nullish.ts +0 -80
- package/src/rules/no-run-in-effect-gen.ts +0 -66
- package/src/rules/no-runtime-run-fork.ts +0 -14
- package/src/rules/no-schema-struct-with-tag.ts +0 -64
- package/src/rules/no-schema-union-of-literals.ts +0 -48
- package/src/rules/no-string-sentinel-const.ts +0 -49
- package/src/rules/no-switch-statement.ts +0 -13
- package/src/rules/no-ternary.ts +0 -30
- package/src/rules/no-throw-statement.ts +0 -39
- package/src/rules/no-try-catch.ts +0 -39
- package/src/rules/no-unnecessary-arrow-block.ts +0 -51
- package/src/rules/no-unnecessary-effect-gen.ts +0 -83
- package/src/rules/no-unnecessary-pipe-chain.ts +0 -47
- package/src/rules/no-unnecessary-pipe.ts +0 -45
- package/src/vendor/effect-oxlint/AST.ts +0 -516
- package/src/vendor/effect-oxlint/Comment.ts +0 -86
- package/src/vendor/effect-oxlint/Diagnostic.ts +0 -199
- package/src/vendor/effect-oxlint/Plugin.ts +0 -71
- package/src/vendor/effect-oxlint/Rule.ts +0 -548
- package/src/vendor/effect-oxlint/RuleContext.ts +0 -144
- package/src/vendor/effect-oxlint/Scope.ts +0 -177
- package/src/vendor/effect-oxlint/SourceCode.ts +0 -377
- package/src/vendor/effect-oxlint/Testing.ts +0 -1227
- package/src/vendor/effect-oxlint/Token.ts +0 -131
- package/src/vendor/effect-oxlint/Visitor.ts +0 -287
- package/src/vendor/effect-oxlint/index.ts +0 -88
- package/tests/ban-rules.test.ts +0 -68
- package/tests/context-rules.test.ts +0 -263
- package/tests/pattern-rules.test.ts +0 -247
- package/tsconfig.json +0 -15
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `Match.when(true/false, () => Effect.void)` and `Match.orElse(() => Effect.void)`.
|
|
3
|
-
*
|
|
4
|
-
* Remove the no-op branch or restructure.
|
|
5
|
-
*
|
|
6
|
-
* Source: biome-effect-linting-rules/no-match-void-branch
|
|
7
|
-
*/
|
|
8
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
9
|
-
import { AST, Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
10
|
-
import * as Effect from "effect/Effect"
|
|
11
|
-
|
|
12
|
-
const isEffectVoid = (node: unknown): boolean => {
|
|
13
|
-
if (node == null || typeof node !== "object") return false
|
|
14
|
-
const n = node as Record<string, unknown>
|
|
15
|
-
if (n["type"] !== "MemberExpression") return false
|
|
16
|
-
const obj = n["object"] as Record<string, unknown> | null
|
|
17
|
-
const prop = n["property"] as Record<string, unknown> | null
|
|
18
|
-
return (
|
|
19
|
-
obj != null && obj["type"] === "Identifier" && obj["name"] === "Effect" &&
|
|
20
|
-
prop != null && prop["type"] === "Identifier" && prop["name"] === "void"
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const isVoidCallback = (node: unknown): boolean => {
|
|
25
|
-
if (node == null || typeof node !== "object") return false
|
|
26
|
-
const n = node as Record<string, unknown>
|
|
27
|
-
if (n["type"] !== "ArrowFunctionExpression" && n["type"] !== "FunctionExpression") return false
|
|
28
|
-
if (!("body" in n)) return false
|
|
29
|
-
return isEffectVoid(n["body"])
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export const noMatchVoidBranch = Rule.define({
|
|
33
|
-
name: "no-match-void-branch",
|
|
34
|
-
meta: Rule.meta({
|
|
35
|
-
type: "suggestion",
|
|
36
|
-
description: "Avoid no-op Match branches returning Effect.void. Remove the branch or restructure.",
|
|
37
|
-
}),
|
|
38
|
-
create: function* () {
|
|
39
|
-
const ctx = yield* RuleContext
|
|
40
|
-
return {
|
|
41
|
-
CallExpression: (node) => {
|
|
42
|
-
const ce = node as ESTree.CallExpression
|
|
43
|
-
const isMatchWhen = AST.isCallOf(ce, "Match", "when")
|
|
44
|
-
const isMatchOrElse = AST.isCallOf(ce, "Match", "orElse")
|
|
45
|
-
|
|
46
|
-
if (!isMatchWhen && !isMatchOrElse) return Effect.void
|
|
47
|
-
|
|
48
|
-
if (!("arguments" in node) || !Array.isArray(node.arguments)) return Effect.void
|
|
49
|
-
|
|
50
|
-
if (isMatchOrElse && node.arguments.length >= 1 && isVoidCallback(node.arguments[0])) {
|
|
51
|
-
return ctx.report(
|
|
52
|
-
Diagnostic.make({
|
|
53
|
-
node,
|
|
54
|
-
message: "Match.orElse returning Effect.void is a no-op. Remove the branch or restructure.",
|
|
55
|
-
}),
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (isMatchWhen && node.arguments.length >= 2 && isVoidCallback(node.arguments[1])) {
|
|
60
|
-
return ctx.report(
|
|
61
|
-
Diagnostic.make({
|
|
62
|
-
node,
|
|
63
|
-
message: "Match.when branch returning Effect.void is a no-op. Remove the branch or restructure.",
|
|
64
|
-
}),
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return Effect.void
|
|
69
|
-
},
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
})
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban consecutive `Effect.provide()` calls — merge into one.
|
|
3
|
-
*
|
|
4
|
-
* Detects: `effect.pipe(Effect.provide(A), Effect.provide(B))`
|
|
5
|
-
* Suggests: `effect.pipe(Effect.provide(Layer.mergeAll(A, B)))`
|
|
6
|
-
*
|
|
7
|
-
* This is an AST heuristic — it catches the common patterns without
|
|
8
|
-
* needing the type checker to confirm they're actually Layer values.
|
|
9
|
-
*
|
|
10
|
-
* Source: language-service/multipleEffectProvide
|
|
11
|
-
*/
|
|
12
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
13
|
-
import { AST, Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
14
|
-
import * as Effect from "effect/Effect"
|
|
15
|
-
import * as Option from "effect/Option"
|
|
16
|
-
|
|
17
|
-
const isEffectProvide = (node: ESTree.Node): boolean => {
|
|
18
|
-
if (node.type !== "CallExpression") return false
|
|
19
|
-
return Option.isSome(AST.matchCallOf(node as ESTree.CallExpression, "Effect", "provide"))
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const countProvides = (args: ReadonlyArray<ESTree.Node>): number => {
|
|
23
|
-
let count = 0
|
|
24
|
-
for (const arg of args) {
|
|
25
|
-
if (isEffectProvide(arg as ESTree.Node)) count++
|
|
26
|
-
}
|
|
27
|
-
return count
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const noMultipleEffectProvide = Rule.define({
|
|
31
|
-
name: "no-multiple-effect-provide",
|
|
32
|
-
meta: Rule.meta({
|
|
33
|
-
type: "suggestion",
|
|
34
|
-
description:
|
|
35
|
-
"Avoid consecutive Effect.provide() calls. Merge into Effect.provide(Layer.mergeAll(...)).",
|
|
36
|
-
}),
|
|
37
|
-
create: function* () {
|
|
38
|
-
const ctx = yield* RuleContext
|
|
39
|
-
return {
|
|
40
|
-
CallExpression: (node) => {
|
|
41
|
-
const call = node as ESTree.CallExpression
|
|
42
|
-
const args = call.arguments
|
|
43
|
-
if (args.length < 3) return Effect.void
|
|
44
|
-
|
|
45
|
-
// Check bare pipe() or .pipe() with multiple Effect.provide args
|
|
46
|
-
const calleeName = Option.getOrUndefined(AST.calleeName(call))
|
|
47
|
-
const callee = call.callee
|
|
48
|
-
|
|
49
|
-
const isPipe =
|
|
50
|
-
calleeName === "pipe" ||
|
|
51
|
-
(callee.type === "MemberExpression" &&
|
|
52
|
-
"property" in callee &&
|
|
53
|
-
(callee as unknown as Record<string, unknown>)["computed"] !== true &&
|
|
54
|
-
(() => {
|
|
55
|
-
const prop = (callee as unknown as Record<string, unknown>)["property"]
|
|
56
|
-
return prop != null && typeof prop === "object" && "name" in prop && prop.name === "pipe"
|
|
57
|
-
})())
|
|
58
|
-
|
|
59
|
-
if (!isPipe) return Effect.void
|
|
60
|
-
|
|
61
|
-
if (countProvides(args as ReadonlyArray<ESTree.Node>) >= 2) {
|
|
62
|
-
return ctx.report(
|
|
63
|
-
Diagnostic.make({
|
|
64
|
-
node,
|
|
65
|
-
message:
|
|
66
|
-
"Multiple Effect.provide() in pipe. Merge into Effect.provide(Layer.mergeAll(...)).",
|
|
67
|
-
}),
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return Effect.void
|
|
72
|
-
},
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
})
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban nested Effect.* calls — `Effect.outer(Effect.inner(...))`.
|
|
3
|
-
*
|
|
4
|
-
* Build values first, then one flat pipeline.
|
|
5
|
-
*
|
|
6
|
-
* Sources: biome-effect-linting-rules/no-nested-effect-call, no-effect-call-in-effect-arg, no-call-tower, no-effect-ladder
|
|
7
|
-
*/
|
|
8
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
9
|
-
import { Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
10
|
-
import * as Effect from "effect/Effect"
|
|
11
|
-
|
|
12
|
-
const isEffectCall = (node: unknown): boolean => {
|
|
13
|
-
if (node == null || typeof node !== "object") return false
|
|
14
|
-
const n = node as Record<string, unknown>
|
|
15
|
-
if (n["type"] !== "CallExpression") return false
|
|
16
|
-
const callee = n["callee"]
|
|
17
|
-
if (callee == null || typeof callee !== "object") return false
|
|
18
|
-
const c = callee as Record<string, unknown>
|
|
19
|
-
if (c["type"] !== "MemberExpression") return false
|
|
20
|
-
const obj = c["object"]
|
|
21
|
-
if (obj == null || typeof obj !== "object") return false
|
|
22
|
-
const o = obj as Record<string, unknown>
|
|
23
|
-
return o["type"] === "Identifier" && o["name"] === "Effect"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const noNestedEffectCall = Rule.define({
|
|
27
|
-
name: "no-nested-effect-call",
|
|
28
|
-
meta: Rule.meta({
|
|
29
|
-
type: "suggestion",
|
|
30
|
-
description:
|
|
31
|
-
"Avoid nesting Effect.* calls. Build values first, then use a flat pipeline.",
|
|
32
|
-
}),
|
|
33
|
-
create: function* () {
|
|
34
|
-
const ctx = yield* RuleContext
|
|
35
|
-
return {
|
|
36
|
-
CallExpression: (node) => {
|
|
37
|
-
if (!isEffectCall(node)) return Effect.void
|
|
38
|
-
|
|
39
|
-
const ce = node as ESTree.CallExpression
|
|
40
|
-
if (!("arguments" in ce) || !Array.isArray(ce.arguments)) return Effect.void
|
|
41
|
-
|
|
42
|
-
for (const arg of ce.arguments) {
|
|
43
|
-
if (isEffectCall(arg)) {
|
|
44
|
-
return ctx.report(
|
|
45
|
-
Diagnostic.make({
|
|
46
|
-
node,
|
|
47
|
-
message:
|
|
48
|
-
"Nested Effect.* call detected. Build values first, then use a flat pipeline.",
|
|
49
|
-
}),
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return Effect.void
|
|
54
|
-
},
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
})
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban nested `Effect.gen` calls.
|
|
3
|
-
*
|
|
4
|
-
* An `Effect.gen` inside another `Effect.gen` should be flattened.
|
|
5
|
-
*
|
|
6
|
-
* Sources: biome-effect-linting-rules/no-nested-effect-gen, language-service/nestedEffectGenYield
|
|
7
|
-
*/
|
|
8
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
9
|
-
import { AST, Diagnostic, Rule, Visitor, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
10
|
-
import * as Effect from "effect/Effect"
|
|
11
|
-
import * as Ref from "effect/Ref"
|
|
12
|
-
|
|
13
|
-
export const noNestedEffectGen = Rule.define({
|
|
14
|
-
name: "no-nested-effect-gen",
|
|
15
|
-
meta: Rule.meta({
|
|
16
|
-
type: "suggestion",
|
|
17
|
-
description:
|
|
18
|
-
"Avoid nested Effect.gen. Flatten to a single Effect.gen per method.",
|
|
19
|
-
}),
|
|
20
|
-
create: function* () {
|
|
21
|
-
const ctx = yield* RuleContext
|
|
22
|
-
const depth = yield* Ref.make(0)
|
|
23
|
-
|
|
24
|
-
return Visitor.merge(
|
|
25
|
-
Visitor.tracked("CallExpression", (node) => AST.isCallOf(node, "Effect", "gen"), depth),
|
|
26
|
-
{
|
|
27
|
-
CallExpression: (node) =>
|
|
28
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
29
|
-
if (d > 1 && AST.isCallOf(node as ESTree.CallExpression, "Effect", "gen")) {
|
|
30
|
-
return ctx.report(
|
|
31
|
-
Diagnostic.make({
|
|
32
|
-
node,
|
|
33
|
-
message:
|
|
34
|
-
"Nested Effect.gen detected. Flatten to a single Effect.gen.",
|
|
35
|
-
}),
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
return Effect.void
|
|
39
|
-
}),
|
|
40
|
-
},
|
|
41
|
-
)
|
|
42
|
-
},
|
|
43
|
-
})
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban nested `pipe()` calls — `pipe(pipe(...), ...)` or `.pipe(...).pipe(...)`.
|
|
3
|
-
*
|
|
4
|
-
* Flatten into a single pipeline.
|
|
5
|
-
*
|
|
6
|
-
* Sources: biome-effect-linting-rules/no-pipe-ladder, language-service/unnecessaryPipeChain
|
|
7
|
-
*/
|
|
8
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
9
|
-
import { Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
10
|
-
import * as Effect from "effect/Effect"
|
|
11
|
-
|
|
12
|
-
const isPipeCall = (node: ESTree.Node): boolean => {
|
|
13
|
-
if (node.type === "CallExpression" && "callee" in node) {
|
|
14
|
-
const callee = (node as unknown as Record<string, unknown>)["callee"]
|
|
15
|
-
if (callee != null && typeof callee === "object" && "type" in callee) {
|
|
16
|
-
// pipe(...)
|
|
17
|
-
if (callee.type === "Identifier" && "name" in callee && callee.name === "pipe") {
|
|
18
|
-
return true
|
|
19
|
-
}
|
|
20
|
-
// x.pipe(...)
|
|
21
|
-
if (
|
|
22
|
-
callee.type === "MemberExpression" &&
|
|
23
|
-
"property" in callee &&
|
|
24
|
-
callee.property != null &&
|
|
25
|
-
typeof callee.property === "object" &&
|
|
26
|
-
"type" in callee.property &&
|
|
27
|
-
callee.property.type === "Identifier" &&
|
|
28
|
-
"name" in callee.property &&
|
|
29
|
-
callee.property.name === "pipe"
|
|
30
|
-
) {
|
|
31
|
-
return true
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return false
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const noNestedPipe = Rule.define({
|
|
39
|
-
name: "no-nested-pipe",
|
|
40
|
-
meta: Rule.meta({
|
|
41
|
-
type: "suggestion",
|
|
42
|
-
description: "Avoid nested pipe() calls. Flatten into a single pipeline.",
|
|
43
|
-
}),
|
|
44
|
-
create: function* () {
|
|
45
|
-
const ctx = yield* RuleContext
|
|
46
|
-
return {
|
|
47
|
-
CallExpression: (node) => {
|
|
48
|
-
if (!isPipeCall(node)) return Effect.void
|
|
49
|
-
|
|
50
|
-
// Check if any argument is itself a pipe call
|
|
51
|
-
if ("arguments" in node && Array.isArray(node.arguments)) {
|
|
52
|
-
for (const arg of node.arguments) {
|
|
53
|
-
if (
|
|
54
|
-
arg != null &&
|
|
55
|
-
typeof arg === "object" &&
|
|
56
|
-
"type" in arg &&
|
|
57
|
-
isPipeCall(arg as unknown as ESTree.Node)
|
|
58
|
-
) {
|
|
59
|
-
return ctx.report(
|
|
60
|
-
Diagnostic.make({
|
|
61
|
-
node: arg,
|
|
62
|
-
message:
|
|
63
|
-
"Nested pipe() detected. Flatten into a single pipeline.",
|
|
64
|
-
}),
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return Effect.void
|
|
70
|
-
},
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
})
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `new Error(...)` and native error constructors. Context-aware messaging.
|
|
3
|
-
*
|
|
4
|
-
* Inside Effect.gen/fn: "Use yield* new MyError() (yieldable TaggedErrorClass)"
|
|
5
|
-
* Outside: "Define class MyError extends Schema.TaggedErrorClass..."
|
|
6
|
-
*
|
|
7
|
-
* Source: language-service/globalErrorInEffectFailure, extendsNativeError
|
|
8
|
-
*/
|
|
9
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
10
|
-
import { Diagnostic, Rule, Visitor, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
11
|
-
import * as Effect from "effect/Effect"
|
|
12
|
-
import * as Ref from "effect/Ref"
|
|
13
|
-
|
|
14
|
-
import { makeEffectContextTracker } from "./_effect-context.js"
|
|
15
|
-
|
|
16
|
-
const nativeErrors = new Set([
|
|
17
|
-
"Error", "TypeError", "RangeError", "ReferenceError", "SyntaxError",
|
|
18
|
-
"URIError", "EvalError",
|
|
19
|
-
])
|
|
20
|
-
|
|
21
|
-
export const noNewError = Rule.define({
|
|
22
|
-
name: "no-new-error",
|
|
23
|
-
meta: Rule.meta({
|
|
24
|
-
type: "suggestion",
|
|
25
|
-
description: "Avoid native Error constructors. Use Schema.TaggedErrorClass.",
|
|
26
|
-
}),
|
|
27
|
-
create: function* () {
|
|
28
|
-
const ctx = yield* RuleContext
|
|
29
|
-
const [depth, tracker] = yield* makeEffectContextTracker
|
|
30
|
-
|
|
31
|
-
return Visitor.merge(tracker, {
|
|
32
|
-
NewExpression: (node) =>
|
|
33
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
34
|
-
const callee = (node as unknown as Record<string, unknown>)["callee"] as ESTree.Node
|
|
35
|
-
if (
|
|
36
|
-
callee.type === "Identifier" &&
|
|
37
|
-
"name" in callee &&
|
|
38
|
-
nativeErrors.has(callee.name as string)
|
|
39
|
-
) {
|
|
40
|
-
return ctx.report(
|
|
41
|
-
Diagnostic.make({
|
|
42
|
-
node,
|
|
43
|
-
message: d > 0
|
|
44
|
-
? "Avoid native Error inside Effect.gen/fn. Define: class MyError extends Schema.TaggedErrorClass<MyError>()(\"MyError\", { ... }) {} — then yield* new MyError()."
|
|
45
|
-
: "Avoid native Error constructors. Define: class MyError extends Schema.TaggedErrorClass<MyError>()(\"MyError\", { message: Schema.String }) {}",
|
|
46
|
-
}),
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
return Effect.void
|
|
50
|
-
}),
|
|
51
|
-
})
|
|
52
|
-
},
|
|
53
|
-
})
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `new Promise()`. Context-aware messaging.
|
|
3
|
-
*
|
|
4
|
-
* Inside Effect.gen/fn: "Use Effect.tryPromise or Effect.async"
|
|
5
|
-
* Outside: "Use Effect.tryPromise or wrap with Effect.fn"
|
|
6
|
-
*
|
|
7
|
-
* Source: language-service/newPromise
|
|
8
|
-
*/
|
|
9
|
-
import { Diagnostic, Rule, Visitor, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
10
|
-
import * as Effect from "effect/Effect"
|
|
11
|
-
import * as Ref from "effect/Ref"
|
|
12
|
-
|
|
13
|
-
import { makeEffectContextTracker } from "./_effect-context.js"
|
|
14
|
-
|
|
15
|
-
export const noNewPromise = Rule.define({
|
|
16
|
-
name: "no-new-promise",
|
|
17
|
-
meta: Rule.meta({
|
|
18
|
-
type: "suggestion",
|
|
19
|
-
description: "Avoid new Promise(). Use Effect.async or Effect.tryPromise.",
|
|
20
|
-
}),
|
|
21
|
-
create: function* () {
|
|
22
|
-
const ctx = yield* RuleContext
|
|
23
|
-
const [depth, tracker] = yield* makeEffectContextTracker
|
|
24
|
-
|
|
25
|
-
return Visitor.merge(tracker, {
|
|
26
|
-
NewExpression: (node) =>
|
|
27
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
28
|
-
const callee = (node as unknown as Record<string, unknown>)["callee"]
|
|
29
|
-
if (
|
|
30
|
-
callee != null &&
|
|
31
|
-
typeof callee === "object" &&
|
|
32
|
-
"type" in callee &&
|
|
33
|
-
callee.type === "Identifier" &&
|
|
34
|
-
"name" in callee &&
|
|
35
|
-
callee.name === "Promise"
|
|
36
|
-
) {
|
|
37
|
-
return ctx.report(
|
|
38
|
-
Diagnostic.make({
|
|
39
|
-
node,
|
|
40
|
-
message: d > 0
|
|
41
|
-
? "Avoid new Promise() inside Effect.gen/fn. Use Effect.async for callback APIs, Effect.tryPromise for existing promises, or Effect.promise for infallible promises."
|
|
42
|
-
: "Avoid new Promise(). Use Effect.async for callback APIs, Effect.tryPromise for existing promises — wrap this function with Effect.fn.",
|
|
43
|
-
}),
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
return Effect.void
|
|
47
|
-
}),
|
|
48
|
-
})
|
|
49
|
-
},
|
|
50
|
-
})
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban Node.js builtin imports.
|
|
3
|
-
*
|
|
4
|
-
* Use Effect platform services: FileSystem, Path, HttpClient, Command.
|
|
5
|
-
*
|
|
6
|
-
* Source: language-service/nodeBuiltinImport
|
|
7
|
-
*/
|
|
8
|
-
import { AST, Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
9
|
-
import * as Effect from "effect/Effect"
|
|
10
|
-
import * as Option from "effect/Option"
|
|
11
|
-
|
|
12
|
-
const hints: Record<string, string> = {
|
|
13
|
-
fs: "Use FileSystem from 'effect'",
|
|
14
|
-
path: "Use Path from 'effect'",
|
|
15
|
-
http: "Use HttpClient from 'effect/unstable/http'",
|
|
16
|
-
https: "Use HttpClient from 'effect/unstable/http'",
|
|
17
|
-
child_process: "Use Command from 'effect'",
|
|
18
|
-
crypto: "Use a service wrapper",
|
|
19
|
-
os: "Use PlatformInfo from 'effect'",
|
|
20
|
-
net: "Use HttpServer from 'effect/unstable/http'",
|
|
21
|
-
dns: "Use a service wrapper",
|
|
22
|
-
tls: "Use a service wrapper",
|
|
23
|
-
dgram: "Use a service wrapper",
|
|
24
|
-
stream: "Use Stream from 'effect'",
|
|
25
|
-
zlib: "Use a service wrapper",
|
|
26
|
-
readline: "Use Terminal from 'effect'",
|
|
27
|
-
cluster: "Use a service wrapper",
|
|
28
|
-
worker_threads: "Use a service wrapper",
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const nodeBuiltins = new Set(Object.keys(hints))
|
|
32
|
-
|
|
33
|
-
const resolveModule = (source: string): string | undefined => {
|
|
34
|
-
const name = source.startsWith("node:") ? source.slice(5) : source
|
|
35
|
-
const base = name.split("/")[0]
|
|
36
|
-
return base !== undefined && nodeBuiltins.has(base) ? base : undefined
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const noNodeBuiltinImport = Rule.define({
|
|
40
|
-
name: "no-node-builtin-import",
|
|
41
|
-
meta: Rule.meta({
|
|
42
|
-
type: "suggestion",
|
|
43
|
-
description:
|
|
44
|
-
"Avoid Node.js builtin imports. Use Effect platform services.",
|
|
45
|
-
}),
|
|
46
|
-
create: function* () {
|
|
47
|
-
const ctx = yield* RuleContext
|
|
48
|
-
return {
|
|
49
|
-
ImportDeclaration: (node) => {
|
|
50
|
-
const source = Option.getOrUndefined(
|
|
51
|
-
AST.narrow(node, "ImportDeclaration").pipe(Option.map(AST.importSource)),
|
|
52
|
-
)
|
|
53
|
-
if (source === undefined) return Effect.void
|
|
54
|
-
|
|
55
|
-
const mod = resolveModule(source)
|
|
56
|
-
if (mod === undefined) return Effect.void
|
|
57
|
-
|
|
58
|
-
const hint = hints[mod] ?? "Use an Effect service wrapper"
|
|
59
|
-
return ctx.report(
|
|
60
|
-
Diagnostic.make({
|
|
61
|
-
node,
|
|
62
|
-
message: `Avoid importing '${source}'. ${hint}.`,
|
|
63
|
-
}),
|
|
64
|
-
)
|
|
65
|
-
},
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
})
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `Option.as`.
|
|
3
|
-
*
|
|
4
|
-
* Use `Option.map` or `Option.match` with explicit value return.
|
|
5
|
-
*
|
|
6
|
-
* Source: biome-effect-linting-rules/no-option-as
|
|
7
|
-
*/
|
|
8
|
-
import { Rule } from "../vendor/effect-oxlint/index.js"
|
|
9
|
-
|
|
10
|
-
export const noOptionAs = Rule.banMember("Option", "as", {
|
|
11
|
-
message: "Avoid Option.as. Use Option.map or Option.match instead.",
|
|
12
|
-
meta: { type: "suggestion" },
|
|
13
|
-
})
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `Option.match(x, { onSome: (v) => v === true, onNone: () => false })`.
|
|
3
|
-
*
|
|
4
|
-
* Normalize at the schema boundary instead.
|
|
5
|
-
*
|
|
6
|
-
* Source: biome-effect-linting-rules/no-option-boolean-normalization
|
|
7
|
-
*/
|
|
8
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
9
|
-
import { AST, Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
10
|
-
import * as Effect from "effect/Effect"
|
|
11
|
-
|
|
12
|
-
export const noOptionBooleanNormalization = Rule.define({
|
|
13
|
-
name: "no-option-boolean-normalization",
|
|
14
|
-
meta: Rule.meta({
|
|
15
|
-
type: "suggestion",
|
|
16
|
-
description:
|
|
17
|
-
"Avoid Option.match for boolean normalization. Normalize at schema boundary.",
|
|
18
|
-
}),
|
|
19
|
-
create: function* () {
|
|
20
|
-
const ctx = yield* RuleContext
|
|
21
|
-
return {
|
|
22
|
-
CallExpression: (node) => {
|
|
23
|
-
if (!AST.isCallOf(node as ESTree.CallExpression, "Option", "match")) return Effect.void
|
|
24
|
-
|
|
25
|
-
if (!("arguments" in node) || !Array.isArray(node.arguments) || node.arguments.length < 2)
|
|
26
|
-
return Effect.void
|
|
27
|
-
|
|
28
|
-
const opts = node.arguments[1]
|
|
29
|
-
if (opts == null || typeof opts !== "object" || !("type" in opts)) return Effect.void
|
|
30
|
-
if (opts.type !== "ObjectExpression" || !("properties" in opts) || !Array.isArray(opts.properties))
|
|
31
|
-
return Effect.void
|
|
32
|
-
|
|
33
|
-
let hasTripleEqualTrue = false
|
|
34
|
-
let hasReturnFalse = false
|
|
35
|
-
|
|
36
|
-
for (const prop of opts.properties) {
|
|
37
|
-
if (prop == null || typeof prop !== "object" || !("type" in prop) || prop.type !== "Property")
|
|
38
|
-
continue
|
|
39
|
-
const key = (prop as unknown as Record<string, unknown>)["key"] as unknown as Record<string, unknown> | null
|
|
40
|
-
const value = (prop as unknown as Record<string, unknown>)["value"] as unknown as Record<string, unknown> | null
|
|
41
|
-
|
|
42
|
-
if (key == null || value == null) continue
|
|
43
|
-
|
|
44
|
-
// onSome: (v) => v === true
|
|
45
|
-
if (
|
|
46
|
-
key["type"] === "Identifier" && key["name"] === "onSome" &&
|
|
47
|
-
value["type"] === "ArrowFunctionExpression" && "body" in value
|
|
48
|
-
) {
|
|
49
|
-
const body = value["body"] as unknown as Record<string, unknown> | null
|
|
50
|
-
if (
|
|
51
|
-
body != null &&
|
|
52
|
-
body["type"] === "BinaryExpression" &&
|
|
53
|
-
body["operator"] === "===" &&
|
|
54
|
-
"right" in body
|
|
55
|
-
) {
|
|
56
|
-
const right = body["right"] as unknown as Record<string, unknown> | null
|
|
57
|
-
if (right != null && right["type"] === "Literal" && right["value"] === true) {
|
|
58
|
-
hasTripleEqualTrue = true
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// onNone: () => false
|
|
64
|
-
if (
|
|
65
|
-
key["type"] === "Identifier" && key["name"] === "onNone" &&
|
|
66
|
-
value["type"] === "ArrowFunctionExpression" && "body" in value
|
|
67
|
-
) {
|
|
68
|
-
const body = value["body"] as unknown as Record<string, unknown> | null
|
|
69
|
-
if (body != null && body["type"] === "Literal" && body["value"] === false) {
|
|
70
|
-
hasReturnFalse = true
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (hasTripleEqualTrue && hasReturnFalse) {
|
|
76
|
-
return ctx.report(
|
|
77
|
-
Diagnostic.make({
|
|
78
|
-
node,
|
|
79
|
-
message:
|
|
80
|
-
"Avoid Option.match for boolean normalization. Normalize at schema boundary instead.",
|
|
81
|
-
}),
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
return Effect.void
|
|
85
|
-
},
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
})
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban positional error/cause arguments in Effect.log* calls.
|
|
3
|
-
*
|
|
4
|
-
* `Effect.logWarning("message", error)` loses error structure.
|
|
5
|
-
* Use `Effect.logWarning("message").pipe(Effect.annotateLogs({ error }))` instead,
|
|
6
|
-
* or pass cause via `Effect.logWarning("message", Cause.fail(error))`.
|
|
7
|
-
*
|
|
8
|
-
* Source: agent session analysis — 57 occurrences across 6 projects
|
|
9
|
-
*/
|
|
10
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
11
|
-
import { AST, Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
12
|
-
import * as Effect from "effect/Effect"
|
|
13
|
-
import * as Option from "effect/Option"
|
|
14
|
-
|
|
15
|
-
const logMethods = new Set([
|
|
16
|
-
"log",
|
|
17
|
-
"logDebug",
|
|
18
|
-
"logInfo",
|
|
19
|
-
"logWarning",
|
|
20
|
-
"logError",
|
|
21
|
-
"logFatal",
|
|
22
|
-
"logTrace",
|
|
23
|
-
])
|
|
24
|
-
|
|
25
|
-
export const noPositionalLogError = Rule.define({
|
|
26
|
-
name: "no-positional-log-error",
|
|
27
|
-
meta: Rule.meta({
|
|
28
|
-
type: "suggestion",
|
|
29
|
-
description:
|
|
30
|
-
"Avoid passing errors as positional arguments to Effect.log*. Use Effect.annotateLogs instead.",
|
|
31
|
-
}),
|
|
32
|
-
create: function* () {
|
|
33
|
-
const ctx = yield* RuleContext
|
|
34
|
-
return {
|
|
35
|
-
CallExpression: (node) => {
|
|
36
|
-
const call = node as ESTree.CallExpression
|
|
37
|
-
const names = Option.getOrUndefined(
|
|
38
|
-
AST.memberNames(call.callee as ESTree.MemberExpression),
|
|
39
|
-
)
|
|
40
|
-
if (names === undefined) return Effect.void
|
|
41
|
-
const [obj, prop] = names
|
|
42
|
-
if (obj !== "Effect") return Effect.void
|
|
43
|
-
if (!logMethods.has(prop)) return Effect.void
|
|
44
|
-
|
|
45
|
-
// Effect.log* with more than 1 argument — the second arg is likely
|
|
46
|
-
// an error/cause being passed positionally
|
|
47
|
-
const args = call.arguments
|
|
48
|
-
if (args.length < 2) return Effect.void
|
|
49
|
-
|
|
50
|
-
// The first arg should be a string message. If second arg exists,
|
|
51
|
-
// it's likely an error being passed positionally.
|
|
52
|
-
const secondArg = args[1]
|
|
53
|
-
if (secondArg === undefined) return Effect.void
|
|
54
|
-
|
|
55
|
-
// Allow Cause.fail/Cause.die as second arg (that's the proper API)
|
|
56
|
-
if (secondArg.type === "CallExpression") {
|
|
57
|
-
const secondNames = Option.getOrUndefined(
|
|
58
|
-
AST.memberNames((secondArg as ESTree.CallExpression).callee as ESTree.MemberExpression),
|
|
59
|
-
)
|
|
60
|
-
if (secondNames !== undefined) {
|
|
61
|
-
const [sObj] = secondNames
|
|
62
|
-
if (sObj === "Cause") return Effect.void
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return ctx.report(
|
|
67
|
-
Diagnostic.make({
|
|
68
|
-
node: secondArg,
|
|
69
|
-
message:
|
|
70
|
-
`Avoid passing errors as positional args to Effect.${prop}. Use .pipe(Effect.annotateLogs({ error: String(err) })) or pass a Cause.`,
|
|
71
|
-
}),
|
|
72
|
-
)
|
|
73
|
-
},
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
})
|