|
| 1 | +//! Java 注释统一入口,复用原生探针并保留项目配置与局部检查边界。 |
| 2 | +
|
| 3 | +use crate::check_budget::{ |
| 4 | + budget_record, parse_check_timeout, resolve_project_default, select_check_timeout, |
| 5 | +}; |
| 6 | +use crate::discovery::discover; |
| 7 | +use crate::java_javadoc_command::{Args, observe}; |
| 8 | +use crate::java_javadoc_scan::{NativeContext, observe_project}; |
| 9 | +use codeguard_adapters::legacy_registry; |
| 10 | +use codeguard_runtime::NativeObservation; |
| 11 | +use serde_json::json; |
| 12 | +use std::collections::{BTreeSet, HashSet}; |
| 13 | +use std::path::PathBuf; |
| 14 | +use std::process::ExitCode; |
| 15 | +use std::sync::atomic::AtomicBool; |
| 16 | +use std::time::{Duration, Instant}; |
| 17 | + |
| 18 | +/// 执行 Java 注释检查;输入为入口剩余参数,返回参数错误、取消或局部未完成状态。 |
| 19 | +pub fn run(args: &[String]) -> ExitCode { |
| 20 | + let mut target = PathBuf::from("."); |
| 21 | + let mut options = std::collections::BTreeMap::new(); |
| 22 | + let mut seen = HashSet::new(); |
| 23 | + let mut positional = false; |
| 24 | + let mut index = 0; |
| 25 | + while index < args.len() { |
| 26 | + let argument = &args[index]; |
| 27 | + if argument.starts_with('-') { |
| 28 | + let (key, inline) = argument |
| 29 | + .split_once('=') |
| 30 | + .map_or((argument.as_str(), None), |(key, value)| (key, Some(value))); |
| 31 | + if !matches!( |
| 32 | + key, |
| 33 | + "--java-home" |
| 34 | + | "--maven-tool" |
| 35 | + | "--maven-repo" |
| 36 | + | "--repo-sha256" |
| 37 | + | "--timeout" |
| 38 | + | "--format" |
| 39 | + ) || !seen.insert(key.to_owned()) |
| 40 | + { |
| 41 | + eprintln!("未知或重复参数:{key}"); |
| 42 | + return ExitCode::from(2); |
| 43 | + } |
| 44 | + let value = if let Some(value) = inline { |
| 45 | + value.to_owned() |
| 46 | + } else { |
| 47 | + index += 1; |
| 48 | + let Some(value) = args.get(index).filter(|value| !value.starts_with('-')) else { |
| 49 | + eprintln!("{key} 缺少参数值"); |
| 50 | + return ExitCode::from(2); |
| 51 | + }; |
| 52 | + value.clone() |
| 53 | + }; |
| 54 | + if value.is_empty() { |
| 55 | + eprintln!("{key} 不能为空"); |
| 56 | + return ExitCode::from(2); |
| 57 | + } |
| 58 | + options.insert(key.to_owned(), value); |
| 59 | + } else if positional { |
| 60 | + eprintln!("只能指定一个检查路径"); |
| 61 | + return ExitCode::from(2); |
| 62 | + } else { |
| 63 | + target = PathBuf::from(argument); |
| 64 | + positional = true; |
| 65 | + } |
| 66 | + index += 1; |
| 67 | + } |
| 68 | + let format = options.get("--format").map_or("human", String::as_str); |
| 69 | + if !matches!(format, "human" | "json") { |
| 70 | + eprintln!("--format 仅支持 human/json"); |
| 71 | + return ExitCode::from(2); |
| 72 | + } |
| 73 | + let timeout = options |
| 74 | + .get("--timeout") |
| 75 | + .map(|value| parse_check_timeout(value)) |
| 76 | + .transpose() |
| 77 | + .and_then(select_check_timeout) |
| 78 | + .and_then(|(value, source)| { |
| 79 | + resolve_project_default( |
| 80 | + if target.is_file() { |
| 81 | + target.parent().unwrap_or(&target) |
| 82 | + } else { |
| 83 | + &target |
| 84 | + }, |
| 85 | + value, |
| 86 | + source, |
| 87 | + ) |
| 88 | + }); |
| 89 | + let (timeout, source) = match timeout { |
| 90 | + Ok(value) => value, |
| 91 | + Err(reason) => { |
| 92 | + eprintln!("{reason}"); |
| 93 | + return ExitCode::from(2); |
| 94 | + } |
| 95 | + }; |
| 96 | + let java_home = options.get("--java-home").map(PathBuf::from); |
| 97 | + let maven_tool = options.get("--maven-tool").map(PathBuf::from); |
| 98 | + let maven_repo = options.get("--maven-repo").map(PathBuf::from); |
| 99 | + if [java_home.as_ref(), maven_tool.as_ref(), maven_repo.as_ref()] |
| 100 | + .into_iter() |
| 101 | + .flatten() |
| 102 | + .any(|path| !path.is_absolute()) |
| 103 | + { |
| 104 | + eprintln!("原生工具与仓库路径必须为绝对路径"); |
| 105 | + return ExitCode::from(2); |
| 106 | + } |
| 107 | + let deadline = Instant::now() + Duration::from_millis(timeout); |
| 108 | + let cancelled = AtomicBool::new(false); |
| 109 | + let mut report = json!({"schema_version":"0.1.0", "report_type":"java_comments_feedback", "operation":"comments", "language":"java", "category":"comments", "target_kind":"unavailable", "command_status":"incomplete", "exit_code":3, "reason":"target_unavailable", "execution_budget":budget_record(timeout, source), "native_observation":null, "discovery":null, "coverage_proven":false, "authority":"local_unverified", "delivery_decision":"not_evaluated", "workbench_status":"not_integrated", "next_actions":["依据原生诊断修复注释或配置;使用相同原生上下文重新执行 comments java", "Javadoc 持久任务适配尚未接通,不能据此关闭任务"]}); |
| 110 | + if let Ok(path) = target.canonicalize() { |
| 111 | + if path.is_file() { |
| 112 | + if maven_tool.is_some() || maven_repo.is_some() || options.contains_key("--repo-sha256") |
| 113 | + { |
| 114 | + eprintln!("Maven 上下文仅用于项目目录检查"); |
| 115 | + return ExitCode::from(2); |
| 116 | + } |
| 117 | + report["target_kind"] = json!("file"); |
| 118 | + report["native_observation"] = observe( |
| 119 | + &Args { |
| 120 | + source: path, |
| 121 | + java_home, |
| 122 | + json: true, |
| 123 | + }, |
| 124 | + deadline, |
| 125 | + &cancelled, |
| 126 | + ); |
| 127 | + report["reason"] = report["native_observation"]["reason"].clone(); |
| 128 | + } else if path.is_dir() { |
| 129 | + report["target_kind"] = json!("project"); |
| 130 | + if let Ok(registry) = legacy_registry() { |
| 131 | + let discovery = discover(&path, ®istry, &NativeObservation); |
| 132 | + let sources = discovery |
| 133 | + .languages |
| 134 | + .get("java") |
| 135 | + .map(|language| language.source_files.clone()) |
| 136 | + .unwrap_or_else(BTreeSet::new); |
| 137 | + report["native_observation"] = observe_project( |
| 138 | + &path, |
| 139 | + &sources, |
| 140 | + &discovery.checker_configurations, |
| 141 | + &NativeContext { |
| 142 | + manifest_sha256: &discovery.manifest_sha256, |
| 143 | + java_home: java_home.as_deref(), |
| 144 | + maven_tool: maven_tool.as_deref(), |
| 145 | + maven_repo: maven_repo.as_deref(), |
| 146 | + repo_sha256: options.get("--repo-sha256").map(String::as_str), |
| 147 | + deadline, |
| 148 | + cancelled: &cancelled, |
| 149 | + }, |
| 150 | + ); |
| 151 | + report["discovery"] = discovery.to_json(); |
| 152 | + report["reason"] = json!("project_comments_observed_with_unverified_coverage"); |
| 153 | + } else { |
| 154 | + report["reason"] = json!("registry_unavailable"); |
| 155 | + } |
| 156 | + } |
| 157 | + } |
| 158 | + let exit = if codeguard_runtime::sigint_cancellation_requested() |
| 159 | + || report["reason"] == "request_cancelled" |
| 160 | + { |
| 161 | + 130 |
| 162 | + } else { |
| 163 | + 3 |
| 164 | + }; |
| 165 | + if exit == 130 { |
| 166 | + report["command_status"] = json!("cancelled"); |
| 167 | + report["reason"] = json!("request_cancelled"); |
| 168 | + report["exit_code"] = json!(130); |
| 169 | + } |
| 170 | + if format == "json" { |
| 171 | + println!("{report}"); |
| 172 | + } else { |
| 173 | + println!( |
| 174 | + "Java 注释检查:{};范围 {};原因 {}", |
| 175 | + report["command_status"], report["target_kind"], report["reason"] |
| 176 | + ); |
| 177 | + println!("原生观察:{}", report["native_observation"]); |
| 178 | + for action in report["next_actions"].as_array().into_iter().flatten() { |
| 179 | + println!("下一步:{action}"); |
| 180 | + } |
| 181 | + } |
| 182 | + ExitCode::from(exit) |
| 183 | +} |
0 commit comments