Repository navigation
feat(code-analysis): add Java support - #1075
Conversation
Add java-parser 3.0.1 (Apache-2.0) for the Java adapter. It is pinned because the parser identity is recorded in raw facts and decides run comparability. It brings chevrotain and lodash transitively; no JVM or native binaries are needed.
Parse captured UTF-8 source with java-parser. Broken, non-UTF-8 and Unicode-escape files stay visible in parse_errors without partial facts; Java expands \uXXXX before lexing and the parser does not. Empty member records, which java-parser 3.0.1 reads as methods returning `record`, are restored as record declarations; no such method compiles. Methods, constructors, compact constructors, initializer blocks and lambda fields emit the shared functions contract with the existing complexity and nesting rules. Code outside methods is measured on its own, as module-level callbacks are in JS/TS. Names carry parameter types so overload ids do not depend on source order.
Map declared type names to files, independent of directory layout, and record an edge only when one declaration in one file matches. Covered: single-type and static imports (including explicitly and implicitly static member types), type positions, instance creation, constructor references and class literals, bound in Java's lexical order. Duplicate declarations and members of ambiguous enclosing types, shadowed names, types local to any block, wildcard imports, qualified creation, other method references and references inside bodies of types with supertypes stay unresolved instead of guessed. Unresolved counters keep the shared meaning: imports of packages no analyzed file declares are external, analyzed names that cannot be resolved are unknown, and references that cannot denote an analyzed type are not recorded.
Register Java language selection and default scan patterns while retaining Python/JS target-directory scans. Extend the shared raw-facts contract without changing the meanings of the unresolved counters. Report Java cycles as informational structural dependencies, with explicit class-initialization assessment notes in their evidence and detail views. Reuse the graph, rules and task pipeline with Java source labels. Preserve JS/Python complexity titles and display Java overload signatures once. Browser regressions cover callback/overload titles and both Java cycle detail views. Task fixtures change only by the default config digest.
Describe recorded imports and type references, conservative resolution guards, unresolved counters and dependency-record weights. Document complexity measurements, overload names and parser boundaries. Explain language selection in terms of the configured scan and its upgrade impact. Link Java guidance from the module architecture, README, usage guide and technical guide.
Build archify.zip from the tracked Skill sources using scripts/build-zip.sh with official Node 22.23.2 and bundled zlib 1.3.1-e00f703. Verify byte-identical repeated builds and package smoke after extraction on Windows.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review: 1
Please check the settings in the CodeRabbit UI or the
|
…types The body guard skipped every reference inside a type that extends or implements anything, because an inherited member type can shadow an imported or same-package name. Bodies now open when every supertype, transitively, resolves to one analyzed declaration. Supertype names are bound in the scope of their header, including member types inherited by enclosing types. A body name matching an analyzed inherited member type stays unresolved and counts as unknown, even over an external import; an external, duplicate or cyclic supertype keeps the body unresolved. Enum bodies leave EnumDesc, the member type every enum inherits from java.lang.Enum, unresolved. Scope fixes the body resolution depends on: - A member record whose body holds only member types is rewritten from the parser's method misreading, so those types are indexed. - Enum and record members are out of scope in their own implements clause; record components still see them. - Member types in scope shadow single static imports. - A qualified name never starts at a subpackage of the current package, and one whose first segment is a type imported on demand stays unresolved.
Directory depth 2 folded a standard single-module Maven tree into one src/main module, so module cycles and hubs could never fire, and merged nested Maven modules in multi-module trees. For Java, unless modules.depth or packageBoundaries: false is set, the nearest directory with pom.xml or build.gradle(.kts) owns a source at any depth. A tree where at most one build module owns analyzed sources is split by package within each src/<set>/java root instead. Files outside those, and files claimed by modules.groups, keep the existing rules. The chosen strategy is reported as grouping.java. Python and JS/TS grouping is unchanged.
…their language Adding **/*.java to the default scan made a JS/TS or Python tree that also holds Java sources, such as a React Native android/ folder, stop with extract/adapter-ambiguous unless --language was given. Java now leaves automatic selection to the other language unless the root holds a Maven or Gradle build or settings file, which keeps the ambiguity error for Java projects with a stray script. The left-out Java files are reported in coverage as before.
Java counts unresolved type references as well as imports, so the coverage line "N imports could not be resolved" overstated the imports. Java runs now say "dependency records"; other languages are unchanged.
|
Thanks for the substantial update. I reviewed 5a65da8 from both product/maintenance and technical perspectives. The Java grouping, inherited-type handling and preservation of existing mixed-repository defaults are meaningful improvements; the earlier blanket limitations on those points no longer apply. I also verified the new CI run 40081479090 against the labs/code-analysis integration, rather than reusing the old head's result. A few focused evidence/maintenance points would help before a directional decision:
The direction looks useful as conservative source-structure analysis, not full Java runtime or build-model analysis. The target remains labs/code-analysis; this review does not approve a merge or expansion into dev. Thanks for the clear limitations and continued work. |
|
Thanks for this — I ran Reproduced
Recall gap: same-package references under an external supertype When any type in a class's supertype chain is outside the analyzed sources, the body guard drops every reference that is not backed by an import — which includes all same-package references. This is the documented conservative behavior, but it hits the most common Spring/JPA shape: an in-repo base entity that In PetClinic, Rough size, using a heuristic (unresolved
So the module structure of the two libraries is barely affected, but domain-model code in typical application repos can lose its core relations. Two possible directions, either of which would be fine for this PR's scope:
Smaller notes
Runs and analysis above were done with Claude (Opus 5.5). Generated by Claude Code |
scripts/run-tests.mjs enumerates only the root test/ directory, so the module's own suite under archify/modules/code-analysis/test (including the Java name-resolution and grouping tests) never ran in CI. Run it once, in the first regression shard, and pin the step in the CI gate test.
A body whose supertype chain left the analyzed sources kept the inheritance guard, so every same-package reference in it went unrecorded. That hit the most common application shape, an entity base class that implements java.io.Serializable. A small table of JDK supertypes and the member types a subclass inherits from them, generated from JDK 17, now completes such chains. Only positive evidence counts: a single-type import or a fully qualified name. Implicit java.lang and on-demand bindings keep the guard, because an unanalyzed same-package source could declare a type of that name. Map's Entry stays unresolved.
Java modules are inferred from build-file locations and source directories, not from Maven or Gradle models, so adding a build module or moving sources can change module findings without any dependency change. Document this and how to pin modules.groups for longitudinal comparison. grouping.java is now omitted when explicit groups claim every nested file.
|
Thanks for the focused follow-up at 4fc2738. I verified the current integration run, including the explicit Code Analysis module step: 209 tests passed with no failures in https://github.com/tt-a1i/archify/actions/runs/40133358396/job/120989657855 . The missing module-CI coverage finding is closed. The new listed-JDK-supertype handling also narrows the previously reported Serializable case; I am not carrying forward that blanket limitation. The updated JAVA.md usefully distinguishes missing edges from absent coupling and explains stable explicit grouping for comparisons. Please align the PR's validation section with the current head and retain the already-requested javac harness/configuration/output links. I have not found a change to result-version/comparison handling, so please clarify the intended treatment of comparisons across the changed analysis semantics within this experimental branch. No broader implementation or benchmark expansion is requested. The target remains labs/code-analysis; this acknowledgment is not merge approval or approval to expand into dev. Thank you for addressing the concrete findings. |
…mports A fully qualified supertype such as java.io.Serializable is no longer treated as the JDK type: an unanalyzed type named like the first segment could obscure the package (JLS 6.4.2). Single-type imports outrank every competing binding, so they remain the only evidence accepted. The body-guard example in JAVA.md now uses a case that still applies: PetClinic's PetValidator implements Spring's Validator, so its use of Pet is not recorded, while Owner's use of Pet is now resolved.
|
Thanks both. Head is now
|
|
@CheeseMiao0520 thanks — re-checked at
On comparisons across the changed Java semantics: I'd suggest not handling it in this PR. The same gap exists for every language today — the extractor ids only track the external tool ( Runs and analysis above were done with Claude (Opus 5.5). Generated by Claude Code |
Problem and value
Add Java support to Code Analysis. On base
4553a4e, a Java-only repository stops withextract/adapter-undetected. It can now produce source dependencies, complexity findings, cycles, hubs, the analysis page and AI tasks through the existing pipeline, without a JDK or project build.This PR targets
labs/code-analysis, the module branch tracked by #998, which integrates Code Analysis intodev.AI assistance and accountability
Implementation and stability impact
java-parser@3.0.1. Resolve supported import and type-reference forms only when the source index identifies one unambiguous declaration; uncertain names stay unresolved and are counted. Unsupported files remain visible inparse_errors.extends/implementsresolve when every supertype, transitively, is one analyzed declaration or a listed JDK type named by a single-type import (import java.io.Serializable;). The list and each type's member types are inJAVA.md; of them onlyjava.util.Mapcontributes one (Entry), which stays unresolved. Implicitjava.langnames, on-demand imports and fully qualified supertypes keep the conservative guard, as do external, duplicate or cyclic supertypes. A name matching an inherited member type staysunknown.extends/implementsclause (record components see them), member types shadow single static imports, enum bodies leaveEnumDescunresolved, and qualified names never start at a subpackage. A parser misreading of member records whose body is also a valid block is rewritten into the record, nested types included.()and display Java signatures as-is.pom.xmlorbuild.gradle(.kts)owns a source at any depth. When at most one build module owns analyzed sources, modules are packages within eachsrc/<set>/javaroot.modules.groupsstill win;modules.depthormodules.packageBoundaries: falserestores directory-depth grouping. The strategy is reported asgrouping.java. Adding a build module or moving source roots can change grouping, and with it module cycles and hubs, without any dependency change;JAVA.mdgives an explicit-group configuration for longitudinal comparison. Python and JS/TS grouping is unchanged..javafiles keeps its language without--language, and coverage lists the Java files left out. A root withpom.xml,build.gradle(.kts)orsettings.gradle(.kts)beside JS/TS or Python sources requires--language, following the existing ambiguity policy.**/*.javaand exclude**/target/**/*.java. Python/JS files undertarget/retain their existing scan behavior. The changed default config digest makes the first comparison after upgrading reportconfig-changed.0.1.0-dev.1) and adapter id (java-parser@3.0.1) did not change while Java resolution and grouping did, socomparability()does not flag a Java result from an earlier revision of this PR. Base cannot produce Java results, so this affects only results produced from this PR's own earlier revisions. They are not intended as baselines; see the reply below for the options.npm --prefix archify run test:code-analysis), pinned by the compatibility gate.graphs/module.mjs,extract/index.mjs) and can be changed without touching the adapter.archify/modules/code-analysis/docs/JAVA.md.Tests run
Base:
4553a4e. Head:b21b90b.CI
Local (Windows 11, Node 24.15.0, Python through
BAUIFY_PYTHON, local Chrome; package build and smoke on Node 22.23.2). The contributor ran the three test commands. The package build, repository analyses and javac check were run with Claude Code. One JavaScript test unchanged by this PR ("function-scope import() and require are deferred" inimport-semantics.test.mjs) fails only when run in an interactive color terminal: its child process then prints ANSI color codes. It passes with piped output and in CI, and behaves the same at241bc52.node --test archify/modules/code-analysis/test/*.test.mjsnode --test test/core-smoke.test.mjs test/compatibility-gate.test.mjs test/start.test.mjsnode --test test/code-analysis-browser.test.mjs test/code-analysis-complexity-browser.test.mjsnode scripts/package-smoke.mjs <extracted-zip>/archifyHuman inspection
Human inspection by @CheeseMiao0520 at
b21b90b(Windows): opened the Code Analysis page for Spring PetClinic500158fwith a hand-written five-component IR and an explicit--map.0 not on this diagram), and the overview reported 0 findings.Owner → Pet), the coverage details, and perceptual design quality.Independent reproduction by @yijiez666-alt at
5a65da8(Linux, Node 22.23, Chrome): module suite 208/208,core-smoke19/19, browser files 5/5; Commons Collections numbers matched; Python/TS outputs byte-identical apart from the config digest. MyBatis 3 and Spring PetClinic also analyzed cleanly.Java repositories. Default scan/role configuration with
--language java:node archify/bin/archify.mjs code-analysis check <repo-root> --language java --out <out-dir> --fail-on never --json. Unique file edges count distinct resolved file pairs across all extracted roles; product edges are those between source-role files. Cycle and hub findings exclude test/generated roles. Source only: no Maven/Gradle build or project code was run.f1585880)892bdc8f)\uXXXX)externalunknowngrouping.java)build-modules)packages)Reading the unresolved counts. Java records every type reference, not only import declarations, so one dependency usually has several records.
externalis JDK and third-party imports.unknownis project names the conservative rules leave unresolved, mostly inside bodies whose supertype chain includes an external type.Compared with
241bc52(the first version of this PR), same inputs:unknownrecords: Dubbo 22,434 → 6,272, Commons Collections 5,362 → 1,877.javac differential check. A synthetic probe: generated programs full of name collisions, compiled by javac 17, with every type reference and import javac binds compared against the adapter's records. At this head, 2,051 programs (59,204 references, 56,185 imports) give 0 defects, while 4,461 references to analyzed files stay unresolved and are counted as
unknown.241bc52gives 1,113 wrong edges and 342 uncounted drops on the same programs; these programs are adversarial, so the counts are not a real-world error rate. Denominator, categories, seeds, discarded programs and limits are in the harness README.Python/JS. On
py-basic,py-tasks,ts-basic, the Code Analysis module and thearchify/package (both--language ts), raw facts, module graphs and review are byte-identical between base4553a4eand5a65da8. Findings differ only inanalysis.configDigest, and overview only in the listed exclude patterns (the added**/target/**/*.java). Commits after5a65da8change only Java resolution, the Java grouping default whenmodules.groupsclaim every nested file, documentation, CI and the package.test:full, the full Node compatibility matrix and macOS. Linux ran only in the reproduction above, at5a65da8.Visual evidence
Generated artifacts
archify.zip: built with official Node 22.23.2 / zlib1.3.1-e00f703; two builds are byte-identical. The extracted final archive passes package smoke outside the repository.test/fixtures/tasks/*: default config digest updates only.