Skip to content

Commit 0af9b98

Browse files
Support runEffectInsideEffect in Effect v4 (#702)
1 parent ff5fd04 commit 0af9b98

13 files changed

+160
-15
lines changed

.changeset/pretty-pigs-push.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@effect/language-service": patch
3+
---
4+
5+
Add Effect v4 support for the `runEffectInsideEffect` diagnostic so it suggests and fixes `Effect.run*With` usage based on `Effect.services`.
6+
7+
Update the generated metadata, schema, README entry, and v4 harness examples/snapshots to document and verify the new behavior.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ Some diagnostics are off by default or have a default severity of suggestion, bu
8181
<tr><td><code>leakingRequirements</code></td><td>💡</td><td></td><td>Detects implementation services leaked in service methods</td><td>✓</td><td>✓</td></tr>
8282
<tr><td><code>multipleEffectProvide</code></td><td>⚠️</td><td>🔧</td><td>Warns against chaining Effect.provide calls which can cause service lifecycle issues</td><td>✓</td><td>✓</td></tr>
8383
<tr><td><code>returnEffectInGen</code></td><td>💡</td><td>🔧</td><td>Warns when returning an Effect in a generator causes nested Effect&lt;Effect&lt;...&gt;&gt;</td><td>✓</td><td>✓</td></tr>
84-
<tr><td><code>runEffectInsideEffect</code></td><td>💡</td><td>🔧</td><td>Suggests using Runtime methods instead of Effect.run* inside Effect contexts</td><td>✓</td><td></td></tr>
84+
<tr><td><code>runEffectInsideEffect</code></td><td>💡</td><td>🔧</td><td>Suggests using Runtime or Effect.run*With methods instead of Effect.run* inside Effect contexts</td><td>✓</td><td></td></tr>
8585
<tr><td><code>schemaSyncInEffect</code></td><td>💡</td><td></td><td>Suggests using Effect-based Schema methods instead of sync methods inside Effect generators</td><td>✓</td><td></td></tr>
8686
<tr><td><code>scopeInLayerEffect</code></td><td>⚠️</td><td>🔧</td><td>Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements</td><td>✓</td><td></td></tr>
8787
<tr><td><code>strictEffectProvide</code></td><td>➖</td><td></td><td>Warns when using Effect.provide with layers outside of application entry points</td><td>✓</td><td>✓</td></tr>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
runEffectInsideEffect_skipNextLine from 404 to 418
2+
runEffectInsideEffect_skipFile from 404 to 418
3+
runEffectInsideEffect_fix from 702 to 719
4+
runEffectInsideEffect_skipNextLine from 702 to 719
5+
runEffectInsideEffect_skipFile from 702 to 719
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Effect.runSync
2+
14:20 - 14:34 | 2 | Using Effect.runSync inside an Effect is not recommended. Effects inside generators can usually just be yielded. effect(runEffectInsideEffect)
3+
4+
Effect.runPromise
5+
22:4 - 22:21 | 2 | Using Effect.runPromise inside an Effect is not recommended. The same services should generally be used instead to run child effects.
6+
Consider extracting the current services by using for example Effect.services and then use Effect.runPromiseWith with the extracted services instead. effect(runEffectInsideEffect)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// code fix runEffectInsideEffect_fix output for range 702 - 719
2+
import { Data, Effect } from "effect"
3+
4+
class DebuggerError extends Data.TaggedError("DebuggerError")<{
5+
cause: unknown
6+
}> {}
7+
8+
export const program = Effect.gen(function*() {
9+
const effectServices = yield* Effect.services<never>()
10+
11+
const response = yield* Effect.tryPromise({
12+
try: () => fetch("http://localhost:9229"),
13+
catch: (e) => new DebuggerError({ cause: e })
14+
})
15+
const data = yield* Effect.promise(() => response.json())
16+
17+
const websocket = Effect.runSync(Effect.sync(() => new WebSocket(data.url)))
18+
// ^- do not runSync in here
19+
20+
websocket.onmessage = (event) => {
21+
const check = Effect.tryPromise({
22+
try: () => fetch(event.data as string),
23+
catch: (e) => new DebuggerError({ cause: e })
24+
})
25+
Effect.runPromiseWith(effectServices)(check)
26+
// ^- no runPromise, use the current services instead
27+
}
28+
})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
runEffectInsideEffect_fix from 183 to 197
2+
runEffectInsideEffect_skipNextLine from 183 to 197
3+
runEffectInsideEffect_skipFile from 183 to 197
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Effect.runSync
2+
6:20 - 6:34 | 0 | Using Effect.runSync inside an Effect is not recommended. The same services should generally be used instead to run child effects.
3+
Consider extracting the current services by using for example Effect.services and then use Effect.runSyncWith with the extracted services instead. effect(runEffectInsideEffect)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// code fix runEffectInsideEffect_fix output for range 183 - 197
2+
// @effect-diagnostics *:off
3+
// @effect-diagnostics runEffectInsideEffect:warning
4+
import { Effect } from "effect"
5+
6+
export const preview = Effect.gen(function*() {
7+
const effectServices = yield* Effect.services<never>()
8+
9+
const run = () => Effect.runSyncWith(effectServices)(Effect.succeed(1))
10+
return run()
11+
})
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Data, Effect } from "effect"
2+
3+
class DebuggerError extends Data.TaggedError("DebuggerError")<{
4+
cause: unknown
5+
}> {}
6+
7+
export const program = Effect.gen(function*() {
8+
const response = yield* Effect.tryPromise({
9+
try: () => fetch("http://localhost:9229"),
10+
catch: (e) => new DebuggerError({ cause: e })
11+
})
12+
const data = yield* Effect.promise(() => response.json())
13+
14+
const websocket = Effect.runSync(Effect.sync(() => new WebSocket(data.url)))
15+
// ^- do not runSync in here
16+
17+
websocket.onmessage = (event) => {
18+
const check = Effect.tryPromise({
19+
try: () => fetch(event.data as string),
20+
catch: (e) => new DebuggerError({ cause: e })
21+
})
22+
Effect.runPromise(check)
23+
// ^- no runPromise, use the current services instead
24+
}
25+
})
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// @effect-diagnostics *:off
2+
// @effect-diagnostics runEffectInsideEffect:warning
3+
import { Effect } from "effect"
4+
5+
export const preview = Effect.gen(function*() {
6+
const run = () => Effect.runSync(Effect.succeed(1))
7+
return run()
8+
})

0 commit comments

Comments
 (0)