Noir::Detection
Detection-pass internals: the ignore lists, the Android source-set
resolver, and the applicable? memo that keeps the per-file walk off the
O(detectors) path.
These were seven top-level defs and seven SCREAMING constants, each
hand-prefixed detector_ / DETECTOR_ — a namespace spelled out by
convention because there was no module to put them in. ANDROID_SOURCE_SUBDIRS
and ANDROID_EMBEDDED_SERVER_MARKER did not even get the prefix, and were
sitting in the global namespace under names generic enough to collide.
The two phase entry points (build_detector_list, detect_techs) stay
top-level: they are the detection pass's public surface, called from
NoirRunner and the specs.
Constants
Strong server-routing constructs. A .kt / .java file that sits
inside an Android app's source set is normally scoped to mobile
detectors only (an incidental import ...SpringApplication must not
flag the project as a server). But an Android app can legitimately
embed an on-device HTTP server — e.g. plain-app runs a local Ktor
web server whose routes live under app/src/main/java/.... When a
file carries one of these markers it is a real server, so the
Ktor / http4k / Spring detectors are allowed to run on it. Kept as a
single precompiled constant — recompiling it per file would recreate
the PCRE2 program on every read.
Paths that embed directory segments real detectors gate on
(/grails-app/, /migrations/, /server/api/, …). Used only to
classify path-sensitive detectors — not as production file samples.
Filenames that detectors match by exact basename (often with path
constraints like "must sit at the project root"). These must not share
an extension-only cache bucket — e.g. vercel.json is not "any .json".
Instance methods
Build a lookup that turns a path into the list of detector indices
whose applicable? returns true — without re-walking every detector
on every file. Most detectors only inspect extension / basename, so
their answers are memoized by basename. Detectors that look at path
segments or root placement are classified as path-sensitive and
always evaluated against the real path.
Whether a directory entry names a subtree the walk prunes on purpose
(dependency cache, build output, Crystal's lib/ next to a shard.yml).
Extracted from the directory branch of the walk so the symlink branch can
ask the same question: a symlink is not a directory to File.info? with
follow_symlinks: false, so a node_modules or .venv symlink lands
there instead — and reporting that as lost coverage would fire on
every pnpm workspace, which is the opposite of the point.