vouchington-tooling 0.6.2 → 0.7.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.
Files changed (50) hide show
  1. package/README.md +4 -0
  2. package/ast-grep/rules/backend-no-test-file-imports.yml +44 -0
  3. package/ast-grep/rules/force-dynamic-layout.yml +57 -0
  4. package/ast-grep/rules/force-dynamic-pages.yml +38 -0
  5. package/ast-grep/rules/no-dynamic-import-in-vitest-config.yml +30 -0
  6. package/ast-grep/rules/no-dynamic-server-components-tsx.yml +71 -0
  7. package/ast-grep/rules/no-dynamic-server-components.yml +71 -0
  8. package/ast-grep/rules/no-enable-indexscan-off-tsx.yml +33 -0
  9. package/ast-grep/rules/no-enable-indexscan-off.yml +41 -0
  10. package/ast-grep/rules/no-exports-in-tests-folder-tsx.yml +42 -0
  11. package/ast-grep/rules/no-exports-in-tests-folder.yml +43 -0
  12. package/ast-grep/rules/no-object-assign-new-error.yml +31 -0
  13. package/ast-grep/rules/no-runner-temp-nullish.yml +33 -0
  14. package/ast-grep/rules/no-self-comparison-assertion-tsx.yml +43 -0
  15. package/ast-grep/rules/no-self-comparison-assertion.yml +53 -0
  16. package/ast-grep/rules/no-sql-offset.yml +65 -0
  17. package/ast-grep/rules/no-sync-throw-assert-on-async-enqueue.yml +42 -0
  18. package/ast-grep/rules/no-tautological-literal-assertion-tsx.yml +31 -0
  19. package/ast-grep/rules/no-tautological-literal-assertion.yml +38 -0
  20. package/ast-grep/rules/no-test-max-lines-disable-tsx.yml +41 -0
  21. package/ast-grep/rules/no-test-max-lines-disable.yml +53 -0
  22. package/ast-grep/rules/no-three-sequential-awaits-tsx.yml +288 -0
  23. package/ast-grep/rules/no-three-sequential-awaits.yml +242 -0
  24. package/ast-grep/rules/nullable-options.yml +429 -0
  25. package/ast-grep/rules/pagination-no-exact-limit-has-next.yml +111 -0
  26. package/ast-grep/rules/playwright-no-bare-meta-press.yml +29 -0
  27. package/ast-grep/rules/storybook-no-webpack-final.yml +31 -0
  28. package/ast-grep/rules/web-class-component-needs-use-client.yml +175 -0
  29. package/ast-grep/rules/web-clickable-needs-pointer.yml +74 -0
  30. package/ast-grep/rules/web-dialog-content-description.yml +56 -0
  31. package/ast-grep/rules/web-icon-button-tooltip.yml +82 -0
  32. package/ast-grep/rules/web-no-inline-scrollbar-hide.yml +41 -0
  33. package/ast-grep/rules/web-no-window-open-navigation.yml +39 -0
  34. package/ast-grep/sgconfig.yml +9 -0
  35. package/dist/ast-grep-pack/index.d.mts +6 -0
  36. package/dist/ast-grep-pack/index.mjs +11 -0
  37. package/dist/cli/commands/ast-grep-pack.d.mts +2 -0
  38. package/dist/cli/commands/ast-grep-pack.mjs +11 -0
  39. package/dist/cli/index.mjs +3 -0
  40. package/dist/cli/parse-options.d.mts +1 -0
  41. package/dist/cli/parse-options.mjs +6 -0
  42. package/dist/cli/parse.d.mts +2 -0
  43. package/dist/cli/parse.mjs +3 -1
  44. package/dist/cli/usage.d.mts +1 -1
  45. package/dist/cli/usage.mjs +2 -0
  46. package/dist/index.d.mts +2 -0
  47. package/dist/index.mjs +1 -0
  48. package/package.json +8 -1
  49. package/skills/manifest.json +24 -18
  50. package/skills/stacked-prs/SKILL.md +43 -0
@@ -0,0 +1,53 @@
1
+ id: no-self-comparison-assertion
2
+ language: TypeScript
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` comment with that rationale instead of weakening this rule.)'
5
+ files:
6
+ - '**/*.test.ts'
7
+ - '**/*.test.mts'
8
+ - '**/*.test.cts'
9
+ - '**/*.spec.ts'
10
+ - '**/*.spec.mts'
11
+ - '**/*.spec.cts'
12
+ rule:
13
+ pattern: 'expect($X).toBe($X)'
14
+ constraints:
15
+ X:
16
+ not:
17
+ any:
18
+ - kind: 'true'
19
+ - kind: 'false'
20
+ - kind: 'number'
21
+ - kind: 'string'
22
+ - kind: 'null'
23
+ - kind: 'undefined'
24
+ - kind: 'call_expression'
25
+ - kind: 'update_expression'
26
+ - has:
27
+ kind: 'call_expression'
28
+ stopBy: end
29
+ examples:
30
+ - code: 'expect(result.id).toBe(result.id)'
31
+ isValid: false
32
+ file: 'backend/services/example/__tests__/items.test.mts'
33
+ - code: 'expect(conversationId).toBe(conversationId)'
34
+ isValid: false
35
+ file: 'src/__tests__/routes.user-admin.part-5.test.mts'
36
+ - code: 'expect(result.id).toBe(expectedId)'
37
+ isValid: true
38
+ file: 'backend/services/example/__tests__/items.test.mts'
39
+ - code: 'expect(await run()).not.toBeNull()'
40
+ isValid: true
41
+ file: 'src/__tests__/routes.user-admin.part-5.test.mts'
42
+ - code: 'expect(buildCanonicalRequestString(...args)).toBe(buildCanonicalRequestString(...args))'
43
+ isValid: true
44
+ file: 'backend/services/example/request-canonical.test.mts'
45
+ - code: 'expect((await load()).id).toBe((await load()).id)'
46
+ isValid: true
47
+ file: 'backend/services/example/__tests__/items.test.mts'
48
+ - code: 'expect(result.id).toBe(result.id)'
49
+ isValid: true
50
+ file: 'backend/services/example/items.mts'
51
+ - code: 'expect(index++).toBe(index++)'
52
+ isValid: true
53
+ file: 'backend/services/example/__tests__/items.test.mts'
@@ -0,0 +1,65 @@
1
+ id: no-sql-offset
2
+ language: TypeScript
3
+ severity: error
4
+ message: 'Do not use SQL OFFSET in backend sql templates; use cursor pagination, LIMIT + 1, COUNT, EXISTS, or ROW_NUMBER() instead'
5
+ files:
6
+ - 'backend/**/*.ts'
7
+ - 'backend/**/*.mts'
8
+ - 'backend/**/*.cts'
9
+ rule:
10
+ all:
11
+ - kind: template_string
12
+ # This repo's SQL templates parameterize with `${...}`; numeric literals catch raw
13
+ # examples and fixtures without flagging ordinary prose such as "offset by".
14
+ - regex: '(?i)\bOFFSET\s+(?:\$\{|[0-9])'
15
+ - inside:
16
+ kind: call_expression
17
+ stopBy: end
18
+ has:
19
+ kind: identifier
20
+ regex: '^sql$'
21
+ stopBy: neighbor
22
+ examples:
23
+ - code: |-
24
+ import sql from 'sql-template-strings'
25
+ export const query = sql`
26
+ SELECT id FROM posts
27
+ ORDER BY id DESC
28
+ OFFSET ${limit}
29
+ `
30
+ isValid: false
31
+ file: 'backend/services/example/search.mts'
32
+ - code: |-
33
+ import sql from 'sql-template-strings'
34
+ export const query = sql`
35
+ SELECT id FROM posts
36
+ ORDER BY id DESC
37
+ OFFSET 10
38
+ `
39
+ isValid: false
40
+ file: 'backend/services/example/search.mts'
41
+ - code: |-
42
+ import sql from 'sql-template-strings'
43
+ export const query = sql`
44
+ SELECT id FROM posts
45
+ ORDER BY id DESC
46
+ LIMIT ${limit + 1}
47
+ `
48
+ isValid: true
49
+ file: 'backend/services/example/search.mts'
50
+ - code: |-
51
+ export function copyBuffer(chunks: Uint8Array[]) {
52
+ let offset = 0
53
+ for (const chunk of chunks) offset += chunk.byteLength
54
+ return offset
55
+ }
56
+ isValid: true
57
+ file: 'backend/services/example/domain-verification-well-known.mts'
58
+ - code: |-
59
+ import sql from 'sql-template-strings'
60
+ export const query = sql`
61
+ INSERT INTO examples (body)
62
+ VALUES ('offset by a travel credit')
63
+ `
64
+ isValid: true
65
+ file: 'backend/src/example.mts'
@@ -0,0 +1,42 @@
1
+ id: no-sync-throw-assert-on-async-enqueue
2
+ language: TypeScript
3
+ severity: error
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
+ files:
6
+ - 'backend/**/*.test.mts'
7
+ rule:
8
+ # NOTE: member-call patterns MUST precede plain-call patterns in this list. ast-grep's `any:`
9
+ # does not backtrack across constraint failures: once an earlier arm structurally matches a node
10
+ # (even loosely, e.g. a bare-identifier pattern absorbing a qualified member-expression) and its
11
+ # constraint then fails, the match for that node is rejected outright rather than falling through
12
+ # to try a later, more specific arm. Listing the more specific member-call shape first avoids this.
13
+ any:
14
+ - pattern: 'expect(() => $OBJ.$CALL($$$ARGS)).not.toThrow()'
15
+ - pattern: 'expect(() => $OBJ.$CALL($$$ARGS)).toThrow()'
16
+ - pattern: 'expect(() => $CALL($$$ARGS)).not.toThrow()'
17
+ - pattern: 'expect(() => $CALL($$$ARGS)).toThrow()'
18
+ constraints:
19
+ CALL:
20
+ all:
21
+ - regex: '^enqueue\w*$'
22
+ - not:
23
+ regex: 'BestEffort'
24
+ examples:
25
+ - code: 'expect(() => enqueueBulkSourceSyncs([])).not.toThrow()'
26
+ isValid: false
27
+ file: 'backend/queues/example/enqueues.generated.test.mts'
28
+ - code: 'expect(() => queueModule.enqueueBulkSourceSyncs([])).not.toThrow()'
29
+ isValid: false
30
+ file: 'backend/queues/example/enqueues.test.mts'
31
+ - code: "expect(() => assertSafeUrlSync('http://example.com/path')).not.toThrow()"
32
+ isValid: true
33
+ file: 'backend/services/example/ssrf.test.mts'
34
+ - code: 'expect(await enqueueBulkSourceSyncs([])).toBeUndefined()'
35
+ isValid: true
36
+ file: 'backend/queues/example/enqueues.generated.test.mts'
37
+ - code: 'expect(() => enqueueBulkSourceSyncs([])).not.toThrow()'
38
+ isValid: true
39
+ file: 'backend/queues/example/enqueues.mts'
40
+ - code: "expect(() => enqueueReportResolutionNotificationsBestEffort([{ user_id: crypto.randomUUID(), id: crypto.randomUUID() }], () => { throw new Error('queue unavailable') })).not.toThrow()"
41
+ isValid: true
42
+ file: 'backend/services/example/__tests__/resolve.test.mts'
@@ -0,0 +1,31 @@
1
+ id: no-tautological-literal-assertion-tsx
2
+ language: Tsx
3
+ severity: error
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
+ files:
6
+ - '**/*.test.tsx'
7
+ - '**/*.spec.tsx'
8
+ rule:
9
+ pattern: 'expect($LIT).toBe($LIT)'
10
+ constraints:
11
+ LIT:
12
+ any:
13
+ - kind: 'true'
14
+ - kind: 'false'
15
+ - kind: 'number'
16
+ - kind: 'string'
17
+ - kind: 'null'
18
+ - kind: 'undefined'
19
+ examples:
20
+ - code: 'expect(true).toBe(true)'
21
+ isValid: false
22
+ file: 'web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx'
23
+ - code: "expect('placeholder').toBe('placeholder')"
24
+ isValid: false
25
+ file: 'web/components/example/__tests__/source-list-item.interactions.mock.test.tsx'
26
+ - code: 'expect(screen.getByRole("button")).toBeInTheDocument()'
27
+ isValid: true
28
+ file: 'web/components/example/__tests__/add-to-list-menu-item.mock.test.tsx'
29
+ - code: 'expect(true).toBe(true)'
30
+ isValid: true
31
+ file: 'web/components/example/add-to-list-menu-item.tsx'
@@ -0,0 +1,38 @@
1
+ id: no-tautological-literal-assertion
2
+ language: TypeScript
3
+ severity: error
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
+ files:
6
+ - '**/*.test.ts'
7
+ - '**/*.test.mts'
8
+ - '**/*.test.cts'
9
+ - '**/*.spec.ts'
10
+ - '**/*.spec.mts'
11
+ - '**/*.spec.cts'
12
+ rule:
13
+ pattern: 'expect($LIT).toBe($LIT)'
14
+ constraints:
15
+ LIT:
16
+ any:
17
+ - kind: 'true'
18
+ - kind: 'false'
19
+ - kind: 'number'
20
+ - kind: 'string'
21
+ - kind: 'null'
22
+ - kind: 'undefined'
23
+ examples:
24
+ - code: 'expect(true).toBe(true)'
25
+ isValid: false
26
+ file: 'src/__tests__/client-content-routes.test.mts'
27
+ - code: "expect('placeholder').toBe('placeholder')"
28
+ isValid: false
29
+ file: 'backend/services/example/__tests__/items.test.mts'
30
+ - code: 'expect(await run()).not.toBeNull()'
31
+ isValid: true
32
+ file: 'src/__tests__/client-content-routes.test.mts'
33
+ - code: 'expect(page2.results.length).toBeLessThanOrEqual(2)'
34
+ isValid: true
35
+ file: 'backend/services/example/get-trending-posts.test.mts'
36
+ - code: 'expect(true).toBe(true)'
37
+ isValid: true
38
+ file: 'backend/services/example/get-trending-posts.mts'
@@ -0,0 +1,41 @@
1
+ # NOTE: This rule flags eslint/oxlint disable directives that suppress max-lines
2
+ # (or all rules) inside test files. Test files must stay within their line limit;
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 `.tsx` files; see no-test-max-lines-disable.yml for the
6
+ # companion rule that covers `.ts/.mts/.cts` files.
7
+ id: no-test-max-lines-disable-tsx
8
+ language: Tsx
9
+ severity: error
10
+ message: 'Test files must not disable max-lines or all lint rules; split the test or extract reusable helpers instead.'
11
+ files:
12
+ - '**/*.test.tsx'
13
+ - '**/*.spec.tsx'
14
+ - '**/__tests__/**/*.tsx'
15
+ rule:
16
+ kind: comment
17
+ any:
18
+ - regex: '^(?://|/\*)\s*(?:eslint|oxlint)-disable(?:-next-line|-line)?\s*(?:\*/\s*$|--|\s*$)'
19
+ - regex: '^(?://|/\*)\s*(?:eslint|oxlint)-disable(?:-next-line|-line)?(?:[^\n-]|-[^\n-])*(?:\s|,)(?:max-lines(?:[^-a-zA-Z0-9_]|$)|eslint/max-lines(?:[^-a-zA-Z0-9_]|$))'
20
+ examples:
21
+ - code: '// oxlint-disable max-lines'
22
+ isValid: false
23
+ file: 'web/components/__tests__/foo.test.tsx'
24
+ - code: '// eslint-disable'
25
+ isValid: false
26
+ file: 'web/components/__tests__/foo.test.tsx'
27
+ - code: '// oxlint-disable vitest/expect-expect'
28
+ isValid: true
29
+ file: 'web/components/__tests__/foo.test.tsx'
30
+ - code: '// oxlint-disable no-console'
31
+ isValid: true
32
+ file: 'web/components/__tests__/foo.spec.tsx'
33
+ - code: '// eslint-disable max-lines-per-function'
34
+ isValid: true
35
+ file: 'web/components/__tests__/foo.test.tsx'
36
+ - code: '// eslint-disable eslint/max-lines-per-function'
37
+ isValid: true
38
+ file: 'web/components/__tests__/foo.test.tsx'
39
+ - code: '// oxlint-disable no-console -- split because of max-lines'
40
+ isValid: true
41
+ file: 'web/components/__tests__/foo.test.tsx'
@@ -0,0 +1,53 @@
1
+ # NOTE: This rule flags eslint/oxlint disable directives that suppress max-lines
2
+ # (or all rules) inside test files. Test files must stay within their line limit;
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.
7
+ id: no-test-max-lines-disable
8
+ language: TypeScript
9
+ severity: error
10
+ message: 'Test files must not disable max-lines or all lint rules; split the test or extract reusable helpers instead.'
11
+ files:
12
+ - '**/*.test.ts'
13
+ - '**/*.test.mts'
14
+ - '**/*.test.cts'
15
+ - '**/*.spec.ts'
16
+ - '**/*.spec.mts'
17
+ - '**/*.spec.cts'
18
+ - '**/__tests__/**/*.ts'
19
+ - '**/__tests__/**/*.mts'
20
+ - '**/__tests__/**/*.cts'
21
+ rule:
22
+ kind: comment
23
+ any:
24
+ - regex: '^(?://|/\*)\s*(?:eslint|oxlint)-disable(?:-next-line|-line)?\s*(?:\*/\s*$|--|\s*$)'
25
+ - regex: '^(?://|/\*)\s*(?:eslint|oxlint)-disable(?:-next-line|-line)?(?:[^\n-]|-[^\n-])*(?:\s|,)(?:max-lines(?:[^-a-zA-Z0-9_]|$)|eslint/max-lines(?:[^-a-zA-Z0-9_]|$))'
26
+ examples:
27
+ - code: '// oxlint-disable max-lines'
28
+ isValid: false
29
+ file: 'backend/services/users.test.ts'
30
+ - code: '// eslint-disable'
31
+ isValid: false
32
+ file: 'backend/services/users.test.mts'
33
+ - code: '// oxlint-disable-next-line max-lines'
34
+ isValid: false
35
+ file: 'backend/services/users.spec.ts'
36
+ - code: '/* eslint-disable eslint/max-lines */'
37
+ isValid: false
38
+ file: 'backend/__tests__/foo.ts'
39
+ - code: '// oxlint-disable vitest/expect-expect'
40
+ isValid: true
41
+ file: 'backend/services/users.test.ts'
42
+ - code: '// oxlint-disable no-console'
43
+ isValid: true
44
+ file: 'backend/services/users.test.mts'
45
+ - code: '// eslint-disable max-lines-per-function'
46
+ isValid: true
47
+ file: 'backend/services/users.test.ts'
48
+ - code: '// eslint-disable eslint/max-lines-per-function'
49
+ isValid: true
50
+ file: 'backend/services/users.test.ts'
51
+ - code: '// oxlint-disable no-console -- split because of max-lines'
52
+ isValid: true
53
+ file: 'backend/services/users.test.ts'
@@ -0,0 +1,288 @@
1
+ id: no-three-sequential-awaits-tsx
2
+ language: Tsx
3
+ severity: error
4
+ message: 'Avoid 3 sequential await statements; abstract dependent work or parallelize independent work with Promise.all().'
5
+ files:
6
+ - '**/*.tsx'
7
+ ignores:
8
+ - '**/*.test.tsx'
9
+ - '**/*.spec.tsx'
10
+ - '**/*.mock.test.tsx'
11
+ - '**/*.stories.tsx'
12
+ - 'web/test-helpers/**'
13
+ rule:
14
+ all:
15
+ - not:
16
+ follows:
17
+ stopBy: neighbor
18
+ any:
19
+ - all:
20
+ - pattern:
21
+ context: await $P
22
+ selector: expression_statement
23
+ - not:
24
+ pattern: await params
25
+ - not:
26
+ pattern: await searchParams
27
+ - not:
28
+ has:
29
+ pattern: Promise.all($$)
30
+ - not:
31
+ has:
32
+ pattern: Promise.allSettled($$)
33
+ - all:
34
+ - pattern: const $W = await $P
35
+ - not:
36
+ pattern: const $W = await params
37
+ - not:
38
+ pattern: const $W = await searchParams
39
+ - not:
40
+ pattern: const $W = await Promise.all($$$)
41
+ - not:
42
+ pattern: const $W = await Promise.allSettled($$$)
43
+ - all:
44
+ - pattern: let $W = await $P
45
+ - not:
46
+ pattern: let $W = await params
47
+ - not:
48
+ pattern: let $W = await searchParams
49
+ - not:
50
+ pattern: let $W = await Promise.all($$$)
51
+ - not:
52
+ pattern: let $W = await Promise.allSettled($$$)
53
+ - all:
54
+ - pattern: var $W = await $P
55
+ - not:
56
+ pattern: var $W = await params
57
+ - not:
58
+ pattern: var $W = await searchParams
59
+ - not:
60
+ pattern: var $W = await Promise.all($$$)
61
+ - not:
62
+ pattern: var $W = await Promise.allSettled($$$)
63
+ - all:
64
+ - pattern:
65
+ context: $W = await $P
66
+ selector: expression_statement
67
+ - not:
68
+ pattern: $W = await params
69
+ - not:
70
+ pattern: $W = await searchParams
71
+ - not:
72
+ pattern: $W = await Promise.all($$$)
73
+ - not:
74
+ pattern: $W = await Promise.allSettled($$$)
75
+ - any:
76
+ - all:
77
+ - pattern:
78
+ context: await $A
79
+ selector: expression_statement
80
+ - not:
81
+ pattern: await params
82
+ - not:
83
+ pattern: await searchParams
84
+ - not:
85
+ has:
86
+ pattern: Promise.all($$)
87
+ - not:
88
+ has:
89
+ pattern: Promise.allSettled($$)
90
+ - all:
91
+ - pattern: const $X = await $A
92
+ - not:
93
+ pattern: const $X = await params
94
+ - not:
95
+ pattern: const $X = await searchParams
96
+ - not:
97
+ pattern: const $X = await Promise.all($$$)
98
+ - not:
99
+ pattern: const $X = await Promise.allSettled($$$)
100
+ - all:
101
+ - pattern: let $X = await $A
102
+ - not:
103
+ pattern: let $X = await params
104
+ - not:
105
+ pattern: let $X = await searchParams
106
+ - not:
107
+ pattern: let $X = await Promise.all($$$)
108
+ - not:
109
+ pattern: let $X = await Promise.allSettled($$$)
110
+ - all:
111
+ - pattern: var $X = await $A
112
+ - not:
113
+ pattern: var $X = await params
114
+ - not:
115
+ pattern: var $X = await searchParams
116
+ - not:
117
+ pattern: var $X = await Promise.all($$$)
118
+ - not:
119
+ pattern: var $X = await Promise.allSettled($$$)
120
+ - all:
121
+ - pattern:
122
+ context: $X = await $A
123
+ selector: expression_statement
124
+ - not:
125
+ pattern: $X = await params
126
+ - not:
127
+ pattern: $X = await searchParams
128
+ - not:
129
+ pattern: $X = await Promise.all($$$)
130
+ - not:
131
+ pattern: $X = await Promise.allSettled($$$)
132
+ - precedes:
133
+ stopBy: neighbor
134
+ all:
135
+ - any:
136
+ - all:
137
+ - pattern:
138
+ context: await $B
139
+ selector: expression_statement
140
+ - not:
141
+ pattern: await params
142
+ - not:
143
+ pattern: await searchParams
144
+ - not:
145
+ has:
146
+ pattern: Promise.all($$)
147
+ - not:
148
+ has:
149
+ pattern: Promise.allSettled($$)
150
+ - all:
151
+ - pattern: const $Y = await $B
152
+ - not:
153
+ pattern: const $Y = await params
154
+ - not:
155
+ pattern: const $Y = await searchParams
156
+ - not:
157
+ pattern: const $Y = await Promise.all($$$)
158
+ - not:
159
+ pattern: const $Y = await Promise.allSettled($$$)
160
+ - all:
161
+ - pattern: let $Y = await $B
162
+ - not:
163
+ pattern: let $Y = await params
164
+ - not:
165
+ pattern: let $Y = await searchParams
166
+ - not:
167
+ pattern: let $Y = await Promise.all($$$)
168
+ - not:
169
+ pattern: let $Y = await Promise.allSettled($$$)
170
+ - all:
171
+ - pattern: var $Y = await $B
172
+ - not:
173
+ pattern: var $Y = await params
174
+ - not:
175
+ pattern: var $Y = await searchParams
176
+ - not:
177
+ pattern: var $Y = await Promise.all($$$)
178
+ - not:
179
+ pattern: var $Y = await Promise.allSettled($$$)
180
+ - all:
181
+ - pattern:
182
+ context: $Y = await $B
183
+ selector: expression_statement
184
+ - not:
185
+ pattern: $Y = await params
186
+ - not:
187
+ pattern: $Y = await searchParams
188
+ - not:
189
+ pattern: $Y = await Promise.all($$$)
190
+ - not:
191
+ pattern: $Y = await Promise.allSettled($$$)
192
+ - precedes:
193
+ stopBy: neighbor
194
+ any:
195
+ - all:
196
+ - pattern:
197
+ context: await $C
198
+ selector: expression_statement
199
+ - not:
200
+ pattern: await params
201
+ - not:
202
+ pattern: await searchParams
203
+ - not:
204
+ has:
205
+ pattern: Promise.all($$)
206
+ - not:
207
+ has:
208
+ pattern: Promise.allSettled($$)
209
+ - all:
210
+ - pattern: const $Z = await $C
211
+ - not:
212
+ pattern: const $Z = await params
213
+ - not:
214
+ pattern: const $Z = await searchParams
215
+ - not:
216
+ pattern: const $Z = await Promise.all($$$)
217
+ - not:
218
+ pattern: const $Z = await Promise.allSettled($$$)
219
+ - all:
220
+ - pattern: let $Z = await $C
221
+ - not:
222
+ pattern: let $Z = await params
223
+ - not:
224
+ pattern: let $Z = await searchParams
225
+ - not:
226
+ pattern: let $Z = await Promise.all($$$)
227
+ - not:
228
+ pattern: let $Z = await Promise.allSettled($$$)
229
+ - all:
230
+ - pattern: var $Z = await $C
231
+ - not:
232
+ pattern: var $Z = await params
233
+ - not:
234
+ pattern: var $Z = await searchParams
235
+ - not:
236
+ pattern: var $Z = await Promise.all($$$)
237
+ - not:
238
+ pattern: var $Z = await Promise.allSettled($$$)
239
+ - all:
240
+ - pattern:
241
+ context: $Z = await $C
242
+ selector: expression_statement
243
+ - not:
244
+ pattern: $Z = await params
245
+ - not:
246
+ pattern: $Z = await searchParams
247
+ - not:
248
+ pattern: $Z = await Promise.all($$$)
249
+ - not:
250
+ pattern: $Z = await Promise.allSettled($$$)
251
+ constraints:
252
+ A:
253
+ not:
254
+ regex: '^Promise\.all(Settled)?\b'
255
+ B:
256
+ not:
257
+ regex: '^Promise\.all(Settled)?\b'
258
+ C:
259
+ not:
260
+ regex: '^Promise\.all(Settled)?\b'
261
+ P:
262
+ not:
263
+ regex: '^Promise\.all(Settled)?\b'
264
+ examples:
265
+ - code: |
266
+ async function run() {
267
+ await one()
268
+ await two()
269
+ await three()
270
+ }
271
+ isValid: false
272
+ file: 'web/app/example.tsx'
273
+ - code: |
274
+ async function run() {
275
+ const { id } = await params
276
+ const topic = await getTopic(id)
277
+ const aliases = await getAliases(id)
278
+ return <div>{topic.name}{aliases.length}</div>
279
+ }
280
+ isValid: true
281
+ file: 'web/app/example.tsx'
282
+ - code: |
283
+ async function run() {
284
+ await Promise.all([one(), two(), three()])
285
+ return <div />
286
+ }
287
+ isValid: true
288
+ file: 'web/app/example.tsx'