Skip to content

feat(entrypoints): Remix, Astro and Next.js src/ layout rules (#206) - #207

Merged
rahlk merged 1 commit into
mainfrom
feat/issue-206-jsts-framework-entrypoints
Sep 11, 2026
Merged

rahlk merged 1 commit into
mainfrom
feat/issue-206-jsts-framework-entrypoints

Conversation

@rahlk

@rahlk rahlk commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Closes #206.

Four framework rows the shipped ruleset was missing, and the two resolver gaps writing the tests for them exposed.

Rules — src/entrypoints/rules.yml

Framework Detect Rules
remix any @remix-run/*, or react-router alone (v7 absorbed Remix) remix.loader, remix.action, remix.route-component over app/routes/**/*.{ts,tsx,js,jsx}
astro astro astro.api-route over src/pages/**/*.{ts,js}, verb exports plus ALL
nextjs next adds nextjs.app-route-src, nextjs.pages-api-src for the src/ layout

One Remix rule per export rather than one rule listing all three: the rule id is then what tells a consumer whether it is looking at a data loader, a mutation, or the rendered component. http_methods stays empty for Remix — mapping loader to GET and action to the mutating verbs needs a per-export method source the rule grammar does not have, and inventing one here would coin a spelling no sibling analyzer has agreed to.

The two nextjs rows are separate rules, not a loosened glob. globToRegExp anchors a pattern containing / at the repo root by design, and a convention path has to match exactly rather than anywhere in the tree — test/entrypoints-files.test.ts locks that in (globToRegExp("app/**/route.{ts,js}").test("src/app/route.ts") === false) and this PR does not touch it.

Resolver fixes — src/entrypoints/matching.ts

  • methodsOf, export_name branch now filters through HTTP_VERBS, exactly as match_suffix already did. Astro's ALL is a real handler but not an HTTP method, and http_methods is a field consumers filter on.
  • routeFromFileKey strips the glob's leading layout directories — app/, pages/, Remix's app/routes/, and an optional src/ in front of any of them. Whatever literal prefix survives is a genuine route prefix, so pages/api/ still serves at /api. A glob with no literal prefix is untouched, so **/+server.{ts,js} still yields the whole key.
  • resolveHandler's INLINE branch no longer gates on name === "(anonymous)". app.get("/x", function named(req, res) {}) is a named function expression: it passed the position test, failed the name test, resolved nothing, and the site was counted unresolved. Position alone identifies it, and the sort takes the outermost callable in the span, so a callable nested inside the handler is never picked instead.
  • resolveDefaultExport handles export default <wrapper>(<handler>) — Nitro/Nuxt's defineEventHandler(h), and every withSentry(h)-shaped wrapper, which displace the callable past the export default token so the exact-offset match misses it. The gap between token and callable must be nothing but call openings, which is what keeps export default defineHandler({onRequest: fn}) counted in unresolved instead of claiming an unrelated later callable in the file.

No contract move

No new field, node label, edge type or property. TSEntrypoint and the Neo4j schema are untouched; the change adds values to existing strings, the same class of change a user's own --entrypoint-rules file makes. bun run gen:schema leaves schema.neo4j.json byte-identical.

Tests

test/entrypoints-jsts-frameworks.test.ts, 11 tests: each rule id with its route/http_methods, Remix detected on react-router alone, ALL carrying no method, the src/ route-prefix unit cases (including the unchanged **/+server one), both resolver gaps, the nested-callable guard, and a three-framework project where the astro and nextjs globs overlap on one file but their exports: lists disagree — so the verb export is astro's and the default export is nextjs'.

Full suite: 369 pass, 8 skip, 0 fail. bun run typecheck clean. test/entrypoints-rules.test.ts's framework inventory assertion grew by the two new names.

Propagation

Propagation verdict: codeanalyzer-python (same http_methods junk-value bug class on its own match_suffix path — _methods_of returns [verb.upper()] unfiltered while _HTTP_VERBS sits in the same module, so the shipped heuristic.http-verb rule emits WEBSOCKET; filed as codellm-devkit/codeanalyzer-python#213), python-sdk (pin bump needed — pyproject.toml hard-pins codeanalyzer-typescript==1.5.3, so these rules do not reach an SDK user until this repo releases and that pin moves).

Checked and negative: no other sibling analyzer implements the file-convention or export_name tier at all (it is TS/JS-only, no python analog — python's entrypoint pass has no files: rules), so the routeFromFileKey and resolveDefaultExport fixes have no counterpart to port. Docs: docs/design/specs/entrypoint-detection.md already lists Remix under "file convention", so the spec is satisfied rather than made stale; the consumer skill's vocabulary reference names the fields, not the frameworks; no README or docs-repo page enumerates rule ids. No fixture in any sibling repo encodes the old behaviour — the only fixtures asserting these paths are this repo's, and both affected files are updated here.

Four framework rows the shipped ruleset was missing, plus the resolver gaps
they exposed. No schema change: no new field, node label, edge type or
property — only new values in `framework`/`rule`/`route`/`http_methods`,
the same class of change a user's own `--entrypoint-rules` file makes.

Rules (`src/entrypoints/rules.yml`):

- `remix` — detected on any `@remix-run/*` or on `react-router` alone, since
  v7 absorbed Remix. Three file rules over `app/routes/**`, one per export
  (`loader`, `action`, `default`), so the `rule` id tells a consumer whether
  it is looking at a data loader, a mutation or the rendered component.
  `http_methods` stays empty: mapping `loader` to GET needs a per-export
  method source the rule grammar does not have.
- `astro` — `astro.api-route` over `src/pages/**/*.{ts,js}`, verb exports plus
  `ALL`. `.astro` files are not parsed, so this covers the endpoints only.
- `nextjs` — `nextjs.app-route-src` and `nextjs.pages-api-src` for the `src/`
  layout Next.js supports officially. Separate rows rather than a loosened
  glob: a convention path must match exactly, not anywhere in the tree.

Resolver fixes (`src/entrypoints/matching.ts`):

- `methodsOf` filters `export_name` through `HTTP_VERBS`, as `match_suffix`
  already did. Astro's `ALL` is a real handler but not an HTTP method, and
  `http_methods` is a field consumers filter on.
- `routeFromFileKey` strips the glob's leading LAYOUT directories — `app/`,
  `pages/`, Remix's `app/routes/`, and an optional `src/` in front of any of
  them. Whatever literal prefix survives is a route prefix, so `pages/api/`
  still serves at `/api`. A glob with no literal prefix is untouched.
- `resolveHandler`'s INLINE branch no longer gates on `name ===
  "(anonymous)"`: `app.get("/x", function named(req, res) {})` is a named
  function expression, and gating on the name resolved nothing while counting
  the site unresolved. Position identifies it; the outermost callable in the
  span wins, so a callable nested inside the handler is never picked.
- `resolveDefaultExport` handles `export default <wrapper>(<handler>)` —
  Nitro/Nuxt's `defineEventHandler(h)` and every `withSentry(h)` shape. The
  gap between token and callable must be nothing but call openings, which is
  what keeps `defineHandler({onRequest: fn})` counted unresolved instead of
  claiming an unrelated later callable.

Tests: `test/entrypoints-jsts-frameworks.test.ts` (11 tests) covers each rule
id, the three-framework project where astro and nextjs globs overlap but
their `exports:` lists disagree, both resolver gaps, and the guard that a
wrapper whose handler is not a direct argument stays unresolved.
@rahlk
rahlk merged commit 5a18364 into main Sep 11, 2026
1 check passed
@rahlk
rahlk deleted the feat/issue-206-jsts-framework-entrypoints branch September 11, 2026 15:48
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.

Entrypoint rules for JS/TS-only frameworks: Remix, Astro, Next.js src/ layout, and four resolver gaps

1 participant