oxlint-plugin-effect 0.0.0 → 0.1.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 +37 -0
- package/package.json +6 -7
- package/src/presets/core.ts +23 -12
- package/src/presets/effect-native.ts +18 -25
- package/src/presets/full.ts +32 -46
- 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 +17 -28
- 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 +89 -0
- package/src/rules/no-effect-fn-generator.ts +1 -2
- package/src/rules/no-effect-gen-adapter.ts +1 -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-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 +111 -173
- package/tests/ban-rules.test.ts +0 -167
- package/tests/context-rules.test.ts +75 -158
- 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-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,100 @@
|
|
|
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
|
-
noRandomInEffect,
|
|
11
|
-
noTimersInEffect,
|
|
12
|
-
noJsonInEffect,
|
|
13
|
-
noProcessEnvInEffect,
|
|
4
|
+
noGlobals,
|
|
5
|
+
noThrowStatement,
|
|
6
|
+
noTryCatch,
|
|
7
|
+
noNewPromise,
|
|
8
|
+
noNewError,
|
|
9
|
+
noReturnNullish,
|
|
14
10
|
} from "../src/rules/index.js"
|
|
15
11
|
|
|
16
12
|
// Helper: simulate entering and exiting Effect.gen context
|
|
17
13
|
const effectGenNode = Testing.callOfMember("Effect", "gen", [])
|
|
18
14
|
|
|
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
|
-
*/
|
|
15
|
+
// --- Global Effect-enforcing rules (fire everywhere) ---
|
|
27
16
|
|
|
28
|
-
describe("
|
|
29
|
-
test("reports throw
|
|
30
|
-
const result = Testing.
|
|
31
|
-
["CallExpression", effectGenNode],
|
|
32
|
-
["ThrowStatement", Testing.throwStmt()],
|
|
33
|
-
["CallExpression:exit", effectGenNode],
|
|
34
|
-
])
|
|
17
|
+
describe("noThrowStatement", () => {
|
|
18
|
+
test("reports throw", () => {
|
|
19
|
+
const result = Testing.runRule(noThrowStatement, "ThrowStatement", Testing.throwStmt())
|
|
35
20
|
expect(result.length).toBe(1)
|
|
36
21
|
})
|
|
22
|
+
})
|
|
37
23
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Testing.expectNoDiagnostics(result)
|
|
24
|
+
describe("noTryCatch", () => {
|
|
25
|
+
test("reports try/catch", () => {
|
|
26
|
+
const result = Testing.runRule(noTryCatch, "TryStatement", Testing.tryStmt())
|
|
27
|
+
expect(result.length).toBe(1)
|
|
43
28
|
})
|
|
44
29
|
})
|
|
45
30
|
|
|
46
|
-
describe("
|
|
47
|
-
test("reports
|
|
48
|
-
const result = Testing.
|
|
49
|
-
["CallExpression", effectGenNode],
|
|
50
|
-
["TryStatement", Testing.tryStmt()],
|
|
51
|
-
["CallExpression:exit", effectGenNode],
|
|
52
|
-
])
|
|
31
|
+
describe("noNewPromise", () => {
|
|
32
|
+
test("reports new Promise()", () => {
|
|
33
|
+
const result = Testing.runRule(noNewPromise, "NewExpression", Testing.newExpr("Promise"))
|
|
53
34
|
expect(result.length).toBe(1)
|
|
54
35
|
})
|
|
36
|
+
})
|
|
55
37
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
38
|
+
describe("noNewError", () => {
|
|
39
|
+
test("reports new Error()", () => {
|
|
40
|
+
const result = Testing.runRule(noNewError, "NewExpression", Testing.newExpr("Error"))
|
|
41
|
+
expect(result.length).toBe(1)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test("reports new TypeError()", () => {
|
|
45
|
+
const result = Testing.runRule(noNewError, "NewExpression", Testing.newExpr("TypeError"))
|
|
46
|
+
expect(result.length).toBe(1)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test("ignores new MyError()", () => {
|
|
50
|
+
const result = Testing.runRule(noNewError, "NewExpression", Testing.newExpr("MyError"))
|
|
60
51
|
Testing.expectNoDiagnostics(result)
|
|
61
52
|
})
|
|
62
53
|
})
|
|
63
54
|
|
|
64
|
-
describe("
|
|
65
|
-
test("reports
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
55
|
+
describe("noReturnNullish", () => {
|
|
56
|
+
test("reports return null", () => {
|
|
57
|
+
const node = {
|
|
58
|
+
type: "ReturnStatement",
|
|
59
|
+
argument: { type: "Literal", value: null },
|
|
60
|
+
} as never
|
|
61
|
+
const result = Testing.runRule(noReturnNullish, "ReturnStatement", node)
|
|
71
62
|
expect(result.length).toBe(1)
|
|
72
63
|
})
|
|
73
64
|
|
|
74
|
-
test("
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
test("reports return undefined", () => {
|
|
66
|
+
const node = {
|
|
67
|
+
type: "ReturnStatement",
|
|
68
|
+
argument: { type: "Identifier", name: "undefined" },
|
|
69
|
+
} as never
|
|
70
|
+
const result = Testing.runRule(noReturnNullish, "ReturnStatement", node)
|
|
71
|
+
expect(result.length).toBe(1)
|
|
79
72
|
})
|
|
80
73
|
})
|
|
81
74
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
// --- Effect-context rules (inside Effect.gen/fn only) ---
|
|
76
|
+
|
|
77
|
+
describe("noGlobals", () => {
|
|
78
|
+
test("reports console.log inside Effect.gen", () => {
|
|
79
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
85
80
|
["CallExpression", effectGenNode],
|
|
86
|
-
["
|
|
81
|
+
["MemberExpression", Testing.memberExpr("console", "log")],
|
|
87
82
|
["CallExpression:exit", effectGenNode],
|
|
88
83
|
])
|
|
89
84
|
expect(result.length).toBe(1)
|
|
90
85
|
})
|
|
91
86
|
|
|
92
|
-
test("
|
|
93
|
-
const result = Testing.runRuleMulti(
|
|
87
|
+
test("reports fetch() inside Effect.gen", () => {
|
|
88
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
89
|
+
["CallExpression", effectGenNode],
|
|
94
90
|
["CallExpression", Testing.callExpr("fetch")],
|
|
91
|
+
["CallExpression:exit", effectGenNode],
|
|
95
92
|
])
|
|
96
|
-
|
|
93
|
+
expect(result.length).toBe(1)
|
|
97
94
|
})
|
|
98
|
-
})
|
|
99
95
|
|
|
100
|
-
describe("noDateInEffect", () => {
|
|
101
96
|
test("reports Date.now inside Effect.gen", () => {
|
|
102
|
-
const result = Testing.runRuleMulti(
|
|
97
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
103
98
|
["CallExpression", effectGenNode],
|
|
104
99
|
["MemberExpression", Testing.memberExpr("Date", "now")],
|
|
105
100
|
["CallExpression:exit", effectGenNode],
|
|
@@ -108,7 +103,7 @@ describe("noDateInEffect", () => {
|
|
|
108
103
|
})
|
|
109
104
|
|
|
110
105
|
test("reports new Date() inside Effect.gen", () => {
|
|
111
|
-
const result = Testing.runRuleMulti(
|
|
106
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
112
107
|
["CallExpression", effectGenNode],
|
|
113
108
|
["NewExpression", Testing.newExpr("Date")],
|
|
114
109
|
["CallExpression:exit", effectGenNode],
|
|
@@ -116,17 +111,8 @@ describe("noDateInEffect", () => {
|
|
|
116
111
|
expect(result.length).toBe(1)
|
|
117
112
|
})
|
|
118
113
|
|
|
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
114
|
test("reports Math.random inside Effect.gen", () => {
|
|
129
|
-
const result = Testing.runRuleMulti(
|
|
115
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
130
116
|
["CallExpression", effectGenNode],
|
|
131
117
|
["MemberExpression", Testing.memberExpr("Math", "random")],
|
|
132
118
|
["CallExpression:exit", effectGenNode],
|
|
@@ -134,17 +120,8 @@ describe("noRandomInEffect", () => {
|
|
|
134
120
|
expect(result.length).toBe(1)
|
|
135
121
|
})
|
|
136
122
|
|
|
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
123
|
test("reports setTimeout inside Effect.gen", () => {
|
|
147
|
-
const result = Testing.runRuleMulti(
|
|
124
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
148
125
|
["CallExpression", effectGenNode],
|
|
149
126
|
["CallExpression", Testing.callExpr("setTimeout")],
|
|
150
127
|
["CallExpression:exit", effectGenNode],
|
|
@@ -152,17 +129,8 @@ describe("noTimersInEffect", () => {
|
|
|
152
129
|
expect(result.length).toBe(1)
|
|
153
130
|
})
|
|
154
131
|
|
|
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
132
|
test("reports JSON.parse inside Effect.gen", () => {
|
|
165
|
-
const result = Testing.runRuleMulti(
|
|
133
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
166
134
|
["CallExpression", effectGenNode],
|
|
167
135
|
["MemberExpression", Testing.memberExpr("JSON", "parse")],
|
|
168
136
|
["CallExpression:exit", effectGenNode],
|
|
@@ -170,17 +138,8 @@ describe("noJsonInEffect", () => {
|
|
|
170
138
|
expect(result.length).toBe(1)
|
|
171
139
|
})
|
|
172
140
|
|
|
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
141
|
test("reports process.env inside Effect.gen", () => {
|
|
183
|
-
const result = Testing.runRuleMulti(
|
|
142
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
184
143
|
["CallExpression", effectGenNode],
|
|
185
144
|
["MemberExpression", Testing.memberExpr("process", "env")],
|
|
186
145
|
["CallExpression:exit", effectGenNode],
|
|
@@ -188,39 +147,8 @@ describe("noProcessEnvInEffect", () => {
|
|
|
188
147
|
expect(result.length).toBe(1)
|
|
189
148
|
})
|
|
190
149
|
|
|
191
|
-
test("
|
|
192
|
-
const result = Testing.runRuleMulti(
|
|
193
|
-
["MemberExpression", Testing.memberExpr("process", "env")],
|
|
194
|
-
])
|
|
195
|
-
Testing.expectNoDiagnostics(result)
|
|
196
|
-
})
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
describe("noPlatformGlobals", () => {
|
|
200
|
-
test("reports Bun.file with FileSystem hint", () => {
|
|
201
|
-
const result = Testing.runRuleMulti(noPlatformGlobals, [
|
|
202
|
-
["CallExpression", effectGenNode],
|
|
203
|
-
["MemberExpression", Testing.memberExpr("Bun", "file")],
|
|
204
|
-
["CallExpression:exit", effectGenNode],
|
|
205
|
-
])
|
|
206
|
-
Testing.expectDiagnostics(result, [{
|
|
207
|
-
message: "Avoid Bun.file inside Effect context. Use FileSystem from 'effect'.",
|
|
208
|
-
}])
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
test("reports Bun.serve with HttpServer hint", () => {
|
|
212
|
-
const result = Testing.runRuleMulti(noPlatformGlobals, [
|
|
213
|
-
["CallExpression", effectGenNode],
|
|
214
|
-
["MemberExpression", Testing.memberExpr("Bun", "serve")],
|
|
215
|
-
["CallExpression:exit", effectGenNode],
|
|
216
|
-
])
|
|
217
|
-
Testing.expectDiagnostics(result, [{
|
|
218
|
-
message: "Avoid Bun.serve inside Effect context. Use HttpServer from 'effect/unstable/http'.",
|
|
219
|
-
}])
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
test("reports process.exit with Effect.fail hint", () => {
|
|
223
|
-
const result = Testing.runRuleMulti(noPlatformGlobals, [
|
|
150
|
+
test("reports process.exit with hint", () => {
|
|
151
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
224
152
|
["CallExpression", effectGenNode],
|
|
225
153
|
["MemberExpression", Testing.memberExpr("process", "exit")],
|
|
226
154
|
["CallExpression:exit", effectGenNode],
|
|
@@ -230,19 +158,19 @@ describe("noPlatformGlobals", () => {
|
|
|
230
158
|
}])
|
|
231
159
|
})
|
|
232
160
|
|
|
233
|
-
test("reports
|
|
234
|
-
const result = Testing.runRuleMulti(
|
|
161
|
+
test("reports Bun.file with FileSystem hint", () => {
|
|
162
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
235
163
|
["CallExpression", effectGenNode],
|
|
236
|
-
["MemberExpression", Testing.memberExpr("
|
|
164
|
+
["MemberExpression", Testing.memberExpr("Bun", "file")],
|
|
237
165
|
["CallExpression:exit", effectGenNode],
|
|
238
166
|
])
|
|
239
167
|
Testing.expectDiagnostics(result, [{
|
|
240
|
-
message: "Avoid
|
|
168
|
+
message: "Avoid Bun.file inside Effect context. Use FileSystem from 'effect'.",
|
|
241
169
|
}])
|
|
242
170
|
})
|
|
243
171
|
|
|
244
172
|
test("reports Deno.readFile with FileSystem hint", () => {
|
|
245
|
-
const result = Testing.runRuleMulti(
|
|
173
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
246
174
|
["CallExpression", effectGenNode],
|
|
247
175
|
["MemberExpression", Testing.memberExpr("Deno", "readFile")],
|
|
248
176
|
["CallExpression:exit", effectGenNode],
|
|
@@ -252,26 +180,15 @@ describe("noPlatformGlobals", () => {
|
|
|
252
180
|
}])
|
|
253
181
|
})
|
|
254
182
|
|
|
255
|
-
test("
|
|
256
|
-
const result = Testing.runRuleMulti(
|
|
257
|
-
["
|
|
258
|
-
["MemberExpression", Testing.memberExpr("Bun", "someNewApi")],
|
|
259
|
-
["CallExpression:exit", effectGenNode],
|
|
260
|
-
])
|
|
261
|
-
Testing.expectDiagnostics(result, [{
|
|
262
|
-
message: "Avoid Bun.someNewApi inside Effect context. Wrap in a service for testability.",
|
|
263
|
-
}])
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
test("ignores Bun.file outside Effect.gen", () => {
|
|
267
|
-
const result = Testing.runRuleMulti(noPlatformGlobals, [
|
|
268
|
-
["MemberExpression", Testing.memberExpr("Bun", "file")],
|
|
183
|
+
test("ignores console.log outside Effect.gen", () => {
|
|
184
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
185
|
+
["MemberExpression", Testing.memberExpr("console", "log")],
|
|
269
186
|
])
|
|
270
187
|
Testing.expectNoDiagnostics(result)
|
|
271
188
|
})
|
|
272
189
|
|
|
273
|
-
test("ignores
|
|
274
|
-
const result = Testing.runRuleMulti(
|
|
190
|
+
test("ignores Effect.succeed inside Effect.gen", () => {
|
|
191
|
+
const result = Testing.runRuleMulti(noGlobals, [
|
|
275
192
|
["CallExpression", effectGenNode],
|
|
276
193
|
["MemberExpression", Testing.memberExpr("Effect", "succeed")],
|
|
277
194
|
["CallExpression:exit", effectGenNode],
|
|
@@ -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
|
-
})
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `fetch()` inside Effect.gen/fn context.
|
|
3
|
-
*
|
|
4
|
-
* Use HttpClient from 'effect/unstable/http' instead.
|
|
5
|
-
*
|
|
6
|
-
* Source: language-service/globalFetchInEffect
|
|
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 noFetchInEffect = Rule.define({
|
|
18
|
-
name: "no-fetch-in-effect",
|
|
19
|
-
meta: Rule.meta({
|
|
20
|
-
type: "suggestion",
|
|
21
|
-
description:
|
|
22
|
-
"Avoid fetch() inside Effect.gen/fn. Use HttpClient from 'effect/unstable/http'.",
|
|
23
|
-
}),
|
|
24
|
-
create: function* () {
|
|
25
|
-
const ctx = yield* RuleContext
|
|
26
|
-
const [depth, tracker] = yield* makeEffectContextTracker
|
|
27
|
-
|
|
28
|
-
return Visitor.merge(tracker, {
|
|
29
|
-
CallExpression: (node) =>
|
|
30
|
-
Effect.flatMap(Ref.get(depth), (d) => {
|
|
31
|
-
if (d > 0) {
|
|
32
|
-
const name = Option.getOrUndefined(AST.calleeName(node as ESTree.CallExpression))
|
|
33
|
-
if (name === "fetch") {
|
|
34
|
-
return ctx.report(
|
|
35
|
-
Diagnostic.make({
|
|
36
|
-
node,
|
|
37
|
-
message:
|
|
38
|
-
"Avoid fetch() inside Effect context. Use HttpClient from 'effect/unstable/http'.",
|
|
39
|
-
}),
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return Effect.void
|
|
44
|
-
}),
|
|
45
|
-
})
|
|
46
|
-
},
|
|
47
|
-
})
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `console.log/warn/error/info/debug/trace`.
|
|
3
|
-
*
|
|
4
|
-
* Use Effect Logger service instead.
|
|
5
|
-
*
|
|
6
|
-
* Source: language-service/globalConsole
|
|
7
|
-
*/
|
|
8
|
-
import { Rule } from "../vendor/effect-oxlint/index.js"
|
|
9
|
-
|
|
10
|
-
export const noGlobalConsole = Rule.banMember(
|
|
11
|
-
"console",
|
|
12
|
-
["log", "warn", "error", "info", "debug", "trace"],
|
|
13
|
-
{
|
|
14
|
-
message: "Avoid console.*. Use Effect Logger service.",
|
|
15
|
-
meta: { type: "suggestion" },
|
|
16
|
-
},
|
|
17
|
-
)
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ban `Date.now()` and `new Date()`.
|
|
3
|
-
*
|
|
4
|
-
* Use Effect Clock service instead.
|
|
5
|
-
*
|
|
6
|
-
* Source: language-service/globalDate
|
|
7
|
-
*/
|
|
8
|
-
import { Rule } from "../vendor/effect-oxlint/index.js"
|
|
9
|
-
|
|
10
|
-
export const noGlobalDate = Rule.banMultiple(
|
|
11
|
-
{
|
|
12
|
-
newExprs: "Date",
|
|
13
|
-
members: [["Date", "now"]],
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
name: "no-global-date",
|
|
17
|
-
message: "Avoid Date.now() and new Date(). Use Clock service.",
|
|
18
|
-
meta: { type: "suggestion" },
|
|
19
|
-
},
|
|
20
|
-
)
|