Use this file to discover all available pages before exploring further.
Style diagnostics suggest code improvements that make Effect code more idiomatic, concise, and consistent. Most include a quick fix (🔧) so the improvement is one keystroke away. All are suggestions or off by default — they never block compilation.
Rules marked 💡 are suggestions. Rules marked ➖ are off by default and must be enabled in diagnosticSeverity.
catchAllToMapError — 💡 suggestion + 🔧 quick fix
V3/V4: ✓ / ✓Suggests using Effect.mapError instead of the pattern Effect.catchAll(e => Effect.fail(transform(e))). When you only need to transform the error type without recovering, mapError is the idiomatic choice.
import { Effect } from "effect"// ❌ Before — verbose catch + fail patternconst result = someEffect.pipe( Effect.catchAll((e) => Effect.fail(new WrappedError({ cause: e }))))// ✅ After (quick fix applied) — concise mapErrorconst result = someEffect.pipe( Effect.mapError((e) => new WrappedError({ cause: e })))
deterministicKeys — ➖ off by default + 🔧 quick fix
V3/V4: ✓ / ✓Enforces deterministic, file-path-based naming for service identifiers, tag identifiers, and error tag strings. Consistent key naming prevents accidental collisions when the same identifier is used in different files. Configure the naming formula with keyPatterns in the plugin options.
import { Effect } from "effect"// ❌ Violation (when enabled) — key does not follow the configured patternclass Database extends Effect.Service<Database>()("DB", { effect: Effect.succeed({ query: () => Effect.void })}) {}// ✅ After quick fix — key matches the deterministic formulaclass Database extends Effect.Service<Database>()("services/Database", { effect: Effect.succeed({ query: () => Effect.void })}) {}
V3/V4: ✓ / ✓Suggests wrapping functions that return an Effect with Effect.fn to get automatic tracing, span naming, and a cleaner call signature. Applies to function declarations and arrow functions at module scope.
import { Effect } from "effect"// ❌ Before — plain function returning Effectconst getUser = (id: string) => Effect.gen(function* () { const db = yield* Database return yield* db.findById(id) })// ✅ After (quick fix applied) — wrapped with Effect.fn for tracingconst getUser = Effect.fn("getUser")((id: string) => Effect.gen(function* () { const db = yield* Database return yield* db.findById(id) }))
effectMapVoid — 💡 suggestion + 🔧 quick fix
V3/V4: ✓ / ✓Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {}). Effect.asVoid is the idiomatic way to discard a success value.
import { Effect } from "effect"// ❌ Before — verbose map to voidconst result = someEffect.pipe( Effect.map(() => void 0))// ✅ After (quick fix applied)const result = someEffect.pipe( Effect.asVoid)
V3/V4: ✓ / ✓Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0). Effect.void is the canonical empty-success value.
import { Effect } from "effect"// ❌ Beforeconst noop = Effect.succeed(undefined)// ✅ After (quick fix applied)const noop = Effect.void
missedPipeableOpportunity — ➖ off by default + 🔧 quick fix
V3/V4: ✓ / ✓Suggests converting deeply nested function calls to .pipe() style for better readability. Triggers when at least pipeableMinArgCount (default: 2) contiguous pipeable transformations are nested.
import { Effect } from "effect"// ❌ Before — nested calls, hard to readconst result = Effect.mapError( Effect.map(someEffect, (x) => x + 1), (e) => new WrappedError({ cause: e }))// ✅ After (quick fix applied) — pipe styleconst result = someEffect.pipe( Effect.map((x) => x + 1), Effect.mapError((e) => new WrappedError({ cause: e })))
missingEffectServiceDependency — ➖ off by default (V3 only)
V3 only · V4: —Checks that an Effect.Service class’s declared dependencies array satisfies all required layer inputs. If a service’s implementation requires another service but that service is not listed in dependencies, the layer will be incomplete.
V3/V4: ✓ / ✓Suggests removing the redundant identifier argument from Schema.TaggedClass, Schema.TaggedError, and Schema.TaggedRequest when the identifier is the same as the class name. The class name is used by default, so passing it explicitly is noise.
import { Schema } from "effect"// ❌ Before — identifier duplicates the class nameclass UserNotFound extends Schema.TaggedError<UserNotFound>()("UserNotFound", { id: Schema.String}) {}// ✅ After (quick fix applied) — identifier removedclass UserNotFound extends Schema.TaggedError<UserNotFound>()({ id: Schema.String}) {}
schemaStructWithTag — 💡 suggestion + 🔧 quick fix
V3/V4: ✓ / ✓Suggests replacing Schema.Struct with a _tag field with Schema.TaggedStruct. Schema.TaggedStruct makes the _tag field optional in the constructor (it defaults to the tag value), reducing boilerplate.
import { Schema } from "effect"// ❌ Before — Schema.Struct with explicit _tagconst UserEvent = Schema.Struct({ _tag: Schema.Literal("UserEvent"), userId: Schema.String,})// ✅ After (quick fix applied) — Schema.TaggedStructconst UserEvent = Schema.TaggedStruct("UserEvent", { userId: Schema.String,})
schemaUnionOfLiterals — ➖ off by default + 🔧 quick fix (V3 only)
V3 only · V4: —Suggests combining multiple Schema.Literal calls inside a Schema.Union into a single Schema.Literal call. A single Schema.Literal with multiple arguments is equivalent and more concise.
import { Schema } from "effect"// ❌ Before — multiple Literal in Unionconst Status = Schema.Union( Schema.Literal("active"), Schema.Literal("inactive"), Schema.Literal("pending"))// ✅ After (quick fix applied) — single Literalconst Status = Schema.Literal("active", "inactive", "pending")
serviceNotAsClass — ➖ off by default + 🔧 quick fix (V4 only)
V4 only · V3: —Warns when ServiceMap.Service is used as a variable expression instead of a class declaration. The class declaration form is the idiomatic V4 pattern and enables better type inference, tooling support, and accessor generation.
V3/V4: ✓ / ✓Enforces that only strictly boolean values are used in conditional expressions. Disallows truthy/falsy coercion of strings, numbers, null, and undefined in conditions. Mirrors TypeScript’s strictNullChecks spirit for conditionals.
// ❌ Violation (when enabled) — number used in condition (truthy coercion)const count = getCount()if (count) { // count is number, not boolean processItems()}// ✅ Correct — explicit boolean comparisonconst count = getCount()if (count > 0) { processItems()}
V3/V4: ✓ / ✓Suggests removing the Effect.gen wrapper when the generator body contains only a single return statement. A single-expression generator adds indentation and function* syntax with no benefit.
import { Effect } from "effect"// ❌ Before — unnecessary Effect.gen for a single expressionconst result = Effect.gen(function* () { return yield* Effect.succeed(42)})// ✅ After (quick fix applied) — direct expressionconst result = Effect.succeed(42)
V3/V4: ✓ / ✓Suggests yielding yieldable error types (such as Data.TaggedError instances) directly with yield* instead of wrapping them in Effect.fail. Tagged errors implement the Yieldable interface, so they can be yielded directly for a more concise syntax.
import { Effect, Data } from "effect"class NotFoundError extends Data.TaggedError("NotFoundError")<{}> {}// ❌ Before — wrapping a yieldable error in Effect.failconst program = Effect.gen(function* () { return yield* Effect.fail(new NotFoundError())})// ✅ After (quick fix applied) — yield* the error directlyconst program = Effect.gen(function* () { return yield* new NotFoundError()})
unnecessaryPipe — 💡 suggestion + 🔧 quick fix
V3/V4: ✓ / ✓Removes pipe() calls that contain no arguments. An empty pipe() call is a no-op and should be removed.
import { pipe } from "effect"// ❌ Before — pipe with no argumentsconst result = pipe(someValue)// ✅ After (quick fix applied)const result = someValue
unnecessaryPipeChain — 💡 suggestion + 🔧 quick fix
V3/V4: ✓ / ✓Simplifies chained pipe calls into a single pipe call. When multiple pipe(...) calls are chained, they can always be flattened into one, improving readability.
import { pipe, Effect } from "effect"// ❌ Before — chained pipe callsconst result = pipe( pipe( someEffect, Effect.map((x) => x + 1) ), Effect.mapError((e) => new WrappedError({ cause: e })))// ✅ After (quick fix applied) — single pipe callconst result = pipe( someEffect, Effect.map((x) => x + 1), Effect.mapError((e) => new WrappedError({ cause: e })))