oxlint-plugin-effect 0.0.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/.effect-lsp.json +60 -0
- package/.oxlintrc.json +33 -0
- package/package.json +51 -0
- package/scripts/add-rule.ts +87 -0
- package/scripts/codegen.ts +108 -0
- package/src/index.ts +9 -0
- package/src/plugin.ts +16 -0
- package/src/presets/core.ts +36 -0
- package/src/presets/effect-native.ts +41 -0
- package/src/presets/full.ts +84 -0
- package/src/presets/index.ts +3 -0
- package/src/rules/_effect-context.ts +28 -0
- package/src/rules/index.ts +83 -0
- package/src/rules/no-arrow-ladder.ts +71 -0
- package/src/rules/no-async-function.ts +48 -0
- package/src/rules/no-console-in-effect.ts +46 -0
- package/src/rules/no-date-in-effect.ts +61 -0
- package/src/rules/no-dynamic-import.ts +13 -0
- package/src/rules/no-effect-as.ts +14 -0
- package/src/rules/no-effect-async.ts +14 -0
- package/src/rules/no-effect-bind.ts +13 -0
- package/src/rules/no-effect-do.ts +14 -0
- package/src/rules/no-effect-fn-generator.ts +59 -0
- package/src/rules/no-effect-gen-adapter.ts +51 -0
- package/src/rules/no-effect-map-void.ts +81 -0
- package/src/rules/no-effect-never.ts +14 -0
- package/src/rules/no-effect-orElse-ladder.ts +80 -0
- package/src/rules/no-effect-succeed-string.ts +53 -0
- package/src/rules/no-effect-succeed-variable.ts +51 -0
- package/src/rules/no-effect-succeed-void.ts +64 -0
- package/src/rules/no-effect-sync-console.ts +92 -0
- package/src/rules/no-effect-sync-wrapper.ts +68 -0
- package/src/rules/no-effect-type-alias.ts +72 -0
- package/src/rules/no-effect-wrapper-alias.ts +98 -0
- package/src/rules/no-extends-native-error.ts +55 -0
- package/src/rules/no-fetch-in-effect.ts +47 -0
- package/src/rules/no-flatmap-ladder.ts +79 -0
- package/src/rules/no-fromnullable-coalesce.ts +63 -0
- package/src/rules/no-global-console.ts +17 -0
- package/src/rules/no-global-date.ts +20 -0
- package/src/rules/no-global-fetch.ts +14 -0
- package/src/rules/no-global-random.ts +13 -0
- package/src/rules/no-global-timers.ts +17 -0
- package/src/rules/no-if-statement.ts +16 -0
- package/src/rules/no-iife-wrapper.ts +52 -0
- package/src/rules/no-inline-runtime-provide.ts +42 -0
- package/src/rules/no-instanceof-schema.ts +43 -0
- package/src/rules/no-json-in-effect.ts +45 -0
- package/src/rules/no-json-parse.ts +18 -0
- package/src/rules/no-manual-effect-channels.ts +59 -0
- package/src/rules/no-match-effect-branch.ts +83 -0
- package/src/rules/no-match-void-branch.ts +73 -0
- package/src/rules/no-nested-effect-call.ts +59 -0
- package/src/rules/no-nested-effect-gen.ts +44 -0
- package/src/rules/no-nested-pipe.ts +76 -0
- package/src/rules/no-new-error.ts +17 -0
- package/src/rules/no-new-promise.ts +14 -0
- package/src/rules/no-node-builtin-import.ts +70 -0
- package/src/rules/no-option-as.ts +13 -0
- package/src/rules/no-option-boolean-normalization.ts +89 -0
- package/src/rules/no-platform-globals.ts +117 -0
- package/src/rules/no-process-env-in-effect.ts +39 -0
- package/src/rules/no-process-env.ts +13 -0
- package/src/rules/no-random-in-effect.ts +39 -0
- package/src/rules/no-return-in-arrow.ts +77 -0
- package/src/rules/no-return-null.ts +47 -0
- package/src/rules/no-run-in-effect.ts +18 -0
- package/src/rules/no-runtime-run-fork.ts +14 -0
- package/src/rules/no-string-sentinel-const.ts +50 -0
- package/src/rules/no-switch-statement.ts +13 -0
- package/src/rules/no-ternary.ts +32 -0
- package/src/rules/no-throw-in-effect-gen.ts +42 -0
- package/src/rules/no-throw-statement.ts +13 -0
- package/src/rules/no-timers-in-effect.ts +47 -0
- package/src/rules/no-try-catch-in-effect-gen.ts +42 -0
- package/src/rules/no-try-catch.ts +14 -0
- package/src/rules/no-unnecessary-arrow-block.ts +52 -0
- package/src/rules/no-unnecessary-effect-gen.ts +85 -0
- package/src/rules/no-unnecessary-pipe.ts +46 -0
- package/src/vendor/effect-oxlint/AST.ts +516 -0
- package/src/vendor/effect-oxlint/Comment.ts +86 -0
- package/src/vendor/effect-oxlint/Diagnostic.ts +199 -0
- package/src/vendor/effect-oxlint/Plugin.ts +71 -0
- package/src/vendor/effect-oxlint/Rule.ts +590 -0
- package/src/vendor/effect-oxlint/RuleContext.ts +144 -0
- package/src/vendor/effect-oxlint/Scope.ts +177 -0
- package/src/vendor/effect-oxlint/SourceCode.ts +377 -0
- package/src/vendor/effect-oxlint/Testing.ts +1227 -0
- package/src/vendor/effect-oxlint/Token.ts +131 -0
- package/src/vendor/effect-oxlint/Visitor.ts +287 -0
- package/src/vendor/effect-oxlint/index.ts +88 -0
- package/tests/ban-rules.test.ts +252 -0
- package/tests/context-rules.test.ts +281 -0
- package/tests/pattern-rules.test.ts +258 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `Effect.runSync`, `Effect.runPromise`, `Effect.runFork`, `Effect.runCallback`.
|
|
3
|
+
*
|
|
4
|
+
* Inside Effect context, use Runtime or Effect.services pattern.
|
|
5
|
+
*
|
|
6
|
+
* Source: language-service/runEffectInsideEffect
|
|
7
|
+
*/
|
|
8
|
+
import { Rule } from "../vendor/effect-oxlint/index.js"
|
|
9
|
+
|
|
10
|
+
export const noRunInEffect = Rule.banMember(
|
|
11
|
+
"Effect",
|
|
12
|
+
["runSync", "runPromise", "runFork", "runCallback"],
|
|
13
|
+
{
|
|
14
|
+
message:
|
|
15
|
+
"Avoid Effect.run* calls. Use Effect.services (v4) or Runtime pattern instead.",
|
|
16
|
+
meta: { type: "problem" },
|
|
17
|
+
},
|
|
18
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `Runtime.runFork`.
|
|
3
|
+
*
|
|
4
|
+
* Use `forkScoped`, `Stream`, or runtime-provided layers.
|
|
5
|
+
*
|
|
6
|
+
* Source: biome-effect-linting-rules/no-runtime-runfork
|
|
7
|
+
*/
|
|
8
|
+
import { Rule } from "../vendor/effect-oxlint/index.js"
|
|
9
|
+
|
|
10
|
+
export const noRuntimeRunFork = Rule.banMember("Runtime", "runFork", {
|
|
11
|
+
message:
|
|
12
|
+
"Avoid Runtime.runFork. Use forkScoped, Stream, or runtime-provided layers.",
|
|
13
|
+
meta: { type: "problem" },
|
|
14
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `const x = "some-string"` string literal const declarations.
|
|
3
|
+
*
|
|
4
|
+
* Use tagged unions, Option, or meaningful domain values.
|
|
5
|
+
*
|
|
6
|
+
* Source: biome-effect-linting-rules/no-string-sentinel-const
|
|
7
|
+
*
|
|
8
|
+
* Note: Very aggressive — should probably be off by default or in a strict preset only.
|
|
9
|
+
*/
|
|
10
|
+
import { Diagnostic, Rule } from "../vendor/effect-oxlint/index.js"
|
|
11
|
+
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
12
|
+
import * as Effect from "effect/Effect"
|
|
13
|
+
|
|
14
|
+
export const noStringSentinelConst = Rule.define({
|
|
15
|
+
name: "no-string-sentinel-const",
|
|
16
|
+
meta: Rule.meta({
|
|
17
|
+
type: "suggestion",
|
|
18
|
+
description: "Avoid string literal const declarations. Use tagged unions or domain values.",
|
|
19
|
+
}),
|
|
20
|
+
create: function* () {
|
|
21
|
+
const ctx = yield* RuleContext
|
|
22
|
+
return {
|
|
23
|
+
VariableDeclarator: (node) => {
|
|
24
|
+
if (!("init" in node) || node.init == null) return Effect.void
|
|
25
|
+
const init = node.init as unknown as Record<string, unknown>
|
|
26
|
+
|
|
27
|
+
if (init["type"] === "Literal" && typeof init["value"] === "string") {
|
|
28
|
+
// Check parent is const declaration
|
|
29
|
+
if (
|
|
30
|
+
"parent" in node &&
|
|
31
|
+
node.parent != null &&
|
|
32
|
+
typeof node.parent === "object" &&
|
|
33
|
+
"type" in node.parent &&
|
|
34
|
+
node.parent.type === "VariableDeclaration" &&
|
|
35
|
+
"kind" in node.parent &&
|
|
36
|
+
node.parent.kind === "const"
|
|
37
|
+
) {
|
|
38
|
+
return ctx.report(
|
|
39
|
+
Diagnostic.make({
|
|
40
|
+
node,
|
|
41
|
+
message: "Avoid string literal const. Use tagged unions, Option, or domain values.",
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return Effect.void
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `switch` statements.
|
|
3
|
+
*
|
|
4
|
+
* Use `Match.value`, `Option.match`, `Either.match` instead.
|
|
5
|
+
*
|
|
6
|
+
* Source: biome-effect-linting-rules/no-switch-statement
|
|
7
|
+
*/
|
|
8
|
+
import { Rule } from "../vendor/effect-oxlint/index.js"
|
|
9
|
+
|
|
10
|
+
export const noSwitchStatement = Rule.banStatement("SwitchStatement", {
|
|
11
|
+
message: "Avoid switch statements. Use Match.value, Option.match, or Either.match.",
|
|
12
|
+
meta: { type: "suggestion" },
|
|
13
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban ternary expressions in Effect codebases.
|
|
3
|
+
*
|
|
4
|
+
* Use `Option.match`, `Either.match`, `Match.value` instead.
|
|
5
|
+
*
|
|
6
|
+
* Source: biome-effect-linting-rules/no-ternary
|
|
7
|
+
*
|
|
8
|
+
* Note: Very opinionated — recommended for strict functional presets only.
|
|
9
|
+
*/
|
|
10
|
+
import { Diagnostic, Rule } from "../vendor/effect-oxlint/index.js"
|
|
11
|
+
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
12
|
+
import * as Effect from "effect/Effect"
|
|
13
|
+
|
|
14
|
+
export const noTernary = Rule.define({
|
|
15
|
+
name: "no-ternary",
|
|
16
|
+
meta: Rule.meta({
|
|
17
|
+
type: "suggestion",
|
|
18
|
+
description: "Avoid ternary expressions. Use Option.match, Either.match, or Match.value.",
|
|
19
|
+
}),
|
|
20
|
+
create: function* () {
|
|
21
|
+
const ctx = yield* RuleContext
|
|
22
|
+
return {
|
|
23
|
+
ConditionalExpression: (node) =>
|
|
24
|
+
ctx.report(
|
|
25
|
+
Diagnostic.make({
|
|
26
|
+
node,
|
|
27
|
+
message: "Avoid ternary expressions. Use Option.match, Either.match, or Match.value.",
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `throw` statements inside Effect.gen/fn generators.
|
|
3
|
+
*
|
|
4
|
+
* Use Effect.fail with tagged errors instead.
|
|
5
|
+
*
|
|
6
|
+
* Source: effect convention — errors should be in the type system
|
|
7
|
+
*/
|
|
8
|
+
import { Diagnostic, Rule, Visitor } from "../vendor/effect-oxlint/index.js"
|
|
9
|
+
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
10
|
+
import * as Effect from "effect/Effect"
|
|
11
|
+
import * as Ref from "effect/Ref"
|
|
12
|
+
|
|
13
|
+
import { makeEffectContextTracker } from "./_effect-context.js"
|
|
14
|
+
|
|
15
|
+
export const noThrowInEffectGen = Rule.define({
|
|
16
|
+
name: "no-throw-in-effect-gen",
|
|
17
|
+
meta: Rule.meta({
|
|
18
|
+
type: "problem",
|
|
19
|
+
description:
|
|
20
|
+
"Avoid throw inside Effect.gen/fn. Use Effect.fail with tagged errors.",
|
|
21
|
+
}),
|
|
22
|
+
create: function* () {
|
|
23
|
+
const ctx = yield* RuleContext
|
|
24
|
+
const [depth, tracker] = yield* makeEffectContextTracker
|
|
25
|
+
|
|
26
|
+
return Visitor.merge(tracker, {
|
|
27
|
+
ThrowStatement: (node) =>
|
|
28
|
+
Effect.flatMap(Ref.get(depth), (d) => {
|
|
29
|
+
if (d > 0) {
|
|
30
|
+
return ctx.report(
|
|
31
|
+
Diagnostic.make({
|
|
32
|
+
node,
|
|
33
|
+
message:
|
|
34
|
+
"Avoid throw inside Effect.gen/fn. Use Effect.fail with tagged errors.",
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
return Effect.void
|
|
39
|
+
}),
|
|
40
|
+
})
|
|
41
|
+
},
|
|
42
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `throw` statements.
|
|
3
|
+
*
|
|
4
|
+
* Use Effect.fail or tagged errors instead.
|
|
5
|
+
*
|
|
6
|
+
* Source: effect convention — errors should be in the type system
|
|
7
|
+
*/
|
|
8
|
+
import { Rule } from "../vendor/effect-oxlint/index.js"
|
|
9
|
+
|
|
10
|
+
export const noThrowStatement = Rule.banStatement("ThrowStatement", {
|
|
11
|
+
message: "Avoid throw. Use Effect.fail with tagged errors.",
|
|
12
|
+
meta: { type: "problem" },
|
|
13
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `setTimeout`/`setInterval` inside Effect.gen/fn context.
|
|
3
|
+
*
|
|
4
|
+
* Use Effect.sleep or Schedule instead.
|
|
5
|
+
*
|
|
6
|
+
* Source: language-service/globalTimersInEffect
|
|
7
|
+
*/
|
|
8
|
+
import type { ESTree } from "@oxlint/plugins"
|
|
9
|
+
import { AST, Diagnostic, Rule, Visitor } from "../vendor/effect-oxlint/index.js"
|
|
10
|
+
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
11
|
+
import * as Effect from "effect/Effect"
|
|
12
|
+
import * as Option from "effect/Option"
|
|
13
|
+
import * as Ref from "effect/Ref"
|
|
14
|
+
|
|
15
|
+
import { makeEffectContextTracker } from "./_effect-context.js"
|
|
16
|
+
|
|
17
|
+
const timerNames = ["setTimeout", "setInterval"]
|
|
18
|
+
|
|
19
|
+
export const noTimersInEffect = Rule.define({
|
|
20
|
+
name: "no-timers-in-effect",
|
|
21
|
+
meta: Rule.meta({
|
|
22
|
+
type: "suggestion",
|
|
23
|
+
description: "Avoid setTimeout/setInterval inside Effect.gen/fn. Use Effect.sleep or Schedule.",
|
|
24
|
+
}),
|
|
25
|
+
create: function* () {
|
|
26
|
+
const ctx = yield* RuleContext
|
|
27
|
+
const [depth, tracker] = yield* makeEffectContextTracker
|
|
28
|
+
|
|
29
|
+
return Visitor.merge(tracker, {
|
|
30
|
+
CallExpression: (node) =>
|
|
31
|
+
Effect.flatMap(Ref.get(depth), (d) => {
|
|
32
|
+
if (d > 0) {
|
|
33
|
+
const name = Option.getOrUndefined(AST.calleeName(node as ESTree.CallExpression))
|
|
34
|
+
if (name !== undefined && timerNames.includes(name)) {
|
|
35
|
+
return ctx.report(
|
|
36
|
+
Diagnostic.make({
|
|
37
|
+
node,
|
|
38
|
+
message: "Avoid setTimeout/setInterval inside Effect context. Use Effect.sleep or Schedule.",
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return Effect.void
|
|
44
|
+
}),
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `try/catch` inside Effect.gen/fn generators.
|
|
3
|
+
*
|
|
4
|
+
* Use Effect.try, Effect.tryPromise, or Effect.catch* instead.
|
|
5
|
+
*
|
|
6
|
+
* Source: language-service/tryCatchInEffectGen
|
|
7
|
+
*/
|
|
8
|
+
import { Diagnostic, Rule, Visitor } from "../vendor/effect-oxlint/index.js"
|
|
9
|
+
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
10
|
+
import * as Effect from "effect/Effect"
|
|
11
|
+
import * as Ref from "effect/Ref"
|
|
12
|
+
|
|
13
|
+
import { makeEffectContextTracker } from "./_effect-context.js"
|
|
14
|
+
|
|
15
|
+
export const noTryCatchInEffectGen = Rule.define({
|
|
16
|
+
name: "no-try-catch-in-effect-gen",
|
|
17
|
+
meta: Rule.meta({
|
|
18
|
+
type: "suggestion",
|
|
19
|
+
description:
|
|
20
|
+
"Avoid try/catch inside Effect.gen/fn. Use Effect.try or Effect.tryPromise.",
|
|
21
|
+
}),
|
|
22
|
+
create: function* () {
|
|
23
|
+
const ctx = yield* RuleContext
|
|
24
|
+
const [depth, tracker] = yield* makeEffectContextTracker
|
|
25
|
+
|
|
26
|
+
return Visitor.merge(tracker, {
|
|
27
|
+
TryStatement: (node) =>
|
|
28
|
+
Effect.flatMap(Ref.get(depth), (d) => {
|
|
29
|
+
if (d > 0) {
|
|
30
|
+
return ctx.report(
|
|
31
|
+
Diagnostic.make({
|
|
32
|
+
node,
|
|
33
|
+
message:
|
|
34
|
+
"Avoid try/catch inside Effect.gen/fn. Use Effect.try, Effect.tryPromise, or Effect.catch*.",
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
return Effect.void
|
|
39
|
+
}),
|
|
40
|
+
})
|
|
41
|
+
},
|
|
42
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `try/catch` statements.
|
|
3
|
+
*
|
|
4
|
+
* Model failures in Effect's typed error channel.
|
|
5
|
+
*
|
|
6
|
+
* Source: biome-effect-linting-rules/no-try-catch, language-service/tryCatchInEffectGen
|
|
7
|
+
*/
|
|
8
|
+
import { Rule } from "../vendor/effect-oxlint/index.js"
|
|
9
|
+
|
|
10
|
+
export const noTryCatch = Rule.banStatement("TryStatement", {
|
|
11
|
+
message:
|
|
12
|
+
"Avoid try/catch. Model failures in Effect's typed error channel using Effect.try or Effect.tryPromise.",
|
|
13
|
+
meta: { type: "suggestion" },
|
|
14
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `() => { return x }` — use `() => x` instead.
|
|
3
|
+
*
|
|
4
|
+
* Source: language-service/unnecessaryArrowBlock
|
|
5
|
+
*/
|
|
6
|
+
import { Diagnostic, Rule } from "../vendor/effect-oxlint/index.js"
|
|
7
|
+
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
9
|
+
|
|
10
|
+
export const noUnnecessaryArrowBlock = Rule.define({
|
|
11
|
+
name: "no-unnecessary-arrow-block",
|
|
12
|
+
meta: Rule.meta({
|
|
13
|
+
type: "suggestion",
|
|
14
|
+
description: "Unnecessary arrow block body. Use concise arrow `() => expr`.",
|
|
15
|
+
fixable: "code",
|
|
16
|
+
}),
|
|
17
|
+
create: function* () {
|
|
18
|
+
const ctx = yield* RuleContext
|
|
19
|
+
return {
|
|
20
|
+
ArrowFunctionExpression: (node) => {
|
|
21
|
+
if (
|
|
22
|
+
"body" in node &&
|
|
23
|
+
node.body != null &&
|
|
24
|
+
typeof node.body === "object" &&
|
|
25
|
+
"type" in node.body &&
|
|
26
|
+
node.body.type === "BlockStatement" &&
|
|
27
|
+
"body" in node.body &&
|
|
28
|
+
Array.isArray(node.body.body) &&
|
|
29
|
+
node.body.body.length === 1
|
|
30
|
+
) {
|
|
31
|
+
const stmt = node.body.body[0]
|
|
32
|
+
if (
|
|
33
|
+
stmt != null &&
|
|
34
|
+
typeof stmt === "object" &&
|
|
35
|
+
"type" in stmt &&
|
|
36
|
+
stmt.type === "ReturnStatement" &&
|
|
37
|
+
"argument" in stmt &&
|
|
38
|
+
stmt.argument != null
|
|
39
|
+
) {
|
|
40
|
+
return ctx.report(
|
|
41
|
+
Diagnostic.make({
|
|
42
|
+
node,
|
|
43
|
+
message: "Unnecessary arrow block body. Use `() => expr` instead of `() => { return expr }`.",
|
|
44
|
+
}),
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return Effect.void
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect unnecessary `Effect.gen` wrappers with a single `yield*` + return.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* // BAD
|
|
6
|
+
* Effect.gen(function* () { return yield* someEffect })
|
|
7
|
+
*
|
|
8
|
+
* // GOOD
|
|
9
|
+
* someEffect
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Source: language-service/unnecessaryEffectGen
|
|
13
|
+
*/
|
|
14
|
+
import type { ESTree } from "@oxlint/plugins"
|
|
15
|
+
import { AST, Diagnostic, Rule } from "../vendor/effect-oxlint/index.js"
|
|
16
|
+
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
17
|
+
import * as Effect from "effect/Effect"
|
|
18
|
+
import * as Option from "effect/Option"
|
|
19
|
+
|
|
20
|
+
export const noUnnecessaryEffectGen = Rule.define({
|
|
21
|
+
name: "no-unnecessary-effect-gen",
|
|
22
|
+
meta: Rule.meta({
|
|
23
|
+
type: "suggestion",
|
|
24
|
+
description:
|
|
25
|
+
"Unnecessary Effect.gen — single yield can be replaced with the yielded effect directly.",
|
|
26
|
+
fixable: "code",
|
|
27
|
+
}),
|
|
28
|
+
create: function* () {
|
|
29
|
+
const ctx = yield* RuleContext
|
|
30
|
+
return {
|
|
31
|
+
CallExpression: (node) => {
|
|
32
|
+
if (!AST.isCallOf(node as ESTree.CallExpression, "Effect", "gen")) return Effect.void
|
|
33
|
+
|
|
34
|
+
// Check for single-arg: Effect.gen(function*() { ... })
|
|
35
|
+
if (!("arguments" in node) || !Array.isArray(node.arguments) || node.arguments.length !== 1)
|
|
36
|
+
return Effect.void
|
|
37
|
+
|
|
38
|
+
const arg = node.arguments[0]
|
|
39
|
+
if (arg == null || typeof arg !== "object" || !("type" in arg)) return Effect.void
|
|
40
|
+
|
|
41
|
+
// Must be a generator function expression
|
|
42
|
+
if (arg.type !== "FunctionExpression" || !("generator" in arg) || !arg.generator)
|
|
43
|
+
return Effect.void
|
|
44
|
+
|
|
45
|
+
// Must have exactly one statement in the body
|
|
46
|
+
if (
|
|
47
|
+
!("body" in arg) ||
|
|
48
|
+
arg.body == null ||
|
|
49
|
+
typeof arg.body !== "object" ||
|
|
50
|
+
!("type" in arg.body) ||
|
|
51
|
+
arg.body.type !== "BlockStatement" ||
|
|
52
|
+
!("body" in arg.body) ||
|
|
53
|
+
!Array.isArray(arg.body.body) ||
|
|
54
|
+
arg.body.body.length !== 1
|
|
55
|
+
)
|
|
56
|
+
return Effect.void
|
|
57
|
+
|
|
58
|
+
const stmt = arg.body.body[0]
|
|
59
|
+
if (stmt == null || typeof stmt !== "object" || !("type" in stmt)) return Effect.void
|
|
60
|
+
|
|
61
|
+
// Must be `return yield* expr`
|
|
62
|
+
if (
|
|
63
|
+
stmt.type === "ReturnStatement" &&
|
|
64
|
+
"argument" in stmt &&
|
|
65
|
+
stmt.argument != null &&
|
|
66
|
+
typeof stmt.argument === "object" &&
|
|
67
|
+
"type" in stmt.argument &&
|
|
68
|
+
stmt.argument.type === "YieldExpression" &&
|
|
69
|
+
"delegate" in stmt.argument &&
|
|
70
|
+
stmt.argument.delegate === true
|
|
71
|
+
) {
|
|
72
|
+
return ctx.report(
|
|
73
|
+
Diagnostic.make({
|
|
74
|
+
node,
|
|
75
|
+
message:
|
|
76
|
+
"Unnecessary Effect.gen — single yield*. Replace with the yielded effect directly.",
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Effect.void
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ban `pipe(x)` with zero pipe arguments.
|
|
3
|
+
*
|
|
4
|
+
* Just use `x` directly.
|
|
5
|
+
*
|
|
6
|
+
* Source: language-service/unnecessaryPipe
|
|
7
|
+
*/
|
|
8
|
+
import type { ESTree } from "@oxlint/plugins"
|
|
9
|
+
import { AST, Diagnostic, Rule } from "../vendor/effect-oxlint/index.js"
|
|
10
|
+
import { RuleContext } from "../vendor/effect-oxlint/index.js"
|
|
11
|
+
import * as Effect from "effect/Effect"
|
|
12
|
+
import * as Option from "effect/Option"
|
|
13
|
+
|
|
14
|
+
export const noUnnecessaryPipe = Rule.define({
|
|
15
|
+
name: "no-unnecessary-pipe",
|
|
16
|
+
meta: Rule.meta({
|
|
17
|
+
type: "suggestion",
|
|
18
|
+
description: "Unnecessary pipe() with single argument. Use the value directly.",
|
|
19
|
+
fixable: "code",
|
|
20
|
+
}),
|
|
21
|
+
create: function* () {
|
|
22
|
+
const ctx = yield* RuleContext
|
|
23
|
+
return {
|
|
24
|
+
CallExpression: (node) => {
|
|
25
|
+
// Match pipe(x) — bare pipe call with exactly one arg
|
|
26
|
+
const name = Option.getOrUndefined(AST.calleeName(node as ESTree.CallExpression))
|
|
27
|
+
if (name !== "pipe") return Effect.void
|
|
28
|
+
|
|
29
|
+
if (
|
|
30
|
+
"arguments" in node &&
|
|
31
|
+
Array.isArray(node.arguments) &&
|
|
32
|
+
node.arguments.length === 1
|
|
33
|
+
) {
|
|
34
|
+
return ctx.report(
|
|
35
|
+
Diagnostic.make({
|
|
36
|
+
node,
|
|
37
|
+
message:
|
|
38
|
+
"Unnecessary pipe() with single argument. Use the value directly.",
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
return Effect.void
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
})
|