1mod require_test_for_mutation;
24
25pub use require_test_for_mutation::RequireTestForMutation;
26
27use crate::{
28 LintSeverity, OpportunityContext, OpportunityId, Suggest, SuggestCategory, SuggestLocation,
29 SuggestOpportunity,
30};
31use ryo_analysis::SymbolId;
32
33pub struct LintDetails {
35 pub suggestion: Option<String>,
37 pub expected: Option<String>,
39 pub actual: Option<String>,
41}
42
43pub trait LintSuggest: Suggest {
45 fn code(&self) -> &'static str;
47
48 fn default_severity(&self) -> LintSeverity;
50
51 fn create_lint_opportunity(
53 &self,
54 id: OpportunityId,
55 targets: Vec<SymbolId>,
56 location: SuggestLocation,
57 message: impl Into<String>,
58 details: LintDetails,
59 ) -> SuggestOpportunity {
60 SuggestOpportunity::new(
61 id,
62 targets,
63 location,
64 message,
65 1.0, OpportunityContext::Lint {
67 code: self.code().to_string(),
68 rule: self.name().to_string(),
69 severity: self.default_severity(),
70 suggestion: details.suggestion,
71 expected: details.expected,
72 actual: details.actual,
73 },
74 )
75 }
76}
77
78pub fn is_lint_suggest(suggest: &dyn Suggest) -> bool {
80 suggest.category() == SuggestCategory::Lint
81}
82
83pub fn format_clippy(opp: &SuggestOpportunity) -> String {
89 match &opp.context {
90 OpportunityContext::Lint {
91 code,
92 severity,
93 suggestion,
94 ..
95 } => {
96 let mut output = format!(
97 "{} ({}): {} [{}] {}\n",
98 opp.location.file, opp.location.symbol_path, severity, code, opp.message
99 );
100
101 if let Some(sugg) = suggestion {
102 output.push_str(&format!(" = help: {}\n", sugg));
103 }
104
105 output
106 }
107 _ => format!("{}: {}\n", opp.location, opp.message),
108 }
109}
110
111pub fn format_clippy_all(opportunities: &[SuggestOpportunity]) -> String {
113 let mut output = String::new();
114
115 for opp in opportunities {
116 output.push_str(&format_clippy(opp));
117 }
118
119 let (errors, warnings, infos) = count_by_severity(opportunities);
121 output.push_str(&format!(
122 "\nFound {} error(s), {} warning(s), {} info(s)\n",
123 errors, warnings, infos
124 ));
125
126 output
127}
128
129pub fn format_json(opp: &SuggestOpportunity) -> String {
131 serde_json::to_string(opp).unwrap_or_else(|_| "{}".to_string())
132}
133
134pub fn format_json_all(opportunities: &[SuggestOpportunity]) -> String {
136 serde_json::to_string_pretty(opportunities).unwrap_or_else(|_| "[]".to_string())
137}
138
139pub fn count_by_severity(opportunities: &[SuggestOpportunity]) -> (usize, usize, usize) {
141 let mut errors = 0;
142 let mut warnings = 0;
143 let mut infos = 0;
144
145 for opp in opportunities {
146 if let OpportunityContext::Lint { severity, .. } = &opp.context {
147 match severity {
148 LintSeverity::Error => errors += 1,
149 LintSeverity::Warning => warnings += 1,
150 LintSeverity::Info => infos += 1,
151 }
152 }
153 }
154
155 (errors, warnings, infos)
156}
157
158pub fn has_errors(opportunities: &[SuggestOpportunity]) -> bool {
160 opportunities.iter().any(|opp| {
161 matches!(
162 &opp.context,
163 OpportunityContext::Lint {
164 severity: LintSeverity::Error,
165 ..
166 }
167 )
168 })
169}
170
171#[derive(Debug, Clone, Default)]
173pub struct LintResult {
174 pub violations: Vec<SuggestOpportunity>,
176 pub files_checked: usize,
178}
179
180impl LintResult {
181 pub fn new() -> Self {
182 Self::default()
183 }
184
185 pub fn has_errors(&self) -> bool {
187 has_errors(&self.violations)
188 }
189
190 pub fn count_by_severity(&self) -> (usize, usize, usize) {
192 count_by_severity(&self.violations)
193 }
194
195 pub fn format_clippy(&self) -> String {
197 format_clippy_all(&self.violations)
198 }
199
200 pub fn format_json(&self) -> String {
202 format_json_all(&self.violations)
203 }
204}