Add shared ast-grep rules and wire them into CI - #423
Conversation
27 ast-grep rules that statically enforce the BaseApp frontend code guidelines. They live here and are shared with consuming templates the same way the agent skills are: the template symlinks `.ast-grep` and keeps its own `sgconfig.yml`. Rules cover TypeScript conventions (no enums, prefix-less type names, `*Props` as interface, loose autocomplete, types out of `index.tsx`), Next.js/React (next/image, Server Component pages, `next/link` over `router.push`, no fetching in `useEffect`), styling (sx prop limit, theme tokens over hex, `styled()` and `StyleSheet.create` placement, `prose-*` classes, BaseApp wrappers over raw MUI, native `useTheme` source), and data/forms/state (preloaded Relay queries, `withRelay` fallback, structured query keys, typed `useForm`, react-hook-form only, `LoadingButton` for submits, no Redux, no module-level zustand store). Every rule has valid/invalid test cases and an accepted snapshot in `.ast-grep/rule-tests/`. Both `sgconfig.yml` files map `*.ts` onto the TSX grammar so one `language: Tsx` rule covers both extensions instead of needing a duplicate per extension. A rule is `severity: error` only where both repos are already clean, so adding the stage can't break the first build. The rest ship as warnings; `.ast-grep/README.md` lists the pre-existing violations that block four of them from graduating. CI: `pnpm lint:ast-grep` (test + scan) runs as an `ast-grep` step in the build-and-lint job here, and as a `Web: AstGrep` stage in the consuming template's Jenkinsfile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCvYtccHsP2W8iU7SrzG83
|
📝 WalkthroughWalkthroughThis PR adds ast-grep tooling, configuration, CI integration, documentation, 19 repository lint rules, and valid/invalid test fixtures with snapshots across design-system, Next.js, data, state, styling, forms, and TypeScript conventions. ChangesAst-grep lint rollout
Estimated code review effort: 3 (Moderate) | ~25 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.ast-grep/rules/ts-loose-autocomplete.yml (1)
9-18: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftValidate the matching behavior for union member boundaries.
The rule uses direct-child matching for
string, but descendant matching for string literals. This can miss valid occurrences like nested unions and can match nested literals inside non-union members. Use matching that only looks at union constituents and add regression cases for both failure modes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.ast-grep/rules/ts-loose-autocomplete.yml around lines 9 - 18, Update the all matcher in the TypeScript loose-autocomplete rule so both the predefined string type and string literal checks inspect only direct union constituents, preventing nested-union misses and literals nested inside non-union members from matching; add regression cases covering both boundary conditions.
🧹 Nitpick comments (3)
.ast-grep/rule-tests/next-page-no-client-data-fetching-test.yml (1)
12-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCover
useSuspenseQueryin this test.The rule matches
useQuery,useSuspenseQuery, anduseLazyLoadQuery, but this file tests only the first and third. Add an invaliduseSuspenseQuery(...)case and update the snapshot so that branch remains covered.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.ast-grep/rule-tests/next-page-no-client-data-fetching-test.yml around lines 12 - 22, Add an invalid test case using useSuspenseQuery(...) to the invalid examples in the next-page client-data-fetching rule tests, alongside the existing useQuery and useLazyLoadQuery cases. Update the associated snapshot to include the new diagnostic and preserve coverage for all matched query hooks..ast-grep/rule-tests/mui-styled-not-in-index-test.yml (1)
1-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest the rule’s file scope.
The rule in
.ast-grep/rules/mui-styled-not-in-index.ymlis restricted to**/index.tsxat Line 6, but this test provides no filename. It verifies only thatstyled()matches. It does not verify rejection inindex.tsxor allowance instyled.tsx. Add a scan-level fixture for both paths, or document that this filter is verified outsideast-grep test. ast-grep documentsfilesas a file-path filter, while rule tests use source-code cases. (ast-grep.github.io)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.ast-grep/rule-tests/mui-styled-not-in-index-test.yml around lines 1 - 14, Update the tests for the mui-styled-not-in-index rule to verify its file-path scope: add scan-level fixtures showing styled() is rejected in index.tsx and allowed in styled.tsx, or explicitly document that this files filter is tested outside ast-grep test. Keep the existing source-matching cases while ensuring the **/index.tsx restriction is covered..ast-grep/rule-tests/mui-sx-prop-limit-test.yml (1)
9-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest the exact four-property boundary.
nthChild: 4matches the fourth named child because ast-grep uses one-based child positions. The rule must report ansxobject with exactly four properties, but this test only covers five properties. Add a four-property invalid case to prevent a threshold regression. (astgrep.com)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.ast-grep/rule-tests/mui-sx-prop-limit-test.yml around lines 9 - 21, Add an invalid fixture in the test’s invalid cases with an sx object containing exactly four named properties, preserving the existing five-property case and JSX structure. Ensure the new case exercises the rule’s exact four-property threshold.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.ast-grep/rules/form-submit-use-loading-button.yml:
- Around line 14-15: Update the JSX attribute matcher in the form-submit rule to
match only the literal type="submit" attribute, excluding dynamic expressions
such as type={submitType}; add a valid fixture covering dynamic or missing type
values that must not match.
In @.ast-grep/rules/native-use-design-system-theme.yml:
- Around line 18-21: Update the source exception in the ast-grep rule to match
only the exact `@baseapp-frontend/design-system/providers/native` import and
explicitly supported relative paths, rather than any import ending in
providers/native. Modify the regex under the not/has source condition while
preserving the rule’s other matching behavior.
In @.ast-grep/rules/next-image-explicit-dimensions.yml:
- Around line 17-20: Update
.ast-grep/rule-tests/next-image-explicit-dimensions-test.yml lines 5-6 so the
Image using fill is wrapped in a positioned parent, making the fixture satisfy
Next.js positioning requirements;
.ast-grep/rules/next-image-explicit-dimensions.yml lines 17-20 requires no
direct change.
In @.ast-grep/rules/next-image-no-custom-loader.yml:
- Around line 3-4: Update the warning message in the next-image-no-custom-loader
rule to remove the claim that images.remotePatterns replaces a custom loader.
State the project’s policy on disallowing custom loaders and direct users to the
approved replacement or configuration documented in the referenced
image-optimization guidance, while preserving the warning’s explanation of the
loader impact.
In @.ast-grep/rules/next-page-no-use-client.yml:
- Around line 7-9: Scope the matcher in next-page-no-use-client to module-level
directive/prologue expression statements so nested literals are ignored. In
.ast-grep/rule-tests/next-page-no-use-client-test.yml lines 2-15, add a valid
nested-string fixture, and update
.ast-grep/rule-tests/__snapshots__/next-page-no-use-client-snapshot.yml to
reflect that it produces no report.
In @.ast-grep/rules/next-prefer-link-over-router-push.yml:
- Around line 15-18: Update the router.push matching rule to recognize static
template_string destinations with no substitutions in addition to quoted string
literals, so calls such as router.push(`/settings`) are covered. Anchor the
change to the arguments matcher in next-prefer-link-over-router-push and
preserve exclusion of interpolated templates.
- Around line 15-18: Update the literal checks in
.ast-grep/rules/next-prefer-link-over-router-push.yml at lines 15-18 and
.ast-grep/rules/query-no-inline-query-key.yml at lines 14-16 to match both
string and template_string nodes. Add an invalid regression test covering a
template-literal inline query-key array item, and ensure static template
destinations such as router.push calls are detected.
In @.ast-grep/rules/relay-withrelay-requires-fallback.yml:
- Around line 11-17: Restrict the fallback-property check in the withRelay rule
to direct keys of the second argument’s options object, rather than descendants
reached by stopBy: end. Preserve matching when that object directly contains
fallback, and add a regression case where the first callback returns fallback
but the withRelay options object omits it.
In @.ast-grep/rules/state-no-redux.yml:
- Line 9: Update the package-matching regex in the state-no-redux rule to reject
imports from the listed Redux packages and any subpath beneath them, while
preserving exact-root matching. Extend the cases in state-no-redux-test.yml to
cover representative subpath imports such as `@reduxjs/toolkit/query/react` and
redux-saga/effects.
In @.ast-grep/rules/tailwind-no-raw-text-size.yml:
- Around line 6-7: The raw text-size rule in
.ast-grep/rules/tailwind-no-raw-text-size.yml must match only standalone
Tailwind text-size tokens, not hyphenated classes such as icon-text-sm,
--text-sm, or text-sm-foo; replace the word-boundary matching with token-aware
boundaries. Update .ast-grep/rule-tests/tailwind-no-raw-text-size-test.yml to
add a valid hyphenated custom-class fixture demonstrating it is not flagged.
---
Outside diff comments:
In @.ast-grep/rules/ts-loose-autocomplete.yml:
- Around line 9-18: Update the all matcher in the TypeScript loose-autocomplete
rule so both the predefined string type and string literal checks inspect only
direct union constituents, preventing nested-union misses and literals nested
inside non-union members from matching; add regression cases covering both
boundary conditions.
---
Nitpick comments:
In @.ast-grep/rule-tests/mui-styled-not-in-index-test.yml:
- Around line 1-14: Update the tests for the mui-styled-not-in-index rule to
verify its file-path scope: add scan-level fixtures showing styled() is rejected
in index.tsx and allowed in styled.tsx, or explicitly document that this files
filter is tested outside ast-grep test. Keep the existing source-matching cases
while ensuring the **/index.tsx restriction is covered.
In @.ast-grep/rule-tests/mui-sx-prop-limit-test.yml:
- Around line 9-21: Add an invalid fixture in the test’s invalid cases with an
sx object containing exactly four named properties, preserving the existing
five-property case and JSX structure. Ensure the new case exercises the rule’s
exact four-property threshold.
In @.ast-grep/rule-tests/next-page-no-client-data-fetching-test.yml:
- Around line 12-22: Add an invalid test case using useSuspenseQuery(...) to the
invalid examples in the next-page client-data-fetching rule tests, alongside the
existing useQuery and useLazyLoadQuery cases. Update the associated snapshot to
include the new diagnostic and preserve coverage for all matched query hooks.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 988d4d54-168a-4a18-8199-f1c1342e7bef
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (87)
.ast-grep/README.md.ast-grep/rule-tests/__snapshots__/ds-prefer-baseapp-wrapper-snapshot.yml.ast-grep/rule-tests/__snapshots__/form-no-other-form-libraries-snapshot.yml.ast-grep/rule-tests/__snapshots__/form-submit-use-loading-button-snapshot.yml.ast-grep/rule-tests/__snapshots__/form-useform-requires-generic-snapshot.yml.ast-grep/rule-tests/__snapshots__/mui-no-hardcoded-color-snapshot.yml.ast-grep/rule-tests/__snapshots__/mui-styled-not-in-index-snapshot.yml.ast-grep/rule-tests/__snapshots__/mui-sx-prop-limit-snapshot.yml.ast-grep/rule-tests/__snapshots__/native-styles-not-in-index-snapshot.yml.ast-grep/rule-tests/__snapshots__/native-use-design-system-theme-snapshot.yml.ast-grep/rule-tests/__snapshots__/next-image-explicit-dimensions-snapshot.yml.ast-grep/rule-tests/__snapshots__/next-image-no-custom-loader-snapshot.yml.ast-grep/rule-tests/__snapshots__/next-no-img-element-snapshot.yml.ast-grep/rule-tests/__snapshots__/next-page-no-client-data-fetching-snapshot.yml.ast-grep/rule-tests/__snapshots__/next-page-no-use-client-snapshot.yml.ast-grep/rule-tests/__snapshots__/next-prefer-link-over-router-push-snapshot.yml.ast-grep/rule-tests/__snapshots__/query-no-inline-query-key-snapshot.yml.ast-grep/rule-tests/__snapshots__/react-no-data-fetching-in-useeffect-snapshot.yml.ast-grep/rule-tests/__snapshots__/relay-no-uselazyloadquery-snapshot.yml.ast-grep/rule-tests/__snapshots__/relay-withrelay-requires-fallback-snapshot.yml.ast-grep/rule-tests/__snapshots__/state-no-redux-snapshot.yml.ast-grep/rule-tests/__snapshots__/state-zustand-no-global-store-snapshot.yml.ast-grep/rule-tests/__snapshots__/tailwind-no-raw-text-size-snapshot.yml.ast-grep/rule-tests/__snapshots__/ts-loose-autocomplete-snapshot.yml.ast-grep/rule-tests/__snapshots__/ts-no-enum-snapshot.yml.ast-grep/rule-tests/__snapshots__/ts-no-type-name-prefix-snapshot.yml.ast-grep/rule-tests/__snapshots__/ts-props-use-interface-snapshot.yml.ast-grep/rule-tests/__snapshots__/ts-types-not-in-index-snapshot.yml.ast-grep/rule-tests/ds-prefer-baseapp-wrapper-test.yml.ast-grep/rule-tests/form-no-other-form-libraries-test.yml.ast-grep/rule-tests/form-submit-use-loading-button-test.yml.ast-grep/rule-tests/form-useform-requires-generic-test.yml.ast-grep/rule-tests/mui-no-hardcoded-color-test.yml.ast-grep/rule-tests/mui-styled-not-in-index-test.yml.ast-grep/rule-tests/mui-sx-prop-limit-test.yml.ast-grep/rule-tests/native-styles-not-in-index-test.yml.ast-grep/rule-tests/native-use-design-system-theme-test.yml.ast-grep/rule-tests/next-image-explicit-dimensions-test.yml.ast-grep/rule-tests/next-image-no-custom-loader-test.yml.ast-grep/rule-tests/next-no-img-element-test.yml.ast-grep/rule-tests/next-page-no-client-data-fetching-test.yml.ast-grep/rule-tests/next-page-no-use-client-test.yml.ast-grep/rule-tests/next-prefer-link-over-router-push-test.yml.ast-grep/rule-tests/query-no-inline-query-key-test.yml.ast-grep/rule-tests/react-no-data-fetching-in-useeffect-test.yml.ast-grep/rule-tests/relay-no-uselazyloadquery-test.yml.ast-grep/rule-tests/relay-withrelay-requires-fallback-test.yml.ast-grep/rule-tests/state-no-redux-test.yml.ast-grep/rule-tests/state-zustand-no-global-store-test.yml.ast-grep/rule-tests/tailwind-no-raw-text-size-test.yml.ast-grep/rule-tests/ts-loose-autocomplete-test.yml.ast-grep/rule-tests/ts-no-enum-test.yml.ast-grep/rule-tests/ts-no-type-name-prefix-test.yml.ast-grep/rule-tests/ts-props-use-interface-test.yml.ast-grep/rule-tests/ts-types-not-in-index-test.yml.ast-grep/rules/ds-prefer-baseapp-wrapper.yml.ast-grep/rules/form-no-other-form-libraries.yml.ast-grep/rules/form-submit-use-loading-button.yml.ast-grep/rules/form-useform-requires-generic.yml.ast-grep/rules/mui-no-hardcoded-color.yml.ast-grep/rules/mui-styled-not-in-index.yml.ast-grep/rules/mui-sx-prop-limit.yml.ast-grep/rules/native-styles-not-in-index.yml.ast-grep/rules/native-use-design-system-theme.yml.ast-grep/rules/next-image-explicit-dimensions.yml.ast-grep/rules/next-image-no-custom-loader.yml.ast-grep/rules/next-no-img-element.yml.ast-grep/rules/next-page-no-client-data-fetching.yml.ast-grep/rules/next-page-no-use-client.yml.ast-grep/rules/next-prefer-link-over-router-push.yml.ast-grep/rules/query-no-inline-query-key.yml.ast-grep/rules/react-no-data-fetching-in-useeffect.yml.ast-grep/rules/relay-no-uselazyloadquery.yml.ast-grep/rules/relay-withrelay-requires-fallback.yml.ast-grep/rules/state-no-redux.yml.ast-grep/rules/state-zustand-no-global-store.yml.ast-grep/rules/tailwind-no-raw-text-size.yml.ast-grep/rules/ts-loose-autocomplete.yml.ast-grep/rules/ts-no-enum.yml.ast-grep/rules/ts-no-type-name-prefix.yml.ast-grep/rules/ts-props-use-interface.yml.ast-grep/rules/ts-types-not-in-index.yml.github/workflows/main.ymlREADME.mdpackage.jsonpnpm-workspace.yamlsgconfig.yml
| kind: jsx_attribute | ||
| regex: '^type=.*submit' |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== rule file =="
cat -n .ast-grep/rules/form-submit-use-loading-button.yml
echo
echo "== related fixtures/tests if present =="
git ls-files | rg 'form-submit-use-loading-button|ast-grep/rules|fixtures' || true
echo
echo "== search for dynamic submit type patterns =="
rg -n '<Button|type=\{.*Type|submitType|type=.*submit' .ast-grep/rules || trueRepository: silverlogic/baseapp-frontend
Length of output: 2937
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== test file =="
cat -n .ast-grep/rule-tests/form-submit-use-loading-button-test.yml
echo
echo "== snapshot =="
cat -n .ast-grep/rule-tests/__snapshots__/form-submit-use-loading-button-snapshot.yml
echo
echo "== inspect jsx_attribute AST shape in rule =="
python3 - <<'PY'
import yaml
from pathlib import Path
p = Path('.ast-grep/rules/form-submit-use-loading-button.yml')
data = yaml.safe_load(p.read_text())
print(data)
PYRepository: silverlogic/baseapp-frontend
Length of output: 1237
🌐 Web query:
ast-grep jsx_attribute regex matches attribute source type={submitType}
💡 Result:
To match a JSX attribute like type={submitType} in ast-grep, you should use the pattern field with a context that includes a JSX element [1][2]. Because regex rules are not AST-aware and are applied to the full node text, combining them with a pattern or kind rule is the recommended approach for performance and accuracy [3][4]. Below is an example rule configuration to match the attribute type={submitType}: id: match-jsx-attribute language: tsx rule: all: - pattern: context:
Citations:
- 1: Matching React props: strings and expressions ast-grep/ast-grep#1103
- 2: https://dev.to/herrington_darkholme/migrate-to-react-19-with-ast-grep-28op
- 3: https://ast-grep.github.io/guide/rule-config/atomic-rule.html
- 4: https://astgrep.com/reference/rule
- 5: https://ast-grep.github.io/catalog/tsx/
- 6: https://ast-grep.github.io/guide/pattern-syntax.html
- 7: https://ast-grep.github.io/guide/rule-config.html
🌐 Web query:
ast-grep jsx_attribute regex matches attribute text
💡 Result:
To match text within a JSX attribute using ast-grep, you typically combine a structural search (using pattern or kind rules) with a regex atomic rule or a constraints field to filter the node's text content [1][2][3]. Because ast-grep operates on an Abstract Syntax Tree (AST) rather than raw text, it does not apply regex directly to the entire file. Instead, you must first narrow down the search to the specific jsx_attribute node or its child nodes (such as property_identifier or string literals) before applying text-based regex matching [1][2][3]. Common Approaches: 1. Using regex inside a constraint: If you want to match a JSX attribute whose value contains specific text, you can match the attribute node and apply a constraint to one of its child nodes [1]. yaml rule: kind: string inside: kind: jsx_attribute regex: "className" # Matches the attribute name if needed stopBy: end constraints: VALUE: regex: \b(pattern_to_match)\b 2. Matching attribute names with regex: To match or transform attribute names (e.g., converting hyphenated SVG attributes to camelCase), you can target the property_identifier node inside a jsx_attribute [4][5]. yaml rule: pattern: $PROP regex: ([a-z]+)-([a-z]) # Regex for hyphenated names kind: property_identifier inside: kind: jsx_attribute 3. Using context patterns: You can use a context pattern to isolate a jsx_attribute and then apply further rules to the matched variable [6][7]. yaml rule: pattern: context: <div variant=$A /> selector: jsx_attribute Key Concepts: - Structural vs. Textual: pattern and kind are for structural matching, while regex is for textual matching of a specific node's content [2][3]. - Combining Rules: Always combine regex with other rules (like kind or inside) to ensure the regular expression is only applied to the correct part of the AST, which also improves performance [2][8]. - StopBy: When searching for text deep inside an attribute (such as inside a className string or object), use stopBy: end in your inside rule to allow the search to traverse children of the attribute node [1]. - Regex Syntax: ast-grep uses Rust's regex engine, which supports standard regex but lacks some features like lookaround and backreferences [2][8]. The regex must match the entire text of the targeted node [8].
Citations:
- 1: https://www.darricheng.com/posts/change-tailwind-classes-with-ast-grep/
- 2: https://ast-grep.github.io/guide/rule-config/atomic-rule.html
- 3: https://ast-grep.github.io/advanced/core-concepts.html
- 4: https://ast-grep.github.io/catalog/tsx/
- 5: https://astgrep.com/catalog/tsx/
- 6: Matching React props: strings and expressions ast-grep/ast-grep#1103
- 7: https://dev.to/herrington_darkholme/migrate-to-react-19-with-ast-grep-28op
- 8: https://astgrep.com/reference/rule
Match only literal submit values.
regex: '^type=.*submit' also matches dynamic attributes such as <Button type={submitType}>, where the value is not statically known to be submit. Match only type="submit", and add a valid fixture for dynamic or missing type values.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/form-submit-use-loading-button.yml around lines 14 - 15,
Update the JSX attribute matcher in the form-submit rule to match only the
literal type="submit" attribute, excluding dynamic expressions such as
type={submitType}; add a valid fixture covering dynamic or missing type values
that must not match.
| - not: | ||
| has: | ||
| field: source | ||
| regex: "providers/native['\"]$" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
tmp_dir=".ast-grep/.tmp-native-theme-$RANDOM"
trap 'rm -rf "$tmp_dir"' EXIT
mkdir -p "$tmp_dir/native"
cat > "$tmp_dir/native/case.tsx" <<'EOF'
import { useTheme } from '`@other/design-system/providers/native`'
EOF
output=$(
pnpm exec ast-grep scan \
--no-ignore hidden \
--rule .ast-grep/rules/native-use-design-system-theme.yml \
"$tmp_dir/native/case.tsx" 2>&1 || true
)
printf '%s\n' "$output"
rg -q 'native-use-design-system-theme' <<<"$output"Repository: silverlogic/baseapp-frontend
Length of output: 729
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf 'Files:\n'
git ls-files | grep -E '(^|/)(package\.json|native-use-design-system-theme\.yml)$' || true
printf '\nRule file:\n'
if [ -f .ast-grep/rules/native-use-design-system-theme.yml ]; then
cat -n .ast-grep/rules/native-use-design-system-theme.yml
fi
printf '\nRelevant package metadata:\n'
if [ -f package.json ]; then
node - <<'JS'
const fs = require('fs')
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
console.log(JSON.stringify({
name: pkg.name,
version: pkg.version,
dependencies: pkg.dependencies,
devDependencies: pkg.devDependencies,
}, null, 2))
JS
fi
printf '\nPackage search for native design-system providers:\n'
rg -n "providers/native|design-system|`@baseapp-frontend/design-system`|`@other/design-system`" package.json .ast-grep/rules || trueRepository: silverlogic/baseapp-frontend
Length of output: 3427
🌐 Web query:
ast-grep rule not pattern syntax has field regex import_statement
💡 Result:
In ast-grep, rule configurations use YAML to define matching criteria [1]. The elements you mentioned—not, has, field, regex, and import_statement—are all supported components within the rule object structure [1]. The standard syntax is as follows: 1. not: A composite rule operator that negates a sub-rule. It matches a node only if the specified sub-rule does not match [2][3]. 2. has: A relational rule that ensures a node contains a descendant or child matching the specified sub-rule [4]. 3. field: Used within a relational rule (like has) to target a specific named field of an AST node [4]. 4. regex: An atomic rule that matches the text content of a node against a Rust regular expression [1][3]. 5. import_statement: This is a node kind (the specific type of the AST node), which can be used to filter matches [1][5]. Example usage combining these features: To match an import_statement that does not have a source path matching a specific regex (e.g., matching imports that are not from a 'local' directory): rule: kind: import_statement not: has: field: source regex: '^"./' In this example: - kind: import_statement restricts the search to import statements [1][3]. - not acts as the negation wrapper [2][3]. - has targets the child node [4]. - field: source selects the specific field (common in JavaScript/TypeScript import nodes) [4][6]. - regex checks the text value of that field [1][3]. You can combine these atoms and relational rules into a single rule object; a node matches the rule if and only if it satisfies all fields defined in that object [1].
Citations:
- 1: https://ast-grep.github.io/guide/rule-config.html
- 2: https://ast-grep.github.io/guide/rule-config/composite-rule.html
- 3: https://ast-grep.github.io/cheatsheet/rule.html
- 4: https://ast-grep.github.io/guide/rule-config/relational-rule.html
- 5: https://github.com/ast-grep/ast-grep/blob/bc593e7b/crates/outline/src/default_rules/javascript.yml
- 6: https://ast-grep.github.io/catalog/typescript/find-import-identifiers.html
🌐 Web query:
ast-grep regex token text value import_string source field
💡 Result:
In ast-grep, the terms you mentioned relate to different aspects of rule configuration and the transformation process: Regex The regex rule is an atomic rule used to match the text of an AST node against a Rust-style regular expression [1][2]. It is not a "positive" rule, meaning it can theoretically match any node, so it is recommended to combine it with other rules (like kind or pattern) for better performance and precision [2][3]. The regex must match the entire text content of the target node [4][3]. Field In ast-grep, a field represents the relationship between a parent node and its child node [5]. Fields are used in relational rules like has and inside to target specific parts of a structure [5]. For example, in a key-value pair, the key and value are distinct fields relative to their parent [5]. Unnamed nodes (which do not have a kind) can still possess a field name in the context of their parent [5]. Source Field (in Transformations) The source field appears specifically within the transform configuration [6]. When you want to manipulate or transform a captured meta-variable, you define a transform object where the source property specifies the meta-variable you are acting upon [6]. For example, in a replacement operation, source identifies which captured variable to process [6]. Import String While there is no specific keyword called "import_string" in ast-grep, this phrase typically refers to the textual content of an import statement in a programming language [7]. When matching imports, ast-grep can be configured with different strictness levels (e.g., ast, smart) to determine whether to include or ignore trivial, unnamed nodes like quotes, allowing you to match import strings regardless of whether single or double quotes are used [7].
Citations:
- 1: https://astgrep.com/guide/rule-config/atomic-rule
- 2: https://ast-grep.github.io/guide/rule-config/atomic-rule.html
- 3: https://ast-grep.github.io/reference/rule.html
- 4: https://astgrep.com/reference/rule
- 5: https://ast-grep.github.io/advanced/core-concepts.html
- 6: https://astgrep.com/reference/yaml
- 7: https://astgrep.com/advanced/match-algorithm
🌐 Web query:
ast-grep regex anchor implicit full match
💡 Result:
In ast-grep, the regex atomic rule operates with an implicit full match on the text of the targeted AST node [1]. When you use the regex atomic rule to match a node, the provided regular expression must match the entire text content of that specific AST node [1][2]. This behavior is enforced by default; if the regex only matches a portion of the node's text, the match will fail. Key details regarding this behavior include: 1. Requirement for full text match: Unlike some regex engines that search for a substring within a larger text, ast-grep's regex rule validates the entire string content of the selected node against your pattern [1]. For example, a regex of ^[a-z]+$ will match a node containing "apple", but it will not match a node containing "apple pie" because the regex does not account for the space and the remaining characters [1]. 2. Anchor implications: Because the regex is applied to the full text of the node, you do not need to manually add start (^) or end ($) anchors to achieve a full match, though using them can be helpful for clarity or to ensure the regex remains valid if requirements change [1][2]. 3. Rust Regex Syntax: The regex engine used is based on Rust's regex crate [1][3]. It does not support certain features like look-around or backreferences, which is important to keep in mind when constructing your patterns [1][4]. 4. Contextual Usage: Because regex matching is applied to the text of a node regardless of its structure, it is highly recommended to combine the regex rule with other atomic rules (such as kind or pattern) to ensure the regex is only evaluated against relevant parts of the syntax tree, which also improves performance [4][3].
Citations:
- 1: https://astgrep.com/reference/rule
- 2: https://ast-grep.github.io/cheatsheet/rule.html
- 3: https://astgrep.com/guide/rule-config/atomic-rule
- 4: https://ast-grep.github.io/guide/rule-config/atomic-rule.html
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf 'package name:\n'
node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).name)"
printf '\nimports from providers/native in tracked files:\n'
python3 - <<'PY'
from pathlib import Path
import re
for p in Path('.').rglob('*'):
if p.parts and p.parts[0] in {'.git','node_modules','dist','build','.astro','.next','coverage'}:
continue
if p.is_file() and p.suffix in {'.ts','.tsx','.js','.jsx'}:
try:
text=p.read_text(encoding='utf-8',errors='ignore')
except Exception:
continue
for i,line in enumerate(text.splitlines(),1):
m=re.search(r"import\s+\{[^}]*useTheme[^}]*\}\s+from\s+(['\"])([^'\"]*providers/native.*?)\1", line)
if m:
print(f"{p}:{i}: {m.group(0).strip()}")
PY
printf '\nall imports containing providers/native:\n'
python3 - <<'PY'
from pathlib import Path
import re
for p in Path('.').rglob('*'):
if p.parts and p.parts[0] in {'.git','node_modules','dist','build','.astro','.next','coverage'}:
continue
if p.is_file() and p.suffix in {'.ts','.tsx','.js','.jsx'}:
try:
text=p.read_text(encoding='utf-8',errors='ignore')
except Exception:
continue
for i,line in enumerate(text.splitlines(),1):
if "providers/native" in line:
print(f"{p}:{i}: {line.strip()}")
PYRepository: silverlogic/baseapp-frontend
Length of output: 50384
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf 'ast-grep availability:\n'
command -v ast-grep || true
node -e "let ok=false; try { require.resolve('`@ast-grep/node`'); ok=true } catch {} console.log(ok)" || true
printf '\nNode AST import_source text probe:\n'
node - <<'JS'
try {
const parser = require('node-tree-sitter')
const ts = require.resolve('tree-sitter-typescript')
// Skip; don't try installing.
console.log('node-tree-sitter unavailable')
} catch (e) {
console.log('node-tree-sitter unavailable')
}
const cases = [
'import { useTheme } from \'providers/native\'\n',
'import { useTheme } from \'`@other/design-system/providers/native`\'\n',
]
// Use a deterministic regex model aligned with the stated full-string behavior,
// including quotes if the rule field matches the quoted source string.
for (const src of cases) {
const node = src.match(/import\s*\{[^}]*\}\s*from\s+(\S+)\n/)
const source = node ? node[1] : null
console.log(JSON.stringify({ source, matches: source === 'providers/native' }))
}
JS
printf '\nPackage exports/import aliases near design-system:\n'
for f in package.json packages/design-system/package.json packages/components/package.json; do
if [ -f "$f" ]; then
echo "--- $f ---"
node -e "const p=require('./$f'); console.log(JSON.stringify({name:p.name, exports:p.exports, module:p.module, main:p.main}, null, 2))"
fi
doneRepository: silverlogic/baseapp-frontend
Length of output: 6435
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/native"
cat > "$tmp/native/case.tsx" <<'EOF'
import { useTheme } from '`@other/design-system/providers/native`'
EOF
rm -rf ~/.config/ast-grep 2>/dev/null || true
output="$(ast-grep scan \
--no-ignore hidden \
--rule .ast-grep/rules/native-use-design-system-theme.yml \
"$tmp/native/case.tsx" 2>&1) 2>&1 || true"
printf '%s\n' "$output"
printf '\nFlagged?: '
if rg -q 'native-use-design-system-theme' <<<"$output"; then
printf 'yes\n'
else
printf 'no\n'
fi
printf '\nRule with exact package/source pattern:\n'
sed -n '1,40p' .ast-grep/rules/native-use-design-system-theme.ymlRepository: silverlogic/baseapp-frontend
Length of output: 925
Restrict providers/native exceptions to the BaseApp design-system source.
The current suffix regex allows any import ending with providers/native, including unrelated packages such as @other/design-system/providers/native. Match the exact @baseapp-frontend/design-system/providers/native source and only explicit relative import paths if those are still needed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/native-use-design-system-theme.yml around lines 18 - 21,
Update the source exception in the ast-grep rule to match only the exact
`@baseapp-frontend/design-system/providers/native` import and explicitly supported
relative paths, rather than any import ending in providers/native. Modify the
regex under the not/has source condition while preserving the rule’s other
matching behavior.
| - not: | ||
| has: | ||
| kind: jsx_attribute | ||
| regex: ^fill($|=) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)(next-image-explicit-dimensions-(rules|test))\.yml|next-image-explicit-dimensions' || true
echo "== rule content =="
if [ -f .ast-grep/rules/next-image-explicit-dimensions.yml ]; then
cat -n .ast-grep/rules/next-image-explicit-dimensions.yml
fi
echo "== test content =="
if [ -f .ast-grep/rule-tests/next-image-explicit-dimensions-test.yml ]; then
cat -n .ast-grep/rule-tests/next-image-explicit-dimensions-test.yml
fi
echo "== all image rule files (concise) =="
rg -n "next-image-explicit-dimensions|fill|height|width|explicit|widths" .ast-grep/rules .ast-grep/rule-tests || true
echo "== parse rule fixture-ish YAML with Python =="
python3 - <<'PY'
from pathlib import Path
import yaml
for p in [Path('.ast-grep/rules/next-image-explicit-dimensions.yml'), Path('.ast-grep/rule-tests/next-image-explicit-dimensions-test.yml')]:
print(f"\n{p}")
if p.exists():
data=yaml.safe_load(p.read_text())
print(type(data), data.get('type'))
if 'valid' in data:
for i,f in enumerate(data['valid'][:10]):
print("valid", i, f.get('language'), f.get('filename'))
if 'invalid' in data:
for i,f in enumerate(data['invalid'][:10]):
print("invalid", i, f.get('language'), f.get('filename'), f.get('message') or f.get('fix') or '')
PYRepository: silverlogic/baseapp-frontend
Length of output: 5659
🌐 Web query:
Next.js Image fill prop requires parent has position CSS documentation
💡 Result:
Yes, when using the fill prop in the Next.js Image component, the parent element must be positioned [1][2][3][4]. Specifically, the parent element must assign one of the following CSS position values: - position: relative [1][2][3][4] - position: absolute [1][2][3][4] - position: fixed [1][2][3][4] This requirement exists because the Image component, when using the fill prop, is automatically assigned position: absolute [1][2][3][4]. By positioning the parent element, you establish a containing block that allows the image to fill the container correctly instead of defaulting to the nearest positioned ancestor (often the document body) [1][2][3]. If these styles are not applied, the image may not render as expected, and in development, Next.js will issue a warning indicating that the parent element has an invalid position [5].
Citations:
- 1: https://nextjs.org/docs/14/app/api-reference/components/image
- 2: https://nextjs.org/docs/pages/api-reference/components/image
- 3: https://nextjs.org/docs/14/pages/api-reference/components/image
- 4: https://vercel-next-js.mintlify.app/api-reference/components/image
- 5: https://github.com/vercel/next.js/blob/fef6f82abafdad6c32a61ebbf4bff77f634bfc2d/packages/next/src/client/image-component.tsx
Align the fill exemption with Next.js positioning requirements.
.ast-grep/rules/next-image-explicit-dimensions.yml#L17-L20 accepts any fill attribute, but fill only replaces explicit dimensions when the Image has a positioned parent. This makes the valid test fixture and rule contract inconsistent. Change one of these:
- Restrict the exemption to statically true
filland add parent-position validation. - Narrow the message to drop the
fillpromise. - Update
.ast-grep/rule-tests/next-image-explicit-dimensions-test.yml#L5-L6to use a positioned parent.
📍 Affects 2 files
.ast-grep/rules/next-image-explicit-dimensions.yml#L17-L20(this comment).ast-grep/rule-tests/next-image-explicit-dimensions-test.yml#L5-L6
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/next-image-explicit-dimensions.yml around lines 17 - 20,
Update .ast-grep/rule-tests/next-image-explicit-dimensions-test.yml lines 5-6 so
the Image using fill is wrapped in a positioned parent, making the fixture
satisfy Next.js positioning requirements;
.ast-grep/rules/next-image-explicit-dimensions.yml lines 17-20 requires no
direct change.
Source: MCP tools
| severity: warning | ||
| message: "A custom `loader` disables ALL Next.js image optimization (format conversion, quality, resizing). Drop it and configure `images.remotePatterns` in `next.config.js` instead. See .claude/skills/frontend-conventions/references/image-optimization.md" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repo files =="
git ls-files | sed -n '1,200p'
echo "== rule file =="
cat -n .ast-grep/rules/next-image-no-custom-loader.yml 2>/dev/null || true
echo "== reference file =="
cat -n .claude/skills/frontend-conventions/references/image-optimization.md 2>/dev/null || true
echo "== search next image conventions/rules =="
rg -n "next-image-no-custom-loader|custom loader|remotePatterns|loaderFile|loader=" . -S || trueRepository: silverlogic/baseapp-frontend
Length of output: 12630
🌐 Web query:
Next.js Image component custom loader support remotePatterns loaderFile documentation
💡 Result:
The Next.js Image component supports custom loaders to manage image URLs, including those from remote domains, through configuration in next.config.js [1][2]. Remote Patterns To safely allow images from external domains, you must configure remotePatterns in your next.config.js [3][4]. This security feature restricts image loading to authorized hosts, protocols, and pathnames [1][5]. Example configuration: module.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'example.com', port: '', pathname: '/account123/', search: '', }, ], }, } [1][5] Custom Loader and loaderFile If you prefer to use an external image optimization service instead of the built-in Next.js Image Optimization API, you can set a custom loader [2][6]. 1. Configure loaderFile: In next.config.js, set loader to 'custom' and specify the path to your loader file [1][7]. module.exports = { images: { loader: 'custom', loaderFile: './my/image/loader.js', }, } [1][8] 2. Define the loader: The loader file must export a default function that receives src, width, and quality, and returns a URL string [1][2]. Note that if you use a function in your loader file, it must be treated as a Client Component [2][9]. 'use client' export default function myImageLoader({ src, width, quality }) { return https://example.com/${src}?w=${width}&q=${quality || 75} } [1][2] Alternatively, you can provide a loader function directly to individual next/image instances using the loader prop instead of a global loaderFile [1][7][8].
Citations:
- 1: https://nextjs.org/docs/api-reference/next/image
- 2: https://nextjs.org/docs/app/api-reference/config/next-config-js/images
- 3: https://nextjs.org/docs/14/app/api-reference/components/image
- 4: https://nextjs.org/docs/app/getting-started/images
- 5: https://vercel.com/docs/image-optimization
- 6: https://nextjs.org/docs/pages/api-reference/config/next-config-js/images
- 7: https://nextjs.org/docs/pages/api-reference/components/image
- 8: https://nextjs.org/docs/15/pages/api-reference/config/next-config-js/images
- 9: https://en.nextjs.im/docs/14/app/api-reference/next-config-js/images/
Correct the replacement guidance for custom loaders.
Custom loader controls per-image URL generation, and loaderFile can use a custom image optimization service. images.remotePatterns only allows remote image sources; it does not replace a custom loader. If this project disallows custom loaders, state that policy and reference the approved replacement instead of saying remotePatterns covers it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/next-image-no-custom-loader.yml around lines 3 - 4, Update
the warning message in the next-image-no-custom-loader rule to remove the claim
that images.remotePatterns replaces a custom loader. State the project’s policy
on disallowing custom loaders and direct users to the approved replacement or
configuration documented in the referenced image-optimization guidance, while
preserving the warning’s explanation of the loader impact.
Source: MCP tools
| rule: | ||
| kind: expression_statement | ||
| regex: "^['\"]use client['\"]" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg '(^|/)(next-page-no-use-client(-test)?\.yml|.*\.ya?ml$)' | sed -n '1,120p'
echo
echo "Rule:"
cat -n .ast-grep/rules/next-page-no-use-client.yml
echo
echo "Rule tests:"
cat -n .ast-grep/rule-tests/next-page-no-use-client-test.yml
echo
echo "Search related rules/tests:"
rg -n "directive prologue|use client|ast-grep.*next-page-no-use-client|next-page-no-use-client" .ast-grep . 2>/dev/null | sed -n '1,160p'
echo
echo "Check for nested-string behavior via standalone ast-grep source files if installed and rule is runnable:"
if command -v ast-grep >/dev/null 2>&1; then
echo "ast-grep version:"
ast-grep --version
tmp="$(mktemp -d)"
cat > "$tmp/input.js" <<'JS'
function fn() {
'use client'
}
const x = () => { 'use client'; return null; }
' at module top
JS
ast-grep --project-root "$tmp" -r "const r = $tmp/input.js" rule --no-navigator --stdin 2>&1 || true
else
echo "ast-grep not installed"
fiRepository: silverlogic/baseapp-frontend
Length of output: 20642
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
target="$tmp/page.tsx"
tmp-rule="$tmp/rule.yml"
tmp-test="$tmp/test.yml"
cat > "$target" <<'JS'
export const metadata = { title: 'Checkout' }
function nestedExpression() {
'use client'
return null
}
export default CheckoutPage
JS
cat > "$tmp-rule" <<'YAML'
id: next-page-no-use-client
language: Tsx
severity: warning
rule:
kind: expression_statement
regex: "^['\"]use client['\"]"
YAML
cat > "$tmp-test" <<'YAML'
id: next-page-no-use-client
valid:
- |
export const metadata = { title: 'Checkout' }
const CheckoutPage = async () => <OrderDetails />
export default CheckoutPage
export default function nestedExpression() {
'use client'
return null
}
invalid:
- |
'use client'
const CheckoutPage = () => <OrderDetails />
export default CheckoutPage
YAML
echo "ast-grep version:"
ast-grep --version
echo
echo "Running rule directly on file:"
ast-grep --lang Tsx -r "$tmp-rule" "$target" || true
echo
echo "Running ast-grep unit test:"
ast-grep u --dir "$tmp" --rule "$tmp-rule" --test "$tmp-test" || trueRepository: silverlogic/baseapp-frontend
Length of output: 249
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
target="$tmp/page.tsx"
tmp-rule="$tmp/rule.yml"
tmp-test="$tmp/test.yml"
cat > "$target" <<'JS'
export const metadata = { title: 'Checkout' }
function nestedExpression() {
'use client'
return null
}
export default CheckoutPage
JS
cat > "$tmp-rule" <<'EOF'
id: next-page-no-use-client
language: Tsx
severity: warning
rule:
kind: expression_statement
regex: "^['\"]use client['\"]"
EOF
cat > "$tmp-test" <<'EOF'
id: next-page-no-use-client
valid:
- |
export const metadata = { title: 'Checkout' }
const CheckoutPage = async () => <OrderDetails />
export default CheckoutPage
export default function nestedExpression() {
'use client'
return null
}
invalid:
- |
'use client'
const CheckoutPage = () => <OrderDetails />
export default CheckoutPage
EOF
echo "ast-grep version:"
ast-grep --version
echo
echo "Running rule directly on file:"
ast-grep --lang Tsx -r "$tmp-rule" "$target" || true
echo
echo "Running ast-grep unit test:"
ast-grep u --dir "$tmp" --rule "$tmp-rule" --test "$tmp-test" || trueRepository: silverlogic/baseapp-frontend
Length of output: 249
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "TEMP env:"
printf 'TEMP=%s\n' "$TEMP"
tmp="$(mktemp -d)"
printf 'tmp=%s\n' "$tmp"
ls -la "$tmp"
target="$tmp/page.tsx"
tmp-rule="$tmp/rule.yml"
tmp-test="$tmp/test.yml"
printf '%s\n' \
"export const metadata = { title: 'Checkout' }" \
"" \
"function nestedExpression() {" \
" 'use client'" \
" return null" \
"}" \
"" \
"export default CheckoutPage" > "$target"
printf '%s\n' \
"id: next-page-no-use-client" \
"language: Tsx" \
"severity: warning" \
"rule:" \
" kind: expression_statement" \
" regex: \"^['\\\"]use client['\\\"]\"" > "$tmp-rule"
printf '%s\n' \
"id: next-page-no-use-client" \
"valid:" \
" - |" \
" export const metadata = { title: 'Checkout' }" \
" const CheckoutPage = async () => <OrderDetails />" \
" export default CheckoutPage" \
" export default function nestedExpression() {" \
" 'use client'" \
" return null" \
" }" \
"invalid:" \
" - |" \
" 'use client'" \
"" \
" const CheckoutPage = () => <OrderDetails />" \
" export default CheckoutPage" > "$tmp-test"
echo "Run rule directly:"
ast-grep --lang Tsx -r "$tmp-rule" "$target" || true
echo
echo "Run unit test:"
ast-grep u --dir "$tmp" --rule "$tmp-rule" --test "$tmp-test" || trueRepository: silverlogic/baseapp-frontend
Length of output: 213
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="/tmp/ast-grep-rule-probe-$$"
mkdir -p "$tmp"
target="$tmp/page.tsx"
tmp-rule="$tmp/rule.yml"
tmp-test="$tmp/test.yml"
cat > "$target" <<'JS'
export const metadata = { title: 'Checkout' }
function nestedExpression() {
'use client'
return null
}
export default CheckoutPage
JS
cat > "$tmp-rule" <<'EOF'
id: next-page-no-use-client
language: Tsx
severity: warning
rule:
kind: expression_statement
regex: "^['\"]use client['\"]"
EOF
cat > "$tmp-test" <<'EOF'
id: next-page-no-use-client
valid:
- |
export const metadata = { title: 'Checkout' }
const CheckoutPage = async () => <OrderDetails />
export default CheckoutPage
export default function nestedExpression() {
'use client'
return null
}
invalid:
- |
'use client'
const CheckoutPage = () => <OrderDetails />
export default CheckoutPage
EOF
echo "ast-grep version:"
ast-grep --version
echo
echo "Running rule directly on file:"
ast-grep --lang Tsx -r "$tmp-rule" "$target" || true
echo
echo "Running ast-grep unit test:"
ast-grep u --dir "$tmp" --rule "$tmp-rule" --test "$tmp-test" || trueRepository: silverlogic/baseapp-frontend
Length of output: 256
🏁 Script executed:
#!/bin/bash
set -u
tmp="/tmp/ast-grep-rule-probe-$$"
mkdir -p "$tmp"
target="$tmp/page.tsx"
tmp-rule="$tmp/rule.yml"
tmp-test="$tmp/test.yml"
cat > "$target" <<'JS'
export const metadata = { title: 'Checkout' }
function nestedExpression() {
'use client'
return null
}
export default CheckoutPage
JS
cat > "$tmp-rule" <<'EOF'
id: next-page-no-use-client
language: Tsx
severity: warning
rule:
kind: expression_statement
regex: "^['\"]use client['\"]"
EOF
cat > "$tmp-test" <<'EOF'
id: next-page-no-use-client
valid:
- |
export const metadata = { title: 'Checkout' }
const CheckoutPage = async () => <OrderDetails />
export default CheckoutPage
export default function nestedExpression() {
'use client'
return null
}
invalid:
- |
'use client'
const CheckoutPage = () => <OrderDetails />
export default CheckoutPage
EOF
echo "ast-grep version:"
ast-grep --version
echo
echo "Running rule directly on file:"
ast-grep --lang Tsx -r "$tmp-rule" "$target" || true
echo
echo "Running ast-grep unit test:"
ast-grep u --dir "$tmp" --rule "$tmp-rule" --test "$tmp-test" || trueRepository: silverlogic/baseapp-frontend
Length of output: 705
🌐 Web query:
ast-grep directive prologue rule kind expression_statement regex regex directive
💡 Result:
In ast-grep, rule fields are typically defined within the rule object of a configuration file [1][2]. There is no specific directive called "prologue," but you can use rule to define the matching logic [1][2]. To target an expression_statement and apply a regex constraint, you would combine these fields within a rule object. A node matches the rule object if and only if it satisfies all fields defined within it [1][3]. Example usage: rule: kind: expression_statement regex: '^your_regex_pattern_here$' In this example: - kind: Specifies the AST node type (e.g., expression_statement) [4]. - regex: Matches the text content of the node against a Rust regular expression [4][5]. It is highly recommended to combine regex with other atomic rules like kind or pattern. Because regex matching applies to the entire text of the node and can be computationally expensive, using it alongside kind ensures that the regular expression is only applied to relevant AST nodes, significantly improving performance [4]. For more complex requirements, you can also use ESQuery-style selectors within the kind field to target specific descendant structures [4][6]. For instance, kind: expression_statement:has(> call_expression) would match an expression statement that contains a direct child call expression [6].
Citations:
- 1: https://ast-grep.github.io/guide/rule-config.html
- 2: https://astgrep.com/reference/yaml
- 3: https://ast-grep.github.io/guide/rule-config/composite-rule.html
- 4: https://ast-grep.github.io/guide/rule-config/atomic-rule.html
- 5: https://ast-grep.github.io/cheatsheet/rule.html
- 6: https://ast-grep.github.io/reference/rule/esquery.html
🌐 Web query:
Next.js use client directive client component boundary module boundary documentation
💡 Result:
The 'use client' directive is a React feature used in Next.js to define a boundary between server and client module graphs, marking the entry point for code that should be bundled for the client [1][2][3]. Key Concepts: - Purpose: It declares that a module and its transitive dependencies are part of the client-side bundle [2][3][4]. This is necessary for components that require browser-specific capabilities such as React hooks (e.g., useState, useEffect), event handlers (e.g., onClick), or direct access to browser APIs [1][5][6]. - Module Boundary: When placed at the top of a file, 'use client' creates a conceptual boundary in the module dependency tree [2][3][4]. Everything imported by that module is automatically included in the client bundle [3][7][8]. It does not need to be added to every client-side component; only to the entry points where you transition from a Server Component to a Client Component [1][3][6]. - Server-Client Relationship: Even though these components are part of the "client" graph, they are still pre-rendered on the server to HTML for initial page loads [4][8][9]. The 'use client' directive simply ensures the necessary JavaScript is sent to the browser to enable interactivity through hydration [5][4][9]. - Best Practices: To keep JavaScript bundles small and maintain performance, the client boundary should be kept as "low" in the component tree as possible [5][4]. A common pattern is to extract interactive elements (like buttons or toggles) into their own components, mark only those as 'use client', and pass server-rendered components as children (via props) to avoid pulling them into the client bundle [5][7][10]. The 'use client' directive does not mean the code cannot run on the server; rather, it signifies that the module belongs to the client-side module graph, necessitating client-side hydration for interactivity [4][8][9].
Citations:
- 1: https://nextjs.org/docs/app/api-reference/directives/use-client
- 2: https://react.dev/reference/rsc/use-client
- 3: https://nextjs.org/docs/app/getting-started/server-and-client-components
- 4: https://www.nazarboyko.com/articles/the-use-client-boundary-explained-clearly
- 5: https://vercel.com/academy/nextjs-foundations/client-server-boundaries
- 6: https://nextjs.org/docs/15/app/api-reference/directives/use-client
- 7: https://vercel.com/academy/nextjs-foundations/server-and-client-components
- 8: https://yceffort.kr/en/2026/05/use-client-deep-dive
- 9: Harding time grasping the Next.js 13 App Router 'use client' directive vercel/next.js#50864
- 10: https://vercel.com/academy/client-server-boundaries
🌐 Web query:
React use client directive at top of file or import paths not string literals documentation
💡 Result:
The 'use client' directive must be placed at the very beginning of a file, preceding any import statements or other executable code [1][2]. While comments are permitted before the directive, it must be the first non-comment line in the file [1][2]. It must be written as a plain string literal (using single or double quotes, not backticks) [1][3]. This placement is required because bundlers interpret 'use client' as a directive to define a boundary in the module dependency tree [1][4][3]. Because the bundler needs to determine which module graph a file belongs to before parsing its imports, the directive must be reachable at the top of the file [2]. If the directive is placed after imports or wrapped in conditional logic, it will not be detected by the bundler, and the file will default to a Server Component, likely resulting in runtime errors if the code relies on client-only features like React state or browser APIs [2].
Citations:
- 1: https://react.dev/reference/rsc/use-client
- 2: https://www.nazarboyko.com/articles/the-use-client-boundary-explained-clearly
- 3: https://uk.react.dev/reference/rsc/use-client
- 4: https://nextjs.org/docs/app/getting-started/server-and-client-components
Keep the use client rule scoped to a top-level directive.
The matcher reports expression_statement nodes whose source text starts with 'use client', including nested literals. Only module-level 'use client' creates the Next.js client boundary, so this rule can signal false negatives in page.tsx files that import child client components.
.ast-grep/rules/next-page-no-use-client.yml#L7-L9: constrain the matcher to a top-level directive/prologue..ast-grep/rule-tests/next-page-no-use-client-test.yml#L2-L15and.ast-grep/rule-tests/__snapshots__/next-page-no-use-client-snapshot.yml: add a valid nested-string fixture and update the snapshot.
📍 Affects 2 files
.ast-grep/rules/next-page-no-use-client.yml#L7-L9(this comment).ast-grep/rule-tests/next-page-no-use-client-test.yml#L2-L15
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/next-page-no-use-client.yml around lines 7 - 9, Scope the
matcher in next-page-no-use-client to module-level directive/prologue expression
statements so nested literals are ignored. In
.ast-grep/rule-tests/next-page-no-use-client-test.yml lines 2-15, add a valid
nested-string fixture, and update
.ast-grep/rule-tests/__snapshots__/next-page-no-use-client-snapshot.yml to
reflect that it produces no report.
| - has: | ||
| field: arguments | ||
| has: | ||
| kind: string |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file =="
cat -n .ast-grep/rules/next-prefer-link-over-router-push.yml
echo "== usages/tests =="
git ls-files | rg '(\.ast-grep|ast-grep|rule|test)' | rg -i 'next-prefer-link-over-router-push|template|router\.push|router-push' || true
echo "== other rule examples using template_string =="
rg -n "template_string|string_literal|kind: string" .ast-grep -S || trueRepository: silverlogic/baseapp-frontend
Length of output: 1898
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== rule tests =="
cat -n .ast-grep/rule-tests/next-prefer-link-over-router-push-test.yml
echo "== snapshot =="
cat -n .ast-grep/rule-tests/__snapshots__/next-prefer-link-over-router-push-snapshot.yml
echo "== dependency versions =="
if [ -f package.json ]; then node -e "const p=require('./package.json'); console.log(JSON.stringify({dependencies:p.dependencies,devDependencies:p.devDependencies?Object.fromEntries(Object.entries(p.devDependencies).filter(([k,v])=>k.includes('ast-grep')||k.includes('astgrep')||k.includes('typescript')||k.includes('next'))):{}}, null, 2))"; fi
if [ -f pnpm-lock.yaml ]; then rg -n "ast-grep|astgrep|`@ts-morph`|typescript|next@" pnpm-lock.yaml | head -80 || true; fi
if [ -f yarn.lock ]; then rg -n "ast-grep|astgrep|`@ts-morph`|typescript|next@" yarn.lock | head -80 || true; fi
if [ -f package-lock.json ]; then rg -n "ast-grep|astgrep|`@ts-morph`|typescript|next@" package-lock.json | head -80 || true; fiRepository: silverlogic/baseapp-frontend
Length of output: 9896
🌐 Web query:
@ast-grep/cli 0.45 TypeScript string literal template_string node kind
💡 Result:
In ast-grep (version 0.45 and earlier), node kinds are determined by the underlying tree-sitter parser [1]. For TypeScript and JavaScript, template literals are represented by the node kind template_string [2][3]. To match or select these nodes, you can use the kind atomic rule in your YAML configuration [4][5][6]: rule: kind: template_string Key distinctions: - Node Kind (template_string): This represents the inherent type of the node itself (e.g., the template literal block) [1][2]. - Node Field (e.g., arguments): This is an optional attribute describing a node's relationship to its parent (e.g., a template literal used as an argument in a tagged template expression) [1][2]. If you are searching for standard string literals (those wrapped in single or double quotes), the node kind is typically string_literal. You can verify the exact kind for any specific construct by using the ast-grep playground, which visually identifies these kinds [1][4][6]. Additionally, ast-grep supports ESQuery-style selectors within the kind field, allowing for more complex matching (e.g., using:has to filter by descendants) [6][7].
Citations:
- 1: https://ast-grep.github.io/advanced/core-concepts.html
- 2: [Question] How to build a rule pattern to select tagged template literal ast-grep/ast-grep#376
- 3: [ Question ] Search JS template literal with variable interpolation ast-grep/ast-grep#161
- 4: https://astgrep.com/guide/rule-config/atomic-rule
- 5: https://ast-grep.github.io/guide/rule-config/atomic-rule.html
- 6: https://astgrep.com/reference/rule
- 7: https://ast-grep.github.io/reference/rule/esquery.html
Cover static template destinations.
kind: string only matches quoted string literals. A call like router.push(/settings) has kind: template_string and skips this rule while still using a static destination. Match static templates without substitutions, or narrow the rule message and tests to quoted literals only.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/next-prefer-link-over-router-push.yml around lines 15 - 18,
Update the router.push matching rule to recognize static template_string
destinations with no substitutions in addition to quoted string literals, so
calls such as router.push(`/settings`) are covered. Anchor the change to the
arguments matcher in next-prefer-link-over-router-push and preserve exclusion of
interpolated templates.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | grep -E '^\.ast-grep/rules/(next-prefer-link-over-router-push|query-no-inline-query-key)\.yml$'
echo "== rule files =="
for f in .ast-grep/rules/next-prefer-link-over-router-push.yml .ast-grep/rules/query-no-inline-query-key.yml; do
echo "--- $f ---"
cat -n "$f"
done
echo "== related tests/fixtures =="
git ls-files | grep -E '(\.ast-grep/.*|ast-grep/.*)|test|fixture|snapshot' | grep -iE '(next-prefer-link-over-router-push|query-no-inline-query-key|link-over-router|inline-query-key|ast-grep)' | head -200Repository: silverlogic/baseapp-frontend
Length of output: 7006
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== rule test files =="
for f in .ast-grep/rule-tests/next-prefer-link-over-router-push-test.yml .ast-grep/rule-tests/query-no-inline-query-key-test.yml; do
echo "--- $f ---"
cat -n "$f"
done
echo "== snapshots relevant sections =="
for f in .ast-grep/rule-tests/__snapshots__/next-prefer-link-over-router-push-snapshot.yml .ast-grep/rule-tests/__snapshots__/query-no-inline-query-key-snapshot.yml; do
echo "--- $f ---"
cat -n "$f" | sed -n '1,220p'
done
echo "== search template-string rule references =="
rg -n "template|STRING|PATTERN|template_string|type_arguments|argument" .ast-grep/rules .ast-grep/rule-tests || trueRepository: silverlogic/baseapp-frontend
Length of output: 3421
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== ast-grep parser/config references =="
cat -n .ast-grep/README.md | sed -n '1,220p'
echo "== find ast-grep configs =="
git ls-files .ast-grep | sed -n '1,200p'
echo "== yaml kind values in rule tests =="
rg -n "kind: (string|pattern|PATTERN|STRING|template|template_string|TS_(Template|String))" .ast-grep/rules .ast-grep/rule-tests || trueRepository: silverlogic/baseapp-frontend
Length of output: 15754
🌐 Web query:
ast-grep rule kind Tsx string node types template literals
💡 Result:
In ast-grep, the kind of a syntax node is determined by the underlying Tree-sitter parser for the language being used [1][2]. For Tsx (and TypeScript), template literals are represented by specific node kinds [3][2]. To identify template literals in ast-grep, you typically use the following node kinds: template_string: This is the primary node kind for template literals (e.g., Hello ${name}) [3]. template_substitution: This node kind represents the interpolated expression inside the template literal (the ${...} part). How to Use Node Kinds in Rules: You can target these nodes using the kind atomic rule [2][4]. Since ast-grep allows you to inspect the tree structure, the best way to confirm the exact node kinds for your specific code is to use the ast-grep playground [1][5]. Example Rule for Template Literals: To match all template literals, you can use a simple rule: rule: kind: template_string If you are looking for tagged template literals (e.g., html...), you can combine the kind rule with a relational rule to check the parent structure [3]: rule: kind: template_string has: field: arguments # If checking inside a call expression Important Concepts: Kind vs. Field: A node's kind is its inherent type (e.g., template_string), while a field describes the relationship a node has with its parent (e.g., arguments or tag) [1][3]. Both are distinct attributes that can be used to refine your rules [1][3]. ESQuery Selectors: From ast-grep v0.39.1+, you can use limited ESQuery syntax within the kind field for more complex selection, such as matching a template_string that is a direct child of another node [5][6]: rule: kind: call_expression > template_string For the most accurate and up-to-date node types for any language, including Tsx, refer to the ast-grep playground, where you can paste your code and view the CST (Concrete Syntax Tree) structure, with node kinds highlighted [1][5].
Citations:
- 1: https://ast-grep.github.io/advanced/core-concepts.html
- 2: https://astgrep.com/guide/rule-config/atomic-rule
- 3: [Question] How to build a rule pattern to select tagged template literal ast-grep/ast-grep#376
- 4: https://ast-grep.github.io/guide/rule-config.html
- 5: https://astgrep.com/reference/rule
- 6: https://astgrep.com/reference/rule/esquery
🌐 Web query:
ast-grep match template literal kind pattern node kind
💡 Result:
In ast-grep, the node kind for a template literal is typically template_string [1]. When working with template literals in ast-grep, you can match them using the pattern rule [2][3]. Because ast-grep uses tree-sitter as its underlying parser, the exact kind can vary slightly depending on the language (e.g., JavaScript or TypeScript) [4]. It is recommended to verify the specific kind name by checking your code in the ast-grep playground [4][3]. If you need to match a template literal but require more control over the selection or context, you can use the pattern object format, which allows you to specify a context and a selector [5][3]. This is particularly useful when a simple pattern string is ambiguous [3]. For example, if you want to match a tagged template literal, you might look for a call_expression that contains a template_string [1]: rule: kind: call_expression has: kind: template_string field: arguments When using the command line, you can also use the --kind flag to filter results by node kind, which supports limited ESQuery-style selectors [6][7][8]. Note that the kind and pattern rules are independent; if you need to enforce that a pattern is parsed as a specific kind, using a pattern object with context and selector is the preferred approach over combining kind and pattern in a single rule [5][3].
Citations:
- 1: [Question] How to build a rule pattern to select tagged template literal ast-grep/ast-grep#376
- 2: https://ast-grep.github.io/guide/pattern-syntax.html
- 3: https://astgrep.com/guide/rule-config/atomic-rule
- 4: https://ast-grep.github.io/advanced/core-concepts.html
- 5: https://astgrep.com/advanced/faq
- 6: https://astgrep.com/reference/cli
- 7: https://astgrep.com/reference/rule
- 8: https://astgrep.com/reference/rule/esquery
🌐 Web query:
tree-sitter TypeScript template literal node type string node
💡 Result:
In the tree-sitter TypeScript grammar, there are two distinct node types relevant to template literals, depending on whether they are used as expressions or as types [1][2][3]. 1. template_string: This is the node type for standard JavaScript/TypeScript template literals used in expressions (e.g., Hello ${name}!) [4][5]. It typically contains string fragments (string_fragment) and interpolation expressions [1]. 2. template_literal_type: This is the node type for TypeScript template literal types (e.g., type T = hello ${string}) [2][3][6]. This node type was explicitly added to the grammar to support template literal types as a first-class citizen in the type system [1][2]. When working with these nodes, note that the internal structure often involves string fragments represented as string_fragment nodes [1], and for template literal types, the interpolation parts are parsed as template_type nodes (which may contain primary_type or infer_type) [6]. If you are experiencing issues with parsing, it is often due to the distinction between these two contexts [2][7].
Citations:
- 1: "Template literal types" and "Mapped type 'as' clauses" tree-sitter/tree-sitter-typescript#138
- 2: Support "Template literal types" tree-sitter/tree-sitter-typescript#136
- 3: tree-sitter/tree-sitter-typescript@d08f57f
- 4: tree-sitter/tree-sitter-typescript@v0.21.2...v0.23.0
- 5: tree-sitter/tree-sitter-typescript@v0.16.3...v0.19.0
- 6: tree-sitter/tree-sitter-typescript@v0.21.1...v0.21.2
- 7: Template strings in types are buggy tree-sitter/tree-sitter-typescript#206
Cover template_string nodes in both literal checks.
kind: string only matches quoted string literals. Add template_string support for:
.ast-grep/rules/next-prefer-link-over-router-push.ymlstatic destinations likerouter.push(\/posts/${staticId}`)`..ast-grep/rules/query-no-inline-query-key.ymlinline query-key array items that use template literals, with an invalid regression test.
📍 Affects 2 files
.ast-grep/rules/next-prefer-link-over-router-push.yml#L15-L18(this comment).ast-grep/rules/query-no-inline-query-key.yml#L14-L16
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/next-prefer-link-over-router-push.yml around lines 15 - 18,
Update the literal checks in
.ast-grep/rules/next-prefer-link-over-router-push.yml at lines 15-18 and
.ast-grep/rules/query-no-inline-query-key.yml at lines 14-16 to match both
string and template_string nodes. Add an invalid regression test covering a
template-literal inline query-key array item, and ensure static template
destinations such as router.push calls are detected.
| - not: | ||
| has: | ||
| field: arguments | ||
| has: | ||
| stopBy: end | ||
| kind: property_identifier | ||
| regex: ^fallback$ |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
tmp_file=".ast-grep/.tmp-relay-withrelay-$RANDOM.tsx"
trap 'rm -f "$tmp_file"' EXIT
cat > "$tmp_file" <<'EOF'
withRelay(() => ({ fallback: null }), { query })
EOF
output=$(
pnpm exec ast-grep scan \
--rule .ast-grep/rules/relay-withrelay-requires-fallback.yml \
"$tmp_file" 2>&1 || true
)
printf '%s\n' "$output"
rg -q 'relay-withrelay-requires-fallback' <<<"$output"Repository: silverlogic/baseapp-frontend
Length of output: 729
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf 'ast-grep version: '
pnpm exec ast-grep --version || true
printf '\nRule file:\n'
cat -n .ast-grep/rules/relay-withrelay-requires-fallback.yml
printf '\nFiles under .ast-grep:\n'
git ls-files .ast-grep | sed -n '1,120p'
printf '\nSearch for fixture-like rule tests or ast-grep config:\n'
rg -n "relay-withrelay-requires-fallback|ast-grep|fallback: null|withRelay" .ast-grep package.json 2>/dev/null || true
printf '\nBehavioral probe: scan the exact false-positive candidate with temp rule.\n'
tmp_file=".ast-grep/.tmp-relay-withrelay-$RANDOM.tsx"
trap 'rm -f "$tmp_file"' EXIT
cat > "$tmp_file" <<'EOF'
withRelay(() => ({ fallback: null }), { query })
EOF
output=$(
pnpm exec ast-grep scan \
--rule .ast-grep/rules/relay-withrelay-requires-fallback.yml \
"$tmp_file" 2>&1 || true
)
printf '%s\n' "$output"
if rg -q 'relay-withrelay-requires-fallback' <<<"$output"; then
printf 'PREDICTION: rule found a match\n'
else
printf 'PREDICTION: rule did not find a match\n'
fiRepository: silverlogic/baseapp-frontend
Length of output: 10974
🌐 Web query:
ast-grep rule has stopBy end descendant traversal relational rule documentation
💡 Result:
In ast-grep, the stopBy field is an option used within relational rules (such as has, inside, follows, and precedes) to control how the search for a target node proceeds through the AST [1][2]. By default, relational rules use stopBy: 'neighbor', which restricts the search to immediate surrounding nodes [1][2]. Setting stopBy: 'end' overrides this behavior, allowing ast-grep to traverse the AST until it reaches the boundary of the search direction [1][2]. Specifically: - For inside: it searches up to the root node [2]. - For has: it searches down to the leaf nodes [2]. - For follows: it searches back to the first sibling [2]. - For precedes: it searches forward to the last sibling [2]. Additionally, stopBy can accept a custom rule object, in which case the search stops when an ancestor or descendant node matches that rule [1][2]. This stopBy rule is inclusive, meaning if the node that triggers the stop also matches the relational rule, the target node is still considered a match [1][3].
Citations:
- 1: https://ast-grep.github.io/guide/rule-config/relational-rule.html
- 2: https://ast-grep.github.io/reference/rule.html
- 3: https://astgrep.com/guide/rule-config/relational-rule
Match fallback on the direct withRelay options object.
With stopBy: end, has also traverses descendants. withRelay(() => ({ fallback: null }), { query }) currently passes because the arrow function argument contains fallback, even though the second argument is the withRelay options object and may omit fallback. Restrict the match to the direct options object keys. Add a regression case for this code path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/relay-withrelay-requires-fallback.yml around lines 11 - 17,
Restrict the fallback-property check in the withRelay rule to direct keys of the
second argument’s options object, rather than descendants reached by stopBy:
end. Preserve matching when that object directly contains fallback, and add a
regression case where the first callback returns fallback but the withRelay
options object omits it.
| kind: import_statement | ||
| has: | ||
| field: source | ||
| regex: "^['\"](redux|react-redux|@reduxjs/toolkit|redux-thunk|redux-saga)['\"]$" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== rule file =="
cat -n .ast-grep/rules/state-no-redux.yml
echo
echo "== test file =="
if [ -f .ast-grep/rule-tests/state-no-redux-test.yml ]; then
cat -n .ast-grep/rule-tests/state-no-redux-test.yml
else
ls -la .ast-grep/rule-tests/ || true
fi
echo
echo "== package imports in repo =="
rg -n --hidden --glob '!.git/**' "^(from [^\n:]+|import\\s*[\\\\]\\[])\\s*['\"](`@reduxjs/toolkit`|redux|react-redux|redux-thunk|redux-saga)(/[^'\"]+)*['\"]\\b" . || trueRepository: silverlogic/baseapp-frontend
Length of output: 1036
🌐 Web query:
@reduxjs/toolkit package exports subpath include query react TypeScript
💡 Result:
The @reduxjs/toolkit package utilizes the exports field in its package.json to provide structured subpaths for core functionality and optional addons [1]. These subpaths are fully compatible with modern TypeScript module resolution (such as Node16 or NodeNext) as they explicitly map types, ESM, and CommonJS entry points [2][1][3]. The primary subpaths available for use are: - @reduxjs/toolkit: The main entry point for standard Redux Toolkit functionality [4][1]. - @reduxjs/toolkit/query: The entry point for core RTK Query features [4][1]. - @reduxjs/toolkit/query/react: The entry point for RTK Query features specifically designed for React, which automatically generates hooks [5][6][4][1]. These subpaths ensure that when you import them in your TypeScript project, the corresponding type definitions are correctly resolved [2][1]. For example, importing from @reduxjs/toolkit/query/react will correctly provide the types for React-specific RTK Query hooks [7][1]. This structure was standardized as part of Redux Toolkit 2.0 to support modern ESM and improve TypeScript portability [2][3]. If you are working in a TypeScript environment, you can safely use these paths in your import statements, and they are the recommended way to access specific modules within the library [6][8][4].
Citations:
- 1: https://github.com/reduxjs/redux-toolkit/blob/master/packages/toolkit/package.json
- 2: Migrate the RTK package to be full ESM reduxjs/redux-toolkit#3095
- 3: https://redux-toolkit.js.org/usage/migrating-rtk-2
- 4: RTK 2.0: Export All Internal Types and Functions reduxjs/redux-toolkit#3681
- 5: https://www.npmjs.com/package/@reduxjs/toolkit
- 6: https://redux-toolkit.js.org/introduction/getting-started
- 7: Support for Typescript Node16 Module Resolution (RTK-Query & RTK-Query/React) reduxjs/redux-toolkit#2485
- 8: https://github.com/reduxjs/redux-toolkit/blob/master/packages/toolkit/README.md
🌐 Web query:
redux-saga effects package exports subpath README effects
💡 Result:
The redux-saga library provides a specific subpath for importing effect creators: redux-saga/effects [1][2][3]. This subpath serves as the primary entry point for accessing declarative effect creators such as call, put, takeEvery, and takeLatest [1][4][3]. When writing Sagas, you import these functions from the subpath to create effect descriptions that the redux-saga middleware interprets to perform operations, such as calling asynchronous functions or dispatching actions [4][3]. There is no separate package named effects; rather, effects is an exported subpath provided by the main redux-saga package [1][2][3]. While there is no dedicated "effects README," the official documentation provides extensive information on using these effects in the API Reference section and the "Declarative Effects" guide [4][3]. In certain environments, such as when using UMD bundles, developers historically encountered issues accessing these effects because they were not explicitly exposed in the UMD global scope in the same way as they are via ESM/CommonJS imports [5]. For such cases, the library has provided support for dedicated UMD bundles or specific access patterns (e.g., accessing via ReduxSaga.effects) [5]. Additionally, TypeScript users may occasionally encounter issues with importing from this subpath if their project configuration—such as preserveSymlinks—interferes with module resolution [6].
Citations:
- 1: https://github.com/redux-saga/redux-saga/blob/40a638e9463972e7a1cda40483c08ad4981be5e2/README.md
- 2: https://github.com/redux-saga/redux-saga
- 3: https://github.com/redux-saga/redux-saga/blob/main/docs/basics/DeclarativeEffects.md
- 4: https://redux-saga.js.org/docs/api/
- 5: What is the recommended way of using effects with the UMD bundle? redux-saga/redux-saga#1880
- 6: Module '"redux-saga/effects"' has no exported member 'call'. redux-saga/redux-saga#2389
Match Redux subpath imports.
Line 9 only rejects exact package roots. Imports such as @reduxjs/toolkit/query/react and redux-saga/effects use official package subpaths and still bypass this error rule. Update the regex to include these subpaths and cover them in .ast-grep/rule-tests/state-no-redux-test.yml.
Suggested regex change
- regex: "^['\"](redux|react-redux|`@reduxjs/toolkit`|redux-thunk|redux-saga)['\"]$"
+ regex: "^['\"](redux|react-redux|`@reduxjs/toolkit`|redux-thunk|redux-saga)(/[^'\"]+)?['\"]$"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| regex: "^['\"](redux|react-redux|@reduxjs/toolkit|redux-thunk|redux-saga)['\"]$" | |
| regex: "^['\"](redux|react-redux|`@reduxjs/toolkit`|redux-thunk|redux-saga)(/[^'\"]+)?['\"]$" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/state-no-redux.yml at line 9, Update the package-matching
regex in the state-no-redux rule to reject imports from the listed Redux
packages and any subpath beneath them, while preserving exact-root matching.
Extend the cases in state-no-redux-test.yml to cover representative subpath
imports such as `@reduxjs/toolkit/query/react` and redux-saga/effects.
| kind: jsx_attribute | ||
| regex: '^className=[\s\S]*\btext-(xs|sm|base|lg|xl|[2-9]xl)\b' |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Files:\n'
fd -a 'tailwind-no-raw-text-size' . | sed 's#^\./##'
printf '\nRule files:\n'
for f in $(fd 'tailwind-no-raw-text-size' .); do
echo "--- $f"
wc -l "$f"
cat -n "$f"
done
printf '\nSearch related usages:\n'
rg -n "tailwind-no-raw-text-size|raw text-size|prose-body2|icon-text-sm|text-xs|text-sm|text-base|text-lg|text-xl|text-[2-9]xl" .ast-grep || true
printf '\nBehavioural probe for \btext- boundary in regex input samples:\n'
python3 - <<'PY'
import re
pattern = re.compile(r'^className=[\s\S]*\btext-(xs|sm|base|lg|xl|[2-9]xl)\b', re.ASCII)
samples = [
r'className="text-xs"',
r'className="prose-body2 text-text-secondary"',
r'className="flex items-center gap-2 text-left"',
r'className="icon-text-sm"',
r'className="text-sm"',
r'className="text-sm-foo"',
r'className="--text-sm"',
r'className="text-[abc]"',
]
for s in samples:
m = pattern.search(s)
print(f"{s!r}: {'MATCH ' + m.group(0) if m else 'no match'}")
PYRepository: silverlogic/baseapp-frontend
Length of output: 2229
Keep the raw text-size rule aligned with Tailwind class tokens.
Hyphenated variants such as icon-text-sm, --text-sm, and text-sm-foo still match because \b treats - as a boundary. Use token-aware matching that limits this rule to standalone Tailwind size classes and add a valid hyphenated custom-class fixture.
📍 Affects 2 files
.ast-grep/rules/tailwind-no-raw-text-size.yml#L6-L7(this comment).ast-grep/rule-tests/tailwind-no-raw-text-size-test.yml#L2-L6
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/tailwind-no-raw-text-size.yml around lines 6 - 7, The raw
text-size rule in .ast-grep/rules/tailwind-no-raw-text-size.yml must match only
standalone Tailwind text-size tokens, not hyphenated classes such as
icon-text-sm, --text-sm, or text-sm-foo; replace the word-boundary matching with
token-aware boundaries. Update
.ast-grep/rule-tests/tailwind-no-raw-text-size-test.yml to add a valid
hyphenated custom-class fixture demonstrating it is not flagged.
`relay-no-uselazyloadquery` flagged every call site, which was misleading: `useLazyLoadQuery` is the right tool when server preloading isn't possible, and one or two per screen costs nothing. It fired 41 times across the two repos, almost all on correct code. What actually hurts is a component that runs the query and is rendered once per item — a 50-row list issues 50 requests. `relay-uselazyloadquery-in-list` detects exactly that: a component declared in the file, calling `useLazyLoadQuery`, and rendered from a `.map()`/`flatMap()` or a list renderer prop (`itemContent`, `renderItem`). A row that reads a fragment instead is not flagged. High enough confidence to gate, so it ships as `severity: error` — both repos are clean today. The correlation is same-file only: ast-grep has no cross-file symbol resolution, so the usual layout (ChatRooms maps over ChatRoomItem from its own file) stays a human check. Recorded in the README limitations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCvYtccHsP2W8iU7SrzG83
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.ast-grep/rules/relay-uselazyloadquery-in-list.yml:
- Around line 14-26: Constrain the $COMP variable_declarator match and its JSX
element usage to the same lexical scope, or replace the name-based match with
symbol-aware resolution so shadowed components do not trigger false positives.
Add a fixture covering an outer queried component shadowing an inner non-queried
component, and retain error severity only once that case is correctly excluded.
- Line 4: The ast-grep rule message in relay-uselazyloadquery-in-list.yml
contains a broken .claude documentation link; replace that reference with an
existing repository documentation path such as packages/graphql/README.md, while
preserving the rest of the warning message.
- Around line 16-26: Update the component declaration matching in the rule to
support both variable declarators and function declarations, while preserving
the existing useLazyLoadQuery detection. Add an invalid fixture covering a
function-declared component such as RoomItem rendered inside map().
- Around line 28-36: Extend the rule path in the relay-uselazyloadquery-in-list
pattern to recognize callback-bound renderer components such as NotificationItem
when invoked through named helpers like renderNotificationItem, including calls
originating from map, flatMap, or itemContent. Preserve existing direct-renderer
detection and add a fixture covering the helper-renderer form, or explicitly
constrain and document the rule as inline-only if that is the intended scope.
- Around line 35-36: The jsx_attribute matcher in the relay-uselazyloadquery
rule only accepts renderer names when '=' follows immediately; update the regex
for itemContent, renderItem, and ItemContent to allow optional whitespace before
the equals sign, while preserving matching for the existing forms.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: a7029d1d-dd42-4f7c-bd69-1ba283ed0243
📒 Files selected for processing (4)
.ast-grep/README.md.ast-grep/rule-tests/__snapshots__/relay-uselazyloadquery-in-list-snapshot.yml.ast-grep/rule-tests/relay-uselazyloadquery-in-list-test.yml.ast-grep/rules/relay-uselazyloadquery-in-list.yml
| id: relay-uselazyloadquery-in-list | ||
| language: Tsx | ||
| severity: error | ||
| message: "This component calls `useLazyLoadQuery` and is rendered once per item — every row fires its own query. Lift the data into the list's own query (a fragment on the item, spread from the parent connection) or preload it with `loadSerializableQuery` + `usePreloadedQuery`. A couple of `useLazyLoadQuery` calls per screen is fine; one per row is not. See .claude/skills/frontend-patterns/references/graphql-data-fetching.md" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
doc='.claude/skills/frontend-patterns/references/graphql-data-fetching.md'
test -f "$doc" || {
echo "Missing diagnostic reference: $doc" >&2
exit 1
}
find . -type l -path '*/.ast-grep*' -print -exec readlink -f {} \;Repository: silverlogic/baseapp-frontend
Length of output: 265
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== target file =="
ls -la ".ast-grep/rules/relay-uselazyloadquery-in-list.yml" 2>/dev/null || true
echo
echo "== symlinks under .ast-grep (if any) =="
find . -type l -path '*/.ast-grep/*' -print -exec ls -la {} \; 2>/dev/null || true
echo
echo "== rule file excerpt =="
cat -n ".ast-grep/rules/relay-uselazyloadquery-in-list.yml" 2>/dev/null | sed -n '1,80p' || true
echo
echo "== .claude tree relevant =="
if [ -d .claude ]; then
find .claude -maxdepth 4 -type f -print | sed -n '1,120p'
else
echo "no .claude directory"
fi
echo
echo "== all refs to path =="
rg -n "graphql-data-fetching|frontend-patterns|relay-uselazyloadquery-in-list|loadSerializableQuery|usePreloadedQuery" . -g '!node_modules' -g '!dist' -g '!build' 2>/dev/null | sed -n '1,200p' || trueRepository: silverlogic/baseapp-frontend
Length of output: 4346
Fix the broken reference in the ast-grep rule message.
relay-uselazyloadquery-in-list.yml points to .claude/skills/frontend-patterns/references/graphql-data-fetching.md, but this repository has no .claude directory. Use a reference that exists for this repo, such as packages/graphql/README.md or another shared doc.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/relay-uselazyloadquery-in-list.yml at line 4, The ast-grep
rule message in relay-uselazyloadquery-in-list.yml contains a broken .claude
documentation link; replace that reference with an existing repository
documentation path such as packages/graphql/README.md, while preserving the rest
of the warning message.
| has: | ||
| stopBy: end | ||
| kind: variable_declarator | ||
| all: | ||
| - has: | ||
| field: name | ||
| pattern: $COMP | ||
| - has: | ||
| stopBy: end | ||
| kind: call_expression | ||
| has: | ||
| field: function | ||
| regex: ^useLazyLoadQuery$ |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
fixture="$(mktemp --suffix=.tsx)"
trap 'rm -f "$fixture"' EXIT
cat >"$fixture" <<'TSX'
const Item = () => {
const data = useLazyLoadQuery<ItemQuery>(ItemQuery, {})
return <div>{data.item}</div>
}
const List = () => {
const Item = () => <span />
return items.map((item) => <Item key={item.id} />)
}
TSX
pnpm exec ast-grep scan \
-r .ast-grep/rules/relay-uselazyloadquery-in-list.yml \
"$fixture" \
--json
# Expected: no diagnostic for the inner shadowed <Item />.Repository: silverlogic/baseapp-frontend
Length of output: 8045
Do not resolve $COMP by file-wide name matching.
The rule matches any variable_declarator(name=$COMP) in the file, so an outer queried component can shadow an inner non-queried component and cause a false positive. Constrain the matched component and JSX element to the same lexical scope, or use a symbol-aware check. Add a valid shadowing fixture before keeping this rule at error severity.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/relay-uselazyloadquery-in-list.yml around lines 14 - 26,
Constrain the $COMP variable_declarator match and its JSX element usage to the
same lexical scope, or replace the name-based match with symbol-aware resolution
so shadowed components do not trigger false positives. Add a fixture covering an
outer queried component shadowing an inner non-queried component, and retain
error severity only once that case is correctly excluded.
| kind: variable_declarator | ||
| all: | ||
| - has: | ||
| field: name | ||
| pattern: $COMP | ||
| - has: | ||
| stopBy: end | ||
| kind: call_expression | ||
| has: | ||
| field: function | ||
| regex: ^useLazyLoadQuery$ |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
fixture="$(mktemp --suffix=.tsx)"
trap 'rm -f "$fixture"' EXIT
cat >"$fixture" <<'TSX'
function RoomItem({ id }) {
const data = useLazyLoadQuery<RoomQueryType>(RoomQuery, { id })
return <div>{data.room.name}</div>
}
const Rooms = ({ ids }) => (
<div>{ids.map((id) => <RoomItem key={id} id={id} />)}</div>
)
TSX
pnpm exec ast-grep scan \
-r .ast-grep/rules/relay-uselazyloadquery-in-list.yml \
"$fixture" \
--json
# Expected after the fix: one diagnostic for <RoomItem ... />.Repository: silverlogic/baseapp-frontend
Length of output: 732
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== rule file =="
cat -n .ast-grep/rules/relay-uselazyloadquery-in-list.yml
echo
echo "== nearby fixtures/rules if any =="
fd -t f '^relay-uselazyloadquery|useLazyLoadQuery' .ast-grep/rules || true
echo
echo "== parse fixture with explicit TSX parser =="
pnpm exec ast-grep parse "$fixture" --lang tsxRepository: silverlogic/baseapp-frontend
Length of output: 1993
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== parse function-declaration fixture =="
tmp="$(mktemp --suffix=.tsx)"
trap 'rm -f "$tmp"' EXIT
cat >"$tmp" <<'TSX'
function RoomItem({ id }) {
const data = useLazyLoadQuery<RoomQueryType>(RoomQuery, { id })
return <div>{data.room.name}</div>
}
const Rooms = ({ ids }) => <div>{ids.map((id) => <RoomItem key={id} id={id} />)}</div>
TSX
pnpm exec ast-grep parse "$tmp" --lang tsx || true
echo
echo "== scan function-declaration fixture =="
pnpm exec ast-grep scan \
-r .ast-grep/rules/relay-uselazyloadquery-in-list.yml \
"$tmp" \
--json || true
echo
echo "== scan arrow-const fixture for comparison =="
tmp2="$(mktemp --suffix=.tsx)"
trap 'rm -f "$tmp"; rm -f "$tmp2"' EXIT
cat >"$tmp2" <<'TSX'
const RoomItem = ({ id }) => {
const data = useLazyLoadQuery<RoomQueryType>(RoomQuery, { id })
return <div>{data.room.name}</div>
}
const Rooms = ({ ids }) => <div>{ids.map((id) => <RoomItem key={id} id={id} />)}</div>
TSX
pnpm exec ast-grep scan \
-r .ast-grep/rules/relay-uselazyloadquery-in-list.yml \
"$tmp2" \
--json || true
echo
echo "== all tracked rules =="
find .ast-grep/rules -maxdepth 1 -type f -print | sed 's#^\./##'Repository: silverlogic/baseapp-frontend
Length of output: 10820
Match function-declared components.
The declaration search at .ast-grep/rules/relay-uselazyloadquery-in-list.yml:16 only accepts variable_declarator. A component declared as function RoomItem(...) { ... } is missed when it is rendered inside .map(), so add a function_declaration branch and an invalid fixture for this form.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/relay-uselazyloadquery-in-list.yml around lines 16 - 26,
Update the component declaration matching in the rule to support both variable
declarators and function declarations, while preserving the existing
useLazyLoadQuery detection. Add an invalid fixture covering a function-declared
component such as RoomItem rendered inside map().
| - inside: | ||
| stopBy: end | ||
| any: | ||
| - kind: call_expression | ||
| has: | ||
| field: function | ||
| regex: \.(map|flatMap)$ | ||
| - kind: jsx_attribute | ||
| regex: ^(itemContent|renderItem|ItemContent)= |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
fixture="$(mktemp --suffix=.tsx)"
trap 'rm -f "$fixture"' EXIT
cat >"$fixture" <<'TSX'
const Item = ({ id }) => {
const data = useLazyLoadQuery<ItemQuery>(ItemQuery, { id })
return <div>{data.item.name}</div>
}
const renderItem = (item) => <Item id={item.id} />
const List = ({ items }) => (
<Virtuoso
data={items}
itemContent={(index, item) => renderItem(item)}
/>
)
TSX
pnpm exec ast-grep scan \
-r .ast-grep/rules/relay-uselazyloadquery-in-list.yml \
"$fixture" \
--json
# Expected if helper traversal is supported: one diagnostic for <Item />.Repository: silverlogic/baseapp-frontend
Length of output: 732
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '--- rule file ---\n'
cat -n .ast-grep/rules/relay-uselazyloadquery-in-list.yml
printf '\n--- target file outline/search ---\n'
grep -n "useLazyLoadQuery\|Virtuoso\|itemContent\|renderItem\|NotificationItem" packages/components/modules/notifications/web/NotificationsList/index.tsx || true
printf '\n--- direct fixture for helper case ---\n'
fixture="$(mktemp --suffix=.tsx)"
trap 'rm -f "$fixture"' EXIT
cat >"$fixture" <<'TSX'
const Item = ({ id }) => {
const data = useLazyLoadQuery<ItemQuery>(ItemQuery, { id })
return <div>{data.item.name}</div>
}
const renderItem = (item) => <Item id={item.id} />
const List = ({ items }) => (
<Virtuoso
data={items}
itemContent={(index, item) => renderItem(item)}
/>
)
TSX
pnpm exec ast-grep scan \
-r .ast-grop/rules/relay-uselazyloadquery-in-list.yml \
"$fixture" \
--json || true
pnpm exec ast-grep scan \
-r .ast-grep/rules/relay-uselazyloadquery-in-list.yml \
"$fixture" \
--json || trueRepository: silverlogic/baseapp-frontend
Length of output: 257
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- rule file ---'
cat -n .ast-grep/rules/relay-uselazyloadquery-in-list.yml
printf '%s\n' ''
printf '%s\n' '--- target file matches ---'
grep -n "useLazyLoadQuery\|Virtuoso\|itemContent\|renderItem\|renderNotificationItem\|NotificationItem" packages/components/modules/notifications/web/NotificationsList/index.tsx || true
printf '%s\n' ''
printf '%s\n' '--- helper-case fixture ---'
fixture="$(mktemp --suffix=.tsx)"
trap 'rm -f "$fixture"' EXIT
cat >"$fixture" <<'TSX'
const Item = ({ id }) => {
const data = useLazyLoadQuery<ItemQuery>(ItemQuery, { id })
return <div>{data.item.name}</div>
}
const renderItem = (item) => <Item id={item.id} />
const List = ({ items }) => (
<Virtuoso
data={items}
itemContent={(index, item) => renderItem(item)}
/>
)
TSX
pnpm exec ast-grep scan \
-r .ast-grep/rules/relay-uselazyloadquery-in-list.yml \
"$fixture" \
--json || true
printf '%s\n' '--- inline-case fixture for comparison ---'
fixture2="$(mktemp --suffix=.tsx)"
trap 'rm -f "$fixture2"' EXIT
cat >"$fixture2" <<'TSX'
const Item = ({ id }) => {
const data = useLazyLoadQuery<ItemQuery>(ItemQuery, { id })
return <div>{data.item.name}</div>
}
const List = ({ items }) => (
<Virtuoso
data={items}
itemContent={(index, item) => <Item id={item.id} />}
/>
)
TSX
pnpm exec ast-grep scan \
-r .ast-grep/rules/relay-uselazyloadquery-in-list.yml \
"$fixture2" \
--json || trueRepository: silverlogic/baseapp-frontend
Length of output: 10723
Support callback-bound renderer components.
The rule detects NotificationItem, but the current rule misses it because NotificationItem is rendered through renderNotificationItem, not directly under itemContent. Extend the .map()/itemContent path to follow named renderer calls, or document limited inline-only support and add a fixture for this helper form.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/relay-uselazyloadquery-in-list.yml around lines 28 - 36,
Extend the rule path in the relay-uselazyloadquery-in-list pattern to recognize
callback-bound renderer components such as NotificationItem when invoked through
named helpers like renderNotificationItem, including calls originating from map,
flatMap, or itemContent. Preserve existing direct-renderer detection and add a
fixture covering the helper-renderer form, or explicitly constrain and document
the rule as inline-only if that is the intended scope.
| - kind: jsx_attribute | ||
| regex: ^(itemContent|renderItem|ItemContent)= |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
fixture="$(mktemp --suffix=.tsx)"
trap 'rm -f "$fixture"' EXIT
cat >"$fixture" <<'TSX'
const Item = ({ id }) => {
const data = useLazyLoadQuery<ItemQuery>(ItemQuery, { id })
return <div>{data.item.name}</div>
}
const List = ({ ids }) => (
<FlatList
data={ids}
renderItem = {({ item }) => <Item id={item} />}
/>
)
TSX
pnpm exec ast-grep scan \
-r .ast-grep/rules/relay-uselazyloadquery-in-list.yml \
"$fixture" \
--json
# Expected: one diagnostic for <Item />.Repository: silverlogic/baseapp-frontend
Length of output: 732
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== files matching relay rule =="
fd -a 'relay-uselazyloadquery-in-list\.yml|relay.*lazy.*list|react-relay.*lazy' . | sed 's#^\./##'
echo "== rule content =="
rule_file=".ast-grep/rules/relay-uselazyloadquery-in-list.yml"
if [ -f "$rule_file" ]; then
grep -n . "$rule_file"
else
echo "missing $rule_file"
fi
echo "== AST/text behavior probe equivalent fixtures =="
tmpdir="$(mktemp -d)"
for attr in 'renderItem={...}' 'renderItem = {...}' 'itemContent = {...}' 'ItemContent={...}'; do
cat > "$tmpdir/fixture.tsx" <<TSX
const Item = ({ id }) => {
const data = useLazyLoadQuery<ItemQuery>(ItemQuery, { id })
return <div>{data.item.name}</div>
}
const List = ({ ids }) => (
<FlatList
data={ids}
$attr
/>
)
TSX
echo "--- attr: $attr"
pnpm exec ast-grep scan \
-r .ast-grep/rules/relay-uselazyloadquery-in-list.yml \
"$tmpdir/fixture.tsx" \
--json || true
done
rm -rf "$tmpdir"Repository: silverlogic/baseapp-frontend
Length of output: 4129
Allow whitespace around renderer attributes.
The regex on jsx_attribute only matches when = immediately follows itemContent, renderItem, or ItemContent. JSX such as renderItem = {...} is valid and currently skips this lint check. Match the attribute name field, add \s*, or add whitespace fixtures.
Suggested matcher change
- - kind: jsx_attribute
- regex: ^(itemContent|renderItem|ItemContent)=
+ - kind: jsx_attribute
+ has:
+ field: name
+ regex: ^(itemContent|renderItem|ItemContent)$📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - kind: jsx_attribute | |
| regex: ^(itemContent|renderItem|ItemContent)= | |
| - kind: jsx_attribute | |
| has: | |
| field: name | |
| regex: ^(itemContent|renderItem|ItemContent)$ |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.ast-grep/rules/relay-uselazyloadquery-in-list.yml around lines 35 - 36, The
jsx_attribute matcher in the relay-uselazyloadquery rule only accepts renderer
names when '=' follows immediately; update the regex for itemContent,
renderItem, and ItemContent to allow optional whitespace before the equals sign,
while preserving matching for the existing forms.



Mirrors what baseapp-backend#437 did for the backend: ast-grep rules that statically enforce the code guidelines, hosted here and shared with consuming templates the same way the agent skills are.
Sharing model
Rules live in
.ast-grep/here. The consuming template symlinks.ast-grep→baseapp-frontend/.ast-grepand keeps its own rootsgconfig.yml— identical to the backend setup.The 27 rules
Derived from the template's
CODE-GUIDELINES.mdand thefrontend-conventions/frontend-patterns/frontend-design-systemskills. Every message names the skill reference file it enforces.ts-no-enum,ts-no-type-name-prefix,ts-props-use-interface,ts-loose-autocomplete,ts-types-not-in-indexnext-no-img-element,next-image-no-custom-loader,next-image-explicit-dimensions,next-page-no-use-client,next-page-no-client-data-fetching,next-prefer-link-over-router-push,react-no-data-fetching-in-useeffectmui-sx-prop-limit,mui-no-hardcoded-color,mui-styled-not-in-index,tailwind-no-raw-text-size,ds-prefer-baseapp-wrapper,native-styles-not-in-index,native-use-design-system-themerelay-no-uselazyloadquery,relay-withrelay-requires-fallback,query-no-inline-query-key,form-useform-requires-generic,form-no-other-form-libraries,form-submit-use-loading-button,state-no-redux,state-zustand-no-global-storeEach has valid/invalid cases plus an accepted snapshot in
.ast-grep/rule-tests/—ast-grep testpasses 27/27.Two decisions worth reviewing
languageGlobsmaps*.tsonto the TSX grammar. Without it, ast-grep treats.tsand.tsxas separate languages and every type-level rule needs a duplicate file per extension. Cost: four.tsfiles here using the old-style generic arrow<T>(x: T) => x(TSX reads<T>as a JSX tag) parse with an error node around that expression. tree-sitter recovers, so matches elsewhere in those files are unaffected. Documented in the README.Only 3 rules are
severity: error—next-no-img-element(scoped toapps/web/**),form-no-other-form-libraries,state-no-redux— the ones already clean in both repos, so the new stage can't fail on day one. Everything else is a warning: 130 findings here, 40 in the template, exit 0 in both.Four rules are held at
warningonly because of pre-existing violations I did not fix here, since each is a behavior or public-API change rather than lint cleanup..ast-grep/README.mdcarries this as a graduation table:next-image-no-custom-loaderpackages/wagtail— removing the loader needsimages.remotePatternsconfigured in consumers firstts-no-enumpackages/utils/constants/languages.ts(LanguagesEnumis a published export, so removing it is breaking), 1 in the templatenext-page-no-use-client(static-layout)state-zustand-no-global-store(.baseapp)/examples/state-managementNot enforceable
.ast-grep/README.mdhas a Limitations section for guidelines that aren't AST-expressible: cross-branch imports, one-fragment-per-component, Tailwind-as-last-resort, route group placement, dialog state ownership.I drafted and then dropped a depth-based cross-branch-import rule — import depth is not a proxy for direction, and it fired 751 times on legitimate upward imports into shared directories.
CI
pnpm lint:ast-grep(=ast-grep test && ast-grep scan) as anast-grepstep in thebuild-and-lintjob.@ast-grep/cliadded to thelintcatalog and root devDependencies; the lockfile diff contains only ast-grep entries.Verified locally with the real script:
pnpm lint:ast-grepexits 0 here and in the template.🤖 Generated with Claude Code
https://claude.ai/code/session_01FCvYtccHsP2W8iU7SrzG83
Summary by CodeRabbit
New Features
Tests