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.
Files changed (27) hide show
  1. package/README.md +3 -0
  2. package/ast-grep/rules/backend-no-test-file-imports.yml +1 -1
  3. package/ast-grep/rules/no-dynamic-import-in-vitest-config.yml +1 -1
  4. package/ast-grep/rules/no-dynamic-server-components.yml +41 -5
  5. package/ast-grep/rules/no-enable-indexscan-off.yml +17 -4
  6. package/ast-grep/rules/no-exports-in-tests-folder.yml +27 -4
  7. package/ast-grep/rules/no-object-assign-new-error.yml +1 -1
  8. package/ast-grep/rules/no-runner-temp-nullish.yml +1 -1
  9. package/ast-grep/rules/no-self-comparison-assertion.yml +21 -1
  10. package/ast-grep/rules/no-sql-offset.yml +1 -1
  11. package/ast-grep/rules/no-sync-throw-assert-on-async-enqueue.yml +1 -1
  12. package/ast-grep/rules/no-tautological-literal-assertion.yml +15 -1
  13. package/ast-grep/rules/no-test-max-lines-disable.yml +26 -4
  14. package/ast-grep/rules/no-three-sequential-awaits.yml +220 -12
  15. package/ast-grep/rules/nullable-options.yml +1 -1
  16. package/ast-grep/rules/pagination-no-exact-limit-has-next.yml +1 -1
  17. package/ast-grep/rules/playwright-no-bare-meta-press.yml +1 -1
  18. package/ast-grep/rules/storybook-no-webpack-final.yml +1 -1
  19. package/ast-grep/sgconfig.yml +1 -2
  20. package/package.json +1 -1
  21. package/ast-grep/rules/no-dynamic-server-components-tsx.yml +0 -71
  22. package/ast-grep/rules/no-enable-indexscan-off-tsx.yml +0 -33
  23. package/ast-grep/rules/no-exports-in-tests-folder-tsx.yml +0 -42
  24. package/ast-grep/rules/no-self-comparison-assertion-tsx.yml +0 -43
  25. package/ast-grep/rules/no-tautological-literal-assertion-tsx.yml +0 -31
  26. package/ast-grep/rules/no-test-max-lines-disable-tsx.yml +0 -41
  27. 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
@@ -1,5 +1,5 @@
1
1
  id: backend-no-test-file-imports
2
- language: TypeScript
2
+ language: Tsx
3
3
  severity: error
4
4
  message: 'Do not import backend test files from backend Vitest files; move shared helpers to test-helpers or a support module.'
5
5
  files:
@@ -1,5 +1,5 @@
1
1
  id: no-dynamic-import-in-vitest-config
2
- language: TypeScript
2
+ language: Tsx
3
3
  severity: error
4
4
  message: >-
5
5
  vitest config files must use static imports — dynamic import() prevents
@@ -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: TypeScript
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
- - 'web/**/*.ts'
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: TypeScript
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
- # NOTE: ast-grep maps `.tsx` to language `Tsx`, not `TypeScript`. This rule
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: TypeScript
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-object-assign-new-error
2
- language: TypeScript
2
+ language: Tsx
3
3
  severity: error
4
4
  message: 'Use http-errors/http-assert or an explicit typed error helper instead of Object.assign(new Error(...))'
5
5
  files:
@@ -1,5 +1,5 @@
1
1
  id: no-runner-temp-nullish
2
- language: TypeScript
2
+ language: Tsx
3
3
  severity: error
4
4
  message: 'Use logical OR for RUNNER_TEMP temp roots so an empty string falls back safely.'
5
5
  files:
@@ -1,5 +1,5 @@
1
1
  id: no-self-comparison-assertion
2
- language: TypeScript
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-sql-offset
2
- language: TypeScript
2
+ language: Tsx
3
3
  severity: error
4
4
  message: 'Do not use SQL OFFSET in backend sql templates; use cursor pagination, LIMIT + 1, COUNT, EXISTS, or ROW_NUMBER() instead'
5
5
  files:
@@ -1,5 +1,5 @@
1
1
  id: no-sync-throw-assert-on-async-enqueue
2
- language: TypeScript
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: TypeScript
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
- # NOTE: ast-grep maps `.tsx` to language `Tsx`, not `TypeScript`. This rule
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: TypeScript
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