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.
- package/CHANGELOG.md +55 -0
- package/package.json +6 -7
- package/src/presets/core.ts +24 -13
- package/src/presets/effect-native.ts +18 -25
- package/src/presets/full.ts +33 -47
- 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 +18 -29
- 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 +90 -0
- package/src/rules/no-effect-fn-generator.ts +1 -2
- package/src/rules/no-effect-gen-adapter.ts +2 -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-run-in-effect-gen.ts +66 -0
- package/src/rules/no-runtime-run-fork.ts +1 -1
- 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 +141 -183
- package/tests/ban-rules.test.ts +0 -184
- package/tests/context-rules.test.ts +127 -145
- 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-run-in-effect.ts +0 -18
- 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
|
@@ -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
|
-
*
|
|
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.
|
|
419
|
+
* @since 0.3.0
|
|
402
420
|
*/
|
|
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
|
-
}
|
|
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
|
|
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
|
-
*
|
|
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
|
-
* )
|
|
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.
|
|
443
|
+
* @since 0.3.0
|
|
461
444
|
*/
|
|
462
|
-
export const banMultiple = (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
readonly
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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:
|
|
476
|
-
description
|
|
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
|
-
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
spec.
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
+
};
|
package/tests/ban-rules.test.ts
CHANGED
|
@@ -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
|
-
})
|