oxlint-plugin-effect 0.0.0 → 0.2.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 (84) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/package.json +6 -7
  3. package/src/presets/core.ts +24 -13
  4. package/src/presets/effect-native.ts +18 -25
  5. package/src/presets/full.ts +33 -47
  6. package/src/presets/functional.ts +18 -0
  7. package/src/presets/index.ts +2 -0
  8. package/src/presets/strict.ts +77 -0
  9. package/src/rules/index.ts +18 -29
  10. package/src/rules/no-arrow-ladder.ts +1 -2
  11. package/src/rules/no-async-function.ts +1 -2
  12. package/src/rules/no-catch-all-to-map-error.ts +90 -0
  13. package/src/rules/no-effect-fn-generator.ts +1 -2
  14. package/src/rules/no-effect-gen-adapter.ts +2 -2
  15. package/src/rules/no-effect-map-flatten.ts +82 -0
  16. package/src/rules/no-effect-map-void.ts +1 -2
  17. package/src/rules/no-effect-orElse-ladder.ts +1 -2
  18. package/src/rules/no-effect-succeed-string.ts +1 -2
  19. package/src/rules/no-effect-succeed-variable.ts +1 -2
  20. package/src/rules/no-effect-succeed-void.ts +1 -2
  21. package/src/rules/no-effect-sync-console.ts +1 -2
  22. package/src/rules/no-effect-sync-wrapper.ts +1 -2
  23. package/src/rules/no-effect-type-alias.ts +1 -2
  24. package/src/rules/no-effect-wrapper-alias.ts +1 -4
  25. package/src/rules/no-extends-native-error.ts +1 -2
  26. package/src/rules/no-flatmap-ladder.ts +1 -3
  27. package/src/rules/no-fromnullable-coalesce.ts +1 -2
  28. package/src/rules/no-global-error-in-catch.ts +104 -0
  29. package/src/rules/no-global-error-in-failure.ts +83 -0
  30. package/src/rules/no-globals.ts +178 -0
  31. package/src/rules/no-iife-wrapper.ts +1 -2
  32. package/src/rules/{no-inline-runtime-provide.ts → no-inline-provide.ts} +3 -4
  33. package/src/rules/no-instanceof-schema.ts +2 -3
  34. package/src/rules/no-manual-effect-channels.ts +1 -2
  35. package/src/rules/no-match-effect-branch.ts +1 -2
  36. package/src/rules/no-match-void-branch.ts +1 -2
  37. package/src/rules/no-multiple-effect-provide.ts +75 -0
  38. package/src/rules/no-nested-effect-call.ts +1 -3
  39. package/src/rules/no-nested-effect-gen.ts +1 -2
  40. package/src/rules/no-nested-pipe.ts +1 -4
  41. package/src/rules/no-new-error.ts +46 -10
  42. package/src/rules/no-new-promise.ts +43 -7
  43. package/src/rules/no-node-builtin-import.ts +1 -3
  44. package/src/rules/no-option-boolean-normalization.ts +1 -2
  45. package/src/rules/no-positional-log-error.ts +76 -0
  46. package/src/rules/no-redundant-schema-tag-identifier.ts +62 -0
  47. package/src/rules/no-return-in-arrow.ts +1 -2
  48. package/src/rules/no-return-nullish.ts +80 -0
  49. package/src/rules/no-run-in-effect-gen.ts +66 -0
  50. package/src/rules/no-runtime-run-fork.ts +1 -1
  51. package/src/rules/no-schema-struct-with-tag.ts +64 -0
  52. package/src/rules/no-schema-union-of-literals.ts +48 -0
  53. package/src/rules/no-string-sentinel-const.ts +1 -2
  54. package/src/rules/no-ternary.ts +1 -3
  55. package/src/rules/no-throw-statement.ts +32 -6
  56. package/src/rules/no-try-catch.ts +32 -7
  57. package/src/rules/no-unnecessary-arrow-block.ts +1 -2
  58. package/src/rules/no-unnecessary-effect-gen.ts +1 -3
  59. package/src/rules/no-unnecessary-pipe-chain.ts +47 -0
  60. package/src/rules/no-unnecessary-pipe.ts +1 -2
  61. package/src/vendor/effect-oxlint/Rule.ts +141 -183
  62. package/tests/ban-rules.test.ts +0 -184
  63. package/tests/context-rules.test.ts +127 -145
  64. package/tests/pattern-rules.test.ts +39 -50
  65. package/src/rules/no-console-in-effect.ts +0 -46
  66. package/src/rules/no-date-in-effect.ts +0 -61
  67. package/src/rules/no-dynamic-import.ts +0 -13
  68. package/src/rules/no-fetch-in-effect.ts +0 -47
  69. package/src/rules/no-global-console.ts +0 -17
  70. package/src/rules/no-global-date.ts +0 -20
  71. package/src/rules/no-global-fetch.ts +0 -14
  72. package/src/rules/no-global-random.ts +0 -13
  73. package/src/rules/no-global-timers.ts +0 -17
  74. package/src/rules/no-json-in-effect.ts +0 -45
  75. package/src/rules/no-json-parse.ts +0 -18
  76. package/src/rules/no-platform-globals.ts +0 -117
  77. package/src/rules/no-process-env-in-effect.ts +0 -39
  78. package/src/rules/no-process-env.ts +0 -13
  79. package/src/rules/no-random-in-effect.ts +0 -39
  80. package/src/rules/no-return-null.ts +0 -47
  81. package/src/rules/no-run-in-effect.ts +0 -18
  82. package/src/rules/no-throw-in-effect-gen.ts +0 -42
  83. package/src/rules/no-timers-in-effect.ts +0 -47
  84. package/src/rules/no-try-catch-in-effect-gen.ts +0 -42
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Ban `Schema.Union(Schema.Literal("a"), Schema.Literal("b"))`.
3
+ * Use `Schema.Literal("a", "b")` instead.
4
+ *
5
+ * Source: language-service/schemaUnionOfLiterals
6
+ */
7
+ import type { ESTree } from "@oxlint/plugins"
8
+ import { AST, Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
9
+ import * as Effect from "effect/Effect"
10
+ import * as Option from "effect/Option"
11
+
12
+ const isSchemaLiteral = (node: ESTree.Node): boolean => {
13
+ if (node.type !== "CallExpression") return false
14
+ return Option.isSome(AST.matchCallOf(node as ESTree.CallExpression, "Schema", "Literal"))
15
+ }
16
+
17
+ export const noSchemaUnionOfLiterals = Rule.define({
18
+ name: "no-schema-union-of-literals",
19
+ meta: Rule.meta({
20
+ type: "suggestion",
21
+ description:
22
+ "Use Schema.Literal('a', 'b') instead of Schema.Union(Schema.Literal('a'), Schema.Literal('b')).",
23
+ }),
24
+ create: function* () {
25
+ const ctx = yield* RuleContext
26
+ return {
27
+ CallExpression: (node) => {
28
+ const call = node as ESTree.CallExpression
29
+ if (Option.isNone(AST.matchCallOf(call, "Schema", "Union"))) return Effect.void
30
+
31
+ const args = call.arguments
32
+ if (args.length < 2) return Effect.void
33
+
34
+ // Check if all arguments are Schema.Literal calls
35
+ const allLiterals = args.every((arg) => isSchemaLiteral(arg as ESTree.Node))
36
+ if (!allLiterals) return Effect.void
37
+
38
+ return ctx.report(
39
+ Diagnostic.make({
40
+ node,
41
+ message:
42
+ "Use Schema.Literal('a', 'b') instead of Schema.Union(Schema.Literal('a'), Schema.Literal('b')).",
43
+ }),
44
+ )
45
+ },
46
+ }
47
+ },
48
+ })
@@ -7,8 +7,7 @@
7
7
  *
8
8
  * Note: Very aggressive — should probably be off by default or in a strict preset only.
9
9
  */
10
- import { Diagnostic, Rule } from "../vendor/effect-oxlint/index.js"
11
- import { RuleContext } from "../vendor/effect-oxlint/index.js"
10
+ import { Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
12
11
  import * as Effect from "effect/Effect"
13
12
 
14
13
  export const noStringSentinelConst = Rule.define({
@@ -7,9 +7,7 @@
7
7
  *
8
8
  * Note: Very opinionated — recommended for strict functional presets only.
9
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"
10
+ import { Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
13
11
 
14
12
  export const noTernary = Rule.define({
15
13
  name: "no-ternary",
@@ -1,13 +1,39 @@
1
1
  /**
2
- * Ban `throw` statements.
2
+ * Ban `throw` statements. Context-aware messaging.
3
3
  *
4
- * Use Effect.fail or tagged errors instead.
4
+ * Inside Effect.gen/fn: "Use yield* Effect.fail() or yield* new MyError()"
5
+ * Outside: "Model errors with Effect.fail() — wrap this function with Effect.fn"
5
6
  *
6
7
  * Source: effect convention — errors should be in the type system
7
8
  */
8
- import { Rule } from "../vendor/effect-oxlint/index.js"
9
+ import { Diagnostic, Rule, Visitor, RuleContext } from "../vendor/effect-oxlint/index.js"
10
+ import * as Effect from "effect/Effect"
11
+ import * as Ref from "effect/Ref"
9
12
 
10
- export const noThrowStatement = Rule.banStatement("ThrowStatement", {
11
- message: "Avoid throw. Use Effect.fail with tagged errors.",
12
- meta: { type: "problem" },
13
+ import { makeEffectContextTracker } from "./_effect-context.js"
14
+
15
+ export const noThrowStatement = Rule.define({
16
+ name: "no-throw-statement",
17
+ meta: Rule.meta({
18
+ type: "problem",
19
+ description: "Avoid throw. Use Effect.fail with tagged errors.",
20
+ }),
21
+ create: function* () {
22
+ const ctx = yield* RuleContext
23
+ const [depth, tracker] = yield* makeEffectContextTracker
24
+
25
+ return Visitor.merge(tracker, {
26
+ ThrowStatement: (node) =>
27
+ Effect.flatMap(Ref.get(depth), (d) =>
28
+ ctx.report(
29
+ Diagnostic.make({
30
+ node,
31
+ message: d > 0
32
+ ? "Avoid throw inside Effect.gen/fn. Use yield* Effect.fail(new MyError()) or yield* new MyError() for yieldable errors."
33
+ : "Avoid throw. Model errors with Effect — wrap this function with Effect.fn and use Effect.fail with tagged errors.",
34
+ }),
35
+ ),
36
+ ),
37
+ })
38
+ },
13
39
  })
@@ -1,14 +1,39 @@
1
1
  /**
2
- * Ban `try/catch` statements.
2
+ * Ban `try/catch` statements. Context-aware messaging.
3
3
  *
4
- * Model failures in Effect's typed error channel.
4
+ * Inside Effect.gen/fn: "Use Effect.try or Effect.tryPromise"
5
+ * Outside: "Wrap with Effect.fn and use Effect.try for error handling"
5
6
  *
6
7
  * Source: biome-effect-linting-rules/no-try-catch, language-service/tryCatchInEffectGen
7
8
  */
8
- import { Rule } from "../vendor/effect-oxlint/index.js"
9
+ import { Diagnostic, Rule, Visitor, RuleContext } from "../vendor/effect-oxlint/index.js"
10
+ import * as Effect from "effect/Effect"
11
+ import * as Ref from "effect/Ref"
9
12
 
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" },
13
+ import { makeEffectContextTracker } from "./_effect-context.js"
14
+
15
+ export const noTryCatch = Rule.define({
16
+ name: "no-try-catch",
17
+ meta: Rule.meta({
18
+ type: "suggestion",
19
+ description: "Avoid try/catch. Use Effect.try or Effect.tryPromise.",
20
+ }),
21
+ create: function* () {
22
+ const ctx = yield* RuleContext
23
+ const [depth, tracker] = yield* makeEffectContextTracker
24
+
25
+ return Visitor.merge(tracker, {
26
+ TryStatement: (node) =>
27
+ Effect.flatMap(Ref.get(depth), (d) =>
28
+ ctx.report(
29
+ Diagnostic.make({
30
+ node,
31
+ message: d > 0
32
+ ? "Avoid try/catch inside Effect.gen/fn. Use Effect.try({ try: () => ..., catch: (e) => new MyError({ cause: e }) })."
33
+ : "Avoid try/catch. Wrap this function with Effect.fn and use Effect.try or Effect.tryPromise for error handling.",
34
+ }),
35
+ ),
36
+ ),
37
+ })
38
+ },
14
39
  })
@@ -3,8 +3,7 @@
3
3
  *
4
4
  * Source: language-service/unnecessaryArrowBlock
5
5
  */
6
- import { Diagnostic, Rule } from "../vendor/effect-oxlint/index.js"
7
- import { RuleContext } from "../vendor/effect-oxlint/index.js"
6
+ import { Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
8
7
  import * as Effect from "effect/Effect"
9
8
 
10
9
  export const noUnnecessaryArrowBlock = Rule.define({
@@ -12,10 +12,8 @@
12
12
  * Source: language-service/unnecessaryEffectGen
13
13
  */
14
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"
15
+ import { AST, Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
17
16
  import * as Effect from "effect/Effect"
18
- import * as Option from "effect/Option"
19
17
 
20
18
  export const noUnnecessaryEffectGen = Rule.define({
21
19
  name: "no-unnecessary-effect-gen",
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Ban `x.pipe(f).pipe(g)` — merge into `x.pipe(f, g)`.
3
+ *
4
+ * Source: language-service/unnecessaryPipeChain
5
+ */
6
+ import type { ESTree } from "@oxlint/plugins"
7
+ import { Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
8
+ import * as Effect from "effect/Effect"
9
+
10
+ const isMethodPipe = (node: ESTree.Node): boolean => {
11
+ if (node.type !== "CallExpression") return false
12
+ const callee = (node as unknown as Record<string, unknown>)["callee"]
13
+ if (callee == null || typeof callee !== "object" || !("type" in callee)) return false
14
+ if (callee.type !== "MemberExpression") return false
15
+ const prop = (callee as unknown as Record<string, unknown>)["property"]
16
+ if (prop == null || typeof prop !== "object" || !("type" in prop)) return false
17
+ return prop.type === "Identifier" && "name" in prop && prop.name === "pipe"
18
+ }
19
+
20
+ export const noUnnecessaryPipeChain = Rule.define({
21
+ name: "no-unnecessary-pipe-chain",
22
+ meta: Rule.meta({
23
+ type: "suggestion",
24
+ description:
25
+ "Avoid chaining .pipe().pipe(). Merge into a single .pipe() call.",
26
+ }),
27
+ create: function* () {
28
+ const ctx = yield* RuleContext
29
+ return {
30
+ CallExpression: (node) => {
31
+ if (!isMethodPipe(node)) return Effect.void
32
+ const callee = (node as unknown as Record<string, unknown>)["callee"] as Record<string, unknown>
33
+ const obj = callee["object"]
34
+ if (obj != null && typeof obj === "object" && isMethodPipe(obj as ESTree.Node)) {
35
+ return ctx.report(
36
+ Diagnostic.make({
37
+ node,
38
+ message:
39
+ "Unnecessary .pipe() chain. Merge into a single .pipe() call.",
40
+ }),
41
+ )
42
+ }
43
+ return Effect.void
44
+ },
45
+ }
46
+ },
47
+ })
@@ -6,8 +6,7 @@
6
6
  * Source: language-service/unnecessaryPipe
7
7
  */
8
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"
9
+ import { AST, Diagnostic, Rule, RuleContext } from "../vendor/effect-oxlint/index.js"
11
10
  import * as Effect from "effect/Effect"
12
11
  import * as Option from "effect/Option"
13
12