class

Analyzer::Groovy::Cli

Inherits CliEndpointSupport < Analyzer < FileHelper < Reference < Object

Surfaces the command-line attack surface of Groovy programs as cli:// endpoints: the built-in CliBuilder (and picocli @Option), JCommander (@Parameter / addCommand subcommands) and Commons CLI (Option.builder / addOption), plus System.getenv. Line-scan; attribution for JCommander subcommands additionally uses a per-file pre-scan (variable -> class -> command name) so it can resolve both the inline addCommand("name", new Class()) form and the more common declare-then-register addCommand("name", instance) form. Root attribution otherwise (CliBuilder/Commons CLI are flat), merged by URL.

NOTE: like CliBuilder, this is a per-file analyzer. When a JCommander subcommand class lives in its own file (registration call in one file, @Parameter fields in another), the class-to-command mapping built here won't span files, so that subcommand's fields fall back to that file's own root endpoint instead of being attributed under the real subcommand's URL. A cross-file pre-pass would be needed to close that gap; not attempted here.

Constants

CLASS_DECL = /\bclass\s+([A-Za-z_]\w*)/
CLI_MARKER = /\bimport\s+org\.apache\.commons\.cli\b|\bOption\.builder\s*\(/

--- Commons CLI ------------------------------------------------------

CLI_OPT = /\bcli\.([A-Za-z_]\w*)\s*\(([^)]*)/
CLI_TEST_PATH_RE = Regex.union("/test/", "spec.groovy", "test.groovy")

One precompiled Regex.union scan (PCRE2 JIT) replaces three separate String#includes? scans of the same buffer -- Crystal's includes? is not Boyer-Moore accelerated, so a single regex pass over the downcased path is cheaper than three. Equivalent to the OR-of-substrings it replaces (union escapes each literal).

COMMONS_CLI_ADD_OPT = /\.addOption\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]\s*,/
COMMONS_CLI_BUILDER = /Option\.builder\s*\(\s*(?:['"]([^'"]*)['"])?\s*\)/
COMMONS_CLI_LONGOPT = /\.longOpt\s*\(\s*['"]([^'"]+)['"]/
GET_ENV = /\bSystem\.getenv\s*\(\s*['"]([^'"]+)['"]/
JC_ADD_CMD_STR = /\.addCommand\s*\(\s*['"]([^'"]+)['"]\s*,\s*new\s+([A-Za-z_]\w*)\s*\(/
JC_ADD_CMD_VAR = /\.addCommand\s*\(\s*['"]([^'"]+)['"]\s*,\s*([A-Za-z_]\w*)\s*\)/
JC_COMMAND_NAMES = /@Parameters\s*\([^)]*\bcommandNames\s*=\s*\{?\s*['"]([^'"]+)['"]/
JC_MARKER = /\bimport\s+com\.beust\.jcommander\b|\bnew\s+JCommander\s*\(|\bJCommander\.newBuilder\s*\(/

--- JCommander ----------------------------------------------------- Gated on library-specific constructs only (never a bare @Parameter, which is too generic on its own) so unrelated annotations/classes named similarly don't light this up.

JC_PARAMETER = /@Parameter\s*\(([^)]*)\)/
LONGOPT = /longOpt:\s*['"]([^'"]+)['"]/
MARKERS = /\bnew\s+CliBuilder\b|\bCliBuilder\s*\(|@picocli|@Command\b/
NON_OPTION = Set {"parse", "usage", "with", "width", "header", "footer", "stopAtNonOption", "expandArgumentFiles", "posix", "errorWriter", "writer", "name"}

CliBuilder methods that are not option definitions.

OPTION_ATTR = /@Option\s*\(([^)]*)\)/
VAR_NEW_DECL = /\b[A-Za-z_]\w*\s+([A-Za-z_]\w*)\s*=\s*new\s+([A-Za-z_]\w*)\s*\(/
WEB_RE = /\bimport\s+(?:grails|org\.springframework)\b|@Controller\b|@RestController\b/

Class methods

tech_name
Source

Instance methods

analyze
Source
tech

Instance-side view of the same declaration. The per-file rescues live on this base class, which has no way to name the analyzer that is running inside them, so a skipped file could not be attributed to a tech. Deriving it from analyzer_for keeps the name written exactly once.

Source