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
The analyzer emits the legacy v1 tree (combinedJsonObject.add("symbol_table", …), CodeAnalyzer.java) with JTypeis_* booleans, per-callable code, and flat start_line/end_line. Canonical schema v2 requires an additive CPG tree: an envelope, per-module source blob, byte-offset spans, can:// ids, structured decorators, nested metrics/refs, and bodycall nodes.
Scope boundary
L1 tree emission only — no call_graph edges (that is the L2 issue), no dataflow. Keep the JavaParser compute guts; rewrite only the emission layer. Both emitters remain selectable during the transition so each can serve as an oracle for the other. Pure structural emission — no analysis-engine changes.
module{id, kind:"module", span, source, package, imports, types, content_hash}; byte offsets on every span — functions is omitted: Java has no module-level callables, and absence encodes "no fact"
type.kind ∈ {class,interface,enum,record,annotation}; base_types/interfaces; structured decorators:[{name,args,span}] (D2) — the nesting{parent?,is_local?} object was dropped: nesting is encoded by containment plus the id path (D4, refined)
bodycall nodes {kind:"call", span, arguments:[local-id]}, keyed by bare local id — callee is absent at L1 rather than null, per the no-nulls convention, and appears when L2 resolves the site
can://java/<app>/<file>/<type>/<sig> id construction from signatureOf() (D8)
Both emitters selectable: --schema v2 opts into the canonical shape; v1 remains the default until the rest of the migration lands, so existing consumers are unaffected
Added during implementation (beyond the original goals)
Library type resolution: dependency jars are put on the symbol solver's path so third-party types resolve to qualified names (v2 was strictly worse than v1 without this); reflection restricted to the JRE so the analyzer's own dependencies cannot be resolved as the project's
Anonymous inner classes modelled as type nodes, both in callable bodies and in field initializers
callable.body_span so source[body_span.bytes] reproduces v1's per-callable code byte for byte
v1 parity for comments, local variables, enum constants, record components, initializer blocks (as kind:"initializer" callables), field initializers, parameter modifiers, declaration, code_start_line, is_implicit, entrypoint flags
Real-world fixture applications added as git submodules
Caveats and known risks
Span byte offsets depend on JavaParser token positions; degrade gracefully where absent.
symbol_table keys must be relative (never absolute or ..-prefixed).
Callable kinds: method, constructor, initializer (static/instance init blocks); lambda bodies stay part of their enclosing callable. is_variadic for varargs.
Definition of done
L1 gate: symbol_table non-empty with relative keys; a known module has source and the get_method_body slice matches; call nodes carry no resolved callee; can:// ids stable across two runs.
Output validates against a strict in-repo JSON Schema (src/test/resources/schema/analysis.v2.schema.json) rather than the SDK models, which do not exist for v2 yet — see the oracle decision in this issue's comments. Gate runs over in-repo fixtures on every change and over four real-world applications via ./gradlew realWorldConformanceTest.
Output is byte-identical across two runs on unchanged source.
Cache reuse on re-run — -c/--cache-dir writes analysis_cache.json; modules whose files still hash to the same content_hash are reused, skipping the parse as well as the build (commons-lang: 130s cold, 4s warm). --eager forces a rebuild. Caching is opt-in.
-j N byte-identical to -j 1 — not applicable yet: no parallel fan-out is implemented for v2, so the flag has nothing to vary. Ids are assigned from sorted source positions, and file/type/callable ordering is sorted throughout, specifically so parallelising later stays deterministic. Not work remaining in this issue.
Plan (optional)
Design spec: https://github.com/codellm-devkit/codeanalyzer-java/blob/main/docs/design/specs/schema-v2-l3-l4-design.md
Problem
The analyzer emits the legacy v1 tree (
combinedJsonObject.add("symbol_table", …),CodeAnalyzer.java) withJTypeis_*booleans, per-callablecode, and flatstart_line/end_line. Canonical schema v2 requires an additive CPG tree: an envelope, per-modulesourceblob, byte-offset spans,can://ids, structured decorators, nested metrics/refs, andbodycallnodes.Scope boundary
L1 tree emission only — no
call_graphedges (that is the L2 issue), no dataflow. Keep the JavaParser compute guts; rewrite only the emission layer. Both emitters remain selectable during the transition so each can serve as an oracle for the other. Pure structural emission — no analysis-engine changes.Goals
{schema_version:"2.0.0", language:"java", max_level, analyzer{name,version}, application{id,kind,symbol_table}}module{id, kind:"module", span, source, package, imports, types, content_hash}; byte offsets on every span —functionsis omitted: Java has no module-level callables, and absence encodes "no fact"type.kind ∈ {class,interface,enum,record,annotation};base_types/interfaces; structureddecorators:[{name,args,span}](D2) — thenesting{parent?,is_local?}object was dropped: nesting is encoded by containment plus the id path (D4, refined)callable{id, kind, signature, span, parameters, return_type, error_channel, modifiers, decorators, metrics{cyclomatic}, refs{types,fields}, body{}}(D1, D3);thrown_exceptions → error_channelbodycallnodes{kind:"call", span, arguments:[local-id]}, keyed by bare local id —calleeis absent at L1 rather thannull, per the no-nulls convention, and appears when L2 resolves the sitecan://java/<app>/<file>/<type>/<sig>id construction fromsignatureOf()(D8)--schema v2opts into the canonical shape; v1 remains the default until the rest of the migration lands, so existing consumers are unaffectedAdded during implementation (beyond the original goals)
typenodes, both in callable bodies and in field initializerscallable.body_spansosource[body_span.bytes]reproduces v1's per-callablecodebyte for bytekind:"initializer"callables), field initializers, parameter modifiers,declaration,code_start_line,is_implicit, entrypoint flagscallnodes (receiver expression/type, argument types/expressions, callee signature, static/constructor flags)Caveats and known risks
symbol_tablekeys must be relative (never absolute or..-prefixed).method,constructor,initializer(static/instance init blocks);lambdabodies stay part of their enclosing callable.is_variadicfor varargs.Definition of done
symbol_tablenon-empty with relative keys; a knownmodulehassourceand theget_method_bodyslice matches;callnodes carry no resolvedcallee;can://ids stable across two runs.src/test/resources/schema/analysis.v2.schema.json) rather than the SDK models, which do not exist for v2 yet — see the oracle decision in this issue's comments. Gate runs over in-repo fixtures on every change and over four real-world applications via./gradlew realWorldConformanceTest.-c/--cache-dirwritesanalysis_cache.json; modules whose files still hash to the samecontent_hashare reused, skipping the parse as well as the build (commons-lang: 130s cold, 4s warm).--eagerforces a rebuild. Caching is opt-in.L1 ⊆ L2— blocked: cannot be exercised until L2 exists (codeanalyzer-java: L2 v2 call_graph (identity edges + external_symbols) #181); the check is vacuous with only L1 emitted. Not work remaining in this issue.-j Nbyte-identical to-j 1— not applicable yet: no parallel fan-out is implemented for v2, so the flag has nothing to vary. Ids are assigned from sorted source positions, and file/type/callable ordering is sorted throughout, specifically so parallelising later stays deterministic. Not work remaining in this issue.