@enfect/oxlint (英语)
有意见 奥克林特 用于 效果 v4 驱动每个模块向Effect服务,打出出错误通道,以及功能组成. 它会标出必须的图案,原始节点 API,未打字出错, 以及其他非平面外形 在林特层,所以它们永远不会被审查.
插件飞船 74条 名称间距 effect/,包括来自 dmmulroy/anti-slop. 规则与 effect-oxlint SDK并运行为标准的克克林特自定义规则.
运行时间和建议目标 效果 4.1.0(稳定)区域进口用途
effect/http, (中文(简体) ), effect/http-api, (中文(简体) ), effect/process等; 旧的 unstable 路径
被删除。 已标记 API @stability unstable 微量释放仍然可以改变.
在同一发行版本上保留直接使用的效果-家庭包.
已公布的插件被检查为oxlint 1.85. 这是其中的一部分 感染,该出版物还出版 克劳德代码和OpenCode的插件.
安装
npm install --save-dev oxlint @enfect/oxlint
# or
bun add -D oxlint @enfect/oxlint
TypeScript来源也作为
@enfect/oxlint。 。 。 .
使用生成的推荐配置 oxlint.config.ts数字 :
import { defineConfig } from 'oxlint';
import effect from '@enfect/oxlint';
export default defineConfig({
extends: [effect.configs.recommended]
});
configs.recommended 通过克林特记录软件包 jsPlugins 并启用所有74条规则 error 严重. configs.all 也包括所有规则.
要推翻个别规则,请添加 rules 条目 extends 块 :
import { defineConfig } from 'oxlint';
import effect from '@enfect/oxlint';
export default defineConfig({
extends: [effect.configs.recommended],
rules: {
'effect/avoid-native-object-helpers': 'off',
'effect/avoid-direct-json': 'warn'
}
});
如果您使用 .oxlintrc.json,oxlint 无法导入软件包配置对象。 配置 JS 插件和您想要的任何规则 :
{
"jsPlugins": ["@enfect/oxlint"],
"rules": {
"effect/avoid-direct-json": "error"
}
}
使用 oxlint.config.ts 当您想要完整生成推荐配置时.
规则一览
反斜坡规则
这些规则适用于JavaScript和TypeScript,独立于Effect导入,
建筑政策除外 no-service-constructor-imports。 。 。 .
他们是同一个人 effect/ 命名空间和同时生成预设 .
| 规则 | 渔获物/例外 |
|---|---|
no-chained-type-assertions | 嵌入 as 或角度-括号断言,包括括号式链条. 仅由 as const 被允许. |
no-conditional-empty-object-spread | 以有条件的空对象分支展开对象。 无自定义 : 省略一个属性与指定不同 undefined。 。 。 . |
no-known-value-widening | 已知的表达式流出到显式 unknown, (中文(简体) ), object,匿名对象,或开放词典目标,包括已知的传递给本地的参数 unknown 类型上游。 空词典累积器和有限键 Record 目标被允许. |
no-module-mocking | 测试/测试 mock, (中文(简体) ), doMock,以及 unstable_mockModule 调用,包括别名进口和计算方法名称。 允许本地阴影框架名称 . |
no-object-parameters | object 输入、含有此种内容的联盟以及范围大/透明的通用别名. |
no-reflect-apply | 全球 Reflect.apply,包括计算访问。 本地阴影 Reflect 约束是允许的. |
no-reflect-get | 全球 Reflect.get,包括计算访问。 本地阴影 Reflect 约束是允许的. |
no-runtime-typeof | 运行时间 typeof 缩小。 与字符串的比较 "undefined" ; 可用选项启用类型保护检查。 类型级别 typeof 不受影响. |
no-service-constructor-imports | 名称 make<CapitalizedName> 从 ./ 或者说 ../ 外部模块 *.test.* 财务报告和审定财务报表 *.spec.*包/路由别名的进口、默认进口和静态构造器不在它的范围内. |
no-shape-in-symbol-names | 不区分大小写 shape 在本地拥有的识别资料中,包括私人和JSX名称。 静态成员访问诸如 schema.shape 允许. |
no-unknown-parameters | 明确 unknown 参数和包含它的结合。 命名的参数 cause 并且允许精确的 类型上游对象. |
no-unknown-returns | 向 unknown, (中文(简体) ), Promise<unknown>,或 PromiseLike<unknown>,包括范围/透明通用别名. |
no-unknown-type-aliases | 范围/透明通用别名 unknown。 。 。 . |
no-unsafe-dictionary-type | 基于 unknown, (中文(简体) ), any, (中文(简体) ), object, (中文(简体) ), {},或语义等同. 一般性制约因素,例如: T extends Record<string, unknown> 被允许. |
no-widen-then-assert | 无法改变的当地流动,将已知证据扩大到 unknown, (中文(简体) ), any, (中文(简体) ), object或广义的记录,然后将它恢复到较窄的类型. |
require-safety-comment-for-type-assertion | 无前有后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无后无. 默认标记是 SAFETY:。 。 。 . |
分析使用了 Oxlint 的 ESTree 和 lexical 范围, 而非 TypeScript 类型
检查器。 同一文件的别名、前置参考、语法阴影和
透明通用别名在有文件记录的情况下解决。 进口类型和
交叉文件调用签名不推断. no-widen-then-assert 蓄意
限制其值流分析,使其在函数边界内具有不可改变的约束.
替换反斜体安装
删除旧的 anti-slop 财务报告和审定财务报表 anti-slop-effect 登记日期: jsPlugins。 。 。 .
将两个规则名前缀替换为 effect/,保持现有严重程度
选项值。 登记册 @enfect/oxlint 一次,或使用
上面推荐的配置,以启用整个个人规则。 没有
运行时间依赖反斜坡,没有单独的效应入站点进行注册.
例如,现有的定制配置变成:
import { defineConfig } from 'oxlint';
import effect from '@enfect/oxlint';
export default defineConfig({
extends: [effect.configs.recommended],
rules: {
'effect/no-runtime-typeof': ['error', { allowInTypeGuards: true }],
'effect/require-safety-comment-for-type-assertion': [
'error',
{ markers: ['INVARIANT', 'SAFETY'] }
]
}
});
allowInTypeGuards 默认为 false 并允许在类型范围内直接进行检查
上位/上位函数,不是嵌入普通函数. markers
默认为 ['SAFETY']; 标记为字面字符串( regex puntuation 为
),在相配前被修剪后,必须随行 : 和一无所有
说明理由。 确认出口申报前的评论;评论
未经证实.
// Rejected: assertions fabricate evidence in two steps.
const user = input as unknown as User;
// Rejected: the known "start" key is erased.
const handlers: Record<string, Handler> = { start: startHandler };
// Preserve known keys while checking their values.
const preciseHandlers = { start: startHandler } satisfies Record<string, Handler>;
// Accepted by require-safety-comment-for-type-assertion:
// SAFETY: parseUserId checked the identifier before branding it.
const userId = parsedId as UserId;
一些现行规则有意更严格或重叠. casting-awareness
即使有安全评论,仍会报告断言;如果有正当理由,将关闭
断言是你的选择政策. prefer-effect-is 仍然可以报告 typeof
内部守卫,即使 no-runtime-typeof 允许他们. avoid-any 重叠
以连锁说法 avoid-object-type 地址 Object编号 :{} 相当
比小写 object 参数合同。 各独立配置.
尾风规则
| 规则 | 渔获物/例外 |
|---|---|
no-unsupported-tailwind-spacing | 尾风 v4 间隔类, 以字符串和模板字元表示, 其数值不等于0. 25 的相克倍数, 例如 gap-1.125 或者说 p-1.50尾风不会给他们带来CSS,所以他们默默无闻。 消息建议在默认的0.25rem步骤上具有等同的任意值. 允许将任意值、分数和符号粘贴在模板插入上. |
覆盖的公用事业:铺垫、边距及其滚动变体; gap· ; space-x编号 :space-y· ;
w, (中文(简体) ), h, (中文(简体) ), size,以及他们的 min-编号 :max- 表单; inset 编辑 top编号 :right编号 :bottom编号 :left编号 :start编号 :end (a) 减少员额经费;
translate-x编号 :-y编号 :-z· ; indent· ; basis· ; leading; 和 : border-spacing备选案文前缀,
负值,以及正向或后向 ! 已确认.
第一效力规则
| 规则 | 捕获到的 |
|---|---|
avoid-any | as any 财务报告和审定财务报表 as unknown as T 铸造 |
avoid-data-tagged-error | Data.TaggedError ——使用 Schema.TaggedError |
avoid-direct-json | JSON.parse 编号 : JSON.stringify ——使用 Schema.fromJsonString |
avoid-direct-tag-checks | x._tag === "..." 检查 - 使用 $is 编号 : $match 编号 : Match |
avoid-expect-in-if | expect(...) 窝在里面 if 测试中的块 |
avoid-mutable-state | let 服务/层工厂内部装订 |
avoid-native-fetch | 土著 fetch() - 使用效果 HttpClient |
avoid-native-object-helpers | Object.keys 编号 : Object.entries 编号 : Object.fromEntries 包括:. |
avoid-node-imports | 裸体 node:* 以平台不可识别代码导入 |
avoid-non-null-assertion | 这个 ! 非虚拟断言运算符 |
avoid-object-type | Object 财务报告和审定财务报表 {} 作为类型 |
avoid-option-getorthrow | .getOrThrow 打开 Option 编号 : Either 编号 : Result |
avoid-platform-coupling | @effect/platform-bun 装订包中的进口 |
avoid-process-env | process.env - 使用一个 Config 服务 |
avoid-react-hooks | useState 编号 : useEffect 编号 : useReducer - 使用 Effect Atom VM |
avoid-schema-suffix | Schema 常数后缀为 Schema |
avoid-sync-fs | fs.readFileSync 和其他同步 fs 电话 |
avoid-try-catch | try 编号 : catch 效果代码 |
avoid-ts-ignore | @ts-ignore 编号 : @ts-expect-error 评论 |
avoid-untagged-errors | new Error(...) 财务报告和审定财务报表 instanceof Error 可收回的失败 |
avoid-yield-ref | yield* ref 编号 : yield* deferred 编号 : yield* fiber (在v4中删除) |
casting-awareness | as T 断言(不包括 as const 编号 : as never(中文(简体) ) |
context-tag-extends | Context.Tag 编号 : Context.GenericTag 编号 : Effect.Service 遗产 ServiceMap.* |
effect-catchall-default | 块 Effect.catch 编号 : catchCause 吞下所有错误 |
effect-promise-vs-trypromise | Effect.promise - 喜欢 Effect.tryPromise |
effect-run-in-body | Effect.runSync 编号 : runPromise 编号 : runFork 外部切入点 |
imperative-loops | for 编号 : while 编号 : do…while 域码 |
maybe-prefix-requires-option | maybe* 名称字段不是 Option<T> |
no-barrel-imports | 命名自 effect 桶套装 |
no-cause-in-http-response | Cause.pretty编号 :Cause.squash/发生原因或出错流入反应机构 |
no-effect-ignore-then-as | 冗余 Effect.ignore 在此之前 Effect.as,或者在已知的不可失的原始人上 |
no-length-comparison | .length === 0 和朋友——使用命名的字符串/阵列上游 |
no-opaque-instance-fields | 诉讼成员 Schema.Opaque 类 |
prefer-arr-match | 手动空/ 非空分枝—— 使用 Arr.match |
prefer-arr-sort | Array.prototype.sort ——使用 Arr.sort 带有 Order |
prefer-array-fromoption-over-option-match-empty | Option.match 产生 [] 编号 : [v] |
prefer-duration-constructors | 原始毫秒数字传递到效果计时 API |
prefer-effect-fn | Effect.gen 捆绑在接地上或用于服务方法 |
prefer-effect-is | typeof x === "string" ——使用 P.isString 朋友们 |
prefer-match-over-switch | switch 语句——使用 Match.value |
prefer-namespace-imports | 从 Effect 子模块命名的进口 – 使用命名空间导入 |
prefer-option-over-null | T | null 编号 : T | undefined 工会类型 |
prefer-redacted-config | Config.String("apiKey") 密钥等 |
require-effect-concurrency | Effect.all 编号 : forEach 编号 : validate 没有明确 concurrency |
require-filter-metadata | Schema.makeFilter 编号 : makeFilterGroup 缺少标识符/ 标题/ 描述 |
require-is-prefix-for-boolean-schema-field | Schema.Boolean 字段没有布尔上游前缀 |
require-schema-type-alias | 不匹配导出计划常数 export type 别名 |
throw-in-effect-gen | throw 内部 Effect.gen 编号 : Effect.fn 编号 : Effect.fnUntraced |
use-clock-service | new Date() 编号 : Date.now() 编号 : Date.UTC() ——使用 Clock 编号 : DateTime |
use-command-executor-service | child_process 编号 : node:child_process 进口 |
use-console-service | console.* ——使用 Effect.log* 编号 : Console |
use-filesystem-service | fs 编号 : node:fs 编号 : fs/promises 进口 |
use-http-client-service | http 编号 : https 编号 : node:http 编号 : node:https 进口 |
use-path-service | path 编号 : node:path 进口 |
use-random-service | Math.random() - 使用 Random 服务 |
use-temp-file-scoped | os.tmpdir() / 无范围 makeTempFile 编号 : makeTempDirectory |
vm-in-wrong-file | 查看外部的模型接口和层 .vm.ts 文件 |
yield-in-for-loop | yield* 内部 for 循环——使用 Effect.forEach |
avoid-any
as any 财务报告和审定财务报表 as unknown as T 清除类型安全。 验证未知数据 Schema.decodeUnknown*,用通称保存类型,或固定上游类型.
// ❌
const user = data as any;
const config = JSON.parse(raw) as unknown as Config;
// ✅
const user = yield* Schema.decodeUnknownEffect(User)(data);
const config = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(AppConfig))(raw);
avoid-data-tagged-error
Data.TaggedError 不与 Schema 编码/ 解码管道合并。 使用 Schema.TaggedError 用于需要解码、编码或RPC运输的公共错误.
// ❌
class NotFound extends Data.TaggedError('NotFound')<{ id: string }> {}
import * as Schema from 'effect/Schema';
class NotFound extends Schema.TaggedError<NotFound>()(
'NotFound',
{ id: Schema.String }
) {}
avoid-direct-json
JSON.parse 生产 any· ; JSON.stringify 不验证域名合同。 使用 Schema.fromJsonString(MySchema) 键入边框或 Schema.fromJsonString(Schema.Unknown) 对于未知的有效载荷.
// ❌
const user: User = JSON.parse(raw);
const body = JSON.stringify(payload);
import * as Schema from 'effect/Schema';
class User extends Schema.Class<User>('User')({ id: Schema.String }) {}
const decodeUser = Schema.decodeUnknownEffect(Schema.fromJsonString(User));
const encodeUser = Schema.encodeEffect(Schema.fromJsonString(User));
const decodeUnknownJson = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown));
avoid-direct-tag-checks
阅读 _tag 直接的情侣将网站呼叫歧视的弦。 使用自动生成 $is 编号 : $match 帮助者或 Match.value 因此重命名一个变体是一个被打入重构的.
// ❌
if (result._tag === 'Success') return result.value;
switch (msg._tag) {
case 'Loaded':
/* ... */
}
// ✅
if (Result.isSuccess(result)) return result.success;
return Match.value(msg).pipe(
Match.tag('Loaded', (m) => /* ... */),
Match.exhaustive
);
avoid-expect-in-if
expect(...) 嵌入一个 if 当条件不实时, 屏蔽默默通过。 紧紧的先说.
// ❌
if (result) {
expect(result.id).toBe('abc');
}
// ✅
expect(result).toBeDefined();
expect(result.id).toBe('abc');
avoid-mutable-state
let 服务或层工厂内部的束缚 隐藏纤维可见状态和生命周期行为。 使用 Ref, (中文(简体) ), SynchronizedRef,或 Effect.cached 因此,并行访问是明确的. let 纯洁的助手和狭长的瞄准镜是好的.
// ❌
export const CounterLive = Layer.effect(
Counter,
Effect.gen(function* () {
let count = 0;
return Counter.of({ inc: () => Effect.sync(() => count++) });
})
);
// ✅
export const CounterLive = Layer.effect(
Counter,
Effect.gen(function* () {
const count = yield* Ref.make(0);
return Counter.of({ inc: () => Ref.update(count, (n) => n + 1) });
})
);
avoid-native-fetch
土著 fetch() 返回 a Promise<Response> 带有未类型错误。 效应 HttpClient 给您输入错误, 请求/ 响应计划, 以及可测试的层替换 .
// ❌
const res = await fetch('/api/users');
const users = await res.json();
// ✅
const client = yield* HttpClient.HttpClient;
const users =
yield* client
.get('/api/users')
.pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(UserList)));
avoid-native-object-helpers
Object.keys 返回时 string[] (无) keyof T(三); Object.entries 丢失值类型。 使用 effect/Record 类型安全等同物的助手.
// ❌
const keys = Object.keys(user);
const entries = Object.entries(config);
const obj = Object.fromEntries(pairs);
// ✅
import * as R from 'effect/Record';
const keys = R.keys(user);
const entries = R.toEntries(config);
const obj = R.fromEntries(pairs);
avoid-node-imports
node:* 导入将域码绑定为单个运行时间。 使用抽象服务 effect 并提供运行时边界的平台执行。 专门规则涵盖最常见的案件use-filesystem-service, (中文(简体) ), use-path-service, (中文(简体) ), use-command-executor-service, (中文(简体) ), use-http-client-service)),这个规则是"包罗万象".
// ❌
import { createHash } from 'node:crypto';
import { Readable } from 'node:stream';
// ✅ Provide the platform layer at the runtime boundary
import { Digest, SecureRandom } from 'effect/crypto';
import * as Stream from 'effect/Stream';
avoid-non-null-assertion
! 告诉编译器"相信我",错误时会崩溃. 模式缺席 Option,解码未知形状通过 Schema.decodeUnknown*,或与 ?. 编号 : ?? 编号 : Option.fromNullishOr。 。 。 .
// ❌
const name = user!.profile!.displayName!;
// ✅
const name = Option.fromNullishOr(user).pipe(
Option.flatMapNullishOr((u) => u.profile?.displayName),
Option.getOrElse(() => 'Anonymous')
);
avoid-object-type
Object 不提供类型安全,以及 {} 匹配任何非nullish 值(包括 42 财务报告和审定财务报表 "hi") (中文(简体) ). 使用特定的接口, Record<string, unknown>,或一个 Schema.Class。 。 。 .
// ❌
function merge(a: object, b: {}): object { ... }
// ✅
function merge<A extends Record<string, unknown>>(a: A, b: Partial<A>): A { ... }
avoid-option-getorthrow
.getOrThrow 击败点 Option 编号 : Either 编号 : Result 扔到那个类型 承诺一个完全处理者。 使用 match, (中文(简体) ), getOrElse,或 map。 。 。 .
// ❌
const value = Option.getOrThrow(maybeUser);
// ✅
const value = Option.match(maybeUser, {
onNone: () => defaultUser,
onSome: (u) => u
});
avoid-platform-coupling
软件包 packages/*/binding/ 使用抽象服务来包裹外部系统. 此规则拒绝混凝土 Bun平台在那里的导入;在应用程序的运行时间切入点提供平台层.
// ❌ packages/myapp/binding/index.ts
import { BunHttpServer } from '@effect/platform-bun';
// ✅ packages/myapp/src/main.ts
import { BunHttpServer } from '@effect/platform-bun';
avoid-process-env
process.env 是无类型、未经测试和全球性的. Config.* 以默认值和验证来构建打字,分层,可编辑的配置.
// ❌
const apiKey = process.env.API_KEY!;
const port = parseInt(process.env.PORT ?? '3000');
// ✅
const apiKey = yield* Config.Redacted('API_KEY');
const port = yield* Config.Int('PORT').pipe(Config.withDefault(3000));
avoid-react-hooks
反应钩会分散状态、效果和渲染 跨越单个部件 具有效应原子的VMs保持原子状态,作用,组件作为纯成像器.
// ❌
function Profile({ id }: Props) {
const [user, setUser] = useState<User>();
useEffect(() => { fetchUser(id).then(setUser); }, [id]);
return <div>{user?.name}</div>;
}
// ✅
// profile.vm.ts
export const userAtom = Atom.family((id: string) =>
Atom.fn(Effect.fn('fetchUser')(function* () { ... }))
);
// profile.tsx
function Profile({ id }: Props) {
const user = useAtomValue(userAtom(id));
return <div>{user.name}</div>;
}
avoid-schema-suffix
Schema常数代表一个域类型,而不是"一个类型的一个计划". 以概念命名( E)User而不是建筑(UserSchema)——这与如何 Schema.Class 名称并保留类型和实例.
// ❌
const UserSchema = Schema.Struct({ id: Schema.String });
// ✅
const User = Schema.Struct({ id: Schema.String });
export type User = typeof User.Type;
avoid-sync-fs
同步 fs 呼叫屏蔽事件循环。 使用 FileSystem 从 effect/FileSystem 用于ASync,可相容,可测试的文件一/O.
// ❌
const text = fs.readFileSync(path, 'utf8');
fs.writeFileSync(path, data);
// ✅
const fs = yield* FileSystem.FileSystem;
const text = yield* fs.readFileString(path);
yield* fs.writeFileString(path, data);
avoid-try-catch
try 编号 : catch 不显示函数返回类型的失败。 使用 Effect.try 或者说 Effect.tryPromise 与 Schema.TaggedError 以保留输入频道中的预期错误.
// ❌
try {
return JSON.parse(raw);
} catch (e) {
return null;
}
// ✅
return (
yield* Effect.try({
try: () => externalParser(raw),
catch: (cause) => new ParseFailed({ cause })
})
);
avoid-ts-ignore
@ts-ignore 财务报告和审定财务报表 @ts-expect-error 遮盖真实的bug,当基础类型改变时会静静地腐烂. 在源头修正类型.
// ❌
// @ts-ignore
const result = someApi.experimental.method();
// ✅
// Augment the third-party type or wrap in a typed adapter
declare module 'some-api' {
interface ExperimentalApi {
method(): Result;
}
}
avoid-untagged-errors
Schema.TaggedError 给每个域失败模式一个标记 catchTag 编号 : catchTags 可在类型层面加以歧视。 使用 Schema.isSchemaError 用于解码器故障; 诸如 makeEffect 直接失败 SchemaIssue.Issue。 。 。 .
// ❌
throw new Error('User not found');
if (err instanceof Error) return null;
// ✅
class UserNotFound extends Schema.TaggedError<UserNotFound>()(
'UserNotFound', { id: Schema.String }
) {}
yield* Effect.fail(new UserNotFound({ id }));
yield* effect.pipe(Effect.catchTag('UserNotFound', () => Effect.succeed(null)));
avoid-yield-ref
直接 yield* ref 编号 : yield* deferred 编号 : yield* fiber 编号 : yield* latch 在Effect v4中被删除. 使用明确的方法调用.
// ❌
const value = yield* counter;
const result = yield* deferred;
// ✅
const value = yield* Ref.get(counter);
const result = yield* Deferred.await(deferred);
const exit = yield* Fiber.join(fiber);
yield* Latch.await(latch);
casting-awareness
每个 as T 断言是检查点:铸造是否多余? 通用软件或 Schema.decodeUnknownEffect 换掉? 上游型号需要修理吗? 规则豁免 as const 财务报告和审定财务报表 as never;这种豁免并不表示没有限制的断言.
// ❌
const items = (data as Array<User>).filter((u) => u.active);
// ✅
const items =
yield* Schema.decodeUnknownEffect(Schema.Array(User))(data).pipe(
Effect.map(Arr.filter((u) => u.active))
);
// ✅ as const is fine
const STATUSES = ['Pending', 'Active', 'Closed'] as const;
context-tag-extends
class FooTag extends Context.Tag(...), (中文(简体) ), Context.GenericTag, (中文(简体) ), Effect.Service和遗产 ServiceMap.* 在Effect v4中别名全部被删除或被取代. 定义服务 Context.Service 并直接命名它们——否 *Tag 后缀.
// ❌
class UserRepoTag extends Context.Tag('UserRepo')<UserRepoTag, Service>() {}
const UserRepo = Context.GenericTag<Service>('UserRepo');
class UserRepo extends Effect.Service<Service>()('UserRepo', { ... }) {}
// ✅
class UserRepo extends Context.Service<UserRepo, Service>()('UserRepo') {}
effect-catchall-default
块 Effect.catch 编号 : Effect.catchCause 返回默认值会悄悄地吞入每一个失败模式——包括那些你不知道的模式. 使用 catchTag 编号 : catchTags 目标恢复.
// ❌
effect.pipe(Effect.catch(() => Effect.succeed(defaultUser)));
// ✅
effect.pipe(
Effect.catchTags({
UserNotFound: () => Effect.succeed(defaultUser),
NetworkError: (e) => Effect.fail(e) // re-raise the rest
})
);
effect-promise-vs-trypromise
Effect.promise 将拒绝视为输入出错通道以外的缺陷。 缺陷/因为处理者仍然可以观察到它们. 使用 Effect.tryPromise 对于预期的拒绝,所以呼叫者可以用 catchTag。 。 。 .
// ❌
const user = yield* Effect.promise(() => fetchUser(id));
// ✅
const user =
yield* Effect.tryPromise({
try: () => fetchUser(id),
catch: (cause) => new FetchFailed({ cause })
});
effect-run-in-body
Effect.runSync 编号 : runPromise 编号 : runFork 将程序崩溃到一个具体值。 让他们留在边界main.ts,测试带, HTTP 路由处理器)并返回 Effect 其他任何地方的价值.
// ❌ inside a service method
const get = (id: string) => {
const user = Effect.runSync(fetchUser(id));
return user;
};
// ✅
const get = (id: string) => fetchUser(id);
imperative-loops
for, (中文(简体) ), while,以及 do…while 隐藏着转变的意图 使用 Arr.map, (中文(简体) ), Arr.filter, (中文(简体) ), Arr.filterMap, (中文(简体) ), Arr.reduce,或 Effect.forEach 所以操作在页面上.
// ❌
const names: Array<string> = [];
for (const user of users) {
if (user.active) names.push(user.name);
}
import * as Arr from 'effect/Array';
import * as Result from 'effect/Result';
declare const users: ReadonlyArray<{ readonly active: boolean; readonly name: string }>;
const names = Arr.filterMap(users, (u) =>
u.active ? Result.succeed(u.name) : Result.fail(undefined)
);
no-barrel-imports
这种更严格的进口政策明确了每个依赖者的自有模块。 从子模块导入命名空间; 根函数导出, 如 pipe 财务报告和审定财务报表 flow 属于 effect/Function。这是一种风格政策,而不是对进口进行命名的主张.
// ❌
import { Effect, Array as Arr, Option } from 'effect';
// ✅
import * as Effect from 'effect/Effect';
import * as Arr from 'effect/Array';
import * as Option from 'effect/Option';
no-opaque-instance-fields
Schema.Opaque 类是纯粹的类型级包装器——它们除了基本计划之外没有运行时间特征. 添加实例方法或字段会把它们变成某种系统无法再视为不透明的东西.
// ❌
class UserId extends Schema.Opaque<UserId>()(Schema.String) {
greet() {
return `Hello ${this.toString()}`;
}
}
// ✅
class UserId extends Schema.Opaque<UserId>()(Schema.String) {}
const greet = (id: UserId) => `Hello ${id}`;
prefer-arr-match
手动 .length === 0 编号 : .length > 0 分支模糊了空对非空意图. Arr.match 使两个分支都清晰,并给你身体中非空阵列的见证.
// ❌
if (items.length === 0) return placeholder;
return list(items);
// ✅
return Arr.match(items, {
onEmpty: () => placeholder,
onNonEmpty: (xs) => list(xs)
});
prefer-arr-sort
Array.prototype.sort 变异在原地, 排序词典默认, 没有概念的 Order。 。 。 . Arr.sort 不可改变,用 Order 组合器 .
// ❌
const sorted = [...users].sort((a, b) => a.age - b.age);
// ✅
const sorted = Arr.sort(
users,
Order.mapInput(Order.Number, (u: User) => u.age)
);
prefer-duration-constructors
原始毫秒升为 Effect.sleep, (中文(简体) ), Schedule.spaced等,读取差. Duration.seconds, (中文(简体) ), Duration.minutes, (中文(简体) ), Duration.millis 保持单位在呼叫地点.
// ❌
yield* Effect.sleep(5000);
const sched = Schedule.spaced(60_000);
// ✅
yield* Effect.sleep(Duration.seconds(5));
const sched = Schedule.spaced(Duration.minutes(1));
prefer-effect-fn
Effect.gen(function*() { ... }) 分配给一个 const,或作为一种服务方法,缺乏所附的跨度名称. Effect.fn("name")(function*() { ... }) 增加了自动追踪.
// ❌
const getUser = (id: string) => Effect.gen(function* () { ... });
const make = Effect.gen(function* () {
return UserRepo.of({
get: (id) => Effect.gen(function* () { ... })
});
});
// ✅
const getUser = Effect.fn('getUser')(function* (id: string) { ... });
const make = Effect.gen(function* () {
return UserRepo.of({
get: Effect.fn('UserRepo.get')(function* (id) { ... })
});
});
prefer-effect-is
typeof x === "string" 是不可分的 而且不会像Effect那样 将结合类型缩小 Predicate 帮手.
// ❌
if (typeof value === 'string') return value;
if (typeof n === 'number' && n > 0) return n;
// ✅
import * as P from 'effect/Predicate';
if (P.isString(value)) return value;
if (P.isNumber(n) && n > 0) return n;
prefer-match-over-switch
switch 并非详尽无遗(TypeScript无法证明每个案件都得到处理),也不用管子作曲. Match.value 是详尽无遗的,表现层面的,和管道友好的.
// ❌
switch (status) {
case 'Pending':
return spinner();
case 'Active':
return view();
case 'Closed':
return summary();
}
// ✅
return Match.value(status).pipe(
Match.when('Pending', () => spinner()),
Match.when('Active', () => view()),
Match.when('Closed', () => summary()),
Match.exhaustive
);
prefer-namespace-imports
使用命名空间导入用于带有 Canonic 化名的助手模块( NameArr 页:1 effect/Array, (中文(简体) ), Option, (中文(简体) ), R 页:1 effect/Record(见A/C.5/49/70,附件)。 命名区域进口, 如 import { HttpClient } from 'effect/http' 继续有效.
// ❌
import { map, filter } from 'effect/Array';
import { Array } from 'effect';
// ✅
import * as Arr from 'effect/Array';
import * as Effect from 'effect/Effect';
prefer-option-over-null
T | null 编号 : T | undefined 不作曲:每个呼叫者必须重复无效检查. Option<T> 船舶 map, (中文(简体) ), flatMap, (中文(简体) ), match, (中文(简体) ), getOrElse还有朋友.
// ❌
function find(id: string): User | null { ... }
// ✅
function find(id: string): Option.Option<User> { ... }
prefer-redacted-config
配置密钥, 其名称通常会识别一个密钥( N)apiKey, (中文(简体) ), authToken, (中文(简体) ), password, (中文(简体) ), privateKey, (中文(简体) ), dsn应使用 Config.Redacted(...) 或者说 Schema.Redacted 内部 Config.schema用于 Config.NonEmptyString中,保留约束 Config.schema(Schema.Redacted(Schema.NonEmptyString), key)。还检测出后遗症小写装载器。 校正口罩记录和检查;它并不妨碍有意解围或所有编码.
// ❌
const apiKey = yield* Config.String('apiKey');
const cfg =
yield* Config.schema(
Schema.Struct({
apiKey: Schema.String
})
);
import * as Config from 'effect/Config';
import * as Schema from 'effect/Schema';
const apiKey = Config.Redacted('apiKey');
const nonEmptyToken = Config.schema(Schema.Redacted(Schema.NonEmptyString), 'TOKEN');
const cfg = Config.schema(
Schema.Struct({
apiKey: Schema.Redacted(Schema.String)
})
);
require-effect-concurrency
Effect.all, (中文(简体) ), Effect.forEach, (中文(简体) ), Effect.validate,朋友默默地默认顺序执行. 顺序有时是正确的——但它是一个货币决定,因此应该在呼叫站点进行复核.
// ❌
yield* Effect.forEach(ids, fetchUser);
// ✅
yield* Effect.forEach(ids, fetchUser, { concurrency: 'unbounded' });
yield* Effect.forEach(ids, fetchUser, { concurrency: 4 });
yield* Effect.forEach(ids, fetchUser, { concurrency: 1 }); // explicit sequential
require-filter-metadata
Schema.makeFilter 财务报告和审定财务报表 Schema.makeFilterGroup 生成可重复使用的验证器。 无 identifier, (中文(简体) ), title,以及 description 它们在错误消息和 OpenAPI 文件中作为不透明的blobs 出现.
// ❌
const PositiveInt = Schema.makeFilter((n: number) => n > 0);
// ✅
const PositiveInt = Schema.makeFilter((n: number) => n > 0, {
identifier: 'PositiveInt',
title: 'Positive integer',
description: 'A whole number strictly greater than zero.'
});
require-schema-type-alias
导出 Schema.Struct 编号 : Schema.TaggedStruct 编号 : Schema.Literals 常数在值名时不带类型脚本。 和他们配对 export type Foo = typeof Foo.Type 因此,进口商可以指推断出的类型.
警卫,检查Schema.isIncluding, (中文(简体) ), Schema.isBetweenLength过滤器,
等同,格式化器不是计划值,不需要这个别名.
// ❌
export const User = Schema.Struct({ id: Schema.String });
// ✅
export const User = Schema.Struct({ id: Schema.String });
export type User = typeof User.Type;
throw-in-effect-gen
throw 内部 Effect.gen 编号 : Effect.fn 编号 : Effect.fnUntraced 成为输入错误通道外的缺陷。 使用 yield* Effect.fail(new MyError(...)) (或 yield* new MyTaggedError({ ... })对预期的失败。 直击和 try: 双臂 Effect.tryPromise 编号 : Effect.try 被排除在外,因为这些界限 捕捉掷出.
// ❌
Effect.gen(function* () {
if (!user) throw new Error('User missing');
return user;
});
// ✅
Effect.gen(function* () {
if (!user) return yield* Effect.fail(new UserMissing({ id }));
return user;
});
use-clock-service
new Date() 编号 : Date.now() 编号 : Date.UTC() 是非决定性的和不可测试的。 使用 Clock 服务或服务 DateTime 模块,使测试可以冻结时间.
// ❌
const now = new Date();
const ms = Date.now();
// ✅
const now = yield* DateTime.now;
const ms = yield* Clock.currentTimeMillis;
use-command-executor-service
child_process 编号 : node:child_process 链接代码与节点进程模型并生成未打出的错误. 使用 ChildProcessSpawner + 键 ChildProcess 从 effect/process 用于打出、放出、可混合过程产卵。 提供运行时边界的平台产能层.
// ❌
import { spawn } from 'node:child_process';
const proc = spawn('git', ['status']);
import * as Effect from 'effect/Effect';
import { ChildProcess, ChildProcessSpawner } from 'effect/process';
const status = Effect.gen(function* () {
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
return yield* spawner.string(ChildProcess.make('git', ['status']));
});
use-console-service
console.* 写到 stdout / stderr 不包含跨度、结构化字段或测试捕获 。 使用 Effect.logInfo 编号 : logError 编号 : logWarning 编号 : logDebug (首选),或 Console 服务.
// ❌
console.log('Fetched user', user.id);
console.error('Failed', err);
// ✅
yield* Effect.logInfo('Fetched user').pipe(
Effect.annotateLogs('userId', user.id)
);
yield* Effect.logError('Failed').pipe(Effect.annotateLogs('cause', err));
use-filesystem-service
fs, (中文(简体) ), node:fs,以及 fs/promises 给节点的运行时间绑上代码 这个 FileSystem 服务从 effect/FileSystem 可移植、可分层替换,并与 Stream, (中文(简体) ), Scope,其余的效应.
// ❌
import * as fs from 'node:fs/promises';
const text = await fs.readFile(path, 'utf8');
// ✅
import * as FileSystem from 'effect/FileSystem';
const fs = yield* FileSystem.FileSystem;
const text = yield* fs.readFileString(path);
use-http-client-service
直接 http 编号 : https 进口暴露出低水平运输装卸。 使用 HttpClient, (中文(简体) ), HttpClientRequest,以及 HttpClientResponse 从 effect/http 用于输入响应和可测试层替换。 明确添加重试,并仅用于经证明具有能力的行动.
// ❌
import * as https from 'node:https';
https.get(url, (res) => { ... });
// ✅
const client = yield* HttpClient.HttpClient;
const json = yield* client.get(url).pipe(
Effect.flatMap(HttpClientResponse.schemaBodyJson(Payload))
);
use-path-service
node:path 视运行时间而定,是Posix-or-Windows的口味。 这个 Path 服务从 effect/Path 明确了您使用的哪一种变体 可测试/可嘲弄.
// ❌
import * as path from 'node:path';
const full = path.join(dir, name);
// ✅
import * as Path from 'effect/Path';
const path_ = yield* Path.Path;
const full = path_.join(dir, name);
use-random-service
Math.random() 绕过 Effect 的随机服务。 使用 Random.withSeed 为可重复序列. Random.nextIntBetween(min, max) 默认情况下包含两个终点; 使用 { halfOpen: true } 排除上层界限.
// ❌
const n = Math.floor(Math.random() * 100);
import * as Random from 'effect/Random';
const n = Random.nextIntBetween(0, 100, { halfOpen: true }).pipe(
Random.withSeed('example')
);
use-temp-file-scoped
os.tmpdir() 和无范围 FileSystem.makeTempFile 编号 : makeTempDirectory 程序崩溃时会泄露临时文件 。 使用 FileSystem.makeTempFileScoped 编号 : makeTempDirectoryScoped 所以,清理是绑在 Scope。 。 。 .
// ❌
import { tmpdir } from 'node:os';
const dir = path.join(tmpdir(), 'work');
// ✅
const fs = yield* FileSystem.FileSystem;
const dir = yield* fs.makeTempDirectoryScoped();
vm-in-wrong-file
查看模型界面及其层属于 .vm.ts 文档。 与构件并排使渲染和状态管理之间的接合平平分——接合是VM图案的整体点.
// ❌ profile.tsx
export interface ProfileVM { ... }
export const ProfileVMLive = Layer.effect(...);
// ✅ profile.vm.ts
export interface ProfileVM { ... }
export const ProfileVMLive = Layer.effect(...);
yield-in-for-loop
yield* 内置 a for 循环强制顺序执行并隐藏迭代意图. Effect.forEach 是宣说和平行的.
// ❌
for (const id of ids) {
yield* fetchUser(id);
}
// ✅
yield* Effect.forEach(ids, fetchUser, { concurrency: 'unbounded' });
maybe-prefix-requires-option
命名的字段 maybeX 承诺 Option<X>。使用平原前缀 T | null, (中文(简体) ), T | undefined,或 Schema 可选/可忽略字段打破读者的期望.
maybe* 名称应打入为 Option<T> 在类型脚本中 Schema.Option(...) 编号 : Schema.OptionFromNullishOr(...) 在司马结构。 如果该值真的无效而非可选,则重命名为 nullableX。 。 。 .
no-effect-ignore-then-as
Effect.ignore 丢弃成功值和错误通道。 当它出现之前 Effect.as(...),则 as 已丢弃成功值, 因此 ignore 只能悄悄地消除失败.
规则也是旗帜 Effect.ignore 在已知的不可倒计时原始物上,没有可以忽略的错误通道.
no-length-comparison
手动 .length === 0, (中文(简体) ), .length > 0,而相关的检查则隐藏该值是字符串还是数组。 首选命名的上游,例如 Str.isEmpty, (中文(简体) ), Str.isNonEmpty, (中文(简体) ), Arr.isReadonlyArrayEmpty, (中文(简体) ), Arr.isReadonlyArrayNonEmpty,或与 Arr.match。 。 。 .
prefer-array-fromoption-over-option-match-empty
Option<A> 改为 ReadonlyArray<A> 实值 Array.fromOption。拼写为 Option.match({ onNone: () => [], onSome: (v) => [v] }) 掩盖了意图.
require-is-prefix-for-boolean-schema-field
Boolean Schema 字段应读作呼叫站点的上游. 使用诸如 is*, (中文(简体) ), has*, (中文(简体) ), can*, (中文(简体) ), should*, (中文(简体) ), was*,或 will* 页:1 Schema.Boolean 字段.
no-cause-in-http-response
Cause.pretty编号 :Cause.squash 使全部失败细节——缺陷堆栈痕迹,解码内部——成为文本. 直接通过 .message, (中文(简体) ), String(...),或模板插值)入HTTP响应机构将内部故障变为信息披露. 登录源服务器侧并返回一个通用正文 .
// ❌
const failureResponse = (cause: Cause.Cause<unknown>) =>
HttpServerResponse.text(Cause.pretty(cause), { status: 500 });
// ✅
const failureResponse = (cause: Cause.Cause<unknown>) =>
Effect.logError(`Request failed: ${Cause.pretty(cause)}`).pipe(
Effect.as(HttpServerResponse.text('Internal error.', { status: 500 }))
);
取缔
所有规则都尊重克林特的标准禁用指令:
// oxlint-disable-next-line effect/<rule-name> -- reason
/* oxlint-disable effect/<rule-name> -- reason */
// ... block ...
/* oxlint-enable effect/<rule-name> */
尾声 -- <reason> 任何超过单一公关审查的抑制措施都受到鼓励.
发展
从寄存器根 :
bun install
bun run --filter @enfect/oxlint test # Vitest on Node; oxlint's RuleTester refuses Bun
bun run check # format, lint, and typecheck
每一个规则都生活在 src/rules/<rule-name>.ts 有兄弟的测试 test/rules/<rule-name>.test.ts规则SDK在 effect-oxlint。 。 。 .
许多规则都有Ast -grep对应的
@enfect/patterns,代理插件在之后运行
每个写.
许可证
麻省理工学院. 反自流规则和固定装置在
licenses/anti-slop.txt; 见
licenses/README.md 因为来源.