Skip to content

Commit bac6d0c

Browse files
committed
add --exclude-rules
1 parent 8f5be29 commit bac6d0c

8 files changed

Lines changed: 208 additions & 24 deletions

File tree

SKILL.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ moon runwasm moonbit-community/moongrep -- scan --pattern '$(callee:id)()' --gua
1717
Synopsis:
1818

1919
```text
20-
moon runwasm moonbit-community/moongrep -- scan [--verbose] [--enable-builtin-rules] [--exclude-dir <dir>...] ((--rules <rules-root> | --rules=<rules-root> | -r <rules-root> | --rule <rule-file>) | --pattern <pattern> [--guard <guard>])... [scan-root]
20+
moon runwasm moonbit-community/moongrep -- scan [--verbose] [--enable-builtin-rules] [--exclude-dir <dir>...] [--exclude-rules <rule-id>...] ((--rules <rules-root> | --rules=<rules-root> | -r <rules-root> | --rule <rule-file>) | --pattern <pattern> [--guard <guard>])... [scan-root]
2121
```
2222

2323
The scanner is available through the `scan` subcommand. `--rules` / `-r` is
@@ -39,11 +39,18 @@ scanning the source tree. When passing multiple excluded directories after one
3939
flag, put `scan-root` before `--exclude-dir`; repeated `--exclude-dir <dir>` and
4040
`--exclude-dir=<dir>` forms are also accepted.
4141

42+
Use `--exclude-rules <rule-id>...` to disable loaded rules by exact rule id.
43+
This applies to builtin, file, directory, and anonymous pattern rules. When
44+
passing multiple excluded rule ids after one flag, put `scan-root` before
45+
`--exclude-rules`; repeated `--exclude-rules <rule-id>` and
46+
`--exclude-rules=<rule-id>` forms are also accepted. Unknown excluded rule ids
47+
are usage errors.
48+
4249
Usage errors print a message and exit with code 2: missing `scan` command,
4350
missing all rule sources (`--rules`, `--rule`, `--pattern`, and
4451
`--enable-builtin-rules`), missing option value, misplaced or malformed
45-
`--guard`, unknown options, or more than one scan root. Non-usage errors,
46-
including unreadable paths, an empty rules
52+
`--guard`, unknown options, unknown excluded rule ids, or more than one scan
53+
root. Non-usage errors, including unreadable paths, an empty rules
4754
directory, invalid YAML/schema/shape, or source read failures, abort the run;
4855
the CLI prints the error and exits with code 1.
4956

cli_args.mbt

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ priv struct CliOptions {
1212
patterns : Array[@rule_model.RulePatternSpec]
1313
scan_root : String
1414
exclude_dirs : Array[String]
15+
exclude_rules : Array[String]
1516
verbose : Bool
1617
enable_builtin_rules : Bool
1718
no_color : Bool
@@ -65,6 +66,11 @@ let scan_command : @argparse.Command = Command(
6566
action=Append,
6667
about="Directory name or path to skip while recursively scanning.",
6768
),
69+
OptionArg(
70+
"exclude-rules",
71+
action=Append,
72+
about="Rule id to disable after loading rules.",
73+
),
6874
],
6975
positionals=[PositionArg("scan-root", about="Directory to scan.")],
7076
)
@@ -229,6 +235,7 @@ fn scan_cli_options(
229235
let exclude_dirs = normalize_scan_exclude_dirs(
230236
all_cli_values(scan_matches.values, "exclude-dir"),
231237
)
238+
let exclude_rules = all_cli_values(scan_matches.values, "exclude-rules")
232239
if rules_root is None &&
233240
rule_file is None &&
234241
patterns.is_empty() &&
@@ -250,6 +257,7 @@ fn scan_cli_options(
250257
patterns,
251258
scan_root,
252259
exclude_dirs,
260+
exclude_rules,
253261
verbose,
254262
enable_builtin_rules,
255263
no_color,
@@ -366,28 +374,28 @@ fn parse_cli_guard_map(source : String) -> Map[String, String] raise {
366374
}
367375

368376
///|
369-
fn normalize_scan_exclude_dir_args(argv : Array[String]) -> Array[String] raise {
377+
fn normalize_scan_multi_value_option_args(
378+
argv : Array[String],
379+
option_names : Array[String],
380+
) -> Array[String] raise {
370381
if argv.length() == 0 || argv[0] != "scan" {
371382
return argv
372383
}
373384
let normalized : Array[String] = [argv[0]]
374385
let mut index = 1
375386
while index < argv.length() {
376387
let arg = argv[index]
377-
if arg == "--exclude-dir" {
388+
if option_names.contains(arg) {
378389
let mut value_count = 0
379390
index += 1
380391
while index < argv.length() && !argv[index].has_prefix("-") {
381-
normalized.push("--exclude-dir")
392+
normalized.push(arg)
382393
normalized.push(argv[index])
383394
value_count += 1
384395
index += 1
385396
}
386397
if value_count == 0 {
387-
raise CliError::Usage(
388-
message="missing value for --exclude-dir",
389-
exit_code=2,
390-
)
398+
raise CliError::Usage(message="missing value for \{arg}", exit_code=2)
391399
}
392400
} else {
393401
normalized.push(arg)
@@ -405,18 +413,22 @@ fn normalize_scan_exclude_dir_args(argv : Array[String]) -> Array[String] raise
405413
/// `--rule <rule-file>`, at least one `--pattern <pattern>`, or
406414
/// `--enable-builtin-rules` is required, and `--verbose` is optional.
407415
/// `--exclude-dir <dir>...` skips matching child directory names or paths
408-
/// during recursive source scanning.
416+
/// during recursive source scanning. `--exclude-rules <rule-id>...` disables
417+
/// loaded rules by exact rule id before matching.
409418
/// One optional scan-root positional may appear in the argument list. If the
410419
/// rules option or single rule option appears multiple times, the last value
411-
/// wins. Repeated patterns and exclude directories are appended in order.
420+
/// wins. Repeated patterns, exclude directories, and excluded rules are
421+
/// appended in order.
412422
/// Missing subcommands, required arguments, unknown options, or more than one
413423
/// scan root raise `CliError::Usage` with exit code 2.
414424
#moongrep.skip
415425
fn parse_cli_command(
416426
argv : Array[String],
417427
env? : Map[String, String] = {},
418428
) -> (String, CliOptions?, String?) raise {
419-
let normalized_argv = normalize_scan_exclude_dir_args(argv)
429+
let normalized_argv = normalize_scan_multi_value_option_args(argv, [
430+
"--exclude-dir", "--exclude-rules",
431+
])
420432
let matches = moongrep_command.parse(argv=normalized_argv, env~) catch {
421433
err => {
422434
let message = "\{err}"

cli_wbtest.mbt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,27 @@ test "cli args parse scan exclude dirs" {
210210
}
211211
}
212212

213+
///|
214+
test "cli args parse scan exclude rules" {
215+
match
216+
parse_cli_command([
217+
"scan", "--rules", "custom-rules", "src", "--exclude-rules", "target", "other",
218+
"--exclude-rules=third", "--exclude-rules", "last",
219+
]) {
220+
("scan", Some(options), None) => {
221+
expect_rules_root(options, "custom-rules")
222+
inspect(options.scan_root, content="src")
223+
debug_inspect(
224+
options.exclude_rules,
225+
content="[\"target\", \"other\", \"third\", \"last\"]",
226+
)
227+
assert_false(options.verbose)
228+
assert_false(options.enable_builtin_rules)
229+
}
230+
_ => fail("unexpected command")
231+
}
232+
}
233+
213234
///|
214235
test "cli args reject json matcher flag" {
215236
expect_cli_usage(
@@ -353,6 +374,11 @@ test "cli args reject missing rules value" {
353374
expect_cli_usage(["scan", "--rules"], 2)
354375
}
355376

377+
///|
378+
test "cli args reject missing exclude rules value" {
379+
expect_cli_usage(["scan", "--rules", "custom-rules", "--exclude-rules"], 2)
380+
}
381+
356382
///|
357383
test "cli args parse builtin rules flag without rules root" {
358384
match parse_cli_command(["scan", "--enable-builtin-rules", "src"]) {

e2etests/BASIC.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ Arguments:
2828
scan-root Directory to scan.
2929
3030
Options:
31-
-h, --help Show help information.
32-
--verbose Print loaded rule ids and directory traversal progress.
33-
--enable-builtin-rules Enable embedded builtin rules.
34-
-r, --rules <rules> Directory containing YAML rules.
35-
--rule <rule> Single YAML rule file.
36-
--pattern <pattern> Anonymous structural pattern to match.
37-
--guard <guard> YAML guard map for the preceding anonymous pattern.
38-
--exclude-dir <exclude-dir> Directory name or path to skip while recursively scanning.
31+
-h, --help Show help information.
32+
--verbose Print loaded rule ids and directory traversal progress.
33+
--enable-builtin-rules Enable embedded builtin rules.
34+
-r, --rules <rules> Directory containing YAML rules.
35+
--rule <rule> Single YAML rule file.
36+
--pattern <pattern> Anonymous structural pattern to match.
37+
--guard <guard> YAML guard map for the preceding anonymous pattern.
38+
--exclude-dir <exclude-dir> Directory name or path to skip while recursively scanning.
39+
--exclude-rules <exclude-rules> Rule id to disable after loading rules.
3940
```
4041

4142
## moongrep scan --enable-builtin-rules

e2etests/SCAN.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,25 @@ source:
138138
7 | }
139139
```
140140

141+
```mooncram
142+
$ cd "$TESTDIR"/.. && moonrun "$TESTDIR"/moongrep.wasm -- scan --rules e2etests/rules/prefilter testdata/prefilter-impl --exclude-rules target
143+
testdata/prefilter-impl/hits.mbt:6:3-6:10
144+
rule: other
145+
description:
146+
Other call.
147+
source:
148+
4 |
149+
5 | fn second {
150+
6 > other()
151+
7 | }
152+
```
153+
154+
```mooncram
155+
$ cd "$TESTDIR"/.. && moonrun "$TESTDIR"/moongrep.wasm -- scan --rules e2etests/rules/prefilter testdata/prefilter-impl --exclude-rules missing
156+
unknown rule id in --exclude-rules: missing
157+
[2]
158+
```
159+
141160
```mooncram
142161
$ cd "$TESTDIR"/.. && moonrun "$TESTDIR"/moongrep.wasm -- scan --rules e2etests/rules/structural testdata/custom-rules
143162
testdata/custom-rules/hit.mbt:1:13-1:21

main.mbt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
///|
2+
#cfg(target="wasm")
3+
fn runtime_exit(code : Int) -> Unit = "wasi_snapshot_preview1" "proc_exit"
4+
5+
///|
6+
#cfg(target="wasm-gc")
7+
fn runtime_exit(code : Int) -> Unit = "__moonbit_sys_unstable" "exit"
8+
19
///|
210
fn write_cli_output(output : String) -> Unit {
311
match output.strip_suffix("\n") {
@@ -27,9 +35,9 @@ async fn main {
2735
_ => fail("invalid CLI command")
2836
}
2937
} catch {
30-
CliError::Usage(message~, ..) as err => {
38+
CliError::Usage(message~, exit_code~) => {
3139
write_cli_output(message)
32-
raise err
40+
runtime_exit(exit_code)
3341
}
3442
err => raise err
3543
}

scan.mbt

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ priv struct SourceContextLine {
3434
///|
3535
async fn collect_directory_hits(options : CliOptions) -> DirectoryScanResult {
3636
let raw_rules = load_scan_rules(options)
37-
let compiled_rules = @rule_compile.compile_rules(raw_rules)
37+
let enabled_rules = exclude_scan_rules(raw_rules, options.exclude_rules)
38+
let compiled_rules = @rule_compile.compile_rules(enabled_rules)
3839
let scan_plan = @rule_apply.ScanPlan::from_rules(compiled_rules)
3940
let files : Array[String] = []
4041
let scan_trace : Array[String] = []
@@ -161,6 +162,47 @@ async fn load_scan_rules(
161162
raw_rules
162163
}
163164

165+
///|
166+
fn exclude_scan_rules(
167+
raw_rules : Array[@rule_model.RawRuleSpec],
168+
exclude_rules : Array[String],
169+
) -> Array[@rule_model.RawRuleSpec] raise {
170+
if exclude_rules.is_empty() {
171+
return raw_rules
172+
}
173+
ensure_exclude_rule_ids_exist(raw_rules, exclude_rules)
174+
let excluded : Map[String, Bool] = {}
175+
for rule_id in exclude_rules {
176+
excluded[rule_id] = true
177+
}
178+
let enabled : Array[@rule_model.RawRuleSpec] = []
179+
for raw in raw_rules {
180+
if !excluded.contains(raw.rule_id) {
181+
enabled.push(raw)
182+
}
183+
}
184+
enabled
185+
}
186+
187+
///|
188+
fn ensure_exclude_rule_ids_exist(
189+
raw_rules : Array[@rule_model.RawRuleSpec],
190+
exclude_rules : Array[String],
191+
) -> Unit raise {
192+
let loaded : Map[String, Bool] = {}
193+
for raw in raw_rules {
194+
loaded[raw.rule_id] = true
195+
}
196+
for rule_id in exclude_rules {
197+
if !loaded.contains(rule_id) {
198+
raise CliError::Usage(
199+
message="unknown rule id in --exclude-rules: \{rule_id}",
200+
exit_code=2,
201+
)
202+
}
203+
}
204+
}
205+
164206
///|
165207
fn anonymous_pattern_rule(
166208
pattern : @rule_model.RulePatternSpec,

0 commit comments

Comments
 (0)