Skip to content

Codebase metrics

A measured snapshot of the Personal Data Vault codebase and its submodules, counted with cloc. Charts show code lines (blank and comment lines excluded) unless stated otherwise.

Snapshot

Measured on the 1.x branch, 2026-09-20, with cloc 2.08. 407 files, 29,881 lines of code, 15,205 comment lines, 5,939 blank (51,025 lines in total). See Methodology to reproduce.

At a glance

Code Comment Blank Files Production Test
29,881 15,205 5,939 407 21,872 8,009

Production vs test

Personal Data Vault ships 8,009 lines of test code backing 21,872 lines of production code, a 37% test-to-production ratio.

Lines by language

Language Code Comment Blank Files
PHP 23,821 14,862 4,597 287
YAML 2,479 256 170 81
PO File 1,648 12 766 11
Markdown 1,488 1 380 15
Text 229 0 7 4
CSS 90 60 16 4
JSON 69 0 0 2
HTML 19 0 0 1
JavaScript 19 13 2 1
SVG 19 1 1 1

Composition

Across all languages, code is 59% of the lines, comments 30%, and blank lines 12%. The comment share reflects Drupal's docblock conventions.

By submodule

Production (brass) and test (steel) code per submodule, largest first.

Submodule Production Test Total
pdv (core) 15,669 4,452 20,121
pdv_client 1,748 1,180 2,928
pdv_webform 1,394 1,117 2,511
pdv_server_api 936 731 1,667
pdv_consumer_console 697 44 741
pdv_audit_trail 524 130 654
pdv_eca 164 108 272
pdv_message 166 84 250
pdv_vault 246 0 246
pdv_mail 123 69 192
pdv_client_api 132 48 180
pdv_client_audit_trail 73 46 119

Test suite

Personal Data Vault ships 85 automated test classes (459 test methods), counted by type below.

Test type Classes Methods
Kernel 74 415
Unit 9 35
Functional 2 9
FunctionalJavascript 0 0
Total 85 459

Architecture surface

What the module contains, counted across Personal Data Vault and its submodules: the Drupal building blocks (services, plugins, hooks, routes, entities) that make up its public and internal API.

Element Count
Services 53
Event subscribers 7
Hook implementations 17
Routes 61
Permissions 6
Forms 46
Controllers 17
Content entity types 5
Config entity types 3
Config schema types 10
Plugins 8

Plugins break down by type as follows, largest first:

Methodology

This page is generated by scripts/generate-metrics.php, run from the module root after a change worth reflecting:

php scripts/generate-metrics.php

It measures the files git ls-files reports, so a checkout that has installed vendor/, previewed the docs into site/ or pulled a node_modules/ counts the same as the archive a release ships. This page is left out of that list: the script writes it, and a page that counts itself changes the tree it just measured, so no two regenerations agree. The snapshot above names the cloc version, because cloc counts differently from one version to the next; that is the version to run to reproduce this page, and the version the pipeline installs.

Each file is attributed to the nearest enclosing module (a directory with a *.info.yml); test-support modules under a tests/ path count as their parent's test code. A file is test code when its path contains a tests/ segment, production otherwise.

Test classes are counted by their directory under tests/src (Unit, Kernel, Functional, FunctionalJavascript); the method column counts each test-prefixed method, and each further one annotated @test or #[Test]. The architecture surface counts service definitions in *.services.yml (excluding aliases), route and permission keys in *.routing.yml and *.permissions.yml, #[Hook] attributes, plugin classes under src/Plugin/, forms and controllers under src/Form and src/Controller, entity-type attributes in src/Entity, and top-level types in config/schema/*.yml, all summed across the module and its submodules.

The charts are static inline SVG generated from the measured data, so they need no JavaScript or charting plugin. They are decorative and marked aria-hidden: every figure one of them draws is also in the table or the sentence beside it, so a screen reader gets the numbers rather than an image with no name. An img role carrying no accessible name is worse than no role at all, because it hides the labels inside the chart as well.