-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve TypeScript diagnostics for Effect (#56)
- Loading branch information
1 parent
19e5a77
commit 5b2b27c
Showing
27 changed files
with
1,125 additions
and
228 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
--- | ||
"@effect/language-service": minor | ||
--- | ||
|
||
Add support for Effect diagnostics | ||
|
||
With this release of the language service plugin, we aim to improve the overall Effect experience by providing additional diagnostics that tries to fix misleading or hard to read TypeScript errors. | ||
|
||
All of the diagnostics provided by the language service are available only in editor-mode, that means that they won't show up when using tsc. | ||
|
||
Diagnostics are enabled by default, but you can opt-out of them by changing the language service configuration and provide diagnostics: false. | ||
|
||
```json | ||
{ | ||
"plugins": [ | ||
{ | ||
"name": "@effect/language-service", | ||
"diagnostics": false | ||
} | ||
] | ||
} | ||
``` | ||
|
||
Please report any false positive or missing diagnostic you encounter over the Github repository. | ||
|
||
## Missing Errors and Services in Effects | ||
|
||
Additionally to the standard TypeScript error that may be cryptic at first: | ||
|
||
``` | ||
Argument of type 'Effect<number, never, ServiceB | ServiceA | ServiceC>' is not assignable to parameter of type 'Effect<number, never, ServiceB | ServiceA>' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. | ||
Type 'ServiceB | ServiceA | ServiceC' is not assignable to type 'ServiceB | ServiceA'. | ||
Type 'ServiceC' is not assignable to type 'ServiceB | ServiceA'. | ||
Type 'ServiceC' is not assignable to type 'ServiceA'. | ||
Types of property 'a' are incompatible. | ||
Type '3' is not assignable to type '1'.ts(2379) | ||
``` | ||
|
||
you'll now receive an additional error: | ||
|
||
``` | ||
Missing 'ServiceC' in the expected Effect context. | ||
``` | ||
|
||
## Floating Effect | ||
|
||
In some situation you may not receive any compile error at all, but that's because you may have forgot to yield your effects inside gen! | ||
|
||
Floating Effects that are not assigned to a variable will be reported into the Effect diagnostics. | ||
|
||
```ts | ||
Effect.runPromise( | ||
Effect.gen(function* () { | ||
Effect.sync(() => console.log("Hello!")); | ||
// ^- Effect must be yielded or assigned to a variable. | ||
}) | ||
); | ||
``` | ||
|
||
## Used yield instead of yield\* | ||
|
||
Similarly, yield instead of yield\* won't result in a type error by itself, but is not the intended usage. | ||
|
||
This yield will be reported in the effect diagnostics. | ||
|
||
```ts | ||
Effect.runPromise( | ||
Effect.gen(function* () { | ||
yield Effect.sync(() => console.log("Hello!")); | ||
// ^- When yielding Effects inside Effect.gen, you should use yield* instead of yield. | ||
}) | ||
); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"name": "Attach to TS Server", | ||
"type": "node", | ||
"request": "attach", | ||
"protocol": "inspector", | ||
"port": 5667, | ||
"sourceMaps": true, | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import * as Effect from "effect/Effect" | ||
|
||
const noError = Effect.succeed(1) | ||
|
||
Effect.succeed("floating") | ||
|
||
Effect.never | ||
|
||
Effect.runPromise(Effect.gen(function*(){ | ||
const thisIsFine = Effect.succeed(1) | ||
Effect.never | ||
})) |
30 changes: 30 additions & 0 deletions
30
examples/diagnostics/missingEffectContext_callExpression.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import * as Effect from "effect/Effect" | ||
|
||
class ServiceA extends Effect.Service<ServiceB>()("ServiceA", { | ||
succeed: { a: 1} | ||
}){} | ||
|
||
class ServiceB extends Effect.Service<ServiceB>()("ServiceB", { | ||
succeed: { a: 2} | ||
}){} | ||
|
||
class ServiceC extends Effect.Service<ServiceB>()("ServiceC", { | ||
succeed: { a: 3} | ||
}){} | ||
|
||
declare const effectWithServices: Effect.Effect<number, never, ServiceA | ServiceB | ServiceC > | ||
|
||
function testFn(effect: Effect.Effect<number>){ | ||
return effect | ||
} | ||
|
||
// @ts-expect-error | ||
testFn(effectWithServices) | ||
|
||
function testFnWithServiceAB(effect: Effect.Effect<number, never, ServiceA | ServiceB>){ | ||
return effect | ||
} | ||
|
||
// @ts-expect-error | ||
testFnWithServiceAB(effectWithServices) | ||
|
38 changes: 38 additions & 0 deletions
38
examples/diagnostics/missingEffectContext_plainAssignment.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import * as Context from "effect/Context" | ||
import * as Effect from "effect/Effect" | ||
|
||
class ServiceA extends Effect.Service<ServiceB>()("ServiceA", { | ||
succeed: { a: 1} | ||
}){} | ||
|
||
class ServiceB extends Effect.Service<ServiceB>()("ServiceB", { | ||
succeed: { a: 2} | ||
}){} | ||
|
||
class ServiceC extends Effect.Service<ServiceB>()("ServiceC", { | ||
succeed: { a: 3} | ||
}){} | ||
|
||
declare const effectWithServices: Effect.Effect<number, never, ServiceA | ServiceB | ServiceC > | ||
|
||
export const noError: Effect.Effect<number> = Effect.succeed(1) | ||
|
||
// @ts-expect-error | ||
export const missingAllServices: Effect.Effect<number> = effectWithServices | ||
|
||
// @ts-expect-error | ||
export const missingServiceC: Effect.Effect<number, never, ServiceA | ServiceB> = effectWithServices | ||
|
||
export interface EffectSubtyping<A> extends Effect.Effect<A, never, ServiceA | ServiceB> {} | ||
|
||
// @ts-expect-error | ||
export const missingServiceCWithSubtyping: EffectSubtyping<number> = effectWithServices | ||
|
||
export function missingServiceWithGenericType<A>(service: A){ | ||
// @ts-expect-error | ||
const missingServiceA: Effect.Effect<Context.Context<A>> = Effect.context<A>() | ||
return missingServiceA | ||
} | ||
|
||
// @ts-expect-error | ||
const _ = effectWithServices satisfies Effect.Effect<number, never, never> |
26 changes: 26 additions & 0 deletions
26
examples/diagnostics/missingEffectContext_returnSignature.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import * as Effect from "effect/Effect" | ||
|
||
class ServiceA extends Effect.Service<ServiceB>()("ServiceA", { | ||
succeed: { a: 1} | ||
}){} | ||
|
||
class ServiceB extends Effect.Service<ServiceB>()("ServiceB", { | ||
succeed: { a: 2} | ||
}){} | ||
|
||
class ServiceC extends Effect.Service<ServiceB>()("ServiceC", { | ||
succeed: { a: 3} | ||
}){} | ||
|
||
declare const effectWithServices: Effect.Effect<number, never, ServiceA | ServiceB | ServiceC > | ||
|
||
export function testFn(): Effect.Effect<number> { | ||
// @ts-expect-error | ||
return effectWithServices | ||
} | ||
|
||
// @ts-expect-error | ||
export const conciseBody: () => Effect.Effect<number> = () => effectWithServices | ||
|
||
// @ts-expect-error | ||
export const conciseBodyMissingServiceC: () => Effect.Effect<number, never, ServiceA | ServiceB> = () => effectWithServices |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import * as Effect from "effect/Effect" | ||
import * as Data from "effect/Data" | ||
|
||
class ErrorA extends Data.Error<{ | ||
a: 1 | ||
}>{} | ||
|
||
class ErrorB extends Data.Error<{ | ||
a: 2 | ||
}>{} | ||
|
||
class ErrorC extends Data.Error<{ | ||
a: 3 | ||
}>{} | ||
|
||
declare const effectWithErrors: Effect.Effect<number, ErrorA | ErrorB | ErrorC> | ||
|
||
function testFn(effect: Effect.Effect<number>){ | ||
return effect | ||
} | ||
|
||
// @ts-expect-error | ||
testFn(effectWithErrors) | ||
|
||
function testFnWithServiceAB(effect: Effect.Effect<number, ErrorA | ErrorB>){ | ||
return effect | ||
} | ||
|
||
// @ts-expect-error | ||
testFnWithServiceAB(effectWithErrors) |
38 changes: 38 additions & 0 deletions
38
examples/diagnostics/missingEffectError_plainAssignment.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import * as Effect from "effect/Effect" | ||
import * as Data from "effect/Data" | ||
|
||
class ErrorA extends Data.Error<{ | ||
a: 1 | ||
}>{} | ||
|
||
class ErrorB extends Data.Error<{ | ||
a: 2 | ||
}>{} | ||
|
||
class ErrorC extends Data.Error<{ | ||
a: 3 | ||
}>{} | ||
|
||
declare const effectWithErrors: Effect.Effect<number, ErrorA | ErrorB | ErrorC> | ||
|
||
export const noError: Effect.Effect<number> = Effect.succeed(1) | ||
|
||
// @ts-expect-error | ||
export const missingAllErrors: Effect.Effect<number> = effectWithErrors | ||
|
||
// @ts-expect-error | ||
export const missingErrorC: Effect.Effect<number, ErrorA | ErrorB> = effectWithErrors | ||
|
||
export interface EffectSubtyping<A> extends Effect.Effect<A, ErrorA | ErrorB> {} | ||
|
||
// @ts-expect-error | ||
export const missingErrorCWithSubtyping: EffectSubtyping<number> = effectWithErrors | ||
|
||
export function missingErrorWithGenericType<A>(error: A){ | ||
// @ts-expect-error | ||
const missingErrorA: Effect.Effect<never> = Effect.fail(error) | ||
return missingErrorA | ||
} | ||
|
||
// @ts-expect-error | ||
const _ = effectWithErrors satisfies Effect.Effect<number, never, never> |
27 changes: 27 additions & 0 deletions
27
examples/diagnostics/missingEffectError_returnSignature.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import * as Effect from "effect/Effect" | ||
import * as Data from "effect/Data" | ||
|
||
class ErrorA extends Data.Error<{ | ||
a: 1 | ||
}>{} | ||
|
||
class ErrorB extends Data.Error<{ | ||
a: 2 | ||
}>{} | ||
|
||
class ErrorC extends Data.Error<{ | ||
a: 3 | ||
}>{} | ||
|
||
declare const effectWithErrors: Effect.Effect<number, ErrorA | ErrorB | ErrorC> | ||
|
||
export function testFn(): Effect.Effect<number> { | ||
// @ts-expect-error | ||
return effectWithErrors | ||
} | ||
|
||
// @ts-expect-error | ||
export const conciseBody: () => Effect.Effect<number> = () => effectWithErrors | ||
|
||
// @ts-expect-error | ||
export const conciseBodyMissingServiceC: () => Effect.Effect<number, ErrorA | ErrorB> = () => effectWithErrors |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import * as Effect from "effect/Effect" | ||
|
||
const noError = Effect.gen(function*(){ | ||
yield* Effect.succeed(1) | ||
}) | ||
|
||
// @ts-expect-error | ||
const missingStarInYield = Effect.gen(function*(){ | ||
yield Effect.succeed(1) | ||
}) | ||
|
||
const missingStarInInnerYield = Effect.gen(function*(){ | ||
// @ts-expect-error | ||
yield* Effect.gen(function*(){ | ||
yield Effect.succeed(1) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/** | ||
* @since 1.0.0 | ||
*/ | ||
import { floatingEffect } from "./diagnostics/floatingEffect.js" | ||
import { missingEffectContext } from "./diagnostics/missingEffectContext.js" | ||
import { missingEffectError } from "./diagnostics/missingEffectError.js" | ||
import { missingStarInYieldEffectGen } from "./diagnostics/missingStarInYieldEffectGen.js" | ||
|
||
/** | ||
* @since 1.0.0 | ||
*/ | ||
export const diagnostics = { | ||
missingEffectContext, | ||
missingEffectError, | ||
floatingEffect, | ||
missingStarInYieldEffectGen | ||
} |
Oops, something went wrong.