A Rust port of the Effect Language Service diagnostics, powered by tsrs. Provides all 130 diagnostic rules for Effect V4 and Effect V3.
The port provides a command-line diagnostic runner and Oxlint type-aware linting.
Effect quick fixes, refactors, and completions are available in the upstream
@effect/tsgo language service.
Add @yielded/effect-tsrs to your project:
npm install --save-dev @yielded/effect-tsrsThen check your project:
npx effect-tsrs diagnostics --project tsconfig.jsonOr run it without adding a dependency:
npx @yielded/effect-tsrs diagnostics --project tsconfig.jsonRequires Node.js 22.14 or newer. Prebuilt binaries are available for macOS (Apple Silicon and Intel), Linux (ARM64 and x64), and Windows (x64). The compiler and TypeScript standard libraries are included; no Rust toolchain is needed.
Keep npm optional dependencies enabled so the binary for your platform is installed.
Existing @effect/language-service plugin options in tsconfig.json are respected.
See Plugin Options to configure diagnostics.
Install a supported Oxlint pair, then run it through the Rust adapter:
npm install --save-dev oxlint@1.89.0 oxlint-tsgolint@7.0.2005
npx effect-tsrs oxlint .Enable Effect rules in .oxlintrc.json. The effecttsgo namespace is retained for
compatibility with existing configurations:
{
"$schema": "./node_modules/@yielded/effect-tsrs/oxlint-schema.json",
"plugins": ["effecttsgo", "typescript"],
"rules": {
"effecttsgo/floating-effect": "error",
"effecttsgo/missing-star-in-yield-effect-gen": "error",
"typescript/no-floating-promises": "error"
}
}The adapter enables --type-aware automatically. Oxlint owns severities,
per-file overrides, ignore patterns, and suppression comments. All 130 Effect
rules run in Rust; other type-aware rules and --type-check use the installed
oxlint-tsgolint backend. Effect behavior options and source
directives remain supported; Oxlint rule selection overrides tsconfig diagnostic
severities. Effect code actions (quick fixes and suggested edits) and per-rule
profiling are not ported.
--threads N limits the compiler workers as well as Oxlint's frontend. Under an
explicit limit the Rust and Go backend passes run sequentially; --threads 1
also disables parallel parsing and checking. Without a limit, tsrs chooses its
checker count and the backends can run concurrently. The standalone diagnostics
command accepts --singleThreaded for single-threaded comparisons.
Presets are available as @yielded/effect-tsrs/oxlint-presets for JS/TS configs,
or through extends, for example:
["./node_modules/@yielded/effect-tsrs/oxlint-presets/recommended.json"].
For existing scripts or editor integrations that invoke Oxlint directly, run
npx effect-tsrs patch --oxlint, then use oxlint --type-aware normally.
npx effect-tsrs unpatch --oxlint restores the original package bytes. Reapply
after reinstalling dependencies. Patching refuses shared symlink targets outside
the project; effect-tsrs oxlint works without modifying installed packages.
Supported Oxlint and oxlint-tsgolint versions, along with the packaged frontend source, are pinned in the Oxlint metadata. The Effect backend is this Rust port. Linux Oxlint integration requires glibc; the standalone diagnostic CLI also works with musl.
From a source checkout, run npm run build:release && npm run prepare:oxlint,
then EFFECT_TSRS_BINARY="$PWD/target/release/effect-tsrs" node bin/effect-tsrs.js oxlint ..
Run npm run verify:oxlint for integration checks against Effect TSGO.
npm run verify:oxlint:all compares all 614 fixture projects through the headless
protocol, covering all 130 rules, including duplicate diagnostics and related ranges.
The CLI reports Effect diagnostics using the defaults and project selection of
the supported @effect/tsgo version. It targets
configured root files and roots of referenced projects; imported files outside
those roots supply types but receive no Effect diagnostics. Shared roots use
the selected project's plugin configuration. See
project selection for reference loading,
overlap precedence, and file eligibility. Add it to your project scripts to run
the same check locally and in CI:
{
"scripts": {
"check": "effect-tsrs diagnostics --project tsconfig.json"
}
}Diagnostics can also be emitted as JSON for other tools:
npx effect-tsrs diagnostics --project tsconfig.json --format jsonThe exit status reflects Effect errors (and warnings with --strict). Add
--type-check to also report ordinary TypeScript diagnostics and include them in
the exit status. This explicitly enables the combined check used by earlier
effect-tsrs versions.
JSON contains separate diagnostics and typescriptDiagnostics arrays,
summary counts, and timings.
typescriptDiagnostics is populated by --type-check. The command
exits with 0 when there are no errors, 1 for project or diagnostic errors,
and 2 for invalid arguments. Warnings and suggestions do not fail the command
by default.
Use --strict to also exit with 1 when warnings are reported, matching
effect-tsgo diagnostics --strict:
npx effect-tsrs diagnostics --project tsconfig.json --strictThis only changes the exit code; warnings keep their severity in text and JSON output. Suggestions still do not fail the command.
The current release is built and tested with the following versions:
| Component | Version |
|---|---|
| Effect | V3 (3.19.19) and V4 (4.0.2) |
| TypeScript | 7.1.0-beta, bundled through tsrs |
| tsrs | 0.19.0, pinned in upstream.json |
| Effect diagnostic rules | @effect/tsgo@0.56.1 |
| Node.js | >=22.14 |
All 130 rules below are implemented. The release passes 4,263/4,263 comparisons against upstream, including diagnostic messages, severities, ranges, and related information. Verification details.
Some rules are disabled by default or produce suggestions. Enable them or change their severity in Plugin Options. Rule descriptions and links below mirror the upstream README.
| Rule | Description |
|---|---|
| Correctness Wrong, unsafe, or structurally invalid code patterns. | |
anyUnknownInErrorContext | Detects 'any' or 'unknown' types in Effect error or requirements channels |
classSelfMismatch | Ensures Self type parameter matches the class name in Context/Service/Tag/Schema classes |
duplicatePackage | Warns when multiple versions of an Effect-related package are detected in the program |
effectFnImplicitAny | Mirrors noImplicitAny for unannotated Effect.fn, Effect.fnUntraced, and Effect.fnUntracedEager callback parameters when no outer contextual function type exists. Requires TS's noImplicitAny: true |
errorPayloadShadowsName | Detects error payload fields named `name` that shadow the error's own name |
experimentalApiUsage | Warns when using an API marked @stability experimental |
floatingEffect | Detects Effect values that are neither yielded nor assigned |
floatingEffectInVitest | Detects Effects returned from non-Effect-aware Vitest callbacks |
genericEffectServices | Prevents services with type parameters that cannot be discriminated at runtime |
missingEffectContext | Detects Effect values with unhandled context requirements |
missingEffectError | Detects Effect values with unhandled error types |
missingLayerContext | Detects Layer values with unhandled context requirements |
missingReturnYieldStar | Suggests using return yield* for Effects that never succeed |
missingStarInYieldEffectGen | Detects bare yield (without *) inside Effect generator scopes |
nonObjectEffectServiceType | Ensures Effect.Service types are objects, not primitives |
obsoleteMatchImport | Warns when importing @effect/match in projects targeting Effect v4, where Match is included directly in the effect package |
obsoleteSchemaImport | Warns when importing @effect/schema in projects targeting Effect v4, where Schema is included directly in the effect package |
outdatedApi | Detects usage of APIs that have been removed or renamed in Effect v4 |
overriddenSchemaConstructor | Prevents overriding constructors in Schema classes which breaks decoding behavior |
promiseInEffectSuccess | Detects Promise types in Effect success channels where they are not awaited |
schemaClassMissingBrand | Warns when Schema classes, errors, or Opaque classes omit the brand type argument |
schemaClassObjectLiteral | Warns when object literals are checked as Schema class instances without the class prototype |
schemaLiteralNonFinite | Reports statically known non-finite numbers passed to Schema literal constructors |
schemaOpaqueInstanceMember | Disallows instance members in classes extending Schema.Opaque |
unsafeEffectTypeAssertion | Detects unsafe type assertions that narrow Effect, Stream, or Layer error or requirements channels |
unstableApiUsage | Warns when using an API marked @stability unstable |
| Anti-pattern Discouraged patterns that often lead to bugs or confusing behavior. | |
catchUnfailableEffect | Warns when using error handling on Effects that never fail |
effectFnIife | Effect.fn or Effect.fnUntraced is called as an IIFE; use Effect.gen instead |
effectGenUsesAdapter | Warns when using the deprecated adapter parameter in Effect.gen |
effectInFailure | Warns when an Effect is used inside an Effect failure channel |
effectInVoidSuccess | Detects nested Effects in void success channels that may cause unexecuted effects |
globalErrorInEffectCatch | Warns when catch callbacks return global Error type instead of typed errors |
globalErrorInEffectFailure | Warns when the global Error type is used in an Effect failure channel |
layerMergeAllWithDependencies | Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires |
lazyEffect | Suggests avoiding exported zero-argument functions and service members that lazily return Effect or Stream values |
lazyPromiseInEffectSync | Warns when Effect.sync lazily returns a Promise instead of using an async Effect constructor |
leakingRequirements | Detects implementation services leaked in service methods |
multipleEffectProvide | Warns against chaining Effect.provide calls which can cause service lifecycle issues |
preferUnsafeConstructor | Suggests replacing Effect.runSync of a pure effect constructor with the synchronous *Unsafe variant exported by the same module |
returnEffectInGen | Warns when returning an Effect in a generator causes nested Effect<Effect<...>> |
runEffectInsideEffect | Suggests using Runtime or Effect.run*With methods instead of Effect.run* inside Effect contexts |
schemaSyncInEffect | Suggests using Effect-based Schema methods instead of sync methods inside Effect generators |
scopeInLayerEffect | Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements |
strictEffectProvide | Warns when using Effect.provide with layers outside of application entry points |
tryCatchInEffectGen | Discourages try/catch in Effect generators in favor of Effect error handling |
unknownInEffectCatch | Warns when catch callbacks return unknown instead of typed errors |
| Effect-native Prefer Effect-native APIs and abstractions when available. | |
abortControllerInEffect | Warns when manually constructing AbortController inside Effect generators instead of using Effect.abortSignal |
asyncFunction | Warns when declaring async functions and suggests using Effect values and Effect.gen for async control flow |
cryptoRandomUUID | Warns when using crypto.randomUUID() outside Effect generators instead of the Effect Crypto module |
cryptoRandomUUIDInEffect | Warns when using crypto.randomUUID() inside Effect generators instead of the Effect Crypto module |
extendsNativeError | Warns when a class directly extends the native Error class |
globalConsole | Warns when using console methods outside Effect generators instead of Effect.log/Logger |
globalConsoleInEffect | Warns when using console methods inside Effect generators instead of Effect.log/Logger |
globalDate | Warns when using Date.now() or new Date() outside Effect generators instead of Clock/DateTime |
globalDateInEffect | Warns when using Date.now() or new Date() inside Effect generators instead of Clock/DateTime |
globalFetch | Warns when using the global fetch function outside Effect generators instead of the Effect HTTP client |
globalFetchInEffect | Warns when using the global fetch function inside Effect generators instead of the Effect HTTP client |
globalRandom | Warns when using Math.random() outside Effect generators instead of the Random service |
globalRandomInEffect | Warns when using Math.random() inside Effect generators instead of the Random service |
globalTimers | Warns when using setTimeout/setInterval outside Effect generators instead of Effect.sleep/Schedule |
globalTimersInEffect | Warns when using setTimeout/setInterval inside Effect generators instead of Effect.sleep/Schedule |
instanceOfSchema | Suggests using Schema.is instead of instanceof for Effect Schema types |
newPromise | Warns when constructing promises with new Promise instead of using Effect APIs |
nodeBuiltinImport | Warns when importing Node.js built-in modules that have Effect-native counterparts |
preferEffectArray | Suggests Effect Array APIs for native array member references, with semantic caveats |
preferEffectRecord | Suggests Effect Record APIs for native Object member references, with semantic caveats |
preferEffectString | Suggests Effect String APIs for native String method invocations, with semantic caveats |
preferSchemaOverJson | Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify |
processEnv | Warns when reading process.env outside Effect generators instead of using Effect Config |
processEnvInEffect | Warns when reading process.env inside Effect generators instead of using Effect Config |
schemaSync | Suggests Effect-based Schema decoding and encoding instead of synchronous methods |
| Style Cleanup, consistency, and idiomatic Effect code. | |
acquireReleaseDisposable | Suggests Effect.acquireDisposable when Effect.acquireRelease only invokes the acquired resource's disposal protocol |
allOfMapToForEach | Suggests using Effect.forEach instead of Effect.all over an effectful Array#map |
catchAllTagDispatchToCatchTag | Suggests Effect.catchTag or Effect.catchTags for catch-all handlers that re-fail unmatched tagged errors |
catchAllToMapError | Suggests using Effect.mapError instead of Effect.catch + Effect.fail |
catchChainToFirstSuccessOf | Suggests Effect.firstSuccessOf for consecutive error-independent Effect.catch fallbacks when the error type is preserved |
catchConditionalRefailToCatchIf | Suggests Effect.catchIf, Effect.catchCauseIf, or Effect.catchTag for conditional catch handlers that re-fail their untouched input |
catchDieToOrDie | Suggests using Effect.orDie instead of Effect.catch or Effect.catchAll with an identity-forwarding Effect.die handler |
catchIfTagToCatchTag | Suggests Effect.catchTag instead of Effect.catchIf with a _tag equality or Predicate.isTagged predicate |
catchRefailToTapError | Suggests Effect.tapError for catch handlers that sequence an effect and then re-fail the original error |
catchTagToCatchReason | Suggests Effect.catchReason or Effect.catchReasons for handlers that re-fail unmatched reason._tag branches |
catchToIgnore | Suggests using Effect.ignore or Effect.ignoreCause instead of Effect.catch/catchCause returning Effect.void |
catchToOrElseSucceed | Suggests using Effect.orElseSucceed instead of Effect.catch + Effect.succeed |
deterministicKeys | Enforces deterministic naming for service/tag/error identifiers based on class names |
effectDoNotation | Suggests using Effect.gen or Effect.fn instead of the Effect.Do notation helpers |
effectFnOpportunity | Suggests using Effect.fn for functions that return an Effect |
effectMapFlatten | Suggests using Effect.flatMap instead of Effect.map followed by Effect.flatten in piping flows |
effectMapVoid | Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {}) |
effectSleepZeroToYieldNow | Suggests Effect.yieldNow for cooperative yielding instead of sleeping for zero duration |
effectSucceedWithVoid | Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0) |
flatMapConditionalToFilterOrFail | Suggests Effect.filterOrFail or Effect.filterOrElse when Effect.flatMap conditionally passes its input through with Effect.succeed |
flatMapIgnoredParamToAndThen | Suggests using Effect.andThen instead of Effect.flatMap when a zero-parameter callback returns an existing Effect value |
flatMapToMap | Suggests using Effect.map instead of Effect.flatMap when the callback only wraps its result with Effect.succeed |
flatMapToTap | Suggests Effect.tap when a flatMap or andThen callback runs an effect and returns its unchanged parameter |
mapSomeToAsSome | Suggests using Effect.asSome instead of Effect.map when the mapper only wraps the success value with Option.some |
matchEffectToMapBoth | Suggests Effect.mapBoth when Effect.matchEffect only transforms the failure and success channels |
matchEffectToMatch | Suggests Effect.match or Effect.matchCause when both Effect.matchEffect handlers only return Effect.succeed |
missedPipeableOpportunity | Suggests using .pipe() for nested function calls |
missingEffectServiceDependency | Checks that Effect.Service dependencies satisfy all required layer inputs |
missingPipeableSignature | Reports exported fixed-arity functions whose call signatures have no corresponding pipeable overload |
multipleCatchTag | Suggests collapsing consecutive Effect.catchTag transformations into a single Effect.catchTags call when semantics stay equivalent |
nestedEffectGenYield | Warns when yielding a nested bare Effect.gen inside an existing Effect generator context |
newSchemaClass | Suggests using Schema make instead of new for Schema classes |
optionMatchToFromOption | Suggests Effect.fromOption when Option.match or an Option tag conditional only converts Some to Effect.succeed and None to Effect.fail |
preferBarrelImport | Reports imports of individual modules from packages configured in barrelImportPackages that should be named imports from the package barrel |
preferNamespaceImport | Reports named imports from packages configured in namespaceImportPackages that should be namespace imports |
preferSchemaTaggedError | Suggests using Schema.TaggedError instead of Data.TaggedError |
preferSchemaTypeProperty | Disallows Schema.Schema.Type<typeof X> in favor of typeof X.Type |
preferSucceedSomeOrNone | Suggests using Effect.succeedNone or Effect.succeedSome instead of wrapping Option.none or Option.some with Effect.succeed |
preferTypedSchemaDecoder | Suggests typed Schema decoders when the input is assignable to the schema's Encoded type |
provideLayerSucceedToProvideService | Suggests providing inline Layer.succeed and Layer.effect services directly |
raceFirstWithSleepToTimeout | Suggests Effect.timeoutOrElse when Effect.raceFirst has exactly one sleep- or delay-based timer arm |
redundantMapError | Suggests hoisting a repeated trailing Effect.mapError from every yield in an Effect generator |
redundantOrDie | Suggests hoisting a repeated trailing Effect.orDie from every yield in an Effect generator |
redundantSchemaTagIdentifier | Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest |
runOfExitToRunExit | Suggests using Effect.runPromiseExit instead of passing Effect.exit to Effect.runPromise |
schemaNumber | Suggests Schema.Finite and Schema.FiniteFromString instead of Schema.Number APIs when describing domain numbers |
schemaStructWithTag | Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field |
schemaUnionOfLiterals | Suggests combining multiple Schema.Literal calls in Schema.Union into a single Schema.Literal |
serviceNotAsClass | Warns when Context.Service is used as a variable instead of a class declaration |
strictBooleanExpressions | Enforces boolean types in conditional expressions for type safety |
syncToSucceed | Suggests using Effect.succeed instead of Effect.sync when the thunk returns a constant value |
timeoutCatchTagToTimeoutOrElse | Suggests dedicated timeout combinators instead of catching TimeoutError immediately after Effect.timeout |
unnecessaryArrowBlock | Suggests using a concise arrow body when the block only returns an expression |
unnecessaryEffectGen | Suggests removing Effect.gen when it contains only a single return statement |
unnecessaryFailYieldableError | Suggests yielding yieldable errors directly instead of wrapping with Effect.fail |
unnecessaryPipe | Removes pipe calls with no arguments |
unnecessaryPipeChain | Simplifies chained pipe calls into a single pipe call |
unnecessaryTypeofType | Suggests replacing typeof Schema.Type style annotations with the matching named type when available |
| Maintainers Guard the public API surface and release hygiene of a package. | |
apiStabilityLeak | Reports exported APIs whose public surface exposes a less stable type |
Refactors and quick fixes are not implemented in this port. Use
@effect/tsgo for editor code actions.
Editor completions are not implemented in this port. See the upstream completion status.
Use effect-tsrs for project diagnostics in your terminal, scripts, and CI.
It runs the Effect rules on tsrs's inferred types and symbols.
@effect/tsgo provides the full editor language service, including quick fixes,
refactors, and completions. effect-tsrs is a standalone diagnostics CLI and
does not replace your editor's TypeScript language server or build command.
Each release bundles a pinned tsrs compiler and its TypeScript standard libraries.
upstream.json records the compiler revision; see
Supported Package Versions for the Effect diagnostic
baseline. Updating a separate TypeScript installation does not change the compiler
used by effect-tsrs.
Upgrade @yielded/effect-tsrs when a release includes compiler fixes or Effect
diagnostics you need. npm installs the matching native package for your platform.
Configure diagnostics in your existing tsconfig.json using the same
@effect/language-service plugin entry as upstream. By default, the CLI skips
Effect diagnostics for a project without a plugin entry. Pass --lspconfig '{}' to
explicitly activate upstream defaults without editing the project config.
Severity values are "off", "error", "warning", "suggestion", and
"message". Inherited configuration, per-file overrides, and source suppression
directives are supported:
// @effect-diagnostics-next-line floatingEffect:off
Effect.succeed(42)To select rules for a single run, also opting in when no plugin entry exists:
npx effect-tsrs diagnostics --project tsconfig.json --rules floatingEffect,asyncFunction
npx effect-tsrs diagnostics --project tsconfig.json --rules allExplicit severity settings and suppression directives still apply.
--rules none disables the source-file Effect pass. Use --lspconfig to replace
the plugin options for a command:
npx effect-tsrs diagnostics --project tsconfig.json \
--lspconfig '{"diagnosticSeverity":{"floatingEffect":"warning"}}'The allowedUnstableApis and allowedExperimentalApis options accept module
paths or individual exports:
{
"allowedUnstableApis": ["effect/http", "effect/rpc/RpcClient#make"],
"allowedExperimentalApis": ["my-package/Preview#make"]
}A module path permits that module and its descendants; #exportName permits
only that export. Matching is case-sensitive and respects path segments.
Per-file overrides replace these lists.
Import preferences can also vary by file:
{
"namespaceImportPackages": ["effect"],
"overrides": [
{
"include": ["src/legacy/**/*.ts"],
"options": {
"namespaceImportPackages": [],
"barrelImportPackages": ["effect"]
}
}
]
}See the porting notes for source builds, verification, benchmarks, and release instructions, and the fixture guide for focused rule comparisons.
MIT. Rules and diagnostic descriptions are adapted from Effect-TS/tsgo. Compiler and dependency notices are included in THIRD-PARTY-NOTICES.
{ "compilerOptions": { "plugins": [ { "name": "@effect/language-service", // Enable Effect diagnostics. (default: true) "diagnostics": true, // Upstream tsc option; standalone diagnostics always includes suggestions. "includeSuggestionsInTsc": true, // Process disabled rules so source directives can re-enable them. "skipDisabledOptimization": false, // Prefer namespace or barrel imports for these packages. "namespaceImportPackages": [], "barrelImportPackages": [], // Follow named reexports at package top level. (default: "ignore") "topLevelNamedReexports": "ignore", // Patterns used by deterministicKeys. "keyPatterns": [ { "target": "service", "pattern": "default", "skipLeadingPath": ["src/"] }, { "target": "custom", "pattern": "default", "skipLeadingPath": ["src/"] } ], "extendedKeyDetection": false, // Minimum pipeable transformations for missedPipeableOpportunity. "pipeableMinArgCount": 2, // Permit selected unstable or experimental APIs. "allowedUnstableApis": [], "allowedExperimentalApis": [], // Permit multiple versions of these packages. "allowedDuplicatedPackages": [], // Variants considered by effectFnOpportunity. "effectFn": ["span"], // Override rule defaults. Use {} to keep the defaults. "diagnosticSeverity": { "floatingEffect": "error", "asyncFunction": "warning" }, // Apply options to matching files. "overrides": [ { "include": ["test/**/*.ts"], "options": { "diagnosticSeverity": { "asyncFunction": "off" } } } ] } ] } }