Use clang-check-20 with the Right Build Flags
You will finish with a repeatable way to syntax-check C and C++ files, inspect their syntax tree, and spot the most dangerous clang-check mistake: analysing a file without the project's real compilation flags. The examples use clang-check-20 from Ubuntu LLVM 20.1.8, supplied by clang-tools-20 version 1:20.1.8~++20250804090239+87f0227cb601-1~exp1~20250804210352.139.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell, the installed package, and source files you are allowed to read. The normal checks are read-only. The --fixit option changes source files, so this guide treats it as an explicit, backed-up step rather than a casual extra flag.
1. Confirm the installed tool
Start by checking the executable and version. These are ordinary commands and do not need elevated privileges:
$ command -v clang-check-20
/usr/bin/clang-check-20
$ clang-check-20 --version
Ubuntu LLVM version 20.1.8
Optimized build.
The version matters when you compare diagnostics or AST output with another machine. Keep the major version aligned with the project's Clang toolchain where possible.
Checkpoint: if the command is missing, stop here. Do not substitute an unverified binary from another directory and assume it has the same behaviour.
2. Check one file without changing it
Pass one source path as the input. This basic invocation builds the translation unit and reports diagnostics:
$ clang-check-20 path/to/source.cpp
Processing path/to/source.cpp.
A clean run can produce only the processing line, or no useful standard output depending on the file and tool invocation. Check the status immediately if you need a scriptable result:
$ clang-check-20 path/to/source.cpp
$ status=$?
$ printf 'clang-check status: %s\n' "$status"
clang-check status: 0
Do not interpret status 0 as proof that the file was parsed with your project's settings. If clang-check cannot find a compilation database, the installed command warns that it is running without flags and may still return 0. That fallback is the main source of misleading results.
3. Point it at the compilation database
Build projects commonly record each translation unit's directory, compiler and flags in compile_commands.json. With CMake, generate it by configuring the build with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON. Then pass the directory containing that file with -p:
$ cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
$ clang-check-20 -p build src/example.cpp
Processing src/example.cpp.
The CMake command reconfigures the build but does not compile it. It is normally unprivileged; use the permissions already required by your project and do not run the whole build as root merely to make clang-check work.
When -p is omitted, clang-check searches parent directories of the first input file for compile_commands.json. The input path also has rules: an absolute path must point inside the CMake source tree, while a relative path must be beneath the current source-tree directory. A path that exists is not automatically a path that the database can match.
Checkpoint: verify that the database is present before debugging a diagnostic:
$ test -s build/compile_commands.json && printf '%s\n' 'compile database found'
compile database found
$ clang-check-20 -p build src/example.cpp
Processing src/example.cpp.
4. Inspect the AST or tokens
Use an output mode that answers one question at a time. --ast-print pretty-prints declarations after parsing:
$ clang-check-20 -p build --ast-print src/example.cpp
int answer() {
return 42;
}
--ast-dump emits Clang's debug representation, which is more verbose but useful when a tool or refactoring needs to distinguish declarations and expressions. --syntax-tree-dump dumps the syntax tree, while --tokens-dump shows preprocessed and spelled tokens. These modes write large outputs for real projects, so redirect them to a reviewable file rather than flooding a terminal:
$ clang-check-20 -p build --tokens-dump src/example.cpp > /tmp/example.tokens
$ sed -n '1,24p' /tmp/example.tokens
expanded tokens:
int answer ( ) { return 42 ; }
The temporary file is disposable and does not alter the project. If the output looks unexpectedly small or headers are missing, return to step 3 and check the database and selected command before changing source code.
5. Add one diagnostic flag without rewriting the build
Use --extra-arg to append one compiler argument, or --extra-arg-before to prepend one. This is useful for a controlled experiment, such as checking a conditional branch:
$ clang-check-20 -p build --extra-arg=-DCLANG_CHECK_EXAMPLE=1 --ast-print src/example.cpp
int answer() {
return 42;
}
These options affect this invocation only. They do not update compile_commands.json or the build system. Keep the added flag obvious and record it in a script if the check must be repeated.
6. Treat fix-it mode as a file change
--fixit applies fix-it advice to the input source. --fix-what-you-can also applies available fixes when other errors remain. Before either option, save or commit your work and inspect the exact files selected by the command. This is the warning point: these options can change source files and are not undoable by clang-check itself.
$ git diff -- path/to/source.cpp
$ clang-check-20 -p build --fixit path/to/source.cpp
$ git diff -- path/to/source.cpp
Review the diff before keeping it. Recovery is your normal version-control restore or editor undo workflow; do not use a broad reset if unrelated work is present. If the fixes are not wanted, restore only the reviewed file from your known-good copy or commit, following your project's recovery process.
Common failure traps
- A warning about no compilation database means the command is falling back to default parsing. Fix the database path or accept that the result is only a limited experiment.
- A valid database can still select the wrong command when the source path does not match its recorded path. Run from the source tree and use the path form recorded by the build.
- Multiple source files are accepted, but a failure in one file can be hidden in a long stream. Start with one file, then expand the command deliberately.
--analyzeruns the static analysis engine. It is not the same question as syntax checking, AST printing or token dumping; use it when you specifically need that analysis mode.
Done means
clang-check-20 --versionreports the expected toolchain.- The selected source path matches an entry in
compile_commands.json. - A normal check returns the status you expect without a missing-database warning.
- AST or token output answers the debugging question you started with.
- Any fix-it diff was reviewed, and unwanted changes were recovered using your existing version-control workflow.