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 +10 -1
- package/dist/presets/core.d.ts +1 -0
- package/dist/presets/core.js +1 -0
- package/dist/presets/effect-native.d.ts +1 -0
- package/dist/presets/effect-native.js +1 -0
- package/dist/presets/full.d.ts +1 -0
- package/dist/presets/full.js +1 -0
- package/dist/presets/strict.d.ts +2 -0
- package/dist/presets/strict.js +2 -0
- package/dist/rules/index.d.ts +4 -2
- package/dist/rules/index.js +5 -1
- package/dist/rules/no-effect-all-step-sequencing.d.ts +6 -0
- package/dist/rules/no-effect-all-step-sequencing.js +63 -0
- package/dist/rules/no-manual-data-guard.d.ts +6 -0
- package/dist/rules/no-manual-data-guard.js +56 -0
- package/dist/rules/no-try-catch.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,16 @@ import { Rule, Diagnostic, RuleContext } from "oxlint-plugin-effect";
|
|
|
66
66
|
|
|
67
67
|
## Rules
|
|
68
68
|
|
|
69
|
-
|
|
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
|
|
package/dist/presets/core.d.ts
CHANGED
|
@@ -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";
|
package/dist/presets/core.js
CHANGED
|
@@ -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",
|
package/dist/presets/full.d.ts
CHANGED
|
@@ -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";
|
package/dist/presets/full.js
CHANGED
|
@@ -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",
|
package/dist/presets/strict.d.ts
CHANGED
|
@@ -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";
|
package/dist/presets/strict.js
CHANGED
|
@@ -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",
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/rules/index.js
CHANGED
|
@@ -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,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,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
|
|
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
|
|
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
|
|
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
|
});
|