oxlint-plugin-effect 0.2.3 → 0.3.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.
package/README.md CHANGED
@@ -66,7 +66,16 @@ import { Rule, Diagnostic, RuleContext } from "oxlint-plugin-effect";
66
66
 
67
67
  ## Rules
68
68
 
69
- 66 AST-only rules across categories: API bans, global bans, import bans, statement bans, AST patterns, and Effect-context rules. Five presets are available: `core`, `full`, `effect-native`, `functional`, and `strict`.
69
+ 68 AST-only rules across categories: API bans, global bans, import bans, statement bans, AST patterns, and Effect-context rules. Five presets are available: `core`, `full`, `effect-native`, `functional`, and `strict`.
70
+
71
+ `effect/noTryCatch` rejects every JavaScript `try` statement, including
72
+ `try/catch`, bare `try/finally`, and `try/catch/finally`. Use Effect's typed
73
+ failure channel and scoped finalization operators instead.
74
+
75
+ The core presets also reject sequential state mutations hidden in
76
+ `Effect.all(..., { concurrency: 1 })`. The stricter `noManualDataGuard` rule
77
+ requires unknown input to cross a Schema boundary instead of being promoted by
78
+ a hand-written type predicate.
70
79
 
71
80
  ## Development
72
81
 
@@ -35,6 +35,7 @@ declare const core: {
35
35
  readonly "effect/noMakeUnsafe": "error";
36
36
  readonly "effect/noHandRolledTaggedUnion": "error";
37
37
  readonly "effect/noDynamicImports": "error";
38
+ readonly "effect/noEffectAllStepSequencing": "error";
38
39
  readonly "effect/noThrowStatement": "error";
39
40
  readonly "effect/noTryCatch": "error";
40
41
  readonly "effect/noNewPromise": "error";
@@ -35,6 +35,7 @@ const core = {
35
35
  "effect/noMakeUnsafe": "error",
36
36
  "effect/noHandRolledTaggedUnion": "error",
37
37
  "effect/noDynamicImports": "error",
38
+ "effect/noEffectAllStepSequencing": "error",
38
39
  "effect/noThrowStatement": "error",
39
40
  "effect/noTryCatch": "error",
40
41
  "effect/noNewPromise": "error",
@@ -9,6 +9,7 @@ declare const effectNative: {
9
9
  readonly "effect/noNodeBuiltinImport": "error";
10
10
  readonly "effect/noThrowStatement": "error";
11
11
  readonly "effect/noTryCatch": "error";
12
+ readonly "effect/noEffectAllStepSequencing": "error";
12
13
  readonly "effect/noNewPromise": "error";
13
14
  readonly "effect/noNewError": "error";
14
15
  readonly "effect/noReturnNullish": "error";
@@ -9,6 +9,7 @@ const effectNative = {
9
9
  "effect/noNodeBuiltinImport": "error",
10
10
  "effect/noThrowStatement": "error",
11
11
  "effect/noTryCatch": "error",
12
+ "effect/noEffectAllStepSequencing": "error",
12
13
  "effect/noNewPromise": "error",
13
14
  "effect/noNewError": "error",
14
15
  "effect/noReturnNullish": "error",
@@ -53,6 +53,7 @@ declare const full: {
53
53
  readonly "effect/noMakeUnsafe": "error";
54
54
  readonly "effect/noHandRolledTaggedUnion": "error";
55
55
  readonly "effect/noDynamicImports": "error";
56
+ readonly "effect/noEffectAllStepSequencing": "error";
56
57
  readonly "effect/noThrowStatement": "error";
57
58
  readonly "effect/noTryCatch": "error";
58
59
  readonly "effect/noNewPromise": "error";
@@ -53,6 +53,7 @@ const full = {
53
53
  "effect/noMakeUnsafe": "error",
54
54
  "effect/noHandRolledTaggedUnion": "error",
55
55
  "effect/noDynamicImports": "error",
56
+ "effect/noEffectAllStepSequencing": "error",
56
57
  "effect/noThrowStatement": "error",
57
58
  "effect/noTryCatch": "error",
58
59
  "effect/noNewPromise": "error",
@@ -59,6 +59,8 @@ declare const strict: {
59
59
  readonly "effect/noDynamicImports": "error";
60
60
  readonly "effect/noThrowStatement": "error";
61
61
  readonly "effect/noTryCatch": "error";
62
+ readonly "effect/noEffectAllStepSequencing": "error";
63
+ readonly "effect/noManualDataGuard": "error";
62
64
  readonly "effect/noNewPromise": "error";
63
65
  readonly "effect/noNewError": "error";
64
66
  readonly "effect/noReturnNullish": "error";
@@ -59,6 +59,8 @@ const strict = {
59
59
  "effect/noDynamicImports": "error",
60
60
  "effect/noThrowStatement": "error",
61
61
  "effect/noTryCatch": "error",
62
+ "effect/noEffectAllStepSequencing": "error",
63
+ "effect/noManualDataGuard": "error",
62
64
  "effect/noNewPromise": "error",
63
65
  "effect/noNewError": "error",
64
66
  "effect/noReturnNullish": "error",
@@ -11,6 +11,7 @@ import { noArrowLadder } from "./no-arrow-ladder.js";
11
11
  import { noAsyncFunction } from "./no-async-function.js";
12
12
  import { noCatchAllToMapError } from "./no-catch-all-to-map-error.js";
13
13
  import { noDynamicImports } from "./no-dynamic-imports.js";
14
+ import { noEffectAllStepSequencing } from "./no-effect-all-step-sequencing.js";
14
15
  import { noEffectFnGenerator } from "./no-effect-fn-generator.js";
15
16
  import { noEffectGenAdapter } from "./no-effect-gen-adapter.js";
16
17
  import { noEffectMapFlatten } from "./no-effect-map-flatten.js";
@@ -32,6 +33,7 @@ import { noHandRolledTaggedUnion } from "./no-hand-rolled-tagged-union.js";
32
33
  import { noIifeWrapper } from "./no-iife-wrapper.js";
33
34
  import { noInstanceofSchema } from "./no-instanceof-schema.js";
34
35
  import { noMakeUnsafe } from "./no-make-unsafe.js";
36
+ import { noManualDataGuard } from "./no-manual-data-guard.js";
35
37
  import { noManualEffectChannels } from "./no-manual-effect-channels.js";
36
38
  import { noMatchEffectBranch } from "./no-match-effect-branch.js";
37
39
  import { noMatchVoidBranch } from "./no-match-void-branch.js";
@@ -67,7 +69,7 @@ import { noTryCatch } from "./no-try-catch.js";
67
69
 
68
70
  //#region src/rules/index.d.ts
69
71
  declare namespace index_d_exports {
70
- export { noArrowLadder, noAsyncFunction, noCatchAllToMapError, noDynamicImports, noEffectAs, noEffectAsync, noEffectBind, noEffectDo, noEffectFnGenerator, noEffectGenAdapter, noEffectMapFlatten, noEffectMapVoid, noEffectNever, noEffectOrElseLadder, noEffectSucceedString, noEffectSucceedVariable, noEffectSucceedVoid, noEffectSyncConsole, noEffectSyncWrapper, noEffectTypeAlias, noEffectWrapperAlias, noExtendsNativeError, noFlatmapLadder, noFromNullableCoalesce, noGlobalErrorInCatch, noGlobalErrorInFailure, noGlobals, noHandRolledTaggedUnion, noIfStatement, noIifeWrapper, noInlineProvide, noInstanceofSchema, noMakeUnsafe, noManualEffectChannels, noMatchEffectBranch, noMatchVoidBranch, noMultipleEffectProvide, noNestedEffectCall, noNestedEffectGen, noNestedPipe, noNewError, noNewPromise, noNodeBuiltinImport, noOptionAs, noOptionBooleanNormalization, noPositionalLogError, noPromiseControlFlowInTests, noRedundantSchemaTagIdentifier, noReturnInArrow, noReturnNullish, noRunInEffectGen, noRuntimeRunFork, noSchemaStruct, noSchemaStructWithTag, noSchemaUnionOfLiterals, noSleepInTests, noSpread, noStringSentinelConst, noSwitchStatement, noTernary, noThrowStatement, noTryCatch, noUnnecessaryArrowBlock, noUnnecessaryEffectGen, noUnnecessaryPipe, noUnnecessaryPipeChain };
72
+ export { noArrowLadder, noAsyncFunction, noCatchAllToMapError, noDynamicImports, noEffectAllStepSequencing, noEffectAs, noEffectAsync, noEffectBind, noEffectDo, noEffectFnGenerator, noEffectGenAdapter, noEffectMapFlatten, noEffectMapVoid, noEffectNever, noEffectOrElseLadder, noEffectSucceedString, noEffectSucceedVariable, noEffectSucceedVoid, noEffectSyncConsole, noEffectSyncWrapper, noEffectTypeAlias, noEffectWrapperAlias, noExtendsNativeError, noFlatmapLadder, noFromNullableCoalesce, noGlobalErrorInCatch, noGlobalErrorInFailure, noGlobals, noHandRolledTaggedUnion, noIfStatement, noIifeWrapper, noInlineProvide, noInstanceofSchema, noMakeUnsafe, noManualDataGuard, noManualEffectChannels, noMatchEffectBranch, noMatchVoidBranch, noMultipleEffectProvide, noNestedEffectCall, noNestedEffectGen, noNestedPipe, noNewError, noNewPromise, noNodeBuiltinImport, noOptionAs, noOptionBooleanNormalization, noPositionalLogError, noPromiseControlFlowInTests, noRedundantSchemaTagIdentifier, noReturnInArrow, noReturnNullish, noRunInEffectGen, noRuntimeRunFork, noSchemaStruct, noSchemaStructWithTag, noSchemaUnionOfLiterals, noSleepInTests, noSpread, noStringSentinelConst, noSwitchStatement, noTernary, noThrowStatement, noTryCatch, noUnnecessaryArrowBlock, noUnnecessaryEffectGen, noUnnecessaryPipe, noUnnecessaryPipeChain };
71
73
  }
72
74
  //#endregion
73
- export { index_d_exports, noArrowLadder, noAsyncFunction, noCatchAllToMapError, noDynamicImports, noEffectAs, noEffectAsync, noEffectBind, noEffectDo, noEffectFnGenerator, noEffectGenAdapter, noEffectMapFlatten, noEffectMapVoid, noEffectNever, noEffectOrElseLadder, noEffectSucceedString, noEffectSucceedVariable, noEffectSucceedVoid, noEffectSyncConsole, noEffectSyncWrapper, noEffectTypeAlias, noEffectWrapperAlias, noExtendsNativeError, noFlatmapLadder, noFromNullableCoalesce, noGlobalErrorInCatch, noGlobalErrorInFailure, noGlobals, noHandRolledTaggedUnion, noIfStatement, noIifeWrapper, noInlineProvide, noInstanceofSchema, noMakeUnsafe, noManualEffectChannels, noMatchEffectBranch, noMatchVoidBranch, noMultipleEffectProvide, noNestedEffectCall, noNestedEffectGen, noNestedPipe, noNewError, noNewPromise, noNodeBuiltinImport, noOptionAs, noOptionBooleanNormalization, noPositionalLogError, noPromiseControlFlowInTests, noRedundantSchemaTagIdentifier, noReturnInArrow, noReturnNullish, noRunInEffectGen, noRuntimeRunFork, noSchemaStruct, noSchemaStructWithTag, noSchemaUnionOfLiterals, noSleepInTests, noSpread, noStringSentinelConst, noSwitchStatement, noTernary, noThrowStatement, noTryCatch, noUnnecessaryArrowBlock, noUnnecessaryEffectGen, noUnnecessaryPipe, noUnnecessaryPipeChain };
75
+ export { index_d_exports, noArrowLadder, noAsyncFunction, noCatchAllToMapError, noDynamicImports, noEffectAllStepSequencing, noEffectAs, noEffectAsync, noEffectBind, noEffectDo, noEffectFnGenerator, noEffectGenAdapter, noEffectMapFlatten, noEffectMapVoid, noEffectNever, noEffectOrElseLadder, noEffectSucceedString, noEffectSucceedVariable, noEffectSucceedVoid, noEffectSyncConsole, noEffectSyncWrapper, noEffectTypeAlias, noEffectWrapperAlias, noExtendsNativeError, noFlatmapLadder, noFromNullableCoalesce, noGlobalErrorInCatch, noGlobalErrorInFailure, noGlobals, noHandRolledTaggedUnion, noIfStatement, noIifeWrapper, noInlineProvide, noInstanceofSchema, noMakeUnsafe, noManualDataGuard, noManualEffectChannels, noMatchEffectBranch, noMatchVoidBranch, noMultipleEffectProvide, noNestedEffectCall, noNestedEffectGen, noNestedPipe, noNewError, noNewPromise, noNodeBuiltinImport, noOptionAs, noOptionBooleanNormalization, noPositionalLogError, noPromiseControlFlowInTests, noRedundantSchemaTagIdentifier, noReturnInArrow, noReturnNullish, noRunInEffectGen, noRuntimeRunFork, noSchemaStruct, noSchemaStructWithTag, noSchemaUnionOfLiterals, noSleepInTests, noSpread, noStringSentinelConst, noSwitchStatement, noTernary, noThrowStatement, noTryCatch, noUnnecessaryArrowBlock, noUnnecessaryEffectGen, noUnnecessaryPipe, noUnnecessaryPipeChain };
@@ -12,6 +12,7 @@ import { noArrowLadder } from "./no-arrow-ladder.js";
12
12
  import { noAsyncFunction } from "./no-async-function.js";
13
13
  import { noCatchAllToMapError } from "./no-catch-all-to-map-error.js";
14
14
  import { noDynamicImports } from "./no-dynamic-imports.js";
15
+ import { noEffectAllStepSequencing } from "./no-effect-all-step-sequencing.js";
15
16
  import { noEffectFnGenerator } from "./no-effect-fn-generator.js";
16
17
  import { noEffectGenAdapter } from "./no-effect-gen-adapter.js";
17
18
  import { noEffectMapFlatten } from "./no-effect-map-flatten.js";
@@ -33,6 +34,7 @@ import { noHandRolledTaggedUnion } from "./no-hand-rolled-tagged-union.js";
33
34
  import { noIifeWrapper } from "./no-iife-wrapper.js";
34
35
  import { noInstanceofSchema } from "./no-instanceof-schema.js";
35
36
  import { noMakeUnsafe } from "./no-make-unsafe.js";
37
+ import { noManualDataGuard } from "./no-manual-data-guard.js";
36
38
  import { noManualEffectChannels } from "./no-manual-effect-channels.js";
37
39
  import { noMatchEffectBranch } from "./no-match-effect-branch.js";
38
40
  import { noMatchVoidBranch } from "./no-match-void-branch.js";
@@ -71,6 +73,7 @@ var rules_exports = /* @__PURE__ */ __exportAll({
71
73
  noAsyncFunction: () => noAsyncFunction,
72
74
  noCatchAllToMapError: () => noCatchAllToMapError,
73
75
  noDynamicImports: () => noDynamicImports,
76
+ noEffectAllStepSequencing: () => noEffectAllStepSequencing,
74
77
  noEffectAs: () => noEffectAs,
75
78
  noEffectAsync: () => noEffectAsync,
76
79
  noEffectBind: () => noEffectBind,
@@ -100,6 +103,7 @@ var rules_exports = /* @__PURE__ */ __exportAll({
100
103
  noInlineProvide: () => noInlineProvide,
101
104
  noInstanceofSchema: () => noInstanceofSchema,
102
105
  noMakeUnsafe: () => noMakeUnsafe,
106
+ noManualDataGuard: () => noManualDataGuard,
103
107
  noManualEffectChannels: () => noManualEffectChannels,
104
108
  noMatchEffectBranch: () => noMatchEffectBranch,
105
109
  noMatchVoidBranch: () => noMatchVoidBranch,
@@ -135,4 +139,4 @@ var rules_exports = /* @__PURE__ */ __exportAll({
135
139
  noUnnecessaryPipeChain: () => noUnnecessaryPipeChain
136
140
  });
137
141
  //#endregion
138
- export { noArrowLadder, noAsyncFunction, noCatchAllToMapError, noDynamicImports, noEffectAs, noEffectAsync, noEffectBind, noEffectDo, noEffectFnGenerator, noEffectGenAdapter, noEffectMapFlatten, noEffectMapVoid, noEffectNever, noEffectOrElseLadder, noEffectSucceedString, noEffectSucceedVariable, noEffectSucceedVoid, noEffectSyncConsole, noEffectSyncWrapper, noEffectTypeAlias, noEffectWrapperAlias, noExtendsNativeError, noFlatmapLadder, noFromNullableCoalesce, noGlobalErrorInCatch, noGlobalErrorInFailure, noGlobals, noHandRolledTaggedUnion, noIfStatement, noIifeWrapper, noInlineProvide, noInstanceofSchema, noMakeUnsafe, noManualEffectChannels, noMatchEffectBranch, noMatchVoidBranch, noMultipleEffectProvide, noNestedEffectCall, noNestedEffectGen, noNestedPipe, noNewError, noNewPromise, noNodeBuiltinImport, noOptionAs, noOptionBooleanNormalization, noPositionalLogError, noPromiseControlFlowInTests, noRedundantSchemaTagIdentifier, noReturnInArrow, noReturnNullish, noRunInEffectGen, noRuntimeRunFork, noSchemaStruct, noSchemaStructWithTag, noSchemaUnionOfLiterals, noSleepInTests, noSpread, noStringSentinelConst, noSwitchStatement, noTernary, noThrowStatement, noTryCatch, noUnnecessaryArrowBlock, noUnnecessaryEffectGen, noUnnecessaryPipe, noUnnecessaryPipeChain, rules_exports };
142
+ export { noArrowLadder, noAsyncFunction, noCatchAllToMapError, noDynamicImports, noEffectAllStepSequencing, noEffectAs, noEffectAsync, noEffectBind, noEffectDo, noEffectFnGenerator, noEffectGenAdapter, noEffectMapFlatten, noEffectMapVoid, noEffectNever, noEffectOrElseLadder, noEffectSucceedString, noEffectSucceedVariable, noEffectSucceedVoid, noEffectSyncConsole, noEffectSyncWrapper, noEffectTypeAlias, noEffectWrapperAlias, noExtendsNativeError, noFlatmapLadder, noFromNullableCoalesce, noGlobalErrorInCatch, noGlobalErrorInFailure, noGlobals, noHandRolledTaggedUnion, noIfStatement, noIifeWrapper, noInlineProvide, noInstanceofSchema, noMakeUnsafe, noManualDataGuard, noManualEffectChannels, noMatchEffectBranch, noMatchVoidBranch, noMultipleEffectProvide, noNestedEffectCall, noNestedEffectGen, noNestedPipe, noNewError, noNewPromise, noNodeBuiltinImport, noOptionAs, noOptionBooleanNormalization, noPositionalLogError, noPromiseControlFlowInTests, noRedundantSchemaTagIdentifier, noReturnInArrow, noReturnNullish, noRunInEffectGen, noRuntimeRunFork, noSchemaStruct, noSchemaStructWithTag, noSchemaUnionOfLiterals, noSleepInTests, noSpread, noStringSentinelConst, noSwitchStatement, noTernary, noThrowStatement, noTryCatch, noUnnecessaryArrowBlock, noUnnecessaryEffectGen, noUnnecessaryPipe, noUnnecessaryPipeChain, rules_exports };
@@ -0,0 +1,6 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-effect-all-step-sequencing.d.ts
4
+ declare const noEffectAllStepSequencing: _$_oxlint_plugins0.CreateRule;
5
+ //#endregion
6
+ export { noEffectAllStepSequencing };
@@ -0,0 +1,63 @@
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 * as Effect from "effect/Effect";
6
+ //#region src/rules/no-effect-all-step-sequencing.ts
7
+ const isAstNode = (value) => typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
8
+ const statefulSteps = new Set([
9
+ "Atom.set",
10
+ "Effect.log",
11
+ "Effect.logDebug",
12
+ "Effect.logError",
13
+ "Effect.logFatal",
14
+ "Effect.logInfo",
15
+ "Effect.logTrace",
16
+ "Effect.logWarning",
17
+ "Fiber.interrupt",
18
+ "Reactivity.invalidate",
19
+ "Ref.set",
20
+ "SubscriptionRef.set"
21
+ ]);
22
+ const memberName = (value) => {
23
+ if (!isAstNode(value) || value.type !== "MemberExpression") return void 0;
24
+ const object = value["object"];
25
+ const property = value["property"];
26
+ if (!isAstNode(object) || !isAstNode(property)) return void 0;
27
+ if (object.type !== "Identifier" || property.type !== "Identifier") return void 0;
28
+ return typeof object["name"] === "string" && typeof property["name"] === "string" ? `${object["name"]}.${property["name"]}` : void 0;
29
+ };
30
+ const containsStatefulStep = (value) => {
31
+ if (Array.isArray(value)) return value.some(containsStatefulStep);
32
+ if (!isAstNode(value)) return false;
33
+ if (value.type === "CallExpression" && statefulSteps.has(memberName(value["callee"]) ?? "")) return true;
34
+ return Object.entries(value).some(([key, child]) => key !== "parent" && containsStatefulStep(child));
35
+ };
36
+ const hasSequentialConcurrency = (value) => {
37
+ if (!isAstNode(value) || value.type !== "ObjectExpression") return false;
38
+ return (Array.isArray(value["properties"]) ? value["properties"] : []).some((property) => {
39
+ if (!isAstNode(property) || property.type !== "Property") return false;
40
+ const key = property["key"];
41
+ const propertyValue = property["value"];
42
+ return isAstNode(key) && (key["name"] === "concurrency" || key["value"] === "concurrency") && isAstNode(propertyValue) && propertyValue["value"] === 1;
43
+ });
44
+ };
45
+ const noEffectAllStepSequencing = define({
46
+ name: "no-effect-all-step-sequencing",
47
+ meta: meta({
48
+ type: "suggestion",
49
+ description: "Use a linear Effect pipeline for sequential stateful steps."
50
+ }),
51
+ create: function* () {
52
+ const ctx = yield* RuleContext;
53
+ return { CallExpression: (node) => {
54
+ if (memberName(node.callee) !== "Effect.all" || node.arguments.length < 2) return Effect.void;
55
+ return hasSequentialConcurrency(node.arguments[1]) && containsStatefulStep(node.arguments[0]) ? ctx.report(make({
56
+ node,
57
+ message: "Avoid Effect.all for sequential side-effect steps. Use one explicit linear pipeline with Effect.andThen or Effect.flatMap."
58
+ })) : Effect.void;
59
+ } };
60
+ }
61
+ });
62
+ //#endregion
63
+ export { noEffectAllStepSequencing };
@@ -0,0 +1,6 @@
1
+ import * as _$_oxlint_plugins0 from "@oxlint/plugins";
2
+
3
+ //#region src/rules/no-manual-data-guard.d.ts
4
+ declare const noManualDataGuard: _$_oxlint_plugins0.CreateRule;
5
+ //#endregion
6
+ export { noManualDataGuard };
@@ -0,0 +1,56 @@
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 * as Effect from "effect/Effect";
6
+ //#region src/rules/no-manual-data-guard.ts
7
+ const isAstNode = (value) => typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
8
+ const functionTypes = new Set([
9
+ "ArrowFunctionExpression",
10
+ "FunctionDeclaration",
11
+ "FunctionExpression"
12
+ ]);
13
+ const findFunction = (node) => {
14
+ let parent = node.parent;
15
+ while (isAstNode(parent)) {
16
+ if (functionTypes.has(parent.type)) return parent;
17
+ parent = parent.parent;
18
+ }
19
+ };
20
+ const containsNodeType = (value, nodeTypes) => {
21
+ if (Array.isArray(value)) return value.some((item) => containsNodeType(item, nodeTypes));
22
+ if (!isAstNode(value)) return false;
23
+ if (nodeTypes.has(value.type)) return true;
24
+ return Object.entries(value).some(([key, child]) => key !== "parent" && containsNodeType(child, nodeTypes));
25
+ };
26
+ const containsInstanceOf = (value) => {
27
+ if (Array.isArray(value)) return value.some(containsInstanceOf);
28
+ if (!isAstNode(value)) return false;
29
+ if (value.type === "BinaryExpression" && value["operator"] === "instanceof") return true;
30
+ return Object.entries(value).some(([key, child]) => key !== "parent" && containsInstanceOf(child));
31
+ };
32
+ const parameterName = (parameter) => {
33
+ if (!isAstNode(parameter)) return void 0;
34
+ if ((parameter.type === "Identifier" || parameter.type === "BindingIdentifier") && typeof parameter["name"] === "string") return parameter["name"];
35
+ };
36
+ const noManualDataGuard = define({
37
+ name: "no-manual-data-guard",
38
+ meta: meta({
39
+ type: "suggestion",
40
+ description: "Decode unknown data with Schema instead of asserting it with a type predicate."
41
+ }),
42
+ create: function* () {
43
+ const ctx = yield* RuleContext;
44
+ return { TSTypePredicate: (node) => {
45
+ const fn = findFunction(node);
46
+ if (fn === void 0 || containsInstanceOf(fn["body"])) return Effect.void;
47
+ const predicateParameter = parameterName(node.parameterName);
48
+ return (Array.isArray(fn["params"]) ? fn["params"] : []).some((parameter) => parameterName(parameter) === predicateParameter && containsNodeType(parameter, new Set(["TSUnknownKeyword", "TSAnyKeyword"]))) ? ctx.report(make({
49
+ node,
50
+ message: "Avoid promoting unknown data with a manual type predicate. Decode it with the canonical Schema first."
51
+ })) : Effect.void;
52
+ } };
53
+ }
54
+ });
55
+ //#endregion
56
+ export { noManualDataGuard };
@@ -8,7 +8,7 @@ import * as Effect from "effect/Effect";
8
8
  import * as Ref from "effect/Ref";
9
9
  //#region src/rules/no-try-catch.ts
10
10
  /**
11
- * Ban `try/catch` statements. Context-aware messaging.
11
+ * Ban all `try` statements: try/catch, try/finally, and try/catch/finally.
12
12
  *
13
13
  * Inside Effect.gen/fn: "Use Effect.try or Effect.tryPromise"
14
14
  * Outside: "Wrap with Effect.fn and use Effect.try for error handling"
@@ -19,14 +19,14 @@ const noTryCatch = define({
19
19
  name: "no-try-catch",
20
20
  meta: meta({
21
21
  type: "suggestion",
22
- description: "Avoid try/catch. Use Effect.try or Effect.tryPromise."
22
+ description: "Avoid try/catch/finally blocks. Use Effect error handling and finalization operators."
23
23
  }),
24
24
  create: function* () {
25
25
  const ctx = yield* RuleContext;
26
26
  const [depth, tracker] = yield* makeEffectContextTracker;
27
27
  return merge(tracker, { TryStatement: (node) => Effect.flatMap(Ref.get(depth), (d) => ctx.report(make({
28
28
  node,
29
- message: d > 0 ? "Avoid try/catch inside Effect.gen/fn. Use Effect.try({ try: () => ..., catch: (e) => new MyError({ cause: e }) })." : "Avoid try/catch. Wrap this function with Effect.fn and use Effect.try or Effect.tryPromise for error handling."
29
+ message: d > 0 ? "Avoid try/catch/finally inside Effect.gen/fn. Use Effect.try or Effect.tryPromise for failures and Effect.ensuring or Effect.acquireUseRelease for finalization." : "Avoid try/catch/finally. Use Effect.try or Effect.tryPromise for failures and Effect.ensuring or Effect.acquireUseRelease for finalization."
30
30
  }))) });
31
31
  }
32
32
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-effect",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "oxlint plugin with Effect-specific lint rules",
5
5
  "license": "MIT",
6
6
  "repository": {