diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 8477183ae56..dcb2de2b1f8 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -57,9 +57,9 @@ impl Lint { } } - /// Returns all non-deprecated lints - pub fn active_lints(lints: impl Iterator) -> impl Iterator { - lints.filter(|l| l.deprecation.is_none()) + /// Returns all non-deprecated lints and non-internal lints + pub fn usable_lints(lints: impl Iterator) -> impl Iterator { + lints.filter(|l| l.deprecation.is_none() && !l.group.starts_with("internal")) } /// Returns the lints in a HashMap, grouped by the different lint groups @@ -101,6 +101,89 @@ fn lint_files() -> impl Iterator { .filter(|f| f.path().extension() == Some(OsStr::new("rs"))) } +/// Replace a region in a file delimited by two lines matching regexes. +/// +/// `path` is the relative path to the file on which you want to perform the replacement. +/// +/// See `replace_region_in_text` for documentation of the other options. +#[allow(clippy::expect_fun_call)] +pub fn replace_region_in_file(path: &str, start: &str, end: &str, replace_start: bool, replacements: F) where F: Fn() -> Vec { + let mut f = fs::File::open(path).expect(&format!("File not found: {}", path)); + let mut contents = String::new(); + f.read_to_string(&mut contents).expect("Something went wrong reading the file"); + let replaced = replace_region_in_text(&contents, start, end, replace_start, replacements); + + let mut f = fs::File::create(path).expect(&format!("File not found: {}", path)); + f.write_all(replaced.as_bytes()).expect("Unable to write file"); + // Ensure we write the changes with a trailing newline so that + // the file has the proper line endings. + f.write_all(b"\n").expect("Unable to write file"); +} + +/// Replace a region in a text delimited by two lines matching regexes. +/// +/// * `text` is the input text on which you want to perform the replacement +/// * `start` is a `&str` that describes the delimiter line before the region you want to replace. +/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too. +/// * `end` is a `&str` that describes the delimiter line until where the replacement should +/// happen. As the `&str` will be converted to a `Regex`, this can contain regex syntax, too. +/// * If `replace_start` is true, the `start` delimiter line is replaced as well. +/// The `end` delimiter line is never replaced. +/// * `replacements` is a closure that has to return a `Vec` which contains the new text. +/// +/// If you want to perform the replacement on files instead of already parsed text, +/// use `replace_region_in_file`. +/// +/// # Example +/// +/// ``` +/// let the_text = "replace_start\nsome text\nthat will be replaced\nreplace_end"; +/// let result = clippy_dev::replace_region_in_text( +/// the_text, +/// r#"replace_start"#, +/// r#"replace_end"#, +/// false, +/// || { +/// vec!["a different".to_string(), "text".to_string()] +/// } +/// ); +/// assert_eq!("replace_start\na different\ntext\nreplace_end", result); +/// ``` +pub fn replace_region_in_text(text: &str, start: &str, end: &str, replace_start: bool, replacements: F) -> String where F: Fn() -> Vec { + let lines = text.lines(); + let mut in_old_region = false; + let mut found = false; + let mut new_lines = vec![]; + let start = Regex::new(start).unwrap(); + let end = Regex::new(end).unwrap(); + + for line in lines { + if in_old_region { + if end.is_match(&line) { + in_old_region = false; + new_lines.extend(replacements()); + new_lines.push(line.to_string()); + } + } else if start.is_match(&line) { + if !replace_start { + new_lines.push(line.to_string()); + } + in_old_region = true; + found = true; + } else { + new_lines.push(line.to_string()); + } + } + + if !found { + // This happens if the provided regex in `clippy_dev/src/main.rs` is not found in the + // given text or file. Most likely this is an error on the programmer's side and the Regex + // is incorrect. + println!("regex {:?} not found. You may have to update it.", start); + } + new_lines.join("\n") +} + #[test] fn test_parse_contents() { let result: Vec = parse_contents( @@ -141,15 +224,54 @@ declare_deprecated_lint! { } #[test] -fn test_active_lints() { +fn test_replace_region() { + let text = r#" +abc +123 +789 +def +ghi"#; + let expected = r#" +abc +hello world +def +ghi"#; + let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, false, || { + vec!["hello world".to_string()] + }); + assert_eq!(expected, result); +} + +#[test] +fn test_replace_region_with_start() { + let text = r#" +abc +123 +789 +def +ghi"#; + let expected = r#" +hello world +def +ghi"#; + let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, true, || { + vec!["hello world".to_string()] + }); + assert_eq!(expected, result); +} + +#[test] +fn test_usable_lints() { let lints = vec![ Lint::new("should_assert_eq", "Deprecated", "abc", Some("Reason"), "module_name"), - Lint::new("should_assert_eq2", "Not Deprecated", "abc", None, "module_name") + Lint::new("should_assert_eq2", "Not Deprecated", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "internal", "abc", None, "module_name"), + Lint::new("should_assert_eq2", "internal_style", "abc", None, "module_name") ]; let expected = vec![ Lint::new("should_assert_eq2", "Not Deprecated", "abc", None, "module_name") ]; - assert_eq!(expected, Lint::active_lints(lints.into_iter()).collect::>()); + assert_eq!(expected, Lint::usable_lints(lints.into_iter()).collect::>()); } #[test] diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 9e78def78fe..7b688836a95 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -32,13 +32,17 @@ fn main() { if let Some(matches) = matches.subcommand_matches("update_lints") { if matches.is_present("print-only") { print_lints(); + } else { + update_lints(); } } } fn print_lints() { - let lint_list = gather_all().collect::>(); - let grouped_by_lint_group = Lint::by_lint_group(&lint_list); + let lint_list = gather_all(); + let usable_lints: Vec = Lint::usable_lints(lint_list).collect(); + let lint_count = usable_lints.len(); + let grouped_by_lint_group = Lint::by_lint_group(&usable_lints); for (lint_group, mut lints) in grouped_by_lint_group { if lint_group == "Deprecated" { continue; } @@ -51,5 +55,23 @@ fn print_lints() { } } - println!("there are {} lints", Lint::active_lints(lint_list.into_iter()).count()); + println!("there are {} lints", lint_count); +} + +fn update_lints() { + let lint_list = gather_all(); + let usable_lints: Vec = Lint::usable_lints(lint_list).collect(); + let lint_count = usable_lints.len(); + + replace_region_in_file( + "../README.md", + r#"\[There are \d+ lints included in this crate!\]\(https://rust-lang-nursery.github.io/rust-clippy/master/index.html\)"#, + "", + true, + || { + vec![ + format!("[There are {} lints included in this crate!](https://rust-lang-nursery.github.io/rust-clippy/master/index.html)", lint_count) + ] + } + ); }