module

Noir::SkippedFiles

Every path by which a scan can quietly lose coverage, funnelled into one place.

AnalyzerFailure covers the case where a whole analyzer raised: coverage lost by the tech. The other half of the same problem is a single file raising inside an analyzer that otherwise completed — the per-file rescues in Analyzer#parallel_analyze and Analyzer#scan_files exist precisely so one unreadable or unparsable file costs only itself. That is the right behaviour, but until now the only trace was a --debug line, so the result of a scan that silently dropped a file was byte-identical to one that read everything.

#2612 made that gap wider by adding a ceiling on parse time: a pathologically malformed file now raises and is skipped where it used to take the process down. Loud-and-fatal became quiet-and-partial, which is the better failure mode only if the "partial" part is visible somewhere.

The analysis pass was only ever half the story, though. A scan drops coverage in six other places — an unlistable directory takes its whole subtree, an oversize or unreadable or binary-looking file is filtered out, a symlinked tree is never walked, an export never lands, -P runs with zero rules loaded — and each of those had its own local counter, its own log line, and no route into errors at all. So --strict (documented as "exit 2 if any analyzer failed or skipped a file") reported green on a scan that had lost most of a codebase.

Rather than six more counters, they all record here. failures is the one list, errors in the output is that list, and degraded in the CLI is "that list is not empty". Adding a seventh drop path is a record call.

Tallied rather than listed one entry per file: a repository that trips this usually trips it in bulk (a vendored minified bundle, a generated tree), and errors is part of the JSON/YAML/TOML output — one entry per tech with a count and a few example paths says the same thing without letting a broken checkout print thousands of lines.

Constants

DELIVER_SCOPE = "deliver"
DETECT_SCOPE = "detect"

Labels for the drop paths that belong to no analyzer. They land in the same errors[].tech field a real tech name would, because the field answers the same question either way: which part of the scan came up short.

MAX_PATHS_PER_TECH = 5

Example paths kept per tech. Beyond this only the count grows.

PASSIVE_SCAN_SCOPE = "passive-scan"

Instance methods

clear(phase : Phase) : Nil
Source
clear

Drops everything, both phases. Used by specs and by library callers that want a clean slate; production code clears one phase at a time.

Source
count

Total files (and directories, and symlinks) skipped since the last clear, across every scope. Excludes record_gap entries, which count no items.

Source
failures(phase : Phase) : Array(AnalyzerFailure)
Source
failures

One AnalyzerFailure per tally plus one per recorded gap, so a dropped file, an unwalked directory and an undelivered export all land in the same errors array — and the same --strict exit code — as a dropped analyzer.

Source
record(tech : String, path : String, reason : String, noun : String = "file", phase : Phase = Phase::Analysis) : Nil

tech may be empty — Analyzer's own tech returns "" and only analyzer_for fills it in, so a base-class caller is still recorded, just unattributed.

noun names what was dropped so the message reads true for callers that skip something other than a file — a directory that would not list, a symlink that is not followed.

Source
record_gap(scope : String, message : String, phase : Phase = Phase::Scan) : Nil

One-off coverage gap, reported as written. Use record instead whenever the loss is per-path and can arrive in bulk.

Source

Nested types