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.
Files changed (81) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/package.json +6 -7
  3. package/src/presets/core.ts +23 -12
  4. package/src/presets/effect-native.ts +18 -25
  5. package/src/presets/full.ts +32 -46
  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 +17 -28
  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 +89 -0
  13. package/src/rules/no-effect-fn-generator.ts +1 -2
  14. package/src/rules/no-effect-gen-adapter.ts +1 -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-schema-struct-with-tag.ts +64 -0
  50. package/src/rules/no-schema-union-of-literals.ts +48 -0
  51. package/src/rules/no-string-sentinel-const.ts +1 -2
  52. package/src/rules/no-ternary.ts +1 -3
  53. package/src/rules/no-throw-statement.ts +32 -6
  54. package/src/rules/no-try-catch.ts +32 -7
  55. package/src/rules/no-unnecessary-arrow-block.ts +1 -2
  56. package/src/rules/no-unnecessary-effect-gen.ts +1 -3
  57. package/src/rules/no-unnecessary-pipe-chain.ts +47 -0
  58. package/src/rules/no-unnecessary-pipe.ts +1 -2
  59. package/src/vendor/effect-oxlint/Rule.ts +111 -173
  60. package/tests/ban-rules.test.ts +0 -167
  61. package/tests/context-rules.test.ts +75 -158
  62. package/tests/pattern-rules.test.ts +39 -50
  63. package/src/rules/no-console-in-effect.ts +0 -46
  64. package/src/rules/no-date-in-effect.ts +0 -61
  65. package/src/rules/no-dynamic-import.ts +0 -13
  66. package/src/rules/no-fetch-in-effect.ts +0 -47
  67. package/src/rules/no-global-console.ts +0 -17
  68. package/src/rules/no-global-date.ts +0 -20
  69. package/src/rules/no-global-fetch.ts +0 -14
  70. package/src/rules/no-global-random.ts +0 -13
  71. package/src/rules/no-global-timers.ts +0 -17
  72. package/src/rules/no-json-in-effect.ts +0 -45
  73. package/src/rules/no-json-parse.ts +0 -18
  74. package/src/rules/no-platform-globals.ts +0 -117
  75. package/src/rules/no-process-env-in-effect.ts +0 -39
  76. package/src/rules/no-process-env.ts +0 -13
  77. package/src/rules/no-random-in-effect.ts +0 -39
  78. package/src/rules/no-return-null.ts +0 -47
  79. package/src/rules/no-throw-in-effect-gen.ts +0 -42
  80. package/src/rules/no-timers-in-effect.ts +0 -47
  81. 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
- 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,
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("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
- ])
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
- test("ignores throw outside Effect.gen", () => {
39
- const result = Testing.runRuleMulti(noThrowInEffectGen, [
40
- ["ThrowStatement", Testing.throwStmt()],
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("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
- ])
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
- test("ignores try/catch outside Effect.gen", () => {
57
- const result = Testing.runRuleMulti(noTryCatchInEffectGen, [
58
- ["TryStatement", Testing.tryStmt()],
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("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
- ])
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("ignores console.log outside Effect.gen", () => {
75
- const result = Testing.runRuleMulti(noConsoleInEffect, [
76
- ["MemberExpression", Testing.memberExpr("console", "log")],
77
- ])
78
- Testing.expectNoDiagnostics(result)
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
- describe("noFetchInEffect", () => {
83
- test("reports fetch() inside Effect.gen", () => {
84
- const result = Testing.runRuleMulti(noFetchInEffect, [
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
- ["CallExpression", Testing.callExpr("fetch")],
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("ignores fetch() outside Effect.gen", () => {
93
- const result = Testing.runRuleMulti(noFetchInEffect, [
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
- Testing.expectNoDiagnostics(result)
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(noDateInEffect, [
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(noDateInEffect, [
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(noRandomInEffect, [
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(noTimersInEffect, [
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(noJsonInEffect, [
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(noProcessEnvInEffect, [
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("ignores process.env outside Effect.gen", () => {
192
- const result = Testing.runRuleMulti(noProcessEnvInEffect, [
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 process.cwd with Path hint", () => {
234
- const result = Testing.runRuleMulti(noPlatformGlobals, [
161
+ test("reports Bun.file with FileSystem hint", () => {
162
+ const result = Testing.runRuleMulti(noGlobals, [
235
163
  ["CallExpression", effectGenNode],
236
- ["MemberExpression", Testing.memberExpr("process", "cwd")],
164
+ ["MemberExpression", Testing.memberExpr("Bun", "file")],
237
165
  ["CallExpression:exit", effectGenNode],
238
166
  ])
239
167
  Testing.expectDiagnostics(result, [{
240
- message: "Avoid process.cwd inside Effect context. Use Path from 'effect'.",
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(noPlatformGlobals, [
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("reports unknown property with generic hint", () => {
256
- const result = Testing.runRuleMulti(noPlatformGlobals, [
257
- ["CallExpression", effectGenNode],
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 non-platform globals inside Effect.gen", () => {
274
- const result = Testing.runRuleMulti(noPlatformGlobals, [
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
- 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
- })
@@ -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
- )