Skip to content

Add shared ast-grep rules and wire them into CI - #423

Open
nossila wants to merge 2 commits into
masterfrom
feature/ast-grep
Open

Add shared ast-grep rules and wire them into CI#423
nossila wants to merge 2 commits into
masterfrom
feature/ast-grep

Conversation

@nossila

@nossila nossila commented Aug 5, 2026

Copy link
Copy Markdown
Member

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-grepbaseapp-frontend/.ast-grep and keeps its own root sgconfig.yml — identical to the backend setup.

The 27 rules

Derived from the template's CODE-GUIDELINES.md and the frontend-conventions / frontend-patterns / frontend-design-system skills. Every message names the skill reference file it enforces.

Area Rules
TypeScript ts-no-enum, ts-no-type-name-prefix, ts-props-use-interface, ts-loose-autocomplete, ts-types-not-in-index
Next.js / React next-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-useeffect
Styling mui-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-theme
Data / forms / state relay-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-store

Each has valid/invalid cases plus an accepted snapshot in .ast-grep/rule-tests/ast-grep test passes 27/27.

Two decisions worth reviewing

languageGlobs maps *.ts onto the TSX grammar. Without it, ast-grep treats .ts and .tsx as separate languages and every type-level rule needs a duplicate file per extension. Cost: four .ts files 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: errornext-no-img-element (scoped to apps/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 warning only 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.md carries this as a graduation table:

Rule Violations
next-image-no-custom-loader 2 in packages/wagtail — removing the loader needs images.remotePatterns configured in consumers first
ts-no-enum packages/utils/constants/languages.ts (LanguagesEnum is a published export, so removing it is breaking), 1 in the template
next-page-no-use-client 7 template pages under (static-layout)
state-zustand-no-global-store 2 in the template's (.baseapp)/examples/state-management

Not enforceable

.ast-grep/README.md has 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 an ast-grep step in the build-and-lint job. @ast-grep/cli added to the lint catalog and root devDependencies; the lockfile diff contains only ast-grep entries.

Verified locally with the real script: pnpm lint:ast-grep exits 0 here and in the template.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FCvYtccHsP2W8iU7SrzG83

Summary by CodeRabbit

  • New Features

    • Added comprehensive automated code-quality rules covering design-system usage, forms, styling, Next.js, state management, data fetching, Relay, and TypeScript conventions.
    • Added guidance and testing documentation for the new rules.
    • Added an automated lint command and integrated it into CI.
  • Tests

    • Added extensive test coverage and snapshots for the new code-quality checks.

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
@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: da143d3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This 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.

Changes

Ast-grep lint rollout

Layer / File(s) Summary
Tooling, CI, and documentation
sgconfig.yml, package.json, pnpm-workspace.yaml, .github/workflows/main.yml, README.md, .ast-grep/README.md
Adds ast-grep configuration, the lint:ast-grep script, the CLI catalog entry, CI execution, and usage and contribution documentation.
Design-system, form, styling, and native rules
.ast-grep/rules/*, .ast-grep/rule-tests/*
Adds rules and tests for design-system imports, form libraries, submit buttons, typed forms, colors, styling placement, native themes, and Tailwind text sizing.
Next.js page, image, and navigation rules
.ast-grep/rules/next-*, .ast-grep/rule-tests/next-*
Adds rules and tests for page client directives, client data fetching, image dimensions and loaders, raw images, and static navigation.
Data fetching, Relay, and state rules
.ast-grep/rules/*query*, .ast-grep/rules/*react*, .ast-grep/rules/relay-*, .ast-grep/rules/state-*, .ast-grep/rule-tests/*
Adds rules and tests for query keys, data fetching in effects, Relay fallbacks and list rendering, Redux imports, and module-scope Zustand stores.
TypeScript naming and file-structure rules
.ast-grep/rules/ts-*, .ast-grep/rule-tests/ts-*
Adds rules and tests for autocomplete unions, enums, type-name prefixes, props interfaces, and type declarations in index files.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: priscilladeroode

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the addition of shared ast-grep rules and their CI integration.
Description check ✅ Passed The description clearly explains the rules, sharing model, test coverage, severity decisions, limitations, and CI integration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ast-grep

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 lift

Validate 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 win

Cover useSuspenseQuery in this test.

The rule matches useQuery, useSuspenseQuery, and useLazyLoadQuery, but this file tests only the first and third. Add an invalid useSuspenseQuery(...) 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 win

Test the rule’s file scope.

The rule in .ast-grep/rules/mui-styled-not-in-index.yml is restricted to **/index.tsx at Line 6, but this test provides no filename. It verifies only that styled() matches. It does not verify rejection in index.tsx or allowance in styled.tsx. Add a scan-level fixture for both paths, or document that this filter is verified outside ast-grep test. ast-grep documents files as 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 win

Test the exact four-property boundary.

nthChild: 4 matches the fourth named child because ast-grep uses one-based child positions. The rule must report an sx object 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1e8ceee and 577b9f7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is 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.yml
  • README.md
  • package.json
  • pnpm-workspace.yaml
  • sgconfig.yml

Comment on lines +14 to +15
kind: jsx_attribute
regex: '^type=.*submit'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 || true

Repository: 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)
PY

Repository: 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:

selector: jsx_attribute - has: kind: jsx_expression has: pattern: $A Explanation: 1. pattern: Uses a context to define a template of the JSX element and a selector to isolate the specific jsx_attribute node [1][2]. 2. has: Ensures the attribute contains a jsx_expression, which corresponds to the {submitType} syntax [5][2]. 3. Meta variables: $A captures the value inside the braces, which can then be used in a fix or transformation [2][6]. If you specifically need to restrict the variable name (e.g., matching only when the variable is exactly submitType), you can add a regex rule or a constraint [3][2]: rule: all: - pattern: context:
selector: jsx_attribute - has: kind: jsx_expression has: pattern: submitType Note: Avoid using regex as a standalone rule for JSX attributes, as it lacks awareness of the syntax tree structure and may cause performance issues or false positives [3][4]. Use the pattern rule for structural matching and combine it with regex or other atomic rules only as necessary [3][7].

Citations:


🌐 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:


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.

Comment on lines +18 to +21
- not:
has:
field: source
regex: "providers/native['\"]$"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 || true

Repository: 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:


🌐 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:


🌐 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:


🏁 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()}")
PY

Repository: 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
done

Repository: 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.yml

Repository: 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.

Comment on lines +17 to +20
- not:
has:
kind: jsx_attribute
regex: ^fill($|=)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 '')
PY

Repository: 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:


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 fill and add parent-position validation.
  • Narrow the message to drop the fill promise.
  • Update .ast-grep/rule-tests/next-image-explicit-dimensions-test.yml#L5-L6 to 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

Comment on lines +3 to +4
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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 || true

Repository: 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:


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

Comment on lines +7 to +9
rule:
kind: expression_statement
regex: "^['\"]use client['\"]"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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"
fi

Repository: 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" || true

Repository: 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" || true

Repository: 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" || true

Repository: 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" || true

Repository: 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" || true

Repository: 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:


🌐 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:


🌐 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:


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-L15 and .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.

Comment on lines +15 to +18
- has:
field: arguments
has:
kind: string

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 || true

Repository: 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; fi

Repository: 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:


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 -200

Repository: 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 || true

Repository: 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 || true

Repository: 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:


🌐 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:


🌐 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:


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.yml static destinations like router.push(\/posts/${staticId}`)`.
  • .ast-grep/rules/query-no-inline-query-key.yml inline 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.

Comment on lines +11 to +17
- not:
has:
field: arguments
has:
stopBy: end
kind: property_identifier
regex: ^fallback$

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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'
fi

Repository: 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:


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)['\"]$"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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" . || true

Repository: 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:


🌐 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:


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.

Suggested change
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.

Comment on lines +6 to +7
kind: jsx_attribute
regex: '^className=[\s\S]*\btext-(xs|sm|base|lg|xl|[2-9]xl)\b'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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'}")
PY

Repository: 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
@sonarqubecloud

sonarqubecloud Bot commented Aug 6, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 577b9f7 and da143d3.

📒 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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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' || true

Repository: 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.

Comment on lines +14 to +26
has:
stopBy: end
kind: variable_declarator
all:
- has:
field: name
pattern: $COMP
- has:
stopBy: end
kind: call_expression
has:
field: function
regex: ^useLazyLoadQuery$

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +16 to +26
kind: variable_declarator
all:
- has:
field: name
pattern: $COMP
- has:
stopBy: end
kind: call_expression
has:
field: function
regex: ^useLazyLoadQuery$

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 tsx

Repository: 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().

Comment on lines +28 to +36
- inside:
stopBy: end
any:
- kind: call_expression
has:
field: function
regex: \.(map|flatMap)$
- kind: jsx_attribute
regex: ^(itemContent|renderItem|ItemContent)=

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 || true

Repository: 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 || true

Repository: 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.

Comment on lines +35 to +36
- kind: jsx_attribute
regex: ^(itemContent|renderItem|ItemContent)=

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
- 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant