Skip to content

About

Native Rust port of all 130 Effect diagnostics to tsrs

Resources

Stars

59 stars

Watchers

0 watching

Forks

Repository files navigation

Effect Diagnostics (tsrs)

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.

Installation

Add @yielded/effect-tsrs to your project:

npm install --save-dev @yielded/effect-tsrs

Then check your project:

npx effect-tsrs diagnostics --project tsconfig.json

Or run it without adding a dependency:

npx @yielded/effect-tsrs diagnostics --project tsconfig.json

Requires 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.

Oxlint integration

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.

Standalone type-aware linter

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 json

The 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 --strict

This only changes the exit code; warnings keep their severity in text and JSON output. Suggestions still do not fail the command.

Supported Package Versions

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

Diagnostic Status

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.

RuleDescription
Correctness Wrong, unsafe, or structurally invalid code patterns.
anyUnknownInErrorContextDetects 'any' or 'unknown' types in Effect error or requirements channels
classSelfMismatchEnsures Self type parameter matches the class name in Context/Service/Tag/Schema classes
duplicatePackageWarns when multiple versions of an Effect-related package are detected in the program
effectFnImplicitAnyMirrors noImplicitAny for unannotated Effect.fn, Effect.fnUntraced, and Effect.fnUntracedEager callback parameters when no outer contextual function type exists. Requires TS's noImplicitAny: true
errorPayloadShadowsNameDetects error payload fields named `name` that shadow the error's own name
experimentalApiUsageWarns when using an API marked @stability experimental
floatingEffectDetects Effect values that are neither yielded nor assigned
floatingEffectInVitestDetects Effects returned from non-Effect-aware Vitest callbacks
genericEffectServicesPrevents services with type parameters that cannot be discriminated at runtime
missingEffectContextDetects Effect values with unhandled context requirements
missingEffectErrorDetects Effect values with unhandled error types
missingLayerContextDetects Layer values with unhandled context requirements
missingReturnYieldStarSuggests using return yield* for Effects that never succeed
missingStarInYieldEffectGenDetects bare yield (without *) inside Effect generator scopes
nonObjectEffectServiceTypeEnsures Effect.Service types are objects, not primitives
obsoleteMatchImportWarns when importing @effect/match in projects targeting Effect v4, where Match is included directly in the effect package
obsoleteSchemaImportWarns when importing @effect/schema in projects targeting Effect v4, where Schema is included directly in the effect package
outdatedApiDetects usage of APIs that have been removed or renamed in Effect v4
overriddenSchemaConstructorPrevents overriding constructors in Schema classes which breaks decoding behavior
promiseInEffectSuccessDetects Promise types in Effect success channels where they are not awaited
schemaClassMissingBrandWarns when Schema classes, errors, or Opaque classes omit the brand type argument
schemaClassObjectLiteralWarns when object literals are checked as Schema class instances without the class prototype
schemaLiteralNonFiniteReports statically known non-finite numbers passed to Schema literal constructors
schemaOpaqueInstanceMemberDisallows instance members in classes extending Schema.Opaque
unsafeEffectTypeAssertionDetects unsafe type assertions that narrow Effect, Stream, or Layer error or requirements channels
unstableApiUsageWarns when using an API marked @stability unstable
Anti-pattern Discouraged patterns that often lead to bugs or confusing behavior.
catchUnfailableEffectWarns when using error handling on Effects that never fail
effectFnIifeEffect.fn or Effect.fnUntraced is called as an IIFE; use Effect.gen instead
effectGenUsesAdapterWarns when using the deprecated adapter parameter in Effect.gen
effectInFailureWarns when an Effect is used inside an Effect failure channel
effectInVoidSuccessDetects nested Effects in void success channels that may cause unexecuted effects
globalErrorInEffectCatchWarns when catch callbacks return global Error type instead of typed errors
globalErrorInEffectFailureWarns when the global Error type is used in an Effect failure channel
layerMergeAllWithDependenciesDetects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires
lazyEffectSuggests avoiding exported zero-argument functions and service members that lazily return Effect or Stream values
lazyPromiseInEffectSyncWarns when Effect.sync lazily returns a Promise instead of using an async Effect constructor
leakingRequirementsDetects implementation services leaked in service methods
multipleEffectProvideWarns against chaining Effect.provide calls which can cause service lifecycle issues
preferUnsafeConstructorSuggests replacing Effect.runSync of a pure effect constructor with the synchronous *Unsafe variant exported by the same module
returnEffectInGenWarns when returning an Effect in a generator causes nested Effect<Effect<...>>
runEffectInsideEffectSuggests using Runtime or Effect.run*With methods instead of Effect.run* inside Effect contexts
schemaSyncInEffectSuggests using Effect-based Schema methods instead of sync methods inside Effect generators
scopeInLayerEffectSuggests using Layer.scoped instead of Layer.effect when Scope is in requirements
strictEffectProvideWarns when using Effect.provide with layers outside of application entry points
tryCatchInEffectGenDiscourages try/catch in Effect generators in favor of Effect error handling
unknownInEffectCatchWarns when catch callbacks return unknown instead of typed errors
Effect-native Prefer Effect-native APIs and abstractions when available.
abortControllerInEffectWarns when manually constructing AbortController inside Effect generators instead of using Effect.abortSignal
asyncFunctionWarns when declaring async functions and suggests using Effect values and Effect.gen for async control flow
cryptoRandomUUIDWarns when using crypto.randomUUID() outside Effect generators instead of the Effect Crypto module
cryptoRandomUUIDInEffectWarns when using crypto.randomUUID() inside Effect generators instead of the Effect Crypto module
extendsNativeErrorWarns when a class directly extends the native Error class
globalConsoleWarns when using console methods outside Effect generators instead of Effect.log/Logger
globalConsoleInEffectWarns when using console methods inside Effect generators instead of Effect.log/Logger
globalDateWarns when using Date.now() or new Date() outside Effect generators instead of Clock/DateTime
globalDateInEffectWarns when using Date.now() or new Date() inside Effect generators instead of Clock/DateTime
globalFetchWarns when using the global fetch function outside Effect generators instead of the Effect HTTP client
globalFetchInEffectWarns when using the global fetch function inside Effect generators instead of the Effect HTTP client
globalRandomWarns when using Math.random() outside Effect generators instead of the Random service
globalRandomInEffectWarns when using Math.random() inside Effect generators instead of the Random service
globalTimersWarns when using setTimeout/setInterval outside Effect generators instead of Effect.sleep/Schedule
globalTimersInEffectWarns when using setTimeout/setInterval inside Effect generators instead of Effect.sleep/Schedule
instanceOfSchemaSuggests using Schema.is instead of instanceof for Effect Schema types
newPromiseWarns when constructing promises with new Promise instead of using Effect APIs
nodeBuiltinImportWarns when importing Node.js built-in modules that have Effect-native counterparts
preferEffectArraySuggests Effect Array APIs for native array member references, with semantic caveats
preferEffectRecordSuggests Effect Record APIs for native Object member references, with semantic caveats
preferEffectStringSuggests Effect String APIs for native String method invocations, with semantic caveats
preferSchemaOverJsonSuggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify
processEnvWarns when reading process.env outside Effect generators instead of using Effect Config
processEnvInEffectWarns when reading process.env inside Effect generators instead of using Effect Config
schemaSyncSuggests Effect-based Schema decoding and encoding instead of synchronous methods
Style Cleanup, consistency, and idiomatic Effect code.
acquireReleaseDisposableSuggests Effect.acquireDisposable when Effect.acquireRelease only invokes the acquired resource's disposal protocol
allOfMapToForEachSuggests using Effect.forEach instead of Effect.all over an effectful Array#map
catchAllTagDispatchToCatchTagSuggests Effect.catchTag or Effect.catchTags for catch-all handlers that re-fail unmatched tagged errors
catchAllToMapErrorSuggests using Effect.mapError instead of Effect.catch + Effect.fail
catchChainToFirstSuccessOfSuggests Effect.firstSuccessOf for consecutive error-independent Effect.catch fallbacks when the error type is preserved
catchConditionalRefailToCatchIfSuggests Effect.catchIf, Effect.catchCauseIf, or Effect.catchTag for conditional catch handlers that re-fail their untouched input
catchDieToOrDieSuggests using Effect.orDie instead of Effect.catch or Effect.catchAll with an identity-forwarding Effect.die handler
catchIfTagToCatchTagSuggests Effect.catchTag instead of Effect.catchIf with a _tag equality or Predicate.isTagged predicate
catchRefailToTapErrorSuggests Effect.tapError for catch handlers that sequence an effect and then re-fail the original error
catchTagToCatchReasonSuggests Effect.catchReason or Effect.catchReasons for handlers that re-fail unmatched reason._tag branches
catchToIgnoreSuggests using Effect.ignore or Effect.ignoreCause instead of Effect.catch/catchCause returning Effect.void
catchToOrElseSucceedSuggests using Effect.orElseSucceed instead of Effect.catch + Effect.succeed
deterministicKeysEnforces deterministic naming for service/tag/error identifiers based on class names
effectDoNotationSuggests using Effect.gen or Effect.fn instead of the Effect.Do notation helpers
effectFnOpportunitySuggests using Effect.fn for functions that return an Effect
effectMapFlattenSuggests using Effect.flatMap instead of Effect.map followed by Effect.flatten in piping flows
effectMapVoidSuggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})
effectSleepZeroToYieldNowSuggests Effect.yieldNow for cooperative yielding instead of sleeping for zero duration
effectSucceedWithVoidSuggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)
flatMapConditionalToFilterOrFailSuggests Effect.filterOrFail or Effect.filterOrElse when Effect.flatMap conditionally passes its input through with Effect.succeed
flatMapIgnoredParamToAndThenSuggests using Effect.andThen instead of Effect.flatMap when a zero-parameter callback returns an existing Effect value
flatMapToMapSuggests using Effect.map instead of Effect.flatMap when the callback only wraps its result with Effect.succeed
flatMapToTapSuggests Effect.tap when a flatMap or andThen callback runs an effect and returns its unchanged parameter
mapSomeToAsSomeSuggests using Effect.asSome instead of Effect.map when the mapper only wraps the success value with Option.some
matchEffectToMapBothSuggests Effect.mapBoth when Effect.matchEffect only transforms the failure and success channels
matchEffectToMatchSuggests Effect.match or Effect.matchCause when both Effect.matchEffect handlers only return Effect.succeed
missedPipeableOpportunitySuggests using .pipe() for nested function calls
missingEffectServiceDependencyChecks that Effect.Service dependencies satisfy all required layer inputs
missingPipeableSignatureReports exported fixed-arity functions whose call signatures have no corresponding pipeable overload
multipleCatchTagSuggests collapsing consecutive Effect.catchTag transformations into a single Effect.catchTags call when semantics stay equivalent
nestedEffectGenYieldWarns when yielding a nested bare Effect.gen inside an existing Effect generator context
newSchemaClassSuggests using Schema make instead of new for Schema classes
optionMatchToFromOptionSuggests Effect.fromOption when Option.match or an Option tag conditional only converts Some to Effect.succeed and None to Effect.fail
preferBarrelImportReports imports of individual modules from packages configured in barrelImportPackages that should be named imports from the package barrel
preferNamespaceImportReports named imports from packages configured in namespaceImportPackages that should be namespace imports
preferSchemaTaggedErrorSuggests using Schema.TaggedError instead of Data.TaggedError
preferSchemaTypePropertyDisallows Schema.Schema.Type<typeof X> in favor of typeof X.Type
preferSucceedSomeOrNoneSuggests using Effect.succeedNone or Effect.succeedSome instead of wrapping Option.none or Option.some with Effect.succeed
preferTypedSchemaDecoderSuggests typed Schema decoders when the input is assignable to the schema's Encoded type
provideLayerSucceedToProvideServiceSuggests providing inline Layer.succeed and Layer.effect services directly
raceFirstWithSleepToTimeoutSuggests Effect.timeoutOrElse when Effect.raceFirst has exactly one sleep- or delay-based timer arm
redundantMapErrorSuggests hoisting a repeated trailing Effect.mapError from every yield in an Effect generator
redundantOrDieSuggests hoisting a repeated trailing Effect.orDie from every yield in an Effect generator
redundantSchemaTagIdentifierSuggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest
runOfExitToRunExitSuggests using Effect.runPromiseExit instead of passing Effect.exit to Effect.runPromise
schemaNumberSuggests Schema.Finite and Schema.FiniteFromString instead of Schema.Number APIs when describing domain numbers
schemaStructWithTagSuggests using Schema.TaggedStruct instead of Schema.Struct with _tag field
schemaUnionOfLiteralsSuggests combining multiple Schema.Literal calls in Schema.Union into a single Schema.Literal
serviceNotAsClassWarns when Context.Service is used as a variable instead of a class declaration
strictBooleanExpressionsEnforces boolean types in conditional expressions for type safety
syncToSucceedSuggests using Effect.succeed instead of Effect.sync when the thunk returns a constant value
timeoutCatchTagToTimeoutOrElseSuggests dedicated timeout combinators instead of catching TimeoutError immediately after Effect.timeout
unnecessaryArrowBlockSuggests using a concise arrow body when the block only returns an expression
unnecessaryEffectGenSuggests removing Effect.gen when it contains only a single return statement
unnecessaryFailYieldableErrorSuggests yielding yieldable errors directly instead of wrapping with Effect.fail
unnecessaryPipeRemoves pipe calls with no arguments
unnecessaryPipeChainSimplifies chained pipe calls into a single pipe call
unnecessaryTypeofTypeSuggests 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.
apiStabilityLeakReports exported APIs whose public surface exposes a less stable type

Refactor Status

Refactors and quick fixes are not implemented in this port. Use @effect/tsgo for editor code actions.

Completion Status

Editor completions are not implemented in this port. See the upstream completion status.

Best Practices

Relationship to effect-tsgo and tsrs

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.

Version Pinning

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.

When to Upgrade

Upgrade @yielded/effect-tsrs when a release includes compiler fixes or Effect diagnostics you need. npm installs the matching native package for your platform.

Plugin Options

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.

{
  "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"
              }
            }
          }
        ]
      }
    ]
  }
}

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 all

Explicit 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"]
      }
    }
  ]
}

Development

See the porting notes for source builds, verification, benchmarks, and release instructions, and the fixture guide for focused rule comparisons.

License

MIT. Rules and diagnostic descriptions are adapted from Effect-TS/tsgo. Compiler and dependency notices are included in THIRD-PARTY-NOTICES.

About

Native Rust port of all 130 Effect diagnostics to tsrs

Resources

Stars

59 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages