1pub mod tailwind;
4pub mod imports;
5pub mod rust;
6pub mod react;
7pub mod css;
8pub mod components;
9pub mod accessibility;
10pub mod security;
11pub mod performance;
12pub mod typescript;
13pub mod testing;
14
15pub use tailwind::TailwindValidator;
16pub use imports::ImportValidator;
17pub use rust::RustValidator;
18pub use react::ReactValidator;
19pub use css::CssValidator;
20pub use components::ComponentValidator;
21pub use accessibility::A11yValidator;
22pub use security::SecurityValidator;
23pub use performance::PerformanceValidator;
24pub use typescript::TypeScriptValidator;
25pub use testing::TestingValidator;
26
27use oparry_core::{Issue, IssueLevel, Result, ValidationResult};
28use oparry_parser::{ParsedCode, Language};
29use std::path::Path;
30
31pub trait Validator: Send + Sync {
33 fn name(&self) -> &str;
35
36 fn supports(&self, language: Language) -> bool;
38
39 fn validate_parsed(&self, code: &ParsedCode, file: &Path) -> Result<ValidationResult>;
41
42 fn validate_raw(&self, source: &str, file: &Path) -> Result<ValidationResult> {
44 let _ = source;
45 let _ = file;
46 Ok(ValidationResult::new())
47 }
48}
49
50#[derive(Default)]
52pub struct Validators {
53 tailwind: Option<TailwindValidator>,
54 imports: Option<ImportValidator>,
55 rust: Option<RustValidator>,
56 react: Option<ReactValidator>,
57 css: Option<CssValidator>,
58 components: Option<ComponentValidator>,
59 accessibility: Option<A11yValidator>,
60 security: Option<SecurityValidator>,
61 performance: Option<PerformanceValidator>,
62 typescript: Option<TypeScriptValidator>,
63 testing: Option<TestingValidator>,
64}
65
66impl Validators {
67 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn with_tailwind(mut self, validator: TailwindValidator) -> Self {
74 self.tailwind = Some(validator);
75 self
76 }
77
78 pub fn with_imports(mut self, validator: ImportValidator) -> Self {
80 self.imports = Some(validator);
81 self
82 }
83
84 pub fn with_rust(mut self, validator: RustValidator) -> Self {
86 self.rust = Some(validator);
87 self
88 }
89
90 pub fn with_react(mut self, validator: ReactValidator) -> Self {
92 self.react = Some(validator);
93 self
94 }
95
96 pub fn with_css(mut self, validator: CssValidator) -> Self {
98 self.css = Some(validator);
99 self
100 }
101
102 pub fn with_components(mut self, validator: ComponentValidator) -> Self {
104 self.components = Some(validator);
105 self
106 }
107
108 pub fn with_accessibility(mut self, validator: A11yValidator) -> Self {
110 self.accessibility = Some(validator);
111 self
112 }
113
114 pub fn with_security(mut self, validator: SecurityValidator) -> Self {
116 self.security = Some(validator);
117 self
118 }
119
120 pub fn with_performance(mut self, validator: PerformanceValidator) -> Self {
122 self.performance = Some(validator);
123 self
124 }
125
126 pub fn with_typescript(mut self, validator: TypeScriptValidator) -> Self {
128 self.typescript = Some(validator);
129 self
130 }
131
132 pub fn with_testing(mut self, validator: TestingValidator) -> Self {
134 self.testing = Some(validator);
135 self
136 }
137
138 pub fn validate(&self, code: &ParsedCode, file: &Path) -> Result<ValidationResult> {
140 let mut result = ValidationResult::new();
141 result.files_checked = 1;
142
143 let language = Language::from_path(file);
144
145 if let Some(ref validator) = self.tailwind {
147 if validator.supports(language) {
148 result.merge(validator.validate_parsed(code, file)?);
149 }
150 }
151
152 if let Some(ref validator) = self.imports {
153 if validator.supports(language) {
154 result.merge(validator.validate_parsed(code, file)?);
155 }
156 }
157
158 if let Some(ref validator) = self.rust {
159 if validator.supports(language) {
160 result.merge(validator.validate_parsed(code, file)?);
161 }
162 }
163
164 if let Some(ref validator) = self.react {
165 if validator.supports(language) {
166 result.merge(validator.validate_parsed(code, file)?);
167 }
168 }
169
170 if let Some(ref validator) = self.css {
171 if validator.supports(language) {
172 result.merge(validator.validate_parsed(code, file)?);
173 }
174 }
175
176 if let Some(ref validator) = self.components {
177 if validator.supports(language) {
178 result.merge(validator.validate_parsed(code, file)?);
179 }
180 }
181
182 if let Some(ref validator) = self.accessibility {
183 if validator.supports(language) {
184 result.merge(validator.validate_parsed(code, file)?);
185 }
186 }
187
188 if let Some(ref validator) = self.security {
189 if validator.supports(language) {
190 result.merge(validator.validate_parsed(code, file)?);
191 }
192 }
193
194 if let Some(ref validator) = self.performance {
195 if validator.supports(language) {
196 result.merge(validator.validate_parsed(code, file)?);
197 }
198 }
199
200 if let Some(ref validator) = self.typescript {
201 if validator.supports(language) {
202 result.merge(validator.validate_parsed(code, file)?);
203 }
204 }
205
206 if let Some(ref validator) = self.testing {
207 if validator.supports(language) {
208 result.merge(validator.validate_parsed(code, file)?);
209 }
210 }
211
212 Ok(result)
213 }
214
215 pub fn validators(&self) -> Vec<&dyn Validator> {
217 let mut validators = Vec::new();
218
219 if let Some(ref v) = self.tailwind {
220 validators.push(v as &dyn Validator);
221 }
222 if let Some(ref v) = self.imports {
223 validators.push(v as &dyn Validator);
224 }
225 if let Some(ref v) = self.rust {
226 validators.push(v as &dyn Validator);
227 }
228 if let Some(ref v) = self.react {
229 validators.push(v as &dyn Validator);
230 }
231 if let Some(ref v) = self.css {
232 validators.push(v as &dyn Validator);
233 }
234 if let Some(ref v) = self.components {
235 validators.push(v as &dyn Validator);
236 }
237 if let Some(ref v) = self.accessibility {
238 validators.push(v as &dyn Validator);
239 }
240 if let Some(ref v) = self.security {
241 validators.push(v as &dyn Validator);
242 }
243 if let Some(ref v) = self.performance {
244 validators.push(v as &dyn Validator);
245 }
246 if let Some(ref v) = self.typescript {
247 validators.push(v as &dyn Validator);
248 }
249 if let Some(ref v) = self.testing {
250 validators.push(v as &dyn Validator);
251 }
252
253 validators
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_validators_default() {
263 let validators = Validators::new();
264 assert!(validators.validators().is_empty());
265 }
266
267 #[test]
268 fn test_validators_builder() {
269 let validators = Validators::new()
270 .with_tailwind(TailwindValidator::default_config())
271 .with_imports(ImportValidator::default_config())
272 .with_rust(RustValidator::default_config());
273
274 assert_eq!(validators.validators().len(), 3);
275 }
276
277 #[test]
278 fn test_validators_validate_tsx() {
279 let validators = Validators::new()
280 .with_tailwind(TailwindValidator::default_config())
281 .with_react(ReactValidator::default_config());
282
283 let code = ParsedCode::Generic(r#"
284 function Button() {
285 return <button className="flex">Click</button>;
286 }
287 "#.to_string());
288
289 let result = validators.validate(&code, Path::new("test.tsx")).unwrap();
290 assert!(result.passed);
291 assert_eq!(result.files_checked, 1);
292 }
293
294 #[test]
295 fn test_validators_validate_rust() {
296 let validators = Validators::new()
297 .with_rust(RustValidator::default_config());
298
299 let code = ParsedCode::Generic(r#"
300 fn main() {
301 println!("Hello");
302 }
303 "#.to_string());
304
305 let result = validators.validate(&code, Path::new("test.rs")).unwrap();
306 assert!(result.passed);
307 }
308
309 #[test]
310 fn test_validators_empty() {
311 let validators = Validators::new();
312 let code = ParsedCode::Generic("const x = 5;".to_string());
313
314 let result = validators.validate(&code, Path::new("test.js")).unwrap();
315 assert!(result.passed);
316 }
317
318 #[test]
319 fn test_validators_language_filtering() {
320 let validators = Validators::new()
321 .with_tailwind(TailwindValidator::default_config())
322 .with_rust(RustValidator::default_config());
323
324 let code = ParsedCode::Generic("const x = 5;".to_string());
325
326 let result = validators.validate(&code, Path::new("test.js")).unwrap();
328 assert!(result.passed);
329 }
330
331 #[test]
332 fn test_validators_merge_results() {
333 let validators = Validators::new()
334 .with_tailwind(TailwindValidator::default_config())
335 .with_react(ReactValidator::default_config());
336
337 let code = ParsedCode::Generic(r#"
338 class Button extends React.Component {
339 render() {
340 return <button className="invalid-class">Click</button>;
341 }
342 }
343 "#.to_string());
344
345 let result = validators.validate(&code, Path::new("test.tsx")).unwrap();
346 assert!(!result.passed || result.issues.len() >= 1);
348 }
349
350 #[test]
351 fn test_validator_trait_bounds() {
352 let validator: &dyn Validator = &TailwindValidator::default_config();
354 assert_eq!(validator.name(), "Tailwind");
355 assert!(validator.supports(Language::JavaScript));
356 }
357}