Skip to content

Commit e13b5fa

Browse files
feat: require --force to format if there's errors
1 parent cf1f0cf commit e13b5fa

File tree

2 files changed

+420
-4
lines changed

2 files changed

+420
-4
lines changed

src/formatting/report.rs

Lines changed: 242 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use std::cell::{Ref, RefCell};
2-
use std::collections::{BTreeMap, BTreeSet, HashSet};
2+
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
33
use std::rc::Rc;
44

55
use crate::formatting::FormattedSnippet;
66
use crate::result::{ErrorKind, FormatError};
7+
use crate::Config;
78
use crate::FileName;
89
use crate::NewlineStyle;
910

@@ -78,6 +79,17 @@ impl FormatResult {
7879
pub(crate) fn formatted_snippet(&self) -> &FormattedSnippet {
7980
&self.formatted_snippet
8081
}
82+
83+
pub(crate) fn has_error_kind(&self, kind: ErrorKind) -> bool {
84+
self.all_errors().any(|e| e.kind() == kind)
85+
}
86+
87+
pub(crate) fn has_any_matching_errors<F>(&self, error_matcher: F) -> bool
88+
where
89+
F: FnMut(&FormatError) -> bool,
90+
{
91+
self.all_errors().any(error_matcher)
92+
}
8193
}
8294

8395
impl FormatReport {
@@ -154,6 +166,67 @@ impl FormatReport {
154166
.insert(format_error);
155167
}
156168

169+
fn has_any_matching_format_result<F>(&self, error_check: F) -> bool
170+
where
171+
F: FnMut((&FileName, &FormatResult)) -> bool,
172+
{
173+
RefCell::borrow(&self.format_result).iter().any(error_check)
174+
}
175+
176+
fn has_error_kind(&self, kind: ErrorKind) -> bool {
177+
self.has_any_matching_format_result(|(_, format_result)| format_result.has_error_kind(kind))
178+
}
179+
180+
pub fn has_deprecated_attribute_errors(&self) -> bool {
181+
self.has_error_kind(ErrorKind::BadAttr)
182+
}
183+
184+
pub fn has_invalid_rustfmt_attribute_errors(&self) -> bool {
185+
self.has_error_kind(ErrorKind::DeprecatedAttr)
186+
}
187+
188+
pub fn has_attribute_errors(&self) -> bool {
189+
self.has_any_matching_format_result(|(_, format_result)| {
190+
format_result.has_any_matching_errors(|e| match e.kind() {
191+
ErrorKind::BadAttr | ErrorKind::DeprecatedAttr => true,
192+
_ => false,
193+
})
194+
})
195+
}
196+
197+
pub fn has_failing_errors(&self, file_config_map: HashMap<FileName, &Config>) -> bool {
198+
self.has_any_matching_format_result(|(file_name, format_result)| {
199+
format_result.has_any_matching_errors(|e| match e.kind() {
200+
ErrorKind::BadAttr | ErrorKind::DeprecatedAttr => true,
201+
ErrorKind::LicenseCheck => {
202+
if let Some(config) = file_config_map.get(file_name) {
203+
if config.was_set().license_template_path() {
204+
return true;
205+
}
206+
}
207+
false
208+
}
209+
ErrorKind::LineOverflow(..) => {
210+
if let Some(config) = file_config_map.get(file_name) {
211+
if config.error_on_line_overflow() {
212+
return true;
213+
}
214+
}
215+
false
216+
}
217+
ErrorKind::TrailingWhitespace => {
218+
if let Some(config) = file_config_map.get(file_name) {
219+
if config.error_on_unformatted() {
220+
return true;
221+
}
222+
}
223+
false
224+
}
225+
_ => false,
226+
})
227+
})
228+
}
229+
157230
pub fn has_errors(&self) -> bool {
158231
RefCell::borrow(&self.format_result)
159232
.iter()
@@ -184,3 +257,171 @@ impl NonFormattedRange {
184257
self.lo <= line && line <= self.hi
185258
}
186259
}
260+
261+
#[cfg(test)]
262+
mod test {
263+
use super::*;
264+
265+
#[cfg(test)]
266+
mod has_failing_errors {
267+
use super::*;
268+
use std::path::PathBuf;
269+
270+
#[test]
271+
fn false_with_only_macro() {
272+
let file_name = FileName::Real(PathBuf::from("foo/bar.rs"));
273+
let report = FormatReport::new();
274+
report.add_format_error(
275+
file_name.clone(),
276+
FormatError::new(ErrorKind::MacroFormatError, 2, String::new()),
277+
);
278+
assert!(
279+
!report.has_failing_errors(
280+
vec![(file_name, &Config::default())].into_iter().collect()
281+
)
282+
);
283+
}
284+
285+
#[test]
286+
fn true_with_bad_attr() {
287+
let file_name = FileName::Real(PathBuf::from("bar/baz.rs"));
288+
let report = FormatReport::new();
289+
report.add_format_error(
290+
file_name.clone(),
291+
FormatError::new(ErrorKind::BadAttr, 2, String::new()),
292+
);
293+
assert!(
294+
report.has_failing_errors(
295+
vec![(file_name, &Config::default())].into_iter().collect()
296+
)
297+
);
298+
}
299+
300+
#[test]
301+
fn true_with_deprecated_attr() {
302+
let file_name = FileName::Real(PathBuf::from("baz/qux.rs"));
303+
let report = FormatReport::new();
304+
report.add_format_error(
305+
file_name.clone(),
306+
FormatError::new(ErrorKind::DeprecatedAttr, 2, String::new()),
307+
);
308+
assert!(
309+
report.has_failing_errors(
310+
vec![(file_name, &Config::default())].into_iter().collect()
311+
)
312+
);
313+
}
314+
315+
#[test]
316+
fn false_with_license_check_and_config_disabled() {
317+
let file_name = FileName::Real(PathBuf::from("foo.rs"));
318+
let bar_file_name = FileName::Real(PathBuf::from("bar.rs"));
319+
let mut license_config = Config::default();
320+
license_config
321+
.set()
322+
.license_template_path(String::from("template.txt"));
323+
let report = FormatReport::new();
324+
report.add_format_error(
325+
bar_file_name.clone(),
326+
FormatError::new(ErrorKind::LicenseCheck, 2, String::new()),
327+
);
328+
assert!(
329+
!report.has_failing_errors(
330+
vec![
331+
(file_name, &license_config),
332+
(bar_file_name, &Config::default()),
333+
]
334+
.into_iter()
335+
.collect(),
336+
)
337+
);
338+
}
339+
340+
#[test]
341+
fn true_with_license_check_and_config_enabled() {
342+
let file_name = FileName::Real(PathBuf::from("foo.rs"));
343+
let report = FormatReport::new();
344+
let mut config = Config::default();
345+
config
346+
.set()
347+
.license_template_path(String::from("license.txt"));
348+
report.add_format_error(
349+
file_name.clone(),
350+
FormatError::new(ErrorKind::LicenseCheck, 2, String::new()),
351+
);
352+
assert!(report.has_failing_errors(vec![(file_name, &config)].into_iter().collect()));
353+
}
354+
355+
#[test]
356+
fn false_with_line_overflow_and_config_disabled() {
357+
let file_name = FileName::Real(PathBuf::from("short_enough.rs"));
358+
let overflow_file_name = FileName::Real(PathBuf::from("too_long.rs"));
359+
let mut overflow_config = Config::default();
360+
overflow_config.set().error_on_line_overflow(true);
361+
let report = FormatReport::new();
362+
report.add_format_error(
363+
overflow_file_name.clone(),
364+
FormatError::new(ErrorKind::LineOverflow(20, 25), 2, String::new()),
365+
);
366+
assert!(
367+
!report.has_failing_errors(
368+
vec![
369+
(file_name, &overflow_config),
370+
(overflow_file_name, &Config::default()),
371+
]
372+
.into_iter()
373+
.collect(),
374+
)
375+
);
376+
}
377+
378+
#[test]
379+
fn true_with_line_overflow_and_config_enabled() {
380+
let file_name = FileName::Real(PathBuf::from("overflowed.rs"));
381+
let report = FormatReport::new();
382+
let mut config = Config::default();
383+
config.set().error_on_line_overflow(true);
384+
report.add_format_error(
385+
file_name.clone(),
386+
FormatError::new(ErrorKind::LineOverflow(100, 103), 2, String::new()),
387+
);
388+
assert!(report.has_failing_errors(vec![(file_name, &config)].into_iter().collect()));
389+
}
390+
391+
#[test]
392+
fn false_with_trailing_whitespace_and_config_disabled() {
393+
let file_name = FileName::Real(PathBuf::from("trimmed.rs"));
394+
let trailing_file_name = FileName::Real(PathBuf::from("trailing_whitespace.rs"));
395+
let mut trailing_config = Config::default();
396+
trailing_config.set().error_on_unformatted(true);
397+
let report = FormatReport::new();
398+
report.add_format_error(
399+
trailing_file_name.clone(),
400+
FormatError::new(ErrorKind::TrailingWhitespace, 3, String::new()),
401+
);
402+
assert!(
403+
!report.has_failing_errors(
404+
vec![
405+
(file_name, &trailing_config),
406+
(trailing_file_name, &Config::default()),
407+
]
408+
.into_iter()
409+
.collect(),
410+
)
411+
);
412+
}
413+
414+
#[test]
415+
fn true_with_trailing_whitespace_and_config_enabled() {
416+
let file_name = FileName::Real(PathBuf::from("trailing_whitespace.rs"));
417+
let report = FormatReport::new();
418+
let mut config = Config::default();
419+
config.set().error_on_unformatted(true);
420+
report.add_format_error(
421+
file_name.clone(),
422+
FormatError::new(ErrorKind::TrailingWhitespace, 42, String::new()),
423+
);
424+
assert!(report.has_failing_errors(vec![(file_name, &config)].into_iter().collect()));
425+
}
426+
}
427+
}

0 commit comments

Comments
 (0)