|
| 1 | +//! CodeGraph grammar 来源覆盖清单的只读投影;不加载 WASM,也不签发检查结果。 |
| 2 | +
|
| 3 | +use codeguard_adapters::bundled_grammar_candidates; |
| 4 | +use serde::Deserialize; |
| 5 | +use serde_json::{Value, json}; |
| 6 | +use std::collections::BTreeSet; |
| 7 | +use std::process::ExitCode; |
| 8 | + |
| 9 | +const SOURCE_COMMIT: &str = "40f112453583a2304c4b605a3a9d6545919662bd"; |
| 10 | +const LANGUAGES: [&str; 32] = [ |
| 11 | + "arkts", |
| 12 | + "c", |
| 13 | + "cfml", |
| 14 | + "cfquery", |
| 15 | + "cfscript", |
| 16 | + "cobol", |
| 17 | + "cpp", |
| 18 | + "csharp", |
| 19 | + "dart", |
| 20 | + "erlang", |
| 21 | + "go", |
| 22 | + "java", |
| 23 | + "javascript", |
| 24 | + "kotlin", |
| 25 | + "lua", |
| 26 | + "luau", |
| 27 | + "nix", |
| 28 | + "objc", |
| 29 | + "pascal", |
| 30 | + "php", |
| 31 | + "python", |
| 32 | + "r", |
| 33 | + "ruby", |
| 34 | + "rust", |
| 35 | + "scala", |
| 36 | + "solidity", |
| 37 | + "swift", |
| 38 | + "terraform", |
| 39 | + "tsx", |
| 40 | + "typescript", |
| 41 | + "vbnet", |
| 42 | + "zig", |
| 43 | +]; |
| 44 | + |
| 45 | +#[derive(Deserialize)] |
| 46 | +#[serde(deny_unknown_fields)] |
| 47 | +struct CoverageInventory { |
| 48 | + schema_version: String, |
| 49 | + source_repository: String, |
| 50 | + source_commit: String, |
| 51 | + scope: String, |
| 52 | + note: String, |
| 53 | + assets: Vec<CoverageAsset>, |
| 54 | +} |
| 55 | + |
| 56 | +#[derive(Deserialize)] |
| 57 | +#[serde(deny_unknown_fields)] |
| 58 | +struct CoverageAsset { |
| 59 | + language: String, |
| 60 | + provider: String, |
| 61 | + source_path: Option<String>, |
| 62 | + sha256: Option<String>, |
| 63 | + bytes: Option<usize>, |
| 64 | +} |
| 65 | + |
| 66 | +/// 展示 CodeGraph 固定来源与 CodeGuard 已验证候选之间的覆盖差距。 |
| 67 | +/// 参数仅接受 `--format human|json`;返回 3 表示仍有未接入或未验收的 grammar。 |
| 68 | +pub fn run(args: &[String]) -> ExitCode { |
| 69 | + let format = match args { |
| 70 | + [] => "human", |
| 71 | + [option, value] if option == "--format" && matches!(value.as_str(), "human" | "json") => { |
| 72 | + value.as_str() |
| 73 | + } |
| 74 | + [option] if option == "--format=human" => "human", |
| 75 | + [option] if option == "--format=json" => "json", |
| 76 | + _ => { |
| 77 | + eprintln!("grammar status 仅支持 --format human|json"); |
| 78 | + return ExitCode::from(2); |
| 79 | + } |
| 80 | + }; |
| 81 | + let report = match coverage_report() { |
| 82 | + Ok(report) => report, |
| 83 | + Err(reason) => { |
| 84 | + eprintln!("grammar 来源清单未完成:{reason}"); |
| 85 | + return ExitCode::from(4); |
| 86 | + } |
| 87 | + }; |
| 88 | + if format == "json" { |
| 89 | + println!("{report}"); |
| 90 | + } else { |
| 91 | + println!( |
| 92 | + "CodeGraph grammar 32 种独立资产:随仓固定 30;CodeGuard 候选 {};已验收发行 0。", |
| 93 | + report["candidate_count"] |
| 94 | + ); |
| 95 | + println!("全部语法初检覆盖未完成;原生 lint 义务不变。使用 --format=json 查看逐语言缺口。"); |
| 96 | + } |
| 97 | + ExitCode::from(3) |
| 98 | +} |
| 99 | + |
| 100 | +fn coverage_report() -> Result<Value, String> { |
| 101 | + let raw = include_bytes!("../../../grammars/codegraph-coverage.json"); |
| 102 | + let strict = codeguard_adapters::parse_unique_json(raw).map_err(str::to_owned)?; |
| 103 | + let inventory: CoverageInventory = |
| 104 | + serde_json::from_value(strict).map_err(|error| error.to_string())?; |
| 105 | + validate_inventory(&inventory)?; |
| 106 | + let candidates = bundled_grammar_candidates()?; |
| 107 | + let candidate_languages = candidates |
| 108 | + .assets |
| 109 | + .iter() |
| 110 | + .map(|asset| asset.language.as_str()) |
| 111 | + .collect::<BTreeSet<_>>(); |
| 112 | + let inventory_languages = inventory |
| 113 | + .assets |
| 114 | + .iter() |
| 115 | + .map(|asset| asset.language.as_str()) |
| 116 | + .collect::<BTreeSet<_>>(); |
| 117 | + if !candidate_languages.is_subset(&inventory_languages) { |
| 118 | + return Err("CodeGuard 候选不在 CodeGraph 来源清单中".into()); |
| 119 | + } |
| 120 | + let assets = inventory |
| 121 | + .assets |
| 122 | + .iter() |
| 123 | + .map(|asset| { |
| 124 | + let candidate = candidates |
| 125 | + .assets |
| 126 | + .iter() |
| 127 | + .find(|candidate| candidate.language == asset.language); |
| 128 | + json!({ |
| 129 | + "language":asset.language, |
| 130 | + "provider":asset.provider, |
| 131 | + "source_path":asset.source_path, |
| 132 | + "source_sha256":asset.sha256, |
| 133 | + "source_bytes":asset.bytes, |
| 134 | + "integration_status":candidate.map_or("not_integrated", |item| item.release_status.as_str()), |
| 135 | + "runtime_observation":candidate.map(|item| item.codeguard_runtime_validation.as_str()), |
| 136 | + "released":false, |
| 137 | + "gap":if asset.provider == "tree_sitter_wasms_dependency" {"dependency_bytes_not_pinned"} |
| 138 | + else if asset.bytes.is_some_and(|bytes| bytes > 8 * 1024 * 1024) {"current_loader_size_limit"} |
| 139 | + else if candidate.is_some() {"language_qualification_and_release_pending"} |
| 140 | + else {"asset_provenance_license_and_loader_validation_pending"}, |
| 141 | + }) |
| 142 | + }) |
| 143 | + .collect::<Vec<_>>(); |
| 144 | + Ok(json!({ |
| 145 | + "schema_version":"1.0.0", |
| 146 | + "report_type":"grammar_coverage_inventory", |
| 147 | + "source_repository":inventory.source_repository, |
| 148 | + "source_commit":inventory.source_commit, |
| 149 | + "codegraph_grammar_count":inventory.assets.len(), |
| 150 | + "codegraph_vendored_count":inventory.assets.iter().filter(|asset| asset.provider == "codegraph_vendored").count(), |
| 151 | + "candidate_count":candidates.assets.len(), |
| 152 | + "released_count":0, |
| 153 | + "authority":"source_inventory_only", |
| 154 | + "parser_capability":"unverified", |
| 155 | + "gate_effect":"none", |
| 156 | + "execution":"not_run", |
| 157 | + "delivery_decision":"not_evaluated", |
| 158 | + "next_action":"固定各缺口资产的上游来源与许可证,逐语言验证 Rust 加载、版本语料及原生对照;不得凭此库存跳过原生 lint。", |
| 159 | + "assets":assets, |
| 160 | + })) |
| 161 | +} |
| 162 | + |
| 163 | +fn validate_inventory(inventory: &CoverageInventory) -> Result<(), String> { |
| 164 | + if inventory.schema_version != "1.0.0" |
| 165 | + || inventory.source_repository != "https://github.com/partme-ai/codegraph" |
| 166 | + || inventory.source_commit != SOURCE_COMMIT |
| 167 | + || inventory.scope != "CodeGraph grammar file map excluding the jsx alias of javascript" |
| 168 | + || inventory.note.is_empty() |
| 169 | + || inventory.assets.len() != LANGUAGES.len() |
| 170 | + { |
| 171 | + return Err("CodeGraph 来源清单版本、来源或范围不符".into()); |
| 172 | + } |
| 173 | + let mut seen = BTreeSet::new(); |
| 174 | + let mut vendored = 0; |
| 175 | + for asset in &inventory.assets { |
| 176 | + if !seen.insert(asset.language.as_str()) { |
| 177 | + return Err("CodeGraph grammar 重复".into()); |
| 178 | + } |
| 179 | + match asset.provider.as_str() { |
| 180 | + "codegraph_vendored" => { |
| 181 | + vendored += 1; |
| 182 | + let filename = if asset.language == "csharp" { |
| 183 | + "c_sharp" |
| 184 | + } else { |
| 185 | + &asset.language |
| 186 | + }; |
| 187 | + if asset.source_path.as_deref() |
| 188 | + != Some(format!("src/extraction/wasm/tree-sitter-{filename}.wasm").as_str()) |
| 189 | + || !asset.sha256.as_deref().is_some_and(valid_sha256) |
| 190 | + || asset.bytes.is_none_or(|bytes| bytes < 8) |
| 191 | + { |
| 192 | + return Err("CodeGraph 随仓资产身份无效".into()); |
| 193 | + } |
| 194 | + } |
| 195 | + "tree_sitter_wasms_dependency" => { |
| 196 | + if !matches!(asset.language.as_str(), "objc" | "solidity") |
| 197 | + || asset.source_path.is_some() |
| 198 | + || asset.sha256.is_some() |
| 199 | + || asset.bytes.is_some() |
| 200 | + { |
| 201 | + return Err("CodeGraph 依赖资产身份无效".into()); |
| 202 | + } |
| 203 | + } |
| 204 | + _ => return Err("CodeGraph 资产来源未知".into()), |
| 205 | + } |
| 206 | + } |
| 207 | + if vendored != 30 || seen != LANGUAGES.into_iter().collect() { |
| 208 | + return Err("CodeGraph grammar 覆盖集合不符".into()); |
| 209 | + } |
| 210 | + Ok(()) |
| 211 | +} |
| 212 | + |
| 213 | +fn valid_sha256(value: &str) -> bool { |
| 214 | + value.len() == 64 |
| 215 | + && value |
| 216 | + .bytes() |
| 217 | + .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) |
| 218 | +} |
0 commit comments