use std::path::Path;
use fallow_types::semantic::SemanticAnalysisIdentity;
use fallow_types::workspace::{NodeModulesState, WorkspaceDiagnostic, WorkspaceDiagnosticKind};
use super::BaselineData;
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub(super) struct BaselineAnalysisIdentity {
#[serde(flatten)]
pub(super) semantic: SemanticAnalysisIdentity,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(super) node_modules: Option<NodeModulesState>,
}
impl From<SemanticAnalysisIdentity> for BaselineAnalysisIdentity {
fn from(semantic: SemanticAnalysisIdentity) -> Self {
Self {
semantic,
node_modules: None,
}
}
}
impl BaselineData {
#[must_use]
pub const fn with_node_modules(mut self, state: NodeModulesState) -> Self {
self.analysis_identity.node_modules = Some(state);
self
}
}
#[must_use]
pub fn saved_node_modules_state(json: &str) -> Option<NodeModulesState> {
let value = serde_json::from_str::<serde_json::Value>(json).ok()?;
let state = value.get("analysis_identity")?.get("node_modules")?;
serde_json::from_value(state.clone()).ok()
}
#[must_use]
pub fn install_state_diagnostic(
root: &Path,
baseline_path: &Path,
json: &str,
run: NodeModulesState,
) -> Option<WorkspaceDiagnostic> {
let saved = saved_node_modules_state(json)?;
(saved != run).then(|| {
WorkspaceDiagnostic::new(
root,
dunce::canonicalize(baseline_path).unwrap_or_else(|_| baseline_path.to_path_buf()),
WorkspaceDiagnosticKind::BaselineInstallStateMismatch {
baseline_node_modules: saved,
run_node_modules: run,
},
)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::results::AnalysisResults;
fn saved(state: Option<NodeModulesState>) -> String {
let data = BaselineData::from_results(&AnalysisResults::default(), Path::new("/p"));
let data = match state {
Some(state) => data.with_node_modules(state),
None => data,
};
serde_json::to_string(&data).expect("serializes")
}
#[test]
fn a_saved_baseline_records_the_install_state_inside_the_analysis_identity() {
let json = saved(Some(NodeModulesState::Present));
let value: serde_json::Value = serde_json::from_str(&json).expect("json");
assert_eq!(value["analysis_identity"]["node_modules"], "present");
assert_eq!(value["analysis_identity"]["mode"], "syntactic");
assert_eq!(
saved_node_modules_state(&json),
Some(NodeModulesState::Present)
);
let loaded: BaselineData = serde_json::from_str(&json).expect("loads");
assert_eq!(
loaded.analysis_identity(),
&SemanticAnalysisIdentity::syntactic()
);
}
#[test]
fn a_legacy_baseline_has_no_install_state_and_still_loads() {
let json = saved(None);
assert!(!json.contains("node_modules"));
assert_eq!(saved_node_modules_state(&json), None);
assert!(serde_json::from_str::<BaselineData>(&json).is_ok());
let without_identity = r#"{"unused_files":[],"unused_exports":[],"unused_types":[],
"unused_dependencies":[],"unused_dev_dependencies":[]}"#;
assert!(serde_json::from_str::<BaselineData>(without_identity).is_ok());
assert_eq!(saved_node_modules_state(without_identity), None);
assert_eq!(saved_node_modules_state("not json"), None);
}
#[test]
fn the_semantic_identity_ignores_the_install_state_key() {
let json = saved(Some(NodeModulesState::Missing));
let value: serde_json::Value = serde_json::from_str(&json).expect("json");
let identity: SemanticAnalysisIdentity =
serde_json::from_value(value["analysis_identity"].clone()).expect("older read");
assert_eq!(identity, SemanticAnalysisIdentity::syntactic());
}
#[test]
fn only_a_known_and_different_state_gives_a_diagnostic() {
let root = Path::new("/p");
let path = root.join("fallow-baseline.json");
let present = saved(Some(NodeModulesState::Present));
assert!(
install_state_diagnostic(root, &path, &present, NodeModulesState::Present).is_none()
);
assert!(
install_state_diagnostic(root, &path, &saved(None), NodeModulesState::Missing)
.is_none()
);
let diagnostic = install_state_diagnostic(root, &path, &present, NodeModulesState::Missing)
.expect("mismatch");
assert_eq!(
diagnostic.kind,
WorkspaceDiagnosticKind::BaselineInstallStateMismatch {
baseline_node_modules: NodeModulesState::Present,
run_node_modules: NodeModulesState::Missing,
}
);
assert!(diagnostic.degrades_analysis);
}
}