oxlint-plugin-effect 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +32 -20
  2. package/dist/presets/recommended.d.ts +12 -0
  3. package/dist/presets/recommended.js +13 -1
  4. package/dist/rules/_effect-context.js +27 -0
  5. package/dist/rules/_effect-namespaces.js +25 -0
  6. package/dist/rules/_tagged-control-flow.js +1 -1
  7. package/dist/rules/index.d.ts +13 -1
  8. package/dist/rules/index.js +25 -1
  9. package/dist/rules/no-inline-provide.d.ts +6 -0
  10. package/dist/rules/no-inline-provide.js +39 -0
  11. package/dist/rules/no-managed-runtime-in-effect.d.ts +6 -0
  12. package/dist/rules/no-managed-runtime-in-effect.js +41 -0
  13. package/dist/rules/no-module-mocks.d.ts +6 -0
  14. package/dist/rules/no-module-mocks.js +73 -0
  15. package/dist/rules/no-nested-effect-gen.d.ts +6 -0
  16. package/dist/rules/no-nested-effect-gen.js +39 -0
  17. package/dist/rules/no-per-call-cache-construction.d.ts +6 -0
  18. package/dist/rules/no-per-call-cache-construction.js +52 -0
  19. package/dist/rules/no-run-collect-on-unbounded-stream.d.ts +6 -0
  20. package/dist/rules/no-run-collect-on-unbounded-stream.js +81 -0
  21. package/dist/rules/no-sequential-effect-all.d.ts +6 -0
  22. package/dist/rules/no-sequential-effect-all.js +61 -0
  23. package/dist/rules/no-silent-catch-all.d.ts +6 -0
  24. package/dist/rules/no-silent-catch-all.js +51 -0
  25. package/dist/rules/no-unbounded-concurrency.d.ts +6 -0
  26. package/dist/rules/no-unbounded-concurrency.js +50 -0
  27. package/dist/rules/no-unbounded-retry.d.ts +6 -0
  28. package/dist/rules/no-unbounded-retry.js +114 -0
  29. package/dist/rules/prefer-catch-tag.js +33 -6
  30. package/dist/rules/prefer-match-tags-exhaustive.js +62 -8
  31. package/dist/rules/prefer-service-of.d.ts +6 -0
  32. package/dist/rules/prefer-service-of.js +65 -0
  33. package/dist/rules/require-named-effect-fn.d.ts +6 -0
  34. package/dist/rules/require-named-effect-fn.js +37 -0
  35. package/package.json +1 -1
package/README.md CHANGED
@@ -25,26 +25,38 @@ If the project uses JSON configuration, copy the exported rule map into `rules`;
25
25
 
26
26
  ## Recommended Rules
27
27
 
28
- | Rule | Contract |
29
- | ---------------------------------- | ----------------------------------------------------------------------------------------- |
30
- | `effect/noAs` | Bans TypeScript `as` assertions; use `satisfies` |
31
- | `effect/noAsyncFunction` | Bans async functions and await expressions |
32
- | `effect/noTryCatch` | Bans every try/catch/finally statement |
33
- | `effect/noTestLifecycleHooks` | Bans `beforeEach`, `afterEach`, `beforeAll`, and `afterAll`; use Effect scopes instead |
34
- | `effect/noThrowStatement` | Bans every throw statement |
35
- | `effect/noNewPromise` | Bans new Promise, Promise calls, and Promise static APIs |
36
- | `effect/noNewError` | Allows native Error values only as direct arguments to Effect.die, Cause.die, or Exit.die |
37
- | `effect/noNullish` | Bans null and undefined; use Option or a domain enum for richer state |
38
- | `effect/noTernary` | Bans conditional expressions while allowing ordinary if statements |
39
- | `effect/noDynamicImports` | Allows import() only behind a named lazy-loading boundary; bans require() |
40
- | `effect/noEffectDo` | Bans Effect.Do |
41
- | `effect/noEffectBind` | Bans Effect.bind |
42
- | `effect/preferCatchTag` | Replaces manual `_tag` predicates in `Effect.catchIf` with tagged recovery |
43
- | `effect/preferEffectFn` | Requires `Effect.fn` for a generator operation that adds a span |
44
- | `effect/preferMatchTagsExhaustive` | Requires exhaustive `Match` for return-only `_tag` switches |
45
- | `effect/preferPredicateIsTagged` | Replaces combined `_tag` comparisons with a named `Predicate` refinement |
46
- | `effect/noGlobals` | Bans ambient capabilities with direct Effect replacements; allows `process.std*.isTTY` |
47
- | `effect/noNodeBuiltinImport` | Bans fully replaced Node modules and replaced operations from partial modules |
28
+ | Rule | Contract |
29
+ | -------------------------------------- | ----------------------------------------------------------------------------------------- |
30
+ | `effect/noAs` | Bans TypeScript `as` assertions; use `satisfies` |
31
+ | `effect/noAsyncFunction` | Bans async functions and await expressions |
32
+ | `effect/noTryCatch` | Bans every try/catch/finally statement |
33
+ | `effect/noTestLifecycleHooks` | Bans `beforeEach`, `afterEach`, `beforeAll`, and `afterAll`; use Effect scopes instead |
34
+ | `effect/noThrowStatement` | Bans every throw statement |
35
+ | `effect/noNewPromise` | Bans new Promise, Promise calls, and Promise static APIs |
36
+ | `effect/noNewError` | Allows native Error values only as direct arguments to Effect.die, Cause.die, or Exit.die |
37
+ | `effect/noNullish` | Bans null and undefined; use Option or a domain enum for richer state |
38
+ | `effect/noModuleMocks` | Bans Vitest and Jest module mocks and method spies; use Effect service test layers |
39
+ | `effect/noTernary` | Bans conditional expressions while allowing ordinary if statements |
40
+ | `effect/noManagedRuntimeInEffect` | Keeps ManagedRuntime construction at non-Effect host boundaries |
41
+ | `effect/noInlineProvide` | Keeps dependency provisioning at explicit composition boundaries |
42
+ | `effect/noNestedEffectGen` | Flattens directly yielded nested generators |
43
+ | `effect/noPerCallCacheConstruction` | Constructs shared caches once in their owning layer |
44
+ | `effect/noRunCollectOnUnboundedStream` | Requires termination before collecting a clearly unbounded Stream |
45
+ | `effect/noSequentialEffectAll` | Uses explicit sequencing when serial aggregation discards its result |
46
+ | `effect/noSilentCatchAll` | Keeps swallowed failures visible or recovers them truthfully |
47
+ | `effect/noUnboundedConcurrency` | Requires finite concurrency for collections that can grow |
48
+ | `effect/noUnboundedRetry` | Requires an attempt or duration bound on retry schedules |
49
+ | `effect/noDynamicImports` | Allows import() only behind a named lazy-loading boundary; bans require() |
50
+ | `effect/noEffectDo` | Bans Effect.Do |
51
+ | `effect/noEffectBind` | Bans Effect.bind |
52
+ | `effect/preferCatchTag` | Replaces manual `_tag` predicates and `catchAll` dispatch with tagged recovery |
53
+ | `effect/preferEffectFn` | Requires `Effect.fn` for a generator operation that adds a span |
54
+ | `effect/preferMatchTagsExhaustive` | Requires exhaustive `Match` for return-only `_tag` switches and if chains |
55
+ | `effect/preferPredicateIsTagged` | Replaces combined `_tag` comparisons with a named `Predicate` refinement |
56
+ | `effect/preferServiceOf` | Checks inline Layer implementations through `Service.of` |
57
+ | `effect/requireNamedEffectFn` | Requires stable names for `Effect.fn` operations |
58
+ | `effect/noGlobals` | Bans ambient capabilities with direct Effect replacements; allows `process.std*.isTTY` |
59
+ | `effect/noNodeBuiltinImport` | Bans fully replaced Node modules and replaced operations from partial modules |
48
60
 
49
61
  ## Anti-Slop Rules
50
62
 
@@ -13,18 +13,28 @@ declare const recommended: {
13
13
  readonly "effect/noEffectBind": "error";
14
14
  readonly "effect/noEffectDo": "error";
15
15
  readonly "effect/noGlobals": "error";
16
+ readonly "effect/noInlineProvide": "error";
16
17
  readonly "effect/noKnownValueWidening": "error";
18
+ readonly "effect/noManagedRuntimeInEffect": "error";
19
+ readonly "effect/noModuleMocks": "error";
20
+ readonly "effect/noNestedEffectGen": "error";
17
21
  readonly "effect/noNewError": "error";
18
22
  readonly "effect/noNewPromise": "error";
19
23
  readonly "effect/noNodeBuiltinImport": "error";
20
24
  readonly "effect/noNullish": "error";
21
25
  readonly "effect/noObjectParameters": "error";
26
+ readonly "effect/noPerCallCacheConstruction": "error";
27
+ readonly "effect/noRunCollectOnUnboundedStream": "error";
22
28
  readonly "effect/noRuntimeTypeof": "error";
29
+ readonly "effect/noSequentialEffectAll": "error";
23
30
  readonly "effect/noShapeInSymbolNames": "error";
31
+ readonly "effect/noSilentCatchAll": "error";
24
32
  readonly "effect/noTernary": "error";
25
33
  readonly "effect/noTestLifecycleHooks": "error";
26
34
  readonly "effect/noThrowStatement": "error";
27
35
  readonly "effect/noTryCatch": "error";
36
+ readonly "effect/noUnboundedConcurrency": "error";
37
+ readonly "effect/noUnboundedRetry": "error";
28
38
  readonly "effect/noUnknownParameters": "error";
29
39
  readonly "effect/noUnknownTypeAliases": "error";
30
40
  readonly "effect/noUnsafeDictionaryType": "error";
@@ -33,6 +43,8 @@ declare const recommended: {
33
43
  readonly "effect/preferEffectFn": "error";
34
44
  readonly "effect/preferMatchTagsExhaustive": "error";
35
45
  readonly "effect/preferPredicateIsTagged": "error";
46
+ readonly "effect/preferServiceOf": "error";
47
+ readonly "effect/requireNamedEffectFn": "error";
36
48
  };
37
49
  //#endregion
38
50
  export { recommended };
@@ -13,18 +13,28 @@ const recommended = {
13
13
  "effect/noEffectBind": "error",
14
14
  "effect/noEffectDo": "error",
15
15
  "effect/noGlobals": "error",
16
+ "effect/noInlineProvide": "error",
16
17
  "effect/noKnownValueWidening": "error",
18
+ "effect/noManagedRuntimeInEffect": "error",
19
+ "effect/noModuleMocks": "error",
20
+ "effect/noNestedEffectGen": "error",
17
21
  "effect/noNewError": "error",
18
22
  "effect/noNewPromise": "error",
19
23
  "effect/noNodeBuiltinImport": "error",
20
24
  "effect/noNullish": "error",
21
25
  "effect/noObjectParameters": "error",
26
+ "effect/noPerCallCacheConstruction": "error",
27
+ "effect/noRunCollectOnUnboundedStream": "error",
22
28
  "effect/noRuntimeTypeof": "error",
29
+ "effect/noSequentialEffectAll": "error",
23
30
  "effect/noShapeInSymbolNames": "error",
31
+ "effect/noSilentCatchAll": "error",
24
32
  "effect/noTernary": "error",
25
33
  "effect/noTestLifecycleHooks": "error",
26
34
  "effect/noThrowStatement": "error",
27
35
  "effect/noTryCatch": "error",
36
+ "effect/noUnboundedConcurrency": "error",
37
+ "effect/noUnboundedRetry": "error",
28
38
  "effect/noUnknownParameters": "error",
29
39
  "effect/noUnknownTypeAliases": "error",
30
40
  "effect/noUnsafeDictionaryType": "error",
@@ -32,7 +42,9 @@ const recommended = {
32
42
  "effect/preferCatchTag": "error",
33
43
  "effect/preferEffectFn": "error",
34
44
  "effect/preferMatchTagsExhaustive": "error",
35
- "effect/preferPredicateIsTagged": "error"
45
+ "effect/preferPredicateIsTagged": "error",
46
+ "effect/preferServiceOf": "error",
47
+ "effect/requireNamedEffectFn": "error"
36
48
  };
37
49
  //#endregion
38
50
  export { recommended };
@@ -0,0 +1,27 @@
1
+ import { isStaticCall, isStaticMember } from "./_effect-namespaces.js";
2
+ //#region src/rules/_effect-context.ts
3
+ const isFunction = (node) => node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
4
+ const effectProgramKind = (node, effectNamespaces) => {
5
+ const parent = node.parent;
6
+ if (parent?.type !== "CallExpression" || !parent.arguments.includes(node)) return void 0;
7
+ if (parent.callee.type !== "Super" && isStaticMember(parent.callee, effectNamespaces, "gen")) return "gen";
8
+ if (parent.callee.type !== "Super" && isStaticMember(parent.callee, effectNamespaces, "fn")) return "fn";
9
+ if (parent.callee.type !== "Super" && isStaticMember(parent.callee, effectNamespaces, "fnUntraced")) return "fnUntraced";
10
+ if (parent.callee.type !== "CallExpression") return void 0;
11
+ if (isStaticCall(parent.callee, effectNamespaces, "fn")) return "fn";
12
+ if (isStaticCall(parent.callee, effectNamespaces, "fnUntraced")) return "fnUntraced";
13
+ };
14
+ const enclosingEffectProgram = (node, effectNamespaces) => {
15
+ let current = node.parent;
16
+ while (current !== void 0 && current !== null) {
17
+ if (isFunction(current)) {
18
+ const kind = effectProgramKind(current, effectNamespaces);
19
+ if (kind === void 0) return void 0;
20
+ return [current, kind];
21
+ }
22
+ current = current.parent;
23
+ }
24
+ };
25
+ const isInsideEffectProgram = (node, effectNamespaces) => enclosingEffectProgram(node, effectNamespaces) !== void 0;
26
+ //#endregion
27
+ export { enclosingEffectProgram, isInsideEffectProgram };
@@ -0,0 +1,25 @@
1
+ import { findVariableUp } from "../vendor/effect-oxlint/Scope.js";
2
+ import "../vendor/effect-oxlint/index.js";
3
+ import * as Option from "effect/Option";
4
+ //#region src/rules/_effect-namespaces.ts
5
+ const importedNamespaces = (node, exportName, directModule) => {
6
+ const source = node.source.value;
7
+ if (source !== "effect" && source !== directModule) return [];
8
+ const namespaces = [];
9
+ for (const specifier of node.specifiers) {
10
+ if (specifier.type === "ImportNamespaceSpecifier" && source === directModule) {
11
+ namespaces.push(specifier.local.name);
12
+ continue;
13
+ }
14
+ if (specifier.type === "ImportSpecifier" && source === "effect" && specifier.imported.type === "Identifier" && specifier.imported.name === exportName) namespaces.push(specifier.local.name);
15
+ }
16
+ return namespaces;
17
+ };
18
+ const visibleNamespaces = (ctx, node, namespaces) => new Set([...namespaces].filter((namespace) => Option.match(findVariableUp(ctx.sourceCode.getScope(node), namespace), {
19
+ onNone: () => true,
20
+ onSome: (variable) => variable.defs.some((definition) => definition.type === "ImportBinding")
21
+ })));
22
+ const isStaticMember = (node, namespaces, property) => node.type === "MemberExpression" && !node.computed && node.object.type === "Identifier" && namespaces.has(node.object.name) && node.property.type === "Identifier" && node.property.name === property;
23
+ const isStaticCall = (node, namespaces, property) => node.type === "CallExpression" && node.callee.type !== "Super" && isStaticMember(node.callee, namespaces, property);
24
+ //#endregion
25
+ export { importedNamespaces, isStaticCall, isStaticMember, visibleNamespaces };
@@ -76,4 +76,4 @@ const isInsideCatchAllHandler = (node) => {
76
76
  };
77
77
  const taggedSwitchSubject = (node) => taggedSubject(node.discriminant);
78
78
  //#endregion
79
- export { effectCallbackLocation, hasOneTaggedSubject, isEffectCall, isInsideCatchAllHandler, tagComparisonsInOr, taggedSwitchSubject };
79
+ export { effectCallbackLocation, hasOneTaggedSubject, isEffectCall, isInsideCatchAllHandler, tagComparison, tagComparisonsInOr, taggedSwitchSubject };
@@ -6,18 +6,28 @@ import { noChainedTypeAssertions } from "./no-chained-type-assertions.js";
6
6
  import { noConditionalEmptyObjectSpread } from "./no-conditional-empty-object-spread.js";
7
7
  import { noDynamicImports } from "./no-dynamic-imports.js";
8
8
  import { noGlobals } from "./no-globals.js";
9
+ import { noInlineProvide } from "./no-inline-provide.js";
9
10
  import { noKnownValueWidening } from "./no-known-value-widening.js";
11
+ import { noManagedRuntimeInEffect } from "./no-managed-runtime-in-effect.js";
12
+ import { noModuleMocks } from "./no-module-mocks.js";
13
+ import { noNestedEffectGen } from "./no-nested-effect-gen.js";
10
14
  import { noNewError } from "./no-new-error.js";
11
15
  import { noNewPromise } from "./no-new-promise.js";
12
16
  import { noNodeBuiltinImport } from "./no-node-builtin-import.js";
13
17
  import { noNullish } from "./no-nullish.js";
14
18
  import { noObjectParameters } from "./no-object-parameters.js";
19
+ import { noPerCallCacheConstruction } from "./no-per-call-cache-construction.js";
20
+ import { noRunCollectOnUnboundedStream } from "./no-run-collect-on-unbounded-stream.js";
15
21
  import { noRuntimeTypeof } from "./no-runtime-typeof.js";
22
+ import { noSequentialEffectAll } from "./no-sequential-effect-all.js";
16
23
  import { noShapeInSymbolNames } from "./no-shape-in-symbol-names.js";
24
+ import { noSilentCatchAll } from "./no-silent-catch-all.js";
17
25
  import { noTernary } from "./no-ternary.js";
18
26
  import { noTestLifecycleHooks } from "./no-test-lifecycle-hooks.js";
19
27
  import { noThrowStatement } from "./no-throw-statement.js";
20
28
  import { noTryCatch } from "./no-try-catch.js";
29
+ import { noUnboundedConcurrency } from "./no-unbounded-concurrency.js";
30
+ import { noUnboundedRetry } from "./no-unbounded-retry.js";
21
31
  import { noUnknownParameters } from "./no-unknown-parameters.js";
22
32
  import { noUnknownTypeAliases } from "./no-unknown-type-aliases.js";
23
33
  import { noUnsafeDictionaryType } from "./no-unsafe-dictionary-type.js";
@@ -26,10 +36,12 @@ import { preferCatchTag } from "./prefer-catch-tag.js";
26
36
  import { preferEffectFn } from "./prefer-effect-fn.js";
27
37
  import { preferMatchTagsExhaustive } from "./prefer-match-tags-exhaustive.js";
28
38
  import { preferPredicateIsTagged } from "./prefer-predicate-is-tagged.js";
39
+ import { preferServiceOf } from "./prefer-service-of.js";
40
+ import { requireNamedEffectFn } from "./require-named-effect-fn.js";
29
41
 
30
42
  //#region src/rules/index.d.ts
31
43
  declare namespace index_d_exports {
32
- export { noAs, noAsyncFunction, noChainedTypeAssertions, noConditionalEmptyObjectSpread, noDynamicImports, noEffectBind, noEffectDo, noGlobals, noKnownValueWidening, noNewError, noNewPromise, noNodeBuiltinImport, noNullish, noObjectParameters, noRuntimeTypeof, noShapeInSymbolNames, noTernary, noTestLifecycleHooks, noThrowStatement, noTryCatch, noUnknownParameters, noUnknownTypeAliases, noUnsafeDictionaryType, noWidenThenAssert, preferCatchTag, preferEffectFn, preferMatchTagsExhaustive, preferPredicateIsTagged };
44
+ export { noAs, noAsyncFunction, noChainedTypeAssertions, noConditionalEmptyObjectSpread, noDynamicImports, noEffectBind, noEffectDo, noGlobals, noInlineProvide, noKnownValueWidening, noManagedRuntimeInEffect, noModuleMocks, noNestedEffectGen, noNewError, noNewPromise, noNodeBuiltinImport, noNullish, noObjectParameters, noPerCallCacheConstruction, noRunCollectOnUnboundedStream, noRuntimeTypeof, noSequentialEffectAll, noShapeInSymbolNames, noSilentCatchAll, noTernary, noTestLifecycleHooks, noThrowStatement, noTryCatch, noUnboundedConcurrency, noUnboundedRetry, noUnknownParameters, noUnknownTypeAliases, noUnsafeDictionaryType, noWidenThenAssert, preferCatchTag, preferEffectFn, preferMatchTagsExhaustive, preferPredicateIsTagged, preferServiceOf, requireNamedEffectFn };
33
45
  }
34
46
  //#endregion
35
47
  export { index_d_exports };
@@ -7,18 +7,28 @@ import { noChainedTypeAssertions } from "./no-chained-type-assertions.js";
7
7
  import { noConditionalEmptyObjectSpread } from "./no-conditional-empty-object-spread.js";
8
8
  import { noDynamicImports } from "./no-dynamic-imports.js";
9
9
  import { noGlobals } from "./no-globals.js";
10
+ import { noInlineProvide } from "./no-inline-provide.js";
10
11
  import { noKnownValueWidening } from "./no-known-value-widening.js";
12
+ import { noManagedRuntimeInEffect } from "./no-managed-runtime-in-effect.js";
13
+ import { noModuleMocks } from "./no-module-mocks.js";
14
+ import { noNestedEffectGen } from "./no-nested-effect-gen.js";
11
15
  import { noNewError } from "./no-new-error.js";
12
16
  import { noNewPromise } from "./no-new-promise.js";
13
17
  import { noNodeBuiltinImport } from "./no-node-builtin-import.js";
14
18
  import { noNullish } from "./no-nullish.js";
15
19
  import { noObjectParameters } from "./no-object-parameters.js";
20
+ import { noPerCallCacheConstruction } from "./no-per-call-cache-construction.js";
21
+ import { noRunCollectOnUnboundedStream } from "./no-run-collect-on-unbounded-stream.js";
16
22
  import { noRuntimeTypeof } from "./no-runtime-typeof.js";
23
+ import { noSequentialEffectAll } from "./no-sequential-effect-all.js";
17
24
  import { noShapeInSymbolNames } from "./no-shape-in-symbol-names.js";
25
+ import { noSilentCatchAll } from "./no-silent-catch-all.js";
18
26
  import { noTernary } from "./no-ternary.js";
19
27
  import { noTestLifecycleHooks } from "./no-test-lifecycle-hooks.js";
20
28
  import { noThrowStatement } from "./no-throw-statement.js";
21
29
  import { noTryCatch } from "./no-try-catch.js";
30
+ import { noUnboundedConcurrency } from "./no-unbounded-concurrency.js";
31
+ import { noUnboundedRetry } from "./no-unbounded-retry.js";
22
32
  import { noUnknownParameters } from "./no-unknown-parameters.js";
23
33
  import { noUnknownTypeAliases } from "./no-unknown-type-aliases.js";
24
34
  import { noUnsafeDictionaryType } from "./no-unsafe-dictionary-type.js";
@@ -27,6 +37,8 @@ import { preferCatchTag } from "./prefer-catch-tag.js";
27
37
  import { preferEffectFn } from "./prefer-effect-fn.js";
28
38
  import { preferMatchTagsExhaustive } from "./prefer-match-tags-exhaustive.js";
29
39
  import { preferPredicateIsTagged } from "./prefer-predicate-is-tagged.js";
40
+ import { preferServiceOf } from "./prefer-service-of.js";
41
+ import { requireNamedEffectFn } from "./require-named-effect-fn.js";
30
42
  //#region src/rules/index.ts
31
43
  var rules_exports = /* @__PURE__ */ __exportAll({
32
44
  noAs: () => noAs,
@@ -37,18 +49,28 @@ var rules_exports = /* @__PURE__ */ __exportAll({
37
49
  noEffectBind: () => noEffectBind,
38
50
  noEffectDo: () => noEffectDo,
39
51
  noGlobals: () => noGlobals,
52
+ noInlineProvide: () => noInlineProvide,
40
53
  noKnownValueWidening: () => noKnownValueWidening,
54
+ noManagedRuntimeInEffect: () => noManagedRuntimeInEffect,
55
+ noModuleMocks: () => noModuleMocks,
56
+ noNestedEffectGen: () => noNestedEffectGen,
41
57
  noNewError: () => noNewError,
42
58
  noNewPromise: () => noNewPromise,
43
59
  noNodeBuiltinImport: () => noNodeBuiltinImport,
44
60
  noNullish: () => noNullish,
45
61
  noObjectParameters: () => noObjectParameters,
62
+ noPerCallCacheConstruction: () => noPerCallCacheConstruction,
63
+ noRunCollectOnUnboundedStream: () => noRunCollectOnUnboundedStream,
46
64
  noRuntimeTypeof: () => noRuntimeTypeof,
65
+ noSequentialEffectAll: () => noSequentialEffectAll,
47
66
  noShapeInSymbolNames: () => noShapeInSymbolNames,
67
+ noSilentCatchAll: () => noSilentCatchAll,
48
68
  noTernary: () => noTernary,
49
69
  noTestLifecycleHooks: () => noTestLifecycleHooks,
50
70
  noThrowStatement: () => noThrowStatement,
51
71
  noTryCatch: () => noTryCatch,
72
+ noUnboundedConcurrency: () => noUnboundedConcurrency,
73
+ noUnboundedRetry: () => noUnboundedRetry,
52
74
  noUnknownParameters: () => noUnknownParameters,
53
75
  noUnknownTypeAliases: () => noUnknownTypeAliases,
54
76
  noUnsafeDictionaryType: () => noUnsafeDictionaryType,
@@ -56,7 +78,9 @@ var rules_exports = /* @__PURE__ */ __exportAll({
56
78
  preferCatchTag: () => preferCatchTag,
57
79
  preferEffectFn: () => preferEffectFn,
58
80
  preferMatchTagsExhaustive: () => preferMatchTagsExhaustive,
59
- preferPredicateIsTagged: () => preferPredicateIsTagged
81
+ preferPredicateIsTagged: () => preferPredicateIsTagged,
82
+ preferServiceOf: () => preferServiceOf,
83
+ requireNamedEffectFn: () => requireNamedEffectFn
60
84
  });
61
85
  //#endregion
62
86
  export { rules_exports };
@@ -0,0 +1,6 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-inline-provide.d.ts
4
+ declare const noInlineProvide: _$_oxlint_plugins0.CreateRule;
5
+ //#endregion
6
+ export { noInlineProvide };
@@ -0,0 +1,39 @@
1
+ import { make } from "../vendor/effect-oxlint/Diagnostic.js";
2
+ import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
3
+ import { define, meta } from "../vendor/effect-oxlint/Rule.js";
4
+ import "../vendor/effect-oxlint/index.js";
5
+ import { importedNamespaces, isStaticMember, visibleNamespaces } from "./_effect-namespaces.js";
6
+ import { isInsideEffectProgram } from "./_effect-context.js";
7
+ import * as Effect from "effect/Effect";
8
+ //#region src/rules/no-inline-provide.ts
9
+ /** Keep Effect provisioning at explicit composition boundaries. */
10
+ const noInlineProvide = define({
11
+ name: "no-inline-provide",
12
+ meta: meta({
13
+ type: "problem",
14
+ description: "Keep Effect provisioning outside domain Effect operations."
15
+ }),
16
+ create: function* () {
17
+ const ctx = yield* RuleContext;
18
+ const effectNamespaces = new Set(["Effect"]);
19
+ return {
20
+ ImportDeclaration: (node) => {
21
+ if (node.type !== "ImportDeclaration") return Effect.void;
22
+ for (const name of importedNamespaces(node, "Effect", "effect/Effect")) effectNamespaces.add(name);
23
+ return Effect.void;
24
+ },
25
+ CallExpression: (node) => {
26
+ if (node.type !== "CallExpression" || node.callee.type === "Super") return Effect.void;
27
+ const namespaces = visibleNamespaces(ctx, node, effectNamespaces);
28
+ if (!isStaticMember(node.callee, namespaces, "provide")) return Effect.void;
29
+ if (!isInsideEffectProgram(node, namespaces)) return Effect.void;
30
+ return ctx.report(make({
31
+ node,
32
+ message: "Do not provide dependencies inside an Effect operation. Provide the layer once at the application, handler, command, or test boundary."
33
+ }));
34
+ }
35
+ };
36
+ }
37
+ });
38
+ //#endregion
39
+ export { noInlineProvide };
@@ -0,0 +1,6 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-managed-runtime-in-effect.d.ts
4
+ declare const noManagedRuntimeInEffect: _$_oxlint_plugins0.CreateRule;
5
+ //#endregion
6
+ export { noManagedRuntimeInEffect };
@@ -0,0 +1,41 @@
1
+ import { make } from "../vendor/effect-oxlint/Diagnostic.js";
2
+ import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
3
+ import { define, meta } from "../vendor/effect-oxlint/Rule.js";
4
+ import "../vendor/effect-oxlint/index.js";
5
+ import { importedNamespaces, isStaticMember, visibleNamespaces } from "./_effect-namespaces.js";
6
+ import { isInsideEffectProgram } from "./_effect-context.js";
7
+ import * as Effect from "effect/Effect";
8
+ //#region src/rules/no-managed-runtime-in-effect.ts
9
+ /** Keep ManagedRuntime construction at non-Effect host boundaries. */
10
+ const noManagedRuntimeInEffect = define({
11
+ name: "no-managed-runtime-in-effect",
12
+ meta: meta({
13
+ type: "problem",
14
+ description: "Create ManagedRuntime only at a non-Effect host boundary."
15
+ }),
16
+ create: function* () {
17
+ const ctx = yield* RuleContext;
18
+ const effectNamespaces = new Set(["Effect"]);
19
+ const managedRuntimeNamespaces = new Set(["ManagedRuntime"]);
20
+ return {
21
+ ImportDeclaration: (node) => {
22
+ if (node.type !== "ImportDeclaration") return Effect.void;
23
+ for (const name of importedNamespaces(node, "Effect", "effect/Effect")) effectNamespaces.add(name);
24
+ for (const name of importedNamespaces(node, "ManagedRuntime", "effect/ManagedRuntime")) managedRuntimeNamespaces.add(name);
25
+ return Effect.void;
26
+ },
27
+ CallExpression: (node) => {
28
+ if (node.type !== "CallExpression" || node.callee.type === "Super") return Effect.void;
29
+ const runtimes = visibleNamespaces(ctx, node, managedRuntimeNamespaces);
30
+ if (!isStaticMember(node.callee, runtimes, "make")) return Effect.void;
31
+ if (!isInsideEffectProgram(node, visibleNamespaces(ctx, node, effectNamespaces))) return Effect.void;
32
+ return ctx.report(make({
33
+ node,
34
+ message: "Create ManagedRuntime only at a non-Effect host boundary. Use the current Effect environment inside Effect programs."
35
+ }));
36
+ }
37
+ };
38
+ }
39
+ });
40
+ //#endregion
41
+ export { noManagedRuntimeInEffect };
@@ -0,0 +1,6 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-module-mocks.d.ts
4
+ declare const noModuleMocks: _$_oxlint_plugins0.CreateRule;
5
+ //#endregion
6
+ export { noModuleMocks };
@@ -0,0 +1,73 @@
1
+ import { importSource, memberNames, narrow } from "../vendor/effect-oxlint/AST.js";
2
+ import { make } from "../vendor/effect-oxlint/Diagnostic.js";
3
+ import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
4
+ import { define, meta } from "../vendor/effect-oxlint/Rule.js";
5
+ import { findVariableUp } from "../vendor/effect-oxlint/Scope.js";
6
+ import "../vendor/effect-oxlint/index.js";
7
+ import * as Option from "effect/Option";
8
+ import * as Effect from "effect/Effect";
9
+ //#region src/rules/no-module-mocks.ts
10
+ const bannedMethods = new Set(["mock", "spyOn"]);
11
+ const identifierName = (node) => {
12
+ if (node?.type === "Identifier") return node.name;
13
+ };
14
+ const globalTestApi = (name) => {
15
+ if (name === "jest" || name === "vi") return name;
16
+ };
17
+ const importedTestApi = (source, imported) => {
18
+ if (source === "vitest" && imported === "vi") return "vi";
19
+ if (source === "@jest/globals" && imported === "jest") return "jest";
20
+ };
21
+ const testApiFromVariable = (variable, importedBindings) => {
22
+ if (variable.defs.some((definition) => definition.type === "ImportBinding")) return importedBindings.get(variable.name);
23
+ };
24
+ const noModuleMocks = define({
25
+ name: "no-module-mocks",
26
+ meta: meta({
27
+ type: "problem",
28
+ description: "Use Effect service test layers instead of module mocks or method spies."
29
+ }),
30
+ create: function* () {
31
+ const context = yield* RuleContext;
32
+ const importedBindings = /* @__PURE__ */ new Map();
33
+ return {
34
+ ImportDeclaration: (node) => Option.match(narrow(node, "ImportDeclaration"), {
35
+ onNone: () => Effect.void,
36
+ onSome: (declaration) => {
37
+ const source = importSource(declaration);
38
+ for (const specifier of declaration.specifiers) {
39
+ if (specifier.type !== "ImportSpecifier") continue;
40
+ const imported = identifierName(specifier.imported);
41
+ if (imported === void 0) continue;
42
+ const api = importedTestApi(source, imported);
43
+ if (api !== void 0) importedBindings.set(specifier.local.name, api);
44
+ }
45
+ return Effect.void;
46
+ }
47
+ }),
48
+ CallExpression: (node) => Option.match(narrow(node, "CallExpression"), {
49
+ onNone: () => Effect.void,
50
+ onSome: (call) => {
51
+ if (call.callee.type !== "MemberExpression") return Effect.void;
52
+ const names = Option.getOrUndefined(memberNames(call.callee));
53
+ if (names === void 0) return Effect.void;
54
+ const [object, method] = names;
55
+ if (!bannedMethods.has(method)) return Effect.void;
56
+ const variable = Option.getOrUndefined(findVariableUp(context.sourceCode.getScope(call), object));
57
+ let api = globalTestApi(object);
58
+ if (variable !== void 0) {
59
+ api = testApiFromVariable(variable, importedBindings);
60
+ if (api === void 0 && variable.defs.length === 0) api = globalTestApi(object);
61
+ }
62
+ if (api === void 0) return Effect.void;
63
+ return context.report(make({
64
+ node: call,
65
+ message: `Avoid ${api}.${method}(). Replace the external boundary with an Effect service test Layer.`
66
+ }));
67
+ }
68
+ })
69
+ };
70
+ }
71
+ });
72
+ //#endregion
73
+ export { noModuleMocks };
@@ -0,0 +1,6 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-nested-effect-gen.d.ts
4
+ declare const noNestedEffectGen: _$_oxlint_plugins0.CreateRule;
5
+ //#endregion
6
+ export { noNestedEffectGen };
@@ -0,0 +1,39 @@
1
+ import { make } from "../vendor/effect-oxlint/Diagnostic.js";
2
+ import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
3
+ import { define, meta } from "../vendor/effect-oxlint/Rule.js";
4
+ import "../vendor/effect-oxlint/index.js";
5
+ import { importedNamespaces, isStaticMember, visibleNamespaces } from "./_effect-namespaces.js";
6
+ import { isInsideEffectProgram } from "./_effect-context.js";
7
+ import * as Effect from "effect/Effect";
8
+ //#region src/rules/no-nested-effect-gen.ts
9
+ const isDirectYield = (node) => node.parent?.type === "YieldExpression";
10
+ const noNestedEffectGen = define({
11
+ name: "no-nested-effect-gen",
12
+ meta: meta({
13
+ type: "suggestion",
14
+ description: "Flatten directly yielded nested Effect.gen programs."
15
+ }),
16
+ create: function* () {
17
+ const ctx = yield* RuleContext;
18
+ const effectNamespaces = new Set(["Effect"]);
19
+ return {
20
+ ImportDeclaration: (node) => {
21
+ if (node.type !== "ImportDeclaration") return Effect.void;
22
+ for (const name of importedNamespaces(node, "Effect", "effect/Effect")) effectNamespaces.add(name);
23
+ return Effect.void;
24
+ },
25
+ CallExpression: (node) => {
26
+ if (node.type !== "CallExpression" || node.callee.type === "Super") return Effect.void;
27
+ const namespaces = visibleNamespaces(ctx, node, effectNamespaces);
28
+ if (!isStaticMember(node.callee, namespaces, "gen") || !isDirectYield(node)) return Effect.void;
29
+ if (!isInsideEffectProgram(node, namespaces)) return Effect.void;
30
+ return ctx.report(make({
31
+ node,
32
+ message: "Flatten this directly yielded Effect.gen into the owning Effect operation."
33
+ }));
34
+ }
35
+ };
36
+ }
37
+ });
38
+ //#endregion
39
+ export { noNestedEffectGen };
@@ -0,0 +1,6 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-per-call-cache-construction.d.ts
4
+ declare const noPerCallCacheConstruction: _$_oxlint_plugins0.CreateRule;
5
+ //#endregion
6
+ export { noPerCallCacheConstruction };
@@ -0,0 +1,52 @@
1
+ import { make } from "../vendor/effect-oxlint/Diagnostic.js";
2
+ import { RuleContext } from "../vendor/effect-oxlint/RuleContext.js";
3
+ import { define, meta } from "../vendor/effect-oxlint/Rule.js";
4
+ import "../vendor/effect-oxlint/index.js";
5
+ import { importedNamespaces, isStaticMember, visibleNamespaces } from "./_effect-namespaces.js";
6
+ import { enclosingEffectProgram } from "./_effect-context.js";
7
+ import * as Effect from "effect/Effect";
8
+ //#region src/rules/no-per-call-cache-construction.ts
9
+ const cachedEffectOperations = new Set([
10
+ "cached",
11
+ "cachedInvalidateWithTTL",
12
+ "cachedWithTTL"
13
+ ]);
14
+ const cacheConstructors = new Set(["make", "makeWith"]);
15
+ const isOperation = (node, namespaces, operations) => {
16
+ for (const operation of operations) if (isStaticMember(node, namespaces, operation)) return true;
17
+ return false;
18
+ };
19
+ const noPerCallCacheConstruction = define({
20
+ name: "no-per-call-cache-construction",
21
+ meta: meta({
22
+ type: "suggestion",
23
+ description: "Construct shared caches once in their owning layer."
24
+ }),
25
+ create: function* () {
26
+ const ctx = yield* RuleContext;
27
+ const effectNamespaces = new Set(["Effect"]);
28
+ const cacheNamespaces = new Set(["Cache"]);
29
+ return {
30
+ ImportDeclaration: (node) => {
31
+ if (node.type !== "ImportDeclaration") return Effect.void;
32
+ for (const name of importedNamespaces(node, "Effect", "effect/Effect")) effectNamespaces.add(name);
33
+ for (const name of importedNamespaces(node, "Cache", "effect/Cache")) cacheNamespaces.add(name);
34
+ return Effect.void;
35
+ },
36
+ CallExpression: (node) => {
37
+ if (node.type !== "CallExpression" || node.callee.type === "Super") return Effect.void;
38
+ const effects = visibleNamespaces(ctx, node, effectNamespaces);
39
+ const program = enclosingEffectProgram(node, effects);
40
+ if (program === void 0 || program[1] === "gen") return Effect.void;
41
+ const caches = visibleNamespaces(ctx, node, cacheNamespaces);
42
+ if (!(isOperation(node.callee, effects, cachedEffectOperations) || isOperation(node.callee, caches, cacheConstructors))) return Effect.void;
43
+ return ctx.report(make({
44
+ node,
45
+ message: "Construct this cache once in its owning layer. Creating it inside an Effect operation creates new cache state on every call."
46
+ }));
47
+ }
48
+ };
49
+ }
50
+ });
51
+ //#endregion
52
+ export { noPerCallCacheConstruction };
@@ -0,0 +1,6 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-run-collect-on-unbounded-stream.d.ts
4
+ declare const noRunCollectOnUnboundedStream: _$_oxlint_plugins0.CreateRule;
5
+ //#endregion
6
+ export { noRunCollectOnUnboundedStream };