diff --git a/.changeset/tall-maps-unite.md b/.changeset/tall-maps-unite.md new file mode 100644 index 00000000..12418d42 --- /dev/null +++ b/.changeset/tall-maps-unite.md @@ -0,0 +1,7 @@ +--- +"@effect/language-service": patch +--- + +Refactor Effect context tracking to use cached node context flags and direct generator lookups. + +This aligns the TypeScript implementation more closely with the TSGo version and simplifies diagnostics that need to detect whether code is inside an Effect generator. diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..57adefa8 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +globalConsoleInEffect_skipNextLine from 207 to 226 +globalConsoleInEffect_skipFile from 207 to 226 +globalConsoleInEffect_skipNextLine from 413 to 439 +globalConsoleInEffect_skipFile from 413 to 439 +globalConsoleInEffect_skipNextLine from 289 to 308 +globalConsoleInEffect_skipFile from 289 to 308 +globalConsoleInEffect_skipNextLine from 522 to 556 +globalConsoleInEffect_skipFile from 522 to 556 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.output new file mode 100644 index 00000000..193f5b60 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +console.log("sync") +6:47 - 6:66 | 0 | This Effect code uses `console.log`, logging in Effect code is represented through `Effect.log or Logger`. effect(globalConsoleInEffect) + +console.warn("try") +9:13 - 9:32 | 0 | This Effect code uses `console.warn`, logging in Effect code is represented through `Effect.logWarning or Logger`. effect(globalConsoleInEffect) + +console.log("try-promise") +13:65 - 13:91 | 0 | This Effect code uses `console.log`, logging in Effect code is represented through `Effect.log or Logger`. effect(globalConsoleInEffect) + +console.warn("try-promise-object") +16:19 - 16:53 | 0 | This Effect code uses `console.warn`, logging in Effect code is represented through `Effect.logWarning or Logger`. effect(globalConsoleInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..03b101cc --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +globalDateInEffect_skipNextLine from 201 to 211 +globalDateInEffect_skipFile from 201 to 211 +globalDateInEffect_skipNextLine from 386 to 396 +globalDateInEffect_skipFile from 386 to 396 +globalDateInEffect_skipNextLine from 274 to 284 +globalDateInEffect_skipFile from 274 to 284 +globalDateInEffect_skipNextLine from 476 to 486 +globalDateInEffect_skipFile from 476 to 486 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.output new file mode 100644 index 00000000..d7e21e4d --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +Date.now() +6:44 - 6:54 | 0 | This Effect code uses `Date.now()`, time access in Effect code is represented through `Clock` from Effect. effect(globalDateInEffect) + +new Date() +9:13 - 9:23 | 0 | This Effect code constructs `new Date()`, date values in Effect code are represented through `DateTime` from Effect. effect(globalDateInEffect) + +Date.now() +13:62 - 13:72 | 0 | This Effect code uses `Date.now()`, time access in Effect code is represented through `Clock` from Effect. effect(globalDateInEffect) + +new Date() +16:19 - 16:29 | 0 | This Effect code constructs `new Date()`, date values in Effect code are represented through `DateTime` from Effect. effect(globalDateInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.codefixes index a6eac123..42189401 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.codefixes +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.codefixes @@ -1 +1,2 @@ -no codefixes \ No newline at end of file +globalFetchInEffect_skipNextLine from 198 to 203 +globalFetchInEffect_skipFile from 198 to 203 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.output index aa033c38..14743a2a 100644 --- a/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.output +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.output @@ -1 +1,2 @@ -// no diagnostics \ No newline at end of file +fetch +6:37 - 6:42 | 0 | This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `@effect/platform`. effect(globalFetchInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..98e621b0 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +globalFetchInEffect_skipNextLine from 203 to 208 +globalFetchInEffect_skipFile from 203 to 208 +globalFetchInEffect_skipNextLine from 432 to 437 +globalFetchInEffect_skipFile from 432 to 437 +globalFetchInEffect_skipNextLine from 297 to 302 +globalFetchInEffect_skipFile from 297 to 302 +globalFetchInEffect_skipNextLine from 553 to 558 +globalFetchInEffect_skipFile from 553 to 558 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.output new file mode 100644 index 00000000..e2119261 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +fetch +6:45 - 6:50 | 0 | This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `@effect/platform`. effect(globalFetchInEffect) + +fetch +9:13 - 9:18 | 0 | This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `@effect/platform`. effect(globalFetchInEffect) + +fetch +13:63 - 13:68 | 0 | This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `@effect/platform`. effect(globalFetchInEffect) + +fetch +16:19 - 16:24 | 0 | This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `@effect/platform`. effect(globalFetchInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..081f1946 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +globalRandomInEffect_skipNextLine from 205 to 218 +globalRandomInEffect_skipFile from 205 to 218 +globalRandomInEffect_skipNextLine from 397 to 410 +globalRandomInEffect_skipFile from 397 to 410 +globalRandomInEffect_skipNextLine from 280 to 293 +globalRandomInEffect_skipFile from 280 to 293 +globalRandomInEffect_skipNextLine from 492 to 505 +globalRandomInEffect_skipFile from 492 to 505 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.output new file mode 100644 index 00000000..56773ceb --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +Math.random() +6:46 - 6:59 | 0 | This Effect code uses `Math.random()`, randomness is represented through the Effect `Random` service. effect(globalRandomInEffect) + +Math.random() +9:13 - 9:26 | 0 | This Effect code uses `Math.random()`, randomness is represented through the Effect `Random` service. effect(globalRandomInEffect) + +Math.random() +13:64 - 13:77 | 0 | This Effect code uses `Math.random()`, randomness is represented through the Effect `Random` service. effect(globalRandomInEffect) + +Math.random() +16:19 - 16:32 | 0 | This Effect code uses `Math.random()`, randomness is represented through the Effect `Random` service. effect(globalRandomInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..cea72497 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +globalTimersInEffect_skipNextLine from 205 to 229 +globalTimersInEffect_skipFile from 205 to 229 +globalTimersInEffect_skipNextLine from 420 to 444 +globalTimersInEffect_skipFile from 420 to 444 +globalTimersInEffect_skipNextLine from 291 to 316 +globalTimersInEffect_skipFile from 291 to 316 +globalTimersInEffect_skipNextLine from 526 to 550 +globalTimersInEffect_skipFile from 526 to 550 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.output new file mode 100644 index 00000000..1547ec24 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +setTimeout(() => {}, 10) +6:46 - 6:70 | 0 | This Effect code uses `setTimeout`, the corresponding timer API in this context is `Effect.sleep or Schedule` from Effect. effect(globalTimersInEffect) + +setInterval(() => {}, 10) +9:13 - 9:38 | 0 | This Effect code uses `setInterval`, the corresponding timer API in this context is `Schedule or Effect.repeat` from Effect. effect(globalTimersInEffect) + +setTimeout(() => {}, 10) +13:64 - 13:88 | 0 | This Effect code uses `setTimeout`, the corresponding timer API in this context is `Effect.sleep or Schedule` from Effect. effect(globalTimersInEffect) + +setTimeout(() => {}, 10) +16:19 - 16:43 | 0 | This Effect code uses `setTimeout`, the corresponding timer API in this context is `Effect.sleep or Schedule` from Effect. effect(globalTimersInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..430101f9 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +processEnvInEffect_skipNextLine from 231 to 254 +processEnvInEffect_skipFile from 231 to 254 +processEnvInEffect_skipNextLine from 436 to 466 +processEnvInEffect_skipFile from 436 to 466 +processEnvInEffect_skipNextLine from 313 to 335 +processEnvInEffect_skipFile from 313 to 335 +processEnvInEffect_skipNextLine from 545 to 582 +processEnvInEffect_skipFile from 545 to 582 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.output new file mode 100644 index 00000000..fe1b56db --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +process.env.SYNC_SECRET +7:43 - 7:66 | 0 | This Effect code reads from `process.env`, environment configuration in Effect code is represented through `Config` from Effect. effect(processEnvInEffect) + +process.env.TRY_SECRET +10:13 - 10:35 | 0 | This Effect code reads from `process.env`, environment configuration in Effect code is represented through `Config` from Effect. effect(processEnvInEffect) + +process.env.TRY_PROMISE_SECRET +14:61 - 14:91 | 0 | This Effect code reads from `process.env`, environment configuration in Effect code is represented through `Config` from Effect. effect(processEnvInEffect) + +process.env.TRY_PROMISE_OBJECT_SECRET +17:19 - 17:56 | 0 | This Effect code reads from `process.env`, environment configuration in Effect code is represented through `Config` from Effect. effect(processEnvInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.codefixes b/packages/harness-effect-v3/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..220c97eb --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +schemaSyncInEffect_skipNextLine from 240 to 257 +schemaSyncInEffect_skipFile from 240 to 257 +schemaSyncInEffect_skipNextLine from 510 to 527 +schemaSyncInEffect_skipFile from 510 to 527 +schemaSyncInEffect_skipNextLine from 354 to 371 +schemaSyncInEffect_skipFile from 354 to 371 +schemaSyncInEffect_skipNextLine from 643 to 667 +schemaSyncInEffect_skipFile from 643 to 667 \ No newline at end of file diff --git a/packages/harness-effect-v3/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.output b/packages/harness-effect-v3/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.output new file mode 100644 index 00000000..7f5cb447 --- /dev/null +++ b/packages/harness-effect-v3/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +Schema.decodeSync +10:46 - 10:63 | 2 | `Schema.decodeSync` is used inside an Effect generator. `Schema.decode` preserves the typed Effect error channel for this operation without throwing. effect(schemaSyncInEffect) + +Schema.decodeSync +13:13 - 13:30 | 2 | `Schema.decodeSync` is used inside an Effect generator. `Schema.decode` preserves the typed Effect error channel for this operation without throwing. effect(schemaSyncInEffect) + +Schema.encodeSync +17:64 - 17:81 | 2 | `Schema.encodeSync` is used inside an Effect generator. `Schema.encode` preserves the typed Effect error channel for this operation without throwing. effect(schemaSyncInEffect) + +Schema.encodeUnknownSync +20:19 - 20:43 | 2 | `Schema.encodeUnknownSync` is used inside an Effect generator. `Schema.encodeUnknown` preserves the typed Effect error channel for this operation without throwing. effect(schemaSyncInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v3/examples/diagnostics/globalConsoleInEffect_thunks.ts b/packages/harness-effect-v3/examples/diagnostics/globalConsoleInEffect_thunks.ts new file mode 100644 index 00000000..f523fe72 --- /dev/null +++ b/packages/harness-effect-v3/examples/diagnostics/globalConsoleInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics globalConsoleInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const consoleInSync = Effect.sync(() => console.log("sync")) + +export const consoleInTryObject = Effect.try({ + try: () => console.warn("try"), + catch: () => new ExampleError() +}) + +export const consoleInTryPromise = Effect.tryPromise(async () => console.log("try-promise")) + +export const consoleInTryPromiseObject = Effect.tryPromise({ + try: async () => console.warn("try-promise-object"), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => console.log("nested")) diff --git a/packages/harness-effect-v3/examples/diagnostics/globalDateInEffect_thunks.ts b/packages/harness-effect-v3/examples/diagnostics/globalDateInEffect_thunks.ts new file mode 100644 index 00000000..3557bd20 --- /dev/null +++ b/packages/harness-effect-v3/examples/diagnostics/globalDateInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics globalDateInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const dateInSync = Effect.sync(() => Date.now()) + +export const newDateInTryObject = Effect.try({ + try: () => new Date(), + catch: () => new ExampleError() +}) + +export const dateInTryPromise = Effect.tryPromise(async () => Date.now()) + +export const dateInTryPromiseObject = Effect.tryPromise({ + try: async () => new Date(), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => new Date()) diff --git a/packages/harness-effect-v3/examples/diagnostics/globalFetchInEffect_thunks.ts b/packages/harness-effect-v3/examples/diagnostics/globalFetchInEffect_thunks.ts new file mode 100644 index 00000000..f2a713f4 --- /dev/null +++ b/packages/harness-effect-v3/examples/diagnostics/globalFetchInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics globalFetchInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const fetchInSync = Effect.sync(() => fetch("https://example.com/sync")) + +export const fetchInTryObject = Effect.try({ + try: () => fetch("https://example.com/try"), + catch: () => new ExampleError() +}) + +export const fetchInTryPromise = Effect.tryPromise(async () => fetch("https://example.com/try-promise")) + +export const fetchInTryPromiseObject = Effect.tryPromise({ + try: async () => fetch("https://example.com/try-promise-object"), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => fetch("https://example.com/nested")) diff --git a/packages/harness-effect-v3/examples/diagnostics/globalRandomInEffect_thunks.ts b/packages/harness-effect-v3/examples/diagnostics/globalRandomInEffect_thunks.ts new file mode 100644 index 00000000..72b52b83 --- /dev/null +++ b/packages/harness-effect-v3/examples/diagnostics/globalRandomInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics globalRandomInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const randomInSync = Effect.sync(() => Math.random()) + +export const randomInTryObject = Effect.try({ + try: () => Math.random(), + catch: () => new ExampleError() +}) + +export const randomInTryPromise = Effect.tryPromise(async () => Math.random()) + +export const randomInTryPromiseObject = Effect.tryPromise({ + try: async () => Math.random(), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => Math.random()) diff --git a/packages/harness-effect-v3/examples/diagnostics/globalTimersInEffect_thunks.ts b/packages/harness-effect-v3/examples/diagnostics/globalTimersInEffect_thunks.ts new file mode 100644 index 00000000..555dc1db --- /dev/null +++ b/packages/harness-effect-v3/examples/diagnostics/globalTimersInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics globalTimersInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const timersInSync = Effect.sync(() => setTimeout(() => {}, 10)) + +export const timersInTryObject = Effect.try({ + try: () => setInterval(() => {}, 10), + catch: () => new ExampleError() +}) + +export const timersInTryPromise = Effect.tryPromise(async () => setTimeout(() => {}, 10)) + +export const timersInTryPromiseObject = Effect.tryPromise({ + try: async () => setTimeout(() => {}, 10), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => setTimeout(() => {}, 10)) diff --git a/packages/harness-effect-v3/examples/diagnostics/preferSchemaOverJson.ts b/packages/harness-effect-v3/examples/diagnostics/preferSchemaOverJson.ts index e3e22eec..27979e18 100644 --- a/packages/harness-effect-v3/examples/diagnostics/preferSchemaOverJson.ts +++ b/packages/harness-effect-v3/examples/diagnostics/preferSchemaOverJson.ts @@ -57,6 +57,8 @@ export const nestedFunctionInGen = Effect.gen(function*() { // Should NOT trigger - JSON.parse not as direct expression in Effect.try export const notDirectExpression = Effect.try(() => { - const parsed = JSON.parse("{\"indirect\":true}") - return parsed.indirect + return () => { + const parsed = JSON.parse("{\"indirect\":true}") + return parsed.indirect + } }) diff --git a/packages/harness-effect-v3/examples/diagnostics/processEnvInEffect_thunks.ts b/packages/harness-effect-v3/examples/diagnostics/processEnvInEffect_thunks.ts new file mode 100644 index 00000000..a59cdecc --- /dev/null +++ b/packages/harness-effect-v3/examples/diagnostics/processEnvInEffect_thunks.ts @@ -0,0 +1,21 @@ +// @effect-diagnostics processEnvInEffect:warning +/// +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const envInSync = Effect.sync(() => process.env.SYNC_SECRET) + +export const envInTryObject = Effect.try({ + try: () => process.env.TRY_SECRET, + catch: () => new ExampleError() +}) + +export const envInTryPromise = Effect.tryPromise(async () => process.env.TRY_PROMISE_SECRET) + +export const envInTryPromiseObject = Effect.tryPromise({ + try: async () => process.env.TRY_PROMISE_OBJECT_SECRET, + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => process.env.NESTED_SECRET) diff --git a/packages/harness-effect-v3/examples/diagnostics/schemaSyncInEffect_thunks.ts b/packages/harness-effect-v3/examples/diagnostics/schemaSyncInEffect_thunks.ts new file mode 100644 index 00000000..bfcbc664 --- /dev/null +++ b/packages/harness-effect-v3/examples/diagnostics/schemaSyncInEffect_thunks.ts @@ -0,0 +1,26 @@ +import { Data, Effect, Schema } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +const Person = Schema.Struct({ + name: Schema.String, + age: Schema.Number +}) + +export const decodeInSync = Effect.sync(() => Schema.decodeSync(Person)({ name: "John", age: 30 })) + +export const decodeInTryObject = Effect.try({ + try: () => Schema.decodeSync(Person)({ name: "Jane", age: 25 }), + catch: () => new ExampleError() +}) + +export const encodeInTryPromise = Effect.tryPromise(async () => Schema.encodeSync(Person)({ name: "Bob", age: 40 })) + +export const encodeInTryPromiseObject = Effect.tryPromise({ + try: async () => Schema.encodeUnknownSync(Person)({ name: "Carol", age: 50 }), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync( + () => () => Schema.decodeSync(Person)({ name: "Nested", age: 10 }) +) diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/cryptoRandomUUIDInEffect_thunks.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/cryptoRandomUUIDInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..3ded6a6d --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/cryptoRandomUUIDInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +cryptoRandomUUIDInEffect_skipNextLine from 207 to 226 +cryptoRandomUUIDInEffect_skipFile from 207 to 226 +cryptoRandomUUIDInEffect_skipNextLine from 407 to 426 +cryptoRandomUUIDInEffect_skipFile from 407 to 426 +cryptoRandomUUIDInEffect_skipNextLine from 286 to 305 +cryptoRandomUUIDInEffect_skipFile from 286 to 305 +cryptoRandomUUIDInEffect_skipNextLine from 506 to 525 +cryptoRandomUUIDInEffect_skipFile from 506 to 525 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/cryptoRandomUUIDInEffect_thunks.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/cryptoRandomUUIDInEffect_thunks.ts.output new file mode 100644 index 00000000..6fab1236 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/cryptoRandomUUIDInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +crypto.randomUUID() +6:44 - 6:63 | 0 | This Effect code uses `crypto.randomUUID()`, prefer the Effect `Random` module instead because it uses Effect-injected randomness rather than the `crypto` module behind the scenes. effect(cryptoRandomUUIDInEffect) + +crypto.randomUUID() +9:13 - 9:32 | 0 | This Effect code uses `crypto.randomUUID()`, prefer the Effect `Random` module instead because it uses Effect-injected randomness rather than the `crypto` module behind the scenes. effect(cryptoRandomUUIDInEffect) + +crypto.randomUUID() +13:62 - 13:81 | 0 | This Effect code uses `crypto.randomUUID()`, prefer the Effect `Random` module instead because it uses Effect-injected randomness rather than the `crypto` module behind the scenes. effect(cryptoRandomUUIDInEffect) + +crypto.randomUUID() +16:19 - 16:38 | 0 | This Effect code uses `crypto.randomUUID()`, prefer the Effect `Random` module instead because it uses Effect-injected randomness rather than the `crypto` module behind the scenes. effect(cryptoRandomUUIDInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..57adefa8 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +globalConsoleInEffect_skipNextLine from 207 to 226 +globalConsoleInEffect_skipFile from 207 to 226 +globalConsoleInEffect_skipNextLine from 413 to 439 +globalConsoleInEffect_skipFile from 413 to 439 +globalConsoleInEffect_skipNextLine from 289 to 308 +globalConsoleInEffect_skipFile from 289 to 308 +globalConsoleInEffect_skipNextLine from 522 to 556 +globalConsoleInEffect_skipFile from 522 to 556 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.output new file mode 100644 index 00000000..193f5b60 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalConsoleInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +console.log("sync") +6:47 - 6:66 | 0 | This Effect code uses `console.log`, logging in Effect code is represented through `Effect.log or Logger`. effect(globalConsoleInEffect) + +console.warn("try") +9:13 - 9:32 | 0 | This Effect code uses `console.warn`, logging in Effect code is represented through `Effect.logWarning or Logger`. effect(globalConsoleInEffect) + +console.log("try-promise") +13:65 - 13:91 | 0 | This Effect code uses `console.log`, logging in Effect code is represented through `Effect.log or Logger`. effect(globalConsoleInEffect) + +console.warn("try-promise-object") +16:19 - 16:53 | 0 | This Effect code uses `console.warn`, logging in Effect code is represented through `Effect.logWarning or Logger`. effect(globalConsoleInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..03b101cc --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +globalDateInEffect_skipNextLine from 201 to 211 +globalDateInEffect_skipFile from 201 to 211 +globalDateInEffect_skipNextLine from 386 to 396 +globalDateInEffect_skipFile from 386 to 396 +globalDateInEffect_skipNextLine from 274 to 284 +globalDateInEffect_skipFile from 274 to 284 +globalDateInEffect_skipNextLine from 476 to 486 +globalDateInEffect_skipFile from 476 to 486 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.output new file mode 100644 index 00000000..d7e21e4d --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalDateInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +Date.now() +6:44 - 6:54 | 0 | This Effect code uses `Date.now()`, time access in Effect code is represented through `Clock` from Effect. effect(globalDateInEffect) + +new Date() +9:13 - 9:23 | 0 | This Effect code constructs `new Date()`, date values in Effect code are represented through `DateTime` from Effect. effect(globalDateInEffect) + +Date.now() +13:62 - 13:72 | 0 | This Effect code uses `Date.now()`, time access in Effect code is represented through `Clock` from Effect. effect(globalDateInEffect) + +new Date() +16:19 - 16:29 | 0 | This Effect code constructs `new Date()`, date values in Effect code are represented through `DateTime` from Effect. effect(globalDateInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.codefixes index a6eac123..42189401 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.codefixes +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.codefixes @@ -1 +1,2 @@ -no codefixes \ No newline at end of file +globalFetchInEffect_skipNextLine from 198 to 203 +globalFetchInEffect_skipFile from 198 to 203 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.output index aa033c38..6533c58c 100644 --- a/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.output +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_preview.ts.output @@ -1 +1,2 @@ -// no diagnostics \ No newline at end of file +fetch +6:37 - 6:42 | 0 | This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `effect/unstable/http`. effect(globalFetchInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..98e621b0 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +globalFetchInEffect_skipNextLine from 203 to 208 +globalFetchInEffect_skipFile from 203 to 208 +globalFetchInEffect_skipNextLine from 432 to 437 +globalFetchInEffect_skipFile from 432 to 437 +globalFetchInEffect_skipNextLine from 297 to 302 +globalFetchInEffect_skipFile from 297 to 302 +globalFetchInEffect_skipNextLine from 553 to 558 +globalFetchInEffect_skipFile from 553 to 558 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.output new file mode 100644 index 00000000..3fc7e90d --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalFetchInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +fetch +6:45 - 6:50 | 0 | This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `effect/unstable/http`. effect(globalFetchInEffect) + +fetch +9:13 - 9:18 | 0 | This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `effect/unstable/http`. effect(globalFetchInEffect) + +fetch +13:63 - 13:68 | 0 | This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `effect/unstable/http`. effect(globalFetchInEffect) + +fetch +16:19 - 16:24 | 0 | This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `effect/unstable/http`. effect(globalFetchInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..081f1946 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +globalRandomInEffect_skipNextLine from 205 to 218 +globalRandomInEffect_skipFile from 205 to 218 +globalRandomInEffect_skipNextLine from 397 to 410 +globalRandomInEffect_skipFile from 397 to 410 +globalRandomInEffect_skipNextLine from 280 to 293 +globalRandomInEffect_skipFile from 280 to 293 +globalRandomInEffect_skipNextLine from 492 to 505 +globalRandomInEffect_skipFile from 492 to 505 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.output new file mode 100644 index 00000000..56773ceb --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalRandomInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +Math.random() +6:46 - 6:59 | 0 | This Effect code uses `Math.random()`, randomness is represented through the Effect `Random` service. effect(globalRandomInEffect) + +Math.random() +9:13 - 9:26 | 0 | This Effect code uses `Math.random()`, randomness is represented through the Effect `Random` service. effect(globalRandomInEffect) + +Math.random() +13:64 - 13:77 | 0 | This Effect code uses `Math.random()`, randomness is represented through the Effect `Random` service. effect(globalRandomInEffect) + +Math.random() +16:19 - 16:32 | 0 | This Effect code uses `Math.random()`, randomness is represented through the Effect `Random` service. effect(globalRandomInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..cea72497 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +globalTimersInEffect_skipNextLine from 205 to 229 +globalTimersInEffect_skipFile from 205 to 229 +globalTimersInEffect_skipNextLine from 420 to 444 +globalTimersInEffect_skipFile from 420 to 444 +globalTimersInEffect_skipNextLine from 291 to 316 +globalTimersInEffect_skipFile from 291 to 316 +globalTimersInEffect_skipNextLine from 526 to 550 +globalTimersInEffect_skipFile from 526 to 550 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.output new file mode 100644 index 00000000..1547ec24 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/globalTimersInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +setTimeout(() => {}, 10) +6:46 - 6:70 | 0 | This Effect code uses `setTimeout`, the corresponding timer API in this context is `Effect.sleep or Schedule` from Effect. effect(globalTimersInEffect) + +setInterval(() => {}, 10) +9:13 - 9:38 | 0 | This Effect code uses `setInterval`, the corresponding timer API in this context is `Schedule or Effect.repeat` from Effect. effect(globalTimersInEffect) + +setTimeout(() => {}, 10) +13:64 - 13:88 | 0 | This Effect code uses `setTimeout`, the corresponding timer API in this context is `Effect.sleep or Schedule` from Effect. effect(globalTimersInEffect) + +setTimeout(() => {}, 10) +16:19 - 16:43 | 0 | This Effect code uses `setTimeout`, the corresponding timer API in this context is `Effect.sleep or Schedule` from Effect. effect(globalTimersInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..430101f9 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.codefixes @@ -0,0 +1,8 @@ +processEnvInEffect_skipNextLine from 231 to 254 +processEnvInEffect_skipFile from 231 to 254 +processEnvInEffect_skipNextLine from 436 to 466 +processEnvInEffect_skipFile from 436 to 466 +processEnvInEffect_skipNextLine from 313 to 335 +processEnvInEffect_skipFile from 313 to 335 +processEnvInEffect_skipNextLine from 545 to 582 +processEnvInEffect_skipFile from 545 to 582 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.output new file mode 100644 index 00000000..fe1b56db --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/processEnvInEffect_thunks.ts.output @@ -0,0 +1,11 @@ +process.env.SYNC_SECRET +7:43 - 7:66 | 0 | This Effect code reads from `process.env`, environment configuration in Effect code is represented through `Config` from Effect. effect(processEnvInEffect) + +process.env.TRY_SECRET +10:13 - 10:35 | 0 | This Effect code reads from `process.env`, environment configuration in Effect code is represented through `Config` from Effect. effect(processEnvInEffect) + +process.env.TRY_PROMISE_SECRET +14:61 - 14:91 | 0 | This Effect code reads from `process.env`, environment configuration in Effect code is represented through `Config` from Effect. effect(processEnvInEffect) + +process.env.TRY_PROMISE_OBJECT_SECRET +17:19 - 17:56 | 0 | This Effect code reads from `process.env`, environment configuration in Effect code is represented through `Config` from Effect. effect(processEnvInEffect) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.codefixes new file mode 100644 index 00000000..a6eac123 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.codefixes @@ -0,0 +1 @@ +no codefixes \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.output new file mode 100644 index 00000000..aa033c38 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/schemaSyncInEffect_thunks.ts.output @@ -0,0 +1 @@ +// no diagnostics \ No newline at end of file diff --git a/packages/harness-effect-v4/examples/diagnostics/cryptoRandomUUIDInEffect_thunks.ts b/packages/harness-effect-v4/examples/diagnostics/cryptoRandomUUIDInEffect_thunks.ts new file mode 100644 index 00000000..b7021b91 --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/cryptoRandomUUIDInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics cryptoRandomUUIDInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const uuidInSync = Effect.sync(() => crypto.randomUUID()) + +export const uuidInTryObject = Effect.try({ + try: () => crypto.randomUUID(), + catch: () => new ExampleError() +}) + +export const uuidInTryPromise = Effect.tryPromise(async () => crypto.randomUUID()) + +export const uuidInTryPromiseObject = Effect.tryPromise({ + try: async () => crypto.randomUUID(), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => crypto.randomUUID()) diff --git a/packages/harness-effect-v4/examples/diagnostics/globalConsoleInEffect_thunks.ts b/packages/harness-effect-v4/examples/diagnostics/globalConsoleInEffect_thunks.ts new file mode 100644 index 00000000..f523fe72 --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/globalConsoleInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics globalConsoleInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const consoleInSync = Effect.sync(() => console.log("sync")) + +export const consoleInTryObject = Effect.try({ + try: () => console.warn("try"), + catch: () => new ExampleError() +}) + +export const consoleInTryPromise = Effect.tryPromise(async () => console.log("try-promise")) + +export const consoleInTryPromiseObject = Effect.tryPromise({ + try: async () => console.warn("try-promise-object"), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => console.log("nested")) diff --git a/packages/harness-effect-v4/examples/diagnostics/globalDateInEffect_thunks.ts b/packages/harness-effect-v4/examples/diagnostics/globalDateInEffect_thunks.ts new file mode 100644 index 00000000..3557bd20 --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/globalDateInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics globalDateInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const dateInSync = Effect.sync(() => Date.now()) + +export const newDateInTryObject = Effect.try({ + try: () => new Date(), + catch: () => new ExampleError() +}) + +export const dateInTryPromise = Effect.tryPromise(async () => Date.now()) + +export const dateInTryPromiseObject = Effect.tryPromise({ + try: async () => new Date(), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => new Date()) diff --git a/packages/harness-effect-v4/examples/diagnostics/globalFetchInEffect_thunks.ts b/packages/harness-effect-v4/examples/diagnostics/globalFetchInEffect_thunks.ts new file mode 100644 index 00000000..f2a713f4 --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/globalFetchInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics globalFetchInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const fetchInSync = Effect.sync(() => fetch("https://example.com/sync")) + +export const fetchInTryObject = Effect.try({ + try: () => fetch("https://example.com/try"), + catch: () => new ExampleError() +}) + +export const fetchInTryPromise = Effect.tryPromise(async () => fetch("https://example.com/try-promise")) + +export const fetchInTryPromiseObject = Effect.tryPromise({ + try: async () => fetch("https://example.com/try-promise-object"), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => fetch("https://example.com/nested")) diff --git a/packages/harness-effect-v4/examples/diagnostics/globalRandomInEffect_thunks.ts b/packages/harness-effect-v4/examples/diagnostics/globalRandomInEffect_thunks.ts new file mode 100644 index 00000000..72b52b83 --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/globalRandomInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics globalRandomInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const randomInSync = Effect.sync(() => Math.random()) + +export const randomInTryObject = Effect.try({ + try: () => Math.random(), + catch: () => new ExampleError() +}) + +export const randomInTryPromise = Effect.tryPromise(async () => Math.random()) + +export const randomInTryPromiseObject = Effect.tryPromise({ + try: async () => Math.random(), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => Math.random()) diff --git a/packages/harness-effect-v4/examples/diagnostics/globalTimersInEffect_thunks.ts b/packages/harness-effect-v4/examples/diagnostics/globalTimersInEffect_thunks.ts new file mode 100644 index 00000000..555dc1db --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/globalTimersInEffect_thunks.ts @@ -0,0 +1,20 @@ +// @effect-diagnostics globalTimersInEffect:warning +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const timersInSync = Effect.sync(() => setTimeout(() => {}, 10)) + +export const timersInTryObject = Effect.try({ + try: () => setInterval(() => {}, 10), + catch: () => new ExampleError() +}) + +export const timersInTryPromise = Effect.tryPromise(async () => setTimeout(() => {}, 10)) + +export const timersInTryPromiseObject = Effect.tryPromise({ + try: async () => setTimeout(() => {}, 10), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => setTimeout(() => {}, 10)) diff --git a/packages/harness-effect-v4/examples/diagnostics/processEnvInEffect_thunks.ts b/packages/harness-effect-v4/examples/diagnostics/processEnvInEffect_thunks.ts new file mode 100644 index 00000000..a59cdecc --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/processEnvInEffect_thunks.ts @@ -0,0 +1,21 @@ +// @effect-diagnostics processEnvInEffect:warning +/// +import { Data, Effect } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +export const envInSync = Effect.sync(() => process.env.SYNC_SECRET) + +export const envInTryObject = Effect.try({ + try: () => process.env.TRY_SECRET, + catch: () => new ExampleError() +}) + +export const envInTryPromise = Effect.tryPromise(async () => process.env.TRY_PROMISE_SECRET) + +export const envInTryPromiseObject = Effect.tryPromise({ + try: async () => process.env.TRY_PROMISE_OBJECT_SECRET, + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync(() => () => process.env.NESTED_SECRET) diff --git a/packages/harness-effect-v4/examples/diagnostics/schemaSyncInEffect_thunks.ts b/packages/harness-effect-v4/examples/diagnostics/schemaSyncInEffect_thunks.ts new file mode 100644 index 00000000..bfcbc664 --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/schemaSyncInEffect_thunks.ts @@ -0,0 +1,26 @@ +import { Data, Effect, Schema } from "effect" + +class ExampleError extends Data.TaggedError("ExampleError")<{}> {} + +const Person = Schema.Struct({ + name: Schema.String, + age: Schema.Number +}) + +export const decodeInSync = Effect.sync(() => Schema.decodeSync(Person)({ name: "John", age: 30 })) + +export const decodeInTryObject = Effect.try({ + try: () => Schema.decodeSync(Person)({ name: "Jane", age: 25 }), + catch: () => new ExampleError() +}) + +export const encodeInTryPromise = Effect.tryPromise(async () => Schema.encodeSync(Person)({ name: "Bob", age: 40 })) + +export const encodeInTryPromiseObject = Effect.tryPromise({ + try: async () => Schema.encodeUnknownSync(Person)({ name: "Carol", age: 50 }), + catch: () => new ExampleError() +}) + +export const shouldNotTriggerReturnedFunction = Effect.sync( + () => () => Schema.decodeSync(Person)({ name: "Nested", age: 10 }) +) diff --git a/packages/language-service/src/core/TypeParser.ts b/packages/language-service/src/core/TypeParser.ts index 50dee22e..c9e0cf13 100644 --- a/packages/language-service/src/core/TypeParser.ts +++ b/packages/language-service/src/core/TypeParser.ts @@ -55,6 +55,15 @@ export interface ParsedPipingFlowTransformation { kind: "pipe" | "pipeable" | "call" | "effectFn" | "effectFnUntraced" // the kind of transformation } +export const enum EffectContextFlags { + None = 0, + CanYieldEffect = 1 << 0, + InEffectConstructorThunk = 1 << 1, + InEffect = CanYieldEffect | InEffectConstructorThunk, + PendingNextFunctionIsEffectThunk = 1 << 2, + PendingNextObjectTryPropertyIsEffectThunk = 1 << 3 +} + export interface TypeParser { effectType: ( type: ts.Type, @@ -154,22 +163,12 @@ export interface TypeParser { }, TypeParserIssue > - findEnclosingScopes: ( + getEffectContextFlags: ( node: ts.Node - ) => Nano.Nano< - { - inEffect: boolean - scopeNode: ts.FunctionLikeDeclaration | undefined - effectGen: { - node: ts.Node - effectModule: ts.Node | ts.Expression - generatorFunction: ts.FunctionExpression - body: ts.Block - pipeArguments?: ReadonlyArray - } | undefined - }, - never - > + ) => Nano.Nano + getEffectYieldGeneratorFunction: ( + node: ts.Node + ) => Nano.Nano effectFn: ( node: ts.Node ) => Nano.Nano< @@ -1256,95 +1255,240 @@ export function make( (node) => node ) - const findEnclosingScopes = Nano.fn("TypeParser.findEnclosingScopes")(function*( - startNode: ts.Node - ) { - let currentParent: ts.Node | undefined = startNode.parent - let scopeNode: ts.FunctionLikeDeclaration | undefined = undefined - let effectGenResult: { - node: ts.Node - effectModule: ts.Node | ts.Expression - generatorFunction: ts.FunctionExpression - body: ts.Block - pipeArguments?: ReadonlyArray - } | undefined = undefined + type EffectContextAnalysis = { + flags: WeakMap + generatorFunctions: WeakMap + } + + const effectContextAnalysis = Nano.cachedBy( + Nano.fn("TypeParser.effectContextAnalysis")(function*(sourceFile: ts.SourceFile) { + const flags = new WeakMap() + const generatorFunctions = new WeakMap() + + const isFunctionLikeNode = (node: ts.Node): node is ts.FunctionLikeDeclaration => + ts.isFunctionExpression(node) || + ts.isFunctionDeclaration(node) || + ts.isMethodDeclaration(node) || + ts.isArrowFunction(node) || + ts.isGetAccessorDeclaration(node) || + ts.isSetAccessorDeclaration(node) + + const isTransparentPendingNode = (node: ts.Node): node is ts.Expression => + ts.isParenthesizedExpression(node) || + ts.isSatisfiesExpression(node) || + ts.isAsExpression(node) || + ts.isNonNullExpression(node) || + ts.isTypeAssertionExpression(node) + + const transparentPendingExpression = (node: ts.Expression): ts.Expression | undefined => { + if (ts.isParenthesizedExpression(node)) return node.expression + if (ts.isSatisfiesExpression(node)) return node.expression + if (ts.isAsExpression(node)) return node.expression + if (ts.isNonNullExpression(node)) return node.expression + if (ts.isTypeAssertionExpression(node)) return node.expression + return undefined + } + + const pendingEnableFlags = new WeakMap() + const pendingDisableFlags = new WeakMap() + const workQueue: Array = [sourceFile] + + const pendingFlagsMask = EffectContextFlags.PendingNextFunctionIsEffectThunk | + EffectContextFlags.PendingNextObjectTryPropertyIsEffectThunk + + const functionScopeResetFlags = EffectContextFlags.CanYieldEffect | + EffectContextFlags.InEffectConstructorThunk | + pendingFlagsMask + + const setPendingEnableFlags = (node: ts.Node, nextFlags: EffectContextFlags) => { + pendingEnableFlags.set(node, (pendingEnableFlags.get(node) ?? EffectContextFlags.None) | nextFlags) + } + + const setPendingDisableFlags = (node: ts.Node, nextFlags: EffectContextFlags) => { + pendingDisableFlags.set(node, (pendingDisableFlags.get(node) ?? EffectContextFlags.None) | nextFlags) + } + + while (workQueue.length > 0) { + const current = workQueue.pop()! + let currentFlags = current.parent + ? (flags.get(current.parent) ?? EffectContextFlags.None) + : EffectContextFlags.None + const currentGeneratorFunction = generatorFunctions.has(current) + ? generatorFunctions.get(current) + : current.parent + ? generatorFunctions.get(current.parent) + : undefined + + if (pendingDisableFlags.has(current)) { + currentFlags &= ~pendingDisableFlags.get(current)! + } + + if (pendingEnableFlags.has(current)) { + currentFlags |= pendingEnableFlags.get(current)! + } - while (currentParent) { - const nodeToCheck: ts.Node = currentParent + flags.set(current, currentFlags) + generatorFunctions.set( + current, + (currentFlags & EffectContextFlags.CanYieldEffect) !== 0 ? currentGeneratorFunction : undefined + ) - // Check if this node introduces a function scope - if (!scopeNode) { if ( - ts.isFunctionExpression(nodeToCheck) || - ts.isFunctionDeclaration(nodeToCheck) || - ts.isMethodDeclaration(nodeToCheck) || - ts.isArrowFunction(nodeToCheck) || - ts.isGetAccessorDeclaration(nodeToCheck) || - ts.isSetAccessorDeclaration(nodeToCheck) + (currentFlags & EffectContextFlags.PendingNextFunctionIsEffectThunk) !== 0 && + (ts.isArrowFunction(current) || ts.isFunctionExpression(current)) ) { - scopeNode = nodeToCheck + setPendingEnableFlags(current.body, EffectContextFlags.InEffectConstructorThunk) + setPendingDisableFlags(current.body, pendingFlagsMask) + } else if ( + (currentFlags & EffectContextFlags.PendingNextObjectTryPropertyIsEffectThunk) !== 0 && + ts.isObjectLiteralExpression(current) + ) { + ts.forEachChild(current, (child) => { + setPendingDisableFlags(child, pendingFlagsMask) + return undefined + }) + + for (const property of current.properties) { + if (!ts.isPropertyAssignment(property)) continue + if (!ts.isIdentifier(property.name) || ts.idText(property.name) !== "try") continue + setPendingEnableFlags(property.initializer, EffectContextFlags.PendingNextFunctionIsEffectThunk) + } + } else if (isTransparentPendingNode(current)) { + const expression = transparentPendingExpression(current) + if (expression) { + ts.forEachChild(current, (child) => { + if (child !== expression) { + setPendingDisableFlags(child, pendingFlagsMask) + } + return undefined + }) + setPendingEnableFlags(expression, currentFlags & pendingFlagsMask) + } + } else if ((currentFlags & pendingFlagsMask) !== 0) { + ts.forEachChild(current, (child) => { + setPendingDisableFlags(child, pendingFlagsMask) + return undefined + }) } - } - // Try to parse as Effect.gen, Effect.fnUntraced, or Effect.fn - if (!effectGenResult) { - const isEffectGen = yield* pipe( - effectGen(nodeToCheck), + const parsedEffectGenerator = yield* pipe( + effectGen(current), Nano.map((result) => ({ - node: result.node, - effectModule: result.effectModule, - generatorFunction: result.generatorFunction, - body: result.body + body: result.body, + generatorFunction: result.generatorFunction })), Nano.orElse(() => pipe( - effectFnUntracedGen(nodeToCheck), + effectFnUntracedGen(current), Nano.map((result) => ({ - node: result.node, - effectModule: result.effectModule, - generatorFunction: result.generatorFunction, body: result.body, - pipeArguments: result.pipeArguments + generatorFunction: result.generatorFunction })) ) ), Nano.orElse(() => pipe( - effectFnGen(nodeToCheck), + effectFnGen(current), Nano.map((result) => ({ - node: result.node, - effectModule: result.effectModule, - generatorFunction: result.generatorFunction, body: result.body, - pipeArguments: result.pipeArguments + generatorFunction: result.generatorFunction })) ) ), Nano.orUndefined ) - if (isEffectGen) { - effectGenResult = isEffectGen + if (parsedEffectGenerator) { + setPendingEnableFlags( + parsedEffectGenerator.body, + EffectContextFlags.CanYieldEffect + ) + generatorFunctions.set(parsedEffectGenerator.body, parsedEffectGenerator.generatorFunction) } - } - // If we found both, we can stop - if (scopeNode && effectGenResult) { - break + if (ts.isCallExpression(current) && current.arguments.length > 0) { + const effectThunkArgument = current.arguments[0] + + if (yield* Nano.orUndefined(isNodeReferenceToEffectModuleApi("sync")(current.expression))) { + setPendingEnableFlags(effectThunkArgument, EffectContextFlags.PendingNextFunctionIsEffectThunk) + } + + if (yield* Nano.orUndefined(isNodeReferenceToEffectModuleApi("promise")(current.expression))) { + setPendingEnableFlags(effectThunkArgument, EffectContextFlags.PendingNextFunctionIsEffectThunk) + } + + if (yield* Nano.orUndefined(isNodeReferenceToEffectModuleApi("callback")(current.expression))) { + setPendingEnableFlags(effectThunkArgument, EffectContextFlags.PendingNextFunctionIsEffectThunk) + } + + if (yield* Nano.orUndefined(isNodeReferenceToEffectModuleApi("suspend")(current.expression))) { + setPendingEnableFlags(effectThunkArgument, EffectContextFlags.PendingNextFunctionIsEffectThunk) + } + + if (yield* Nano.orUndefined(isNodeReferenceToEffectModuleApi("try")(current.expression))) { + setPendingEnableFlags( + effectThunkArgument, + EffectContextFlags.PendingNextFunctionIsEffectThunk | + EffectContextFlags.PendingNextObjectTryPropertyIsEffectThunk + ) + } + + if (yield* Nano.orUndefined(isNodeReferenceToEffectModuleApi("tryPromise")(current.expression))) { + setPendingEnableFlags( + effectThunkArgument, + EffectContextFlags.PendingNextFunctionIsEffectThunk | + EffectContextFlags.PendingNextObjectTryPropertyIsEffectThunk + ) + } + } + + if (isFunctionLikeNode(current)) { + ts.forEachChild(current, (child) => { + setPendingDisableFlags(child, functionScopeResetFlags) + return undefined + }) + } + + ts.forEachChild(current, (child) => { + workQueue.push(child) + return undefined + }) } - currentParent = nodeToCheck.parent - } + return { + flags, + generatorFunctions + } satisfies EffectContextAnalysis + }), + "TypeParser.effectContextAnalysis", + (sourceFile) => sourceFile + ) - return { - inEffect: effectGenResult !== undefined && - effectGenResult.body.statements.length > 0 && - scopeNode === effectGenResult.generatorFunction, - scopeNode, - effectGen: effectGenResult + const getEffectContextFlags = Nano.fn("TypeParser.getEffectContextFlags")(function*(node: ts.Node) { + const sourceFile = tsUtils.getSourceFileOfNode(node) + + if (!sourceFile) { + return EffectContextFlags.None } + + const analysis = yield* effectContextAnalysis(sourceFile) + return (analysis.flags.get(node) ?? EffectContextFlags.None) & + EffectContextFlags.InEffect }) + const getEffectYieldGeneratorFunction = Nano.fn("TypeParser.getEffectYieldGeneratorFunction")( + function*(node: ts.Node) { + const sourceFile = tsUtils.getSourceFileOfNode(node) + + if (!sourceFile) { + return undefined + } + + const analysis = yield* effectContextAnalysis(sourceFile) + return analysis.generatorFunctions.get(node) + } + ) + const effectFn = Nano.cachedBy( function(node: ts.Node) { // Effect.fn("name")(regularFunction, ...pipeArgs) or Effect.fn(regularFunction, ...pipeArgs) @@ -3192,7 +3336,8 @@ export function make( effectGen, effectFnUntracedGen, effectFnGen, - findEnclosingScopes, + getEffectContextFlags, + getEffectYieldGeneratorFunction, effectFn, extendsCauseYieldableError, unnecessaryEffectGen, diff --git a/packages/language-service/src/diagnostics/cryptoRandomUUIDInEffect.ts b/packages/language-service/src/diagnostics/cryptoRandomUUIDInEffect.ts index c3b027a8..bace6158 100644 --- a/packages/language-service/src/diagnostics/cryptoRandomUUIDInEffect.ts +++ b/packages/language-service/src/diagnostics/cryptoRandomUUIDInEffect.ts @@ -37,7 +37,7 @@ export const makeCryptoRandomUUIDApply = (checkInEffect: boolean) => if (!symbol) continue if (typeCheckerUtils.resolveToGlobalSymbol(symbol) !== cryptoSymbol) continue - const { inEffect } = yield* typeParser.findEnclosingScopes(node) + const inEffect = ((yield* typeParser.getEffectContextFlags(node)) & TypeParser.EffectContextFlags.InEffect) !== 0 if (inEffect !== checkInEffect) continue report({ diff --git a/packages/language-service/src/diagnostics/globalConsoleInEffect.ts b/packages/language-service/src/diagnostics/globalConsoleInEffect.ts index bee1d63f..dead6b01 100644 --- a/packages/language-service/src/diagnostics/globalConsoleInEffect.ts +++ b/packages/language-service/src/diagnostics/globalConsoleInEffect.ts @@ -49,7 +49,7 @@ export const makeGlobalConsoleApply = (checkInEffect: boolean) => if (!symbol) continue if (typeCheckerUtils.resolveToGlobalSymbol(symbol) !== consoleSymbol) continue - const { inEffect } = yield* typeParser.findEnclosingScopes(node) + const inEffect = ((yield* typeParser.getEffectContextFlags(node)) & TypeParser.EffectContextFlags.InEffect) !== 0 if (inEffect !== checkInEffect) continue report({ diff --git a/packages/language-service/src/diagnostics/globalDateInEffect.ts b/packages/language-service/src/diagnostics/globalDateInEffect.ts index abfc745b..5aedd5b9 100644 --- a/packages/language-service/src/diagnostics/globalDateInEffect.ts +++ b/packages/language-service/src/diagnostics/globalDateInEffect.ts @@ -51,7 +51,7 @@ export const makeGlobalDateApply = (checkInEffect: boolean) => if (!symbol) continue if (typeCheckerUtils.resolveToGlobalSymbol(symbol) !== dateSymbol) continue - const { inEffect } = yield* typeParser.findEnclosingScopes(node) + const inEffect = ((yield* typeParser.getEffectContextFlags(node)) & TypeParser.EffectContextFlags.InEffect) !== 0 if (inEffect !== checkInEffect) continue report({ location: node, messageText, fixes: [] }) diff --git a/packages/language-service/src/diagnostics/globalFetchInEffect.ts b/packages/language-service/src/diagnostics/globalFetchInEffect.ts index 884a8514..0988cdef 100644 --- a/packages/language-service/src/diagnostics/globalFetchInEffect.ts +++ b/packages/language-service/src/diagnostics/globalFetchInEffect.ts @@ -35,7 +35,8 @@ export const makeGlobalFetchApply = (checkInEffect: boolean) => if (ts.isCallExpression(node)) { const symbol = typeChecker.getSymbolAtLocation(node.expression) if (symbol && typeCheckerUtils.resolveToGlobalSymbol(symbol) === fetchSymbol) { - const { inEffect } = yield* typeParser.findEnclosingScopes(node) + const inEffect = + ((yield* typeParser.getEffectContextFlags(node)) & TypeParser.EffectContextFlags.InEffect) !== 0 if (inEffect === checkInEffect) { report({ location: node.expression, diff --git a/packages/language-service/src/diagnostics/globalRandomInEffect.ts b/packages/language-service/src/diagnostics/globalRandomInEffect.ts index 6534b676..c6e361de 100644 --- a/packages/language-service/src/diagnostics/globalRandomInEffect.ts +++ b/packages/language-service/src/diagnostics/globalRandomInEffect.ts @@ -37,7 +37,7 @@ export const makeGlobalRandomApply = (checkInEffect: boolean) => if (!symbol) continue if (typeCheckerUtils.resolveToGlobalSymbol(symbol) !== mathSymbol) continue - const { inEffect } = yield* typeParser.findEnclosingScopes(node) + const inEffect = ((yield* typeParser.getEffectContextFlags(node)) & TypeParser.EffectContextFlags.InEffect) !== 0 if (inEffect !== checkInEffect) continue report({ diff --git a/packages/language-service/src/diagnostics/globalTimersInEffect.ts b/packages/language-service/src/diagnostics/globalTimersInEffect.ts index 9d365b0f..28be1462 100644 --- a/packages/language-service/src/diagnostics/globalTimersInEffect.ts +++ b/packages/language-service/src/diagnostics/globalTimersInEffect.ts @@ -61,7 +61,7 @@ export const makeGlobalTimersApply = (checkInEffect: boolean) => } if (!messageText) continue - const { inEffect } = yield* typeParser.findEnclosingScopes(node) + const inEffect = ((yield* typeParser.getEffectContextFlags(node)) & TypeParser.EffectContextFlags.InEffect) !== 0 if (inEffect !== checkInEffect) continue report({ location: node, messageText, fixes: [] }) diff --git a/packages/language-service/src/diagnostics/missingReturnYieldStar.ts b/packages/language-service/src/diagnostics/missingReturnYieldStar.ts index 7ca85b4d..b3ee2d53 100644 --- a/packages/language-service/src/diagnostics/missingReturnYieldStar.ts +++ b/packages/language-service/src/diagnostics/missingReturnYieldStar.ts @@ -44,7 +44,8 @@ export const missingReturnYieldStar = LSP.createDiagnostic({ if (!(Option.isSome(maybeEffect) && maybeEffect.value.A.flags & ts.TypeFlags.Never)) continue // Ensure we're in the direct body scope of an Effect.gen-like function. - const { inEffect } = yield* typeParser.findEnclosingScopes(node) + const inEffect = + ((yield* typeParser.getEffectContextFlags(node)) & TypeParser.EffectContextFlags.CanYieldEffect) !== 0 if (!inEffect) continue const fix = [{ diff --git a/packages/language-service/src/diagnostics/nestedEffectGenYield.ts b/packages/language-service/src/diagnostics/nestedEffectGenYield.ts index 3d1ab19b..aba508c1 100644 --- a/packages/language-service/src/diagnostics/nestedEffectGenYield.ts +++ b/packages/language-service/src/diagnostics/nestedEffectGenYield.ts @@ -29,7 +29,8 @@ export const nestedEffectGenYield = LSP.createDiagnostic({ if (!ts.isYieldExpression(node) || !node.asteriskToken || !node.expression) continue - const { inEffect } = yield* typeParser.findEnclosingScopes(node) + const inEffect = + ((yield* typeParser.getEffectContextFlags(node)) & TypeParser.EffectContextFlags.CanYieldEffect) !== 0 if (!inEffect) continue const bareNestedEffectGen = yield* Nano.orUndefined(typeParser.effectGen(node.expression)) diff --git a/packages/language-service/src/diagnostics/preferSchemaOverJson.ts b/packages/language-service/src/diagnostics/preferSchemaOverJson.ts index 2fc9df70..88ac3a05 100644 --- a/packages/language-service/src/diagnostics/preferSchemaOverJson.ts +++ b/packages/language-service/src/diagnostics/preferSchemaOverJson.ts @@ -135,7 +135,8 @@ export const preferSchemaOverJson = LSP.createDiagnostic({ // Match direct JSON.parse/stringify inside Effect generator const jsonMethodInEffectGen = Nano.fn("preferSchemaOverJson.jsonMethodInEffectGen")( function*(jsonCall: ts.CallExpression) { - const { inEffect } = yield* typeParser.findEnclosingScopes(jsonCall) + const inEffect = + ((yield* typeParser.getEffectContextFlags(jsonCall)) & TypeParser.EffectContextFlags.InEffect) !== 0 if (!inEffect) { return yield* TypeParser.TypeParserIssue.issue } diff --git a/packages/language-service/src/diagnostics/processEnvInEffect.ts b/packages/language-service/src/diagnostics/processEnvInEffect.ts index 963086b4..3aa1311f 100644 --- a/packages/language-service/src/diagnostics/processEnvInEffect.ts +++ b/packages/language-service/src/diagnostics/processEnvInEffect.ts @@ -46,7 +46,7 @@ export const makeProcessEnvApply = (checkInEffect: boolean) => if (!symbol) continue if (typeCheckerUtils.resolveToGlobalSymbol(symbol) !== processSymbol) continue - const { inEffect } = yield* typeParser.findEnclosingScopes(node) + const inEffect = ((yield* typeParser.getEffectContextFlags(node)) & TypeParser.EffectContextFlags.InEffect) !== 0 if (inEffect !== checkInEffect) continue report({ diff --git a/packages/language-service/src/diagnostics/runEffectInsideEffect.ts b/packages/language-service/src/diagnostics/runEffectInsideEffect.ts index fb56a512..5a20c4e9 100644 --- a/packages/language-service/src/diagnostics/runEffectInsideEffect.ts +++ b/packages/language-service/src/diagnostics/runEffectInsideEffect.ts @@ -21,6 +21,22 @@ export const runEffectInsideEffect = LSP.createDiagnostic({ const tsUtils = yield* Nano.service(TypeScriptUtils.TypeScriptUtils) const supportedEffect = typeParser.supportedEffect() + const getContainingFunctionLike = (node: ts.Node): ts.FunctionLikeDeclaration | undefined => { + for (let current = node.parent; current; current = current.parent) { + if ( + ts.isFunctionExpression(current) || + ts.isFunctionDeclaration(current) || + ts.isMethodDeclaration(current) || + ts.isArrowFunction(current) || + ts.isGetAccessorDeclaration(current) || + ts.isSetAccessorDeclaration(current) + ) { + return current + } + } + return undefined + } + const parseEffectMethod = (node: ts.Node, methodName: string) => pipe( typeParser.isNodeReferenceToEffectModuleApi(methodName)(node), @@ -53,8 +69,66 @@ export const runEffectInsideEffect = LSP.createDiagnostic({ if (Option.isNone(isEffectRunCall)) continue - // Find enclosing scope and Effect generator using TypeParser helper - const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node) + const scopeNode = getContainingFunctionLike(node) + let generatorFunction = yield* typeParser.getEffectYieldGeneratorFunction(node) + + if (!generatorFunction) { + for (let current = node.parent; current; current = current.parent) { + const currentFlags = yield* typeParser.getEffectContextFlags(current) + if ((currentFlags & TypeParser.EffectContextFlags.CanYieldEffect) === 0) continue + generatorFunction = yield* typeParser.getEffectYieldGeneratorFunction(current) + if (generatorFunction) break + } + } + + let effectGen: { + node: ts.Node + effectModule: ts.Node | ts.Expression + generatorFunction: ts.FunctionExpression + body: ts.Block + pipeArguments?: ReadonlyArray + } | undefined = undefined + + if (generatorFunction) { + for (let current = generatorFunction.parent; current; current = current.parent) { + effectGen = yield* pipe( + typeParser.effectGen(current), + Nano.map((result) => ({ + node: result.node, + effectModule: result.effectModule, + generatorFunction: result.generatorFunction, + body: result.body + })), + Nano.orElse(() => + pipe( + typeParser.effectFnUntracedGen(current), + Nano.map((result) => ({ + node: result.node, + effectModule: result.effectModule, + generatorFunction: result.generatorFunction, + body: result.body, + pipeArguments: result.pipeArguments + })) + ) + ), + Nano.orElse(() => + pipe( + typeParser.effectFnGen(current), + Nano.map((result) => ({ + node: result.node, + effectModule: result.effectModule, + generatorFunction: result.generatorFunction, + body: result.body, + pipeArguments: result.pipeArguments + })) + ) + ), + Nano.orUndefined + ) + + if (effectGen) break + } + } if (effectGen && effectGen.body.statements.length > 0) { const nodeText = sourceFile.text.substring( diff --git a/packages/language-service/src/diagnostics/schemaSyncInEffect.ts b/packages/language-service/src/diagnostics/schemaSyncInEffect.ts index 61a6b031..de984f29 100644 --- a/packages/language-service/src/diagnostics/schemaSyncInEffect.ts +++ b/packages/language-service/src/diagnostics/schemaSyncInEffect.ts @@ -64,7 +64,7 @@ export const schemaSyncInEffect = LSP.createDiagnostic({ if (Option.isNone(isSchemaSyncCall)) continue // Find enclosing scope and Effect generator using TypeParser helper - const { inEffect } = yield* typeParser.findEnclosingScopes(node) + const inEffect = ((yield* typeParser.getEffectContextFlags(node)) & TypeParser.EffectContextFlags.InEffect) !== 0 if (!inEffect) continue diff --git a/packages/language-service/src/metadata.json b/packages/language-service/src/metadata.json index a5b27603..f0cc6583 100644 --- a/packages/language-service/src/metadata.json +++ b/packages/language-service/src/metadata.json @@ -978,7 +978,13 @@ ], "preview": { "sourceText": "import { Effect } from \"effect\"\n\nexport const preview = Effect.gen(function*() {\n return yield* Effect.promise(() => fetch(\"https://example.com\"))\n})\n", - "diagnostics": [] + "diagnostics": [ + { + "start": 118, + "end": 123, + "text": "This Effect code calls the global `fetch` function, HTTP requests in Effect code are represented through `HttpClient` from `effect/unstable/http`. effect(globalFetchInEffect)" + } + ] } }, {