|
| 1 | +//! Clippy 修复事件的当前输入核对与脱敏投影;不在编辑事件中运行项目编译。 |
| 2 | +use crate::rust_lint_inputs::RustLintInputs; |
| 3 | +use serde_json::{Value, json}; |
| 4 | +use sha2::{Digest, Sha256}; |
| 5 | +use std::{path::Path, time::Instant}; |
| 6 | + |
| 7 | +/// 复检启动前冻结已观察源码、清单、锁及配置;失败保持未完成而不制造源码违规。 |
| 8 | +pub(crate) fn capture(root: &Path) -> Option<RustLintInputs> { |
| 9 | + let inventory = crate::rust_input_inventory::RustInputInventory::capture(root).ok()?; |
| 10 | + RustLintInputs::capture(root, &inventory.source_files).ok() |
| 11 | +} |
| 12 | + |
| 13 | +/// 从当前同任务原生复检报告投影规则与行号;参数为根、原任务、报告、父请求快照与截止时间。 |
| 14 | +/// 只提供局部 lint 观察,不猜测列单位,不关闭任务或签发覆盖与交付许可。 |
| 15 | +pub(crate) fn project( |
| 16 | + root: &Path, |
| 17 | + brief: &Value, |
| 18 | + scan: &Value, |
| 19 | + inputs: Option<&RustLintInputs>, |
| 20 | + selected_tool: Option<&Path>, |
| 21 | + deadline: Instant, |
| 22 | +) -> Result<Value, &'static str> { |
| 23 | + if !matches!(scan["schema_version"].as_str(), Some("0.2.0" | "0.3.0")) |
| 24 | + || scan["report_type"] != "rust_clippy_local_observation" |
| 25 | + || scan["checker_id"] != "rust.cargo_clippy" |
| 26 | + || scan["language"] != "rust" |
| 27 | + || scan["authority"] != "local_unverified" |
| 28 | + || scan["coverage_proven"] != false |
| 29 | + || scan["delivery_decision"] != "not_evaluated" |
| 30 | + || !scan["local_scan_complete"].is_boolean() |
| 31 | + || !scan["findings"].is_array() |
| 32 | + { |
| 33 | + return Err("verification_report_invalid"); |
| 34 | + } |
| 35 | + let tool_current = (scan["local_scan_complete"] != true && scan["tool_sha256"].is_null()) |
| 36 | + || selected_tool |
| 37 | + .and_then(|tool| tool.canonicalize().ok()) |
| 38 | + .and_then(|path| { |
| 39 | + codeguard_runtime::read_bounded_regular_file(&path, 128 * 1024 * 1024).ok() |
| 40 | + }) |
| 41 | + .is_some_and(|bytes| scan["tool_sha256"] == format!("{:x}", Sha256::digest(bytes))); |
| 42 | + let mut current = tool_current |
| 43 | + && Instant::now() < deadline |
| 44 | + && !codeguard_runtime::sigint_cancellation_requested() |
| 45 | + && inputs.is_some_and(|snapshot| snapshot.unchanged(root)); |
| 46 | + let mut positions = Vec::new(); |
| 47 | + if current && scan["local_scan_complete"] == true && brief["kind"] == "finding" { |
| 48 | + for finding in scan["findings"] |
| 49 | + .as_array() |
| 50 | + .ok_or("verification_report_invalid")? |
| 51 | + { |
| 52 | + if finding["finding_id"] != brief["task_id"] |
| 53 | + || finding["path"] != brief["scope"] |
| 54 | + || finding["rule_id"] != brief["native_rule_id"] |
| 55 | + { |
| 56 | + continue; |
| 57 | + } |
| 58 | + let path = finding["path"] |
| 59 | + .as_str() |
| 60 | + .ok_or("verification_report_invalid")?; |
| 61 | + if !Path::new(path) |
| 62 | + .components() |
| 63 | + .all(|component| matches!(component, std::path::Component::Normal(_))) |
| 64 | + { |
| 65 | + return Err("verification_report_invalid"); |
| 66 | + } |
| 67 | + let Ok(source) = crate::plain_syntax_source::read_plain_source(&root.join(path)) else { |
| 68 | + current = false; |
| 69 | + break; |
| 70 | + }; |
| 71 | + let Ok(source_text) = std::str::from_utf8(&source) else { |
| 72 | + current = false; |
| 73 | + break; |
| 74 | + }; |
| 75 | + let line = finding["line"] |
| 76 | + .as_u64() |
| 77 | + .filter(|line| *line > 0 && *line as usize <= source_text.split('\n').count()) |
| 78 | + .ok_or("verification_report_invalid")?; |
| 79 | + let rule = finding["rule_id"] |
| 80 | + .as_str() |
| 81 | + .filter(|rule| { |
| 82 | + rule.strip_prefix("clippy::").is_some_and(|name| { |
| 83 | + !name.is_empty() |
| 84 | + && name.len() <= 72 |
| 85 | + && name |
| 86 | + .bytes() |
| 87 | + .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_') |
| 88 | + }) |
| 89 | + }) |
| 90 | + .ok_or("verification_report_invalid")?; |
| 91 | + if finding["source_sha256"] != format!("{:x}", Sha256::digest(&source)) { |
| 92 | + current = false; |
| 93 | + break; |
| 94 | + } |
| 95 | + positions.push(json!({"line":line,"rule_id":rule})); |
| 96 | + if positions.len() > 32 { |
| 97 | + return Err("verification_output_exceeded"); |
| 98 | + } |
| 99 | + } |
| 100 | + } |
| 101 | + current = current |
| 102 | + && Instant::now() < deadline |
| 103 | + && !codeguard_runtime::sigint_cancellation_requested() |
| 104 | + && inputs.is_some_and(|snapshot| snapshot.unchanged(root)); |
| 105 | + if !current { |
| 106 | + positions.clear(); |
| 107 | + } |
| 108 | + let status = if !current { |
| 109 | + "stale" |
| 110 | + } else if scan["local_scan_complete"] != true { |
| 111 | + "incomplete" |
| 112 | + } else if positions.is_empty() { |
| 113 | + "completed" |
| 114 | + } else { |
| 115 | + "diagnostics_observed" |
| 116 | + }; |
| 117 | + Ok( |
| 118 | + json!({"schema_version":"0.8.0","native_confirmation_status":status, |
| 119 | + "native_confirmation_reason":if !current {"clippy_confirmation_inputs_changed"} else if status=="incomplete" {"clippy_confirmation_incomplete"} else if positions.is_empty(){"clippy_confirmation_no_task_diagnostics"}else{"clippy_confirmation_task_diagnostics"}, |
| 120 | + "native_column_unit":"unavailable","native_diagnostic_positions":positions}), |
| 121 | + ) |
| 122 | +} |
0 commit comments