vouchington-tooling 0.13.4 → 0.15.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/README.md +3 -0
- package/ast-grep/rules/backend-no-test-file-imports.yml +1 -1
- package/ast-grep/rules/no-dynamic-import-in-vitest-config.yml +1 -1
- package/ast-grep/rules/no-dynamic-server-components.yml +41 -5
- package/ast-grep/rules/no-enable-indexscan-off.yml +17 -4
- package/ast-grep/rules/no-exports-in-tests-folder.yml +27 -4
- package/ast-grep/rules/no-object-assign-new-error.yml +1 -1
- package/ast-grep/rules/no-runner-temp-nullish.yml +1 -1
- package/ast-grep/rules/no-self-comparison-assertion.yml +21 -1
- package/ast-grep/rules/no-sql-offset.yml +1 -1
- package/ast-grep/rules/no-sync-throw-assert-on-async-enqueue.yml +1 -1
- package/ast-grep/rules/no-tautological-literal-assertion.yml +15 -1
- package/ast-grep/rules/no-test-max-lines-disable.yml +26 -4
- package/ast-grep/rules/no-three-sequential-awaits.yml +220 -12
- package/ast-grep/rules/nullable-options.yml +1 -1
- package/ast-grep/rules/pagination-no-exact-limit-has-next.yml +1 -1
- package/ast-grep/rules/playwright-no-bare-meta-press.yml +1 -1
- package/ast-grep/rules/storybook-no-webpack-final.yml +1 -1
- package/ast-grep/sgconfig.yml +1 -2
- package/package.json +1 -1
- package/skills/agent-workflow/SKILL.md +4 -1
- package/skills/agent-workflow/references/review-response.md +32 -0
- package/ast-grep/rules/no-dynamic-server-components-tsx.yml +0 -71
- package/ast-grep/rules/no-enable-indexscan-off-tsx.yml +0 -33
- package/ast-grep/rules/no-exports-in-tests-folder-tsx.yml +0 -42
- package/ast-grep/rules/no-self-comparison-assertion-tsx.yml +0 -43
- package/ast-grep/rules/no-tautological-literal-assertion-tsx.yml +0 -31
- package/ast-grep/rules/no-test-max-lines-disable-tsx.yml +0 -41
- package/ast-grep/rules/no-three-sequential-awaits-tsx.yml +0 -466
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
id: no-three-sequential-awaits
|
|
2
|
-
language:
|
|
2
|
+
language: Tsx
|
|
3
3
|
severity: error
|
|
4
4
|
message: 'Avoid 3 sequential await statements; abstract dependent work or parallelize independent work with Promise.all().'
|
|
5
5
|
files:
|
|
6
6
|
- '**/*.ts'
|
|
7
7
|
- '**/*.mts'
|
|
8
8
|
- '**/*.cts'
|
|
9
|
+
- '**/*.tsx'
|
|
9
10
|
ignores:
|
|
10
11
|
- '**/*.test.ts'
|
|
11
12
|
- '**/*.test.mts'
|
|
@@ -18,19 +19,23 @@ ignores:
|
|
|
18
19
|
- '**/*.mock.test.cts'
|
|
19
20
|
- '**/*.stories.ts'
|
|
20
21
|
- '**/*.stories.mts'
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
22
|
+
- backend/test-helpers/**
|
|
23
|
+
- test-helpers/**
|
|
24
|
+
- test-infra/**
|
|
25
|
+
- web/test-helpers/**
|
|
25
26
|
- '**/test-helpers/**'
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
27
|
+
- playwright/**
|
|
28
|
+
- playwright/setup/**
|
|
29
|
+
- integration-tests/**
|
|
30
|
+
- seed/**
|
|
30
31
|
- '**/seeds/**'
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
32
|
+
- backend/scripts/seeds/**
|
|
33
|
+
- backend/scripts/seed/**
|
|
34
|
+
- backend/scripts/explain-analyze/**
|
|
35
|
+
- '**/*.test.tsx'
|
|
36
|
+
- '**/*.spec.tsx'
|
|
37
|
+
- '**/*.mock.test.tsx'
|
|
38
|
+
- '**/*.stories.tsx'
|
|
34
39
|
rule:
|
|
35
40
|
all:
|
|
36
41
|
- not:
|
|
@@ -45,6 +50,10 @@ rule:
|
|
|
45
50
|
- pattern:
|
|
46
51
|
context: await $P
|
|
47
52
|
selector: expression_statement
|
|
53
|
+
- not:
|
|
54
|
+
pattern: await params
|
|
55
|
+
- not:
|
|
56
|
+
pattern: await searchParams
|
|
48
57
|
- not:
|
|
49
58
|
has:
|
|
50
59
|
pattern: Promise.all($$)
|
|
@@ -53,18 +62,30 @@ rule:
|
|
|
53
62
|
pattern: Promise.allSettled($$)
|
|
54
63
|
- all:
|
|
55
64
|
- pattern: const $W = await $P
|
|
65
|
+
- not:
|
|
66
|
+
pattern: const $W = await params
|
|
67
|
+
- not:
|
|
68
|
+
pattern: const $W = await searchParams
|
|
56
69
|
- not:
|
|
57
70
|
pattern: const $W = await Promise.all($$$)
|
|
58
71
|
- not:
|
|
59
72
|
pattern: const $W = await Promise.allSettled($$$)
|
|
60
73
|
- all:
|
|
61
74
|
- pattern: let $W = await $P
|
|
75
|
+
- not:
|
|
76
|
+
pattern: let $W = await params
|
|
77
|
+
- not:
|
|
78
|
+
pattern: let $W = await searchParams
|
|
62
79
|
- not:
|
|
63
80
|
pattern: let $W = await Promise.all($$$)
|
|
64
81
|
- not:
|
|
65
82
|
pattern: let $W = await Promise.allSettled($$$)
|
|
66
83
|
- all:
|
|
67
84
|
- pattern: var $W = await $P
|
|
85
|
+
- not:
|
|
86
|
+
pattern: var $W = await params
|
|
87
|
+
- not:
|
|
88
|
+
pattern: var $W = await searchParams
|
|
68
89
|
- not:
|
|
69
90
|
pattern: var $W = await Promise.all($$$)
|
|
70
91
|
- not:
|
|
@@ -73,6 +94,10 @@ rule:
|
|
|
73
94
|
- pattern:
|
|
74
95
|
context: $W = await $P
|
|
75
96
|
selector: expression_statement
|
|
97
|
+
- not:
|
|
98
|
+
pattern: $W = await params
|
|
99
|
+
- not:
|
|
100
|
+
pattern: $W = await searchParams
|
|
76
101
|
- not:
|
|
77
102
|
pattern: $W = await Promise.all($$$)
|
|
78
103
|
- not:
|
|
@@ -86,6 +111,10 @@ rule:
|
|
|
86
111
|
- pattern:
|
|
87
112
|
context: await $A
|
|
88
113
|
selector: expression_statement
|
|
114
|
+
- not:
|
|
115
|
+
pattern: await params
|
|
116
|
+
- not:
|
|
117
|
+
pattern: await searchParams
|
|
89
118
|
- not:
|
|
90
119
|
has:
|
|
91
120
|
pattern: Promise.all($$)
|
|
@@ -94,18 +123,30 @@ rule:
|
|
|
94
123
|
pattern: Promise.allSettled($$)
|
|
95
124
|
- all:
|
|
96
125
|
- pattern: const $X = await $A
|
|
126
|
+
- not:
|
|
127
|
+
pattern: const $X = await params
|
|
128
|
+
- not:
|
|
129
|
+
pattern: const $X = await searchParams
|
|
97
130
|
- not:
|
|
98
131
|
pattern: const $X = await Promise.all($$$)
|
|
99
132
|
- not:
|
|
100
133
|
pattern: const $X = await Promise.allSettled($$$)
|
|
101
134
|
- all:
|
|
102
135
|
- pattern: let $X = await $A
|
|
136
|
+
- not:
|
|
137
|
+
pattern: let $X = await params
|
|
138
|
+
- not:
|
|
139
|
+
pattern: let $X = await searchParams
|
|
103
140
|
- not:
|
|
104
141
|
pattern: let $X = await Promise.all($$$)
|
|
105
142
|
- not:
|
|
106
143
|
pattern: let $X = await Promise.allSettled($$$)
|
|
107
144
|
- all:
|
|
108
145
|
- pattern: var $X = await $A
|
|
146
|
+
- not:
|
|
147
|
+
pattern: var $X = await params
|
|
148
|
+
- not:
|
|
149
|
+
pattern: var $X = await searchParams
|
|
109
150
|
- not:
|
|
110
151
|
pattern: var $X = await Promise.all($$$)
|
|
111
152
|
- not:
|
|
@@ -114,6 +155,10 @@ rule:
|
|
|
114
155
|
- pattern:
|
|
115
156
|
context: $X = await $A
|
|
116
157
|
selector: expression_statement
|
|
158
|
+
- not:
|
|
159
|
+
pattern: $X = await params
|
|
160
|
+
- not:
|
|
161
|
+
pattern: $X = await searchParams
|
|
117
162
|
- not:
|
|
118
163
|
pattern: $X = await Promise.all($$$)
|
|
119
164
|
- not:
|
|
@@ -126,6 +171,10 @@ rule:
|
|
|
126
171
|
- pattern:
|
|
127
172
|
context: await $B
|
|
128
173
|
selector: expression_statement
|
|
174
|
+
- not:
|
|
175
|
+
pattern: await params
|
|
176
|
+
- not:
|
|
177
|
+
pattern: await searchParams
|
|
129
178
|
- not:
|
|
130
179
|
has:
|
|
131
180
|
pattern: Promise.all($$)
|
|
@@ -134,18 +183,30 @@ rule:
|
|
|
134
183
|
pattern: Promise.allSettled($$)
|
|
135
184
|
- all:
|
|
136
185
|
- pattern: const $Y = await $B
|
|
186
|
+
- not:
|
|
187
|
+
pattern: const $Y = await params
|
|
188
|
+
- not:
|
|
189
|
+
pattern: const $Y = await searchParams
|
|
137
190
|
- not:
|
|
138
191
|
pattern: const $Y = await Promise.all($$$)
|
|
139
192
|
- not:
|
|
140
193
|
pattern: const $Y = await Promise.allSettled($$$)
|
|
141
194
|
- all:
|
|
142
195
|
- pattern: let $Y = await $B
|
|
196
|
+
- not:
|
|
197
|
+
pattern: let $Y = await params
|
|
198
|
+
- not:
|
|
199
|
+
pattern: let $Y = await searchParams
|
|
143
200
|
- not:
|
|
144
201
|
pattern: let $Y = await Promise.all($$$)
|
|
145
202
|
- not:
|
|
146
203
|
pattern: let $Y = await Promise.allSettled($$$)
|
|
147
204
|
- all:
|
|
148
205
|
- pattern: var $Y = await $B
|
|
206
|
+
- not:
|
|
207
|
+
pattern: var $Y = await params
|
|
208
|
+
- not:
|
|
209
|
+
pattern: var $Y = await searchParams
|
|
149
210
|
- not:
|
|
150
211
|
pattern: var $Y = await Promise.all($$$)
|
|
151
212
|
- not:
|
|
@@ -154,6 +215,10 @@ rule:
|
|
|
154
215
|
- pattern:
|
|
155
216
|
context: $Y = await $B
|
|
156
217
|
selector: expression_statement
|
|
218
|
+
- not:
|
|
219
|
+
pattern: $Y = await params
|
|
220
|
+
- not:
|
|
221
|
+
pattern: $Y = await searchParams
|
|
157
222
|
- not:
|
|
158
223
|
pattern: $Y = await Promise.all($$$)
|
|
159
224
|
- not:
|
|
@@ -173,6 +238,10 @@ rule:
|
|
|
173
238
|
- pattern:
|
|
174
239
|
context: await $C
|
|
175
240
|
selector: expression_statement
|
|
241
|
+
- not:
|
|
242
|
+
pattern: await params
|
|
243
|
+
- not:
|
|
244
|
+
pattern: await searchParams
|
|
176
245
|
- not:
|
|
177
246
|
has:
|
|
178
247
|
pattern: Promise.all($$)
|
|
@@ -181,18 +250,30 @@ rule:
|
|
|
181
250
|
pattern: Promise.allSettled($$)
|
|
182
251
|
- all:
|
|
183
252
|
- pattern: const $Z = await $C
|
|
253
|
+
- not:
|
|
254
|
+
pattern: const $Z = await params
|
|
255
|
+
- not:
|
|
256
|
+
pattern: const $Z = await searchParams
|
|
184
257
|
- not:
|
|
185
258
|
pattern: const $Z = await Promise.all($$$)
|
|
186
259
|
- not:
|
|
187
260
|
pattern: const $Z = await Promise.allSettled($$$)
|
|
188
261
|
- all:
|
|
189
262
|
- pattern: let $Z = await $C
|
|
263
|
+
- not:
|
|
264
|
+
pattern: let $Z = await params
|
|
265
|
+
- not:
|
|
266
|
+
pattern: let $Z = await searchParams
|
|
190
267
|
- not:
|
|
191
268
|
pattern: let $Z = await Promise.all($$$)
|
|
192
269
|
- not:
|
|
193
270
|
pattern: let $Z = await Promise.allSettled($$$)
|
|
194
271
|
- all:
|
|
195
272
|
- pattern: var $Z = await $C
|
|
273
|
+
- not:
|
|
274
|
+
pattern: var $Z = await params
|
|
275
|
+
- not:
|
|
276
|
+
pattern: var $Z = await searchParams
|
|
196
277
|
- not:
|
|
197
278
|
pattern: var $Z = await Promise.all($$$)
|
|
198
279
|
- not:
|
|
@@ -201,6 +282,10 @@ rule:
|
|
|
201
282
|
- pattern:
|
|
202
283
|
context: $Z = await $C
|
|
203
284
|
selector: expression_statement
|
|
285
|
+
- not:
|
|
286
|
+
pattern: $Z = await params
|
|
287
|
+
- not:
|
|
288
|
+
pattern: $Z = await searchParams
|
|
204
289
|
- not:
|
|
205
290
|
pattern: $Z = await Promise.all($$$)
|
|
206
291
|
- not:
|
|
@@ -409,3 +494,126 @@ examples:
|
|
|
409
494
|
}
|
|
410
495
|
isValid: false
|
|
411
496
|
file: 'backend/services/example.mts'
|
|
497
|
+
- code: |
|
|
498
|
+
async function run() {
|
|
499
|
+
await one()
|
|
500
|
+
await two()
|
|
501
|
+
await three()
|
|
502
|
+
}
|
|
503
|
+
isValid: false
|
|
504
|
+
file: web/app/example.tsx
|
|
505
|
+
- code: |
|
|
506
|
+
async function run() {
|
|
507
|
+
const { id } = await params
|
|
508
|
+
const topic = await getTopic(id)
|
|
509
|
+
const aliases = await getAliases(id)
|
|
510
|
+
return <div>{topic.name}{aliases.length}</div>
|
|
511
|
+
}
|
|
512
|
+
isValid: true
|
|
513
|
+
file: web/app/example.tsx
|
|
514
|
+
- code: |
|
|
515
|
+
async function run() {
|
|
516
|
+
await Promise.all([one(), two(), three()])
|
|
517
|
+
return <div />
|
|
518
|
+
}
|
|
519
|
+
isValid: true
|
|
520
|
+
file: web/app/example.tsx
|
|
521
|
+
- code: |
|
|
522
|
+
async function run() {
|
|
523
|
+
await using transaction = await beginTransaction()
|
|
524
|
+
await applyChange(transaction)
|
|
525
|
+
await transaction.commit()
|
|
526
|
+
return <div />
|
|
527
|
+
}
|
|
528
|
+
isValid: true
|
|
529
|
+
file: web/app/example.tsx
|
|
530
|
+
- code: |
|
|
531
|
+
async function run() {
|
|
532
|
+
await using transaction = await beginBoundedTransaction({ timeoutMs: 1000 })
|
|
533
|
+
await applyFirstChange(transaction)
|
|
534
|
+
await applySecondChange(transaction)
|
|
535
|
+
await transaction.commit()
|
|
536
|
+
return <div />
|
|
537
|
+
}
|
|
538
|
+
isValid: true
|
|
539
|
+
file: web/app/example.tsx
|
|
540
|
+
- code: |
|
|
541
|
+
async function run() {
|
|
542
|
+
await using transaction = await beginTransaction()
|
|
543
|
+
await applyFirstChange(transaction)
|
|
544
|
+
await applySecondChange(transaction)
|
|
545
|
+
await applyThirdChange(transaction)
|
|
546
|
+
await transaction.commit()
|
|
547
|
+
return <div />
|
|
548
|
+
}
|
|
549
|
+
isValid: false
|
|
550
|
+
file: web/app/example.tsx
|
|
551
|
+
- code: |
|
|
552
|
+
async function run() {
|
|
553
|
+
await unrelatedFirst()
|
|
554
|
+
await unrelatedSecond()
|
|
555
|
+
await unrelatedThird()
|
|
556
|
+
await using transaction = await beginTransaction()
|
|
557
|
+
await applyChange(transaction)
|
|
558
|
+
await transaction.commit()
|
|
559
|
+
return <div />
|
|
560
|
+
}
|
|
561
|
+
isValid: false
|
|
562
|
+
file: web/app/example.tsx
|
|
563
|
+
- code: |
|
|
564
|
+
async function run() {
|
|
565
|
+
await using transaction = await beginTransaction()
|
|
566
|
+
await applyFirstChange(transaction)
|
|
567
|
+
await applySecondChange(transaction)
|
|
568
|
+
await other.commit()
|
|
569
|
+
return <div />
|
|
570
|
+
}
|
|
571
|
+
isValid: false
|
|
572
|
+
file: web/app/example.tsx
|
|
573
|
+
- code: |
|
|
574
|
+
async function run() {
|
|
575
|
+
await using transaction = await beginTransaction()
|
|
576
|
+
await applyChange(transaction)
|
|
577
|
+
await transaction.commit()
|
|
578
|
+
await afterFirst()
|
|
579
|
+
await afterSecond()
|
|
580
|
+
await afterThird()
|
|
581
|
+
return <div />
|
|
582
|
+
}
|
|
583
|
+
isValid: false
|
|
584
|
+
file: web/app/example.tsx
|
|
585
|
+
- code: |
|
|
586
|
+
async function run() {
|
|
587
|
+
await using outer = await acquire()
|
|
588
|
+
{
|
|
589
|
+
await using inner = await acquire()
|
|
590
|
+
await innerFirst(inner)
|
|
591
|
+
await innerSecond(inner)
|
|
592
|
+
await innerThird(inner)
|
|
593
|
+
await inner.commit()
|
|
594
|
+
}
|
|
595
|
+
await outer.commit()
|
|
596
|
+
return <div />
|
|
597
|
+
}
|
|
598
|
+
isValid: false
|
|
599
|
+
file: web/app/example.tsx
|
|
600
|
+
- code: |
|
|
601
|
+
async function run() {
|
|
602
|
+
await using transaction = await beginTransaction()
|
|
603
|
+
await applyFirstChange(transaction)
|
|
604
|
+
await applySecondChange(transaction)
|
|
605
|
+
await transaction.rollback()
|
|
606
|
+
return <div />
|
|
607
|
+
}
|
|
608
|
+
isValid: false
|
|
609
|
+
file: web/app/example.tsx
|
|
610
|
+
- code: |
|
|
611
|
+
async function run() {
|
|
612
|
+
await using resource = await acquire()
|
|
613
|
+
await useFirst(resource)
|
|
614
|
+
await useSecond(resource)
|
|
615
|
+
await resource.dispose()
|
|
616
|
+
return <div />
|
|
617
|
+
}
|
|
618
|
+
isValid: false
|
|
619
|
+
file: web/app/example.tsx
|
package/ast-grep/sgconfig.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Vouchington CLI and extractable tooling libraries.",
|
|
5
5
|
"homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -22,9 +22,12 @@ branching, review, and release policy.
|
|
|
22
22
|
checks with a concrete reason.
|
|
23
23
|
6. Review the diff for accidental files, secrets, generated output, broken documentation links,
|
|
24
24
|
and assumptions that belong in local instructions instead.
|
|
25
|
+
7. When feedback arrives on an open change, give every surfaced item a disposition and drive it to
|
|
26
|
+
closure before hand-off; do not leave items unaddressed or silently absorb or defer them.
|
|
25
27
|
|
|
26
28
|
For portable implementation and review checks, read [implementation](references/implementation.md),
|
|
27
|
-
[review](references/review.md),
|
|
29
|
+
[review](references/review.md), [review response](references/review-response.md), and
|
|
30
|
+
[evidence sweep](references/evidence-sweep.md). The older
|
|
28
31
|
[implementation and review](references/implementation-and-review.md) remains a compact overview.
|
|
29
32
|
Local instructions remain authoritative for commands, commits, review systems, and release policy.
|
|
30
33
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Review response
|
|
2
|
+
|
|
3
|
+
Applies when responding to feedback on an open change — a human reviewer, an automated reviewer, or
|
|
4
|
+
a shepherding tool that surfaces both. This is the response side of [review](review.md); its closing
|
|
5
|
+
line, to record unresolved risk as an explicit decision or follow-up and never as an unnoticed gap,
|
|
6
|
+
is what the ladder below operationalizes.
|
|
7
|
+
|
|
8
|
+
Give every surfaced item exactly one disposition before its resolution runs, recorded where the
|
|
9
|
+
review conversation lives. Invalid — wrong, already satisfied by the current diff, or out of an
|
|
10
|
+
already-settled scope — gets a reason and closes with no code change and no follow-up. Blocking —
|
|
11
|
+
correctness, security, data safety, or a gap against a linked requirement — gets fixed, pushed, and
|
|
12
|
+
confirmed on the change's head commit before closing; closing first can leave an unfixed commit
|
|
13
|
+
behind a closed conversation. Non-blocking gets folded into an already-planned push when the fix is
|
|
14
|
+
cheap and low-risk, and otherwise gets recorded as a follow-up only when leaving it undone would
|
|
15
|
+
change behavior, structure, or risk: reuse or extend an existing follow-up before opening a new one,
|
|
16
|
+
and group related items from the same round into one. A correct item that clears none of those bars
|
|
17
|
+
— a style preference, a restatement, polish the change is fine without — is declined with a reason
|
|
18
|
+
and no follow-up; that is the expected outcome for a minor suggestion, not a lapse. Escalate — work
|
|
19
|
+
the change cannot absorb as feedback, such as a large architectural or ownership change — is
|
|
20
|
+
recorded where decisions are tracked and reported for direction rather than implemented or silently
|
|
21
|
+
downgraded to a follow-up.
|
|
22
|
+
|
|
23
|
+
Read every outstanding item before editing and drain the round locally; the cost of iterating is the
|
|
24
|
+
push, not the commit, because a push re-runs checks and re-triggers automated reviewers. Declining a
|
|
25
|
+
round without pushing is what lets it converge instead of regenerating the same suggestions on every
|
|
26
|
+
cycle. By hand-off, every item carries a disposition and reaches closure — an escalation through its
|
|
27
|
+
decision record rather than an open conversation — except where the review surface itself withholds
|
|
28
|
+
the capability to close an item; say so and leave it rather than forcing a resolution it never
|
|
29
|
+
authorized.
|
|
30
|
+
|
|
31
|
+
This skill supplies no review system, resolution mechanism, issue tracker, label, or severity
|
|
32
|
+
vocabulary; a consumer wrapper owns those.
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# NOTE: This rule flags calls to `dynamic` or `nextDynamic` (next/dynamic) that use a dynamic import.
|
|
2
|
-
# It cannot verify the import source — it matches by function name.
|
|
3
|
-
# If you have other functions named `dynamic` or `nextDynamic`, add a suppression comment.
|
|
4
|
-
id: no-dynamic-server-components-tsx
|
|
5
|
-
language: Tsx
|
|
6
|
-
severity: error
|
|
7
|
-
message: "dynamic()/nextDynamic() may wrap a server component — ensure the target has 'use client'"
|
|
8
|
-
# NOTE: ast-grep maps `.tsx` to language `Tsx`, not `TypeScript`. This rule
|
|
9
|
-
# only scans `.tsx` files; see no-dynamic-server-components.yml for the
|
|
10
|
-
# companion rule that covers `.ts` files.
|
|
11
|
-
files:
|
|
12
|
-
- 'web/**/*.tsx'
|
|
13
|
-
ignores:
|
|
14
|
-
- '**/*.test.tsx'
|
|
15
|
-
- '**/*.spec.tsx'
|
|
16
|
-
rule:
|
|
17
|
-
any:
|
|
18
|
-
- pattern: '$FN(() => import($PATH))'
|
|
19
|
-
- pattern: '$FN(() => { return import($PATH); })'
|
|
20
|
-
- pattern: '$FN(() => import($PATH).then($$$))'
|
|
21
|
-
- pattern: '$FN(() => import($PATH), $OPTS)'
|
|
22
|
-
- pattern: '$FN(() => { return import($PATH); }, $OPTS)'
|
|
23
|
-
- pattern: '$FN(() => import($PATH).then($$$), $OPTS)'
|
|
24
|
-
constraints:
|
|
25
|
-
FN:
|
|
26
|
-
regex: '^(dynamic|nextDynamic)$'
|
|
27
|
-
PATH:
|
|
28
|
-
regex: "^['\"`](\\.\\./|\\./|@/)"
|
|
29
|
-
# NOTE: `$OPTS` is only bound by the two-argument pattern arms above. This
|
|
30
|
-
# `not` constraint filters out `{ ssr: false }` only for those arms; the
|
|
31
|
-
# single-argument dynamic import arms still match and remain violations.
|
|
32
|
-
OPTS:
|
|
33
|
-
not:
|
|
34
|
-
regex: '(?:\bssr\b|[''"]ssr[''"])\s*:\s*false\b'
|
|
35
|
-
examples:
|
|
36
|
-
- code: "const Comp = dynamic(() => import('./my-component'))"
|
|
37
|
-
isValid: false
|
|
38
|
-
file: 'web/app/page.tsx'
|
|
39
|
-
- code: 'const Comp = dynamic(() => import(`./my-component`))'
|
|
40
|
-
isValid: false
|
|
41
|
-
file: 'web/app/page.tsx'
|
|
42
|
-
- code: "const Comp = dynamic(() => import('../components/comp'))"
|
|
43
|
-
isValid: false
|
|
44
|
-
file: 'web/app/page.tsx'
|
|
45
|
-
- code: "const Comp = dynamic(() => import('./add-tag-form').then(m => ({ default: m.AddTagForm })))"
|
|
46
|
-
isValid: false
|
|
47
|
-
file: 'web/app/page.tsx'
|
|
48
|
-
- code: "const Comp = nextDynamic(() => import('./my-component'))"
|
|
49
|
-
isValid: false
|
|
50
|
-
file: 'web/app/page.tsx'
|
|
51
|
-
- code: "const Comp = dynamic(() => import('./c'), { loading: () => null })"
|
|
52
|
-
isValid: false
|
|
53
|
-
file: 'web/app/page.tsx'
|
|
54
|
-
- code: "const Comp = dynamic(() => import('./c'), { not_ssr: false })"
|
|
55
|
-
isValid: false
|
|
56
|
-
file: 'web/app/page.tsx'
|
|
57
|
-
- code: "const Comp = dynamic(() => import('./c'), { ssr: falsey })"
|
|
58
|
-
isValid: false
|
|
59
|
-
file: 'web/app/page.tsx'
|
|
60
|
-
- code: "const Comp = dynamic(() => import('./c'), { ssr: false })"
|
|
61
|
-
isValid: true
|
|
62
|
-
file: 'web/app/page.tsx'
|
|
63
|
-
- code: 'const Comp = dynamic(() => import("./c"), { "ssr": false })'
|
|
64
|
-
isValid: true
|
|
65
|
-
file: 'web/app/page.tsx'
|
|
66
|
-
- code: "const Comp = dynamic(() => import('./c'), { 'ssr': false })"
|
|
67
|
-
isValid: true
|
|
68
|
-
file: 'web/app/page.tsx'
|
|
69
|
-
- code: "const Comp = lazy(() => import('./my-component'))"
|
|
70
|
-
isValid: true
|
|
71
|
-
file: 'web/app/page.tsx'
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# NOTE: ast-grep maps `.tsx` to language `Tsx`, not `TypeScript`. This rule
|
|
2
|
-
# only scans `.tsx` files; see no-enable-indexscan-off.yml for the companion
|
|
3
|
-
# rule that covers `.ts/.mts/.cts` files.
|
|
4
|
-
id: no-enable-indexscan-off-tsx
|
|
5
|
-
language: Tsx
|
|
6
|
-
severity: error
|
|
7
|
-
message: >-
|
|
8
|
-
Do not use `enable_indexscan = off` — it disables index scans repo-wide within the transaction
|
|
9
|
-
(not just for HNSW vector search), forcing a full sequential scan. For HNSW overfiltering on a
|
|
10
|
-
small hard-capped result window, use `applyFilteredVectorScan()` from `@modules/search-utils`
|
|
11
|
-
(pgvector `hnsw.iterative_scan`) instead.
|
|
12
|
-
rule:
|
|
13
|
-
any:
|
|
14
|
-
- kind: string
|
|
15
|
-
- kind: template_string
|
|
16
|
-
regex: '(?i)enable_indexscan[\s\S]{0,12}(=|to)[\s\S]{0,12}off'
|
|
17
|
-
examples:
|
|
18
|
-
- code: |-
|
|
19
|
-
import sql from 'sql-template-strings'
|
|
20
|
-
export const query = sql`SET LOCAL enable_indexscan = off`
|
|
21
|
-
isValid: false
|
|
22
|
-
file: 'web/lib/__tests__/example.tsx'
|
|
23
|
-
- code: |-
|
|
24
|
-
import sql from 'sql-template-strings'
|
|
25
|
-
export const query = sql`SET LOCAL enable_indexscan TO off`
|
|
26
|
-
isValid: false
|
|
27
|
-
file: 'web/lib/__tests__/example.tsx'
|
|
28
|
-
- code: |-
|
|
29
|
-
export function Example() {
|
|
30
|
-
return <div>hnsw.iterative_scan = strict_order</div>
|
|
31
|
-
}
|
|
32
|
-
isValid: true
|
|
33
|
-
file: 'web/components/example.tsx'
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# NOTE: `__tests__` directories must contain tests only. Shared helpers, registries,
|
|
2
|
-
# fixtures, and other imported support modules belong in a nearby `test-helpers/`
|
|
3
|
-
# directory so test roots stay easy to scan.
|
|
4
|
-
# NOTE: ast-grep maps `.tsx` to language `Tsx`, not `TypeScript`. This rule
|
|
5
|
-
# only scans `.tsx` files; see no-exports-in-tests-folder.yml for the companion
|
|
6
|
-
# rule that covers `.ts/.mts/.cts` files.
|
|
7
|
-
id: no-exports-in-tests-folder-tsx
|
|
8
|
-
language: Tsx
|
|
9
|
-
severity: error
|
|
10
|
-
message: 'Do not export from files under __tests__; move shared test support to a test-helpers directory.'
|
|
11
|
-
files:
|
|
12
|
-
- '**/__tests__/**/*.tsx'
|
|
13
|
-
rule:
|
|
14
|
-
all:
|
|
15
|
-
- kind: export_statement
|
|
16
|
-
- not:
|
|
17
|
-
pattern: export {}
|
|
18
|
-
examples:
|
|
19
|
-
- code: |
|
|
20
|
-
export function renderThing() {
|
|
21
|
-
return <div />
|
|
22
|
-
}
|
|
23
|
-
isValid: false
|
|
24
|
-
file: 'web/components/example/__tests__/comment-tree-test-helpers.tsx'
|
|
25
|
-
- code: 'export type RenderThing = () => JSX.Element'
|
|
26
|
-
isValid: false
|
|
27
|
-
file: 'web/components/__tests__/render-helpers.tsx'
|
|
28
|
-
- code: |
|
|
29
|
-
it('renders shell exports', () => {
|
|
30
|
-
expect(`export NEXT_PORT=3901`).toContain('export')
|
|
31
|
-
})
|
|
32
|
-
isValid: true
|
|
33
|
-
file: 'web/components/example/__tests__/comment-tree.test.tsx'
|
|
34
|
-
- code: |
|
|
35
|
-
function renderThing() {
|
|
36
|
-
return <div />
|
|
37
|
-
}
|
|
38
|
-
isValid: true
|
|
39
|
-
file: 'web/components/example/__tests__/comment-tree.test.tsx'
|
|
40
|
-
- code: 'export {}'
|
|
41
|
-
isValid: true
|
|
42
|
-
file: 'web/components/example/__tests__/global-jsx.test.tsx'
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
id: no-self-comparison-assertion-tsx
|
|
2
|
-
language: Tsx
|
|
3
|
-
severity: error
|
|
4
|
-
message: 'This compares an expression against itself (e.g. expect(result.id).toBe(result.id)). ast-grep metavariables enforce identical text, so both sides read the same value the same way; nearly every real occurrence of this shape is a copy-paste bug where the second operand should have been an independently derived expected value. Compare against that instead, or a fixture-provided value the code under test did not produce. (Two syntactic shapes are exempted because they cannot be tautological by construction: an expression containing a call anywhere in it, e.g. expect(f(a)).toBe(f(a)) or expect((await load()).id).toBe((await load()).id), is a fresh invocation on each side and legitimately tests that the call is deterministic/pure; and a postfix/prefix increment or decrement, e.g. expect(index++).toBe(index++), advances state between the two reads. A getter or other stateful property access is NOT exempted -- ast-grep cannot see whether a given `.foo` is a plain field or a getter with side effects, and every real violation this rule has caught in this repo is a plain field, so this stays flagged by design; if you are deliberately asserting that such an accessor is stable across two reads, add an `ast-grep-ignore: no-self-comparison-assertion-tsx` comment with that rationale instead of weakening this rule.)'
|
|
5
|
-
files:
|
|
6
|
-
- '**/*.test.tsx'
|
|
7
|
-
- '**/*.spec.tsx'
|
|
8
|
-
rule:
|
|
9
|
-
pattern: 'expect($X).toBe($X)'
|
|
10
|
-
constraints:
|
|
11
|
-
X:
|
|
12
|
-
not:
|
|
13
|
-
any:
|
|
14
|
-
- kind: 'true'
|
|
15
|
-
- kind: 'false'
|
|
16
|
-
- kind: 'number'
|
|
17
|
-
- kind: 'string'
|
|
18
|
-
- kind: 'null'
|
|
19
|
-
- kind: 'undefined'
|
|
20
|
-
- kind: 'call_expression'
|
|
21
|
-
- kind: 'update_expression'
|
|
22
|
-
- has:
|
|
23
|
-
kind: 'call_expression'
|
|
24
|
-
stopBy: end
|
|
25
|
-
examples:
|
|
26
|
-
- code: 'expect(item.id).toBe(item.id)'
|
|
27
|
-
isValid: false
|
|
28
|
-
file: 'web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx'
|
|
29
|
-
- code: 'expect(item.id).toBe(expectedId)'
|
|
30
|
-
isValid: true
|
|
31
|
-
file: 'web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx'
|
|
32
|
-
- code: 'expect(formatItemLabel(item)).toBe(formatItemLabel(item))'
|
|
33
|
-
isValid: true
|
|
34
|
-
file: 'web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx'
|
|
35
|
-
- code: 'expect((await loadItem()).id).toBe((await loadItem()).id)'
|
|
36
|
-
isValid: true
|
|
37
|
-
file: 'web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx'
|
|
38
|
-
- code: 'expect(item.id).toBe(item.id)'
|
|
39
|
-
isValid: true
|
|
40
|
-
file: 'web/components/example/add-to-list-menu-item.tsx'
|
|
41
|
-
- code: 'expect(index++).toBe(index++)'
|
|
42
|
-
isValid: true
|
|
43
|
-
file: 'web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx'
|