Skip to content

Commit 207e958

Browse files
committed
收紧共享后缀的 WASM 语法路由
1 parent 874973c commit 207e958

10 files changed

Lines changed: 80 additions & 7 deletions

File tree

‎crates/codeguard-cli/src/grammar_route.rs‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@ pub fn route_source<'a>(relative_path: &str, source: &'a [u8]) -> Vec<GrammarRou
3636
Some("lua") => "lua",
3737
Some("luau") => "luau",
3838
Some("nix") => "nix",
39-
Some("m" | "mm") => "objc",
39+
Some("m") if has_objc_marker(source) => "objc",
40+
Some("mm") => "objc",
4041
Some("pas" | "dpr" | "dpk" | "lpr") => "pascal",
4142
Some("php") => "php",
4243
Some("py") => "python",
4344
Some("r") => "r",
4445
Some("rb") => "ruby",
4546
Some("rs") => "rust",
46-
Some("scala" | "sc") => "scala",
47+
Some("scala") => "scala",
4748
Some("sol") => "solidity",
4849
Some("swift") => "swift",
4950
Some("tf" | "tfvars" | "tofu") => "terraform",
@@ -94,6 +95,30 @@ pub fn route_source<'a>(relative_path: &str, source: &'a [u8]) -> Vec<GrammarRou
9495
routes
9596
}
9697

98+
// `.m` 也用于 MATLAB;只在源码具有 Objective-C 专属词法标记时自动解析。
99+
// `.sc` 同时用于 Scala 脚本与 SuperCollider,缺少项目上下文时不猜测。
100+
fn has_objc_marker(source: &[u8]) -> bool {
101+
let markers: &[&[u8]] = &[
102+
b"@interface".as_slice(),
103+
b"@implementation",
104+
b"@protocol",
105+
b"@class",
106+
b"@property",
107+
b"@synthesize",
108+
b"@autoreleasepool",
109+
b"#import",
110+
];
111+
source.split(|byte| *byte == b'\n').any(|line| {
112+
let line = line.trim_ascii_start();
113+
markers.iter().any(|marker| {
114+
line.starts_with(marker)
115+
&& line.get(marker.len()).is_some_and(|next| {
116+
next.is_ascii_whitespace() || matches!(next, b'(' | b'{' | b'<' | b'"')
117+
})
118+
})
119+
})
120+
}
121+
97122
fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
98123
haystack
99124
.windows(needle.len())

‎crates/codeguard-cli/tests/check_all_grammar_candidates.rs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() {
6868
fs::create_dir_all(&root).unwrap();
6969
fs::write(root.join("component.tsx"), "const C = () => <div />;\n").unwrap();
7070
fs::write(root.join("script.js"), "const x = 1;\n").unwrap();
71+
fs::write(
72+
root.join("plot.m"),
73+
"title('@interface Foo');\nplot(1:3);\n",
74+
)
75+
.unwrap();
76+
fs::write(root.join("synth.sc"), "{ SinOsc.ar(440) }.play;\n").unwrap();
7177
fs::write(
7278
root.join("component.cfs"),
7379
"component { function f() { return 1; } }\n",
@@ -103,6 +109,9 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() {
103109
"{language}: {observations:?}"
104110
);
105111
}
112+
assert!(!observations.iter().any(|item| item["path"] == "plot.m"));
113+
assert!(!observations.iter().any(|item| item["path"] == "synth.sc"));
114+
assert_eq!(report["syntax_candidates"]["unrouted_count"], 2);
106115
assert_eq!(
107116
observations
108117
.iter()

‎crates/codeguard-cli/tests/grammar_route.rs‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ fn routes_every_pinned_grammar_without_merging_dialects() {
3939
];
4040
let mut observed = BTreeSet::new();
4141
for (language, path) in cases {
42-
let routes = route_source(path, b"source");
42+
let source: &[u8] = if path == "a.m" {
43+
b"@interface Foo : NSObject\n@end\n"
44+
} else {
45+
b"source"
46+
};
47+
let routes = route_source(path, source);
4348
assert_eq!(routes.len(), 1, "{path}");
4449
assert_eq!(routes[0].language, language, "{path}");
4550
observed.insert(language);
@@ -60,6 +65,23 @@ fn routes_every_pinned_grammar_without_merging_dialects() {
6065
fn ambiguous_and_embedded_sources_do_not_get_speculative_routes() {
6166
assert!(route_source("a.h", b"int x;").is_empty());
6267
assert!(route_source("a.sql", b"SELECT 1").is_empty());
68+
assert!(route_source("plot.m", b"x = [1, 2, 3];\nplot(x);\n").is_empty());
69+
assert!(
70+
route_source(
71+
"plot.m",
72+
b"title('@interface Foo');\n% #import is only an example\n"
73+
)
74+
.is_empty()
75+
);
76+
assert!(route_source("synth.sc", b"{ SinOsc.ar(440) }.play;\n").is_empty());
77+
assert_eq!(
78+
route_source("model.m", b"@implementation Model\n@end\n")[0].language,
79+
"objc"
80+
);
81+
assert_eq!(
82+
route_source("script.scala", b"object App {}\n")[0].language,
83+
"scala"
84+
);
6385
assert!(
6486
route_source("a.cfm", b"<cfquery>SELECT 1")
6587
.iter()

‎docs/Codeguard-Architecture.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ WASM requirements and 19 implementation tasks are recorded in the existing chang
3232

3333
A source build with `--features wasm-precheck` now exposes `codeguard grammar probe <language> <file> --format=json` for all 32 pinned candidates. It uses the isolated Rust worker and reports unqualified observations with exit 3, not a lint or delivery verdict. Successful observations and input failures for a valid language share the [closed 0.1.0 JSON schema](../schemas/grammar-probe-v0.1.schema.json) and remain incomplete. Native-first `lint/check`, language/dialect acceptance, task/host feedback and distribution remain separate open work; current discovery also groups JavaScript/TSX under TypeScript and has no separate CFQuery/CFScript source mapping.
3434

35-
The source-built `check all` now runs a bounded candidate pass after its existing native checks. A separate [grammar router](../crates/codeguard-cli/src/grammar_route.rs) distinguishes `.tsx`, JavaScript, and `.cfs`; complete `<cfquery>...</cfquery>` bodies are parsed with CFQuery, while ordinary `.sql` and ambiguous `.h` are not guessed. Four bounded real CLI fixtures collectively invoked all 32 pinned workers. [Check feedback 0.33.0](../schemas/check-feedback.schema.json) records selected dialect, source/grammar digests, file-relative recovery positions, skipped work and incomplete status; Python files with completed, source-matched Ruff scans skip duplicate WASM; style-only P3C does not confirm syntax. Candidate observations never become confirmed violations or delivery permission. The pass has a 64-file/64-fragment/90-second cap and was about 54 seconds for four bounded fixtures on this Mac; a single 31-file Linux run observed 26/32 before its 90-second cap. It is a partial candidate route, not a qualified native fallback, task/host integration or released npm capability. See [acceptance evidence](../tests/acceptance/check-all-32-grammar-candidates.md).
35+
The source-built `check all` now runs a bounded candidate pass after its existing native checks. A separate [grammar router](../crates/codeguard-cli/src/grammar_route.rs) distinguishes `.tsx`, JavaScript, and `.cfs`; complete `<cfquery>...</cfquery>` bodies are parsed with CFQuery, while ordinary `.sql` and ambiguous `.h` are not guessed. Shared `.m` requires an Objective-C line marker, and `.sc` is not guessed as Scala without project evidence; see the [ambiguity regression](../tests/acceptance/ambiguous-grammar-extensions.md). Four bounded real CLI fixtures collectively invoked all 32 pinned workers. [Check feedback 0.33.0](../schemas/check-feedback.schema.json) records selected dialect, source/grammar digests, file-relative recovery positions, skipped work and incomplete status; Python files with completed, source-matched Ruff scans skip duplicate WASM; style-only P3C does not confirm syntax. Candidate observations never become confirmed violations or delivery permission. The pass has a 64-file/64-fragment/90-second cap and was about 54 seconds for four bounded fixtures on this Mac; a single 31-file Linux run observed 26/32 before its 90-second cap. It is a partial candidate route, not a qualified native fallback, task/host integration or released npm capability. See [acceptance evidence](../tests/acceptance/check-all-32-grammar-candidates.md).
3636

3737
```mermaid
3838
flowchart LR

‎docs/Codeguard-Architecture.zh_CN.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ WASM 的规范与 19 项实施任务已纳入既有 change;可选 Rust worker
3232

3333
源码以 `--features wasm-precheck` 构建后,可通过 `codeguard grammar probe <language> <file> --format=json` 显式调用全部 32 份固定候选资产。命令使用隔离 Rust worker,以退出码 3 报告未经语言验收的观察,不能作为 lint 或交付结论。成功观察与有效语种的输入失败都遵守[0.1.0 封闭 JSON Schema](../schemas/grammar-probe-v0.1.schema.json),保持未完成。原生优先 `lint/check`、语言/方言验收、任务/宿主反馈及发行仍需独立完成;现有发现清单还把 JavaScript/TSX 归入 TypeScript,并未为 CFQuery/CFScript 建立独立源码映射。
3434

35-
源码构建的 `check all` 现会在既有原生检查之后执行有界候选初检。[语法路由](../crates/codeguard-cli/src/grammar_route.rs)区分 `.tsx`、JavaScript 和 `.cfs`,只对完整的 `<cfquery>...</cfquery>` 标签体选用 CFQuery;普通 `.sql`、有歧义的 `.h` 不猜测语种。四个有界真实 CLI 样例合计逐份调用了 32 种固定 worker。[检查反馈 0.33.0](../schemas/check-feedback.schema.json)记录方言、源码及 grammar 摘要、原文件位置、未执行范围和未完成状态;Ruff 本轮完整检查且摘要匹配的 Python 文件跳过重复 WASM;P3C 等规范检查不视为语法确认。候选观察不生成已确认违规或交付许可。候选阶段限 64 文件、64 片段、90 秒,本机四批 32 语种样例约 54 秒;Linux 单次 31 文件检查在 90 秒上限内完成 26/32。当前只是候选路由,尚非逐语言验收的原生兜底、任务/宿主接线或已发布 npm 能力;见[验收记录](../tests/acceptance/check-all-32-grammar-candidates.md)。
35+
源码构建的 `check all` 现会在既有原生检查之后执行有界候选初检。[语法路由](../crates/codeguard-cli/src/grammar_route.rs)区分 `.tsx`、JavaScript 和 `.cfs`,只对完整的 `<cfquery>...</cfquery>` 标签体选用 CFQuery;普通 `.sql`、有歧义的 `.h` 不猜测语种。共用后缀的 `.m` 要求 Objective-C 行首标记,`.sc` 在缺项目证据时不猜成 Scala;见[歧义回归](../tests/acceptance/ambiguous-grammar-extensions.md)。四个有界真实 CLI 样例合计逐份调用了 32 种固定 worker。[检查反馈 0.33.0](../schemas/check-feedback.schema.json)记录方言、源码及 grammar 摘要、原文件位置、未执行范围和未完成状态;Ruff 本轮完整检查且摘要匹配的 Python 文件跳过重复 WASM;P3C 等规范检查不视为语法确认。候选观察不生成已确认违规或交付许可。候选阶段限 64 文件、64 片段、90 秒,本机四批 32 语种样例约 54 秒;Linux 单次 31 文件检查在 90 秒上限内完成 26/32。当前只是候选路由,尚非逐语言验收的原生兜底、任务/宿主接线或已发布 npm 能力;见[验收记录](../tests/acceptance/check-all-32-grammar-candidates.md)。
3636

3737
```mermaid
3838
flowchart LR

‎docs/Codeguard-Technical-Design.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ The source-built `check all` now adds a narrower actual project report: native a
212212
}
213213
```
214214

215-
The actual report also includes native results, source/grammar digests, fragment offsets, bounded file-relative recovery anchors, skipped counts and unresolved obligations. A complete `<cfquery>` tag body is an embedded candidate; ordinary SQL or ambiguous C/C++ headers are not guessed. Four bounded 32-grammar CLI fixtures took about 54 seconds on this Mac; a single 31-file Linux run completed 26/32 within 90 seconds; language-version corpora, native differential checks, task synchronization, host delivery and package release are still open.
215+
The actual report also includes native results, source/grammar digests, fragment offsets, bounded file-relative recovery anchors, skipped counts and unresolved obligations. A complete `<cfquery>` tag body is an embedded candidate; ordinary SQL or ambiguous C/C++ headers are not guessed. Automatic `.m` routing requires an Objective-C line marker, while `.sc` is left unresolved because SuperCollider shares that suffix; the static language inventory and per-file unresolved diagnostics still need improvement. Four bounded 32-grammar CLI fixtures took about 54 seconds on this Mac; a single 31-file Linux run completed 26/32 within 90 seconds; language-version corpora, native differential checks, task synchronization, host delivery and package release are still open.
216216

217217
The opt-in CLI build first checks for a local ESLint 10 package and a single ordinary flat config on single-file TypeScript requests without explicit native context. With an executable Node resolved from `PATH`, it invokes the existing bounded native version/report probe; without Node it reports the preparation gap rather than running WASM first. Only when no project-local ESLint package is observed does the CLI emit [ESLint feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) for `.ts/.mts/.cts` or [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json) for `.tsx`. TSX uses its own pinned grammar. Candidate prechecks preserve `native=not_run` and `delivery=not_evaluated`, and remain incomplete even with zero recoveries. An ambiguous config, untrusted local path or invalid package identity remains a setup blocker and does not trigger WASM; a partial explicit context also retains its existing path. This is a single-file slice, not project script equivalence, per-module orchestration or automatic host feedback; see the [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md).
218218

‎docs/Codeguard-Technical-Design.zh_CN.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ flowchart LR
212212
}
213213
```
214214

215-
实际报告还包含原生结果、源码与 grammar 摘要、片段偏移、有界原文件恢复坐标、未执行数量和未解决义务。完整 `<cfquery>` 标签体可作嵌入候选;普通 SQL 和有歧义的 C/C++ 头文件不猜测。本机四批 32 份 grammar 的 CLI 样例约 54 秒;Linux 单次 31 文件检查在 90 秒内完成 26/32;语言版本语料、原生差分对照、任务同步、宿主交付和发行包验收仍待完成。
215+
实际报告还包含原生结果、源码与 grammar 摘要、片段偏移、有界原文件恢复坐标、未执行数量和未解决义务。完整 `<cfquery>` 标签体可作嵌入候选;普通 SQL 和有歧义的 C/C++ 头文件不猜测。自动 `.m` 路由要求 Objective-C 行首标记,`.sc` 与 SuperCollider 共用后缀,缺少项目证据时保持未解析;静态语言库存及逐文件未解析诊断仍待完善。本机四批 32 份 grammar 的 CLI 样例约 54 秒;Linux 单次 31 文件检查在 90 秒内完成 26/32;语言版本语料、原生差分对照、任务同步、宿主交付和发行包验收仍待完成。
216216

217217
可选特性构建的 CLI 在无显式原生上下文的 TypeScript 单文件请求中,先核对本地 ESLint 10 包及唯一普通 flat config。若从 `PATH` 解析到可执行 Node,就调用既有有界原生版本与报告探针;缺 Node 时报告准备缺口,不让 WASM 抢跑。仅未观察到本地 ESLint 包时,`.ts/.mts/.cts` 输出 [ESLint 反馈 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json),`.tsx` 用独立 grammar 输出 [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json)。候选初检保留 `native=not_run`、`delivery=not_evaluated`,即使零恢复节点也保持未完成。配置选择歧义、本地路径不可信或包身份损坏时保留环境阻塞而不启动 WASM;部分显式上下文也沿用原路径。该增量不证明项目脚本参数等价、逐模块调度或宿主对话交付,见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)。
218218

‎openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ Rust runtime MUST 按需加载 grammar,在受控解析工作进程中限制输
138138

139139
同一语言族有独立 grammar 的方言 MUST 按实际文件类型选择和报告;尤其 `.tsx` MUST 使用 TSX grammar,不能拿普通 TypeScript grammar 的恢复节点当作 JSX 源码异常。候选报告读者 MUST 核对 grammar 方言与源文件扩展名一致;不匹配视为报告无效,不生成源码 finding。
140140

141+
多语言共用后缀 MUST 保留歧义:`.m` 可能属于 Objective-C 或 MATLAB,`.sc` 可能属于 Scala 或 SuperCollider。没有可核对的语言证据时,自动检查 MUST 不调用猜测的 grammar、不把恢复节点记为源码问题,并保持范围未完成;用户显式指定 `grammar probe` 的语种仍可作候选诊断。
142+
143+
#### Scenario: Shared extension would produce a speculative syntax finding
144+
- **WHEN** 项目包含无 Objective-C 专属标记的 MATLAB `.m` 源码,或 SuperCollider `.sc` 源码
145+
- **THEN** 自动候选路由不把它们交给 Objective-C/Scala WASM;歧义范围保持未完成,不能用资产数量或其它文件的检查结果代替该范围
146+
141147
#### Scenario: Valid JSX is parsed with a TypeScript-only grammar
142148

143149
- **WHEN** `.tsx` 文件含合法 JSX,而普通 TypeScript grammar 会产生恢复节点

‎openspec/changes/introduce-rust-codeguard-cli/tasks.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,3 +1087,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30
10871087
14.6 / 14.7 / 14.19 Go 原生优先局部增量:`check all` 现在按模块一次运行受控 `go list`,仅在本轮 Go 1.23.4 vet 已完成、工具/模块/源码摘要与默认包清单均匹配时跳过被选中 Go 文件的重复 WASM;构建标签排除的破损源码仍进入隔离候选,原生 `printf` 发现保留。真实 Go 测试先 RED 后 1/1 通过,伪造越界包清单常规回归 1/1 通过,全 32 份候选分批路由 4/4 保持通过,见[Go 原生优先局部验收](../../../tests/acceptance/check-all-native-preferred-go.md)。平台/标签覆盖、其它 31 种 grammar、任务/宿主反馈和发行仍缺,父任务不勾选。
10881088

10891089
14.4 / 14.17 / 14.19 Ruby 与 Swift 原生差分增量:Ruby 2.6.10 `ruby -c` 和隔离 Ruby worker 的 13 例分类一致,显式原生测试与常规 worker 语料均 1/1 通过;见[Ruby 局部验收](../../../tests/acceptance/ruby-native-differential.md)。Swift 6.4 `swiftc -frontend -parse` 对 13 例中 12 例与 WASM 一致,但缺参数类型的 `func f(_ x: ) {}` 被原生拒绝而 WASM 零恢复,确认为漏检;显式测试 1/1 通过仅表示成功捕获这一差异,不表示语法验收,见[Swift 精度阻塞](../../../tests/acceptance/swift-native-differential.md)。须修 grammar、固定重建产物并以原生对照复验;三项父任务不勾选。
1090+
1091+
14.4 / 14.6 / 14.19 共享后缀误报收敛:自动路由不再把任意 `.m` 当 Objective-C,也不再把 `.sc` 当 Scala;`.m` 需行首 Objective-C 专属标记,`.scala` 与 `.mm` 路由保留。MATLAB 字符串/注释中的标记及 SuperCollider 正例不进入错误 grammar;32 份候选的明确样例仍需完整回归。现有静态发现仍可能将共享后缀归入旧语言目录,缺少项目级方言裁决和逐文件未路由解释,父任务保持未完成;见[局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# 共享后缀的语法候选路由局部验收
2+
3+
日期:2026-10-03。对应 OpenSpec 14.4、14.6、14.19 的低误报局部修复,父任务仍未完成。
4+
5+
`.m` 可表示 Objective-C 或 MATLAB,`.sc` 可表示 Scala 脚本或 SuperCollider。先写路由反例并确认失败:普通 MATLAB `plot.m` 被送入 Objective-C 候选;带 `@interface` 字符串与 `% #import` 注释的 MATLAB 文件也被宽松的字节匹配误识别。现只在 `.m` 源码行首出现 Objective-C 专属声明或 `#import` 时自动路由,`.mm` 与 `.scala` 保持明确路由;`.sc` 没有项目级证据时不猜测。显式 `grammar probe objc FILE` 仍可由用户指定语种运行。
6+
7+
`grammar_route` 的共享后缀、32 份资产可路由集合及 CFQuery 边界回归 5/5 通过;真实 `check all` 混合项目测试确认 MATLAB/SuperCollider 文件未产生错误候选观察,`unrouted_count=2` 且报告仍是未完成。四个有界项目的 32 份 worker 全量回归 1/1 通过,用时 54.74 秒,候选语种并集仍与固定清单一致。
8+
9+
这只减少自动候选误路由;静态发现仍可能按旧语言注册表把 `.m/.sc` 归入 Objective-C/Scala,自动报告也仅给未路由总数。项目级语言证据、未路由文件逐项诊断、`.m` 中不带标记的合法 Objective-C 范围及原生优先/发行验收仍缺,不能宣布这两个方言已完整支持。

0 commit comments

Comments
 (0)