oxlint-plugin-effect 0.0.0 → 0.2.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/CHANGELOG.md +55 -0
- package/package.json +6 -7
- package/src/presets/core.ts +24 -13
- package/src/presets/effect-native.ts +18 -25
- package/src/presets/full.ts +33 -47
- package/src/presets/functional.ts +18 -0
- package/src/presets/index.ts +2 -0
- package/src/presets/strict.ts +77 -0
- package/src/rules/index.ts +18 -29
- package/src/rules/no-arrow-ladder.ts +1 -2
- package/src/rules/no-async-function.ts +1 -2
- package/src/rules/no-catch-all-to-map-error.ts +90 -0
- package/src/rules/no-effect-fn-generator.ts +1 -2
- package/src/rules/no-effect-gen-adapter.ts +2 -2
- package/src/rules/no-effect-map-flatten.ts +82 -0
- package/src/rules/no-effect-map-void.ts +1 -2
- package/src/rules/no-effect-orElse-ladder.ts +1 -2
- package/src/rules/no-effect-succeed-string.ts +1 -2
- package/src/rules/no-effect-succeed-variable.ts +1 -2
- package/src/rules/no-effect-succeed-void.ts +1 -2
- package/src/rules/no-effect-sync-console.ts +1 -2
- package/src/rules/no-effect-sync-wrapper.ts +1 -2
- package/src/rules/no-effect-type-alias.ts +1 -2
- package/src/rules/no-effect-wrapper-alias.ts +1 -4
- package/src/rules/no-extends-native-error.ts +1 -2
- package/src/rules/no-flatmap-ladder.ts +1 -3
- package/src/rules/no-fromnullable-coalesce.ts +1 -2
- package/src/rules/no-global-error-in-catch.ts +104 -0
- package/src/rules/no-global-error-in-failure.ts +83 -0
- package/src/rules/no-globals.ts +178 -0
- package/src/rules/no-iife-wrapper.ts +1 -2
- package/src/rules/{no-inline-runtime-provide.ts → no-inline-provide.ts} +3 -4
- package/src/rules/no-instanceof-schema.ts +2 -3
- package/src/rules/no-manual-effect-channels.ts +1 -2
- package/src/rules/no-match-effect-branch.ts +1 -2
- package/src/rules/no-match-void-branch.ts +1 -2
- package/src/rules/no-multiple-effect-provide.ts +75 -0
- package/src/rules/no-nested-effect-call.ts +1 -3
- package/src/rules/no-nested-effect-gen.ts +1 -2
- package/src/rules/no-nested-pipe.ts +1 -4
- package/src/rules/no-new-error.ts +46 -10
- package/src/rules/no-new-promise.ts +43 -7
- package/src/rules/no-node-builtin-import.ts +1 -3
- package/src/rules/no-option-boolean-normalization.ts +1 -2
- package/src/rules/no-positional-log-error.ts +76 -0
- package/src/rules/no-redundant-schema-tag-identifier.ts +62 -0
- package/src/rules/no-return-in-arrow.ts +1 -2
- package/src/rules/no-return-nullish.ts +80 -0
- package/src/rules/no-run-in-effect-gen.ts +66 -0
- package/src/rules/no-runtime-run-fork.ts +1 -1
- package/src/rules/no-schema-struct-with-tag.ts +64 -0
- package/src/rules/no-schema-union-of-literals.ts +48 -0
- package/src/rules/no-string-sentinel-const.ts +1 -2
- package/src/rules/no-ternary.ts +1 -3
- package/src/rules/no-throw-statement.ts +32 -6
- package/src/rules/no-try-catch.ts +32 -7
- package/src/rules/no-unnecessary-arrow-block.ts +1 -2
- package/src/rules/no-unnecessary-effect-gen.ts +1 -3
- package/src/rules/no-unnecessary-pipe-chain.ts +47 -0
- package/src/rules/no-unnecessary-pipe.ts +1 -2
- package/src/vendor/effect-oxlint/Rule.ts +141 -183
- package/tests/ban-rules.test.ts +0 -184
- package/tests/context-rules.test.ts +127 -145
- package/tests/pattern-rules.test.ts +39 -50
- package/src/rules/no-console-in-effect.ts +0 -46
- package/src/rules/no-date-in-effect.ts +0 -61
- package/src/rules/no-dynamic-import.ts +0 -13
- package/src/rules/no-fetch-in-effect.ts +0 -47
- package/src/rules/no-global-console.ts +0 -17
- package/src/rules/no-global-date.ts +0 -20
- package/src/rules/no-global-fetch.ts +0 -14
- package/src/rules/no-global-random.ts +0 -13
- package/src/rules/no-global-timers.ts +0 -17
- package/src/rules/no-json-in-effect.ts +0 -45
- package/src/rules/no-json-parse.ts +0 -18
- package/src/rules/no-platform-globals.ts +0 -117
- package/src/rules/no-process-env-in-effect.ts +0 -39
- package/src/rules/no-process-env.ts +0 -13
- package/src/rules/no-random-in-effect.ts +0 -39
- package/src/rules/no-return-null.ts +0 -47
- package/src/rules/no-run-in-effect.ts +0 -18
- package/src/rules/no-throw-in-effect-gen.ts +0 -42
- package/src/rules/no-timers-in-effect.ts +0 -47
- package/src/rules/no-try-catch-in-effect-gen.ts +0 -42
|
@@ -1,105 +1,101 @@
|
|
|
1
1
|
import { describe, test, expect } from "bun:test"
|
|
2
2
|
import { Testing } from "../src/vendor/effect-oxlint/index.js"
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
noTimersInEffect,
|
|
12
|
-
noJsonInEffect,
|
|
13
|
-
noProcessEnvInEffect,
|
|
4
|
+
noGlobals,
|
|
5
|
+
noThrowStatement,
|
|
6
|
+
noTryCatch,
|
|
7
|
+
noNewPromise,
|
|
8
|
+
noNewError,
|
|
9
|
+
noReturnNullish,
|
|
10
|
+
noRunInEffectGen,
|
|
14
11
|
} from "../src/rules/index.js"
|
|
15
12
|
|
|
16
13
|
// Helper: simulate entering and exiting Effect.gen context
|
|
17
14
|
const effectGenNode = Testing.callOfMember("Effect", "gen", [])
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
* For context-aware rules, we need to:
|
|
21
|
-
* 1. Enter Effect.gen (CallExpression event)
|
|
22
|
-
* 2. Fire the target event (ThrowStatement, MemberExpression, etc.)
|
|
23
|
-
* 3. Exit Effect.gen (CallExpression:exit event)
|
|
24
|
-
*
|
|
25
|
-
* Using runRuleMulti to simulate the sequence.
|
|
26
|
-
*/
|
|
16
|
+
// --- Global Effect-enforcing rules (fire everywhere) ---
|
|
27
17
|
|
|
28
|
-
describe("
|
|
29
|
-
test("reports throw
|
|
30
|
-
const result = Testing.
|
|
31
|
-
["CallExpression", effectGenNode],
|
|
32
|
-
["ThrowStatement", Testing.throwStmt()],
|
|
33
|
-
["CallExpression:exit", effectGenNode],
|
|
34
|
-
])
|
|
18
|
+
describe("noThrowStatement", () => {
|
|
19
|
+
test("reports throw", () => {
|
|
20
|
+
const result = Testing.runRule(noThrowStatement, "ThrowStatement", Testing.throwStmt())
|
|
35
21
|
expect(result.length).toBe(1)
|
|
36
22
|
})
|
|
23
|
+
})
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Testing.expectNoDiagnostics(result)
|
|
25
|
+
describe("noTryCatch", () => {
|
|
26
|
+
test("reports try/catch", () => {
|
|
27
|
+
const result = Testing.runRule(noTryCatch, "TryStatement", Testing.tryStmt())
|
|
28
|
+
expect(result.length).toBe(1)
|
|
43
29
|
})
|
|
44
30
|
})
|
|
45
31
|
|
|
46
|
-
describe("
|
|
47
|
-
test("reports
|
|
48
|
-
const result = Testing.
|
|
49
|
-
["CallExpression", effectGenNode],
|
|
50
|
-
["TryStatement", Testing.tryStmt()],
|
|
51
|
-
["CallExpression:exit", effectGenNode],
|
|
52
|
-
])
|
|
32
|
+
describe("noNewPromise", () => {
|
|
33
|
+
test("reports new Promise()", () => {
|
|
34
|
+
const result = Testing.runRule(noNewPromise, "NewExpression", Testing.newExpr("Promise"))
|
|
53
35
|
expect(result.length).toBe(1)
|
|
54
36
|
})
|
|
37
|
+
})
|
|
55
38
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
39
|
+
describe("noNewError", () => {
|
|
40
|
+
test("reports new Error()", () => {
|
|
41
|
+
const result = Testing.runRule(noNewError, "NewExpression", Testing.newExpr("Error"))
|
|
42
|
+
expect(result.length).toBe(1)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test("reports new TypeError()", () => {
|
|
46
|
+
const result = Testing.runRule(noNewError, "NewExpression", Testing.newExpr("TypeError"))
|
|
47
|
+
expect(result.length).toBe(1)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("ignores new MyError()", () => {
|
|
51
|
+
const result = Testing.runRule(noNewError, "NewExpression", Testing.newExpr("MyError"))
|
|
60
52
|
Testing.expectNoDiagnostics(result)
|
|
61
53
|
})
|
|
62
54
|
})
|
|
63
55
|
|
|
64
|
-
describe("
|
|
65
|
-
test("reports
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
describe("noReturnNullish", () => {
|
|
57
|
+
test("reports return null", () => {
|
|
58
|
+
const node = {
|
|
59
|
+
type: "ReturnStatement",
|
|
60
|
+
argument: { type: "Literal", value: null },
|
|
61
|
+
} as never
|
|
62
|
+
const result = Testing.runRule(noReturnNullish, "ReturnStatement", node)
|
|
71
63
|
expect(result.length).toBe(1)
|
|
72
64
|
})
|
|
73
65
|
|
|
74
|
-
test("
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
test("reports return undefined", () => {
|
|
67
|
+
const node = {
|
|
68
|
+
type: "ReturnStatement",
|
|
69
|
+
argument: { type: "Identifier", name: "undefined" },
|
|
70
|
+
} as never
|
|
71
|
+
const result = Testing.runRule(noReturnNullish, "ReturnStatement", node)
|
|
72
|
+
expect(result.length).toBe(1)
|
|
79
73
|
})
|
|
80
74
|
})
|
|
81
75
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
// --- Effect-context rules (inside Effect.gen/fn only) ---
|
|
77
|
+
|
|
78
|
+
describe("noGlobals", () => {
|
|
79
|
+
test("reports console.log inside Effect.gen", () => {
|
|
80
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
85
81
|
["CallExpression", effectGenNode],
|
|
86
|
-
["
|
|
82
|
+
["MemberExpression", Testing.memberExpr("console", "log")],
|
|
87
83
|
["CallExpression:exit", effectGenNode],
|
|
88
84
|
])
|
|
89
85
|
expect(result.length).toBe(1)
|
|
90
86
|
})
|
|
91
87
|
|
|
92
|
-
test("
|
|
93
|
-
const result = Testing.runRuleMulti(
|
|
88
|
+
test("reports fetch() inside Effect.gen", () => {
|
|
89
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
90
|
+
["CallExpression", effectGenNode],
|
|
94
91
|
["CallExpression", Testing.callExpr("fetch")],
|
|
92
|
+
["CallExpression:exit", effectGenNode],
|
|
95
93
|
])
|
|
96
|
-
|
|
94
|
+
expect(result.length).toBe(1)
|
|
97
95
|
})
|
|
98
|
-
})
|
|
99
96
|
|
|
100
|
-
describe("noDateInEffect", () => {
|
|
101
97
|
test("reports Date.now inside Effect.gen", () => {
|
|
102
|
-
const result = Testing.runRuleMulti(
|
|
98
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
103
99
|
["CallExpression", effectGenNode],
|
|
104
100
|
["MemberExpression", Testing.memberExpr("Date", "now")],
|
|
105
101
|
["CallExpression:exit", effectGenNode],
|
|
@@ -108,7 +104,7 @@ describe("noDateInEffect", () => {
|
|
|
108
104
|
})
|
|
109
105
|
|
|
110
106
|
test("reports new Date() inside Effect.gen", () => {
|
|
111
|
-
const result = Testing.runRuleMulti(
|
|
107
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
112
108
|
["CallExpression", effectGenNode],
|
|
113
109
|
["NewExpression", Testing.newExpr("Date")],
|
|
114
110
|
["CallExpression:exit", effectGenNode],
|
|
@@ -116,17 +112,8 @@ describe("noDateInEffect", () => {
|
|
|
116
112
|
expect(result.length).toBe(1)
|
|
117
113
|
})
|
|
118
114
|
|
|
119
|
-
test("ignores Date.now outside Effect.gen", () => {
|
|
120
|
-
const result = Testing.runRuleMulti(noDateInEffect, [
|
|
121
|
-
["MemberExpression", Testing.memberExpr("Date", "now")],
|
|
122
|
-
])
|
|
123
|
-
Testing.expectNoDiagnostics(result)
|
|
124
|
-
})
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
describe("noRandomInEffect", () => {
|
|
128
115
|
test("reports Math.random inside Effect.gen", () => {
|
|
129
|
-
const result = Testing.runRuleMulti(
|
|
116
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
130
117
|
["CallExpression", effectGenNode],
|
|
131
118
|
["MemberExpression", Testing.memberExpr("Math", "random")],
|
|
132
119
|
["CallExpression:exit", effectGenNode],
|
|
@@ -134,17 +121,8 @@ describe("noRandomInEffect", () => {
|
|
|
134
121
|
expect(result.length).toBe(1)
|
|
135
122
|
})
|
|
136
123
|
|
|
137
|
-
test("ignores Math.random outside Effect.gen", () => {
|
|
138
|
-
const result = Testing.runRuleMulti(noRandomInEffect, [
|
|
139
|
-
["MemberExpression", Testing.memberExpr("Math", "random")],
|
|
140
|
-
])
|
|
141
|
-
Testing.expectNoDiagnostics(result)
|
|
142
|
-
})
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
describe("noTimersInEffect", () => {
|
|
146
124
|
test("reports setTimeout inside Effect.gen", () => {
|
|
147
|
-
const result = Testing.runRuleMulti(
|
|
125
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
148
126
|
["CallExpression", effectGenNode],
|
|
149
127
|
["CallExpression", Testing.callExpr("setTimeout")],
|
|
150
128
|
["CallExpression:exit", effectGenNode],
|
|
@@ -152,17 +130,8 @@ describe("noTimersInEffect", () => {
|
|
|
152
130
|
expect(result.length).toBe(1)
|
|
153
131
|
})
|
|
154
132
|
|
|
155
|
-
test("ignores setTimeout outside Effect.gen", () => {
|
|
156
|
-
const result = Testing.runRuleMulti(noTimersInEffect, [
|
|
157
|
-
["CallExpression", Testing.callExpr("setTimeout")],
|
|
158
|
-
])
|
|
159
|
-
Testing.expectNoDiagnostics(result)
|
|
160
|
-
})
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
describe("noJsonInEffect", () => {
|
|
164
133
|
test("reports JSON.parse inside Effect.gen", () => {
|
|
165
|
-
const result = Testing.runRuleMulti(
|
|
134
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
166
135
|
["CallExpression", effectGenNode],
|
|
167
136
|
["MemberExpression", Testing.memberExpr("JSON", "parse")],
|
|
168
137
|
["CallExpression:exit", effectGenNode],
|
|
@@ -170,17 +139,8 @@ describe("noJsonInEffect", () => {
|
|
|
170
139
|
expect(result.length).toBe(1)
|
|
171
140
|
})
|
|
172
141
|
|
|
173
|
-
test("ignores JSON.parse outside Effect.gen", () => {
|
|
174
|
-
const result = Testing.runRuleMulti(noJsonInEffect, [
|
|
175
|
-
["MemberExpression", Testing.memberExpr("JSON", "parse")],
|
|
176
|
-
])
|
|
177
|
-
Testing.expectNoDiagnostics(result)
|
|
178
|
-
})
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
describe("noProcessEnvInEffect", () => {
|
|
182
142
|
test("reports process.env inside Effect.gen", () => {
|
|
183
|
-
const result = Testing.runRuleMulti(
|
|
143
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
184
144
|
["CallExpression", effectGenNode],
|
|
185
145
|
["MemberExpression", Testing.memberExpr("process", "env")],
|
|
186
146
|
["CallExpression:exit", effectGenNode],
|
|
@@ -188,17 +148,19 @@ describe("noProcessEnvInEffect", () => {
|
|
|
188
148
|
expect(result.length).toBe(1)
|
|
189
149
|
})
|
|
190
150
|
|
|
191
|
-
test("
|
|
192
|
-
const result = Testing.runRuleMulti(
|
|
193
|
-
["
|
|
151
|
+
test("reports process.exit with hint", () => {
|
|
152
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
153
|
+
["CallExpression", effectGenNode],
|
|
154
|
+
["MemberExpression", Testing.memberExpr("process", "exit")],
|
|
155
|
+
["CallExpression:exit", effectGenNode],
|
|
194
156
|
])
|
|
195
|
-
Testing.
|
|
157
|
+
Testing.expectDiagnostics(result, [{
|
|
158
|
+
message: "Avoid process.exit inside Effect context. Use Effect.fail or Effect.die.",
|
|
159
|
+
}])
|
|
196
160
|
})
|
|
197
|
-
})
|
|
198
161
|
|
|
199
|
-
describe("noPlatformGlobals", () => {
|
|
200
162
|
test("reports Bun.file with FileSystem hint", () => {
|
|
201
|
-
const result = Testing.runRuleMulti(
|
|
163
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
202
164
|
["CallExpression", effectGenNode],
|
|
203
165
|
["MemberExpression", Testing.memberExpr("Bun", "file")],
|
|
204
166
|
["CallExpression:exit", effectGenNode],
|
|
@@ -208,72 +170,92 @@ describe("noPlatformGlobals", () => {
|
|
|
208
170
|
}])
|
|
209
171
|
})
|
|
210
172
|
|
|
211
|
-
test("reports
|
|
212
|
-
const result = Testing.runRuleMulti(
|
|
173
|
+
test("reports Deno.readFile with FileSystem hint", () => {
|
|
174
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
213
175
|
["CallExpression", effectGenNode],
|
|
214
|
-
["MemberExpression", Testing.memberExpr("
|
|
176
|
+
["MemberExpression", Testing.memberExpr("Deno", "readFile")],
|
|
215
177
|
["CallExpression:exit", effectGenNode],
|
|
216
178
|
])
|
|
217
179
|
Testing.expectDiagnostics(result, [{
|
|
218
|
-
message: "Avoid
|
|
180
|
+
message: "Avoid Deno.readFile inside Effect context. Use FileSystem from 'effect'.",
|
|
219
181
|
}])
|
|
220
182
|
})
|
|
221
183
|
|
|
222
|
-
test("
|
|
223
|
-
const result = Testing.runRuleMulti(
|
|
184
|
+
test("ignores console.log outside Effect.gen", () => {
|
|
185
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
186
|
+
["MemberExpression", Testing.memberExpr("console", "log")],
|
|
187
|
+
])
|
|
188
|
+
Testing.expectNoDiagnostics(result)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test("ignores Effect.succeed inside Effect.gen", () => {
|
|
192
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
224
193
|
["CallExpression", effectGenNode],
|
|
225
|
-
["MemberExpression", Testing.memberExpr("
|
|
194
|
+
["MemberExpression", Testing.memberExpr("Effect", "succeed")],
|
|
226
195
|
["CallExpression:exit", effectGenNode],
|
|
227
196
|
])
|
|
228
|
-
Testing.
|
|
229
|
-
message: "Avoid process.exit inside Effect context. Use Effect.fail or Effect.die.",
|
|
230
|
-
}])
|
|
197
|
+
Testing.expectNoDiagnostics(result)
|
|
231
198
|
})
|
|
199
|
+
})
|
|
232
200
|
|
|
233
|
-
|
|
234
|
-
|
|
201
|
+
describe("noRunInEffectGen", () => {
|
|
202
|
+
test("reports Effect.runPromise inside Effect.gen", () => {
|
|
203
|
+
const result = Testing.runRuleMulti(noRunInEffectGen, [
|
|
235
204
|
["CallExpression", effectGenNode],
|
|
236
|
-
["
|
|
205
|
+
["CallExpression", Testing.callOfMember("Effect", "runPromise", [Testing.id("x")])],
|
|
237
206
|
["CallExpression:exit", effectGenNode],
|
|
238
207
|
])
|
|
239
|
-
|
|
240
|
-
message: "Avoid process.cwd inside Effect context. Use Path from 'effect'.",
|
|
241
|
-
}])
|
|
208
|
+
expect(result.length).toBe(1)
|
|
242
209
|
})
|
|
243
210
|
|
|
244
|
-
test("reports
|
|
245
|
-
const
|
|
211
|
+
test("reports Effect.runFork inside Effect.fn", () => {
|
|
212
|
+
const effectFnNode = Testing.callOfMember("Effect", "fn", [])
|
|
213
|
+
const result = Testing.runRuleMulti(noRunInEffectGen, [
|
|
214
|
+
["CallExpression", effectFnNode],
|
|
215
|
+
["CallExpression", Testing.callOfMember("Effect", "runFork", [Testing.id("x")])],
|
|
216
|
+
["CallExpression:exit", effectFnNode],
|
|
217
|
+
])
|
|
218
|
+
expect(result.length).toBe(1)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test("reports Runtime.runFork inside Effect.gen", () => {
|
|
222
|
+
const result = Testing.runRuleMulti(noRunInEffectGen, [
|
|
246
223
|
["CallExpression", effectGenNode],
|
|
247
|
-
["
|
|
224
|
+
["CallExpression", Testing.callOfMember("Runtime", "runFork", [Testing.id("x")])],
|
|
248
225
|
["CallExpression:exit", effectGenNode],
|
|
249
226
|
])
|
|
250
|
-
|
|
251
|
-
message: "Avoid Deno.readFile inside Effect context. Use FileSystem from 'effect'.",
|
|
252
|
-
}])
|
|
227
|
+
expect(result.length).toBe(1)
|
|
253
228
|
})
|
|
254
229
|
|
|
255
|
-
test("
|
|
256
|
-
const result = Testing.runRuleMulti(
|
|
230
|
+
test("ignores Effect.runForkWith inside Effect.gen (legit v4 callback boundary)", () => {
|
|
231
|
+
const result = Testing.runRuleMulti(noRunInEffectGen, [
|
|
257
232
|
["CallExpression", effectGenNode],
|
|
258
|
-
["
|
|
233
|
+
["CallExpression", Testing.callOfMember("Effect", "runForkWith", [Testing.id("services")])],
|
|
259
234
|
["CallExpression:exit", effectGenNode],
|
|
260
235
|
])
|
|
261
|
-
Testing.
|
|
262
|
-
message: "Avoid Bun.someNewApi inside Effect context. Wrap in a service for testability.",
|
|
263
|
-
}])
|
|
236
|
+
Testing.expectNoDiagnostics(result)
|
|
264
237
|
})
|
|
265
238
|
|
|
266
|
-
test("ignores
|
|
267
|
-
const result = Testing.runRuleMulti(
|
|
268
|
-
["
|
|
239
|
+
test("ignores managedRuntime.runFork on identifier callee", () => {
|
|
240
|
+
const result = Testing.runRuleMulti(noRunInEffectGen, [
|
|
241
|
+
["CallExpression", effectGenNode],
|
|
242
|
+
["CallExpression", Testing.callOfMember("managedRuntime", "runFork", [Testing.id("x")])],
|
|
243
|
+
["CallExpression:exit", effectGenNode],
|
|
269
244
|
])
|
|
270
245
|
Testing.expectNoDiagnostics(result)
|
|
271
246
|
})
|
|
272
247
|
|
|
273
|
-
test("ignores
|
|
274
|
-
const result = Testing.runRuleMulti(
|
|
248
|
+
test("ignores Effect.runPromise outside Effect.gen", () => {
|
|
249
|
+
const result = Testing.runRuleMulti(noRunInEffectGen, [
|
|
250
|
+
["CallExpression", Testing.callOfMember("Effect", "runPromise", [Testing.id("x")])],
|
|
251
|
+
])
|
|
252
|
+
Testing.expectNoDiagnostics(result)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test("ignores yield* child effect inside Effect.gen", () => {
|
|
256
|
+
const result = Testing.runRuleMulti(noRunInEffectGen, [
|
|
275
257
|
["CallExpression", effectGenNode],
|
|
276
|
-
["
|
|
258
|
+
["CallExpression", Testing.callOfMember("Effect", "succeed", [Testing.id("x")])],
|
|
277
259
|
["CallExpression:exit", effectGenNode],
|
|
278
260
|
])
|
|
279
261
|
Testing.expectNoDiagnostics(result)
|
|
@@ -1,43 +1,20 @@
|
|
|
1
1
|
import { describe, test, expect } from "bun:test"
|
|
2
2
|
import { Testing } from "../src/vendor/effect-oxlint/index.js"
|
|
3
3
|
import {
|
|
4
|
-
noReturnNull,
|
|
5
4
|
noUnnecessaryPipe,
|
|
6
5
|
noEffectSucceedVoid,
|
|
7
6
|
noEffectMapVoid,
|
|
8
7
|
noEffectFnGenerator,
|
|
9
8
|
noExtendsNativeError,
|
|
10
|
-
noAsyncFunction,
|
|
11
9
|
noIifeWrapper,
|
|
12
10
|
noEffectSucceedString,
|
|
13
11
|
noFromNullableCoalesce,
|
|
14
12
|
noInstanceofSchema,
|
|
15
13
|
noNestedPipe,
|
|
16
14
|
noNestedEffectCall,
|
|
17
|
-
|
|
18
|
-
noEffectOrElseLadder,
|
|
15
|
+
noPositionalLogError,
|
|
19
16
|
} from "../src/rules/index.js"
|
|
20
17
|
|
|
21
|
-
describe("noReturnNull", () => {
|
|
22
|
-
test("reports return null", () => {
|
|
23
|
-
const node = {
|
|
24
|
-
type: "ReturnStatement",
|
|
25
|
-
argument: { type: "Literal", value: null },
|
|
26
|
-
} as never
|
|
27
|
-
const result = Testing.runRule(noReturnNull, "ReturnStatement", node)
|
|
28
|
-
expect(result.length).toBe(1)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
test("ignores return 42", () => {
|
|
32
|
-
const node = {
|
|
33
|
-
type: "ReturnStatement",
|
|
34
|
-
argument: { type: "Literal", value: 42 },
|
|
35
|
-
} as never
|
|
36
|
-
const result = Testing.runRule(noReturnNull, "ReturnStatement", node)
|
|
37
|
-
Testing.expectNoDiagnostics(result)
|
|
38
|
-
})
|
|
39
|
-
})
|
|
40
|
-
|
|
41
18
|
describe("noUnnecessaryPipe", () => {
|
|
42
19
|
test("reports pipe(x) with single arg", () => {
|
|
43
20
|
const node = Testing.callExpr("pipe", [Testing.id("myEffect")])
|
|
@@ -126,32 +103,6 @@ describe("noExtendsNativeError", () => {
|
|
|
126
103
|
})
|
|
127
104
|
})
|
|
128
105
|
|
|
129
|
-
describe("noAsyncFunction", () => {
|
|
130
|
-
test("reports async function", () => {
|
|
131
|
-
const node = {
|
|
132
|
-
type: "FunctionDeclaration",
|
|
133
|
-
async: true,
|
|
134
|
-
id: Testing.id("fetchData"),
|
|
135
|
-
params: [],
|
|
136
|
-
body: Testing.blockStmt([]),
|
|
137
|
-
} as never
|
|
138
|
-
const result = Testing.runRule(noAsyncFunction, "FunctionDeclaration", node)
|
|
139
|
-
expect(result.length).toBe(1)
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
test("ignores non-async function", () => {
|
|
143
|
-
const node = {
|
|
144
|
-
type: "FunctionDeclaration",
|
|
145
|
-
async: false,
|
|
146
|
-
id: Testing.id("fetchData"),
|
|
147
|
-
params: [],
|
|
148
|
-
body: Testing.blockStmt([]),
|
|
149
|
-
} as never
|
|
150
|
-
const result = Testing.runRule(noAsyncFunction, "FunctionDeclaration", node)
|
|
151
|
-
Testing.expectNoDiagnostics(result)
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
|
|
155
106
|
describe("noIifeWrapper", () => {
|
|
156
107
|
test("reports (() => expr)()", () => {
|
|
157
108
|
const node = {
|
|
@@ -256,3 +207,41 @@ describe("noNestedEffectCall", () => {
|
|
|
256
207
|
Testing.expectNoDiagnostics(result)
|
|
257
208
|
})
|
|
258
209
|
})
|
|
210
|
+
|
|
211
|
+
describe("noPositionalLogError", () => {
|
|
212
|
+
test("reports Effect.logWarning with two args", () => {
|
|
213
|
+
const node = Testing.callOfMember("Effect", "logWarning", [
|
|
214
|
+
Testing.strLiteral("something failed"),
|
|
215
|
+
Testing.id("error"),
|
|
216
|
+
])
|
|
217
|
+
const result = Testing.runRule(noPositionalLogError, "CallExpression", node)
|
|
218
|
+
expect(result.length).toBe(1)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test("reports Effect.logError with two args", () => {
|
|
222
|
+
const node = Testing.callOfMember("Effect", "logError", [
|
|
223
|
+
Testing.strLiteral("fatal"),
|
|
224
|
+
Testing.id("err"),
|
|
225
|
+
])
|
|
226
|
+
const result = Testing.runRule(noPositionalLogError, "CallExpression", node)
|
|
227
|
+
expect(result.length).toBe(1)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test("ignores Effect.log with single arg", () => {
|
|
231
|
+
const node = Testing.callOfMember("Effect", "log", [
|
|
232
|
+
Testing.strLiteral("hello"),
|
|
233
|
+
])
|
|
234
|
+
const result = Testing.runRule(noPositionalLogError, "CallExpression", node)
|
|
235
|
+
Testing.expectNoDiagnostics(result)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test("allows Cause as second arg", () => {
|
|
239
|
+
const causeArg = Testing.callOfMember("Cause", "fail", [Testing.id("error")])
|
|
240
|
+
const node = Testing.callOfMember("Effect", "logWarning", [
|
|
241
|
+
Testing.strLiteral("something failed"),
|
|
242
|
+
causeArg,
|
|
243
|
+
])
|
|
244
|
+
const result = Testing.runRule(noPositionalLogError, "CallExpression", node)
|
|
245
|
+
Testing.expectNoDiagnostics(result)
|
|
246
|
+
})
|
|
247
|
+
})
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `console.*` calls inside Effect.gen/fn context.
|
|
3
|
-
*
|
|
4
|
-
* Use Effect Logger service instead.
|
|
5
|
-
*
|
|
6
|
-
* Source: language-service/globalConsoleInEffect
|
|
7
|
-
*/
|
|
8
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
9
|
-
import { AST, Diagnostic, Rule, Visitor } from "../vendor/effect-oxlint/index.js"
|
|
10
|
-
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
11
|
-
import * as Effect from "effect/Effect"
|
|
12
|
-
import * as Option from "effect/Option"
|
|
13
|
-
import * as Ref from "effect/Ref"
|
|
14
|
-
|
|
15
|
-
import { makeEffectContextTracker } from "./_effect-context.js"
|
|
16
|
-
|
|
17
|
-
const consoleMethods = ["log", "warn", "error", "info", "debug", "trace"]
|
|
18
|
-
|
|
19
|
-
export const noConsoleInEffect = Rule.define({
|
|
20
|
-
name: "no-console-in-effect",
|
|
21
|
-
meta: Rule.meta({
|
|
22
|
-
type: "suggestion",
|
|
23
|
-
description:
|
|
24
|
-
"Avoid console.* inside Effect.gen/fn. Use Effect Logger service.",
|
|
25
|
-
}),
|
|
26
|
-
create: function* () {
|
|
27
|
-
const ctx = yield* RuleContext
|
|
28
|
-
const [depth, tracker] = yield* makeEffectContextTracker
|
|
29
|
-
|
|
30
|
-
return Visitor.merge(tracker, {
|
|
31
|
-
MemberExpression: (node) =>
|
|
32
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
33
|
-
if (d > 0 && Option.isSome(AST.matchMember(node as ESTree.MemberExpression, "console", consoleMethods))) {
|
|
34
|
-
return ctx.report(
|
|
35
|
-
Diagnostic.make({
|
|
36
|
-
node,
|
|
37
|
-
message:
|
|
38
|
-
"Avoid console.* inside Effect context. Use Effect Logger service.",
|
|
39
|
-
}),
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
return Effect.void
|
|
43
|
-
}),
|
|
44
|
-
})
|
|
45
|
-
},
|
|
46
|
-
})
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `Date.now()` and `new Date()` inside Effect.gen/fn context.
|
|
3
|
-
*
|
|
4
|
-
* Use Clock service instead.
|
|
5
|
-
*
|
|
6
|
-
* Source: language-service/globalDateInEffect
|
|
7
|
-
*/
|
|
8
|
-
import type { ESTree } from "@oxlint/plugins"
|
|
9
|
-
import { AST, Diagnostic, Rule, Visitor } from "../vendor/effect-oxlint/index.js"
|
|
10
|
-
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
11
|
-
import * as Effect from "effect/Effect"
|
|
12
|
-
import * as Option from "effect/Option"
|
|
13
|
-
import * as Ref from "effect/Ref"
|
|
14
|
-
|
|
15
|
-
import { makeEffectContextTracker } from "./_effect-context.js"
|
|
16
|
-
|
|
17
|
-
export const noDateInEffect = Rule.define({
|
|
18
|
-
name: "no-date-in-effect",
|
|
19
|
-
meta: Rule.meta({
|
|
20
|
-
type: "suggestion",
|
|
21
|
-
description:
|
|
22
|
-
"Avoid Date.now() and new Date() inside Effect.gen/fn. Use Clock service.",
|
|
23
|
-
}),
|
|
24
|
-
create: function* () {
|
|
25
|
-
const ctx = yield* RuleContext
|
|
26
|
-
const [depth, tracker] = yield* makeEffectContextTracker
|
|
27
|
-
|
|
28
|
-
return Visitor.merge(
|
|
29
|
-
tracker,
|
|
30
|
-
{
|
|
31
|
-
MemberExpression: (node) =>
|
|
32
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
33
|
-
if (d > 0 && Option.isSome(AST.matchMember(node as ESTree.MemberExpression, "Date", "now"))) {
|
|
34
|
-
return ctx.report(
|
|
35
|
-
Diagnostic.make({ node, message: "Avoid Date.now() inside Effect context. Use Clock service." }),
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
return Effect.void
|
|
39
|
-
}),
|
|
40
|
-
NewExpression: (node) =>
|
|
41
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
42
|
-
if (
|
|
43
|
-
d > 0 &&
|
|
44
|
-
"callee" in node &&
|
|
45
|
-
node.callee != null &&
|
|
46
|
-
typeof node.callee === "object" &&
|
|
47
|
-
"type" in node.callee &&
|
|
48
|
-
node.callee.type === "Identifier" &&
|
|
49
|
-
"name" in node.callee &&
|
|
50
|
-
node.callee.name === "Date"
|
|
51
|
-
) {
|
|
52
|
-
return ctx.report(
|
|
53
|
-
Diagnostic.make({ node, message: "Avoid new Date() inside Effect context. Use Clock service." }),
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
return Effect.void
|
|
57
|
-
}),
|
|
58
|
-
},
|
|
59
|
-
)
|
|
60
|
-
},
|
|
61
|
-
})
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban dynamic `import()` expressions.
|
|
3
|
-
*
|
|
4
|
-
* Use static module imports.
|
|
5
|
-
*
|
|
6
|
-
* Source: biome-effect-linting-rules/prevent-dynamic-imports
|
|
7
|
-
*/
|
|
8
|
-
import { Rule } from "../vendor/effect-oxlint/index.js"
|
|
9
|
-
|
|
10
|
-
export const noDynamicImport = Rule.banStatement("ImportExpression", {
|
|
11
|
-
message: "Avoid dynamic import(). Use static module imports.",
|
|
12
|
-
meta: { type: "problem" },
|
|
13
|
-
})
|