1use std::collections::HashMap;
16
17use ryo_analysis::context::AnalysisContext;
18use ryo_analysis::symbol::WorkspaceFilePath;
19use ryo_analysis::SymbolId;
20use ryo_source::pure::PureFile;
21use ryo_spec::comment::{CommentSpec, CommentSpecExtractor};
22
23#[derive(Debug, Default)]
28pub struct AllowStore {
29 specs: HashMap<String, CommentSpec>,
31}
32
33impl AllowStore {
34 pub fn new() -> Self {
36 Self::default()
37 }
38
39 pub fn from_context(ctx: &AnalysisContext) -> Self {
43 let mut store = Self::new();
44 let extractor = CommentSpecExtractor::new();
45
46 for (path, file) in ctx.files() {
47 store.extract_from_file(path, file, &extractor);
48 }
49
50 store
51 }
52
53 fn extract_from_file(
55 &mut self,
56 _path: &WorkspaceFilePath,
57 file: &PureFile,
58 extractor: &CommentSpecExtractor,
59 ) {
60 let specs = extractor.extract(file);
61 for spec in specs {
62 self.specs.insert(spec.target.clone(), spec);
64 }
65 }
66
67 pub fn is_allowed(&self, symbol_name: &str, rule_id: &str) -> bool {
71 if let Some(spec) = self.specs.get(symbol_name) {
72 spec.is_rule_allowed(rule_id)
73 } else {
74 false
75 }
76 }
77
78 pub fn is_allowed_for_symbols(
82 &self,
83 ctx: &AnalysisContext,
84 symbol_ids: &[SymbolId],
85 rule_id: &str,
86 ) -> bool {
87 for &symbol_id in symbol_ids {
88 if let Some(path) = ctx.registry.path(symbol_id) {
89 let symbol_name = path.name();
91 if self.is_allowed(symbol_name, rule_id) {
92 return true;
93 }
94 }
95 }
96 false
97 }
98
99 pub fn get(&self, symbol_name: &str) -> Option<&CommentSpec> {
101 self.specs.get(symbol_name)
102 }
103
104 pub fn len(&self) -> usize {
106 self.specs.len()
107 }
108
109 pub fn is_empty(&self) -> bool {
111 self.specs.is_empty()
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use ryo_source::ItemKind;
119 use ryo_spec::comment::SpecDirective;
120
121 #[test]
122 fn test_allow_store_basic() {
123 let mut store = AllowStore::new();
124
125 let spec = CommentSpec::new("LegacyConfig".into(), ItemKind::Struct)
127 .with_directive(SpecDirective::Allow(vec!["RL001".into(), "RL002".into()]));
128 store.specs.insert("LegacyConfig".into(), spec);
129
130 assert!(store.is_allowed("LegacyConfig", "RL001"));
131 assert!(store.is_allowed("LegacyConfig", "RL002"));
132 assert!(!store.is_allowed("LegacyConfig", "RL003"));
133 assert!(!store.is_allowed("OtherStruct", "RL001"));
134 }
135
136 #[test]
137 fn test_allow_store_wildcard() {
138 let mut store = AllowStore::new();
139
140 let spec = CommentSpec::new("LegacyModule".into(), ItemKind::Mod)
141 .with_directive(SpecDirective::Allow(vec!["RL*".into()]));
142 store.specs.insert("LegacyModule".into(), spec);
143
144 assert!(store.is_allowed("LegacyModule", "RL001"));
145 assert!(store.is_allowed("LegacyModule", "RL999"));
146 assert!(!store.is_allowed("LegacyModule", "PT001"));
147 }
148
149 #[test]
150 fn test_allow_store_empty() {
151 let store = AllowStore::new();
152
153 assert!(!store.is_allowed("AnyStruct", "RL001"));
154 assert!(store.is_empty());
155 }
156}