moongrep
moongrep is an experimental structural search and taint-analysis tool for
MoonBit.
Quick Start
To check the current MoonBit project with the embedded builtin rules, run:
moongrep lint
lint is the command that loads embedded builtin rules. It defaults to the
current directory and accepts the scan options for adding custom rules,
filtering findings, or changing the output format.
For structural search, run the scan command from the root of a MoonBit
project. By default, it scans recursively and skips directories generated by
Git and the MoonBit toolchain. Specify an expression pattern to match with
the --pattern option. For example, the following command matches a typical
expression that uses match on an Option value:
moongrep scan --pattern 'match $(value:exp) { Some($(some:id)) => $(some_body:exp); None => $(none_body:exp) }'
Note: All examples in this document use the direct moongrep command form. To
use the WebAssembly CLI instead, replace moongrep with
moonx moonbit-community/moongrep --.
moongrep outputs scan results directly as it scans. For a better terminal
reading experience, human users should use it with a terminal pager such as
less:
moongrep scan --pattern 'match $(value:exp) { Some($(some:id)) => $(some_body:exp); None => $(none_body:exp) }' | less -R
Expression Patterns and Metavariables
moongrep parses both the expression pattern and the MoonBit code being
scanned into untyped concrete syntax trees (CSTs), then compares the structures of the
two trees. Ordinary MoonBit syntax in a pattern represents fixed structure.
Metavariables represent syntax nodes to match and capture. Formatting
differences such as line breaks and indentation generally do not affect
matching.
Metavariables use the following format:
$(name:kind)
name is the metavariable name. When a match succeeds, the code at that
position is recorded under this name. kind specifies the category of syntax
node that may be matched.
Common kind values include:
- exp: matches a complete expression, such as a variable, function call,
field access, or if expression;
- id: matches an identifier, such as a variable name, parameter name, or a
name bound in a pattern;
- const: matches a literal constant, such as an integer, string, or Boolean
value;
- arg: matches a complete function-call argument;
- pat: matches a complete pattern;
- type: matches a complete type.
The following pattern matches a match expression with both Some and None
branches:
match $(value:exp) {
Some($(some:id)) => $(some_body:exp)
None => $(none_body:exp)
}
In this pattern:
- $(value:exp) captures the expression examined by match;
- $(some:id) captures the identifier bound by the Some pattern;
- $(some_body:exp) captures the expression in the Some branch;
- $(none_body:exp) captures the expression in the None branch.
This pattern can match:
match load_user() {
Some(user) => display(user)
None => show_error()
}
The match produces the following captures:
value = load_user()
some = user
some_body = display(user)
none_body = show_error()
The match, Some, and None syntax and the positions of the two branches
are fixed structure in the pattern. An expression with Ok and Err branches
does not match it. Some(1) does not match Some($(some:id)) either, because
1 is syntactically a constant. $(some:id) requires an identifier in that
position.
The same named metavariable may appear multiple times in a pattern. Repeated
captures must agree according to their kind: id compares normalized names,
const compares parsed constants, and CST-valued kinds such as exp, arg,
pat, and type compare syntax-tree structure while ignoring source
locations. For example:
$(value:exp) == $(value:exp)
This pattern can match:
user.name == user.name
The following expression does not match the pattern:
user.name == other.name
Here value is an exp capture, so its two syntax trees must be structurally
equal; source locations are not considered.
$_ is a discard placeholder. It matches any content at its position without
recording a capture. Multiple $_ placeholders in the same pattern are
independent of one another.
Structured Output
By default, moongrep produces a report intended for human readers. To make
its output suitable for a coding agent, add the --output-json option:
moongrep scan --pattern 'inspect($_, content="true")' --output-json
moongrep dump --output-json --expr 'x + 1'
In JSON mode, every nonempty application-output line is a compact JSON object.
For scan and lint, standard output contains only finding records; for
dump, a successful non-exit-code invocation writes one dump record there.
Standard error contains only trace, warning, and error records. When no
match is found, standard output is empty. Records are streamed in traversal
order and a later failure does not withdraw records already written.
The stable record shapes are:
finding: { "type":"finding", "file", "rule_id", "description", "range",
"matched_source", "source_context" }
warning: { "type":"warning", "category", "message", ...details }
trace: { "type":"trace", "event", ...event fields }
dump: { "type":"dump", "kind":"impl"|"expr", "content" }
error: { "type":"error", "category", "exit_code", "message",
...optional diagnostic fields }
Warning categories are parse and invalid_skip_payload. Trace events are
rule_loaded, directory_entered, path_skipped, and file_started. Error
categories are internal, usage, dump_input, rule_source,
rule_content, scan_input, and output.
The CLI detects a standalone --output-json for an initial scan, lint, or
dump before full argument parsing, so usage errors such as unknown options
and missing values are also JSON. --output-json after a -- option terminator
is not treated as an option. Successful help remains ordinary text.
Pattern Guards
Use --guard to add filters to an expression pattern. The filters apply to
the contents captured by metavariables in the pattern.
The following example uses --guard to find inspect calls whose expected
value is a number.
moongrep scan --pattern 'inspect($_, content=$(str:const))' --guard '{$str: "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"}'
The argument to --guard is a YAML map and normally immediately follows the
--pattern it filters:
--pattern '...$(name:id)...$(value:const)...' \
--guard '{$name: "regular expression", $value: "regular expression"}'
Each key must be a named metavariable declared in the pattern and must retain
the $ prefix. Each value must be a regular-expression string. Enclose the
entire YAML map in single quotes to prevent the shell from expanding $name,
and enclose each regular expression in double quotes. Backslashes in a
double-quoted YAML string must be escaped; for example, write \. in a regular
expression as \\..
Guards currently support only id and const captures. They cannot filter
exp, arg, pat, type, ellipsis captures, or $_. For an id capture,
the regular expression is matched against the normalized identifier, such as
name or @pkg.name. For a const capture, it is matched against the constant
value produced by the parser. For example, the string literal "raw" produces
raw, the number 42 produces 42, and the Boolean value true produces
true.
Regular expressions use substring matching by default. For example, "raw"
also matches "draw". To match the entire captured value, use ^ and $, as
in "^raw$". All conditions in a map must match, so a guard can constrain both
a function name and its argument:
moongrep scan --pattern '$(callee:id)($(value:const))' --guard '{$callee: "^@html\\.render$", $value: "^(danger|raw)$"}'
Each --pattern accepts at most one --guard. To scan multiple guarded
patterns, provide --pattern and --guard in pairs. To apply multiple
conditions to one pattern, put them in the same YAML map.
Dump CST
MoonBit untyped_cst debug dumps are available through the dump subcommand:
moongrep dump --impl 'fn answer { 42 }'
moongrep dump --expr 'x + 1'
moongrep dump --output-json --expr 'x + 1'
moongrep dump --exit-code --expr 'x + 1'
Command-line synopsis:
moongrep dump [--exit-code] [--output-json] (--impl <impl> | --expr <expr>)
Use --impl <impl> to parse a MoonBit top-level implementation item and print
its untyped_cst debug output. Use --expr <expr> to parse a MoonBit
expression and print its untyped_cst debug output. To produce a dump, provide
exactly one of these mutually exclusive options.
Use --output-json to write one compact record with type set to "dump",
kind set to "impl" or "expr", and content containing the same CST
Repr text. JSON escaping keeps the record on one physical output line.
Use --exit-code to perform the same parse validation without printing the CST.
A successful check writes nothing to either output stream and exits with code
0, even when --output-json is also present. Invalid input still prints its
diagnostic and exits with code 3; with --output-json, it uses the existing
JSON error record schema.
Invoking moongrep dump without arguments prints the dump help and exits
successfully with code 0. Usage errors, such as combining --impl with
--expr, print a message and exit with code 2. Parse or lexical failures print
a message and exit with code 3. dump --output-json without an input reports a
JSON usage error, while dump --output-json --help still prints ordinary help.
Exit Status
moongrep returns 0 for successful help, dump, finding, no-finding, and
recoverable scan-warning outcomes. Failures use these fixed categories:
| Code | Meaning |
|---|
| 1 | Internal or unclassified error, including damaged builtin rules |
| 2 | Command-line usage error |
| 3 | Invalid dump input |
| 4 | Missing, unreadable, or incorrectly typed rule source, or a rule directory containing no .yaml or .yml files |
| 5 | Blank or invalid YAML, an unsupported single-file --rule suffix, or invalid rule schema, pattern, guard, or compiled rule content |
| 6 | Missing or unreadable scan input |
| 7 | Standard-output or standard-error write failure |
No-match scans do not use grep-style status 1. Source parse warnings also keep
status 0.
Documentation
The repository contains these detailed references:
- RuleSpec specifies YAML rules, validation, and matcher
semantics.
- CLISpec specifies command-line parsing, scanning, output,
diagnostics, and exit behavior.
Their English versions are also available through the docs subcommand:
moongrep docs --list
moongrep docs RuleSpec
moongrep docs CLISpec