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.
Files changed (95) hide show
  1. package/.effect-lsp.json +60 -0
  2. package/.oxlintrc.json +33 -0
  3. package/package.json +51 -0
  4. package/scripts/add-rule.ts +87 -0
  5. package/scripts/codegen.ts +108 -0
  6. package/src/index.ts +9 -0
  7. package/src/plugin.ts +16 -0
  8. package/src/presets/core.ts +36 -0
  9. package/src/presets/effect-native.ts +41 -0
  10. package/src/presets/full.ts +84 -0
  11. package/src/presets/index.ts +3 -0
  12. package/src/rules/_effect-context.ts +28 -0
  13. package/src/rules/index.ts +83 -0
  14. package/src/rules/no-arrow-ladder.ts +71 -0
  15. package/src/rules/no-async-function.ts +48 -0
  16. package/src/rules/no-console-in-effect.ts +46 -0
  17. package/src/rules/no-date-in-effect.ts +61 -0
  18. package/src/rules/no-dynamic-import.ts +13 -0
  19. package/src/rules/no-effect-as.ts +14 -0
  20. package/src/rules/no-effect-async.ts +14 -0
  21. package/src/rules/no-effect-bind.ts +13 -0
  22. package/src/rules/no-effect-do.ts +14 -0
  23. package/src/rules/no-effect-fn-generator.ts +59 -0
  24. package/src/rules/no-effect-gen-adapter.ts +51 -0
  25. package/src/rules/no-effect-map-void.ts +81 -0
  26. package/src/rules/no-effect-never.ts +14 -0
  27. package/src/rules/no-effect-orElse-ladder.ts +80 -0
  28. package/src/rules/no-effect-succeed-string.ts +53 -0
  29. package/src/rules/no-effect-succeed-variable.ts +51 -0
  30. package/src/rules/no-effect-succeed-void.ts +64 -0
  31. package/src/rules/no-effect-sync-console.ts +92 -0
  32. package/src/rules/no-effect-sync-wrapper.ts +68 -0
  33. package/src/rules/no-effect-type-alias.ts +72 -0
  34. package/src/rules/no-effect-wrapper-alias.ts +98 -0
  35. package/src/rules/no-extends-native-error.ts +55 -0
  36. package/src/rules/no-fetch-in-effect.ts +47 -0
  37. package/src/rules/no-flatmap-ladder.ts +79 -0
  38. package/src/rules/no-fromnullable-coalesce.ts +63 -0
  39. package/src/rules/no-global-console.ts +17 -0
  40. package/src/rules/no-global-date.ts +20 -0
  41. package/src/rules/no-global-fetch.ts +14 -0
  42. package/src/rules/no-global-random.ts +13 -0
  43. package/src/rules/no-global-timers.ts +17 -0
  44. package/src/rules/no-if-statement.ts +16 -0
  45. package/src/rules/no-iife-wrapper.ts +52 -0
  46. package/src/rules/no-inline-runtime-provide.ts +42 -0
  47. package/src/rules/no-instanceof-schema.ts +43 -0
  48. package/src/rules/no-json-in-effect.ts +45 -0
  49. package/src/rules/no-json-parse.ts +18 -0
  50. package/src/rules/no-manual-effect-channels.ts +59 -0
  51. package/src/rules/no-match-effect-branch.ts +83 -0
  52. package/src/rules/no-match-void-branch.ts +73 -0
  53. package/src/rules/no-nested-effect-call.ts +59 -0
  54. package/src/rules/no-nested-effect-gen.ts +44 -0
  55. package/src/rules/no-nested-pipe.ts +76 -0
  56. package/src/rules/no-new-error.ts +17 -0
  57. package/src/rules/no-new-promise.ts +14 -0
  58. package/src/rules/no-node-builtin-import.ts +70 -0
  59. package/src/rules/no-option-as.ts +13 -0
  60. package/src/rules/no-option-boolean-normalization.ts +89 -0
  61. package/src/rules/no-platform-globals.ts +117 -0
  62. package/src/rules/no-process-env-in-effect.ts +39 -0
  63. package/src/rules/no-process-env.ts +13 -0
  64. package/src/rules/no-random-in-effect.ts +39 -0
  65. package/src/rules/no-return-in-arrow.ts +77 -0
  66. package/src/rules/no-return-null.ts +47 -0
  67. package/src/rules/no-run-in-effect.ts +18 -0
  68. package/src/rules/no-runtime-run-fork.ts +14 -0
  69. package/src/rules/no-string-sentinel-const.ts +50 -0
  70. package/src/rules/no-switch-statement.ts +13 -0
  71. package/src/rules/no-ternary.ts +32 -0
  72. package/src/rules/no-throw-in-effect-gen.ts +42 -0
  73. package/src/rules/no-throw-statement.ts +13 -0
  74. package/src/rules/no-timers-in-effect.ts +47 -0
  75. package/src/rules/no-try-catch-in-effect-gen.ts +42 -0
  76. package/src/rules/no-try-catch.ts +14 -0
  77. package/src/rules/no-unnecessary-arrow-block.ts +52 -0
  78. package/src/rules/no-unnecessary-effect-gen.ts +85 -0
  79. package/src/rules/no-unnecessary-pipe.ts +46 -0
  80. package/src/vendor/effect-oxlint/AST.ts +516 -0
  81. package/src/vendor/effect-oxlint/Comment.ts +86 -0
  82. package/src/vendor/effect-oxlint/Diagnostic.ts +199 -0
  83. package/src/vendor/effect-oxlint/Plugin.ts +71 -0
  84. package/src/vendor/effect-oxlint/Rule.ts +590 -0
  85. package/src/vendor/effect-oxlint/RuleContext.ts +144 -0
  86. package/src/vendor/effect-oxlint/Scope.ts +177 -0
  87. package/src/vendor/effect-oxlint/SourceCode.ts +377 -0
  88. package/src/vendor/effect-oxlint/Testing.ts +1227 -0
  89. package/src/vendor/effect-oxlint/Token.ts +131 -0
  90. package/src/vendor/effect-oxlint/Visitor.ts +287 -0
  91. package/src/vendor/effect-oxlint/index.ts +88 -0
  92. package/tests/ban-rules.test.ts +252 -0
  93. package/tests/context-rules.test.ts +281 -0
  94. package/tests/pattern-rules.test.ts +258 -0
  95. 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
+ })