Source Linter
The source linter runs deterministic AST-based checks against your Effect code and emits findings with rule codes, severities, source spans, docs URLs, and copy-pasteable Bad → Good examples. It is independent of IR analysis — it operates on the TypeScript AST directly so it is fast enough to run on every commit.
effect-analyze ./src --lint-sourceWhat it catches
Section titled “What it catches”Fifteen rules covering the most common Effect anti-patterns surfaced in EffectPatterns, effect-ts/examples, and real-world reviews:
| Code | Severity | Catches |
|---|---|---|
detached-fiber-in-test |
warning | Effect.runFork(Effect.never) in tests — fiber outlives test scope |
empty-effect-all |
info | Effect.all([]) — dead or placeholder code |
forEach-without-concurrency |
info | Effect.forEach(items, f) without an explicit concurrency option |
identity-catch |
warning | Effect.catch(e, (e) => Effect.fail(e)) — no-op handler |
live-layer-in-test |
warning | Live layer references in test files where test layers are expected |
mutable-in-concurrent |
warning | let/var mutation inside parallel / fork / race contexts |
nondeterministic-test-api |
warning | Date.now() / new Date() / Math.random() in test code |
promise-api-in-gen |
warning | Promise.all / Promise.race inside Effect.gen |
runPromise-then-chain |
info | .then / .catch after Effect.runPromise |
runSync-on-async |
error | Effect.runSync on an async-tainted effect — throws at runtime |
runSyncExit-on-async |
error | Effect.runSyncExit on an async-tainted effect |
schedule-unbounded |
warning | Schedule.forever and friends without visible bounds |
sleep-without-testclock |
info | Real-time Effect.sleep in tests without TestClock |
unsafe-api-usage |
warning | Effect.unsafe* APIs bypassing runtime safety |
untagged-throw |
warning | throw new Error(...) inside an Effect context |
Every finding carries:
- rule code + severity
- source span (file, line, column, end line, end column)
docs.effect.websitelink for the rule- Bad → Good example snippet pulled from the rule registry
Type-aware rules via @effect/tsgo
Section titled “Type-aware rules via @effect/tsgo”These rules are AST-based and deliberately narrow. For the ~95 type-aware
Effect rules — floating effects, missing context/error channels, Effect-native
replacements for fetch / console / Date / process.env, Schema checks —
use the official @effect/tsgo language
service. effect-analyzer does not reimplement them; it merges them in:
effect-analyze ./src --lint-source --tsgo=./tsconfig.jsonBare --tsgo uses tsconfig.json. When options come before the source path,
use --tsgo=path/to/tsconfig.json to provide an explicit project without
ambiguity.
@effect/tsgo is a required effect-analyzer dependency, so the bridge and its
platform binary are installed automatically. It resolves the target project’s
typescript package and selects the native artifact matching that exact
version (which is why the project needs TypeScript 7+). The CLI therefore uses
the same compiler generation and Effect language-service rules as the editor.
Inspect a single rule
Section titled “Inspect a single rule”effect-analyze --explain-rule runSync-on-asyncList or search the registry
Section titled “List or search the registry”effect-analyze --list-rules # full tableeffect-analyze --search-rules concurrency # text search across codes/titles/descriptionseffect-analyze --index-rules # searchable index entries (one per line)SARIF output
Section titled “SARIF output”Emit SARIF 2.1.0 — the standard format consumed by GitHub Code Scanning, Azure DevOps, SonarQube, and most static analysis tools.
effect-analyze ./src --lint-source --sarif -o findings.sarifUpload to GitHub Code Scanning in a workflow:
- name: Lint Effect source run: pnpm exec effect-analyze ./src --lint-source --sarif -o findings.sarif
- name: Upload SARIF uses: github/codeql-action/upload-sarif@v3 with: sarif_file: findings.sarifBaselines (CI gating)
Section titled “Baselines (CI gating)”To adopt the linter on a non-greenfield codebase, snapshot current findings as a baseline and fail CI only on new findings.
# 1. Snapshot onceeffect-analyze ./src --lint-source -o .cache/effect-lint-baseline.json
# 2. In CI, fail only on findings not in the baselineeffect-analyze ./src --lint-source \ --baseline .cache/effect-lint-baseline.json \ --fail-on-newThis is the recommended adoption path: get the workflow into CI today, then drive the baseline to zero over time.
Per-file scorecard
Section titled “Per-file scorecard”A compact {file → counts by severity} report for tracking lint debt by area:
effect-analyze ./src --lint-source --scorecardSuppression comments
Section titled “Suppression comments”Suppress a single line with an effect-analyzer-disable-next-line comment:
// effect-analyzer-disable-next-line runSync-on-async — initial bootstrap onlyEffect.runSync(initEffect)Tighten the policy in CI:
| Flag | Effect |
|---|---|
--require-suppression-reason |
Suppressions must include a — reason after the rule code |
--fail-on-stale-suppressions |
Suppressions that no longer suppress anything fail the build |
Bundle output for artifacts
Section titled “Bundle output for artifacts”Produce a deterministic artifact directory that contains diagnostics, SARIF, summary, the rule registry snapshot, and the session envelope:
effect-analyze ./src --lint-source --bundle-output ./.artifacts/effect-lintUseful for build attestation, debugging CI regressions, or feeding the bundle into a downstream agent.
Rule profiles
Section titled “Rule profiles”Switch the active rule set with --profile:
| Profile | Use |
|---|---|
strict |
All rules, including info-level |
ci |
Errors + warnings only |
migration |
Rules tuned for codebases being migrated to Effect |
docs |
Rules surfaced in published documentation |
effect-analyze ./src --lint-source --profile ci --fail-on-new --baseline .cache/baseline.jsonRelated
Section titled “Related”- Strict Diagnostics — IR-level diagnostics (separate from source lints)
- Improve Mode — auto-fix fixable lint findings
- CLI Reference — all source-linter flags in one table