oxlint-plugin-effect 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +13 -0
- package/dist/presets/index.js +18 -0
- package/dist/presets/strict.d.ts +69 -0
- package/dist/presets/strict.js +69 -0
- package/dist/presets/v3.d.ts +15 -0
- package/dist/presets/v3.js +15 -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 -37
- 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 -89
- 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 -50
- 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.ts +0 -18
- 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 -528
- 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 -85
- package/tests/context-rules.test.ts +0 -198
- package/tests/pattern-rules.test.ts +0 -247
- package/tsconfig.json +0 -15
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `new Error()` inside Effect catch handlers.
|
|
3
|
-
*
|
|
4
|
-
* Catches: `.catchAll(e => new Error(...))`, `.catch(e => new Error(...))`
|
|
5
|
-
* Use Schema.TaggedErrorClass or Data.TaggedError instead.
|
|
6
|
-
*
|
|
7
|
-
* Source: language-service/globalErrorInEffectCatch
|
|
8
|
-
*/
|
|
9
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
10
|
-
import { AST, Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
11
|
-
import * as Effect from "effect/Effect"
|
|
12
|
-
import * as Option from "effect/Option"
|
|
13
|
-
|
|
14
|
-
const nativeErrors = new Set([
|
|
15
|
-
"Error",
|
|
16
|
-
"TypeError",
|
|
17
|
-
"RangeError",
|
|
18
|
-
"ReferenceError",
|
|
19
|
-
"SyntaxError",
|
|
20
|
-
"URIError",
|
|
21
|
-
"EvalError",
|
|
22
|
-
])
|
|
23
|
-
|
|
24
|
-
const catchMethods = new Set([
|
|
25
|
-
"catch",
|
|
26
|
-
"catchAll",
|
|
27
|
-
"catchAllCause",
|
|
28
|
-
"catchIf",
|
|
29
|
-
"catchCauseIf",
|
|
30
|
-
"catchTag",
|
|
31
|
-
"catchTags",
|
|
32
|
-
"catchDefect",
|
|
33
|
-
])
|
|
34
|
-
|
|
35
|
-
const isNativeErrorNew = (node: ESTree.Node): boolean => {
|
|
36
|
-
if (node.type !== "NewExpression") return false
|
|
37
|
-
const callee = (node as unknown as Record<string, unknown>)["callee"] as ESTree.Node
|
|
38
|
-
return callee.type === "Identifier" && "name" in callee && nativeErrors.has(callee.name as string)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Check if a function body returns `new Error(...)`.
|
|
43
|
-
*/
|
|
44
|
-
const bodyReturnsNativeError = (fn: ESTree.Node): boolean => {
|
|
45
|
-
const body = (fn as unknown as Record<string, unknown>)["body"] as ESTree.Node
|
|
46
|
-
// Arrow expression body: (e) => new Error(...)
|
|
47
|
-
if (body.type !== "BlockStatement") {
|
|
48
|
-
return isNativeErrorNew(body)
|
|
49
|
-
}
|
|
50
|
-
// Block body: (e) => { return new Error(...) }
|
|
51
|
-
const stmts = (body as unknown as Record<string, unknown>)["body"]
|
|
52
|
-
if (!Array.isArray(stmts) || stmts.length !== 1) return false
|
|
53
|
-
const stmt = stmts[0] as ESTree.Node
|
|
54
|
-
if (stmt.type !== "ReturnStatement") return false
|
|
55
|
-
const arg = (stmt as unknown as Record<string, unknown>)["argument"]
|
|
56
|
-
return arg != null && typeof arg === "object" && isNativeErrorNew(arg as ESTree.Node)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const noGlobalErrorInCatch = Rule.define({
|
|
60
|
-
name: "no-global-error-in-catch",
|
|
61
|
-
meta: Rule.meta({
|
|
62
|
-
type: "suggestion",
|
|
63
|
-
description:
|
|
64
|
-
"Avoid native Error in Effect catch handlers. Use Schema.TaggedErrorClass or Data.TaggedError.",
|
|
65
|
-
}),
|
|
66
|
-
create: function* () {
|
|
67
|
-
const ctx = yield* RuleContext
|
|
68
|
-
return {
|
|
69
|
-
CallExpression: (node) => {
|
|
70
|
-
const call = node as ESTree.CallExpression
|
|
71
|
-
const names = Option.getOrUndefined(
|
|
72
|
-
AST.memberNames(call.callee as ESTree.MemberExpression),
|
|
73
|
-
)
|
|
74
|
-
if (names === undefined) return Effect.void
|
|
75
|
-
const [obj, prop] = names
|
|
76
|
-
if (obj !== "Effect") return Effect.void
|
|
77
|
-
if (!catchMethods.has(prop)) return Effect.void
|
|
78
|
-
|
|
79
|
-
// The callback is the last argument
|
|
80
|
-
const args = call.arguments
|
|
81
|
-
if (args.length === 0) return Effect.void
|
|
82
|
-
const callback = args[args.length - 1]
|
|
83
|
-
if (callback === undefined) return Effect.void
|
|
84
|
-
if (
|
|
85
|
-
callback.type !== "ArrowFunctionExpression" &&
|
|
86
|
-
callback.type !== "FunctionExpression"
|
|
87
|
-
) {
|
|
88
|
-
return Effect.void
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (bodyReturnsNativeError(callback as ESTree.Node)) {
|
|
92
|
-
return ctx.report(
|
|
93
|
-
Diagnostic.make({
|
|
94
|
-
node: callback,
|
|
95
|
-
message:
|
|
96
|
-
"Avoid native Error in Effect catch handler. Use Schema.TaggedErrorClass or Data.TaggedError.",
|
|
97
|
-
}),
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
return Effect.void
|
|
101
|
-
},
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
})
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `new Error()` inside `Effect.fail()`.
|
|
3
|
-
*
|
|
4
|
-
* Use Schema.TaggedErrorClass or Data.TaggedError instead.
|
|
5
|
-
*
|
|
6
|
-
* Source: language-service/globalErrorInEffectFailure
|
|
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
|
-
import * as Option from "effect/Option"
|
|
12
|
-
|
|
13
|
-
const nativeErrors = new Set([
|
|
14
|
-
"Error",
|
|
15
|
-
"TypeError",
|
|
16
|
-
"RangeError",
|
|
17
|
-
"ReferenceError",
|
|
18
|
-
"SyntaxError",
|
|
19
|
-
"URIError",
|
|
20
|
-
"EvalError",
|
|
21
|
-
])
|
|
22
|
-
|
|
23
|
-
const isNativeErrorNew = (node: ESTree.Node): boolean => {
|
|
24
|
-
if (node.type !== "NewExpression") return false
|
|
25
|
-
const callee = (node as unknown as Record<string, unknown>)["callee"] as ESTree.Node
|
|
26
|
-
return callee.type === "Identifier" && "name" in callee && nativeErrors.has(callee.name as string)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const noGlobalErrorInFailure = Rule.define({
|
|
30
|
-
name: "no-global-error-in-failure",
|
|
31
|
-
meta: Rule.meta({
|
|
32
|
-
type: "suggestion",
|
|
33
|
-
description:
|
|
34
|
-
"Avoid native Error in Effect.fail(). Use Schema.TaggedErrorClass or Data.TaggedError.",
|
|
35
|
-
}),
|
|
36
|
-
create: function* () {
|
|
37
|
-
const ctx = yield* RuleContext
|
|
38
|
-
return {
|
|
39
|
-
CallExpression: (node) => {
|
|
40
|
-
const call = node as ESTree.CallExpression
|
|
41
|
-
if (
|
|
42
|
-
Option.isNone(AST.matchCallOf(call, "Effect", "fail")) &&
|
|
43
|
-
Option.isNone(AST.matchCallOf(call, "Effect", "failSync")) &&
|
|
44
|
-
Option.isNone(AST.matchCallOf(call, "Effect", "failCause"))
|
|
45
|
-
) {
|
|
46
|
-
return Effect.void
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const args = call.arguments
|
|
50
|
-
if (args.length === 0) return Effect.void
|
|
51
|
-
|
|
52
|
-
// Check direct arg: Effect.fail(new Error(...))
|
|
53
|
-
const arg = args[args.length - 1]
|
|
54
|
-
if (arg === undefined) return Effect.void
|
|
55
|
-
if (isNativeErrorNew(arg as ESTree.Node)) {
|
|
56
|
-
return ctx.report(
|
|
57
|
-
Diagnostic.make({
|
|
58
|
-
node: arg,
|
|
59
|
-
message:
|
|
60
|
-
"Avoid native Error in Effect.fail(). Use Schema.TaggedErrorClass or Data.TaggedError.",
|
|
61
|
-
}),
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Check arrow body: Effect.failSync(() => new Error(...))
|
|
66
|
-
if (arg.type === "ArrowFunctionExpression") {
|
|
67
|
-
const body = (arg as unknown as Record<string, unknown>)["body"] as ESTree.Node
|
|
68
|
-
if (body.type !== "BlockStatement" && isNativeErrorNew(body)) {
|
|
69
|
-
return ctx.report(
|
|
70
|
-
Diagnostic.make({
|
|
71
|
-
node: arg,
|
|
72
|
-
message:
|
|
73
|
-
"Avoid native Error in Effect.failSync(). Use Schema.TaggedErrorClass or Data.TaggedError.",
|
|
74
|
-
}),
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return Effect.void
|
|
80
|
-
},
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
})
|
package/src/rules/no-globals.ts
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban global/platform APIs inside Effect.gen/fn context.
|
|
3
|
-
*
|
|
4
|
-
* Consolidates: console, fetch(), Date/new Date, Math.random/crypto,
|
|
5
|
-
* setTimeout/setInterval, JSON.parse/stringify, process/Bun/Deno globals
|
|
6
|
-
*
|
|
7
|
-
* Each has a specific Effect alternative hint.
|
|
8
|
-
*
|
|
9
|
-
* Sources: language-service globalConsoleInEffect, globalFetchInEffect,
|
|
10
|
-
* globalDateInEffect, globalRandomInEffect, globalTimersInEffect,
|
|
11
|
-
* preferSchemaOverJson, processEnvInEffect, cryptoRandomUUIDInEffect
|
|
12
|
-
*/
|
|
13
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
14
|
-
import { AST, Diagnostic, Rule, Visitor, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
15
|
-
import * as Effect from "effect/Effect"
|
|
16
|
-
import * as Option from "effect/Option"
|
|
17
|
-
import * as Ref from "effect/Ref"
|
|
18
|
-
|
|
19
|
-
import { makeEffectContextTracker } from "./_effect-context.js"
|
|
20
|
-
|
|
21
|
-
// --- Member expression bans (obj.prop) ---
|
|
22
|
-
|
|
23
|
-
type MemberBan = readonly [obj: string, props: string | ReadonlyArray<string>, message: string]
|
|
24
|
-
|
|
25
|
-
const memberBans: ReadonlyArray<MemberBan> = [
|
|
26
|
-
// console
|
|
27
|
-
["console", ["log", "warn", "error", "info", "debug", "trace"], "Avoid console.* inside Effect context. Use Effect.log / Effect.logWarning / Effect.logError."],
|
|
28
|
-
// Date
|
|
29
|
-
["Date", "now", "Avoid Date.now() inside Effect context. Use Clock service from 'effect'."],
|
|
30
|
-
// JSON
|
|
31
|
-
["JSON", ["parse", "stringify"], "Avoid JSON.parse/stringify inside Effect context. Use Schema.fromJsonString."],
|
|
32
|
-
// Math.random
|
|
33
|
-
["Math", "random", "Avoid Math.random() inside Effect context. Use Random service from 'effect'."],
|
|
34
|
-
// crypto
|
|
35
|
-
["crypto", ["randomUUID", "getRandomValues"], "Avoid crypto.randomUUID() inside Effect context. Use Random service from 'effect'."],
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
// --- Platform globals (Bun.*, process.*, Deno.*) ---
|
|
39
|
-
|
|
40
|
-
const platformObjects = new Set(["Bun", "process", "Deno"])
|
|
41
|
-
|
|
42
|
-
const alternatives: Record<string, Record<string, string>> = {
|
|
43
|
-
Bun: {
|
|
44
|
-
file: "Use FileSystem from 'effect'",
|
|
45
|
-
write: "Use FileSystem from 'effect'",
|
|
46
|
-
read: "Use FileSystem from 'effect'",
|
|
47
|
-
serve: "Use HttpServer from 'effect/unstable/http'",
|
|
48
|
-
env: "Use Config from 'effect'",
|
|
49
|
-
spawn: "Use Command from 'effect'",
|
|
50
|
-
sleep: "Use Effect.sleep",
|
|
51
|
-
stdin: "Use Terminal from 'effect'",
|
|
52
|
-
stdout: "Use Terminal from 'effect'",
|
|
53
|
-
stderr: "Use Terminal from 'effect'",
|
|
54
|
-
cwd: "Use Path from 'effect'",
|
|
55
|
-
which: "Use Command from 'effect'",
|
|
56
|
-
resolveSync: "Use Path from 'effect'",
|
|
57
|
-
pathToFileURL: "Use Path from 'effect'",
|
|
58
|
-
randomUUID: "Use Random service from 'effect'",
|
|
59
|
-
randomUUIDv7: "Use Random service from 'effect'",
|
|
60
|
-
},
|
|
61
|
-
process: {
|
|
62
|
-
env: "Use Config from 'effect'",
|
|
63
|
-
exit: "Use Effect.fail or Effect.die",
|
|
64
|
-
cwd: "Use Path from 'effect'",
|
|
65
|
-
argv: "Use Args/Command from 'effect/unstable/cli'",
|
|
66
|
-
stdin: "Use Terminal from 'effect'",
|
|
67
|
-
stdout: "Use Terminal from 'effect'",
|
|
68
|
-
stderr: "Use Terminal from 'effect'",
|
|
69
|
-
chdir: "Use Path from 'effect'",
|
|
70
|
-
kill: "Use Command from 'effect'",
|
|
71
|
-
on: "Use Effect.async or Stream.async",
|
|
72
|
-
platform: "Use PlatformInfo from 'effect'",
|
|
73
|
-
arch: "Use PlatformInfo from 'effect'",
|
|
74
|
-
execPath: "Use Command from 'effect'",
|
|
75
|
-
execArgv: "Use Args/Command from 'effect/unstable/cli'",
|
|
76
|
-
},
|
|
77
|
-
Deno: {
|
|
78
|
-
readFile: "Use FileSystem from 'effect'",
|
|
79
|
-
writeFile: "Use FileSystem from 'effect'",
|
|
80
|
-
readTextFile: "Use FileSystem from 'effect'",
|
|
81
|
-
writeTextFile: "Use FileSystem from 'effect'",
|
|
82
|
-
open: "Use FileSystem from 'effect'",
|
|
83
|
-
remove: "Use FileSystem from 'effect'",
|
|
84
|
-
mkdir: "Use FileSystem from 'effect'",
|
|
85
|
-
readDir: "Use FileSystem from 'effect'",
|
|
86
|
-
stat: "Use FileSystem from 'effect'",
|
|
87
|
-
cwd: "Use Path from 'effect'",
|
|
88
|
-
env: "Use Config from 'effect'",
|
|
89
|
-
exit: "Use Effect.fail or Effect.die",
|
|
90
|
-
serve: "Use HttpServer from 'effect/unstable/http'",
|
|
91
|
-
run: "Use Command from 'effect'",
|
|
92
|
-
args: "Use Args/Command from 'effect/unstable/cli'",
|
|
93
|
-
},
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function getHint(obj: string, prop: string): string {
|
|
97
|
-
const hint = alternatives[obj]?.[prop]
|
|
98
|
-
if (hint) return `. ${hint}`
|
|
99
|
-
return ". Wrap in a service for testability"
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// --- Call expression bans (bare function calls) ---
|
|
103
|
-
|
|
104
|
-
const callBans: Record<string, string> = {
|
|
105
|
-
fetch: "Avoid fetch() inside Effect context. Use HttpClient from 'effect/unstable/http'.",
|
|
106
|
-
setTimeout: "Avoid setTimeout inside Effect context. Use Effect.sleep or Schedule.",
|
|
107
|
-
setInterval: "Avoid setInterval inside Effect context. Use Effect.sleep or Schedule.",
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export const noGlobals = Rule.define({
|
|
111
|
-
name: "no-globals",
|
|
112
|
-
meta: Rule.meta({
|
|
113
|
-
type: "suggestion",
|
|
114
|
-
description:
|
|
115
|
-
"Avoid global/platform APIs inside Effect.gen/fn. Use Effect platform services instead.",
|
|
116
|
-
}),
|
|
117
|
-
create: function* () {
|
|
118
|
-
const ctx = yield* RuleContext
|
|
119
|
-
const [depth, tracker] = yield* makeEffectContextTracker
|
|
120
|
-
|
|
121
|
-
return Visitor.merge(tracker, {
|
|
122
|
-
// Member expressions: console.log, Date.now, JSON.parse, Math.random, process.env, Bun.*, etc.
|
|
123
|
-
MemberExpression: (node) =>
|
|
124
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
125
|
-
if (d <= 0) return Effect.void
|
|
126
|
-
const memberNode = node as ESTree.MemberExpression
|
|
127
|
-
|
|
128
|
-
// Check specific member bans
|
|
129
|
-
for (const [obj, props, message] of memberBans) {
|
|
130
|
-
if (Option.isSome(AST.matchMember(memberNode, obj, props))) {
|
|
131
|
-
return ctx.report(Diagnostic.make({ node, message }))
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Check platform globals (Bun.*, process.*, Deno.*)
|
|
136
|
-
const names = Option.getOrUndefined(AST.memberNames(memberNode))
|
|
137
|
-
if (names !== undefined) {
|
|
138
|
-
const [obj, prop] = names
|
|
139
|
-
if (platformObjects.has(obj)) {
|
|
140
|
-
return ctx.report(
|
|
141
|
-
Diagnostic.make({
|
|
142
|
-
node,
|
|
143
|
-
message: `Avoid ${obj}.${prop} inside Effect context${getHint(obj, prop)}.`,
|
|
144
|
-
}),
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return Effect.void
|
|
150
|
-
}),
|
|
151
|
-
|
|
152
|
-
// Call expressions: fetch(), setTimeout(), setInterval()
|
|
153
|
-
CallExpression: (node) =>
|
|
154
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
155
|
-
if (d <= 0) return Effect.void
|
|
156
|
-
const name = Option.getOrUndefined(AST.calleeName(node as ESTree.CallExpression))
|
|
157
|
-
const message = name !== undefined ? callBans[name] : undefined
|
|
158
|
-
if (message !== undefined) {
|
|
159
|
-
return ctx.report(Diagnostic.make({ node, message }))
|
|
160
|
-
}
|
|
161
|
-
return Effect.void
|
|
162
|
-
}),
|
|
163
|
-
|
|
164
|
-
// New expressions: new Date()
|
|
165
|
-
NewExpression: (node) =>
|
|
166
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
167
|
-
if (d <= 0) return Effect.void
|
|
168
|
-
const callee = (node as unknown as Record<string, unknown>)["callee"] as ESTree.Node
|
|
169
|
-
if (callee.type === "Identifier" && "name" in callee && callee.name === "Date") {
|
|
170
|
-
return ctx.report(
|
|
171
|
-
Diagnostic.make({ node, message: "Avoid new Date() inside Effect context. Use Clock service from 'effect'." }),
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
return Effect.void
|
|
175
|
-
}),
|
|
176
|
-
})
|
|
177
|
-
},
|
|
178
|
-
})
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `if` statements in Effect codebases.
|
|
3
|
-
*
|
|
4
|
-
* Use `Option.match`, `Either.match`, `Match.value`, or data combinators.
|
|
5
|
-
*
|
|
6
|
-
* Source: biome-effect-linting-rules/no-if-statement
|
|
7
|
-
*
|
|
8
|
-
* Note: Very opinionated — recommended for strict functional presets only.
|
|
9
|
-
*/
|
|
10
|
-
import { Rule } from "../vendor/effect-oxlint/index.js"
|
|
11
|
-
|
|
12
|
-
export const noIfStatement = Rule.banStatement("IfStatement", {
|
|
13
|
-
message:
|
|
14
|
-
"Avoid if statements. Use Option.match, Either.match, Match.value, or Effect.if.",
|
|
15
|
-
meta: { type: "suggestion" },
|
|
16
|
-
})
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban immediately-invoked function expressions (IIFEs).
|
|
3
|
-
*
|
|
4
|
-
* Use named const binding + flat pipeline.
|
|
5
|
-
*
|
|
6
|
-
* Source: biome-effect-linting-rules/no-iife-wrapper
|
|
7
|
-
*/
|
|
8
|
-
import { Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
9
|
-
import * as Effect from "effect/Effect"
|
|
10
|
-
|
|
11
|
-
export const noIifeWrapper = Rule.define({
|
|
12
|
-
name: "no-iife-wrapper",
|
|
13
|
-
meta: Rule.meta({
|
|
14
|
-
type: "suggestion",
|
|
15
|
-
description: "Avoid IIFEs. Use named const bindings + flat pipeline.",
|
|
16
|
-
}),
|
|
17
|
-
create: function* () {
|
|
18
|
-
const ctx = yield* RuleContext
|
|
19
|
-
return {
|
|
20
|
-
CallExpression: (node) => {
|
|
21
|
-
if (!("callee" in node)) return Effect.void
|
|
22
|
-
const callee = node.callee
|
|
23
|
-
if (callee == null || typeof callee !== "object" || !("type" in callee)) return Effect.void
|
|
24
|
-
|
|
25
|
-
const isWrappedFn =
|
|
26
|
-
callee.type === "ArrowFunctionExpression" ||
|
|
27
|
-
callee.type === "FunctionExpression"
|
|
28
|
-
|
|
29
|
-
// Also catch (expr)(...) where expr is parenthesized fn
|
|
30
|
-
const isParenWrapped =
|
|
31
|
-
callee.type === "ParenthesizedExpression" &&
|
|
32
|
-
"expression" in callee &&
|
|
33
|
-
callee.expression != null &&
|
|
34
|
-
typeof callee.expression === "object" &&
|
|
35
|
-
"type" in callee.expression &&
|
|
36
|
-
(callee.expression.type === "ArrowFunctionExpression" ||
|
|
37
|
-
callee.expression.type === "FunctionExpression")
|
|
38
|
-
|
|
39
|
-
if (isWrappedFn || isParenWrapped) {
|
|
40
|
-
return ctx.report(
|
|
41
|
-
Diagnostic.make({
|
|
42
|
-
node,
|
|
43
|
-
message: "Avoid IIFEs. Use named const binding + flat pipeline.",
|
|
44
|
-
}),
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
return Effect.void
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
})
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `Effect.provide(layer)` used inline inside Effect.gen/fn.
|
|
3
|
-
*
|
|
4
|
-
* Provide layers at the boundary, not deep in generators.
|
|
5
|
-
*
|
|
6
|
-
* Source: biome-effect-linting-rules/no-inline-runtime-provide
|
|
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
|
-
import { makeEffectContextTracker } from "./_effect-context.js"
|
|
14
|
-
|
|
15
|
-
export const noInlineProvide = Rule.define({
|
|
16
|
-
name: "no-inline-provide",
|
|
17
|
-
meta: Rule.meta({
|
|
18
|
-
type: "suggestion",
|
|
19
|
-
description:
|
|
20
|
-
"Avoid Effect.provide inside Effect.gen/fn. Provide layers at the boundary.",
|
|
21
|
-
}),
|
|
22
|
-
create: function* () {
|
|
23
|
-
const ctx = yield* RuleContext
|
|
24
|
-
const [depth, tracker] = yield* makeEffectContextTracker
|
|
25
|
-
|
|
26
|
-
return Visitor.merge(tracker, {
|
|
27
|
-
CallExpression: (node) =>
|
|
28
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
29
|
-
if (d <= 0) return Effect.void
|
|
30
|
-
if (!AST.isCallOf(node as ESTree.CallExpression, "Effect", "provide")) return Effect.void
|
|
31
|
-
|
|
32
|
-
return ctx.report(
|
|
33
|
-
Diagnostic.make({
|
|
34
|
-
node,
|
|
35
|
-
message: "Avoid Effect.provide inside Effect.gen/fn. Provide layers at the boundary.",
|
|
36
|
-
}),
|
|
37
|
-
)
|
|
38
|
-
}),
|
|
39
|
-
})
|
|
40
|
-
},
|
|
41
|
-
})
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `instanceof` checks against Schema-derived classes.
|
|
3
|
-
*
|
|
4
|
-
* Use `Schema.is(SchemaType)` instead.
|
|
5
|
-
*
|
|
6
|
-
* Source: language-service/instanceOfSchema
|
|
7
|
-
*
|
|
8
|
-
* Note: Without type information, we detect common Effect Schema class names
|
|
9
|
-
* used in `instanceof` checks. This is a best-effort heuristic.
|
|
10
|
-
*/
|
|
11
|
-
import { Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
12
|
-
import * as Effect from "effect/Effect"
|
|
13
|
-
|
|
14
|
-
export const noInstanceofSchema = Rule.define({
|
|
15
|
-
name: "no-instanceof-schema",
|
|
16
|
-
meta: Rule.meta({
|
|
17
|
-
type: "suggestion",
|
|
18
|
-
description:
|
|
19
|
-
"Avoid instanceof with Schema types. Use Schema.is(SchemaType) instead.",
|
|
20
|
-
}),
|
|
21
|
-
create: function* () {
|
|
22
|
-
const ctx = yield* RuleContext
|
|
23
|
-
return {
|
|
24
|
-
BinaryExpression: (node) => {
|
|
25
|
-
if (
|
|
26
|
-
"operator" in node &&
|
|
27
|
-
node.operator === "instanceof"
|
|
28
|
-
) {
|
|
29
|
-
// Report all instanceof — in Effect codebases, instanceof is generally wrong
|
|
30
|
-
return ctx.report(
|
|
31
|
-
Diagnostic.make({
|
|
32
|
-
node,
|
|
33
|
-
message:
|
|
34
|
-
"Avoid instanceof. Add a static `is` field to your class: `static is = Schema.is(this)`, then use `MyClass.is(value)`. For tagged types, use `_tag` checks.",
|
|
35
|
-
}),
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
return Effect.void
|
|
39
|
-
},
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
})
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban manual `Effect.Effect<A, E, R>` type annotations.
|
|
3
|
-
*
|
|
4
|
-
* Let return types infer from the Effect/Layer you return.
|
|
5
|
-
*
|
|
6
|
-
* Source: biome-effect-linting-rules/no-manual-effect-channels
|
|
7
|
-
*/
|
|
8
|
-
import { Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
9
|
-
import * as Effect from "effect/Effect"
|
|
10
|
-
|
|
11
|
-
export const noManualEffectChannels = Rule.define({
|
|
12
|
-
name: "no-manual-effect-channels",
|
|
13
|
-
meta: Rule.meta({
|
|
14
|
-
type: "suggestion",
|
|
15
|
-
description:
|
|
16
|
-
"Avoid manual Effect.Effect<A, E, R> annotations. Let types infer from the returned Effect.",
|
|
17
|
-
}),
|
|
18
|
-
create: function* () {
|
|
19
|
-
const ctx = yield* RuleContext
|
|
20
|
-
return {
|
|
21
|
-
TSTypeReference: (node) => {
|
|
22
|
-
if (!("typeName" in node) || node.typeName == null) return Effect.void
|
|
23
|
-
const typeName = node.typeName as unknown as Record<string, unknown>
|
|
24
|
-
|
|
25
|
-
// Effect.Effect<...> or Layer.Layer<...>
|
|
26
|
-
if (typeName["type"] === "TSQualifiedName" && "left" in typeName && "right" in typeName) {
|
|
27
|
-
const left = typeName["left"] as unknown as Record<string, unknown> | null
|
|
28
|
-
const right = typeName["right"] as unknown as Record<string, unknown> | null
|
|
29
|
-
if (
|
|
30
|
-
left != null && left["type"] === "Identifier" &&
|
|
31
|
-
(left["name"] === "Effect" || left["name"] === "Layer") &&
|
|
32
|
-
right != null && right["type"] === "Identifier" &&
|
|
33
|
-
(right["name"] === "Effect" || right["name"] === "Layer")
|
|
34
|
-
) {
|
|
35
|
-
// Only flag if explicit type parameters are provided
|
|
36
|
-
if (
|
|
37
|
-
"typeParameters" in node &&
|
|
38
|
-
node.typeParameters != null &&
|
|
39
|
-
typeof node.typeParameters === "object" &&
|
|
40
|
-
"params" in (node.typeParameters as unknown as Record<string, unknown>) &&
|
|
41
|
-
Array.isArray((node.typeParameters as unknown as Record<string, unknown>)["params"]) &&
|
|
42
|
-
((node.typeParameters as unknown as Record<string, unknown>)["params"] as unknown[]).length > 0
|
|
43
|
-
) {
|
|
44
|
-
return ctx.report(
|
|
45
|
-
Diagnostic.make({
|
|
46
|
-
node,
|
|
47
|
-
message:
|
|
48
|
-
"Avoid manual Effect/Layer channel annotations. Let types infer from the returned value.",
|
|
49
|
-
}),
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return Effect.void
|
|
55
|
-
},
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
})
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban Match.when/orElse branches that contain multi-step Effect sequencing.
|
|
3
|
-
*
|
|
4
|
-
* Select the value in Match, then run one Effect pipeline outside.
|
|
5
|
-
*
|
|
6
|
-
* Source: biome-effect-linting-rules/no-match-effect-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 sequencingMethods = new Set(["flatMap", "map", "andThen", "tap", "zipRight", "pipe"])
|
|
13
|
-
|
|
14
|
-
const containsEffectSequencing = (node: unknown): boolean => {
|
|
15
|
-
if (node == null || typeof node !== "object") return false
|
|
16
|
-
const n = node as Record<string, unknown>
|
|
17
|
-
if (n["type"] === "CallExpression" && "callee" in n) {
|
|
18
|
-
const callee = n["callee"] as Record<string, unknown> | null
|
|
19
|
-
if (callee != null && callee["type"] === "MemberExpression") {
|
|
20
|
-
const obj = callee["object"] as Record<string, unknown> | null
|
|
21
|
-
const prop = callee["property"] as Record<string, unknown> | null
|
|
22
|
-
if (
|
|
23
|
-
obj != null && obj["type"] === "Identifier" && obj["name"] === "Effect" &&
|
|
24
|
-
prop != null && prop["type"] === "Identifier" && typeof prop["name"] === "string" &&
|
|
25
|
-
sequencingMethods.has(prop["name"])
|
|
26
|
-
) {
|
|
27
|
-
return true
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if ("arguments" in n && Array.isArray(n["arguments"])) {
|
|
31
|
-
for (const arg of n["arguments"]) {
|
|
32
|
-
if (containsEffectSequencing(arg)) return true
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if ("body" in n) {
|
|
37
|
-
if (Array.isArray(n["body"])) {
|
|
38
|
-
for (const child of n["body"]) {
|
|
39
|
-
if (containsEffectSequencing(child)) return true
|
|
40
|
-
}
|
|
41
|
-
} else if (containsEffectSequencing(n["body"])) return true
|
|
42
|
-
}
|
|
43
|
-
if ("argument" in n && containsEffectSequencing(n["argument"])) return true
|
|
44
|
-
if ("expression" in n && containsEffectSequencing(n["expression"])) return true
|
|
45
|
-
return false
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export const noMatchEffectBranch = Rule.define({
|
|
49
|
-
name: "no-match-effect-branch",
|
|
50
|
-
meta: Rule.meta({
|
|
51
|
-
type: "suggestion",
|
|
52
|
-
description:
|
|
53
|
-
"Avoid Effect sequencing inside Match branches. Select value in Match, run Effect outside.",
|
|
54
|
-
}),
|
|
55
|
-
create: function* () {
|
|
56
|
-
const ctx = yield* RuleContext
|
|
57
|
-
return {
|
|
58
|
-
CallExpression: (node) => {
|
|
59
|
-
const ce = node as ESTree.CallExpression
|
|
60
|
-
const isMatchWhen = AST.isCallOf(ce, "Match", "when")
|
|
61
|
-
const isMatchOrElse = AST.isCallOf(ce, "Match", "orElse")
|
|
62
|
-
|
|
63
|
-
if (!isMatchWhen && !isMatchOrElse) return Effect.void
|
|
64
|
-
if (!("arguments" in node) || !Array.isArray(node.arguments)) return Effect.void
|
|
65
|
-
|
|
66
|
-
// Check the callback argument for Effect sequencing
|
|
67
|
-
const callbackIdx = isMatchWhen ? 1 : 0
|
|
68
|
-
const callback = node.arguments[callbackIdx]
|
|
69
|
-
if (callback != null && containsEffectSequencing(callback)) {
|
|
70
|
-
return ctx.report(
|
|
71
|
-
Diagnostic.make({
|
|
72
|
-
node,
|
|
73
|
-
message:
|
|
74
|
-
"Avoid Effect sequencing inside Match branches. Select value in Match, then run one Effect pipeline outside.",
|
|
75
|
-
}),
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
return Effect.void
|
|
79
|
-
},
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
})
|