vouchington-tooling 0.14.0 → 0.15.1
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/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
package/README.md
CHANGED
|
@@ -93,6 +93,9 @@ ancestor of `HEAD`. `gitleaks-directory-scan` builds and scans isolated staged-i
|
|
|
93
93
|
nonignored-working-tree mirrors with an explicit config; `--directory` selects the repository root.
|
|
94
94
|
`ast-grep-examples` runs native `ast-grep test`, then validates each scoped rule's `files:` and
|
|
95
95
|
`ignores:` examples with project `languageGlobs` replay from its root `--config`.
|
|
96
|
+
The shipped pack maps `**/*.ts`, `**/*.mts`, `**/*.cts`, and `**/*.tsx` to `Tsx` so one rule
|
|
97
|
+
covers script and JSX surfaces. Consumers must use the same `languageGlobs` and `language: Tsx`;
|
|
98
|
+
do not add `-tsx` companion YAML. Angle-bracket type assertions (`<T>value`) are not valid TSX.
|
|
96
99
|
`compareAstGrepCompanions` compares recursive `<name>.yml`/`.yaml` and `<name>-tsx` companion
|
|
97
100
|
rules across every YAML field, returns stable JSON-pointer differences, and rejects unsafe or
|
|
98
101
|
ambiguous rule paths. The supplied rule path and every ancestor must be physical directories; YAML
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
# It cannot verify the import source — it matches by function name.
|
|
3
3
|
# If you have other functions named `dynamic` or `nextDynamic`, add a suppression comment.
|
|
4
4
|
id: no-dynamic-server-components
|
|
5
|
-
language:
|
|
5
|
+
language: Tsx
|
|
6
6
|
severity: error
|
|
7
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 `.ts` files; see no-dynamic-server-components-tsx.yml for the
|
|
10
|
-
# companion rule that covers `.tsx` files.
|
|
11
8
|
files:
|
|
12
|
-
-
|
|
9
|
+
- web/**/*.ts
|
|
10
|
+
- web/**/*.tsx
|
|
13
11
|
ignores:
|
|
14
12
|
- '**/*.test.ts'
|
|
15
13
|
- '**/*.spec.ts'
|
|
14
|
+
- '**/*.test.tsx'
|
|
15
|
+
- '**/*.spec.tsx'
|
|
16
16
|
rule:
|
|
17
17
|
any:
|
|
18
18
|
- pattern: '$FN(() => import($PATH))'
|
|
@@ -69,3 +69,39 @@ examples:
|
|
|
69
69
|
- code: "const Comp = lazy(() => import('./my-component'))"
|
|
70
70
|
isValid: true
|
|
71
71
|
file: 'web/app/page.ts'
|
|
72
|
+
- code: const Comp = dynamic(() => import('./my-component'))
|
|
73
|
+
isValid: false
|
|
74
|
+
file: web/app/page.tsx
|
|
75
|
+
- code: const Comp = dynamic(() => import(`./my-component`))
|
|
76
|
+
isValid: false
|
|
77
|
+
file: web/app/page.tsx
|
|
78
|
+
- code: const Comp = dynamic(() => import('../components/comp'))
|
|
79
|
+
isValid: false
|
|
80
|
+
file: web/app/page.tsx
|
|
81
|
+
- code: "const Comp = dynamic(() => import('./add-tag-form').then(m => ({ default: m.AddTagForm })))"
|
|
82
|
+
isValid: false
|
|
83
|
+
file: web/app/page.tsx
|
|
84
|
+
- code: const Comp = nextDynamic(() => import('./my-component'))
|
|
85
|
+
isValid: false
|
|
86
|
+
file: web/app/page.tsx
|
|
87
|
+
- code: "const Comp = dynamic(() => import('./c'), { loading: () => null })"
|
|
88
|
+
isValid: false
|
|
89
|
+
file: web/app/page.tsx
|
|
90
|
+
- code: "const Comp = dynamic(() => import('./c'), { not_ssr: false })"
|
|
91
|
+
isValid: false
|
|
92
|
+
file: web/app/page.tsx
|
|
93
|
+
- code: "const Comp = dynamic(() => import('./c'), { ssr: falsey })"
|
|
94
|
+
isValid: false
|
|
95
|
+
file: web/app/page.tsx
|
|
96
|
+
- code: "const Comp = dynamic(() => import('./c'), { ssr: false })"
|
|
97
|
+
isValid: true
|
|
98
|
+
file: web/app/page.tsx
|
|
99
|
+
- code: 'const Comp = dynamic(() => import("./c"), { "ssr": false })'
|
|
100
|
+
isValid: true
|
|
101
|
+
file: web/app/page.tsx
|
|
102
|
+
- code: "const Comp = dynamic(() => import('./c'), { 'ssr': false })"
|
|
103
|
+
isValid: true
|
|
104
|
+
file: web/app/page.tsx
|
|
105
|
+
- code: const Comp = lazy(() => import('./my-component'))
|
|
106
|
+
isValid: true
|
|
107
|
+
file: web/app/page.tsx
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
# NOTE: ast-grep maps `.tsx` to language `Tsx`, not `TypeScript`. This rule
|
|
2
|
-
# only scans `.ts/.mts/.cts` files; see no-enable-indexscan-off-tsx.yml for
|
|
3
|
-
# the companion rule that covers `.tsx` files.
|
|
4
1
|
id: no-enable-indexscan-off
|
|
5
|
-
language:
|
|
2
|
+
language: Tsx
|
|
6
3
|
severity: error
|
|
7
4
|
message: >-
|
|
8
5
|
Do not use `enable_indexscan = off` — it disables index scans repo-wide within the transaction
|
|
@@ -39,3 +36,19 @@ examples:
|
|
|
39
36
|
const note = 'see applyFilteredVectorScan'
|
|
40
37
|
isValid: true
|
|
41
38
|
file: 'backend/src/vector-scan.mts'
|
|
39
|
+
- code: |-
|
|
40
|
+
import sql from 'sql-template-strings'
|
|
41
|
+
export const query = sql`SET LOCAL enable_indexscan = off`
|
|
42
|
+
isValid: false
|
|
43
|
+
file: web/lib/__tests__/example.tsx
|
|
44
|
+
- code: |-
|
|
45
|
+
import sql from 'sql-template-strings'
|
|
46
|
+
export const query = sql`SET LOCAL enable_indexscan TO off`
|
|
47
|
+
isValid: false
|
|
48
|
+
file: web/lib/__tests__/example.tsx
|
|
49
|
+
- code: |-
|
|
50
|
+
export function Example() {
|
|
51
|
+
return <div>hnsw.iterative_scan = strict_order</div>
|
|
52
|
+
}
|
|
53
|
+
isValid: true
|
|
54
|
+
file: web/components/example.tsx
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
# NOTE: `__tests__` directories must contain tests only. Shared helpers, registries,
|
|
2
2
|
# fixtures, and other imported support modules belong in a nearby `test-helpers/`
|
|
3
3
|
# directory so test roots stay easy to scan.
|
|
4
|
-
|
|
5
|
-
# only scans `.ts/.mts/.cts` files; see no-exports-in-tests-folder-tsx.yml for
|
|
6
|
-
# the companion rule that covers `.tsx` files.
|
|
4
|
+
|
|
7
5
|
id: no-exports-in-tests-folder
|
|
8
|
-
language:
|
|
6
|
+
language: Tsx
|
|
9
7
|
severity: error
|
|
10
8
|
message: 'Do not export from files under __tests__; move shared test support to a test-helpers directory.'
|
|
11
9
|
files:
|
|
12
10
|
- '**/__tests__/**/*.ts'
|
|
13
11
|
- '**/__tests__/**/*.mts'
|
|
14
12
|
- '**/__tests__/**/*.cts'
|
|
13
|
+
- '**/__tests__/**/*.tsx'
|
|
15
14
|
rule:
|
|
16
15
|
all:
|
|
17
16
|
- kind: export_statement
|
|
@@ -41,3 +40,27 @@ examples:
|
|
|
41
40
|
- code: 'export {}'
|
|
42
41
|
isValid: true
|
|
43
42
|
file: 'backend/services/example/__tests__/global-script.test.mts'
|
|
43
|
+
- code: |
|
|
44
|
+
export function renderThing() {
|
|
45
|
+
return <div />
|
|
46
|
+
}
|
|
47
|
+
isValid: false
|
|
48
|
+
file: web/components/example/__tests__/comment-tree-test-helpers.tsx
|
|
49
|
+
- code: export type RenderThing = () => JSX.Element
|
|
50
|
+
isValid: false
|
|
51
|
+
file: web/components/__tests__/render-helpers.tsx
|
|
52
|
+
- code: |
|
|
53
|
+
it('renders shell exports', () => {
|
|
54
|
+
expect(`export NEXT_PORT=3901`).toContain('export')
|
|
55
|
+
})
|
|
56
|
+
isValid: true
|
|
57
|
+
file: web/components/example/__tests__/comment-tree.test.tsx
|
|
58
|
+
- code: |
|
|
59
|
+
function renderThing() {
|
|
60
|
+
return <div />
|
|
61
|
+
}
|
|
62
|
+
isValid: true
|
|
63
|
+
file: web/components/example/__tests__/comment-tree.test.tsx
|
|
64
|
+
- code: export {}
|
|
65
|
+
isValid: true
|
|
66
|
+
file: web/components/example/__tests__/global-jsx.test.tsx
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
id: no-self-comparison-assertion
|
|
2
|
-
language:
|
|
2
|
+
language: Tsx
|
|
3
3
|
severity: error
|
|
4
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` comment with that rationale instead of weakening this rule.)'
|
|
5
5
|
files:
|
|
@@ -9,6 +9,8 @@ files:
|
|
|
9
9
|
- '**/*.spec.ts'
|
|
10
10
|
- '**/*.spec.mts'
|
|
11
11
|
- '**/*.spec.cts'
|
|
12
|
+
- '**/*.test.tsx'
|
|
13
|
+
- '**/*.spec.tsx'
|
|
12
14
|
rule:
|
|
13
15
|
pattern: 'expect($X).toBe($X)'
|
|
14
16
|
constraints:
|
|
@@ -51,3 +53,21 @@ examples:
|
|
|
51
53
|
- code: 'expect(index++).toBe(index++)'
|
|
52
54
|
isValid: true
|
|
53
55
|
file: 'backend/services/example/__tests__/items.test.mts'
|
|
56
|
+
- code: expect(item.id).toBe(item.id)
|
|
57
|
+
isValid: false
|
|
58
|
+
file: web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx
|
|
59
|
+
- code: expect(item.id).toBe(expectedId)
|
|
60
|
+
isValid: true
|
|
61
|
+
file: web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx
|
|
62
|
+
- code: expect(formatItemLabel(item)).toBe(formatItemLabel(item))
|
|
63
|
+
isValid: true
|
|
64
|
+
file: web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx
|
|
65
|
+
- code: expect((await loadItem()).id).toBe((await loadItem()).id)
|
|
66
|
+
isValid: true
|
|
67
|
+
file: web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx
|
|
68
|
+
- code: expect(item.id).toBe(item.id)
|
|
69
|
+
isValid: true
|
|
70
|
+
file: web/components/example/add-to-list-menu-item.tsx
|
|
71
|
+
- code: expect(index++).toBe(index++)
|
|
72
|
+
isValid: true
|
|
73
|
+
file: web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
id: no-sync-throw-assert-on-async-enqueue
|
|
2
|
-
language:
|
|
2
|
+
language: Tsx
|
|
3
3
|
severity: error
|
|
4
4
|
message: 'Best-effort syntactic heuristic (not type-aware): this wraps a synchronous throw-assertion around a call that looks like a Promise-returning enqueue call. expect(() => fn()).not.toThrow() and .toThrow() only observe a synchronous throw and never see an async rejection, so this gives false confidence in the async contract. Await the call and assert on its resolved value instead, e.g. expect(await enqueueFoo()).toBeUndefined() or await expect(enqueueFoo()).resolves.toBeDefined().'
|
|
5
5
|
files:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
id: no-tautological-literal-assertion
|
|
2
|
-
language:
|
|
2
|
+
language: Tsx
|
|
3
3
|
severity: error
|
|
4
4
|
message: 'This asserts a literal against an identical literal (e.g. expect(true).toBe(true)), which can never fail and provides zero real coverage of the code under test. Assert on the actual value the code under test produced instead -- for example expect(await run()).not.toBeNull(), or a specific value or shape derived from calling the function being tested.'
|
|
5
5
|
files:
|
|
@@ -9,6 +9,8 @@ files:
|
|
|
9
9
|
- '**/*.spec.ts'
|
|
10
10
|
- '**/*.spec.mts'
|
|
11
11
|
- '**/*.spec.cts'
|
|
12
|
+
- '**/*.test.tsx'
|
|
13
|
+
- '**/*.spec.tsx'
|
|
12
14
|
rule:
|
|
13
15
|
pattern: 'expect($LIT).toBe($LIT)'
|
|
14
16
|
constraints:
|
|
@@ -36,3 +38,15 @@ examples:
|
|
|
36
38
|
- code: 'expect(true).toBe(true)'
|
|
37
39
|
isValid: true
|
|
38
40
|
file: 'backend/services/example/get-trending-posts.mts'
|
|
41
|
+
- code: expect(true).toBe(true)
|
|
42
|
+
isValid: false
|
|
43
|
+
file: web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx
|
|
44
|
+
- code: expect('placeholder').toBe('placeholder')
|
|
45
|
+
isValid: false
|
|
46
|
+
file: web/components/example/__tests__/source-list-item.interactions.mock.test.tsx
|
|
47
|
+
- code: expect(screen.getByRole("button")).toBeInTheDocument()
|
|
48
|
+
isValid: true
|
|
49
|
+
file: web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx
|
|
50
|
+
- code: expect(true).toBe(true)
|
|
51
|
+
isValid: true
|
|
52
|
+
file: web/components/example/add-to-list-menu-item.tsx
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# NOTE: This rule flags eslint/oxlint disable directives that suppress max-lines
|
|
2
2
|
# (or all rules) inside test files. Test files must stay within their line limit;
|
|
3
3
|
# split the test or extract reusable helpers instead.
|
|
4
|
-
|
|
5
|
-
# only scans `.ts/.mts/.cts` files; see no-test-max-lines-disable-tsx.yml for
|
|
6
|
-
# the companion rule that covers `.tsx` files.
|
|
4
|
+
|
|
7
5
|
id: no-test-max-lines-disable
|
|
8
|
-
language:
|
|
6
|
+
language: Tsx
|
|
9
7
|
severity: error
|
|
10
8
|
message: 'Test files must not disable max-lines or all lint rules; split the test or extract reusable helpers instead.'
|
|
11
9
|
files:
|
|
@@ -18,6 +16,9 @@ files:
|
|
|
18
16
|
- '**/__tests__/**/*.ts'
|
|
19
17
|
- '**/__tests__/**/*.mts'
|
|
20
18
|
- '**/__tests__/**/*.cts'
|
|
19
|
+
- '**/*.test.tsx'
|
|
20
|
+
- '**/*.spec.tsx'
|
|
21
|
+
- '**/__tests__/**/*.tsx'
|
|
21
22
|
rule:
|
|
22
23
|
kind: comment
|
|
23
24
|
any:
|
|
@@ -51,3 +52,24 @@ examples:
|
|
|
51
52
|
- code: '// oxlint-disable no-console -- split because of max-lines'
|
|
52
53
|
isValid: true
|
|
53
54
|
file: 'backend/services/users.test.ts'
|
|
55
|
+
- code: // oxlint-disable max-lines
|
|
56
|
+
isValid: false
|
|
57
|
+
file: web/components/__tests__/foo.test.tsx
|
|
58
|
+
- code: // eslint-disable
|
|
59
|
+
isValid: false
|
|
60
|
+
file: web/components/__tests__/foo.test.tsx
|
|
61
|
+
- code: // oxlint-disable vitest/expect-expect
|
|
62
|
+
isValid: true
|
|
63
|
+
file: web/components/__tests__/foo.test.tsx
|
|
64
|
+
- code: // oxlint-disable no-console
|
|
65
|
+
isValid: true
|
|
66
|
+
file: web/components/__tests__/foo.spec.tsx
|
|
67
|
+
- code: // eslint-disable max-lines-per-function
|
|
68
|
+
isValid: true
|
|
69
|
+
file: web/components/__tests__/foo.test.tsx
|
|
70
|
+
- code: // eslint-disable eslint/max-lines-per-function
|
|
71
|
+
isValid: true
|
|
72
|
+
file: web/components/__tests__/foo.test.tsx
|
|
73
|
+
- code: // oxlint-disable no-console -- split because of max-lines
|
|
74
|
+
isValid: true
|
|
75
|
+
file: web/components/__tests__/foo.test.tsx
|