oparry_cli/commands/
check.rs1use crate::{CliContext, OutputFormatter, HumanFormatter, JsonFormatter, SarifFormatter};
4use oparry_core::{OutputFormat, Result, ValidationResult};
5use oparry_parser::parser_for_path;
6use oparry_validators::{Validators, TailwindValidator, ImportValidator, RustValidator, ComponentValidator};
7use oparry_validators::{A11yValidator, SecurityValidator, PerformanceValidator, TypeScriptValidator, TestingValidator};
8use oparry_validators::{tailwind::TailwindConfig, imports::ImportConfig, rust::RustConfig, components::ComponentConfig};
9use oparry_validators::{accessibility::A11yConfig, security::SecurityConfig, performance::PerformanceConfig, typescript::TypeScriptConfig, testing::TestingConfig};
10use std::path::PathBuf;
11use glob::glob;
12
13pub struct CheckCommand {
15 paths: Vec<PathBuf>,
17 validators: Vec<String>,
19 format: OutputFormat,
21 fix: bool,
23 strict: bool,
25}
26
27impl CheckCommand {
28 pub fn new(
30 paths: Vec<PathBuf>,
31 validators: Vec<String>,
32 format: OutputFormat,
33 fix: bool,
34 strict: bool,
35 ) -> Self {
36 Self {
37 paths,
38 validators,
39 format,
40 fix,
41 strict,
42 }
43 }
44
45 pub fn run(&self, ctx: &CliContext) -> Result<()> {
47 let validators = self.build_validators(ctx)?;
49
50 let files = self.collect_files()?;
52
53 let mut total_result = ValidationResult::new();
54
55 for file in files {
57 let content = std::fs::read_to_string(&file)
58 .map_err(|e| oparry_core::Error::File {
59 path: file.clone(),
60 source: e,
61 })?;
62
63 let parser = parser_for_path(&file);
64 let parsed = parser.parse(&content)?;
65
66 let result = validators.validate(&parsed, &file)?;
67 total_result.merge(result);
68 }
69
70 let formatter = self.create_formatter();
72 println!("{}", formatter.format_result(&total_result));
73
74 total_result.finalize_with_strict_mode(self.strict);
76
77 if total_result.is_passing_with_strict(self.strict) {
79 Ok(())
80 } else {
81 Err(oparry_core::Error::Validation(
82 "Validation failed".to_string()
83 ))
84 }
85 }
86
87 fn build_validators(&self, ctx: &CliContext) -> Result<Validators> {
89 let mut validators = Validators::new();
90
91 if self.should_run_validator("tailwind") && ctx.config.tailwind.enabled {
93 let tw_config = TailwindConfig {
94 safe_list: ctx.config.tailwind.safe_list.clone(),
95 block_list: ctx.config.tailwind.block_list.clone(),
96 max_arbitrary: ctx.config.tailwind.max_arbitrary_values,
97 ..Default::default()
98 };
99 validators = validators.with_tailwind(TailwindValidator::new(tw_config));
100 }
101
102 if self.should_run_validator("imports") {
104 let imp_config = ImportConfig {
105 enforce_alias: ctx.config.imports.enforce_alias,
106 alias_map: ctx.config.imports.alias_map.clone(),
107 require_extensions: ctx.config.imports.require_extensions,
108 ..Default::default()
109 };
110 validators = validators.with_imports(ImportValidator::new(imp_config));
111 }
112
113 if self.should_run_validator("rust") && ctx.config.rust.enabled {
115 let rust_config = RustConfig {
116 deny_unsafe: ctx.config.rust.deny_unsafe.is_some(),
117 warn_unwrap: ctx.config.rust.warn_unwrap,
118 enforce_result_handling: ctx.config.rust.enforce_result_handling,
119 };
120 validators = validators.with_rust(RustValidator::new(rust_config));
121 }
122
123 if self.should_run_validator("components") && ctx.config.components.enforce_shadcn {
125 let comp_config = ComponentConfig {
126 enforce_shadcn: ctx.config.components.enforce_shadcn,
127 shadcn_path: ctx.config.components.shadcn_path.clone(),
128 ..Default::default()
129 };
130 validators = validators.with_components(ComponentValidator::new(comp_config));
131 }
132
133 if self.should_run_validator("accessibility") || self.should_run_validator("a11y") {
135 let a11y_config = A11yConfig::default();
136 validators = validators.with_accessibility(A11yValidator::new(a11y_config));
137 }
138
139 if self.should_run_validator("security") {
141 let sec_config = SecurityConfig::default();
142 validators = validators.with_security(SecurityValidator::new(sec_config));
143 }
144
145 if self.should_run_validator("performance") || self.should_run_validator("perf") {
147 let perf_config = PerformanceConfig::default();
148 validators = validators.with_performance(PerformanceValidator::new(perf_config));
149 }
150
151 if self.should_run_validator("typescript") || self.should_run_validator("ts") {
153 let ts_config = TypeScriptConfig::default();
154 validators = validators.with_typescript(TypeScriptValidator::new(ts_config));
155 }
156
157 if self.should_run_validator("testing") || self.should_run_validator("test") {
159 let test_config = TestingConfig::default();
160 validators = validators.with_testing(TestingValidator::new(test_config));
161 }
162
163 Ok(validators)
164 }
165
166 fn should_run_validator(&self, name: &str) -> bool {
168 self.validators.is_empty() || self.validators.iter().any(|v| v == name)
169 }
170
171 fn collect_files(&self) -> Result<Vec<PathBuf>> {
173 let mut files = Vec::new();
174
175 let patterns = if self.paths.is_empty() {
176 vec!["**/*.{ts,tsx,js,jsx,rs}"]
177 } else {
178 self.paths.iter().map(|p| p.to_str().unwrap()).collect()
179 };
180
181 for pattern in patterns {
182 if let Ok(entries) = glob(pattern) {
183 for entry in entries.flatten() {
184 if entry.is_file() {
185 files.push(entry);
186 }
187 }
188 } else {
189 files.push(PathBuf::from(pattern));
191 }
192 }
193
194 Ok(files)
195 }
196
197 fn create_formatter(&self) -> Box<dyn OutputFormatter> {
199 match self.format {
200 OutputFormat::Human => Box::new(HumanFormatter::new(true, true)),
201 OutputFormat::Json => Box::new(JsonFormatter::new()),
202 OutputFormat::Sarif => Box::new(SarifFormatter::new()),
203 }
204 }
205}