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.
Files changed (84) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/package.json +6 -7
  3. package/src/presets/core.ts +24 -13
  4. package/src/presets/effect-native.ts +18 -25
  5. package/src/presets/full.ts +33 -47
  6. package/src/presets/functional.ts +18 -0
  7. package/src/presets/index.ts +2 -0
  8. package/src/presets/strict.ts +77 -0
  9. package/src/rules/index.ts +18 -29
  10. package/src/rules/no-arrow-ladder.ts +1 -2
  11. package/src/rules/no-async-function.ts +1 -2
  12. package/src/rules/no-catch-all-to-map-error.ts +90 -0
  13. package/src/rules/no-effect-fn-generator.ts +1 -2
  14. package/src/rules/no-effect-gen-adapter.ts +2 -2
  15. package/src/rules/no-effect-map-flatten.ts +82 -0
  16. package/src/rules/no-effect-map-void.ts +1 -2
  17. package/src/rules/no-effect-orElse-ladder.ts +1 -2
  18. package/src/rules/no-effect-succeed-string.ts +1 -2
  19. package/src/rules/no-effect-succeed-variable.ts +1 -2
  20. package/src/rules/no-effect-succeed-void.ts +1 -2
  21. package/src/rules/no-effect-sync-console.ts +1 -2
  22. package/src/rules/no-effect-sync-wrapper.ts +1 -2
  23. package/src/rules/no-effect-type-alias.ts +1 -2
  24. package/src/rules/no-effect-wrapper-alias.ts +1 -4
  25. package/src/rules/no-extends-native-error.ts +1 -2
  26. package/src/rules/no-flatmap-ladder.ts +1 -3
  27. package/src/rules/no-fromnullable-coalesce.ts +1 -2
  28. package/src/rules/no-global-error-in-catch.ts +104 -0
  29. package/src/rules/no-global-error-in-failure.ts +83 -0
  30. package/src/rules/no-globals.ts +178 -0
  31. package/src/rules/no-iife-wrapper.ts +1 -2
  32. package/src/rules/{no-inline-runtime-provide.ts → no-inline-provide.ts} +3 -4
  33. package/src/rules/no-instanceof-schema.ts +2 -3
  34. package/src/rules/no-manual-effect-channels.ts +1 -2
  35. package/src/rules/no-match-effect-branch.ts +1 -2
  36. package/src/rules/no-match-void-branch.ts +1 -2
  37. package/src/rules/no-multiple-effect-provide.ts +75 -0
  38. package/src/rules/no-nested-effect-call.ts +1 -3
  39. package/src/rules/no-nested-effect-gen.ts +1 -2
  40. package/src/rules/no-nested-pipe.ts +1 -4
  41. package/src/rules/no-new-error.ts +46 -10
  42. package/src/rules/no-new-promise.ts +43 -7
  43. package/src/rules/no-node-builtin-import.ts +1 -3
  44. package/src/rules/no-option-boolean-normalization.ts +1 -2
  45. package/src/rules/no-positional-log-error.ts +76 -0
  46. package/src/rules/no-redundant-schema-tag-identifier.ts +62 -0
  47. package/src/rules/no-return-in-arrow.ts +1 -2
  48. package/src/rules/no-return-nullish.ts +80 -0
  49. package/src/rules/no-run-in-effect-gen.ts +66 -0
  50. package/src/rules/no-runtime-run-fork.ts +1 -1
  51. package/src/rules/no-schema-struct-with-tag.ts +64 -0
  52. package/src/rules/no-schema-union-of-literals.ts +48 -0
  53. package/src/rules/no-string-sentinel-const.ts +1 -2
  54. package/src/rules/no-ternary.ts +1 -3
  55. package/src/rules/no-throw-statement.ts +32 -6
  56. package/src/rules/no-try-catch.ts +32 -7
  57. package/src/rules/no-unnecessary-arrow-block.ts +1 -2
  58. package/src/rules/no-unnecessary-effect-gen.ts +1 -3
  59. package/src/rules/no-unnecessary-pipe-chain.ts +47 -0
  60. package/src/rules/no-unnecessary-pipe.ts +1 -2
  61. package/src/vendor/effect-oxlint/Rule.ts +141 -183
  62. package/tests/ban-rules.test.ts +0 -184
  63. package/tests/context-rules.test.ts +127 -145
  64. package/tests/pattern-rules.test.ts +39 -50
  65. package/src/rules/no-console-in-effect.ts +0 -46
  66. package/src/rules/no-date-in-effect.ts +0 -61
  67. package/src/rules/no-dynamic-import.ts +0 -13
  68. package/src/rules/no-fetch-in-effect.ts +0 -47
  69. package/src/rules/no-global-console.ts +0 -17
  70. package/src/rules/no-global-date.ts +0 -20
  71. package/src/rules/no-global-fetch.ts +0 -14
  72. package/src/rules/no-global-random.ts +0 -13
  73. package/src/rules/no-global-timers.ts +0 -17
  74. package/src/rules/no-json-in-effect.ts +0 -45
  75. package/src/rules/no-json-parse.ts +0 -18
  76. package/src/rules/no-platform-globals.ts +0 -117
  77. package/src/rules/no-process-env-in-effect.ts +0 -39
  78. package/src/rules/no-process-env.ts +0 -13
  79. package/src/rules/no-random-in-effect.ts +0 -39
  80. package/src/rules/no-return-null.ts +0 -47
  81. package/src/rules/no-run-in-effect.ts +0 -18
  82. package/src/rules/no-throw-in-effect-gen.ts +0 -42
  83. package/src/rules/no-timers-in-effect.ts +0 -47
  84. 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
- noPlatformGlobals,
5
- noThrowInEffectGen,
6
- noTryCatchInEffectGen,
7
- noConsoleInEffect,
8
- noFetchInEffect,
9
- noDateInEffect,
10
- noRandomInEffect,
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("noThrowInEffectGen", () => {
29
- test("reports throw inside Effect.gen", () => {
30
- const result = Testing.runRuleMulti(noThrowInEffectGen, [
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
- test("ignores throw outside Effect.gen", () => {
39
- const result = Testing.runRuleMulti(noThrowInEffectGen, [
40
- ["ThrowStatement", Testing.throwStmt()],
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("noTryCatchInEffectGen", () => {
47
- test("reports try/catch inside Effect.gen", () => {
48
- const result = Testing.runRuleMulti(noTryCatchInEffectGen, [
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
- test("ignores try/catch outside Effect.gen", () => {
57
- const result = Testing.runRuleMulti(noTryCatchInEffectGen, [
58
- ["TryStatement", Testing.tryStmt()],
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("noConsoleInEffect", () => {
65
- test("reports console.log inside Effect.gen", () => {
66
- const result = Testing.runRuleMulti(noConsoleInEffect, [
67
- ["CallExpression", effectGenNode],
68
- ["MemberExpression", Testing.memberExpr("console", "log")],
69
- ["CallExpression:exit", effectGenNode],
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("ignores console.log outside Effect.gen", () => {
75
- const result = Testing.runRuleMulti(noConsoleInEffect, [
76
- ["MemberExpression", Testing.memberExpr("console", "log")],
77
- ])
78
- Testing.expectNoDiagnostics(result)
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
- describe("noFetchInEffect", () => {
83
- test("reports fetch() inside Effect.gen", () => {
84
- const result = Testing.runRuleMulti(noFetchInEffect, [
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
- ["CallExpression", Testing.callExpr("fetch")],
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("ignores fetch() outside Effect.gen", () => {
93
- const result = Testing.runRuleMulti(noFetchInEffect, [
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
- Testing.expectNoDiagnostics(result)
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(noDateInEffect, [
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(noDateInEffect, [
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(noRandomInEffect, [
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(noTimersInEffect, [
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(noJsonInEffect, [
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(noProcessEnvInEffect, [
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("ignores process.env outside Effect.gen", () => {
192
- const result = Testing.runRuleMulti(noProcessEnvInEffect, [
193
- ["MemberExpression", Testing.memberExpr("process", "env")],
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.expectNoDiagnostics(result)
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(noPlatformGlobals, [
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 Bun.serve with HttpServer hint", () => {
212
- const result = Testing.runRuleMulti(noPlatformGlobals, [
173
+ test("reports Deno.readFile with FileSystem hint", () => {
174
+ const result = Testing.runRuleMulti(noGlobals, [
213
175
  ["CallExpression", effectGenNode],
214
- ["MemberExpression", Testing.memberExpr("Bun", "serve")],
176
+ ["MemberExpression", Testing.memberExpr("Deno", "readFile")],
215
177
  ["CallExpression:exit", effectGenNode],
216
178
  ])
217
179
  Testing.expectDiagnostics(result, [{
218
- message: "Avoid Bun.serve inside Effect context. Use HttpServer from 'effect/unstable/http'.",
180
+ message: "Avoid Deno.readFile inside Effect context. Use FileSystem from 'effect'.",
219
181
  }])
220
182
  })
221
183
 
222
- test("reports process.exit with Effect.fail hint", () => {
223
- const result = Testing.runRuleMulti(noPlatformGlobals, [
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("process", "exit")],
194
+ ["MemberExpression", Testing.memberExpr("Effect", "succeed")],
226
195
  ["CallExpression:exit", effectGenNode],
227
196
  ])
228
- Testing.expectDiagnostics(result, [{
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
- test("reports process.cwd with Path hint", () => {
234
- const result = Testing.runRuleMulti(noPlatformGlobals, [
201
+ describe("noRunInEffectGen", () => {
202
+ test("reports Effect.runPromise inside Effect.gen", () => {
203
+ const result = Testing.runRuleMulti(noRunInEffectGen, [
235
204
  ["CallExpression", effectGenNode],
236
- ["MemberExpression", Testing.memberExpr("process", "cwd")],
205
+ ["CallExpression", Testing.callOfMember("Effect", "runPromise", [Testing.id("x")])],
237
206
  ["CallExpression:exit", effectGenNode],
238
207
  ])
239
- Testing.expectDiagnostics(result, [{
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 Deno.readFile with FileSystem hint", () => {
245
- const result = Testing.runRuleMulti(noPlatformGlobals, [
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
- ["MemberExpression", Testing.memberExpr("Deno", "readFile")],
224
+ ["CallExpression", Testing.callOfMember("Runtime", "runFork", [Testing.id("x")])],
248
225
  ["CallExpression:exit", effectGenNode],
249
226
  ])
250
- Testing.expectDiagnostics(result, [{
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("reports unknown property with generic hint", () => {
256
- const result = Testing.runRuleMulti(noPlatformGlobals, [
230
+ test("ignores Effect.runForkWith inside Effect.gen (legit v4 callback boundary)", () => {
231
+ const result = Testing.runRuleMulti(noRunInEffectGen, [
257
232
  ["CallExpression", effectGenNode],
258
- ["MemberExpression", Testing.memberExpr("Bun", "someNewApi")],
233
+ ["CallExpression", Testing.callOfMember("Effect", "runForkWith", [Testing.id("services")])],
259
234
  ["CallExpression:exit", effectGenNode],
260
235
  ])
261
- Testing.expectDiagnostics(result, [{
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 Bun.file outside Effect.gen", () => {
267
- const result = Testing.runRuleMulti(noPlatformGlobals, [
268
- ["MemberExpression", Testing.memberExpr("Bun", "file")],
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 non-platform globals inside Effect.gen", () => {
274
- const result = Testing.runRuleMulti(noPlatformGlobals, [
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
- ["MemberExpression", Testing.memberExpr("Effect", "succeed")],
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
- noFlatmapLadder,
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
- })