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
@@ -130,6 +130,16 @@ export const define = <Options = undefined>(
130
130
  // Metadata helper
131
131
  // ---------------------------------------------------------------------------
132
132
 
133
+ /**
134
+ * Which Effect version a rule applies to.
135
+ *
136
+ * Stored on `meta.docs.effectVersion` so preset authors can filter rules
137
+ * per project. Default `both`, meaning the rule is version-agnostic.
138
+ *
139
+ * @since 0.2.0
140
+ */
141
+ export type EffectVersion = 'v3' | 'v4' | 'both';
142
+
133
143
  /**
134
144
  * Build `RuleMeta` with sensible defaults.
135
145
  *
@@ -142,6 +152,7 @@ export const meta = (opts: {
142
152
  readonly hasSuggestions?: boolean | undefined;
143
153
  readonly messages?: Record<string, string> | undefined;
144
154
  readonly docs?: RuleDocs | undefined;
155
+ readonly effectVersion?: EffectVersion | undefined;
145
156
  }): RuleMeta => ({
146
157
  type: opts.type,
147
158
  ...(opts.fixable !== undefined ? { fixable: opts.fixable } : {}),
@@ -151,6 +162,7 @@ export const meta = (opts: {
151
162
  ...(opts.messages !== undefined ? { messages: opts.messages } : {}),
152
163
  docs: {
153
164
  description: opts.description,
165
+ effectVersion: opts.effectVersion ?? 'both',
154
166
  ...opts.docs
155
167
  }
156
168
  });
@@ -172,7 +184,7 @@ export const banMember = (
172
184
  opts: {
173
185
  readonly message: string;
174
186
  readonly meta?:
175
- | { readonly type?: 'problem' | 'suggestion' }
187
+ | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
176
188
  | undefined;
177
189
  }
178
190
  ): CreateRule =>
@@ -180,7 +192,8 @@ export const banMember = (
180
192
  name: `ban-${obj}-${P.isString(prop) ? prop : Arr.join(prop, '-')}`,
181
193
  meta: meta({
182
194
  type: opts.meta?.type ?? 'suggestion',
183
- description: opts.message
195
+ description: opts.message,
196
+ effectVersion: opts.meta?.effectVersion
184
197
  }),
185
198
  create: function* () {
186
199
  const ctx = yield* RuleContext;
@@ -216,7 +229,7 @@ export const banImport = (
216
229
  opts: {
217
230
  readonly message: string;
218
231
  readonly meta?:
219
- | { readonly type?: 'problem' | 'suggestion' }
232
+ | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
220
233
  | undefined;
221
234
  }
222
235
  ): CreateRule =>
@@ -224,7 +237,8 @@ export const banImport = (
224
237
  name: 'ban-import',
225
238
  meta: meta({
226
239
  type: opts.meta?.type ?? 'suggestion',
227
- description: opts.message
240
+ description: opts.message,
241
+ effectVersion: opts.meta?.effectVersion
228
242
  }),
229
243
  create: function* () {
230
244
  const ctx = yield* RuleContext;
@@ -270,7 +284,7 @@ export const banCallOf = (
270
284
  opts: {
271
285
  readonly message: string;
272
286
  readonly meta?:
273
- | { readonly type?: 'problem' | 'suggestion' }
287
+ | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
274
288
  | undefined;
275
289
  }
276
290
  ): CreateRule => {
@@ -279,7 +293,8 @@ export const banCallOf = (
279
293
  name: `ban-call-${Arr.join(names, '-')}`,
280
294
  meta: meta({
281
295
  type: opts.meta?.type ?? 'suggestion',
282
- description: opts.message
296
+ description: opts.message,
297
+ effectVersion: opts.meta?.effectVersion
283
298
  }),
284
299
  create: function* () {
285
300
  const ctx = yield* RuleContext;
@@ -327,7 +342,7 @@ export const banNewExpr = (
327
342
  opts: {
328
343
  readonly message: string;
329
344
  readonly meta?:
330
- | { readonly type?: 'problem' | 'suggestion' }
345
+ | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
331
346
  | undefined;
332
347
  }
333
348
  ): CreateRule => {
@@ -336,7 +351,8 @@ export const banNewExpr = (
336
351
  name: `ban-new-${Arr.join(names, '-')}`,
337
352
  meta: meta({
338
353
  type: opts.meta?.type ?? 'suggestion',
339
- description: opts.message
354
+ description: opts.message,
355
+ effectVersion: opts.meta?.effectVersion
340
356
  }),
341
357
  create: function* () {
342
358
  const ctx = yield* RuleContext;
@@ -372,7 +388,7 @@ export const banStatement = (
372
388
  opts: {
373
389
  readonly message: string;
374
390
  readonly meta?:
375
- | { readonly type?: 'problem' | 'suggestion' }
391
+ | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
376
392
  | undefined;
377
393
  }
378
394
  ): CreateRule =>
@@ -380,7 +396,8 @@ export const banStatement = (
380
396
  name: `ban-${nodeType}`,
381
397
  meta: meta({
382
398
  type: opts.meta?.type ?? 'suggestion',
383
- description: opts.message
399
+ description: opts.message,
400
+ effectVersion: opts.meta?.effectVersion
384
401
  }),
385
402
  create: function* () {
386
403
  const ctx = yield* RuleContext;
@@ -396,195 +413,136 @@ export const banStatement = (
396
413
  // ---------------------------------------------------------------------------
397
414
 
398
415
  /**
399
- * Specification for `banMultiple`: which patterns to ban under one rule.
416
+ * A single ban specification. Each spec carries its own message so
417
+ * diagnostics are specific to the exact pattern matched.
400
418
  *
401
- * @since 0.2.0
419
+ * @since 0.3.0
402
420
  */
403
- export interface BanMultipleSpec {
404
- /** Bare identifier calls to ban (e.g. `'fetch'` or `['useState', 'useEffect']`). */
405
- readonly calls?: string | ReadonlyArray<string> | undefined;
406
- /** `new` expressions to ban (e.g. `'Date'` or `['Error', 'TypeError']`). */
407
- readonly newExprs?: string | ReadonlyArray<string> | undefined;
408
- /** Member expressions to ban: `[object, property | properties]` tuples. */
409
- readonly members?:
410
- | ReadonlyArray<
411
- readonly [obj: string, prop: string | ReadonlyArray<string>]
412
- >
413
- | undefined;
414
- /** Import sources to ban (string or predicate). */
415
- readonly imports?:
416
- | ReadonlyArray<string | ((source: string) => boolean)>
417
- | undefined;
418
- /** Statement node types to ban (e.g. `'ThrowStatement'`). */
419
- readonly statements?: ReadonlyArray<string> | undefined;
420
- }
421
+ export type BanSpec =
422
+ | { readonly type: 'member'; readonly object: string; readonly property: string | ReadonlyArray<string>; readonly message: string }
423
+ | { readonly type: 'call'; readonly name: string | ReadonlyArray<string>; readonly message: string }
424
+ | { readonly type: 'new'; readonly name: string | ReadonlyArray<string>; readonly message: string }
425
+ | { readonly type: 'import'; readonly source: string | ((source: string) => boolean); readonly message: string }
426
+ | { readonly type: 'statement'; readonly nodeType: string; readonly message: string };
421
427
 
422
428
  /**
423
- * Create a rule that bans multiple patterns with a shared message.
424
- *
425
- * Combines call bans, `new` expression bans, member bans, import bans,
426
- * and statement bans into a single rule with merged visitors.
429
+ * Create a rule that bans multiple patterns, each with its own message.
427
430
  *
428
431
  * @example
429
432
  * ```ts
430
- * // Ban 5 loop statement types
431
- * Rule.banMultiple(
432
- * {
433
- * statements: [
434
- * 'ForStatement', 'ForInStatement', 'ForOfStatement',
435
- * 'WhileStatement', 'DoWhileStatement'
436
- * ]
437
- * },
438
- * { message: 'Use Arr.map / Effect.forEach instead' }
439
- * )
440
- *
441
- * // Combine import + member bans
442
- * Rule.banMultiple(
443
- * {
444
- * imports: ['node:fs'],
445
- * members: [['fs', ['readFileSync', 'writeFileSync']]]
446
- * },
447
- * { message: 'Use Effect FileSystem service' }
448
- * )
449
- *
450
- * // Combine new expression + member bans
451
- * Rule.banMultiple(
452
- * {
453
- * newExprs: 'Date',
454
- * members: [['Date', 'now']]
455
- * },
456
- * { message: 'Use Clock service' }
457
- * )
433
+ * Rule.banMultiple({
434
+ * name: "no-global-date",
435
+ * meta: { type: "suggestion" },
436
+ * specs: [
437
+ * { type: "new", name: "Date", message: "Avoid new Date(). Use Clock service." },
438
+ * { type: "member", object: "Date", property: "now", message: "Avoid Date.now(). Use Clock service." },
439
+ * ],
440
+ * })
458
441
  * ```
459
442
  *
460
- * @since 0.2.0
443
+ * @since 0.3.0
461
444
  */
462
- export const banMultiple = (
463
- spec: BanMultipleSpec,
464
- opts: {
465
- readonly name?: string | undefined;
466
- readonly message: string;
467
- readonly meta?:
468
- | { readonly type?: 'problem' | 'suggestion' }
469
- | undefined;
470
- }
471
- ): CreateRule =>
472
- define({
473
- name: opts.name ?? 'ban-multiple',
445
+ export const banMultiple = (config: {
446
+ readonly name: string;
447
+ readonly meta?:
448
+ | { readonly type?: 'problem' | 'suggestion'; readonly effectVersion?: EffectVersion }
449
+ | undefined;
450
+ readonly specs: ReadonlyArray<BanSpec>;
451
+ }): CreateRule => {
452
+ const description = config.specs.map((s) => s.message).join(' ');
453
+ return define({
454
+ name: config.name,
474
455
  meta: meta({
475
- type: opts.meta?.type ?? 'suggestion',
476
- description: opts.message
456
+ type: config.meta?.type ?? 'suggestion',
457
+ description,
458
+ effectVersion: config.meta?.effectVersion
477
459
  }),
478
460
  create: function* () {
479
461
  const ctx = yield* RuleContext;
480
- const report = (node: ESTree.Node) =>
481
- ctx.report(makeDiagnostic({ node, message: opts.message }));
482
-
483
- // Statement bans
484
- const stmtVisitors: ReadonlyArray<EffectVisitor> =
485
- spec.statements !== undefined
486
- ? Arr.map(
487
- spec.statements,
488
- (nodeType): EffectVisitor => ({
489
- [nodeType]: report
490
- })
491
- )
492
- : [];
493
-
494
- // Call bans
495
- const callVisitors: ReadonlyArray<EffectVisitor> =
496
- spec.calls !== undefined
497
- ? ((names: ReadonlyArray<string>) => [
498
- {
499
- CallExpression: (node: ESTree.Node) =>
500
- pipe(
501
- AST.narrow(node, 'CallExpression'),
502
- Option.flatMap(AST.calleeName),
503
- Option.filter((n) =>
504
- Arr.contains(names, n)
505
- ),
506
- Option.match({
507
- onNone: () => Effect.void,
508
- onSome: () => report(node)
509
- })
510
- )
511
- } satisfies EffectVisitor
512
- ])(P.isString(spec.calls) ? [spec.calls] : spec.calls)
513
- : [];
514
-
515
- // NewExpression bans
516
- const newExprVisitors: ReadonlyArray<EffectVisitor> =
517
- spec.newExprs !== undefined
518
- ? ((names: ReadonlyArray<string>) => [
519
- {
520
- NewExpression: (node: ESTree.Node) =>
521
- pipe(
522
- AST.narrow(node, 'NewExpression'),
523
- Option.flatMap((n) =>
524
- newExprCalleeName(n.callee)
525
- ),
526
- Option.filter((n) =>
527
- Arr.contains(names, n)
528
- ),
529
- Option.match({
530
- onNone: () => Effect.void,
531
- onSome: () => report(node)
532
- })
533
- )
534
- } satisfies EffectVisitor
535
- ])(
536
- P.isString(spec.newExprs)
537
- ? [spec.newExprs]
538
- : spec.newExprs
539
- )
540
- : [];
541
462
 
542
- // Member bans
543
- const memberVisitors: ReadonlyArray<EffectVisitor> =
544
- spec.members !== undefined
545
- ? Arr.map(
546
- spec.members,
547
- ([obj, prop]): EffectVisitor => ({
548
- MemberExpression: (node: ESTree.Node) =>
549
- pipe(
550
- AST.narrow(node, 'MemberExpression'),
551
- Option.flatMap(
552
- AST.matchMember(obj, prop)
553
- ),
554
- Option.match({
555
- onNone: () => Effect.void,
556
- onSome: (matched) => report(matched)
557
- })
558
- )
559
- })
560
- )
561
- : [];
463
+ const visitors: Array<EffectVisitor> = [];
562
464
 
563
- // Import bans
564
- const importVisitors: ReadonlyArray<EffectVisitor> =
565
- spec.imports !== undefined
566
- ? Arr.map(
567
- spec.imports,
568
- (source): EffectVisitor => ({
569
- ImportDeclaration: (node: ESTree.Node) =>
570
- pipe(
571
- AST.narrow(node, 'ImportDeclaration'),
572
- Option.flatMap(AST.matchImport(source)),
573
- Option.match({
574
- onNone: () => Effect.void,
575
- onSome: (matched) => report(matched)
576
- })
577
- )
578
- })
579
- )
580
- : [];
465
+ for (const spec of config.specs) {
466
+ switch (spec.type) {
467
+ case 'member': {
468
+ const report = (node: ESTree.Node) =>
469
+ ctx.report(makeDiagnostic({ node, message: spec.message }));
470
+ visitors.push({
471
+ MemberExpression: (node: ESTree.Node) =>
472
+ pipe(
473
+ AST.narrow(node, 'MemberExpression'),
474
+ Option.flatMap(AST.matchMember(spec.object, spec.property)),
475
+ Option.match({
476
+ onNone: () => Effect.void,
477
+ onSome: (matched) => report(matched)
478
+ })
479
+ )
480
+ });
481
+ break;
482
+ }
483
+ case 'call': {
484
+ const names = P.isString(spec.name) ? [spec.name] : spec.name;
485
+ const report = (node: ESTree.Node) =>
486
+ ctx.report(makeDiagnostic({ node, message: spec.message }));
487
+ visitors.push({
488
+ CallExpression: (node: ESTree.Node) =>
489
+ pipe(
490
+ AST.narrow(node, 'CallExpression'),
491
+ Option.flatMap(AST.calleeName),
492
+ Option.filter((n) => Arr.contains(names, n)),
493
+ Option.match({
494
+ onNone: () => Effect.void,
495
+ onSome: () => report(node)
496
+ })
497
+ )
498
+ });
499
+ break;
500
+ }
501
+ case 'new': {
502
+ const names = P.isString(spec.name) ? [spec.name] : spec.name;
503
+ const report = (node: ESTree.Node) =>
504
+ ctx.report(makeDiagnostic({ node, message: spec.message }));
505
+ visitors.push({
506
+ NewExpression: (node: ESTree.Node) =>
507
+ pipe(
508
+ AST.narrow(node, 'NewExpression'),
509
+ Option.flatMap((n) => newExprCalleeName(n.callee)),
510
+ Option.filter((n) => Arr.contains(names, n)),
511
+ Option.match({
512
+ onNone: () => Effect.void,
513
+ onSome: () => report(node)
514
+ })
515
+ )
516
+ });
517
+ break;
518
+ }
519
+ case 'import': {
520
+ const report = (node: ESTree.Node) =>
521
+ ctx.report(makeDiagnostic({ node, message: spec.message }));
522
+ visitors.push({
523
+ ImportDeclaration: (node: ESTree.Node) =>
524
+ pipe(
525
+ AST.narrow(node, 'ImportDeclaration'),
526
+ Option.flatMap(AST.matchImport(spec.source)),
527
+ Option.match({
528
+ onNone: () => Effect.void,
529
+ onSome: (matched) => report(matched)
530
+ })
531
+ )
532
+ });
533
+ break;
534
+ }
535
+ case 'statement': {
536
+ visitors.push({
537
+ [spec.nodeType]: (node: ESTree.Node) =>
538
+ ctx.report(makeDiagnostic({ node, message: spec.message }))
539
+ });
540
+ break;
541
+ }
542
+ }
543
+ }
581
544
 
582
- return mergeVisitors(
583
- ...stmtVisitors,
584
- ...callVisitors,
585
- ...newExprVisitors,
586
- ...memberVisitors,
587
- ...importVisitors
588
- );
545
+ return mergeVisitors(...visitors);
589
546
  }
590
547
  });
548
+ };
@@ -8,20 +8,6 @@ import {
8
8
  noEffectBind,
9
9
  noOptionAs,
10
10
  noRuntimeRunFork,
11
- noRunInEffect,
12
- noGlobalFetch,
13
- noGlobalConsole,
14
- noGlobalDate,
15
- noGlobalRandom,
16
- noGlobalTimers,
17
- noNewPromise,
18
- noNewError,
19
- noProcessEnv,
20
- noNodeBuiltinImport,
21
- noDynamicImport,
22
- noThrowStatement,
23
- noTryCatch,
24
- noJsonParse,
25
11
  } from "../src/rules/index.js"
26
12
 
27
13
  // --- API ban rules ---
@@ -80,173 +66,3 @@ describe("noRuntimeRunFork", () => {
80
66
  })
81
67
  })
82
68
 
83
- describe("noRunInEffect", () => {
84
- test("reports Effect.runSync", () => {
85
- const result = Testing.runRule(noRunInEffect, "MemberExpression", Testing.memberExpr("Effect", "runSync"))
86
- expect(result.length).toBe(1)
87
- })
88
-
89
- test("reports Effect.runPromise", () => {
90
- const result = Testing.runRule(noRunInEffect, "MemberExpression", Testing.memberExpr("Effect", "runPromise"))
91
- expect(result.length).toBe(1)
92
- })
93
-
94
- test("ignores Effect.gen", () => {
95
- const result = Testing.runRule(noRunInEffect, "MemberExpression", Testing.memberExpr("Effect", "gen"))
96
- Testing.expectNoDiagnostics(result)
97
- })
98
- })
99
-
100
- // --- Global ban rules ---
101
-
102
- describe("noGlobalFetch", () => {
103
- test("reports fetch()", () => {
104
- const result = Testing.runRule(noGlobalFetch, "CallExpression", Testing.callExpr("fetch"))
105
- expect(result.length).toBe(1)
106
- })
107
-
108
- test("ignores other calls", () => {
109
- const result = Testing.runRule(noGlobalFetch, "CallExpression", Testing.callExpr("getData"))
110
- Testing.expectNoDiagnostics(result)
111
- })
112
- })
113
-
114
- describe("noGlobalConsole", () => {
115
- test("reports console.log", () => {
116
- const result = Testing.runRule(noGlobalConsole, "MemberExpression", Testing.memberExpr("console", "log"))
117
- expect(result.length).toBe(1)
118
- })
119
-
120
- test("reports console.error", () => {
121
- const result = Testing.runRule(noGlobalConsole, "MemberExpression", Testing.memberExpr("console", "error"))
122
- expect(result.length).toBe(1)
123
- })
124
-
125
- test("ignores logger.log", () => {
126
- const result = Testing.runRule(noGlobalConsole, "MemberExpression", Testing.memberExpr("logger", "log"))
127
- Testing.expectNoDiagnostics(result)
128
- })
129
- })
130
-
131
- describe("noGlobalDate", () => {
132
- test("reports Date.now", () => {
133
- const result = Testing.runRule(noGlobalDate, "MemberExpression", Testing.memberExpr("Date", "now"))
134
- expect(result.length).toBe(1)
135
- })
136
-
137
- test("reports new Date()", () => {
138
- const result = Testing.runRule(noGlobalDate, "NewExpression", Testing.newExpr("Date"))
139
- expect(result.length).toBe(1)
140
- })
141
- })
142
-
143
- describe("noGlobalRandom", () => {
144
- test("reports Math.random", () => {
145
- const result = Testing.runRule(noGlobalRandom, "MemberExpression", Testing.memberExpr("Math", "random"))
146
- expect(result.length).toBe(1)
147
- })
148
- })
149
-
150
- describe("noGlobalTimers", () => {
151
- test("reports setTimeout", () => {
152
- const result = Testing.runRule(noGlobalTimers, "CallExpression", Testing.callExpr("setTimeout"))
153
- expect(result.length).toBe(1)
154
- })
155
-
156
- test("reports setInterval", () => {
157
- const result = Testing.runRule(noGlobalTimers, "CallExpression", Testing.callExpr("setInterval"))
158
- expect(result.length).toBe(1)
159
- })
160
- })
161
-
162
- describe("noNewPromise", () => {
163
- test("reports new Promise()", () => {
164
- const result = Testing.runRule(noNewPromise, "NewExpression", Testing.newExpr("Promise"))
165
- expect(result.length).toBe(1)
166
- })
167
- })
168
-
169
- describe("noNewError", () => {
170
- test("reports new Error()", () => {
171
- const result = Testing.runRule(noNewError, "NewExpression", Testing.newExpr("Error"))
172
- expect(result.length).toBe(1)
173
- })
174
-
175
- test("reports new TypeError()", () => {
176
- const result = Testing.runRule(noNewError, "NewExpression", Testing.newExpr("TypeError"))
177
- expect(result.length).toBe(1)
178
- })
179
-
180
- test("ignores new MyError()", () => {
181
- const result = Testing.runRule(noNewError, "NewExpression", Testing.newExpr("MyError"))
182
- Testing.expectNoDiagnostics(result)
183
- })
184
- })
185
-
186
- describe("noProcessEnv", () => {
187
- test("reports process.env", () => {
188
- const result = Testing.runRule(noProcessEnv, "MemberExpression", Testing.memberExpr("process", "env"))
189
- expect(result.length).toBe(1)
190
- })
191
- })
192
-
193
- // --- Import ban rules ---
194
-
195
- describe("noNodeBuiltinImport", () => {
196
- test("reports import from 'fs'", () => {
197
- const result = Testing.runRule(noNodeBuiltinImport, "ImportDeclaration", Testing.importDecl("fs"))
198
- expect(result.length).toBe(1)
199
- })
200
-
201
- test("reports import from 'node:fs'", () => {
202
- const result = Testing.runRule(noNodeBuiltinImport, "ImportDeclaration", Testing.importDecl("node:fs"))
203
- expect(result.length).toBe(1)
204
- })
205
-
206
- test("reports import from 'child_process'", () => {
207
- const result = Testing.runRule(noNodeBuiltinImport, "ImportDeclaration", Testing.importDecl("child_process"))
208
- expect(result.length).toBe(1)
209
- })
210
-
211
- test("ignores import from 'effect'", () => {
212
- const result = Testing.runRule(noNodeBuiltinImport, "ImportDeclaration", Testing.importDecl("effect"))
213
- Testing.expectNoDiagnostics(result)
214
- })
215
- })
216
-
217
- // --- Statement ban rules ---
218
-
219
- describe("noThrowStatement", () => {
220
- test("reports throw", () => {
221
- const result = Testing.runRule(noThrowStatement, "ThrowStatement", Testing.throwStmt())
222
- expect(result.length).toBe(1)
223
- })
224
- })
225
-
226
- describe("noTryCatch", () => {
227
- test("reports try/catch", () => {
228
- const result = Testing.runRule(noTryCatch, "TryStatement", Testing.tryStmt())
229
- expect(result.length).toBe(1)
230
- })
231
- })
232
-
233
- describe("noDynamicImport", () => {
234
- test("reports dynamic import()", () => {
235
- const result = Testing.runRule(noDynamicImport, "ImportExpression", { type: "ImportExpression" } as never)
236
- expect(result.length).toBe(1)
237
- })
238
- })
239
-
240
- // --- JSON ---
241
-
242
- describe("noJsonParse", () => {
243
- test("reports JSON.parse", () => {
244
- const result = Testing.runRule(noJsonParse, "MemberExpression", Testing.memberExpr("JSON", "parse"))
245
- expect(result.length).toBe(1)
246
- })
247
-
248
- test("reports JSON.stringify", () => {
249
- const result = Testing.runRule(noJsonParse, "MemberExpression", Testing.memberExpr("JSON", "stringify"))
250
- expect(result.length).toBe(1)
251
- })
252
- })