Skip to content

feat(artifacts): view-template role and J_DISPATCHES_TO from dispatch sites to the Artifact they reach #259

Description

@rahlk

Plan (optional)

Spec: codellm-devkit/.github docs/design/specs/2026-09-11-java-view-templates-and-dispatch.md (PR #80, Group D of the 2026-09-11 roadmap pass).

Problem

A Java web application's view layer is in the graph as bytes and absent as structure. ArtifactDiscovery.java:48-90 has no rule for .jsp, .jspx, .jspf, .tag, .xhtml or Thymeleaf .html, so every view is inventoried with its full source but roles: ["unknown"]; faces-config.xml falls through to the generic *.xml row as unknown too. Nothing connects a servlet or controller to the page it renders: getRequestDispatcher("/x.jsp").forward(req, res) is two call body nodes whose argument_expr / receiver_expr carry the path as text, and the SDG stops there. return body nodes carry argument_expr: [], so a Spring controller's return "home" is invisible even to a literal tier. On DayTrader (3.2.0): 23 .jsp, 15 .xhtml, 20 .html all unknown; 22 getRequestDispatcher + 1 sendRedirect sites, 5 with literal targets, 17 with variables; 33 distinct .jsp strings in the graph, none as an edge.

This issue adds the view-template role and its rules, populates argument_expr on return nodes, and emits J_DISPATCHES_TO from the dispatching body node to the Artifact, with application.view_dispatches[] / view_dispatches_unresolved[] in analysis.json — the same level-graded, never-guessing shape as config_uses.

Scope boundary

Role, return expressions, and the dispatch edge — nothing inside a template. Not <%= %> / ${…} / th:* / <jsp:include> (roadmap Group F). Not web.xml servlet mappings, welcome files, *.faces or @RequestMapping routes (Group E): a dispatch whose literal target is a URL rather than a file is recorded unresolved with reason: "no-such-artifact", not resolved. Not JSF outcome navigation: via: "navigation" is reserved by the spec and activates only when a JSF entrypoint finder exists (roadmap candidate 22). No new node kind, no schema_version bump. The python-sdk surface is a follow-up PR, listed under Definition of done.

Goals

  • ArtifactDiscovery rules, inserted before the generic *.xml row: *.jsp *.jspx *.jspf *.tag *.tagx → format: jsp; *.xhtml → xhtml; */templates/*.html, */WEB-INF/*.html → html; all roles: ["view-template"]. faces-config.xml → xml, ["tool-config"]. Bare *.html elsewhere stays unknown.
  • return body nodes carry argument_expr: ["<expression source>"] when a value is returned, [] for a bare return.
  • Literal tier (L1): for RequestDispatcher.forward / .include (target read from the producing getRequestDispatcher / getNamedDispatcher call's literal argument), HttpServletResponse.sendRedirect, new ModelAndView(…), ModelAndView.setViewName, and return in a Spring-entrypoint callable returning String / ModelAndView: resolve per spec § 4.4 (path targets by unique repo-path suffix; view names via spring.mvc.view.* / spring.thymeleaf.* config keys with Thymeleaf defaults; redirect: / forward: prefixes re-dispatch) and emit view_dispatches[] {src, dst, via, prov: ["literal"]}.
  • Unresolved sites emit view_dispatches_unresolved[] {site, callee, target, via, reason ∈ non-literal | no-such-artifact | ambiguous, prov}; both lists absent, not empty, when nothing was produced.
  • Dataflow tier (L3+): a non-literal target closed over reaching definitions on the DDG resolves with prov: ["literal", "dataflow"], mirroring config_uses' tier ladder.
  • V2SchemaCatalog: J_DISPATCHES_TO from [JBodyNode] to [Artifact] with {via: string, prov: string[]}, MERGE on the endpoint pair; V2GraphProjector emits it; schema.neo4j.json regenerated.
  • Projection-parity test covers view_dispatches like config_uses.
  • plantsbywebsphere fixture (has .jsp and .xhtml under src/main/webapp) drives the tests; add a minimal Spring controller + templates/home.html fixture for the view-name path.

Caveats and known risks

  • The literal tier alone reaches 3 of DayTrader's 23 JSPs; the remaining 17 dispatch sites take their target from a variable. The dataflow tier is therefore in scope here, not deferred — without it the feature demonstrates on fixtures and does little on real code.
  • argument_expr on return is a population change to an existing field, so no consumer's schema breaks; a consumer that assumed return nodes carry nothing will now see one string. prune still drops the empty list.
  • View-name resolution reads spring.* config keys that the config layer may hold as non-literal (${…} placeholders); then Thymeleaf defaults apply and, if still ambiguous or absent, the site is ambiguous / no-such-artifact — never a best guess.
  • */templates/*.html relies on globMatches' * crossing /; a Thymeleaf project with templates elsewhere stays unknown (spec § 9.3), documented rather than widened.
  • no-such-artifact sites (servlet URLs, *.faces) are JSON-only until Group E gives them a node; a Cypher-only consumer will not see them (spec § 9.1).

Definition of done

  • On plantsbywebsphere, the artifact set with roles containing view-template equals the hand-listed set of its .jsp and .xhtml files; no .java and no .html outside templates/ / WEB-INF/ is in it; faces-config.xml is tool-config.
  • A fixture servlet with getRequestDispatcher("/x.jsp").forward(req, res), one with include, one sendRedirect("/y.jsp"), and one Spring controller return "home" with templates/home.html yield exactly four view_dispatches entries with via = forward, include, redirect, view-name and prov: ["literal"], whose dst ids equal the artifacts' ids.
  • The same fixture with getRequestDispatcher("/servlet/Other").forward(…) and getRequestDispatcher(page).forward(…) (page a parameter) yields exactly two view_dispatches_unresolved entries, reasons no-such-artifact and non-literal, and at L3 with String page = "/x.jsp" as the only definition the second becomes a view_dispatches entry with prov: ["literal", "dataflow"].
  • Every return body node with a value in the fixture has argument_expr of length 1 whose text equals the returned expression's source; bare returns have none.
  • --emit neo4j on the fixture produces one J_DISPATCHES_TO per resolved entry and none for unresolved ones; the parity test asserts analysis.json and the graph agree on the set.
  • schema.neo4j.json diff shows exactly the new relationship and nothing else; schema_version unchanged at 2.0.0.
  • Released as codeanalyzer-java 3.3.0; python-sdk follow-up (models JViewDispatch / JViewDispatchUnresolved, get_view_dispatches / get_view_dispatchers, Neo4j reconstruct) filed against the released wheel.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions