Skip to content

Commit 21eb88c

Browse files
feat: require --force to format if there's errors (#4256)
1 parent bae7bdc commit 21eb88c

File tree

2 files changed

+416
-4
lines changed

2 files changed

+416
-4
lines changed

src/formatting/report.rs

Lines changed: 238 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,69 @@ impl FormatReport {
154166
.insert(format_error);
155167
}
156168

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

0 commit comments

Comments
 (0)