Icpplint Processor
Purpose
Lints C and C++ files with cpplint’s rules, in-process (no cpplint program, no Python). The Rust alternative to the cpplint processor: a port of cpplint 2.0.2, so a file gets the same findings with the same messages, categories and confidence levels, and passes or fails the way cpplint would exit.
How It Works
Each file goes through what cpplint does to it: the four line views (raw, raw strings blanked, comments removed, strings collapsed), the nesting stack of namespaces, classes, extern "C" blocks, constructors and preprocessor branches, the include-order and header-guard state, NOLINT/NOLINTNEXTLINE/NOLINTBEGIN/NOLINTEND suppressions, and every check cpplint has, from legal/copyright to whitespace/todo (the sixty-six categories cpplint --filter= lists). Findings are printed in cpplint’s own format and any finding fails the file:
src/main.cc:0: No copyright message found. You should have a line: "Copyright [year] <Copyright Owner>" [legal/copyright] [5]
src/main.cc:1: Missing space before { [whitespace/braces] [5]
src/main.cc:2: Tab found; better to use spaces [whitespace/tab] [1]
src/main.cc:2: Using C-style cast. Use static_cast<int>(...) instead [readability/casting] [4]
Header guards are derived the way cpplint derives them: from the path relative to the repository root (the nearest directory with .git, .hg or .svn, or repository), adjusted by root. The file name cpplint sees is the path relative to the project root, which is what cpplint src/foo.cc run from the project root would see, so filters with a file part (-whitespace:src/foo.cc:14) use that path.
Configuration
cpplint’s per-directory configuration files work as they do in cpplint: CPPLINT.cfg (or the file config_file names) is read from the file’s directory upwards until one says set noparent, with filter, exclude_files, linelength, root, headers, extensions and includeorder. Every such file between a source file and the project root is an input of that file’s product, so editing one re-checks the files under it; there is no need to list them in dep_inputs. The TOML fields are cpplint’s command-line options:
[processor.checker.icpplint]
src_dirs = ["src"]
config_file = ".cpplint"
filters = ["-whitespace/tab", "-build/include_order"]
line_length = 100
A file an exclude_files pattern matches is skipped, as cpplint skips it. A configuration file with an option cpplint does not know, a non-numeric linelength, a filter that does not start with + or -, or a file whose extension is not one cpplint accepts fails the build; cpplint prints a message and goes on, which hides the mistake.
Fidelity
Checked against cpplint 2.0.2 over its own sample corpus (samples/*/*.def: seven projects under twelve option sets, including CPPLINT.cfg, --filter, --includeorder=standardcfirst, --headers, --recursive --exclude), the 1245 snippets its unit tests lint, demos-os-linux’s 1231 C/C++ files with and without the repo’s .cpplint, and 3000 mutated copies of those files (tabs, casts, missing spaces, using namespace, VLAs, sprintf, NOLINT variants, header-guard and include edits, CRLF line endings, no final newline): the same findings, file for file. Two things cpplint does are deliberately not reproduced: its “Unexpected \r (^M)” warning, which cpplint itself can never emit because Python’s text mode translates line endings before it looks; and the way a linelength, root, headers or extensions setting read from one directory’s CPPLINT.cfg stays in force for every file cpplint processes afterwards in the same run (here every file starts from the configured options).
Source Files
- Input:
.c,.cc,.cpp,.cxx,.c++,.cu,.h,.hh,.hpp,.hxx,.h++,.cuhfiles undersrc_dirs, or the files insrc_files - Output: none (checker)
The default src_dirs is empty, so a stanza with no src_dirs or src_files checks nothing: name the directories ([""] for the whole project) or files to check.
Configuration
| Key | Type | Default | Description |
|---|---|---|---|
filters | string[] | [] | Category filters as --filter: -whitespace, +whitespace/braces, -runtime/printf:foo.cc:14 (an entry may hold several, comma-separated) |
verbose | integer | 1 | Report only findings with at least this confidence (0-5), as --verbose |
line_length | integer | 80 | The allowed line length, as --linelength |
root | string | "" | The directory header guards are derived from, as --root. Empty: unset |
repository | string | "" | The repository top directory, as --repository. Empty: the nearest .git, .hg or .svn |
headers | string[] | [] | Extensions (without the dot) treated as headers, as --headers |
extensions | string[] | [] | Extensions (without the dot) cpplint accepts, as --extensions |
include_order | string | "default" | default or standardcfirst, as --includeorder |
config_file | string | "CPPLINT.cfg" | The name of the per-directory configuration file, as --config |
src_dirs | string[] | [] | Directories to scan |
src_extensions | string[] | the twelve above | File name suffixes to check |
dep_inputs | string[] | [] | Extra files whose changes trigger rebuilds |
dep_auto | string[] | [] | Config files that are inputs when they exist (the config_file chain is tracked already) |
Switching a repo from cpplint: [processor.checker.cpplint] → [processor.checker.icpplint], and args = ["--config=.cpplint"] becomes config_file = ".cpplint". Any other --option=value in args becomes the field of the same name.
Batch support
Files are checked individually within a batch, so one bad file fails only its own product.
Clean behavior
This processor is a Checker — rsconstruct clean outputs is a no-op for it (checkers produce no outputs). See Clean behavior and rsconstruct clean.