qlty_analysis/workspace_entries/
workspace_entry_finder.rs1use std::sync::Arc;
2
3use crate::{code::File, WorkspaceEntry, WorkspaceEntrySource};
4
5use super::{
6 all_source::AllSource,
7 matchers::{AnyMatcher, WorkspaceEntryMatcher},
8};
9use anyhow::Result;
10use rand::seq::SliceRandom;
11use rand::thread_rng;
12use tracing::{debug, trace, warn};
13
14pub struct WorkspaceEntryFinder {
15 source: Arc<dyn WorkspaceEntrySource>,
16 matcher: Box<dyn WorkspaceEntryMatcher>,
17 results: Option<Vec<WorkspaceEntry>>,
18}
19
20impl Default for WorkspaceEntryFinder {
21 fn default() -> Self {
22 Self::new(Arc::new(AllSource::default()), Box::new(AnyMatcher))
23 }
24}
25
26impl WorkspaceEntryFinder {
27 pub fn new(
28 source: Arc<dyn WorkspaceEntrySource>,
29 matcher: Box<dyn WorkspaceEntryMatcher>,
30 ) -> Self {
31 debug!("Creating workspace entry finder from source: {:?}", source);
32
33 Self {
34 source,
35 matcher,
36 results: None,
37 }
38 }
39
40 pub fn files(&mut self) -> Result<Vec<Arc<File>>> {
41 let workspace_entries = self.workspace_entries()?;
42 let mut files = Vec::new();
43
44 for workspace_entry in workspace_entries {
45 match File::from_workspace_entry(&workspace_entry) {
46 Ok(file) => {
47 trace!("{:?}", workspace_entry);
48 files.push(file)
49 }
50 Err(e) => warn!("Unable to process workspace entry: {}", e),
51 }
52 }
53
54 Ok(files)
55 }
56
57 pub fn workspace_entries(&mut self) -> Result<Vec<WorkspaceEntry>> {
58 self.compute_workspace_entries()?;
59 Ok(self.results.as_ref().unwrap().to_owned())
60 }
61
62 pub fn sample(&mut self, sample: usize) -> Result<Vec<WorkspaceEntry>> {
63 self.compute_workspace_entries()?;
64 let mut workspace_entries = self.results.as_ref().unwrap().to_owned();
65 workspace_entries.shuffle(&mut thread_rng());
66 workspace_entries.truncate(sample);
67 Ok(workspace_entries)
68 }
69
70 fn compute_workspace_entries(&mut self) -> Result<()> {
71 if self.results.is_some() {
72 return Ok(());
73 }
74
75 let mut workspace_entries = vec![];
76
77 for workspace_entry in self.source.entries().iter() {
78 if let Some(workspace_entry) = self.matcher.matches(workspace_entry.clone()) {
79 trace!("Adding workspace entry: {:?}", &workspace_entry);
80 workspace_entries.push(workspace_entry);
81 } else {
82 trace!("Skipping workspace entry: {:?}", &workspace_entry);
83 }
84 }
85
86 self.results = Some(workspace_entries);
87 Ok(())
88 }
89}
90
91#[cfg(test)]
92mod test {
93 use super::*;
94 use crate::{
95 workspace_entries::{
96 matchers::{FileMatcher, GlobsMatcher},
97 AndMatcher,
98 },
99 WorkspaceEntryKind,
100 };
101 use itertools::Itertools;
102 use qlty_config::config::Builder;
103 use qlty_test_utilities::git::build_sample_project;
104 use std::path::PathBuf;
105
106 #[test]
107 fn test_find_without_matchers() -> Result<()> {
108 let root = build_sample_project();
109 let source = AllSource::new(root.path().to_path_buf());
110 let mut workspace_entry_finder =
111 WorkspaceEntryFinder::new(Arc::new(source), Box::new(AnyMatcher));
112 let workspace_entries = workspace_entry_finder.workspace_entries().unwrap();
113 let mut paths = vec![];
114 for workspace_entry in workspace_entries {
115 paths.push((workspace_entry.path, workspace_entry.kind));
116 }
117
118 let expected_paths = build_expected_workspace_entries(vec![
119 ("", WorkspaceEntryKind::Directory),
120 (".gitignore", WorkspaceEntryKind::File),
121 ("lib", WorkspaceEntryKind::Directory),
122 ("lib/hello.rb", WorkspaceEntryKind::File),
123 ("lib/tasks", WorkspaceEntryKind::Directory),
124 ("lib/tasks/ops", WorkspaceEntryKind::Directory),
125 ("lib/tasks/ops/deploy.rb", WorkspaceEntryKind::File),
126 ("lib/tasks/ops/setup.rb", WorkspaceEntryKind::File),
127 ("lib/tasks/some.rb", WorkspaceEntryKind::File),
128 ("greetings.rb", WorkspaceEntryKind::File),
129 ("README.md", WorkspaceEntryKind::File),
130 ]);
131
132 assert_eq!(
133 paths
134 .iter()
135 .cloned()
136 .sorted()
137 .collect::<Vec<(PathBuf, WorkspaceEntryKind)>>(),
138 expected_paths
139 );
140
141 Ok(())
142 }
143
144 #[test]
145 fn test_find_with_matchers() -> Result<()> {
146 let root = build_sample_project();
147 let source = AllSource::new(root.path().to_path_buf());
148 let file_matcher = Box::new(FileMatcher) as Box<dyn WorkspaceEntryMatcher>;
149 let file_type_matcher = build_file_types_workspace_entry_matcher()?;
150 let file_type_matcher = Box::new(file_type_matcher) as Box<dyn WorkspaceEntryMatcher>;
151
152 let mut workspace_entry_finder = WorkspaceEntryFinder::new(
153 Arc::new(source),
154 Box::new(AndMatcher::new(vec![file_matcher, file_type_matcher])),
155 );
156
157 let workspace_entries = workspace_entry_finder.workspace_entries().unwrap();
158 let mut paths = vec![];
159 for workspace_entry in workspace_entries {
160 paths.push((workspace_entry.path, workspace_entry.kind));
161 }
162
163 let expected_paths = build_expected_workspace_entries(vec![
164 ("lib/hello.rb", WorkspaceEntryKind::File),
165 ("lib/tasks/ops/deploy.rb", WorkspaceEntryKind::File),
166 ("lib/tasks/ops/setup.rb", WorkspaceEntryKind::File),
167 ("lib/tasks/some.rb", WorkspaceEntryKind::File),
168 ("greetings.rb", WorkspaceEntryKind::File),
169 ]);
170
171 assert_eq!(
172 paths
173 .iter()
174 .cloned()
175 .sorted()
176 .collect::<Vec<(PathBuf, WorkspaceEntryKind)>>(),
177 expected_paths
178 );
179
180 Ok(())
181 }
182
183 fn build_file_types_workspace_entry_matcher() -> Result<GlobsMatcher> {
184 let project_config = Builder::default_config().unwrap().to_owned();
185 let all_file_types = project_config.file_types.to_owned();
186 let file_types_names = vec!["ruby".to_owned()];
187 let file_types = all_file_types
188 .iter()
189 .filter_map(|(name, file_type)| {
190 if file_types_names.contains(&name) {
191 Some(file_type.clone())
192 } else {
193 None
194 }
195 })
196 .collect::<Vec<_>>();
197
198 GlobsMatcher::new_for_file_types(&file_types)
199 }
200
201 #[test]
202 fn test_sample() -> Result<()> {
203 let root = build_sample_project();
204 let source = AllSource::new(root.path().to_path_buf());
205 let mut workspace_entry_finder =
206 WorkspaceEntryFinder::new(Arc::new(source), Box::new(AnyMatcher));
207 let workspace_entries = workspace_entry_finder.sample(3).unwrap();
208 let mut paths = vec![];
209 for workspace_entry in workspace_entries {
210 paths.push(workspace_entry.path);
211 }
212
213 let possible_paths = vec![
214 PathBuf::from(""),
215 PathBuf::from(".gitignore"),
216 PathBuf::from("lib"),
217 PathBuf::from("lib/hello.rb"),
218 PathBuf::from("lib/tasks"),
219 PathBuf::from("lib/tasks/ops"),
220 PathBuf::from("lib/tasks/ops/deploy.rb"),
221 PathBuf::from("lib/tasks/ops/setup.rb"),
222 PathBuf::from("lib/tasks/some.rb"),
223 PathBuf::from("greetings.rb"),
224 PathBuf::from("README.md"),
225 ];
226
227 assert!(possible_paths.contains(&paths[0]));
228 assert!(possible_paths.contains(&paths[1]));
229 assert!(possible_paths.contains(&paths[2]));
230 assert!(paths.len() == 3);
231
232 Ok(())
233 }
234
235 fn build_expected_workspace_entries(
236 workspace_entries: Vec<(&str, WorkspaceEntryKind)>,
237 ) -> Vec<(PathBuf, WorkspaceEntryKind)> {
238 workspace_entries
239 .into_iter()
240 .map(|(s, tt)| (PathBuf::from(s), tt))
241 .sorted()
242 .collect()
243 }
244}