A duplication report is only useful if what is in it is worth reading. Suppression is how the code a project does not maintain, does not write by hand, or cannot unify stops crowding out the rest.
Every classification is recorded whatever the policy does with it, so
--show-suppressed lists the hidden groups with the reason each was hidden, and
JSON and SARIF retain suppressed findings regardless of the flag. Replaying a
recorded run shows the suppressions as that run applied them, not the policy in
force now.
Pre-1.0 surface. The defaults are part of what Structural mode reports and are measured with it. The configuration keys that change them are documented and tested, but have not had the real use that would make them worth a promise, so they can change between releases.
Most policies take one of three values:
hide— the group is not listed.rank-down— the group is listed below behavioural duplication.report— the group keeps its measured priority.
rank-down is the default wherever the classification is a strong hint rather
than a certainty: the group is still there for a reader who scrolls, and it is not
in the way of one who does not.
[suppression]
# paths = [] # path globs to hide
# vendored-paths = [...] # trees the project vendors rather than writesvendored-paths ships with the conventional set — third_party, vendor,
external, deps, subprojects, node_modules, .venv and their variants —
and hiding them is on by default, because duplication inside a dependency is not
this project's to remove. Setting the key replaces the defaults; setting it to
[] reads vendored code like any other. --include-vendored undoes it for one
run.
Duplication spanning a vendored tree and the project's own code stays visible either way. That holds for every rule here: a group is hidden only when every occurrence in it is.
# symbols = [] # globs over the name of the enclosing unitMatched against the name of the unit an occurrence sits in, which is the way to
quiet one generated accessor family without hiding a whole file. In C and C++
a destructor is named ~Name and an operator by its canonical spelling with no
space before the symbol, such as operator==; a conversion operator keeps the
space between words, as in operator bool, in Fast and Structural modes alike.
# generated-markers = ["@generated", "do not edit", "automatically generated", "auto-generated", "autogenerated"]A file whose first lines carry one of these banners is machine output, and its duplication is the generator's rather than a maintenance problem. Matching ignores case. Setting the key replaces the defaults, so a project adding its own generator's banner lists these alongside it.
Such a file is excluded when files are discovered, before any duplication is
looked for, so it is only counted in the summary: --show-suppressed does not
list it, and the rule that a group is hidden only when every occurrence is
does not apply to it.
# test-paths = ["**/tests/**", "**/test/**", "**/__tests__/**", "**/*_test.*", "**/*_tests.*", "**/test_*.*", "**/*_spec.*"]
# test-code = "rank-down"Test code repeats itself by design: twenty cases that differ in one argument are a suite, not a duplication to unify. Path evidence is combined with source evidence — a Rust test attribute, a C or C++ framework case macro — and a Rust module the crate declares as test-only counts wherever it is written, including files it is split across.
A group spanning both a suite and the code it exercises is not test code, and is not classified as such.
[suppression.boilerplate]
# trivial-body = "rank-down"
# forwarding = "hide"
# macro-repetition = "rank-down"
# guarded-dispatch = "hide"
# configured-answer = "hide"
# composed-answer = "hide"
# built-answer = "hide"These name shapes rather than files: a body that forwards to one call, a match arm that returns a constant, a dispatch guarded by one condition. Unifying them usually costs more than it saves, and reporting them buries what does not.
--include-trivial restores trivially-shaped predicate groups to their measured
priority for one run, which is what an explicit review of the predicate families
wants.
# width-family = "hide"One routine per integer width is often what a typed language requires, so these
are hidden by default. Set this to report where a macro, generic or template
could express the family once.
# entropy-ratio-floor = 0.60This key sits at the top level of the file, outside [suppression].
A group below this normalized content-entropy ratio is marked as degenerate repetition — the same few tokens repeating rather than a routine. The value is relative to the largest entropy a group of the same token length can carry, so it does not turn clone length into a noise filter.
# clone-ids = []A specific group, named by its stable id, for the case where a project has decided about that one duplication and nothing more general describes it. An id describes one group's content, so it stops matching once that content changes — which is the intended behaviour: the decision was about the code as it was.
A baseline is keyed the same way, by the content of the groups it froze: editing every copy of an accepted group, or adding a copy with new content, makes the group appear as new rather than keeping the baseline's decision. The baseline records where each occurrence stood, so a scan can say that a new group stands where a removed entry stood, but that note only describes the replacement and hides nothing.
These classifications need the parsed shape, so Structural and Semantic scans apply them and Fast scans say explicitly that they are unavailable. That is the main reason a Fast report on a large tree is longer than it is useful. See Analysis modes.