class Detector
- Detector
- Reference
- Object
Direct Known Subclasses
- Detector::Asp::Classic
- Detector::Aspnet::WebForms
- Detector::Cfml::Coldbox
- Detector::Cfml::Fw1
- Detector::Cfml::Pure
- Detector::Cfml::Taffy
- Detector::Cfml::Wheels
- Detector::Clojure::Cli
- Detector::Clojure::Compojure
- Detector::Clojure::Pedestal
- Detector::Clojure::Reitit
- Detector::Clojure::Ring
- Detector::Cpp::Cli
- Detector::Cpp::Crow
- Detector::Cpp::Drogon
- Detector::Cpp::Httplib
- Detector::Cpp::Oatpp
- Detector::Crystal::Amber
- Detector::Crystal::Cli
- Detector::Crystal::Grip
- Detector::Crystal::Http
- Detector::Crystal::Kemal
- Detector::Crystal::Lucky
- Detector::Crystal::Marten
- Detector::CSharp::AspNetCoreMinimalApi
- Detector::CSharp::AspNetCoreMvc
- Detector::CSharp::AspNetMvc
- Detector::CSharp::Carter
- Detector::CSharp::Cli
- Detector::CSharp::FastEndpoints
- Detector::CSharp::HttpListener
- Detector::CSharp::SignalR
- Detector::Dart::Alfred
- Detector::Dart::Angel3
- Detector::Dart::Cli
- Detector::Dart::DartFrog
- Detector::Dart::GetServer
- Detector::Dart::Http
- Detector::Dart::Serverpod
- Detector::Dart::Shelf
- Detector::Elixir::Bandit
- Detector::Elixir::Cli
- Detector::Elixir::Phoenix
- Detector::Elixir::PhoenixChannel
- Detector::Elixir::Plug
- Detector::Erlang::Cowboy
- Detector::Erlang::Elli
- Detector::Fsharp::Giraffe
- Detector::Gleam::Wisp
- Detector::Go::Beego
- Detector::Go::Chi
- Detector::Go::Cli
- Detector::Go::ConnectRpc
- Detector::Go::Echo
- Detector::Go::Fasthttp
- Detector::Go::Fiber
- Detector::Go::Gf
- Detector::Go::Gin
- Detector::Go::GoRestful
- Detector::Go::Goyave
- Detector::Go::GoZero
- Detector::Go::Hertz
- Detector::Go::Http
- Detector::Go::Httprouter
- Detector::Go::Huma
- Detector::Go::Iris
- Detector::Go::Mux
- Detector::Go::Pocketbase
- Detector::Groovy::Cli
- Detector::Groovy::Grails
- Detector::Haskell::Cli
- Detector::Haskell::Scotty
- Detector::Haskell::Servant
- Detector::Haskell::Yesod
- Detector::Java::Armeria
- Detector::Java::Cli
- Detector::Java::Dropwizard
- Detector::Java::HttpServer
- Detector::Java::Javalin
- Detector::Java::JaxRs
- Detector::Java::Jsp
- Detector::Java::Micronaut
- Detector::Java::Play
- Detector::Java::Quarkus
- Detector::Java::Spark
- Detector::Java::Spring
- Detector::Java::Struts2
- Detector::Java::Vertx
- Detector::Java::Wicket
- Detector::Javascript::Adonisjs
- Detector::Javascript::Apollo
- Detector::Javascript::Astro
- Detector::Javascript::Cli
- Detector::Javascript::Elysia
- Detector::Javascript::Express
- Detector::Javascript::Fastify
- Detector::Javascript::Fresh
- Detector::Javascript::GraphqlYoga
- Detector::Javascript::Hapi
- Detector::Javascript::Hono
- Detector::Javascript::Http
- Detector::Javascript::Koa
- Detector::Javascript::Nestjs
- Detector::Javascript::Nextjs
- Detector::Javascript::Nitro
- Detector::Javascript::Nuxtjs
- Detector::Javascript::Remix
- Detector::Javascript::Restify
- Detector::Javascript::SocketIO
- Detector::Javascript::Sveltekit
- Detector::Kotlin::Cli
- Detector::Kotlin::Http4k
- Detector::Kotlin::Ktor
- Detector::Kotlin::Spring
- Detector::Lua::Cli
- Detector::Lua::Lapis
- Detector::Lua::Lor
- Detector::Mobile::Android
- Detector::Mobile::Ios
- Detector::Mobile::WellKnown
- Detector::Perl::Catalyst
- Detector::Perl::Cli
- Detector::Perl::Dancer2
- Detector::Perl::Mojolicious
- Detector::Php::CakePHP
- Detector::Php::Cli
- Detector::Php::CodeIgniter
- Detector::Php::Drupal
- Detector::Php::Hyperf
- Detector::Php::Laminas
- Detector::Php::Laravel
- Detector::Php::Lumen
- Detector::Php::Magento
- Detector::Php::Mautic
- Detector::Php::Php
- Detector::Php::Slim
- Detector::Php::Symfony
- Detector::Php::ThinkPHP
- Detector::Php::Wordpress
- Detector::Php::Yii
- Detector::Python::Aiohttp
- Detector::Python::Bottle
- Detector::Python::Cli
- Detector::Python::Django
- Detector::Python::DjangoNinja
- Detector::Python::Falcon
- Detector::Python::FastAPI
- Detector::Python::Flask
- Detector::Python::HttpServer
- Detector::Python::Litestar
- Detector::Python::Pyramid
- Detector::Python::Quart
- Detector::Python::Robyn
- Detector::Python::Sanic
- Detector::Python::Starlette
- Detector::Python::Tornado
- Detector::R::Plumber
- Detector::Ruby::ActionCable
- Detector::Ruby::Cli
- Detector::Ruby::Grape
- Detector::Ruby::Hanami
- Detector::Ruby::Rails
- Detector::Ruby::Roda
- Detector::Ruby::Sinatra
- Detector::Ruby::Webrick
- Detector::Rust::ActixWeb
- Detector::Rust::Axum
- Detector::Rust::Cli
- Detector::Rust::Gotham
- Detector::Rust::Loco
- Detector::Rust::Poem
- Detector::Rust::Rocket
- Detector::Rust::Rwf
- Detector::Rust::Salvo
- Detector::Rust::Tide
- Detector::Rust::Warp
- Detector::Scala::Akka
- Detector::Scala::Cli
- Detector::Scala::Http4s
- Detector::Scala::Play
- Detector::Scala::Scalatra
- Detector::Scala::Tapir
- Detector::Scala::ZioHttp
- Detector::Specification::ApacheHttpd
- Detector::Specification::Apisix
- Detector::Specification::Appwrite
- Detector::Specification::AsyncApi
- Detector::Specification::AwsCdk
- Detector::Specification::AwsCloudformation
- Detector::Specification::AzureFunctions
- Detector::Specification::Bruno
- Detector::Specification::Burp
- Detector::Specification::Caddy
- Detector::Specification::Caido
- Detector::Specification::CloudflareWrangler
- Detector::Specification::Directus
- Detector::Specification::Envoy
- Detector::Specification::GraphqlSdl
- Detector::Specification::Grpc
- Detector::Specification::Har
- Detector::Specification::Hasura
- Detector::Specification::HttpFile
- Detector::Specification::Insomnia
- Detector::Specification::IstioVirtualservice
- Detector::Specification::K8sGatewayApi
- Detector::Specification::K8sIngress
- Detector::Specification::Kamal
- Detector::Specification::Kong
- Detector::Specification::Mitmproxy
- Detector::Specification::Netlify
- Detector::Specification::Nginx
- Detector::Specification::Oas2
- Detector::Specification::Oas3
- Detector::Specification::OData
- Detector::Specification::OpenRpc
- Detector::Specification::PayloadCms
- Detector::Specification::Postman
- Detector::Specification::RAML
- Detector::Specification::ServerlessFramework
- Detector::Specification::Smithy
- Detector::Specification::Strapi
- Detector::Specification::Supabase
- Detector::Specification::Terraform
- Detector::Specification::Traefik
- Detector::Specification::TypeSpec
- Detector::Specification::Vercel
- Detector::Specification::WSDL
- Detector::Specification::ZapSitesTree
- Detector::Swift::Cli
- Detector::Swift::Hummingbird
- Detector::Swift::Kitura
- Detector::Swift::Vapor
- Detector::Typescript::Nestjs
- Detector::Typescript::TanstackRouter
- Detector::Typescript::TRPC
- Detector::Zig::Cli
- Detector::Zig::Http
- Detector::Zig::Httpz
- Detector::Zig::Jetzig
- Detector::Zig::Tokamak
- Detector::Zig::Zap
Defined in:
models/detector.crConstant Summary
-
CONTENT_MATCH_OPTIONS =
Noir::TextFile::MATCH_OPTIONS -
Detector content always arrives from
Noir::TextFile.read, so the subject is known-valid UTF-8 and PCRE2's per-call revalidation is skippable. SeeNoir::TextFile::MATCH_OPTIONS.
Constructors
Macro Summary
-
detector_for(tech, extensions = nil, basenames = nil, path_segments = nil, idempotent = nil)
Declares the detector's tech name and the cheap filename gate that lets the detect loop skip
#detecton files this detector cannot match.
Instance Method Summary
-
#applicable?(filename : String) : Bool
Cheap filename-only filter the detector pass uses to skip
#detecton files the detector cannot possibly match. -
#base_relative_path(filename : String) : String
filenamerelative to the scan base that owns it,/-separated and rooted with a leading/. -
#content_matches?(file_contents : String, markers : Regex) : Bool
Whether any alternative of a precompiled
Regex.unionappears infile_contents. - #detect(filename : String, file_contents : String) : Bool
-
#gemfile_dependency?(file_contents : String, gem_name : String) : Bool
Tolerant matcher for a Gemfile
gem "<name>"line. -
#gemspec_dependency?(file_contents : String, gem_name : String) : Bool
Tolerant matcher for a gemspec runtime dependency on
<name>, in either the space or parenthesized call form — gems routinely writes.add_dependency('sinatra', "~> 4.0")(geminabox) orspec.add_runtime_dependency "railties", neither of which the old"add_dependency 'sinatra'"substring markers matched. -
#idempotent? : Bool
Whether the detector can be skipped on subsequent files once it has matched.
- #logger : NoirLogger
- #name : String
-
#path_sensitive? : Bool
Whether
#applicable?inspects more than the basename — directory segments (/metadata/,/.kamal/) or root placement.
Constructor Detail
Macro Detail
Declares the detector's tech name and the cheap filename gate that lets
the detect loop skip #detect on files this detector cannot match.
class Gin < Detector
detector_for "go_gin", extensions: %w[.go], basenames: %w[go.mod]
end
Expands to the same short-circuiting chain the detectors used to write by
hand — one ends_with? / == per declared term, not a runtime loop
over an array — so the hot path is unchanged. Terms are emitted
extension, then path segment, then basename: extensions reject the most
files for the least work, and only a surviving candidate pays for
File.basename.
A path_segments term that names a directory ("/metadata/") implies
#path_sensitive?, and that link is the whole point. #applicable? is
memoized by basename, so a detector that consults directory segments
without declaring itself path-sensitive has its gate silently deleted —
that is how the Hasura metadata/** gate was lost. Declaring the
segment is declaring the sensitivity, so the two can no longer drift
apart.
A separator-free term ("go.mod") is a plain substring test on the whole
path and deliberately does not imply sensitivity, because
detector_path_sensitive? does not flag those today: the probe compares
#applicable?("a/b/c/go.mod") with #applicable?("go.mod"), which agree.
Declaring them sensitive would drop those detectors out of the basename
memo and slow the hot loop for no correctness gain here.
Pass idempotent: false for a detector whose #detect has side effects
(registering spec paths in CodeLocator), so the pass keeps calling it
after its first match.
A gate that is more than a term list — one that normalises separators,
checks a parent directory, or excludes .d.ts — keeps its hand-written
#applicable?. Pass just the tech name and define the method below; with
no terms the macro emits no gate to collide with.
Instance Method Detail
Cheap filename-only filter the detector pass uses to skip
#detect on files the detector cannot possibly match. The
default true preserves prior behavior (every detector runs on
every file). Override with the same predicate the body of
#detect starts with — e.g., filename.ends_with?(".py") for a
Python framework detector — so the detector loop avoids the
#detect dispatch on files outside the detector's language.
On large codebases (saleor's 4255 .py files) this lifts ~100
virtual #detect calls per file out of the hot loop because
most detectors' inner first-line is exactly this kind of cheap
filename check.
filename relative to the scan base that owns it, /-separated and
rooted with a leading /.
A detector that keys off a DIRECTORY (wp-content/, vendor/yiisoft/,
routes/) rather than a filename must match on this: on the absolute
path, a checkout that merely sat under a same-named directory made
every file in the project look like the framework's. Filename markers
(composer.json, Cargo.toml) are unaffected either way — an
ancestor directory contributes no filename.
Reads the roots from CodeLocator, which NoirRunner#detect publishes
before the walk; with none registered (detector unit specs) the path is
returned unchanged.
Whether any alternative of a precompiled Regex.union appears in
file_contents. With a union of plain literals this is exactly
OR-ing String#includes? over the same literals — Regex.union
escapes every String argument — but it costs one pass instead of N.
String#includes? runs Rabin-Karp: a rolling hash over every byte
position, restarted for each marker. PCRE2 JIT-compiles the union
into a program that skips ahead on a start-byte bitmap. Measured on
a 467 KB locale file against four non-matching markers: 2.16 ms of
chained includes? versus 26 µs here (82x). Even a single marker
over 300 small Ruby files is 13x, so the win is not a big-file
artifact.
Use this for the marker sweep at the top of #detect. A union is not
a substitute for a real pattern: keep purpose-built regexes as they
are, and keep a single includes? that merely gates an expensive
parse (there the substring check is the cheap half, not the cost).
Tolerant matcher for a Gemfile gem "<name>" line. Accepts both the
bare and parenthesized call forms with arbitrary spacing — gem 'x',
gem "x", gem('x'), gem( "x" ) — and a trailing version
constraint, while still requiring the closing quote right after the
name so gem 'sinatra' never matches gem 'sinatra-contrib'.
Tolerant matcher for a gemspec runtime dependency on <name>, in
either the space or parenthesized call form — gems routinely write
s.add_dependency('sinatra', "~> 4.0") (geminabox) or
spec.add_runtime_dependency "railties", neither of which the old
"add_dependency 'sinatra'" substring markers matched.
Whether the detector can be skipped on subsequent files once it
has matched. Defaults to true (idempotent — the detector only
signals tech presence). Detectors that perform side effects in
#detect (e.g., the C# ASP.NET ones populate the CodeLocator
with route-config paths, the OAS/RAML detectors register spec
paths) must override to false so the detector pass keeps
invoking them on every file.
Whether #applicable? inspects more than the basename — directory
segments (/metadata/, /.kamal/) or root placement.
The detect loop memoizes #applicable? by basename, so a detector
that consults the path but does not declare it here has its path
gate silently deleted: #applicable? is only ever asked about the
bare filename. That is a false-negative, not a crash, so nothing
fails loudly. detector_path_sensitive? also probes for this, but
the probe is fail-open — a probe whose basename independently
matches masks the directory gate behind it (this is exactly how the
Hasura metadata/** gate was lost). Declare it explicitly.
Guarded by spec/unit_test/detector/applicable_lookup_fidelity_spec.cr.