diff --git a/crates/cli/src/cli_configuration.rs b/crates/cli/src/cli_configuration.rs index a5e087b..1d752e3 100644 --- a/crates/cli/src/cli_configuration.rs +++ b/crates/cli/src/cli_configuration.rs @@ -83,14 +83,14 @@ impl CliConfiguration { } } -fn calculate_config_by_results(_results: &TokenSearchResults) -> Option { +fn calculate_config_by_results(results: &TokenSearchResults) -> Option { let config_path: Option = dirs::home_dir().and_then(|ref p| { let final_path = Path::new(p).join(".unused.yml"); final_path.to_str().map(|v| v.to_owned()) }); match config_path { Some(path) => match read_file(&path) { - Ok(contents) => ProjectConfigurations::load(&contents).get("Rails"), + Ok(contents) => ProjectConfigurations::load(&contents).best_match(results), _ => None, }, None => None, diff --git a/crates/project_configuration/src/config.rs b/crates/project_configuration/src/config.rs index 39a6b0b..595db51 100644 --- a/crates/project_configuration/src/config.rs +++ b/crates/project_configuration/src/config.rs @@ -1,6 +1,6 @@ use super::value_assertion::Assertion; use std::path::Path; -use token_search::TokenSearchResult; +use token_search::{TokenSearchResult, TokenSearchResults}; #[derive(Clone)] pub struct ProjectConfiguration { @@ -9,6 +9,7 @@ pub struct ProjectConfiguration { pub test_file: Vec, pub config_file: Vec, pub low_likelihood: Vec, + pub matches_if: Vec, } #[derive(Clone, Debug, PartialEq)] @@ -44,6 +45,7 @@ impl ProjectConfiguration { test_file: vec![PathPrefix::new("test/")], config_file: vec![], low_likelihood: vec![], + matches_if: vec![], } } @@ -55,4 +57,13 @@ impl ProjectConfiguration { .iter() .find(|ll| ll.matches(token_search_result)) } + + pub fn codebase_config_match(&self, results: &TokenSearchResults) -> bool { + self.matches_if.iter().all(|assertion| { + results + .value() + .iter() + .any(|result| assertion.matches(result)) + }) + } } diff --git a/crates/project_configuration/src/loader.rs b/crates/project_configuration/src/loader.rs index 84d4cf2..c53a92f 100644 --- a/crates/project_configuration/src/loader.rs +++ b/crates/project_configuration/src/loader.rs @@ -1,6 +1,7 @@ use super::config::*; use super::value_assertion::{Assertion, ValueMatcher}; use std::collections::{HashMap, HashSet}; +use token_search::TokenSearchResults; use yaml_rust::{Yaml, YamlLoader}; pub struct ProjectConfigurations { @@ -20,6 +21,14 @@ impl ProjectConfigurations { ProjectConfigurations { configs } } + pub fn best_match(&self, results: &TokenSearchResults) -> Option { + self.configs + .iter() + .filter(|(_, config)| config.codebase_config_match(results)) + .nth(0) + .map(|(_, v)| v.clone()) + } + fn parse_all_from_yaml(contents: &[Yaml]) -> HashMap { match contents { [Yaml::Array(items)] => items.iter().filter(|i| !i["name"].is_badvalue()).fold( @@ -44,6 +53,7 @@ impl ProjectConfigurations { test_file: Self::parse_path_prefixes("test_files", contents), config_file: Self::parse_path_prefixes("config_files", contents), low_likelihood: Self::parse_low_likelihoods(contents), + matches_if: Self::parse_matches_if(contents), } } @@ -69,6 +79,37 @@ impl ProjectConfigurations { _ => vec![], } } + + fn parse_matches_if(contents: &Yaml) -> Vec { + match &contents["matches_if"] { + Yaml::Array(items) => items + .iter() + .flat_map(|i| Self::parse_individual_matches_if(i)) + .collect(), + _ => vec![], + } + } + + fn parse_individual_matches_if(contents: &Yaml) -> Vec { + vec![ + "path_starts_with", + "path_ends_with", + "path_equals", + "token_equals", + "token_starts_with", + "token_ends_with", + "allowed_tokens", + "class_or_module", + ] + .iter() + .map(|&k| match &contents[k] { + Yaml::String(v) => Self::parse_single_assertion(k, &v), + _ => None, + }) + .filter_map(|a| a) + .collect() + } + fn parse_low_likelihood_item(contents: &Yaml) -> Option { match &contents["name"] { Yaml::String(name) => Some(LowLikelihoodConfig { @@ -76,8 +117,10 @@ impl ProjectConfigurations { matchers: vec![ "path_starts_with", "path_ends_with", + "path_equals", "token_starts_with", "token_ends_with", + "token_equals", "allowed_tokens", "class_or_module", ] @@ -112,12 +155,18 @@ impl ProjectConfigurations { "path_ends_with" => Some(Assertion::PathAssertion(ValueMatcher::EndsWith( val.to_string(), ))), + "path_equals" => Some(Assertion::PathAssertion(ValueMatcher::Equals( + val.to_string(), + ))), "token_starts_with" => Some(Assertion::TokenAssertion(ValueMatcher::StartsWith( val.to_string(), ))), "token_ends_with" => Some(Assertion::TokenAssertion(ValueMatcher::EndsWith( val.to_string(), ))), + "token_equals" => Some(Assertion::TokenAssertion(ValueMatcher::Equals( + val.to_string(), + ))), _ => None, } } @@ -152,6 +201,10 @@ mod tests { fn yaml_contents() -> String { " - name: Phoenix + matches_if: + - token_equals: Application + - token_equals: Endpoint + - token_equals: Repo application_files: - lib/ - web/ @@ -279,6 +332,15 @@ mod tests { vec![PathPrefix::new("lib/"), PathPrefix::new("web/")] ); + assert_eq!( + phoenix_config.matches_if, + vec![ + Assertion::TokenAssertion(ValueMatcher::Equals(String::from("Application"))), + Assertion::TokenAssertion(ValueMatcher::Equals(String::from("Endpoint"))), + Assertion::TokenAssertion(ValueMatcher::Equals(String::from("Repo"))), + ] + ); + assert_eq!(phoenix_config.test_file, vec![PathPrefix::new("test/"),]); assert_eq!(phoenix_config.config_file, vec![PathPrefix::new("priv/"),]); diff --git a/crates/project_configuration/src/value_assertion.rs b/crates/project_configuration/src/value_assertion.rs index 7c9eb35..0652bc0 100644 --- a/crates/project_configuration/src/value_assertion.rs +++ b/crates/project_configuration/src/value_assertion.rs @@ -24,6 +24,7 @@ impl Assertion { pub enum ValueMatcher { StartsWith(String), EndsWith(String), + Equals(String), ExactMatchOnAnyOf(HashSet), StartsWithCapital, } @@ -33,6 +34,7 @@ impl ValueMatcher { match self { ValueMatcher::StartsWith(v) => haystack.starts_with(v), ValueMatcher::EndsWith(v) => haystack.ends_with(v), + ValueMatcher::Equals(v) => haystack == v, ValueMatcher::ExactMatchOnAnyOf(vs) => vs.contains(haystack), ValueMatcher::StartsWithCapital => haystack.starts_with(|v: char| v.is_uppercase()), } @@ -77,4 +79,12 @@ mod tests { assert!(ValueMatcher::StartsWithCapital.check(&"Foo")); assert!(!ValueMatcher::StartsWithCapital.check(&"foo")); } + + #[test] + fn matches_equals() { + assert!(ValueMatcher::Equals(foo()).check("foo")); + assert!(!ValueMatcher::Equals(foo()).check("Foo")); + assert!(!ValueMatcher::Equals(foo()).check(" foo")); + assert!(!ValueMatcher::Equals(foo()).check("foo ")); + } }