affected_core/resolvers/
mod.rs1use anyhow::Result;
2use std::path::Path;
3
4use crate::types::{Ecosystem, PackageId, ProjectGraph};
5
6pub mod cargo;
7pub mod go;
8pub mod gradle;
9pub mod maven;
10pub mod npm;
11pub mod python;
12pub mod yarn;
13
14pub trait Resolver {
16 fn ecosystem(&self) -> Ecosystem;
18
19 fn detect(&self, root: &Path) -> bool;
21
22 fn resolve(&self, root: &Path) -> Result<ProjectGraph>;
24
25 fn package_for_file(&self, graph: &ProjectGraph, file: &Path) -> Option<PackageId>;
27
28 fn test_command(&self, package_id: &PackageId) -> Vec<String>;
30}
31
32pub fn all_resolvers() -> Vec<Box<dyn Resolver>> {
34 vec![
35 Box::new(cargo::CargoResolver),
36 Box::new(yarn::YarnResolver), Box::new(npm::NpmResolver),
38 Box::new(go::GoResolver),
39 Box::new(python::PythonResolver),
40 Box::new(maven::MavenResolver),
41 Box::new(gradle::GradleResolver),
42 ]
43}
44
45pub fn detect_resolver(root: &Path) -> Result<Box<dyn Resolver>> {
47 for resolver in all_resolvers() {
48 if resolver.detect(root) {
49 return Ok(resolver);
50 }
51 }
52 anyhow::bail!("No supported project type detected at {}", root.display())
53}
54
55pub fn file_to_package(graph: &ProjectGraph, file: &Path) -> Option<PackageId> {
57 let mut best: Option<(&PackageId, usize)> = None;
58
59 for (id, pkg) in &graph.packages {
60 let pkg_rel = pkg.path.strip_prefix(&graph.root).unwrap_or(&pkg.path);
62
63 if file.starts_with(pkg_rel) {
64 let depth = pkg_rel.components().count();
65 if best.is_none() || depth > best.unwrap().1 {
66 best = Some((id, depth));
67 }
68 }
69 }
70
71 best.map(|(id, _)| id.clone())
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use crate::types::Package;
78 use std::collections::HashMap;
79 use std::path::PathBuf;
80
81 fn make_project_graph(pkgs: &[(&str, &str)]) -> ProjectGraph {
82 let root = PathBuf::from("/project");
83 let mut packages = HashMap::new();
84 for (name, rel_path) in pkgs {
85 let id = PackageId(name.to_string());
86 packages.insert(
87 id.clone(),
88 Package {
89 id: id.clone(),
90 name: name.to_string(),
91 version: None,
92 path: root.join(rel_path),
93 manifest_path: root.join(rel_path).join("Cargo.toml"),
94 },
95 );
96 }
97 ProjectGraph {
98 packages,
99 edges: vec![],
100 root,
101 }
102 }
103
104 #[test]
105 fn test_file_to_package_basic() {
106 let pg = make_project_graph(&[("core", "crates/core"), ("cli", "crates/cli")]);
107 let result = file_to_package(&pg, &PathBuf::from("crates/core/src/lib.rs"));
108 assert_eq!(result, Some(PackageId("core".into())));
109 }
110
111 #[test]
112 fn test_file_to_package_no_match() {
113 let pg = make_project_graph(&[("core", "crates/core")]);
114 let result = file_to_package(&pg, &PathBuf::from("scripts/build.sh"));
115 assert!(result.is_none());
116 }
117
118 #[test]
119 fn test_file_to_package_longest_prefix() {
120 let pg = make_project_graph(&[("foo", "crates/foo"), ("foo-bar", "crates/foo/bar")]);
122
123 let result = file_to_package(&pg, &PathBuf::from("crates/foo/bar/src/lib.rs"));
125 assert_eq!(result, Some(PackageId("foo-bar".into())));
126
127 let result = file_to_package(&pg, &PathBuf::from("crates/foo/src/lib.rs"));
129 assert_eq!(result, Some(PackageId("foo".into())));
130 }
131
132 #[test]
133 fn test_file_to_package_root_level_file() {
134 let pg = make_project_graph(&[("core", "crates/core")]);
135 let result = file_to_package(&pg, &PathBuf::from("README.md"));
136 assert!(result.is_none());
137 }
138
139 #[test]
140 fn test_detect_resolver_cargo() {
141 let dir = tempfile::tempdir().unwrap();
142 std::fs::write(dir.path().join("Cargo.toml"), "[workspace]\nmembers = []\n").unwrap();
143
144 let resolver = detect_resolver(dir.path()).unwrap();
145 assert_eq!(resolver.ecosystem(), Ecosystem::Cargo);
146 }
147
148 #[test]
149 fn test_detect_resolver_none() {
150 let dir = tempfile::tempdir().unwrap();
151 assert!(detect_resolver(dir.path()).is_err());
152 }
153
154 #[test]
155 fn test_all_resolvers_count() {
156 assert_eq!(all_resolvers().len(), 7);
157 }
158}