You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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-90has no rule for.jsp,.jspx,.jspf,.tag,.xhtmlor Thymeleaf.html, so every view is inventoried with its fullsourcebutroles: ["unknown"];faces-config.xmlfalls through to the generic*.xmlrow asunknowntoo. Nothing connects a servlet or controller to the page it renders:getRequestDispatcher("/x.jsp").forward(req, res)is twocallbody nodes whoseargument_expr/receiver_exprcarry the path as text, and the SDG stops there.returnbody nodes carryargument_expr: [], so a Spring controller'sreturn "home"is invisible even to a literal tier. On DayTrader (3.2.0): 23.jsp, 15.xhtml, 20.htmlallunknown; 22getRequestDispatcher+ 1sendRedirectsites, 5 with literal targets, 17 with variables; 33 distinct.jspstrings in the graph, none as an edge.This issue adds the
view-templaterole and its rules, populatesargument_expronreturnnodes, and emitsJ_DISPATCHES_TOfrom the dispatching body node to theArtifact, withapplication.view_dispatches[]/view_dispatches_unresolved[]inanalysis.json— the same level-graded, never-guessing shape asconfig_uses.Scope boundary
Role, return expressions, and the dispatch edge — nothing inside a template. Not
<%= %>/${…}/th:*/<jsp:include>(roadmap Group F). Notweb.xmlservlet mappings, welcome files,*.facesor@RequestMappingroutes (Group E): a dispatch whose literal target is a URL rather than a file is recorded unresolved withreason: "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, noschema_versionbump. The python-sdk surface is a follow-up PR, listed under Definition of done.Goals
ArtifactDiscoveryrules, inserted before the generic*.xmlrow:*.jsp *.jspx *.jspf *.tag *.tagx→format: jsp;*.xhtml→xhtml;*/templates/*.html,*/WEB-INF/*.html→html; allroles: ["view-template"].faces-config.xml→xml,["tool-config"]. Bare*.htmlelsewhere staysunknown.returnbody nodes carryargument_expr: ["<expression source>"]when a value is returned,[]for a barereturn.RequestDispatcher.forward/.include(target read from the producinggetRequestDispatcher/getNamedDispatchercall's literal argument),HttpServletResponse.sendRedirect,new ModelAndView(…),ModelAndView.setViewName, andreturnin a Spring-entrypoint callable returningString/ModelAndView: resolve per spec § 4.4 (path targets by unique repo-path suffix; view names viaspring.mvc.view.*/spring.thymeleaf.*config keys with Thymeleaf defaults;redirect:/forward:prefixes re-dispatch) and emitview_dispatches[] {src, dst, via, prov: ["literal"]}.view_dispatches_unresolved[] {site, callee, target, via, reason ∈ non-literal | no-such-artifact | ambiguous, prov}; both lists absent, not empty, when nothing was produced.prov: ["literal", "dataflow"], mirroringconfig_uses' tier ladder.V2SchemaCatalog:J_DISPATCHES_TOfrom[JBodyNode]to[Artifact]with{via: string, prov: string[]}, MERGE on the endpoint pair;V2GraphProjectoremits it;schema.neo4j.jsonregenerated.view_dispatcheslikeconfig_uses.plantsbywebspherefixture (has.jspand.xhtmlundersrc/main/webapp) drives the tests; add a minimal Spring controller +templates/home.htmlfixture for the view-name path.Caveats and known risks
argument_expronreturnis a population change to an existing field, so no consumer's schema breaks; a consumer that assumedreturnnodes carry nothing will now see one string.prunestill drops the empty list.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 isambiguous/no-such-artifact— never a best guess.*/templates/*.htmlrelies onglobMatches'*crossing/; a Thymeleaf project with templates elsewhere staysunknown(spec § 9.3), documented rather than widened.no-such-artifactsites (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
plantsbywebsphere, the artifact set withrolescontainingview-templateequals the hand-listed set of its.jspand.xhtmlfiles; no.javaand no.htmloutsidetemplates//WEB-INF/is in it;faces-config.xmlistool-config.getRequestDispatcher("/x.jsp").forward(req, res), one withinclude, onesendRedirect("/y.jsp"), and one Spring controllerreturn "home"withtemplates/home.htmlyield exactly fourview_dispatchesentries withvia=forward,include,redirect,view-nameandprov: ["literal"], whosedstids equal the artifacts' ids.getRequestDispatcher("/servlet/Other").forward(…)andgetRequestDispatcher(page).forward(…)(page a parameter) yields exactly twoview_dispatches_unresolvedentries, reasonsno-such-artifactandnon-literal, and at L3 withString page = "/x.jsp"as the only definition the second becomes aview_dispatchesentry withprov: ["literal", "dataflow"].returnbody node with a value in the fixture hasargument_exprof length 1 whose text equals the returned expression's source; bare returns have none.--emit neo4jon the fixture produces oneJ_DISPATCHES_TOper resolved entry and none for unresolved ones; the parity test assertsanalysis.jsonand the graph agree on the set.schema.neo4j.jsondiff shows exactly the new relationship and nothing else;schema_versionunchanged at 2.0.0.JViewDispatch/JViewDispatchUnresolved,get_view_dispatches/get_view_dispatchers, Neo4j reconstruct) filed against the released wheel.