Skip to content
Source
Sign inSign up

prefer-effect-fn.test.ts

202 lines5.3 KB

1import { assert, describe } from "@effect/vitest";
2​
3import { createOxlintRuleHarness } from "../test/utils.ts";
4​
5const rule = createOxlintRuleHarness("t3code/prefer-effect-fn");
6​
7describe("t3code/prefer-effect-fn", () => {
8 rule.valid(
9 "allows inline programs and layer construction",
10 `
11 import * as Effect from "effect/Effect";
12 import * as Layer from "effect/Layer";
13 export const program = Effect.gen(function* () { yield* Effect.void; });
14 export const layer = Layer.effectDiscard(Effect.gen(function* () { yield* Effect.void; }));
15 `,
16 );
17​
18 rule.valid(
19 "allows named functions and deliberate untraced functions",
20 `
21 import * as Effect from "effect/Effect";
22 export const read = Effect.fn("Files.read")(function* () { yield* Effect.void; });
23 export const chunk = Effect.fnUntraced(function* () { yield* Effect.void; });
24 `,
25 );
26​
27 rule.valid(
28 "allows inline combinator callbacks",
29 `
30 import * as Effect from "effect/Effect";
31 export const program = Effect.void.pipe(Effect.flatMap(() => Effect.gen(function* () {
32 yield* Effect.void;
33 })));
34 `,
35 );
36​
37 rule.valid(
38 "allows inline option and error callbacks",
39 `
40 import * as Effect from "effect/Effect";
41 import * as Option from "effect/Option";
42 export const program = Option.match(Option.none(), {
43 onNone: () => Effect.gen(function* () { yield* Effect.void; }),
44 onSome: () => Effect.void,
45 });
46 export const recovered = Effect.fail({ _tag: "Failed" }).pipe(Effect.catchTags({
47 Failed: () => Effect.gen(function* () { yield* Effect.void; }),
48 }));
49 `,
50 );
51​
52 rule.valid(
53 "allows reference default-value callbacks",
54 `
55 import * as Context from "effect/Context";
56 import * as Effect from "effect/Effect";
57 export const Default = Context.Reference("Default", {
58 defaultValue: () => Effect.gen(function* () { yield* Effect.void; }),
59 });
60 `,
61 );
62​
63 for (const name of ["onSome", "onNone", "onSuccess", "onFailure", "defaultValue"]) {
64 rule.invalid(
65 `reports service members named ${name}`,
66 `
67 import * as Effect from "effect/Effect";
68 export const service = Service.of({
69 ${name}: () => Effect.gen(function* () { yield* Effect.void; }),
70 });
71 `,
72 );
73 }
74​
75 rule.valid(
76 "does not mistake other namespaces or shadowed locals for Effect",
77 `
78 import * as Effect from "effect/Effect";
79 const Other = { gen: (body: () => unknown) => body() };
80 export const other = () => Other.gen(function* () {});
81 export function scoped(Effect: typeof Other) {
82 return Effect.gen(function* () {});
83 }
84 export const effect = Effect.void;
85 `,
86 );
87​
88 rule.valid(
89 "leaves functions with synchronous setup for manual review",
90 `
91 import * as Effect from "effect/Effect";
92 export const read = () => {
93 const state = new Map();
94 return Effect.gen(function* () { yield* Effect.void; return state; });
95 };
96 `,
97 );
98​
99 rule.invalid(
100 "reports a named arrow wrapper",
101 `
102 import * as Effect from "effect/Effect";
103 export const read = (path: string) => Effect.gen(function* () {
104 yield* Effect.void;
105 return path;
106 });
107 `,
108 (output) => {
109 assert.match(output, /Effect\.fn/);
110 assert.match(output, /documented hot paths/);
111 },
112 );
113​
114 rule.invalid(
115 "reports a block-bodied wrapper",
116 `
117 import * as Effect from "effect/Effect";
118 export const read = () => { return Effect.gen(function* () { yield* Effect.void; }); };
119 `,
120 );
121​
122 rule.invalid(
123 "reports a declaration with pipe transforms",
124 `
125 import * as Effect from "effect/Effect";
126 export function read() {
127 return Effect.gen(function* () { yield* Effect.void; }).pipe(Effect.asVoid);
128 }
129 `,
130 );
131​
132 rule.invalid(
133 "reports a function expression",
134 `
135 import * as Effect from "effect/Effect";
136 export const read = function() { return Effect.gen(function* () { yield* Effect.void; }); };
137 `,
138 );
139​
140 rule.invalid(
141 "reports service members",
142 `
143 import * as Effect from "effect/Effect";
144 export const service = {
145 read: () => Effect.gen(function* () { yield* Effect.void; }),
146 };
147 `,
148 );
149​
150 rule.invalid(
151 "reports class fields",
152 `
153 import * as Effect from "effect/Effect";
154 export class Service {
155 read = () => Effect.gen(function* () { yield* Effect.void; });
156 }
157 `,
158 );
159​
160 rule.invalid(
161 "recognizes namespace aliases",
162 `
163 import * as Eff from "effect/Effect";
164 export const read = () => Eff.gen(function* () { yield* Eff.void; });
165 `,
166 );
167​
168 rule.invalid(
169 "recognizes bare-package Effect imports",
170 `
171 import { Effect } from "effect";
172 export const read = () => Effect.gen(function* () { yield* Effect.void; });
173 `,
174 );
175​
176 rule.invalid(
177 "recognizes a direct gen import",
178 `
179 import { gen, void as noop } from "effect/Effect";
180 export const read = () => gen(function* () { yield* noop; });
181 `,
182 );
183 rule.invalid(
184 "reports class methods",
185 `
186 import * as Effect from "effect/Effect";
187 export class Service {
188 read() { return Effect.gen(function* () { yield* Effect.void; }); }
189 }
190 `,
191 );
192​
193 rule.valid(
194 "allows getters that expose an effect value",
195 `
196 import * as Effect from "effect/Effect";
197 export class Service {
198 get read() { return Effect.gen(function* () { yield* Effect.void; }); }
199 }
200 `,
201 );
202});