156 lines
15 KiB
Markdown
156 lines
15 KiB
Markdown
# 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:
|
|
|
|
1. The PIT plugin writes the class data it already reads into the report or a second file.
|
|
2. 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 `pitest` again, 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, as `ccsh` counts them.
|
|
`package`, `import` and 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 the `main` source set; without it there is no `rloc` and a warning names the class.
|
|
Another top-level class in the same file gets no `rloc`, so no line counts twice.
|
|
This takes back the decision below that `rloc` is not needed: it was taken on the misunderstanding that only blank lines and comments differ.
|
|
- `executable_lines` (eLOC; called `loc` until 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 most `rloc`; in CodeCharta's own vocabulary `loc` means all lines, blank and comment included, which is why the name `loc` was 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 state `SCORED`, 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 are `INCONCLUSIVE`, 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 state `SCORED`.
|
|
- `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), `apiVersion` `2.0`.
|
|
Its schema (`visualization/app/codeCharta/util/ccJson2Schema.json` at commit `2af9db04`) 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 in `docs/compatibility.md`.
|
|
- Node ID: the first 16 hex characters of SHA-256 over the type name and the canonical path, as CodeCharta's `NodeId` computes it (`"File" + "/shop/domain/Order"`).
|
|
- `meta.checksum`: MD5 over the compact `{"files":…,"lenses":…}`, as `ccsh` computes 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 an `index.html` that 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 parameter `file` (`@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 allow `https://codecharta.com` by 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 `jdeps` sees 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.
|
|
The `InnerClasses` and 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 `dependencies` counts 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 as `finally` blocks 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 carry `isCyclic` and `isPointingUpwards`, and the dependency lens the `level` of every node, computed as `ccsh dependencyparser` does:
|
|
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 viewer `vis-2.7.0` refuses these fields; `levelization = false` leaves them out, and the file stays valid for it.
|
|
- The edges are checked against both pinned schemas: with levelization against CodeCharta `main` at 364a7449a, which the default viewer is built from; without against `vis-2.7.0`.
|
|
|
|
## Commit and Source Links (RFC Step 3, PR#24)
|
|
|
|
- `meta.commitHash` is the full SHA of `git rev-parse HEAD` in the project directory; a configured value overrides it, for example from a CI variable.
|
|
Without git and without a configured value there is no `commitHash`.
|
|
- 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 the `main` source 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`).
|