Static analysis of expl3 programs (11): Inter-file dependencies, segments, and code coverage
Today, I’m excited to release the next major update to expltools, a bundle that includes the static analysis tool explcheck for the expl3 programming language. This update improves upon the semantic analysis processing step introduced in the previous post and lays groundwork for the final processing step of flow analysis.
Inter-file dependencies
The original project proposal assumed that files would be checked individually. In practice, this limited the semantic analysis of multi-file projects, where one file defines a symbol and another uses it. To address this, the new release introduces the concept of file groups: sets of files that are assumed to be used together.
For example, suppose we have a file foo.tex
:
\cs_new:Nn
\__example_foo:n
{ foo }
\cs_generate_variant:Nn
\__example_foo:n
{ V }
and another file bar.tex
:
\__example_foo:V
\l_tmpa_tl
Previously, running explcheck foo.tex bar.tex
produced:
Checking 2 files
Checking foo.tex 1 warning
foo.tex:4:1: W402 unused private function variant
Checking bar.tex 1 error
bar.tex:1:1: E408 calling an undefined function
Total: 1 error, 1 warning in 2 files
With the new release, files from the same directory are automatically grouped, and the result changes to:
Checking 2 files
Checking foo.tex OK
Checking bar.tex OK
Total: 0 errors, 0 warnings in 2 files
This greatly reduces false positives.
You can control this behavior with the --group-files
option or by using +
and ,
between filenames to group or separate them.
In addition, explcheck will report missing symbols from external packages. To suppress these, use the imported_prefixes
option in your configuration.
Segments
Earlier versions of explcheck had special-case support for only one kind of nested code: function replacement texts. For instance, consider the example from a previous post:
\cs_new:Nn
\example_foo:n
{
\cs_new:Nn
\example_bar:n
{
#1,~##1!
}
}
\example_foo:n { Hello }
\example_bar:n { world }
Here we have three top-level calls (\cs_new:Nn
, \example_foo:n
, \example_bar:n
) and two replacement texts.
But expl3 contains many more forms of nested code: T
- and F
-branches of conditional functions, loop bodies (e.g., \ior_map_inline:Nn
), key–value definitions, and more. Ad-hoc support for each would be brittle, and the upcoming flow analysis requires a uniform way to connect code across different nesting types and levels.
To address this, the new release introduces segments, a unified representation for both top-level and nested code. This simplifies implementation and makes future extensions easier.

The first new segment type added is support for T
- and F
-branches of conditional functions, and future updates will continue expanding segment coverage.
Code coverage
To show how well explcheck understands a piece of code, the --verbose
output now reports code coverage: the ratio of well-understood expl3 tokens to the total number of tokens. A token is well-understood if it is either simple text or part of a recognized statement in the most deeply nested segment containing it.
For example:
\cs_new:Nn
\example_baz:n
{ \unexpected }
some~text
- The nine tokens in
some~text
are well-understood (simple text). - The four tokens
\cs_new:Nn
,\example_baz:n
,{
, and}
are well-understood (recognized statement). - The token
\unexpected
is not well-understood, because there are no recognized statements in the replacement text.
Thus, coverage = 13/14 tokens ≈ 93%.
For context, explcheck currently achieves ~14% code coverage across all expl3 code in TeX Live. Future releases aim to raise this figure by recognizing more statements, segment types, and plain TeX / LaTeX2e constructs.
Working with the community
Since March, I’ve been reaching out to package maintainers with reports of issues found by explcheck. Of 11 reported issues, 8 have already been fixed:
- BITNP/BIThesis#604: fixed
- jspitz/jslectureplanner#7: fixed
- dbitouze/nwejm#5
- dbitouze/gzt#56
- michal-h21/responsive-latex#1: fixed
- fpantigny/nicematrix#12: fixed
- josephwright/siunitx#796: wontfix
- BITNP/BIThesis#640: fixed
- se2p/se2thesis#23: fixed
- John02139/asmeconf#11: fixed
- dickimaw-books.com#303: fixed
Following today’s update, at least 15 new potential issues were detected in public TeX Live repositories. Reports have been submitted, including:
- An e-mail to Martin Hensel about the issues detected in package mhchem
- An e-mail to Herbert Voß about the issues detected in package hvarabic
- An e-mail to Andrew Parsloe about the issues detected in their many packages
- nwafu_nan/nwafuthesis-l3#ID0M68
- nwafu_nan/chinesechess#ID0MAM
- samcarter/beamertheme-tcolorbox#5
- hust-latex/hustthesis#45
- Zeta611/simplebnf#10
- irenier/sysuthesis#1
- slatex/sTeX#453
- dcpurton/scripture#117
- luatexja/luatexja#35
- leo-colisson/robust-externalize#59
- Vidabe/cooking-units#32
- fpantigny/nicematrix#117
Let’s see how many more we can fix before the next update! 😉
Get involved!
Your feedback is invaluable. Feel free to contribute or share your thoughts by visiting the project repository. More improvements are on the way as explcheck continues to grow.
Last updated on September 30, 2025