Skip to content

feat(eslint-plugin): add no-raw-param-reads and no-href-arithmetic rules - #43

Merged
JasonPaff merged 2 commits into
mainfrom
eslint-update
Aug 12, 2026
Merged

JasonPaff merged 2 commits into
mainfrom
eslint-update

Conversation

@JasonPaff

Copy link
Copy Markdown
Owner

Summary

First release from the design-20 rule roadmap (LR1 + LR3), completing the bypass audit in both directions: no-raw-hrefs finds every raw write paramour never sees; these add the raw reads and guard the integrity of what href() builds.

no-raw-param-reads (LR1, surfaces 1–2)

  • Flags useSearchParams() / useParams() imported from next/navigation (named/aliased/namespace forms, .js spelling included), nudging useSearch(route) / useRouteParams(route) from @paramour-js/next/app.
  • Flags router.query on a next/router useRouter() router — variable, direct-call, and destructured forms (including renames and nested patterns) — nudging the @paramour-js/next/pages hooks.
  • Escape hatch: allow: ("routerQuery" | "useParams" | "useSearchParams")[] switches a surface off wholesale; one-off legitimate reads (e.g. untyped utm_* forwarding) use a targeted disable comment, blessed in the docs.
  • Never fires on same-named hooks from other modules (react-router), type-only imports, shadowed locals, or App Router routers (next/navigation's router has no .query).

no-href-arithmetic (LR3)

  • Flags content appended after an href() result in + chains (flattened, reported once at the top) and template literals. Prefix-only concatenation — origin + href(route), the legitimate absolute-URL pattern — never fires.
  • Three messages: generic, hash suffix, and ? suffix (which teaches the search option, since the appended query needs a codec key — not autofixable per design-15 LP6).
  • The plugin's first autofix: href(route) + "#top" → href(route, { hash: "top" }), applied only when the expression is exactly call-plus-literal and the options provably carry no hash (bail-outs for spreads, computed keys, non-object options, trailing commas, and comments the rewrite would delete).
  • Roadmap note: the .href() method-form concern is moot — core has no route.href() method; href() is standalone, which is what makes scope-resolved detection precise.

Supporting changes

  • Shared scope-resolution helpers (getImportBinding, resolveDef, generalized getImportedCallee/isImportedCall, getStaticPath, createRule/DOCS_URL) extracted to src/utils/; no-raw-hrefs refactored onto them with zero behavior change (its 44 tests pass untouched).
  • no-raw-hrefs message wording fixed: "the route's href()" → href(route, …).
  • Both new rules registered in configs.recommended at warn (LP7); docs page gains two template-following sections (anchors match rule names for the RuleCreator URLs); README and package description updated; minor changeset added.

Test plan

  • pnpm test packages/eslint-plugin — 110 tests (44 existing unchanged + 30 + 36 new, including autofix output assertions)
  • Full repo suite — 57 files, 1251 passed
  • pnpm lint, pnpm typecheck, pnpm format:check
  • pnpm build:packages + pnpm check:publish

🤖 Generated with Claude Code

Completes the bypass audit in both directions (design-20 LR1 + LR3):

- no-raw-param-reads: flags useSearchParams()/useParams() from
  next/navigation and router.query from next/router's useRouter()
  (variable, direct-call, and destructured forms), nudging the typed
  hooks from @paramour-js/next/app and /pages. Escape hatch: an allow
  option naming surfaces; per-site exemptions use disable comments.
- no-href-arithmetic: flags content appended after an href() result in
  + chains and template literals; prefix-only concatenation (origin +
  href(route)) stays legal. The pure hash case is the plugin's first
  autofix: href(route) + "#top" rewrites to href(route, { hash: "top" })
  when the options provably carry no hash.
- Shared scope-resolution helpers extracted to src/utils/ and
  no-raw-hrefs refactored onto them with no behavior change.
- no-raw-hrefs messages now say href(route, ...) - there is no
  route.href() method to point at.
- Both new rules ship in configs.recommended at warn per design-15 LP7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
paramour-docs Ready Ready Preview Aug 12, 2026 2:17am

@JasonPaff
JasonPaff merged commit 622bb4d into main Aug 12, 2026
13 checks passed
@JasonPaff
JasonPaff deleted the eslint-update branch August 12, 2026 02:21
@github-actions github-actions Bot mentioned this pull request Aug 12, 2026

This branch was successfully deployed

1 active deployment
Preview — 55fc051d Deployed Aug 12, 2026 by vercel[bot]
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