@aurelienbbn/oxlint-plugin-effect

中文文档
0.8.1发布于 2026/10/14MITTypeScriptES Module构建来源已验证近 30 天下载 2109

Custom oxlint rules for Effect projects.

原始使用文档
文档目录7 节
  1. Upstream first, this plugin second
  2. What fires
  3. 1 autofix, 5 suggestions
  4. Migration
  5. Contract
  6. Registered contract inventory
  7. Credited concepts

@aurelienbbn/oxlint-plugin-effect

npm downloads CI license node

42 oxlint rules for Effect code: runtime boundaries, failure channels, concurrency, services, Schema. Only what @effect/tsgo doesn't own.

pnpm add -D @aurelienbbn/oxlint-plugin-effect oxlint   # oxlint >=1.82.0 <2.0.0
{
  "jsPlugins": ["@aurelienbbn/oxlint-plugin-effect"],
  "rules": {
    "effect/require-all-concurrency": "error",
    "effect/require-tagged-effect-fail": "error",
    "effect/no-run-promise-in-runtime": ["error", { "allow": ["**/src/main.ts"] }]
  }
}

No preset: enable each rule by name.

Upstream first, this plugin second

@effect/tsgo recommended oxlint preset   ← enable FIRST, wins every overlap (type-aware)
  floating Effects · missing yield* / return yield* · Effect-native globals & JSON
  Effect.fn opportunities · try/catch and timers in generators · class Self mismatch
        +
withEffectTsgoLayer extras               ← @aurelienbbn/oxlint-config; off in recommended
  any/unknown in error & requirements channels · unsafe Effect type assertions
  deterministic service/error keys · Effect.provide outside entry points
        │
        ▼
@aurelienbbn/oxlint-plugin-effect        ← policies and syntactic failure modes tsgo doesn't own

Wire tsgo with withEffectTsgoLayer from @aurelienbbn/oxlint-config: it adds the four extras and settles every overlap with this plugin in one table. Mind its version lock: @effect/tsgo 0.45.0 accepts only oxlint 1.81.0 / 1.82.0 and oxlint-tsgolint 7.0.2001.

13 rules removed in favor of tsgoOwner
no-floating-effect, no-plain-yield, require-return-on-failure-yield, prefer-effect-fn, no-raw-json-parse, no-raw-json-stringify, no-ambient-nondeterminism✅ recommended
no-effect-type-assertion, no-unsafe-error-channel, matching-identifier⚠️ only with withEffectTsgoLayer (or tsgo's correctness + style presets)
no-nested-layer-provide, no-cascading-layer-provide, use-root-imports❌ no exact owner

Type-dependent conclusions stay with @effect/tsgo. Ownership: docs/rule-ownership.md; the catalog gate enforces the inventory.

What fires

const values = Effect.all(programs); // ❌ require-all-concurrency
const values = Effect.all(programs, { concurrency: 1 }); // ✅ explicit choice

const program = Effect.fail("boom"); // ❌ require-tagged-effect-fail
const program = Effect.fail(new DomainError({ reason })); // ✅ tagged error

const load = (id: string) => Effect.fn(`users.load.${id}`)(body); // ❌ no-dynamic-span-name
const load = Effect.fn("users.load")(body); // ✅ ids go in span attributes

class NotFound extends Schema.TaggedError<NotFound>()("Missing", {}) {} // ❌ tagged-error-name, twice
class NotFoundError extends Schema.TaggedError<NotFoundError>()("NotFoundError", {}) {} // ✅ catchTag greps to the class

const Status = Schema.Literals(["Pending", "partially-refunded"]); // ❌ schema-literal-case, twice
const Status = Schema.Literals(["pending", "partially_refunded"]); // ✅ one case on the wire

// orders/domain/order.ts
export interface Order {
  readonly id: OrderId;
} // ❌ schema-domain-types
export const Order = Schema.Struct({ id: OrderId }); // ✅ the schema owns the shape
export type Order = typeof Order.Type;
const Concurrency = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(1))); // ❌ schema-check-branded
const Concurrency = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(1)), Schema.brand("Concurrency")); // ✅ only make or a decode builds one

const sync = Effect.fn("OrderSync.run")(body); // ❌ telemetry-name-format
const sync = Effect.fn("orders.sync")(body); // ✅ <area>.<operation>, lowercase snake_case

Effect.tryPromise(() => fetch(url)); // ❌ no-untyped-try-promise-catch, require-abort-signal
Effect.tryPromise({
  try: (signal) => fetch(url, { signal }),
  catch: (cause) => new HttpError({ cause }), // ✅ typed, cause kept, signal forwarded
});
  • Concurrency rules demand an explicit choice; they don't claim omitted concurrency is unbounded.
  • Same allow paths on no-run-promise-in-runtime and no-unscoped-runtime-launch: shared boundary matching.
  • tagged-error-name clashes with tsgo deterministic-keys only when tsgo's keyPatterns gain an error target: that target wants a package-qualified tag (pkg/file/NotFoundError). Keep errors out of keyPatterns, or turn this rule off.
  • schema-literal-case keeps Schema.Literals([...]) values in one case, snake_case by default: they travel as data (URLs, SQL, logs, wire contracts), and snake_case is what Postgres, Shopify REST (partially_refunded) and Stripe (requires_payment_method) use. Schema.Literal("..."), _tags and non-string literals are out of scope. No fix: renaming a wire value needs a coordinated change.
  • schema-domain-types makes a domain folder Schema-only: in **/domain/** (option files), an interface, an object type (bare, in Readonly<>, in a union or an intersection), a union of literals or an enum is reported; declare a Schema.Struct, Schema.TaggedStruct, Schema.Union or Schema.Literals and derive the type with typeof X.Type. Silent: generic declarations, declarations holding behavior (methods, function-typed members) or runtime handles (Schema.*, Effect.*, Layer.*, Stream.* members), unions of named types, and an empty interface that extends a schema type. No fix: the schema is the decision.
  • schema-check-branded puts a domain check in the type: a check runs only when a value is decoded, encoded or built with make, so without a brand code can still write { concurrency: 0 }. In **/domain/** (option files), a Schema.check(...) or .check(...) whose chain has no Schema.brand is reported. A wire DTO needs no brand (decoding and encoding both check it), so the rule stays out of other folders by default; built-in schemas used as they are (Schema.Int, Schema.NonEmptyString) are not checks it sees. A counter that only grows (attempts + 1) is the usual exception: suppress it there.
  • telemetry-name-format wants indexed names as <area>.<operation> in lowercase dotted snake_case (mcp.auth.verify_api_key): span names, Rpc.make tags, and the keys of annotateLogs, annotateSpans, annotateCurrentSpan and withSpan's attributes (one segment allowed: event). String literals only; no-dynamic-span-name owns runtime names.
  • telemetry-name-format with logMessages: true treats a log message as an event name: the first argument of Effect.log, logTrace, logDebug, logInfo, logWarning, logError and logFatal (through Effect, an alias, or a named import from effect/Effect) must be a literal in the name format, like Effect.logInfo("webhook.rejected", { attempt }), so log backends can search and count by it. A variable, a template with values or a concatenation is reported too: values go in later arguments or Effect.annotateLogs.
  • Enable telemetry-name-format or effect-fn-name-matches-binding, not both: the binding rule ties the last span segment to the binding (const syncAll = Effect.fn("orders.syncAll")), which snake_case rejects.
  • 🎨 dependencies-first, padding-after-dependencies, no-switch, prefer-match, prefer-effect-array-helpers, schema-type-adjacent, schema-literal-case, schema-domain-types, schema-check-branded, tagged-error-name, telemetry-name-format are opinionated. Suppress at the exceptional call site, not in the shared config.
44 rules by job
JobRules
🚨 failures (10)no-catch-all-cause, no-effect-ordie, no-swallowed-failure, no-untyped-try-promise-catch, preserve-thrown-cause, no-unsafe-error-mapper, require-tagged-effect-fail, no-effect-promise, bounded-retry, tagged-error-name 🎨
🚪 runtime boundaries (7)no-run-promise-in-runtime, no-unscoped-runtime-launch, prefer-run-main, no-fork-detach, no-managed-runtime-per-call, prefer-it-effect (opt-in, needs @effect/vitest), no-fake-timers-in-effect-tests
🧬 bodies & tracing (7)no-unsafe-effect-body, require-named-effect-fn, effect-fn-name-matches-binding, no-dynamic-span-name, telemetry-name-format 🎨, dependencies-first 🎨, padding-after-dependencies 🎨
🧩 services & layers (4)no-service-constructor-imports, no-service-dependency-parameters, no-service-option, no-static-service-forwarders
📐 Schema & config (6)no-schema-any, schema-type-adjacent 🎨, schema-literal-case 🎨, schema-domain-types 🎨, schema-check-branded 🎨, require-redacted-secret-config
⚡ concurrency (3)require-all-concurrency, require-for-each-concurrency, require-abort-signal
🎨 style (3)no-switch, prefer-match, prefer-effect-array-helpers
🔁 arrays (4)no-array-callback-reference, no-array-for-each, no-array-method-this-argument, no-array-sort: unicorn's array checks, skipping Effect modules (Option.some, Effect.forEach, Arr.sort)
Options and defaults

tests = **/*.{test,spec}.{ts,tsx}.

RuleOptionDefault
no-run-promise-in-runtimeallowtests, **/scripts/**
no-unscoped-runtime-launchallowtests, **/scripts/**
prefer-run-mainallowtests, **/scripts/**
no-managed-runtime-per-callallowtests, **/scripts/**
prefer-effect-array-helpersallowtests, **/scripts/**
ignoredObjectsArray, Arr, Effect, HashMap, HashSet, Match, Option, Record, Schedule, Schema, Stream
no-schema-anyallowtests, **/fixtures/**, **/scripts/**, tools/**
no-effect-ordieallow, allowedCalls[], []
no-effect-promiseallow[]
mode"all" · or "rejectable-only"
no-fork-detachallow[]
no-swallowed-failureallowInFinalizerstrue
prefer-it-effecttestFilestests
require-abort-signalabortableCalls["fetch"]
require-redacted-secret-configsecretPattern(SECRET|PASSWORD|PASSWD|TOKEN|API_?KEY|PRIVATE_?KEY|CREDENTIAL|DATABASE_URL|_DSN$)
benignPattern_(TTL|LENGTH|NAME|HEADER|URL_PREFIX|COUNT|ENABLED)$
effect-fn-name-matches-bindingignorePatternnone
no-service-constructor-importsserviceModulesnone (project-local sources always count)
no-service-dependency-parametersserviceTypeNames[]
tagged-error-namesuffix"Error"
schema-check-brandedfiles, allow["**/domain/**"], tests and **/scripts/**
schema-domain-typesfiles, allow["**/domain/**"], tests and **/scripts/**
schema-literal-casecase"snake" · or "kebab", "camel", "pascal"
telemetry-name-formatpattern^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$ (names)
minSegments2 (dot-separated segments a name needs)
keyPattern^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$ (annotation and attribute keys)
logMessagesfalse; true holds the first argument of Effect.log* to the name format

1 autofix, 5 suggestions

Only padding-after-dependencies has one behavior-preserving rewrite: it inserts the blank line below the leading service dependencies. Every other fix needs project knowledge: error type, runtime boundary, concurrency policy, layer owner, tracing name, platform adapter.

Effect.gen(function* () {
  const repo = yield* UserRepo;
  const user = yield* repo.findUser(id); // ❌ padding-after-dependencies
});

Effect.gen(function* () {
  const repo = yield* UserRepo;

  const user = yield* repo.findUser(id); // ✅ dependencies stand apart
});
The 5 editor suggestions
RuleSuggests
require-abort-signalforward the thunk's signal into fetch
effect-fn-name-matches-bindingrename the span to the binding name
no-fork-detachforkChild → forkScoped inside a Layer constructor
no-swallowed-failureadd { log: true } to Effect.ignore
require-redacted-secret-configConfig.String / Config.NonEmptyString → Config.Redacted

Migration

ChangeWhyNow
prefer-schema-decode-unknown removedEffect 4's typed decoders (decodeSync, decodeEffect, …) take S["Encoded"], so as unknown input fails typecheck; decodeEither no longer existseffecttsgo/prefer-schema-over-json reports JSON.parse, typescript/no-explicit-any reports as any. Drop the rule from your config
no-unsafe-effect-body keeps only throwtry/catch and global timers in generators are effecttsgo/try-catch-in-effect-gen and effecttsgo/global-timers-in-effect (both in recommended); Effect.gen rejects an async function* at typechecksame rule name, no config change. ⚠️ try/finally around yield* without catch has no verified tsgo owner
no-catch-all-cause, no-swallowed-failure widenedtsgo's catch-to-ignore / catch-to-or-else-succeed rewrites led to Effect.ignoreCause and Effect.orElseSucceed(() => placeholder), which passed silentlyEffect.ignoreCause, Effect.catchDefect report under no-catch-all-cause; a placeholder Effect.orElseSucceed under no-swallowed-failure

Contract

TopicContract
Effect versionexercised on 4.0.0-rc.115; matched names checked against 4.0.0, which moves effect/unstable/* to effect/* (both recognized). ⚠️ Effect 3/4 spellings not promised interchangeable
importsnamespace and aliased imports recognized; local shadows ignored
overlap@effect/tsgo's type-aware diagnostics win; settled in withEffectTsgoLayer

Registered contract inventory

Generated from package exports by pnpm catalog. Rule-specific options and limitations are described above and in the source tests.

Rule/checkTrigger or review scope
bounded-retryRequire Effect.retry policies visible in the same file to carry a bound such as Schedule.recurs, Schedule.upTo, Schedule.during, times, while, or until.
dependencies-firstYield service dependencies before runtime logic in Effect bodies.
effect-fn-name-matches-bindingRequire the last dot-segment of an Effect.fn span name to equal the variable or property the function is bound to.
no-array-callback-referenceDisallow passing a function reference to an array method's callback, skipping calls on Effect modules that are not arrays.
no-array-for-eachDisallow Array#forEach in favor of for…of, skipping Effect's forEach helpers (Effect.forEach).
no-array-method-this-argumentDisallow the thisArg of array methods, skipping Effect's data-first helpers (Arr.filter(xs, f)), whose second argument is the callback.
no-array-sortDisallow Array#sort, which mutates, in favor of toSorted, skipping Effect's sort helpers (Arr.sort), which do not.
no-catch-all-causeDisallow Effect.catchCause, catchCauseIf, catchCauseFilter, catchDefect, ignoreCause, sandbox, Layer.catchCause, and Effect 3 catchAllCause because they catch defects.
no-dynamic-span-nameDisallow template literals with runtime values and string concatenation as span names in Effect.fn, Effect.withSpan, withSpanScoped, useSpan, makeSpan, makeSpanScoped, Layer.withSpan, and Stream.withSpan.
no-effect-ordieDisallow Effect.orDie, Effect.orDieWith, Layer.orDie, and Effect.catch handlers that only die outside configured escape hatches.
no-effect-promiseDisallow Effect.promise outside configured files unless the thunk is a syntactically total promise such as Promise.resolve or a resolve-only timer.
no-fake-timers-in-effect-testsDisallow vi.useFakeTimers, vi.advanceTimers*, vi.run*Timers, and vi.setSystemTime in files importing @effect/vitest; use TestClock.
no-fork-detachDisallow Effect.forkDetach outside configured files and Effect.forkChild directly inside Layer constructors, where Effect.forkScoped ties the fiber to the layer scope.
no-managed-runtime-per-callDisallow ManagedRuntime.make inside function bodies outside configured files; build the runtime once at module scope.
no-run-promise-in-runtimeDisallow Effect.runPromise, runPromiseExit, and their run*With variants outside configured runtime boundaries.
no-schema-anyDisallow Schema.Any outside configured escape-hatch files.
no-service-constructor-importsDisallow make-prefixed imports from project service directories into runtime code.
no-service-dependency-parametersDisallow service projection types and configured service names in parameters.
no-service-optionDisallow Effect.serviceOption in favor of required services provided by layers.
no-static-service-forwardersDisallow static class properties that only forward to an Effect service method.
no-swallowed-failureDisallow Effect.ignore without a log option, and Effect.catch handlers or Effect.orElseSucceed fallbacks that discard the error for a placeholder.
no-switchDisallow switch statements in Effect code in favor of Match.
no-unsafe-effect-bodyDisallow throw inside Effect.gen, Effect.fn, and Effect.fnUntraced bodies.
no-unsafe-error-mapperDisallow unknown and any in Effect error mapper parameters.
no-unscoped-runtime-launchDisallow Effect.runFork, runSync, runSyncExit, runCallback, their run*With variants, and Layer.launch outside configured runtime boundaries.
no-untyped-try-promise-catchRequire Effect.try and Effect.tryPromise to map thrown or rejected values with a catch handler.
padding-after-dependenciesRequire a blank line after the leading service dependencies of an Effect.gen, Effect.fn, or Effect.fnUntraced generator body when logic follows them.
prefer-effect-array-helpersPrefer Effect array helpers over native array helper methods.
prefer-it-effectPrefer it.effect from @effect/vitest over Effect.runPromise or Effect.runSync inside plain it/test callbacks in test files (opt-in; requires @effect/vitest).
prefer-matchPrefer Match from effect over chained literal ternaries.
prefer-run-mainPrefer NodeRuntime.runMain or BunRuntime.runMain over a module top-level Effect.runPromise or Effect.runFork statement.
preserve-thrown-causeRequire Effect.try and Effect.tryPromise catch mappers to use the thrown value they receive.
require-abort-signalRequire Effect.tryPromise and Effect.promise thunks that call fetch (or configured abortable calls) to accept the AbortSignal parameter and pass it on.
require-all-concurrencyRequire explicit concurrency for Effect.all.
require-for-each-concurrencyRequire explicit concurrency for Effect.forEach.
require-named-effect-fnRequire Effect.fn calls to include a non-empty name.
require-redacted-secret-configRequire Config.Redacted instead of Config.String or Config.NonEmptyString for configuration keys whose name looks like a secret.
require-tagged-effect-failRequire tagged error values for Effect.fail and Effect.failSync, rejecting literals, native Errors, and same-file untagged Error subclasses.
schema-check-brandedRequire a schema that adds a check in a domain folder to be branded, so code builds its values through make, which runs the check.
schema-domain-typesRequire domain types to be declared as Schemas, with the TypeScript type derived from them, instead of interfaces, object types, literal unions or enums.
schema-literal-caseRequire the string values of Schema.Literals([...]) to follow one case: snake_case by default, or kebab, camel, or pascal.
schema-type-adjacentKeep a Schema's matching type alias adjacent, allowing whitespace and JSDoc.
tagged-error-nameRequire classes extending Schema.TaggedError, Schema.TaggedErrorClass, or Data.TaggedError to end with the error suffix and to use their class name as the literal _tag.
telemetry-name-formatRequire literal span names, Rpc.make tags and (with logMessages) Effect log event names in lowercase dotted snake_case with at least two segments, and annotation keys in lowercase dotted snake_case.

Credited concepts

  • Effect bundled AGENTS.md "The name string should match the function name" (concept)
  • Effect bundled ai-docs "capped exponential backoff with jitter and max attempts" pattern (concept)
  • Effect bundled ai-docs 04_integration/10_managed-runtime.ts module-level runtime (concept)
  • Effect bundled ai-docs 09_testing/10_effect-tests.ts "controls time with TestClock" (concept)
  • Effect bundled ai-docs catch: (cause) => new X({ cause }) convention (concept)
  • OpenTelemetry Tracing API specification, span name guidance "most general string ... low cardinality" (Apache-2.0, concept)
  • ai-automation by Sandro Maglione (inspiration, independently re-implemented)
  • anti-slop by Dillon Mulroy (MIT, concept re-implemented)
  • eslint-plugin-unicorn no-array-callback-reference (MIT; rule concept, independently re-implemented)
  • eslint-plugin-unicorn no-array-for-each (MIT; rule concept, independently re-implemented)
  • eslint-plugin-unicorn no-array-method-this-argument (MIT; rule concept, independently re-implemented)
  • eslint-plugin-unicorn no-array-sort (MIT; rule concept, independently re-implemented)
  • executor by Rhys Sullivan, dotted snake_case span and attribute names such as mcp.auth.verify_api_key (MIT, naming-style inspiration)

版本与发布记录

@aurelienbbn/oxlint-plugin-effect 共发布 7 个版本,首次发布于 2026/09/25;过去 12 个月发布了 7 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.8.1latest2026/10/1424 天前
0.7.02026/10/011 个月前
0.6.02026/09/301 个月前
0.5.02026/09/301 个月前
0.3.02026/09/291 个月前
0.2.02026/09/251 个月前

关于 @aurelienbbn/oxlint-plugin-effect 的常见问题

@aurelienbbn/oxlint-plugin-effect 是什么?

Custom oxlint rules for Effect projects.

如何安装 @aurelienbbn/oxlint-plugin-effect?

在项目目录运行 npm install @aurelienbbn/oxlint-plugin-effect,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:oxlint(>=1.82.0 <2.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@aurelienbbn/oxlint-plugin-effect 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@aurelienbbn/oxlint-plugin-effect 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@aurelienbbn/oxlint-plugin-effect 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js ^22.19.0 || ^24.11.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@aurelienbbn/oxlint-plugin-effect 可以商用吗?

@aurelienbbn/oxlint-plugin-effect 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@aurelienbbn/oxlint-plugin-effect 的下载量有多少?

根据 npm 官方下载统计,@aurelienbbn/oxlint-plugin-effect 在 2026/10/07 – 2026/11/05 这 30 天内被下载 2,109 次,日均约 70 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@aurelienbbn/oxlint-plugin-effect 还在维护吗?

最新版本 0.8.1 发布于 2026/10/14(24 天前);至今共发布 7 个版本,其中过去 12 个月发布了 7 个;首次发布于 2026/09/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。