1use std::path::{Path, PathBuf};
4
5use rustc_hash::FxHashMap;
6
7use fallow_config::WorkspaceInfo;
8
9use fallow_types::discover::{DiscoveredFile, FileId};
10
11pub struct ProjectState {
21 files: Vec<DiscoveredFile>,
22 path_to_id: FxHashMap<PathBuf, FileId>,
23 workspaces: Vec<WorkspaceInfo>,
24}
25
26impl ProjectState {
27 pub fn new(files: Vec<DiscoveredFile>, workspaces: Vec<WorkspaceInfo>) -> Self {
29 debug_assert!(
30 files.iter().enumerate().all(|(i, f)| f.id.0 as usize == i),
31 "FileIds must be densely packed starting at 0"
32 );
33 let path_to_id = files.iter().map(|f| (f.path.clone(), f.id)).collect();
34 Self {
35 files,
36 path_to_id,
37 workspaces,
38 }
39 }
40
41 pub fn files(&self) -> &[DiscoveredFile] {
43 &self.files
44 }
45
46 pub fn workspaces(&self) -> &[WorkspaceInfo] {
48 &self.workspaces
49 }
50
51 pub fn file_by_id(&self, id: FileId) -> Option<&DiscoveredFile> {
53 self.files.get(id.0 as usize)
54 }
55
56 pub fn id_for_path(&self, path: &Path) -> Option<FileId> {
58 self.path_to_id.get(path).copied()
59 }
60
61 pub fn workspace_for_file(&self, id: FileId) -> Option<&WorkspaceInfo> {
63 let path = &self.files.get(id.0 as usize)?.path;
64 self.workspaces.iter().find(|ws| path.starts_with(&ws.root))
65 }
66
67 pub fn workspace_by_name(&self, name: &str) -> Option<&WorkspaceInfo> {
69 self.workspaces.iter().find(|ws| ws.name == name)
70 }
71
72 pub fn files_in_workspace(&self, ws: &WorkspaceInfo) -> Vec<FileId> {
74 self.files
75 .iter()
76 .filter(|f| f.path.starts_with(&ws.root))
77 .map(|f| f.id)
78 .collect()
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 fn make_file(id: u32, path: &str) -> DiscoveredFile {
87 DiscoveredFile {
88 id: FileId(id),
89 path: PathBuf::from(path),
90 size_bytes: 100,
91 }
92 }
93
94 fn make_workspace(name: &str, root: &str) -> WorkspaceInfo {
95 WorkspaceInfo {
96 root: PathBuf::from(root),
97 name: name.to_string(),
98 is_internal_dependency: false,
99 }
100 }
101
102 #[test]
103 fn id_for_path_lookup() {
104 let files = vec![
105 make_file(0, "/project/packages/a/src/index.ts"),
106 make_file(1, "/project/packages/b/src/index.ts"),
107 ];
108 let state = ProjectState::new(files, vec![]);
109 assert_eq!(
110 state.id_for_path(Path::new("/project/packages/a/src/index.ts")),
111 Some(FileId(0))
112 );
113 assert_eq!(
114 state.id_for_path(Path::new("/project/packages/b/src/index.ts")),
115 Some(FileId(1))
116 );
117 assert_eq!(state.id_for_path(Path::new("/project/missing.ts")), None);
118 }
119
120 #[test]
121 fn workspace_for_file_lookup() {
122 let files = vec![
123 make_file(0, "/project/packages/ui/src/button.ts"),
124 make_file(1, "/project/src/app.ts"),
125 ];
126 let workspaces = vec![make_workspace("ui", "/project/packages/ui")];
127 let state = ProjectState::new(files, workspaces);
128
129 assert_eq!(
130 state.workspace_for_file(FileId(0)).map(|ws| &ws.name),
131 Some(&"ui".to_string())
132 );
133 assert!(state.workspace_for_file(FileId(1)).is_none());
134 }
135
136 #[test]
137 fn workspace_by_name_lookup() {
138 let workspaces = vec![
139 make_workspace("ui", "/project/packages/ui"),
140 make_workspace("core", "/project/packages/core"),
141 ];
142 let state = ProjectState::new(vec![], workspaces);
143
144 assert!(state.workspace_by_name("ui").is_some());
145 assert!(state.workspace_by_name("core").is_some());
146 assert!(state.workspace_by_name("missing").is_none());
147 }
148
149 #[test]
150 fn files_in_workspace() {
151 let files = vec![
152 make_file(0, "/project/packages/ui/src/a.ts"),
153 make_file(1, "/project/packages/ui/src/b.ts"),
154 make_file(2, "/project/packages/core/src/c.ts"),
155 make_file(3, "/project/src/app.ts"),
156 ];
157 let workspaces = vec![
158 make_workspace("ui", "/project/packages/ui"),
159 make_workspace("core", "/project/packages/core"),
160 ];
161 let state = ProjectState::new(files, workspaces);
162
163 let ui_ws = state.workspace_by_name("ui").unwrap();
164 let ui_files = state.files_in_workspace(ui_ws);
165 assert_eq!(ui_files, vec![FileId(0), FileId(1)]);
166
167 let core_ws = state.workspace_by_name("core").unwrap();
168 let core_files = state.files_in_workspace(core_ws);
169 assert_eq!(core_files, vec![FileId(2)]);
170 }
171}