Skip to content

Static analysis and code quality ​

Three tools watch the quality of the code rather than its behaviour. SonarCloud measures duplication and maintainability issues (code smells, bugs, vulnerabilities) of the master branch; Codecov measures its coverage. Clang-Tidy gates include hygiene on every pull request and runs an advisory performance audit on request. The roadmap turns their reports into cleaning work: SonarCloud's duplication ranking orders item 3.4, and item 3.5 widens Clang-Tidy beyond includes. The shared kernels that duplicated code should move to are described in shared geometry and FEM kernels.

SonarCloud ​

The analysis runs in its own workflow, .github/workflows/sonarcloud.yml, on every push to master, again when ci.yml finishes on master (to report that commit's coverage, below), and on demand (Actions → sonarcloud → Run workflow). It never runs on a pull request or a release tag: it is a baseline for the branch, not a gate on a review. It builds nothing and runs no tests, so a scan takes minutes rather than waiting on the hours-long instrumented coverage job of ci.yml, which a newer push to master used to cancel before its scan step was reached. A newer push waits for a running scan instead of cancelling it. The quality gate and the duplication density appear on sonarcloud.io and as badges in the README.

One-time setup ​

The scan is skipped, with a warning in the job log, until the repository is set up on SonarCloud:

  1. Sign in to sonarcloud.io with GitHub, import the loumalouomega organization and analyse the meshioplusplus repository.
  2. In the project's Administration → Analysis Method, turn Automatic Analysis off. C and C++ are analysed only from CI, with the build's compilation database; automatic analysis would conflict with the CI scan.
  3. Create a token (My Account → Security) and add it as the SONAR_TOKEN repository secret on GitHub.
  4. Check that the organization and project key SonarCloud assigned match sonar.organization and sonar.projectKey in sonar-project.properties (loumalouomega and loumalouomega_meshioplusplus, SonarCloud's defaults for this repository); change the file if they differ.

The next push to master then runs the scan. A failed scan (a SonarCloud outage, an expired token) does not fail the workflow.

What is analysed ​

sonar-project.properties at the repository root holds the configuration. Sources are src/cpp, src/python/meshioplusplus and bindings; tests are tests. Excluded from analysis:

  • vendored third-party code (src/cpp/third_party);
  • the amalgamated single header (src/single_include), a generated copy of src/cpp that would otherwise double every duplication count;
  • tables generated by tools/gen_*.py (the hex dual schemes, the colormaps, _format_table.py, the Nastran HDF5 schema);
  • bindings/wasm and bindings/fortran, which Codecov also ignores;
  • test fixtures (tests/python/meshes, tests/python/input, tests/fuzz).

Duplication is measured on library code only (sonar.cpd.exclusions lists tests/**): tests repeat their setup on purpose. The same property also lists the files that are literal tables alone (src/cpp/src/detail/node_order.cpp, the per-format node permutations): the detector normalises literals, so two tables of the same shape match although no entry agrees.

The compilation database ​

The C/C++ analyzer needs the exact compile command of every translation unit. The workflow gets it from CMake's configure step alone (-DCMAKE_EXPORT_COMPILE_COMMANDS=ON), which is enough because no analysed source includes a header generated at build time. It configures two trees with HDF5, netCDF, zlib and OpenCASCADE on, mirroring the coverage job's two builds: the GoogleTest and C API tree, and the Python extension's tree, the only one that compiles bindings/python/_core.cpp. A step merges them into compile_commands.json and keeps one command per source. Sources neither tree compiles (the other mesh backends' headers) are not analysed.

sh
# The same database locally, without building:
cmake -S . -B build-sonar -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DMESHIOPLUSPLUS_BUILD_TESTS=ON -DMESHIOPLUSPLUS_BUILD_C_API=ON -DMESHIOPLUSPLUS_BUILD_PYTHON=OFF

Coverage ​

Nothing runs under instrumentation in this workflow; the coverage SonarCloud shows is measured by the coverage job of ci.yml, the one Codecov reads. That job keeps its two reports as the coverage-reports artifact (14 days): coverage.xml from pytest-cov (repo-relative paths through relative_files in pyproject.toml) and cpp-coverage-sonar.xml, the filtered lcov tracefile converted by tools/lcov_to_sonar.py into SonarQube's generic coverage format, so Codecov and SonarCloud count the same lines. A scan passes whichever of the two it finds as sonar.python.coverage.reportPaths and sonar.coverageReportPaths:

  • the scan of a push, or a manual run, downloads the newest coverage-reports artifact on master, which belongs to an earlier commit while this one's coverage job is still running, so its coverage lags by up to one commit;
  • when ci.yml finishes on master (workflow_run), the same commit is scanned again with the reports that run measured; a run a newer push cancelled kept none, and that scan is skipped.

The quality gate's Coverage on New Code condition is judged on the second scan. Without coverage it could never pass, which is what turned the gate red after the first analyses.

sh
# The C++ conversion locally, from an lcov capture:
tools/lcov_to_sonar.py cpp-coverage.info cpp-coverage-sonar.xml --root "$PWD"

False positives ​

A finding shown to be wrong is closed so the next scan does not report it again (roadmap §9), one of two ways:

  • Inline, on the reported line: // NOSONAR(<rule>): <reason> in C++, # NOSONAR(<rule>): <reason> in Python, <!-- NOSONAR(<rule>): <reason> --> in HTML, with a comment above when the reason needs more than a few words. SonarCloud skips every issue on a line that carries NOSONAR; the rule key in parentheses is for the reader. Prefer a code change that says the same thing (a guard the analyzer can follow, a local that holds a value it evaluated twice) wherever it leaves the behaviour unchanged.
  • Once per rule, in sonar.issue.ignore.multicriteria of sonar-project.properties, with a comment there giving the decision: when the pattern is the code's contract rather than one line's, or when an inline marker would not stay on the reported line (black reflows a long Python condition onto several).

The decisions recorded per rule so far: the taint rules' path and file-content reports (pythonsecurity:S2083, S6549) in the library's top-level modules, which open what their caller names by design (the MCP server under mcp/ is not covered and keeps its sandbox); the same in the trainer, which reads and writes the job directory the server laid out, and S8707 on its --spec; and Python's floating-point equality (python:S1244), whose every report compares with an exact sentinel the Python reference must match the C++ core on bit for bit.

Local duplication report ​

SonarCloud reports on master only, so tools/dup_report.py gives the same kind of number on any checkout, before a change lands. It works like Sonar's copy-paste detector: every C++ and Python source is tokenised, identifiers, numbers and strings are normalised so a renamed copy still matches, and every window of 100 consecutive tokens is hashed; a window found in two places marks its lines as duplicated. A window made of literals and table punctuation ({, }, [, ], (, ), ,, :, ;, signs and dots), with at most one token in ten anything else, is skipped: it is a run of table rows, and two tables of one shape would otherwise match entry for entry although no entry agrees (roadmap §8). It excludes what sonar-project.properties excludes, including the table-only files of sonar.cpd.exclusions, and it scans the R binding's C sources (bindings/r/meshioplusplus/src/*.c) beside the C++ ones, as SonarCloud does, so the two measures agree on what counts.

sh
tools/dup_report.py                      # density, top files and largest shared regions, C++ and Python
tools/dup_report.py --lang cpp --top 60  # one language, longer lists
tools/dup_report.py --json report.json   # the full result, for a before/after comparison

The density is a guide, not a target. Some repetition is structure: a table of node orders, a list of py::arg defaults that is the Python signature, or the typed signature of each MCP tool, which the SDK reads to build its schema. The largest regions shared by two files are the useful part of the report, because each names two line ranges that could call one helper.

The November 2026 measurements, by pass of roadmap 3.4:

LanguageBefore the second passAfter the secondAfter the third
C++10.6% (26,097 of 245,231 lines)9.3% (22,739 of 243,556)9.1% (22,041 of 243,222)
Python11.0% (15,038 of 137,250 lines)9.9% (13,481 of 136,357)9.2% (12,467 of 135,765)

The shared-utilities pass of roadmap §8 (November 2026) changed the measure as well as the code: the report now skips literal-table windows and the table-only files and scans the R binding's C sources. On the earlier C++ roots the table filter alone took 22,253 duplicated lines to 20,830, and the code changes to 19,725 (8.1% of 243,917); with the R sources, 21,699 of 251,295 (8.6%). Python went from 12,468 to 9,739 with the filter and to 8,962 of 135,752 (6.6%) with the code.

The binding pass the same month took C++ to 20,554 of 250,625 (8.2%) and Python to 7,214 of 133,787 (5.4%): the MCP server's forwarding tools, the C API's result accessors, _core.cpp's near-copy m.defs and the R binding's report lists became helper calls (what it shared). Two of its changes raise their file's count instead, registry.cpp (263 to 424) and cli/main.cpp (2,270 to 2,348): each replaced several lists of the same names with one table, whose rows the normalising detector matches against each other.

The third pass took C++ to 19,411 of 249,708 (7.8%) and Python to 6,500 of 132,440 (4.9%). The report also counts true, false, nullptr, True, False and None as table tokens since then, so an option table's {"name", {}, true} rows no longer match each other, and it skips the generated pipeline step tables (_pipeline_table.py, pipeline_table.hpp).

What remains at the top is mostly structure the report cannot tell apart from copying: the explicit py::arg signatures of bindings/python/_core.cpp, the extern "C" entry points of bindings/c/c_api.cpp, the argument tables of the native CLI and the typed MCP tool signatures. The rest is listed in roadmap 3.4.8 to 3.4.12.

Clang-Tidy ​

Two Clang-Tidy runs exist today:

  • Include hygiene, on every pull request. The include-cleanup CI job runs tools/include-cleanup.sh --check with the repository's .clang-tidy, which enables misc-include-cleaner only. Unused includes fail the job; missing-include suggestions are advisory.
  • Performance audit, on request. tools/performance-tidy.sh runs the performance-* checks over a separate Clang tree and writes a JSON report; see the audit for its commands and the triage of its first inventory.

Widening Clang-Tidy to the bug-finding and modernisation check families, first as advisory reports and then as a gate on new findings, is roadmap item 3.5.

Acting on a report ​

A finding is a candidate, not a mandate. Before removing a duplicate:

  • check whether a comment marks the copy as deliberate (a flat-buffer variant of a hot loop, a better-conditioned formula); those stay, and the reason stays with them;
  • move the shared code to the existing kernels (detail/geometry.hpp, src/cpp/src/detail/fem_kernels.hpp, the topology tables) rather than adding a third copy;
  • prove the output did not move: the determinism check with a baseline must report identical digests, and the instruction-count gate must pass.

Released under the MIT License.