Skip to content

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.

Terminal window
effect-analyze ./src --lint-source

Eighteen rules covering the most common Effect anti-patterns surfaced in EffectPatterns, effect-ts/examples, and real-world reviews:

CodeSeverityCatches
detached-fiber-in-testwarningEffect.runFork(Effect.never) in tests — fiber outlives test scope
effect-fail-untaggedwarningEffect.fail(new Error("…")) — degrades the typed error channel
empty-effect-allinfoEffect.all([]) — dead or placeholder code
forEach-without-concurrencyinfoEffect.forEach(items, f) without an explicit concurrency option
identity-catchwarningEffect.catchAll(e, (e) => Effect.fail(e)) — no-op handler
live-layer-in-testwarningLive layer references in test files where test layers are expected
mutable-in-concurrentwarninglet/var mutation inside parallel / fork / race contexts
nondeterministic-test-apiwarningDate.now() / new Date() / Math.random() in test code
promise-api-in-genwarningPromise.all / Promise.race inside Effect.gen
raw-side-effect-in-genwarningfetch / process.env / setTimeout / new Promise in generator bodies
run-effect-in-genwarningEffect.runPromise / runSync inside Effect.gen — should be yield*
runPromise-then-chaininfo.then / .catch after Effect.runPromise
runSync-on-asyncerrorEffect.runSync on an async-tainted effect — throws at runtime
runSyncExit-on-asyncerrorEffect.runSyncExit on an async-tainted effect
schedule-unboundedwarningSchedule.forever and friends without visible bounds
sleep-without-testclockinfoReal-time Effect.sleep in tests without TestClock
unsafe-api-usagewarningEffect.unsafe* APIs bypassing runtime safety
untagged-throwwarningthrow new Error(...) inside an Effect context

Every finding carries:

  • rule code + severity
  • source span (file, line, column, end line, end column)
  • docs.effect.website link for the rule
  • Bad → Good example snippet pulled from the rule registry
Terminal window
effect-analyze --explain-rule runSync-on-async
Terminal window
effect-analyze --list-rules # full table
effect-analyze --search-rules concurrency # text search across codes/titles/descriptions
effect-analyze --index-rules # searchable index entries (one per line)

Emit SARIF 2.1.0 — the standard format consumed by GitHub Code Scanning, Azure DevOps, SonarQube, and most static analysis tools.

Terminal window
effect-analyze ./src --lint-source --sarif -o findings.sarif

Upload 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.sarif

To adopt the linter on a non-greenfield codebase, snapshot current findings as a baseline and fail CI only on new findings.

Terminal window
# 1. Snapshot once
effect-analyze ./src --lint-source -o .cache/effect-lint-baseline.json
# 2. In CI, fail only on findings not in the baseline
effect-analyze ./src --lint-source \
--baseline .cache/effect-lint-baseline.json \
--fail-on-new

This is the recommended adoption path: get the workflow into CI today, then drive the baseline to zero over time.

A compact {file → counts by severity} report for tracking lint debt by area:

Terminal window
effect-analyze ./src --lint-source --scorecard

Suppress a single line with an effect-lint-disable-next-line comment:

// effect-lint-disable-next-line runSync-on-async — initial bootstrap only
Effect.runSync(initEffect)

Tighten the policy in CI:

FlagEffect
--require-suppression-reasonSuppressions must include a — reason after the rule code
--fail-on-stale-suppressionsSuppressions that no longer suppress anything fail the build

Produce a deterministic artifact directory that contains diagnostics, SARIF, summary, the rule registry snapshot, and the session envelope:

Terminal window
effect-analyze ./src --lint-source --bundle-output ./.artifacts/effect-lint

Useful for build attestation, debugging CI regressions, or feeding the bundle into a downstream agent.

Switch the active rule set with --profile:

ProfileUse
strictAll rules, including info-level
ciErrors + warnings only
migrationRules tuned for codebases being migrated to Effect
docsRules surfaced in published documentation
Terminal window
effect-analyze ./src --lint-source --profile ci --fail-on-new --baseline .cache/baseline.json