cloudcover_javascript/
analysis.rs1use std::{path::Path, process::Command};
2
3use cloudcover_core::{JavaScriptMethodReference, SdkModule};
4
5use crate::{JavaScriptAnalysisError, response::AnalyzerResponse};
6
7const ANALYZER: &str = include_str!("../analyzer/analyzer.js");
8
9#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct JavaScriptAnalysis {
11 methods: Vec<JavaScriptMethodReference>,
12 modules: Vec<SdkModule>,
13}
14
15impl JavaScriptAnalysis {
16 #[must_use]
17 pub fn methods(&self) -> &[JavaScriptMethodReference] {
18 &self.methods
19 }
20
21 #[must_use]
22 pub fn modules(&self) -> &[SdkModule] {
23 &self.modules
24 }
25}
26
27pub fn analyze_dir(path: impl AsRef<Path>) -> Result<JavaScriptAnalysis, JavaScriptAnalysisError> {
37 let path = path.as_ref();
38 if !path.is_dir() {
39 return Err(JavaScriptAnalysisError::NotDirectory {
40 path: path.to_path_buf(),
41 });
42 }
43
44 let path = path
45 .to_str()
46 .ok_or_else(|| JavaScriptAnalysisError::NonUtf8Path {
47 path: path.to_path_buf(),
48 })?;
49 let output = Command::new("node")
50 .arg("-e")
51 .arg(ANALYZER)
52 .arg(path)
53 .output()
54 .map_err(JavaScriptAnalysisError::NodeUnavailable)?;
55
56 if !output.status.success() {
57 return Err(JavaScriptAnalysisError::NodeFailed {
58 status: output.status,
59 stderr: String::from_utf8_lossy(&output.stderr).trim().to_owned(),
60 });
61 }
62
63 let response: AnalyzerResponse =
64 serde_json::from_slice(&output.stdout).map_err(JavaScriptAnalysisError::InvalidResponse)?;
65 let (methods, modules) = response
66 .into_analysis()
67 .map_err(JavaScriptAnalysisError::Analyzer)?;
68 Ok(JavaScriptAnalysis { methods, modules })
69}