qlty_analysis/workspace_entries/
workspace_entry_finder_builder.rs1use super::{LanguagesShebangMatcher, OrMatcher, TargetMode};
2use crate::{
3 git::GitDiff,
4 workspace_entries::{AndMatcher, LanguageGlobsMatcher},
5 AllSource, ArgsSource, DiffSource, FileMatcher, GlobsMatcher, WorkspaceEntryFinder,
6 WorkspaceEntryMatcher, WorkspaceEntrySource,
7};
8use anyhow::{bail, Result};
9use qlty_config::{
10 issue_transformer::{IssueTransformer, NullIssueTransformer},
11 QltyConfig,
12};
13use std::{collections::HashMap, path::PathBuf, sync::Arc};
14use tracing::debug;
15
16#[derive(Debug, Clone)]
17pub struct WorkspaceEntryFinderBuilder {
18 pub mode: TargetMode,
19 pub root: PathBuf,
20 pub paths: Vec<PathBuf>,
21 pub config: QltyConfig,
22 pub exclude_tests: bool,
23 pub cached_git_diff: Option<GitDiff>,
24}
25
26impl Default for WorkspaceEntryFinderBuilder {
27 fn default() -> Self {
28 Self {
29 mode: TargetMode::All,
30 root: std::env::current_dir().unwrap(),
31 paths: Vec::new(),
32 config: QltyConfig::default(),
33 exclude_tests: true,
34 cached_git_diff: None,
35 }
36 }
37}
38
39impl WorkspaceEntryFinderBuilder {
40 pub fn build(&mut self) -> Result<WorkspaceEntryFinder> {
41 Ok(WorkspaceEntryFinder::new(self.source()?, self.matcher()?))
42 }
43
44 pub fn diff_line_filter(&mut self) -> Result<Box<dyn IssueTransformer>> {
45 match self.mode {
46 TargetMode::HeadDiff | TargetMode::UpstreamDiff(_) => {
47 Ok(Box::new(self.git_diff()?.line_filter))
48 }
49 _ => Ok(Box::new(NullIssueTransformer)),
50 }
51 }
52
53 fn source(&mut self) -> Result<Arc<dyn WorkspaceEntrySource>> {
54 match self.mode {
55 TargetMode::All => Ok(Arc::new(AllSource::new(self.root.clone()))),
56 TargetMode::Paths(_) => Ok(Arc::new(ArgsSource::new(
57 self.root.clone(),
58 self.paths.clone(),
59 ))),
60 TargetMode::UpstreamDiff(_) => Ok(Arc::new(DiffSource::new(
61 self.git_diff()?.changed_files,
62 &self.root,
63 ))),
64 _ => bail!("Unsupported workspace entry mode: {:?}", self.mode),
65 }
66 }
67
68 fn matcher(&self) -> Result<Box<dyn WorkspaceEntryMatcher>> {
69 let mut matcher = AndMatcher::default();
70
71 matcher.push(Box::new(FileMatcher));
73
74 let mut ignores = self
76 .config
77 .ignore
78 .iter()
79 .flat_map(|i| i.file_patterns.clone())
80 .collect::<Vec<_>>();
81 debug!("Ignoring globs: {:?}", ignores);
82
83 if self.exclude_tests {
84 if !self.config.test_patterns.is_empty() {
85 debug!("Ignoring test patterns: {:?}", self.config.test_patterns);
86 ignores.extend(self.config.test_patterns.clone());
87 } else {
88 debug!("Ignoring test patterns: none");
89 }
90 }
91
92 let ignores = GlobsMatcher::new_for_globs(&ignores, false)?;
93 matcher.push(Box::new(ignores));
94
95 matcher.push(self.languages_matcher()?);
97
98 Ok(Box::new(matcher))
99 }
100
101 fn languages_matcher(&self) -> Result<Box<dyn WorkspaceEntryMatcher>> {
102 let mut languages = OrMatcher::default();
103 let mut interpreters = HashMap::new();
104
105 for language_name in self.config.language.keys() {
106 let file_type = self.config.file_types.get(language_name).unwrap();
107 debug!(
108 "Matching {} with globs: {:?}",
109 language_name, file_type.globs
110 );
111 let matcher = LanguageGlobsMatcher::new(language_name, &file_type.globs)?;
112 languages.push(Box::new(matcher));
113
114 if !file_type.interpreters.is_empty() {
115 debug!(
116 "Matching {} with interpretters: {:?}",
117 language_name, file_type.interpreters
118 );
119 interpreters.insert(language_name.to_string(), file_type.interpreters.to_owned());
120 }
121 }
122
123 let shebangs = LanguagesShebangMatcher::new(interpreters);
126 languages.push(Box::new(shebangs));
127
128 Ok(Box::new(languages))
129 }
130
131 fn git_diff(&mut self) -> Result<GitDiff> {
132 GitDiff::compute(self.mode.diff_mode(), &self.root)
133 }
134}