Unified · Split
| @@ -152,6 +152,25 @@ Effect.ignore( · 151 unmodified lines | |||
| 152 | ); | 152 | ); |
| 153 | ``` | 153 | ``` |
| 154 | 154 | ||
| 155 | ### Effect die / orDie escape hatches | ||
| 156 | |||
| 157 | Bad in domain code: | ||
| 158 | |||
| 159 | ```ts | ||
| 160 | program.pipe(Effect.orDie) | ||
| 161 | Effect.die(error) | ||
| 162 | ``` | ||
| 163 | |||
| 164 | Good: | ||
| 165 | |||
| 166 | ```ts | ||
| 167 | program.pipe( | ||
| 168 | Effect.mapError((cause) => new DomainError({ message: "Operation failed", cause })), | ||
| 169 | ) | ||
| 170 | ``` | ||
| 171 | |||
| 172 | `Effect.die`, `Effect.dieMessage`, `Effect.orDie`, and `Effect.orDieWith` turn typed failures into defects. Use them only at a true runtime boundary where the host cannot represent typed failures, and keep that usage behind a narrow lint suppression with a `boundary:` reason. Do not use `orDie` to avoid threading an error type through normal Effect code. | ||
| 173 | |||
| 155 | ### try/catch | 174 | ### try/catch |
| 156 | 175 | ||
| 157 | Bad: | 176 | Bad: |
| @@ -8,6 +8,7 @@ · 7 unmodified lines | |||
| 8 | "executor/no-double-cast": "error", | 8 | "executor/no-double-cast": "error", |
| 9 | "executor/no-cross-package-relative-imports": "error", | 9 | "executor/no-cross-package-relative-imports": "error", |
| 10 | "executor/require-reactivity-keys": "error", | 10 | "executor/require-reactivity-keys": "error", |
| 11 | "executor/no-effect-escape-hatch": "error", | ||
| 11 | "executor/no-effect-internal-tags": "error", | 12 | "executor/no-effect-internal-tags": "error", |
| 12 | "executor/no-error-constructor": "error", | 13 | "executor/no-error-constructor": "error", |
| 13 | "executor/no-instanceof-error": "error", | 14 | "executor/no-instanceof-error": "error", |
| @@ -47,6 +48,7 @@ · 33 unmodified lines | |||
| 47 | "packages/kernel/runtime-*/src/**/*.{ts,tsx,js,mjs}", | 48 | "packages/kernel/runtime-*/src/**/*.{ts,tsx,js,mjs}", |
| 48 | ], | 49 | ], |
| 49 | "rules": { | 50 | "rules": { |
| 51 | "executor/no-effect-escape-hatch": "off", | ||
| 50 | "executor/no-error-constructor": "off", | 52 | "executor/no-error-constructor": "off", |
| 51 | "executor/no-instanceof-error": "off", | 53 | "executor/no-instanceof-error": "off", |
| 52 | "executor/no-json-parse": "off", | 54 | "executor/no-json-parse": "off", |
| @@ -59,6 +61,7 @@ · 6 unmodified lines | |||
| 59 | { | 61 | { |
| 60 | "files": ["apps/marketing/src/**/*.astro"], | 62 | "files": ["apps/marketing/src/**/*.astro"], |
| 61 | "rules": { | 63 | "rules": { |
| 64 | "executor/no-effect-escape-hatch": "off", | ||
| 62 | "executor/no-error-constructor": "off", | 65 | "executor/no-error-constructor": "off", |
| 63 | "executor/no-instanceof-error": "off", | 66 | "executor/no-instanceof-error": "off", |
| 64 | "executor/no-json-parse": "off", | 67 | "executor/no-json-parse": "off", |
| @@ -1,6 +1,7 @@ | |||
| 1 | import noConditionalTests from "./oxlint-plugin-executor/rules/no-conditional-tests.js"; | 1 | import noConditionalTests from "./oxlint-plugin-executor/rules/no-conditional-tests.js"; |
| 2 | import noCrossPackageRelativeImports from "./oxlint-plugin-executor/rules/no-cross-package-relative-imports.js"; | 2 | import noCrossPackageRelativeImports from "./oxlint-plugin-executor/rules/no-cross-package-relative-imports.js"; |
| 3 | import noDoubleCast from "./oxlint-plugin-executor/rules/no-double-cast.js"; | 3 | import noDoubleCast from "./oxlint-plugin-executor/rules/no-double-cast.js"; |
| 4 | import noEffectEscapeHatch from "./oxlint-plugin-executor/rules/no-effect-escape-hatch.js"; | ||
| 4 | import noEffectInternalTags from "./oxlint-plugin-executor/rules/no-effect-internal-tags.js"; | 5 | import noEffectInternalTags from "./oxlint-plugin-executor/rules/no-effect-internal-tags.js"; |
| 5 | import noErrorConstructor from "./oxlint-plugin-executor/rules/no-error-constructor.js"; | 6 | import noErrorConstructor from "./oxlint-plugin-executor/rules/no-error-constructor.js"; |
| 6 | import noInlineObjectTypeAssertion from "./oxlint-plugin-executor/rules/no-inline-object-type-assertion.js"; | 7 | import noInlineObjectTypeAssertion from "./oxlint-plugin-executor/rules/no-inline-object-type-assertion.js"; |
| @@ -34,6 +35,7 @@ export default { · 27 unmodified lines | |||
| 34 | "no-double-cast": noDoubleCast, | 35 | "no-double-cast": noDoubleCast, |
| 35 | "no-cross-package-relative-imports": noCrossPackageRelativeImports, | 36 | "no-cross-package-relative-imports": noCrossPackageRelativeImports, |
| 36 | "require-reactivity-keys": requireReactivityKeys, | 37 | "require-reactivity-keys": requireReactivityKeys, |
| 38 | "no-effect-escape-hatch": noEffectEscapeHatch, | ||
| 37 | "no-effect-internal-tags": noEffectInternalTags, | 39 | "no-effect-internal-tags": noEffectInternalTags, |
| 38 | "no-error-constructor": noErrorConstructor, | 40 | "no-error-constructor": noErrorConstructor, |
| 39 | "no-ts-nocheck": noTsNocheck, | 41 | "no-ts-nocheck": noTsNocheck, |
| @@ -0,0 +1,33 @@ | |||
| 1 | import { getPropertyName, isTestLike, unwrapExpression } from "../utils.js"; | ||
| 2 | |||
| 3 | const escapeHatches = new Set(["die", "dieMessage", "orDie", "orDieWith"]); | ||
| 4 | |||
| 5 | const message = | ||
| 6 | "Do not collapse Effect failures with die/orDie escape hatches. Keep typed errors in the Effect error channel; at true runtime edges use a narrow boundary suppression. Skill: wrdn-effect-typed-errors."; | ||
| 7 | |||
| 8 | const isEffectEscapeHatch = (node) => { | ||
| 9 | const expression = unwrapExpression(node); | ||
| 10 | if (expression?.type !== "MemberExpression") return false; | ||
| 11 | const property = getPropertyName(expression.property); | ||
| 12 | return escapeHatches.has(property); | ||
| 13 | }; | ||
| 14 | |||
| 15 | export default { | ||
| 16 | meta: { | ||
| 17 | type: "problem", | ||
| 18 | docs: { | ||
| 19 | description: "Disallow Effect die/orDie escape hatches outside test code.", | ||
| 20 | }, | ||
| 21 | }, | ||
| 22 | create(context) { | ||
| 23 | if (isTestLike(context.filename)) return {}; | ||
| 24 | |||
| 25 | return { | ||
| 26 | MemberExpression(node) { | ||
| 27 | if (isEffectEscapeHatch(node)) { | ||
| 28 | context.report({ node, message }); | ||
| 29 | } | ||
| 30 | }, | ||
| 31 | }; | ||
| 32 | }, | ||
| 33 | }; | ||