Skip to content

Commit fb5cbce

Browse files
committed
tighten #moongrep.skip contract
1 parent a881ae0 commit fb5cbce

15 files changed

Lines changed: 499 additions & 111 deletions

SKILL.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,41 @@ moongrep scan --pattern 'inspect($_, content="true")' --output-json
143143
```
144144

145145
JSON match records are written to standard output, one per line. Verbose
146-
traversal messages and parse warnings are written to standard error. When no
146+
traversal messages and scan warnings are written to standard error. When no
147147
match is found, JSON mode writes nothing to standard output.
148148

149+
## Suppressing Structural Rules in Source
150+
151+
Use a bare `#moongrep.skip` attribute to suppress structural rules for one
152+
top-level item:
153+
154+
```moonbit
155+
#moongrep.skip
156+
fn generated_adapter {
157+
legacy_call()
158+
}
159+
```
160+
161+
The attribute is supported on function definitions, impl methods, top-level
162+
`let` definitions, tests, and view declarations. It suppresses all structural
163+
rules within the attached item. Taint analysis is never suppressed and still
164+
runs on marked function definitions and impl methods.
165+
166+
The attribute does not accept a payload. Forms such as `#moongrep.skip()`,
167+
`#moongrep.skip(true)`, `#moongrep.skip(false)`, other payloads, and malformed
168+
payloads do not suppress structural rules. Each invalid payload produces this
169+
warning on standard error, and scanning continues without changing the exit
170+
status:
171+
172+
```text
173+
warning: <location>: #moongrep.skip does not accept a payload; use bare #moongrep.skip
174+
```
175+
176+
Unrelated attributes such as `#skip` and `#other.skip` are ignored. If an item
177+
has both a valid bare attribute and one or more invalid payload forms, each
178+
invalid form produces a warning while the bare attribute still suppresses
179+
structural rules.
180+
149181
## Pattern Guards
150182

151183
Use `--guard` to add filters to an expression pattern. The filters apply to

SKILL_CN.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,36 @@ user.name == other.name
112112
moongrep scan --pattern 'inspect($_, content="true")' --output-json
113113
```
114114

115-
JSON 匹配记录会逐行写入标准输出。详细遍历信息和解析 warning 会写入标准错误;
115+
JSON 匹配记录会逐行写入标准输出。详细遍历信息和扫描 warning 会写入标准错误;
116116
JSON 模式下没有命中时,标准输出为空。
117117

118+
## 在源码中抑制结构规则
119+
120+
使用裸 `#moongrep.skip` 属性,可以抑制一个顶层项中的结构规则:
121+
122+
```moonbit
123+
#moongrep.skip
124+
fn generated_adapter {
125+
legacy_call()
126+
}
127+
```
128+
129+
该属性支持函数定义、impl 方法、顶层 `let` 定义、test 和 view 声明。它会抑制
130+
所附顶层项内的全部结构规则。污点分析不会被抑制;对于带标记的函数定义和
131+
impl 方法,污点分析仍会运行。
132+
133+
该属性不接受 payload。`#moongrep.skip()``#moongrep.skip(true)`
134+
`#moongrep.skip(false)`、其他 payload 和畸形 payload 都不会抑制结构规则。
135+
每个无效 payload 都会在标准错误输出以下 warning,扫描会继续,退出码不变:
136+
137+
```text
138+
warning: <location>: #moongrep.skip does not accept a payload; use bare #moongrep.skip
139+
```
140+
141+
`#skip``#other.skip` 等无关属性会被忽略。如果同一顶层项同时带有有效的裸属性
142+
和一个或多个无效 payload,每个无效形式仍会产生 warning,而裸属性仍会抑制
143+
结构规则。
144+
118145
## 模式附加条件
119146

120147
`--guard`选项可以为表达式模式添加额外的筛选条件,筛选条件的对象是表达式模式里面的元变量捕获的内容。

TODO.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@
88
an explicit kind for cross-position reuse, improving the diagnostic, or
99
adding a storage-path metavariable kind.
1010

11-
- Define and document the contract for `#moongrep.skip`. It is currently
12-
recognized on functions, impls, top-level lets, tests, and views; ignores
13-
any payload, including `false`; and suppresses structural rules but not
14-
taint analysis. If it remains public, cover those semantics in the user and
15-
e2e documentation. Otherwise, make it internal or remove it.
16-
1711
- Clarify constant matching in the RuleSpec overview. Constants are compared
1812
using the parser AST constant kind and preserved source spelling, so
1913
equivalent values such as `1000` and `1_000` do not necessarily match.

cli/json_render.mbt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ fn render_scan_hit_json(hit : ScanHit) -> String {
6767
}
6868

6969
///|
70-
// JSON stdout is finding-only: scan trace, parse warnings, and the human
70+
// JSON stdout is finding-only: scan trace, scan warnings, and the human
7171
// no-match summary are omitted so every non-empty line is a machine-readable
72-
// finding record. Parse warnings are written separately to stderr.
72+
// finding record. Scan warnings are written separately to stderr.
7373
fn render_directory_scan_json(result : DirectoryScanResult) -> String {
7474
let records : Array[String] = []
7575
for hit in result.hits {

cli/json_render_wbtest.mbt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,21 @@ test "json directory output contains only one line per finding" {
8484
render_directory_scan_json({
8585
scan_trace: ["trace"],
8686
hits: [],
87-
skipped_files: [
88-
{ file: "bad.mbt", block_start_line: None, reason: "parse failed" },
87+
warnings: [
88+
Parse({
89+
file: "bad.mbt",
90+
block_start_line: None,
91+
reason: "parse failed",
92+
}),
8993
],
9094
}),
9195
content="",
9296
)
9397
let rendered = render_directory_scan_json({
9498
scan_trace: ["trace"],
9599
hits: [render_test_hit(3, 3), render_test_hit(8, 8)],
96-
skipped_files: [
97-
{ file: "bad.mbt", block_start_line: None, reason: "parse failed" },
100+
warnings: [
101+
Parse({ file: "bad.mbt", block_start_line: None, reason: "parse failed" }),
98102
],
99103
})
100104
let records = rendered.split("\n").to_array()

cli/render.mbt

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
///|
2-
// Renders warnings first and match hits second so parse failures never hide
2+
// Renders warnings first and match hits second so scan diagnostics never hide
33
// successful findings from other files.
44
fn render_directory_scan_result(
55
result : DirectoryScanResult,
@@ -9,7 +9,7 @@ fn render_directory_scan_result(
99
if result.scan_trace.length() > 0 {
1010
sections.push(result.scan_trace.join("\n"))
1111
}
12-
let warnings = render_skipped_file_warnings(result.skipped_files)
12+
let warnings = render_scan_warnings(result.warnings)
1313
if warnings != "" {
1414
sections.push(warnings)
1515
}
@@ -26,20 +26,25 @@ fn render_directory_scan_result(
2626
}
2727

2828
///|
29-
fn render_skipped_file_warnings(skipped_files : Array[SkippedFile]) -> String {
30-
let warnings : Array[String] = []
31-
for skipped in skipped_files {
32-
warnings.push(render_skipped_file_warning(skipped))
29+
fn render_scan_warnings(scan_warnings : Array[ScanWarning]) -> String {
30+
let rendered : Array[String] = []
31+
for warning in scan_warnings {
32+
rendered.push(render_scan_warning(warning))
3333
}
34-
warnings.join("\n")
34+
rendered.join("\n")
3535
}
3636

3737
///|
38-
fn render_skipped_file_warning(skipped : SkippedFile) -> String {
39-
if skipped.block_start_line is Some(line) {
40-
"warning: skipping \{skipped.file} block starting at line \{line}: \{skipped.reason}"
41-
} else {
42-
"warning: skipping \{skipped.file}: \{skipped.reason}"
38+
fn render_scan_warning(warning : ScanWarning) -> String {
39+
match warning {
40+
Parse(parse_warning) =>
41+
if parse_warning.block_start_line is Some(line) {
42+
"warning: skipping \{parse_warning.file} block starting at line \{line}: \{parse_warning.reason}"
43+
} else {
44+
"warning: skipping \{parse_warning.file}: \{parse_warning.reason}"
45+
}
46+
InvalidMoongrepSkipPayload(file, loc) =>
47+
"warning: \{format_location(file, loc)}: #moongrep.skip does not accept a payload; use bare #moongrep.skip"
4348
}
4449
}
4550

0 commit comments

Comments
 (0)