Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ cargo build --locked -p codeguard-cli

Version output includes `cli_version`, `target`, and protocol information. Discovery and init preview return observations and unknowns. Dry-run creates neither `AGENTS.md` nor a project data directory. Add Cargo `--offline` only when dependencies are cached.

The workspace requires `std`. The opt-in `wasm-precheck` build feature uses a bounded Rust worker with pinned Java/TypeScript grammar candidates. It currently reaches only single-file `lint java` and `lint typescript` requests without explicit native context; it is absent from the published npm `0.1.2` package. No `no_std`, completed project-wide WASM fallback, zero-unsafe, or fastest-runtime claim is made. Runtime OS calls require safety review; a comprehensive security audit is not claimed.
The workspace requires `std`. The opt-in `wasm-precheck` build feature uses a bounded Rust worker with pinned Java, TypeScript, and TSX grammar candidates. It currently reaches only single-file `lint java` and `lint typescript` requests without explicit native context; `.tsx` uses its own grammar. It is absent from the published npm `0.1.2` package. No `no_std`, completed project-wide WASM fallback, zero-unsafe, or fastest-runtime claim is made. Runtime OS calls require safety review; a comprehensive security audit is not claimed.

The current source-built CLI's `codeguard grammar status --format=json` reports the coverage gap without loading grammars or running lint: the pinned CodeGraph source has 30 vendored WASM files plus two distinct grammars resolved through its dependency; CodeGuard has two unqualified candidates and zero released syntax capabilities. The separate [coverage inventory](grammars/codegraph-coverage.json) is not an approved asset manifest. COBOL's 16.4 MB source asset also exceeds the current Rust loader's 8 MiB limit.
The current source-built CLI's `codeguard grammar status --format=json` reports the coverage gap without loading grammars or running lint: the pinned CodeGraph source has 30 vendored WASM files plus two distinct grammars resolved through its dependency; CodeGuard has three unqualified candidates and zero released syntax capabilities. The separate [coverage inventory](grammars/codegraph-coverage.json) is not an approved asset manifest. COBOL's 16.4 MB source asset also exceeds the current Rust loader's 8 MiB limit.

### npm installation and one-off use

Expand Down
4 changes: 2 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ cargo build --locked -p codeguard-cli

版本输出包含 `cli_version`、`target` 和协议信息。发现与 init 预览返回观察及未知项。Dry-run 不创建 `AGENTS.md` 或项目数据目录。仅在依赖已缓存时为 Cargo 添加 `--offline`。

工作区要求 `std`。可选 `wasm-precheck` 构建特性已用固定的 Java/TypeScript grammar 候选资产与有界 Rust worker,为缺少显式原生上下文的 `lint java`、`lint typescript` 单文件请求提供疑似语法观察;公开 npm `0.1.2` 不含该路径。不声称 `no_std`、已完成项目级 WASM 兜底、零 unsafe 或性能最快。运行层系统调用需要安全审查,当前不宣称已完成整体安全审计。
工作区要求 `std`。可选 `wasm-precheck` 构建特性已用固定的 Java、TypeScript、TSX grammar 候选资产与有界 Rust worker,为缺少显式原生上下文的 `lint java`、`lint typescript` 单文件请求提供疑似语法观察;`.tsx` 使用独立 grammar。公开 npm `0.1.2` 不含该路径。不声称 `no_std`、已完成项目级 WASM 兜底、零 unsafe 或性能最快。运行层系统调用需要安全审查,当前不宣称已完成整体安全审计。

当前源码构建的 CLI 可用 `codeguard grammar status --format=json` 只读展示来源覆盖缺口,不加载 grammar 或运行 lint:固定 CodeGraph 来源含 30 份随仓 WASM,另有两种独立 grammar 从其依赖取得;CodeGuard 当前只有两份未验收候选,已发行语法能力为零。[覆盖库存](grammars/codegraph-coverage.json)不是已批准资产清单。COBOL 来源资产约 16.4 MB,还超过当前 Rust 加载器的 8 MiB 限额。
当前源码构建的 CLI 可用 `codeguard grammar status --format=json` 只读展示来源覆盖缺口,不加载 grammar 或运行 lint:固定 CodeGraph 来源含 30 份随仓 WASM,另有两种独立 grammar 从其依赖取得;CodeGuard 当前有三份未验收候选,已发行语法能力为零。[覆盖库存](grammars/codegraph-coverage.json)不是已批准资产清单。COBOL 来源资产约 16.4 MB,还超过当前 Rust 加载器的 8 MiB 限额。

### npm 安装与一次性调用

Expand Down
20 changes: 17 additions & 3 deletions crates/codeguard-adapters/src/grammar_asset_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub struct GrammarAsset {
pub release_status: String,
}

/// 读取仓内固定清单,并检查 CodeGraph 许可证及两份资产的原始字节。
/// 读取仓内固定清单,并检查 CodeGraph 许可证及三份资产的原始字节。
pub fn bundled_grammar_candidates() -> Result<GrammarAssetManifest, String> {
let manifest = parse_grammar_asset_manifest(include_bytes!("../../../grammars/manifest.json"))?;
let codegraph_license = include_bytes!("../../../grammars/LICENSE.codegraph");
Expand All @@ -85,6 +85,10 @@ pub fn bundled_grammar_candidates() -> Result<GrammarAssetManifest, String> {
include_bytes!("../../../grammars/typescript/parser.wasm").as_slice(),
include_bytes!("../../../grammars/typescript/LICENSE").as_slice(),
),
"tsx" => (
include_bytes!("../../../grammars/tsx/parser.wasm").as_slice(),
include_bytes!("../../../grammars/typescript/LICENSE").as_slice(),
),
_ => return Err("候选语言未知".into()),
};
verify_grammar_asset(asset, wasm, license)?;
Expand All @@ -106,7 +110,7 @@ pub fn parse_grammar_asset_manifest(raw: &[u8]) -> Result<GrammarAssetManifest,
|| manifest.codegraph_license != "LICENSE.codegraph"
|| manifest.codegraph_license_sha256 != CODEGRAPH_LICENSE_SHA256
|| manifest.build_tool != "tree-sitter-cli 0.25.10 build --wasm"
|| manifest.assets.len() != 2
|| manifest.assets.len() != 3
{
return Err("grammar 清单版本、来源或资产数量不符".into());
}
Expand All @@ -115,7 +119,7 @@ pub fn parse_grammar_asset_manifest(raw: &[u8]) -> Result<GrammarAssetManifest,
.iter()
.map(|asset| asset.language.as_str())
.collect();
if languages != BTreeSet::from(["java", "typescript"]) {
if languages != BTreeSet::from(["java", "typescript", "tsx"]) {
return Err("grammar 语言资产缺失或重复".into());
}
for asset in &manifest.assets {
Expand All @@ -141,6 +145,16 @@ pub fn parse_grammar_asset_manifest(raw: &[u8]) -> Result<GrammarAssetManifest,
"typescript/LICENSE",
"49bf33cf78ef5897e4e161ce1517df7de1ae5042a65b6bcfd44401e0fc606559",
),
"tsx" => (
"https://github.com/tree-sitter/tree-sitter-typescript",
TYPESCRIPT_COMMIT,
"v0.23.2",
"tsx/parser.wasm",
"8f647a1b2cafe9ab00fb2056d79021d2a144ba17a72f45511072311c1b05d08e",
1445641,
"typescript/LICENSE",
"49bf33cf78ef5897e4e161ce1517df7de1ae5042a65b6bcfd44401e0fc606559",
),
_ => return Err("grammar 语言未知".into()),
};
if asset.dialect != asset.language
Expand Down
14 changes: 14 additions & 0 deletions crates/codeguard-adapters/src/syntax_precheck_candidate_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ pub fn parse_syntax_precheck_candidate_report(
}
let mut observations = Vec::with_capacity(report.files.len());
for file in report.files {
if !path_matches_language(&file.path, &report.backend.language) {
return Err("syntax_precheck_source_dialect_mismatch".into());
}
let source = source_bytes
.get(&file.path)
.ok_or("syntax_precheck_source_missing")?;
Expand Down Expand Up @@ -132,3 +135,14 @@ pub fn parse_syntax_precheck_candidate_report(
}
Ok(actual)
}

fn path_matches_language(path: &str, language: &str) -> bool {
match language {
"java" => path.ends_with(".java"),
"typescript" => [".ts", ".mts", ".cts"]
.iter()
.any(|suffix| path.ends_with(suffix)),
"tsx" => path.ends_with(".tsx"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add TSX to the candidate report schema

When a TSX syntax_precheck_candidate report is produced, this reader now explicitly accepts the tsx language and the added test treats that as valid, but schemas/syntax-precheck-candidate.schema.json still restricts both backend.language and backend.dialect to java or typescript (lines 25–26). Consequently, every TSX candidate report accepted by the Rust contract is rejected by consumers validating against the published schema; extend those schema enums to include tsx.

Useful? React with 👍 / 👎.

_ => false,
}
}
6 changes: 5 additions & 1 deletion crates/codeguard-adapters/tests/grammar_asset_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use codeguard_adapters::{
#[test]
fn bundled_candidates_pin_source_bytes_license_and_abi_without_claiming_support() {
let manifest = bundled_grammar_candidates().expect("bundled manifest");
assert_eq!(manifest.assets.len(), 2);
assert_eq!(manifest.assets.len(), 3);
for asset in &manifest.assets {
let (wasm, license) = match asset.language.as_str() {
"java" => (
Expand All @@ -16,6 +16,10 @@ fn bundled_candidates_pin_source_bytes_license_and_abi_without_claiming_support(
include_bytes!("../../../grammars/typescript/parser.wasm").as_slice(),
include_bytes!("../../../grammars/typescript/LICENSE").as_slice(),
),
"tsx" => (
include_bytes!("../../../grammars/tsx/parser.wasm").as_slice(),
include_bytes!("../../../grammars/typescript/LICENSE").as_slice(),
),
other => panic!("unexpected language {other}"),
};
verify_grammar_asset(asset, wasm, license).expect("pinned candidate bytes");
Expand Down
17 changes: 17 additions & 0 deletions crates/codeguard-adapters/tests/syntax_precheck_report_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,20 @@ fn candidate_schema_declares_closed_versioned_contract() {
assert_eq!(schema["additionalProperties"], false);
assert_eq!(schema["properties"]["precheck"]["$ref"], "#/$defs/precheck");
}

#[test]
fn tsx_candidate_requires_tsx_source_and_cannot_relabel_java() {
let mut report = candidate();
report["backend"]["language"] = json!("tsx");
report["backend"]["dialect"] = json!("tsx");
report["backend"]["grammar_sha256"] =
json!("8f647a1b2cafe9ab00fb2056d79021d2a144ba17a72f45511072311c1b05d08e");
assert!(read(&report).is_err());

report["files"][0]["path"] = json!("src/Component.tsx");
let sources = BTreeMap::from([("src/Component.tsx".into(), b"hello".to_vec())]);
let status =
parse_syntax_precheck_candidate_report(&serde_json::to_vec(&report).unwrap(), &sources)
.unwrap();
assert_eq!(status.status, SyntaxPrecheckStatus::Incomplete);
}
1 change: 1 addition & 0 deletions crates/codeguard-cli/src/syntax_worker_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ fn observe(language: &str, source: &[u8]) -> Result<SyntaxWorkerEnvelope, String
let wasm = match language {
"java" => include_bytes!("../../../grammars/java/parser.wasm").as_slice(),
"typescript" => include_bytes!("../../../grammars/typescript/parser.wasm").as_slice(),
"tsx" => include_bytes!("../../../grammars/tsx/parser.wasm").as_slice(),
_ => return Err("不支持的 grammar 语种".into()),
};
let mut grammar = WasmGrammar::load(language, wasm, &asset.sha256, asset.abi_version as usize)?;
Expand Down
17 changes: 13 additions & 4 deletions crates/codeguard-cli/src/typescript_syntax_precheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,24 @@ pub(crate) fn eligible(args: &EslintLintArguments) -> bool {
}

/// 使用本轮源码快照与私有 Rust worker 补充疑似语法观察;原生仍未运行。
/// 参数为受支持的单文件请求及共同截止时间;返回无交付权威的 0.3.0 反馈。
/// 参数为受支持的单文件请求及共同截止时间;返回无交付权威的 TypeScript 0.3.0 或 TSX 0.4.0 反馈。
pub(crate) fn observe(args: &EslintLintArguments, deadline: Instant) -> Value {
let language = if args
.source
.extension()
.is_some_and(|extension| extension == "tsx")
{
"tsx"
} else {
"typescript"
};
let source_name = args
.source
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("source.ts");
let mut report = json!({
"schema_version": "0.3.0",
"schema_version": if language == "tsx" { "0.4.0" } else { "0.3.0" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document the separate TSX feedback version

For a .tsx request without native context, this branch now emits eslint_local_feedback 0.4.0, but the public command documentation still says that lint typescript <file> returns TypeScript feedback 0.3.0 (README.md:184, docs/Codeguard-Command-Reference.md:13, and its Chinese counterpart). A consumer following those documented contracts will select eslint-local-feedback-v0.3.schema.json, whose version and language constants reject this output; update the command-facing documentation to distinguish .tsx 0.4.0 from .ts/.mts/.cts 0.3.0.

Useful? React with 👍 / 👎.

"report_type": "eslint_local_feedback",
"status": "incomplete",
"local_coherent": false,
Expand All @@ -51,7 +60,7 @@ pub(crate) fn observe(args: &EslintLintArguments, deadline: Instant) -> Value {
"setup": {"requirement": "required", "reason": "native_confirmation_needed", "task_id": null},
"syntax_precheck": {
"backend": "bundled_tree_sitter_wasm_candidate",
"language": "typescript",
"language": language,
"status": "incomplete",
"reason": "not_run",
"source_path": source_name,
Expand Down Expand Up @@ -82,7 +91,7 @@ pub(crate) fn observe(args: &EslintLintArguments, deadline: Instant) -> Value {
};
let observation = match run_syntax_worker_candidate(
&executable,
"typescript",
language,
source_name,
&source,
deadline,
Expand Down
4 changes: 2 additions & 2 deletions crates/codeguard-cli/tests/grammar_status_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion() {
assert_eq!(report["report_type"], "grammar_coverage_inventory");
assert_eq!(report["codegraph_grammar_count"], 32);
assert_eq!(report["codegraph_vendored_count"], 30);
assert_eq!(report["candidate_count"], 2);
assert_eq!(report["candidate_count"], 3);
assert_eq!(report["released_count"], 0);
assert_eq!(report["authority"], "source_inventory_only");
assert_eq!(report["parser_capability"], "unverified");
Expand All @@ -25,7 +25,7 @@ fn codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion() {
assert_eq!(asset["released"], false);
assert!(!asset["gap"].as_str().unwrap().is_empty());
}
for language in ["java", "typescript"] {
for language in ["java", "typescript", "tsx"] {
let asset = assets
.iter()
.find(|row| row["language"] == language)
Expand Down
66 changes: 66 additions & 0 deletions crates/codeguard-cli/tests/typescript_syntax_fallback_candidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,72 @@ fn no_recovery_remains_incomplete_and_javascript_does_not_use_typescript_grammar
assert!(js_report.get("syntax_precheck").is_none());
}

#[test]
fn tsx_uses_its_own_grammar_instead_of_reporting_valid_jsx_as_suspect() {
let project = Project::new();
fs::write(
project.0.join("component.tsx"),
"const node = <div title=\"ok\">hello</div>;\n",
)
.unwrap();
let (status, report) = project.lint("component.tsx");
assert_eq!(status, 3);
assert_eq!(report["schema_version"], "0.4.0");
assert_eq!(report["native"]["status"], "not_run");
assert_eq!(report["syntax_precheck"]["language"], "tsx");
assert_eq!(report["syntax_precheck"]["checked_files"], 1);
assert_eq!(
report["syntax_precheck"]["grammar_sha256"],
"8f647a1b2cafe9ab00fb2056d79021d2a144ba17a72f45511072311c1b05d08e"
);
assert!(
report["syntax_precheck"]["observations"]
.as_array()
.unwrap()
.is_empty()
);
assert_eq!(report["delivery_decision"], "not_evaluated");

fs::write(
project.0.join("broken.tsx"),
"const node = <div title=></div>;\n",
)
.unwrap();
let (_, broken) = project.lint("broken.tsx");
assert_eq!(broken["syntax_precheck"]["language"], "tsx");
assert!(
!broken["syntax_precheck"]["observations"]
.as_array()
.unwrap()
.is_empty()
);
assert_eq!(broken["syntax_precheck"]["status"], "incomplete");
assert!(broken["findings"].as_array().unwrap().is_empty());

let schema: Value = serde_json::from_str(include_str!(
"../../../schemas/eslint-local-feedback-v0.4.schema.json"
))
.unwrap();
assert_eq!(schema["properties"]["schema_version"]["const"], "0.4.0");
assert_eq!(
schema["properties"]["syntax_precheck"]["properties"]["language"]["const"],
"tsx"
);
let actual: std::collections::BTreeSet<_> = report
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.collect();
let required: std::collections::BTreeSet<_> = schema["required"]
.as_array()
.unwrap()
.iter()
.map(|item| item.as_str().unwrap())
.collect();
assert_eq!(actual, required);
}

#[test]
fn candidate_feedback_schema_is_versioned_and_closed() {
let schema: Value = serde_json::from_str(include_str!(
Expand Down
22 changes: 22 additions & 0 deletions crates/codeguard-runtime/tests/wasm_grammar_load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use codeguard_runtime::WasmGrammar;

const JAVA: &[u8] = include_bytes!("../../../grammars/java/parser.wasm");
const TYPESCRIPT: &[u8] = include_bytes!("../../../grammars/typescript/parser.wasm");
const TSX: &[u8] = include_bytes!("../../../grammars/tsx/parser.wasm");

#[test]
fn pinned_grammars_load_offline_and_parse_valid_sources() {
Expand Down Expand Up @@ -45,6 +46,27 @@ fn pinned_grammars_load_offline_and_parse_valid_sources() {
.has_error()
);
assert!(typescript.parse(&vec![b'x'; 1024 * 1024 + 1]).is_err());

let mut tsx = WasmGrammar::load(
"tsx",
TSX,
"8f647a1b2cafe9ab00fb2056d79021d2a144ba17a72f45511072311c1b05d08e",
14,
)
.expect("固定 TSX WASM 应可由 Rust 加载");
assert_eq!(tsx.abi_version(), 14);
assert!(
!tsx.parse(b"const node = <div title=\"ok\">hello</div>;")
.unwrap()
.root_node()
.has_error()
);
assert!(
tsx.parse(b"const node = <div title=></div>;")
.unwrap()
.root_node()
.has_error()
);
}

#[test]
Expand Down
Loading
Loading