15 KiB
ADR 0007: CodeCharta Export From the Finished Report
- Status: accepted
- Date: 2026-10-02
- PR: #13
Context
RFC 0001 asks for a CodeCharta cc.json 2.x file: packages as districts, classes as buildings, with LOC, complexity and Mutation-CRAP per class.
mutation-crap.json holds complexity and Mutation-CRAP per method, but no line numbers per method and no dependencies.
The RFC wants the export to be a step after the analysis, without a dependency on PIT's listener lifecycle, and mutation-crap.json to stay unchanged.
Two sources for the missing class data were possible:
- The PIT plugin writes the class data it already reads into the report or a second file.
- The export reads the original class files again.
Decision
The export is a Gradle task after pitest, mutationCrapCodeCharta, not part of check.
It reads mutation-crap.json and, for the line numbers, the original class files named in provenance.classFiles.
- Complexity and Mutation-CRAP come only from the report; the task computes neither again.
- Before it uses a class file, the task compares its SHA-256 with the one in the report.
A different or missing file stops the task with a message to run
pitestagain, so the LOC always belongs to the bytes PIT analysed (@MC-CC-016). - The line numbers come from JaCoCo Core over those bytes, the same version and the same static analysis as the complexity.
- The logic lives in
de.javagil.werkcrappit.pit.codecharta; the Gradle task only reads and writes files.
Why not option 1: it changes the report contract for a visualization, and it puts work into every PIT run that only the export needs. Reading the class files again costs milliseconds per class, and RFC step 2 needs the bytecode anyway for the dependencies.
Class Metrics
A building is a top-level class: its nested, inner, local and anonymous classes (Outer$Inner, Outer$1) belong to it.
One building is one source file, as a reader of the code sees it.
Since PR#17 every top-level class in the report's code roots is a building, unless werkcrappit.excludeClasses or codeCharta.excludeClasses excludes it (@MC-CC-024).
The counted methods of a building are its methods that are not EXCLUDED.
A class outside the PIT run, for example one PIT's excludedClasses leaves out, gets its complexity and lines from the same static JaCoCo analysis the PIT plugin uses, and no mutation_crap (@MC-CC-022).
CodeCharta vis-2.7.0 reads a missing metric as 0 when it loads a file, so such a building shows green, as if well tested; PR#17 wrongly expected grey.
Every real Mutation-CRAP is at least 1, as it is at least C, so the viewer rule "flatten where mutation_crap < 1" matches exactly the unscored buildings and draws them flat and grey; the report page and the metric's description say so (PR#19).
A file cannot carry that rule, and the viewer takes no rule from its URL.
Since PR#20 the report page links, by default, a CodeCharta built from a fork with a fix: it records the metrics a file has no value for and shows such a file grey, keeping area and height (@MC-CC-026).
The fork is git.javagil.de/mi/codecharta, branch mihoe/missing-metrics-stay-missing, served statically at https://javagil.de/codecharta/; the fix is offered to MaibornWolff upstream, and the default goes back to the original once it is released there.
Since PR#23 the link also sets the colour range, colorFrom and colorTo, and colorMode=absolute, URL parameters of the fork (mihoe/color-range-url-params, offered upstream as well): red from the gate's maxMutationCrap on, green below its half, without a limit 5 and 10 (@MC-CC-029).
Since PR#29 red starts at 90 % of the limit, 9 for 10, so that the city warns before the build fails; codeCharta.colorRedFrom sets another start (@MC-CC-029, @MC-CC-030).
Since PR#35 yellow starts at half of colorRedFrom when that is set, not at half of the limit (@MC-CC-030).
A limit used as a ratchet, such as 650 on the way to 15, had swallowed yellow: half of it lay above red, and yellow was capped at red.
codeCharta.colorYellowFrom sets the start of yellow itself, at most where red starts (@MC-CC-031).
CodeCharta's default colour mode, a weighted gradient, already turns a value from three quarters of the range towards red, so hsadmin-ng's 9.3 looked red under a limit of 10; the absolute mode has hard steps.
It shows a value of exactly the limit red, although the gate lets it pass.
CodeCharta otherwise starts with thirds of the range of values, and one outlier, such as 182 in Halo, turns every other building green.
The range cannot go into the file: cc.json 2.0 carries no view settings, and its schema refuses unknown fields.
werkcrappit { codeCharta { viewerUrl } } chooses another viewer, for example the original at https://codecharta.com/visualization/app/index.html.
Why a fork: the original viewer cannot show "no score" apart from "good" without taking the building's height away.
CodeCharta is under the BSD-3-Clause license, so the fork keeps its copyright notice and license text, and the served viewer must carry them beside it, together with the notice that it is modified.
Its class file is not in the report, so its SHA-256 cannot be checked; it is read as it lies in the code root.
A class without code, such as a Spring Data repository interface, is a building of its rloc only (@MC-CC-023).
Until PR#17 a building needed a counted method; that left out interfaces, repositories and classes of Lombok code only, and the area understated a package such as hs.office of hsadmin-ng.
-
complexity: the sum of the JaCoCo complexities of the counted methods; omitted when none has a complexity. When only some have one, the partial sum is exported and the task names the buildings concerned in a warning. -
rloc(since PR#16, the area): the lines of the building's source file that are neither blank nor comment only, asccshcounts them.package,importand annotation lines count: they are written and read like any other line, and every reference to elsewhere adds to what a reader must keep in mind. Code a processor such as Lombok generates is not in the file and never counts. The export finds the file through the bytecode's source file name in the source roots of themainsource set; without it there is norlocand a warning names the class. Another top-level class in the same file gets norloc, so no line counts twice. This takes back the decision below thatrlocis not needed: it was taken on the misunderstanding that only blank lines and comments differ. -
executable_lines(eLOC; calledlocuntil PR#16): the number of distinct source lines on which JaCoCo finds instructions of the counted methods; omitted without line information. This is executable LOC from the bytecode, not a count of the lines in the source file. Blank lines and comments never count, as no instruction lies on them; neither do imports, annotations and declarations without code. So it is at mostrloc; in CodeCharta's own vocabularylocmeans all lines, blank and comment included, which is why the namelocwas given up. Area and height so describe the same code. Without line information the task warns once with the number of buildings concerned; CodeCharta draws them without area. -
mutation_crap: the highest Mutation-CRAP of the counted methods in stateSCORED, rounded to 4 decimals as in the report; omitted when no method is scored. The maximum is compared exactly; it is a visualization aggregate, not a class metric, and the gate never uses it. When other counted methods areINCONCLUSIVE, the maximum of the scored ones is still exported, and the task names the buildings concerned in a warning (@MC-CC-014). -
max_method_complexity: the highest complexity among the counted methods. -
method_count: the number of counted methods;scored_method_count: those in stateSCORED. -
mutant_count: all mutants of the counted methods, every status included.
Which method sets the color stays in mutation-crap.md; a CodeCharta metric is a number only.
When maxMutationCrap is set, the description of mutation_crap names it; the color scale is set in the viewer.
A missing value is omitted, never written as 0.
A nested class whose top-level class has no counted method still makes the building of that top-level class (@MC-CC-015).
Methods that JaCoCo filters, for example many generated by the Kotlin compiler, are EXCLUDED in the report and so count for neither LOC nor complexity.
Packages are nested folders; since PR#15 a chain of folders with one sub-folder each and no class becomes one folder named by the dotted packages, net.hostsharing.hsadminng, as an IDE compacts middle packages (@MC-CC-017).
Such a chain adds levels without showing anything.
CodeCharta computes a folder's value itself: the sum of its children for an absolute metric, their median for a relative one; it knows no maximum.
Since PR#15 mutation_crap and max_method_complexity are relative, all other metrics absolute (@MC-CC-018).
Summed, the maxima of a large package lay far above any color threshold, so the package was red although every package below it was green.
The median shows the typical class; an outlier stays visible as its red building.
The root folder is named root, as ccsh names it; meta.projectName is the analysisContext of the report, for example :app:pitest.
Format
- Target: CodeCharta visualization
vis-2.7.0(2026-09-28),apiVersion2.0. Its schema (visualization/app/codeCharta/util/ccJson2Schema.jsonat commit2af9db04) is pinned as a test resource; tests validate against it without network access, together with the viewer's own checks for unique IDs and unique siblings. The rendering in the viewer is a manual probe, recorded indocs/compatibility.md. - Node ID: the first 16 hex characters of SHA-256 over the type name and the canonical path, as CodeCharta's
NodeIdcomputes it ("File" + "/shop/domain/Order"). meta.checksum: MD5 over the compact{"files":…,"lenses":…}, asccshcomputes it. The viewer takes the checksum as an opaque identity and does not compute it again.- The file name is
mutation-crap.cc.json, indented like the report, so it can be read and diffed. Since PR#14 it lies in a report directory of its own,build/reports/codecharta, with anindex.htmlthat links it and the viewer. A CI server lists a report directory by its index page; so Werkator shows the code city without a change of its own, where a file beside PIT's report stayed unlisted. The page is self-contained, without external resources, as a sandboxed artifact view requires. Since PR#18 an inline script links "Open in CodeCharta" with the file's absolute URL as the viewer's parameterfile(@MC-CC-025): the page does not know its own address, and only a script can turn the relative name into one. The viewer fetches the file in the reader's browser, so the server must allowhttps://codecharta.comby CORS and serve the file without a login; Werkator, whose private repositories need a cookie the browser does not send there, is not changed for it. - Children, metrics and descriptors are written in a fixed order, so the same inputs give the same bytes.
Consequences
- The export needs the class files of the PIT run; after a new compile without a new PIT run it stops instead of mixing two builds.
- A report of an unfinished run is refused; a report with an incomplete scope is exported with a warning, without the unreadable classes' LOC.
- A newer CodeCharta version with a changed schema needs a new pinned schema and a new entry in
docs/compatibility.md.
Dependency Edges (RFC Step 2, PR#22)
- A references B when B appears anywhere in A's class file, as
jdepssees it: super types, field and method descriptors, generic signatures, annotations, and in the code calls, field accesses,new, casts,instanceof,catch, class literals and method handles. TheInnerClassesand nest attributes are left out: they list classes, they do not use them. - A reference of a nested class counts for its top-level class; references within one building are dropped.
- One edge per ordered pair of buildings; its
dependenciescounts the distinct things of B that A uses: B as a type counts 1, every method and every field of B used counts 1 each. This is stable against code the compiler duplicates, such asfinallyblocks and bridge methods. - A reference to a class without a building is dropped silently, whether it is a library, the JDK, or an own class outside the city.
- By default (
codeCharta.levelization) the edges carryisCyclicandisPointingUpwards, and the dependency lens thelevelof every node, computed asccsh dependencyparserdoes: an edge is cyclic when its buildings reach each other; levels are local to a folder, its edges lifted to its direct children, cycles broken by dropping the edge into the child with the least incoming weight; an edge points upwards when, under the lowest folder holding both ends, its target's child has a level as high as or higher than its source's. The released viewervis-2.7.0refuses these fields;levelization = falseleaves them out, and the file stays valid for it. - The edges are checked against both pinned schemas: with levelization against CodeCharta
mainat 364a7449a, which the default viewer is built from; without againstvis-2.7.0.
Commit and Source Links (RFC Step 3, PR#24)
meta.commitHashis the full SHA ofgit rev-parse HEADin the project directory; a configured value overrides it, for example from a CI variable. Without git and without a configured value there is nocommitHash.- With uncommitted changes the HEAD commit is still exported, and the task warns that the analysed code may differ from it.
- A source link exists only with a configured template; there is no default and no rule for a particular Git host.
Its placeholders are
{commit},{path}(the source file relative to the Git root, found in themainsource set) and{line}. {line}is the first line of the method with the highest Mutation-CRAP, else the smallest first line of the counted methods: a click leads to the hotspot. A class without any method with code, an interface say, is linked at line 1 (PR#25); before, it got no link, 43 of hsadmin-ng's 267 buildings.- A template without a commit to fill in stops the task; a building whose source file is not found gets no link, and the task counts these in one warning.
Since PR#37 the city carries cohesion and coupling (RFC 0002): lcom4 relative, efferent_coupling and afferent_coupling absolute, from the report's section classes; the report page links the city coloured by each of the three dimensions (@MC-CK-008, @MC-CK-009).
Since PR#50 the export also writes structure.html, the level diagrams of RFC 0003,
from the same tree, dependencies and Levelization;
codeCharta.levelization = false leaves the flags and levels out of the city only, the page always has them.
Since PR#51 it also writes matrix.html, the expandable dependency matrix of the same data.
Since PR#40 the export also writes one CodeCharta scenario per view, Mutation-CRAP, cohesion and coupling, each with its colour band; the Mutation-CRAP link passes them with the parameter scenario, which the fork reads (mihoe/scenario-url-param), and the page offers them for import by hand (@MC-CC-032 to @MC-CC-034).