oxlint-plugin-effect 0.0.0 → 0.1.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/CHANGELOG.md +37 -0
- package/package.json +6 -7
- package/src/presets/core.ts +23 -12
- package/src/presets/effect-native.ts +18 -25
- package/src/presets/full.ts +32 -46
- package/src/presets/functional.ts +18 -0
- package/src/presets/index.ts +2 -0
- package/src/presets/strict.ts +77 -0
- package/src/rules/index.ts +17 -28
- package/src/rules/no-arrow-ladder.ts +1 -2
- package/src/rules/no-async-function.ts +1 -2
- package/src/rules/no-catch-all-to-map-error.ts +89 -0
- package/src/rules/no-effect-fn-generator.ts +1 -2
- package/src/rules/no-effect-gen-adapter.ts +1 -2
- package/src/rules/no-effect-map-flatten.ts +82 -0
- package/src/rules/no-effect-map-void.ts +1 -2
- package/src/rules/no-effect-orElse-ladder.ts +1 -2
- package/src/rules/no-effect-succeed-string.ts +1 -2
- package/src/rules/no-effect-succeed-variable.ts +1 -2
- package/src/rules/no-effect-succeed-void.ts +1 -2
- package/src/rules/no-effect-sync-console.ts +1 -2
- package/src/rules/no-effect-sync-wrapper.ts +1 -2
- package/src/rules/no-effect-type-alias.ts +1 -2
- package/src/rules/no-effect-wrapper-alias.ts +1 -4
- package/src/rules/no-extends-native-error.ts +1 -2
- package/src/rules/no-flatmap-ladder.ts +1 -3
- package/src/rules/no-fromnullable-coalesce.ts +1 -2
- package/src/rules/no-global-error-in-catch.ts +104 -0
- package/src/rules/no-global-error-in-failure.ts +83 -0
- package/src/rules/no-globals.ts +178 -0
- package/src/rules/no-iife-wrapper.ts +1 -2
- package/src/rules/{no-inline-runtime-provide.ts → no-inline-provide.ts} +3 -4
- package/src/rules/no-instanceof-schema.ts +2 -3
- package/src/rules/no-manual-effect-channels.ts +1 -2
- package/src/rules/no-match-effect-branch.ts +1 -2
- package/src/rules/no-match-void-branch.ts +1 -2
- package/src/rules/no-multiple-effect-provide.ts +75 -0
- package/src/rules/no-nested-effect-call.ts +1 -3
- package/src/rules/no-nested-effect-gen.ts +1 -2
- package/src/rules/no-nested-pipe.ts +1 -4
- package/src/rules/no-new-error.ts +46 -10
- package/src/rules/no-new-promise.ts +43 -7
- package/src/rules/no-node-builtin-import.ts +1 -3
- package/src/rules/no-option-boolean-normalization.ts +1 -2
- package/src/rules/no-positional-log-error.ts +76 -0
- package/src/rules/no-redundant-schema-tag-identifier.ts +62 -0
- package/src/rules/no-return-in-arrow.ts +1 -2
- package/src/rules/no-return-nullish.ts +80 -0
- package/src/rules/no-schema-struct-with-tag.ts +64 -0
- package/src/rules/no-schema-union-of-literals.ts +48 -0
- package/src/rules/no-string-sentinel-const.ts +1 -2
- package/src/rules/no-ternary.ts +1 -3
- package/src/rules/no-throw-statement.ts +32 -6
- package/src/rules/no-try-catch.ts +32 -7
- package/src/rules/no-unnecessary-arrow-block.ts +1 -2
- package/src/rules/no-unnecessary-effect-gen.ts +1 -3
- package/src/rules/no-unnecessary-pipe-chain.ts +47 -0
- package/src/rules/no-unnecessary-pipe.ts +1 -2
- package/src/vendor/effect-oxlint/Rule.ts +111 -173
- package/tests/ban-rules.test.ts +0 -167
- package/tests/context-rules.test.ts +75 -158
- package/tests/pattern-rules.test.ts +39 -50
- package/src/rules/no-console-in-effect.ts +0 -46
- package/src/rules/no-date-in-effect.ts +0 -61
- package/src/rules/no-dynamic-import.ts +0 -13
- package/src/rules/no-fetch-in-effect.ts +0 -47
- package/src/rules/no-global-console.ts +0 -17
- package/src/rules/no-global-date.ts +0 -20
- package/src/rules/no-global-fetch.ts +0 -14
- package/src/rules/no-global-random.ts +0 -13
- package/src/rules/no-global-timers.ts +0 -17
- package/src/rules/no-json-in-effect.ts +0 -45
- package/src/rules/no-json-parse.ts +0 -18
- package/src/rules/no-platform-globals.ts +0 -117
- package/src/rules/no-process-env-in-effect.ts +0 -39
- package/src/rules/no-process-env.ts +0 -13
- package/src/rules/no-random-in-effect.ts +0 -39
- package/src/rules/no-return-null.ts +0 -47
- package/src/rules/no-throw-in-effect-gen.ts +0 -42
- package/src/rules/no-timers-in-effect.ts +0 -47
- package/src/rules/no-try-catch-in-effect-gen.ts +0 -42
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ban `throw` statements.
|
|
2
|
+
* Ban `throw` statements. Context-aware messaging.
|
|
3
3
|
*
|
|
4
|
-
* Use Effect.fail or
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
|
@@ -396,195 +396,133 @@ export const banStatement = (
|
|
|
396
396
|
// ---------------------------------------------------------------------------
|
|
397
397
|
|
|
398
398
|
/**
|
|
399
|
-
*
|
|
399
|
+
* A single ban specification. Each spec carries its own message so
|
|
400
|
+
* diagnostics are specific to the exact pattern matched.
|
|
400
401
|
*
|
|
401
|
-
* @since 0.
|
|
402
|
+
* @since 0.3.0
|
|
402
403
|
*/
|
|
403
|
-
export
|
|
404
|
-
|
|
405
|
-
readonly
|
|
406
|
-
|
|
407
|
-
readonly
|
|
408
|
-
|
|
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
|
-
}
|
|
404
|
+
export type BanSpec =
|
|
405
|
+
| { readonly type: 'member'; readonly object: string; readonly property: string | ReadonlyArray<string>; readonly message: string }
|
|
406
|
+
| { readonly type: 'call'; readonly name: string | ReadonlyArray<string>; readonly message: string }
|
|
407
|
+
| { readonly type: 'new'; readonly name: string | ReadonlyArray<string>; readonly message: string }
|
|
408
|
+
| { readonly type: 'import'; readonly source: string | ((source: string) => boolean); readonly message: string }
|
|
409
|
+
| { readonly type: 'statement'; readonly nodeType: string; readonly message: string };
|
|
421
410
|
|
|
422
411
|
/**
|
|
423
|
-
* Create a rule that bans multiple patterns with
|
|
424
|
-
*
|
|
425
|
-
* Combines call bans, `new` expression bans, member bans, import bans,
|
|
426
|
-
* and statement bans into a single rule with merged visitors.
|
|
412
|
+
* Create a rule that bans multiple patterns, each with its own message.
|
|
427
413
|
*
|
|
428
414
|
* @example
|
|
429
415
|
* ```ts
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
* {
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
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
|
-
* )
|
|
416
|
+
* Rule.banMultiple({
|
|
417
|
+
* name: "no-global-date",
|
|
418
|
+
* meta: { type: "suggestion" },
|
|
419
|
+
* specs: [
|
|
420
|
+
* { type: "new", name: "Date", message: "Avoid new Date(). Use Clock service." },
|
|
421
|
+
* { type: "member", object: "Date", property: "now", message: "Avoid Date.now(). Use Clock service." },
|
|
422
|
+
* ],
|
|
423
|
+
* })
|
|
458
424
|
* ```
|
|
459
425
|
*
|
|
460
|
-
* @since 0.
|
|
426
|
+
* @since 0.3.0
|
|
461
427
|
*/
|
|
462
|
-
export const banMultiple = (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
): CreateRule =>
|
|
472
|
-
define({
|
|
473
|
-
name: opts.name ?? 'ban-multiple',
|
|
428
|
+
export const banMultiple = (config: {
|
|
429
|
+
readonly name: string;
|
|
430
|
+
readonly meta?: { readonly type?: 'problem' | 'suggestion' } | undefined;
|
|
431
|
+
readonly specs: ReadonlyArray<BanSpec>;
|
|
432
|
+
}): CreateRule => {
|
|
433
|
+
const description = config.specs.map((s) => s.message).join(' ');
|
|
434
|
+
return define({
|
|
435
|
+
name: config.name,
|
|
474
436
|
meta: meta({
|
|
475
|
-
type:
|
|
476
|
-
description
|
|
437
|
+
type: config.meta?.type ?? 'suggestion',
|
|
438
|
+
description
|
|
477
439
|
}),
|
|
478
440
|
create: function* () {
|
|
479
441
|
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
442
|
|
|
494
|
-
|
|
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
|
-
: [];
|
|
443
|
+
const visitors: Array<EffectVisitor> = [];
|
|
514
444
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
{
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
)
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
445
|
+
for (const spec of config.specs) {
|
|
446
|
+
switch (spec.type) {
|
|
447
|
+
case 'member': {
|
|
448
|
+
const report = (node: ESTree.Node) =>
|
|
449
|
+
ctx.report(makeDiagnostic({ node, message: spec.message }));
|
|
450
|
+
visitors.push({
|
|
451
|
+
MemberExpression: (node: ESTree.Node) =>
|
|
452
|
+
pipe(
|
|
453
|
+
AST.narrow(node, 'MemberExpression'),
|
|
454
|
+
Option.flatMap(AST.matchMember(spec.object, spec.property)),
|
|
455
|
+
Option.match({
|
|
456
|
+
onNone: () => Effect.void,
|
|
457
|
+
onSome: (matched) => report(matched)
|
|
458
|
+
})
|
|
459
|
+
)
|
|
460
|
+
});
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case 'call': {
|
|
464
|
+
const names = P.isString(spec.name) ? [spec.name] : spec.name;
|
|
465
|
+
const report = (node: ESTree.Node) =>
|
|
466
|
+
ctx.report(makeDiagnostic({ node, message: spec.message }));
|
|
467
|
+
visitors.push({
|
|
468
|
+
CallExpression: (node: ESTree.Node) =>
|
|
469
|
+
pipe(
|
|
470
|
+
AST.narrow(node, 'CallExpression'),
|
|
471
|
+
Option.flatMap(AST.calleeName),
|
|
472
|
+
Option.filter((n) => Arr.contains(names, n)),
|
|
473
|
+
Option.match({
|
|
474
|
+
onNone: () => Effect.void,
|
|
475
|
+
onSome: () => report(node)
|
|
476
|
+
})
|
|
477
|
+
)
|
|
478
|
+
});
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
case 'new': {
|
|
482
|
+
const names = P.isString(spec.name) ? [spec.name] : spec.name;
|
|
483
|
+
const report = (node: ESTree.Node) =>
|
|
484
|
+
ctx.report(makeDiagnostic({ node, message: spec.message }));
|
|
485
|
+
visitors.push({
|
|
486
|
+
NewExpression: (node: ESTree.Node) =>
|
|
487
|
+
pipe(
|
|
488
|
+
AST.narrow(node, 'NewExpression'),
|
|
489
|
+
Option.flatMap((n) => newExprCalleeName(n.callee)),
|
|
490
|
+
Option.filter((n) => Arr.contains(names, n)),
|
|
491
|
+
Option.match({
|
|
492
|
+
onNone: () => Effect.void,
|
|
493
|
+
onSome: () => report(node)
|
|
494
|
+
})
|
|
495
|
+
)
|
|
496
|
+
});
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
case 'import': {
|
|
500
|
+
const report = (node: ESTree.Node) =>
|
|
501
|
+
ctx.report(makeDiagnostic({ node, message: spec.message }));
|
|
502
|
+
visitors.push({
|
|
503
|
+
ImportDeclaration: (node: ESTree.Node) =>
|
|
504
|
+
pipe(
|
|
505
|
+
AST.narrow(node, 'ImportDeclaration'),
|
|
506
|
+
Option.flatMap(AST.matchImport(spec.source)),
|
|
507
|
+
Option.match({
|
|
508
|
+
onNone: () => Effect.void,
|
|
509
|
+
onSome: (matched) => report(matched)
|
|
510
|
+
})
|
|
511
|
+
)
|
|
512
|
+
});
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
case 'statement': {
|
|
516
|
+
visitors.push({
|
|
517
|
+
[spec.nodeType]: (node: ESTree.Node) =>
|
|
518
|
+
ctx.report(makeDiagnostic({ node, message: spec.message }))
|
|
519
|
+
});
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
581
524
|
|
|
582
|
-
return mergeVisitors(
|
|
583
|
-
...stmtVisitors,
|
|
584
|
-
...callVisitors,
|
|
585
|
-
...newExprVisitors,
|
|
586
|
-
...memberVisitors,
|
|
587
|
-
...importVisitors
|
|
588
|
-
);
|
|
525
|
+
return mergeVisitors(...visitors);
|
|
589
526
|
}
|
|
590
527
|
});
|
|
528
|
+
};
|
package/tests/ban-rules.test.ts
CHANGED
|
@@ -9,19 +9,6 @@ import {
|
|
|
9
9
|
noOptionAs,
|
|
10
10
|
noRuntimeRunFork,
|
|
11
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
12
|
} from "../src/rules/index.js"
|
|
26
13
|
|
|
27
14
|
// --- API ban rules ---
|
|
@@ -96,157 +83,3 @@ describe("noRunInEffect", () => {
|
|
|
96
83
|
Testing.expectNoDiagnostics(result)
|
|
97
84
|
})
|
|
98
85
|
})
|
|
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
|
-
})
|