183 lines
9.5 KiB
Markdown
183 lines
9.5 KiB
Markdown
# AST Graph Schema — Nodes & Edges per Parser
|
|
|
|
Visualizes the node types and edges each parser actually emits, verified against
|
|
the `edge(...)` call sites in `JavaParser` and `NaturalParser` (not the documented
|
|
schema — only what is produced today).
|
|
|
|
`CALLS` edges carry a language-aware `callKind` property (see the `CallKind` enum in
|
|
`ac-parser-core`), surfaced as `edgeKind` by the `callers`/`callees` queries.
|
|
|
|
## Neo4j labels (item 111a, 2026-08-05)
|
|
|
|
Every node carries **two** labels: `AstNode`, plus its `NodeType` name — `:MODULE`, `:FUNCTION`,
|
|
`:VARIABLE`, `:DATA_STRUCTURE`, `:DB_TABLE`, `:CONSTANT`, `:FIELD`, `:DB_ACCESS`, `:WORKFILE`,
|
|
`:WORKFILE_ACCESS`, `:CONTROL_FLOW`, `:PAYLOAD_FIELD`. Written by both persist paths and backfilled
|
|
onto pre-existing nodes at startup.
|
|
|
|
The type is **also still a `type` property**, and every query currently reads that property. The
|
|
label is the migration target, not yet the source of truth: filtering on `type` out of the property
|
|
store cost ~935k of 1.6M dbHits (58%) in a profiled `upms` traversal, because the property sits in
|
|
an 11-property chain, whereas a label lives in the node record. Query sites move to `:LABEL` one at
|
|
a time under measurement (roadmap 111b); the property is dropped only once none read it (111c).
|
|
|
|
**Generation stamps.** Nodes and, since item 198, every parser-emitted edge carry `ingestGen`,
|
|
the persist run that last wrote them (a re-emitted edge is re-stamped through its MERGE key). A
|
|
deep refresh deletes a re-parsed file's edges whose stamp is older than the run's; edges the
|
|
finalize builds (resolved counterparts, links) carry no stamp unless copied from a parser edge.
|
|
|
|
Writing your own Cypher: prefer `MATCH (n:MODULE {project: $p})` over
|
|
`MATCH (n:AstNode {type: 'MODULE', project: $p})` — both are correct today, the first is cheaper.
|
|
|
|
## Java — `JavaParser`
|
|
|
|
```mermaid
|
|
graph LR
|
|
M[MODULE<br/>class / interface]
|
|
F[FUNCTION<br/>method / constructor]
|
|
P[VARIABLE<br/>parameter]
|
|
FD[FIELD<br/>instance attribute]
|
|
C[CONSTANT<br/>static final]
|
|
T[DB_TABLE]
|
|
A[DB_ACCESS<br/>JPA/Panache call]
|
|
M -->|CONTAINS| F
|
|
M -->|CONTAINS| FD
|
|
M -->|CONTAINS| C
|
|
F -->|CONTAINS| P
|
|
F -->|" CALLS — callKind=METHOD_CALL<br/>(intra-class foo) "| F
|
|
M -->|" CALLS — callKind=METHOD_CALL<br/>(cross-class other.foo) "| M
|
|
M -->|" CALLS — callKind=CONSTRUCTOR<br/>(new Foo) "| M
|
|
F -->|READS / WRITES| FD
|
|
M -->|EXTENDS| M
|
|
M -->|IMPLEMENTS| M
|
|
M -->|" IMPLEMENTED_BY (interface→impl) "| M
|
|
M -->|" INJECTS (@Inject / ctor) "| M
|
|
M -->|" REFERENCES (X.class arg) "| M
|
|
M -->|" MAPS_TO (JPA @Entity) "| T
|
|
F -->|" READS / WRITES (J1) "| T
|
|
F -->|CONTAINS| A
|
|
A -->|USES_TYPE| T
|
|
```
|
|
|
|
- Same-class calls stay function→function; cross-class calls and `new` are
|
|
class→class (`MODULE→MODULE`).
|
|
- `EXTENDS` / `IMPLEMENTS` / `MAPS_TO` are Java-only. `INJECTS` (CDI injection) and
|
|
`REFERENCES` (`X.class` argument) are Java-only wiring edges (item J2), surfaced in
|
|
`callers`/`callees` as `edgeKind`.
|
|
- `IMPLEMENTED_BY` (item J3) is the derived inverse of `IMPLEMENTS` (interface → concrete
|
|
impl), built in enrichment; it drives the `?resolveInterfaces=true` hop on
|
|
`callees`/`call-tree`. Java MODULE nodes carry an `isInterface` property.
|
|
- `OVERRIDDEN_BY` (item J4) links a base-class method `FUNCTION` to the same-named
|
|
overriding `FUNCTION` in a subclass (virtual dispatch), built in enrichment; exposed via
|
|
`GET /modules/{name}/functions/{fn}/overrides`.
|
|
- DB access (item J1): a JPA/Panache repository/`EntityManager`/active-record call
|
|
becomes a `DB_ACCESS` node whose entity resolves to the `DB_TABLE`, materializing
|
|
`FUNCTION -READS/WRITES-> DB_TABLE` (for `db-accesses`) and
|
|
`DB_ACCESS -USES_TYPE-> DB_TABLE` (for `sql-statements`) — the same shape Natural uses.
|
|
- Java emits no `DATA_STRUCTURE` edges.
|
|
|
|
## TypeScript / React — `TypeScriptParser` (item 192)
|
|
|
|
```mermaid
|
|
graph LR
|
|
M[MODULE<br/>.ts / .tsx file — name = root-relative path without extension]
|
|
C[MODULE<br/>.css file — name keeps .css]
|
|
F[FUNCTION<br/>function / component / hook / thunk / styled / class]
|
|
D[DATA_STRUCTURE<br/>interface / type / enum]
|
|
M -->|CONTAINS|F
|
|
M -->|CONTAINS|D
|
|
M -->|" REFERENCES (import; value = clause, specifier) "|M
|
|
F -->|" CALLS — same file, callSyntax=call|new|tagged|jsx "|F
|
|
M -->|" CALLS — cross-module, callKind=METHOD_CALL|CONSTRUCTOR,<br/>callerFn, calleeMethod, receiver, member "|M
|
|
```
|
|
|
|
- Module properties: `simpleName`, `workspace`, `moduleKind` (`ts`/`tsx`/`css`), `generated`,
|
|
`generator`, `externalImports` (npm packages, comma list — never placeholders), `sourceHash`,
|
|
`loc`/`sloc`, `ingestTier=2` after a sidecar pass.
|
|
- `FUNCTION.kind` and `exported`; `DATA_STRUCTURE.dataType` = `interface`/`type`/`enum`.
|
|
- Tier-1 (regex, Java) emits the shells and import placeholders; Tier-2 (Node sidecar on the
|
|
TypeScript compiler API) replaces declarations/imports from the checker and adds the `CALLS`
|
|
edges, shaped exactly like the Java parser's so every call-graph query and the placeholder
|
|
rewiring apply unchanged. A `slice`/`const` declaration is not a node in 192 (item 194).
|
|
- **Item 193:** a generated web-service call is a `FUNCTION` with `kind=endpoint`, `outbound=true`,
|
|
`httpMethod`, `restPath` (base-less, like a Java handler's composed `@Path`), `restBase`, `restUrl`,
|
|
`backend`, `queryParams`, `requestType`, `responseType`, `generator`, `owner`, `member`; an
|
|
interface's properties are `FIELD`s under its `DATA_STRUCTURE` (`dataType` = the TS type,
|
|
`optional`). A member call on an Endpoint instance carries `calleeMethod = <Class>.<member>`.
|
|
- **`COUNTERPART_OF`** (item 193, cross-project, built by enrichment, never by a parser):
|
|
endpoint `FUNCTION` → handler `FUNCTION` in a counterpart project (`via=rest`),
|
|
generated `DATA_STRUCTURE` → Java `MODULE` of the same simple name (`via=dto`),
|
|
`FIELD` → `FIELD` by name (`via=field`). Rebuilt on every refresh of either project; the
|
|
project node carries the `counterparts` list.
|
|
- **Item 194 (Redux store):**
|
|
|
|
```mermaid
|
|
graph LR
|
|
M[MODULE<br/>slice file]
|
|
S[STORE_SLICE<br/>name = reducer key, sliceName, stateType, store=true]
|
|
SF[FIELD<br/>name = key.field, dataType, optional, store=true, slice, field]
|
|
R[FUNCTION<br/>kind=reducer, reducerKind=reducer|case|matcher|default,<br/>name = action type, slice, trigger, actionType]
|
|
T[FUNCTION<br/>kind=thunk]
|
|
C[FUNCTION<br/>component / hook — any file]
|
|
M -->|CONTAINS|S
|
|
M -->|CONTAINS| R
|
|
S -->|CONTAINS|SF
|
|
R -->|" READS / WRITES — path, lineNo, via=reducer "|SF
|
|
R -->|" READS / WRITES — whole state "|S
|
|
T -->|" CALLS — callSyntax=extraReducer, actionType "|R
|
|
C -->|" READS — path, lineNo, via=useAppSelector|wrapper hook|getState "|SF
|
|
```
|
|
|
|
A consumer's read is emitted against a placeholder `<key>.<field>` (`sourceFile=""`, `store=true`)
|
|
and redirected by the finalize step `resolve-store-placeholder` onto the real field of the same
|
|
type and name (unique by construction), which then drops the placeholder. A dispatched action
|
|
creator is a cross-module `CALLS` edge with `actionType` and `calleeMethod` = the reducer's name.
|
|
|
|
- **Item 195 (DTO field bindings):** `FUNCTION`(component/hook) `-READS->` / `-WRITES->` the generated
|
|
interface's `FIELD` (`Broker` → `ebene`), edge props `path` (`broker.ebene`), `rootDto`, `kind`
|
|
(`field`/`prefix`), `partial`, `component`, `attribute`, `via=binding`, `lineNo`. Cross-file targets
|
|
are `FIELD` placeholders `<Dto>.<field>` with `binding=true`, `owner`, `field`, `targetModule`,
|
|
resolved exactly (module → structure → field) by `resolve-binding-placeholder` and then dropped.
|
|
- **Item 196 (styling):** the theme file's `DATA_STRUCTURE theme` (`kind=theme`) `-CONTAINS->`
|
|
`FIELD theme.<token>` (`theme=true`, `token`, `tokenKind=path|constant`, `value`, `constant`);
|
|
`FUNCTION`(component) `-CONTAINS->` `STYLE` (`styleKind=sx|style|styled`, `element`, `properties`,
|
|
`literals`, `dynamic`, `spread`; a CSS `MODULE -CONTAINS-> STYLE` per rule with `styleKind=css`,
|
|
`selector`); `STYLE -REFERENCES-> FIELD theme.<token>` (`via=theme`, `property`) and
|
|
`FUNCTION -REFERENCES-> FIELD theme.<token>` (`via=theme`, `context`). Cross-file token targets are
|
|
placeholders resolved by exact name by `resolve-theme-placeholder`; an undeclared token keeps its
|
|
placeholder (`declared=false` in `GET /theme`).
|
|
- No `DB_*` for TypeScript.
|
|
|
|
## Natural — `NaturalParser`
|
|
|
|
```mermaid
|
|
graph LR
|
|
M[MODULE<br/>source file]
|
|
F[FUNCTION<br/>subroutine]
|
|
V[VARIABLE]
|
|
DS[DATA_STRUCTURE<br/>DEFINE DATA group]
|
|
CF[CONTROL_FLOW<br/>IF / FOR / REPEAT / DECIDE]
|
|
A[DB_ACCESS<br/>READ/FIND/STORE/SELECT...]
|
|
T[DB_TABLE<br/>ADABAS view / SQL table]
|
|
M -->|CONTAINS| F
|
|
M -->|CONTAINS| DS
|
|
DS -->|CONTAINS| V
|
|
M -->|" INCLUDES (copycode / PDA) "| DS
|
|
F -->|CONTAINS| CF
|
|
F -->|" CALLS — callKind=PERFORM "| F
|
|
M -->|" CALLS — callKind=CALLNAT "| M
|
|
F -->|READS / WRITES| V
|
|
F -->|READS / WRITES| T
|
|
F -->|CONTAINS| A
|
|
A -->|USES_TYPE| T
|
|
A -->|USES_TYPE| DS
|
|
V -->|USES_TYPE| T
|
|
```
|
|
|
|
- `PERFORM` is function→function; `CALLNAT` is module→module.
|
|
- DB access is modeled two ways: direct `FUNCTION -READS/WRITES-> DB_TABLE`
|
|
(e.g. `READ`/`STORE`) and a `DB_ACCESS` statement node for SQL/ADABAS
|
|
statements (`FUNCTION -CONTAINS-> DB_ACCESS -USES_TYPE-> DB_TABLE` / view
|
|
`DATA_STRUCTURE`).
|
|
- `CONTROL_FLOW` blocks are contained by their enclosing function (or module).
|
|
- `INCLUDES` / `CONTROL_FLOW` are Natural-only. `DB_ACCESS` is now emitted by both
|
|
parsers (Natural SQL/ADABAS statements; Java JPA/Panache calls — item J1). |