use std::collections::HashMap;
use crate::pattern::{PatternBasedSuggest, RuleStore};
use crate::store::SuggestIndex;
use crate::suggest::{Suggest, SuggestBox, SuggestCategory};
pub struct SuggestRegistry {
suggests: Vec<SuggestBox>,
name_to_index: HashMap<&'static str, SuggestIndex>,
category_to_indices: HashMap<SuggestCategory, Vec<SuggestIndex>>,
}
impl Default for SuggestRegistry {
fn default() -> Self {
Self::new()
}
}
impl SuggestRegistry {
pub fn new() -> Self {
Self {
suggests: Vec::new(),
name_to_index: HashMap::new(),
category_to_indices: HashMap::new(),
}
}
pub fn register<S: Suggest + 'static>(&mut self, suggest: S) {
let name = suggest.name();
let category = suggest.category();
if self.name_to_index.contains_key(name) {
panic!("Suggest '{}' is already registered", name);
}
let index = SuggestIndex(self.suggests.len());
self.name_to_index.insert(name, index);
self.category_to_indices
.entry(category)
.or_default()
.push(index);
self.suggests.push(Box::new(suggest));
}
pub fn try_register<S: Suggest + 'static>(&mut self, suggest: S) -> bool {
let name = suggest.name();
if self.name_to_index.contains_key(name) {
return false;
}
self.register(suggest);
true
}
pub fn register_from_rule_store(&mut self, store: &RuleStore) -> usize {
let mut count = 0;
for rule in store.all_rules() {
let suggest = PatternBasedSuggest::new(rule.clone());
if self.try_register(suggest) {
count += 1;
}
}
count
}
pub fn get(&self, idx: SuggestIndex) -> Option<&dyn Suggest> {
self.suggests.get(idx.0).map(|s| s.as_ref())
}
pub fn get_by_name(&self, name: &str) -> Option<(SuggestIndex, &dyn Suggest)> {
self.name_to_index
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(name))
.and_then(|(_, &idx)| self.get(idx).map(|s| (idx, s)))
}
pub fn by_category(&self, category: SuggestCategory) -> Vec<(SuggestIndex, &dyn Suggest)> {
self.category_to_indices
.get(&category)
.map(|indices| {
indices
.iter()
.filter_map(|&idx| self.get(idx).map(|s| (idx, s)))
.collect()
})
.unwrap_or_default()
}
pub fn iter(&self) -> impl Iterator<Item = (SuggestIndex, &dyn Suggest)> {
self.suggests
.iter()
.enumerate()
.map(|(i, s)| (SuggestIndex(i), s.as_ref()))
}
pub fn names(&self) -> impl Iterator<Item = &'static str> + '_ {
self.suggests.iter().map(|s| s.name())
}
pub fn len(&self) -> usize {
self.suggests.len()
}
pub fn is_empty(&self) -> bool {
self.suggests.is_empty()
}
pub fn contains(&self, name: &str) -> bool {
self.name_to_index
.keys()
.any(|k| k.eq_ignore_ascii_case(name))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::suggest::{MutationSpec, SafetyLevel, SuggestOpportunity, SuggestResult};
use ryo_analysis::context::AnalysisContext;
use ryo_analysis::SymbolId;
struct TestSuggest {
name: &'static str,
category: SuggestCategory,
}
impl Suggest for TestSuggest {
fn name(&self) -> &'static str {
self.name
}
fn description(&self) -> &str {
"Test suggest"
}
fn category(&self) -> SuggestCategory {
self.category
}
fn safety_level(&self) -> SafetyLevel {
SafetyLevel::Auto
}
fn detect(&self, _ctx: &AnalysisContext, _symbols: &[SymbolId]) -> Vec<SuggestOpportunity> {
vec![]
}
fn to_mutation_specs(
&self,
_ctx: &AnalysisContext,
_opportunity: &SuggestOpportunity,
) -> SuggestResult<Vec<MutationSpec>> {
Ok(vec![])
}
}
#[test]
fn test_registry_register_and_get() {
let mut registry = SuggestRegistry::new();
registry.register(TestSuggest {
name: "Test",
category: SuggestCategory::Derive,
});
assert_eq!(registry.len(), 1);
assert!(registry.contains("Test"));
assert!(registry.contains("test")); }
#[test]
fn test_registry_get_by_name() {
let mut registry = SuggestRegistry::new();
registry.register(TestSuggest {
name: "Builder",
category: SuggestCategory::Pattern,
});
let (idx, suggest) = registry.get_by_name("builder").unwrap();
assert_eq!(suggest.name(), "Builder");
assert_eq!(idx.as_usize(), 0);
}
#[test]
fn test_registry_by_category() {
let mut registry = SuggestRegistry::new();
registry.register(TestSuggest {
name: "Default",
category: SuggestCategory::Derive,
});
registry.register(TestSuggest {
name: "Clone",
category: SuggestCategory::Derive,
});
registry.register(TestSuggest {
name: "Builder",
category: SuggestCategory::Pattern,
});
let derives = registry.by_category(SuggestCategory::Derive);
assert_eq!(derives.len(), 2);
let patterns = registry.by_category(SuggestCategory::Pattern);
assert_eq!(patterns.len(), 1);
let performance = registry.by_category(SuggestCategory::Performance);
assert_eq!(performance.len(), 0);
}
#[test]
#[should_panic(expected = "already registered")]
fn test_registry_duplicate_panics() {
let mut registry = SuggestRegistry::new();
registry.register(TestSuggest {
name: "Test",
category: SuggestCategory::Derive,
});
registry.register(TestSuggest {
name: "Test",
category: SuggestCategory::Derive,
});
}
#[test]
fn test_registry_try_register() {
let mut registry = SuggestRegistry::new();
assert!(registry.try_register(TestSuggest {
name: "Test",
category: SuggestCategory::Derive,
}));
assert!(!registry.try_register(TestSuggest {
name: "Test",
category: SuggestCategory::Derive,
}));
assert_eq!(registry.len(), 1);
}
#[test]
fn test_registry_iter() {
let mut registry = SuggestRegistry::new();
registry.register(TestSuggest {
name: "A",
category: SuggestCategory::Derive,
});
registry.register(TestSuggest {
name: "B",
category: SuggestCategory::Pattern,
});
registry.register(TestSuggest {
name: "C",
category: SuggestCategory::Performance,
});
let names: Vec<_> = registry.iter().map(|(_, s)| s.name()).collect();
assert_eq!(names, vec!["A", "B", "C"]);
}
#[test]
fn test_register_from_rule_store() {
use crate::pattern::RuleStore;
let store = RuleStore::builtin_only().unwrap();
let builtin_count = store.len();
assert!(builtin_count > 0, "Should have builtin rules");
let mut registry = SuggestRegistry::new();
let registered = registry.register_from_rule_store(&store);
assert_eq!(registered, builtin_count);
assert_eq!(registry.len(), builtin_count);
let lints = registry.by_category(SuggestCategory::Lint);
assert_eq!(lints.len(), builtin_count);
}
#[test]
fn test_register_from_rule_store_skips_duplicates() {
use crate::pattern::RuleStore;
let store = RuleStore::builtin_only().unwrap();
let mut registry = SuggestRegistry::new();
let first = registry.register_from_rule_store(&store);
let second = registry.register_from_rule_store(&store);
assert!(first > 0);
assert_eq!(second, 0); assert_eq!(registry.len(), first);
}
}