mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-08 01:28:18 +03:00
317 lines
12 KiB
Rust
317 lines
12 KiB
Rust
use crate::parse::cursor::Cursor;
|
|
use crate::parse::{Lint, LintData, LintPass, VecBuf};
|
|
use crate::utils::{FileUpdater, UpdateMode, UpdateStatus, update_text_region_fn};
|
|
use core::range::Range;
|
|
use itertools::Itertools;
|
|
use std::collections::HashSet;
|
|
use std::fmt::Write;
|
|
use std::path::{self, Path};
|
|
|
|
const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\
|
|
// Use that command to update this file and do not edit by hand.\n\
|
|
// Manual edits will be overwritten.\n\n";
|
|
|
|
const DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html";
|
|
|
|
impl LintData<'_> {
|
|
#[expect(clippy::too_many_lines)]
|
|
pub fn gen_decls(&self, update_mode: UpdateMode) {
|
|
let mut updater = FileUpdater::default();
|
|
|
|
let mut lints: Vec<_> = self.lints.iter().map(|(&x, y)| (x, y)).collect();
|
|
lints.sort_by_key(|&(x, _)| x);
|
|
updater.update_file_checked(
|
|
"cargo dev update_lints",
|
|
update_mode,
|
|
"CHANGELOG.md",
|
|
&mut update_text_region_fn(
|
|
"<!-- begin autogenerated links to lint list -->\n",
|
|
"<!-- end autogenerated links to lint list -->",
|
|
|dst| {
|
|
for &(lint, _) in &lints {
|
|
writeln!(dst, "[`{lint}`]: {DOCS_LINK}#{lint}").unwrap();
|
|
}
|
|
},
|
|
),
|
|
);
|
|
|
|
let mut active = Vec::with_capacity(lints.len());
|
|
let mut deprecated = Vec::with_capacity(lints.len() / 8);
|
|
let mut renamed = Vec::with_capacity(lints.len() / 8);
|
|
for &(name, lint) in &lints {
|
|
match lint {
|
|
Lint::Active(lint) => active.push((name, lint)),
|
|
Lint::Deprecated(lint) => deprecated.push((name, lint)),
|
|
Lint::Renamed(lint) => renamed.push((name, lint)),
|
|
}
|
|
}
|
|
active.sort_by_key(|&(_, lint)| lint.module);
|
|
|
|
// Round to avoid updating the readme every time a lint is added/deprecated.
|
|
let lint_count = active.len() / 50 * 50;
|
|
updater.update_file_checked(
|
|
"cargo dev update_lints",
|
|
update_mode,
|
|
"README.md",
|
|
&mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| {
|
|
write!(dst, "{lint_count}").unwrap();
|
|
}),
|
|
);
|
|
updater.update_file_checked(
|
|
"cargo dev update_lints",
|
|
update_mode,
|
|
"book/src/README.md",
|
|
&mut update_text_region_fn("[There are over ", " lints included in this crate!]", |dst| {
|
|
write!(dst, "{lint_count}").unwrap();
|
|
}),
|
|
);
|
|
|
|
updater.update_file_checked(
|
|
"cargo dev update_lints",
|
|
update_mode,
|
|
"clippy_lints/src/deprecated_lints.rs",
|
|
&mut |_, src, dst| {
|
|
let mut cursor = Cursor::new(src);
|
|
assert!(
|
|
cursor.find_ident("declare_with_version").is_some()
|
|
&& cursor.find_ident("declare_with_version").is_some(),
|
|
"error reading deprecated lints"
|
|
);
|
|
dst.push_str(&src[..cursor.pos() as usize]);
|
|
dst.push_str("! { DEPRECATED(DEPRECATED_VERSION) = [\n");
|
|
for &(name, data) in &deprecated {
|
|
write!(
|
|
dst,
|
|
" #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n",
|
|
data.version, data.reason,
|
|
)
|
|
.unwrap();
|
|
}
|
|
dst.push_str(
|
|
"]}\n\n\
|
|
#[rustfmt::skip]\n\
|
|
declare_with_version! { RENAMED(RENAMED_VERSION) = [\n\
|
|
",
|
|
);
|
|
for &(name, data) in &renamed {
|
|
write!(
|
|
dst,
|
|
" #[clippy::version = \"{}\"]\n (\"clippy::{name}\", \"{}\"),\n",
|
|
data.version, data.new_name,
|
|
)
|
|
.unwrap();
|
|
}
|
|
dst.push_str("]}\n");
|
|
UpdateStatus::from_changed(src != dst)
|
|
},
|
|
);
|
|
updater.update_file_checked(
|
|
"cargo dev update_lints",
|
|
update_mode,
|
|
"tests/ui/deprecated.rs",
|
|
&mut |_, src, dst| {
|
|
dst.push_str(GENERATED_FILE_COMMENT);
|
|
for &(lint, _) in &deprecated {
|
|
writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap();
|
|
}
|
|
dst.push_str("\nfn main() {}\n");
|
|
UpdateStatus::from_changed(src != dst)
|
|
},
|
|
);
|
|
updater.update_file_checked(
|
|
"cargo dev update_lints",
|
|
update_mode,
|
|
"tests/ui/rename.rs",
|
|
&mut move |_, src, dst| {
|
|
let mut seen_lints = HashSet::new();
|
|
dst.push_str(GENERATED_FILE_COMMENT);
|
|
dst.push_str("#![allow(clippy::duplicated_attributes)]\n");
|
|
for &(_, lint) in &renamed {
|
|
if seen_lints.insert(lint.new_name) {
|
|
writeln!(dst, "#![allow({})]", lint.new_name).unwrap();
|
|
}
|
|
}
|
|
for &(lint, _) in &renamed {
|
|
writeln!(dst, "#![warn(clippy::{lint})] //~ ERROR: lint `clippy::{lint}`").unwrap();
|
|
}
|
|
dst.push_str("\nfn main() {}\n");
|
|
UpdateStatus::from_changed(src != dst)
|
|
},
|
|
);
|
|
for (crate_name, lints) in active.iter().copied().into_group_map_by(|&(_, lint)| {
|
|
let Some(path::Component::Normal(name)) = lint.path.components().next() else {
|
|
// All paths should start with `{crate_name}/src` when parsed from `find_lint_decls`
|
|
panic!(
|
|
"internal error: can't read crate name from path `{}`",
|
|
lint.path.display()
|
|
);
|
|
};
|
|
name
|
|
}) {
|
|
updater.update_file_checked(
|
|
"cargo dev update_lints",
|
|
update_mode,
|
|
Path::new(crate_name).join("src/lib.rs"),
|
|
&mut update_text_region_fn(
|
|
"// begin lints modules, do not remove this comment, it's used in `update_lints`\n",
|
|
"// end lints modules, do not remove this comment, it's used in `update_lints`",
|
|
|dst| {
|
|
let mut prev = "";
|
|
for &(_, lint) in &lints {
|
|
if lint.module != prev {
|
|
writeln!(dst, "mod {};", lint.module).unwrap();
|
|
prev = lint.module;
|
|
}
|
|
}
|
|
},
|
|
),
|
|
);
|
|
updater.update_file_checked(
|
|
"cargo dev update_lints",
|
|
update_mode,
|
|
Path::new(crate_name).join("src/declared_lints.rs"),
|
|
&mut |_, src, dst| {
|
|
dst.push_str(GENERATED_FILE_COMMENT);
|
|
dst.push_str("pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[\n");
|
|
let mut buf = String::new();
|
|
for &(name, lint) in &lints {
|
|
buf.clear();
|
|
buf.push_str(name);
|
|
buf.make_ascii_uppercase();
|
|
if lint.module.is_empty() {
|
|
writeln!(dst, " crate::{buf}_INFO,").unwrap();
|
|
} else {
|
|
writeln!(dst, " crate::{}::{buf}_INFO,", lint.module).unwrap();
|
|
}
|
|
}
|
|
dst.push_str("];\n");
|
|
UpdateStatus::from_changed(src != dst)
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl LintPass<'_> {
|
|
pub fn gen_mac(&self, dst: &mut String) {
|
|
let mut line_start = dst.len();
|
|
dst.extend([self.mac.name(), "!("]);
|
|
let has_docs = write_comment_lines(self.docs, "\n ", dst);
|
|
let (list_indent, list_multi_end, end) = if has_docs {
|
|
dst.push_str("\n ");
|
|
line_start = dst.len() - 4;
|
|
(" ", "\n ", "]\n);")
|
|
} else {
|
|
(" ", "\n", "]);")
|
|
};
|
|
dst.push_str(self.name);
|
|
if let Some(lt) = self.lt {
|
|
dst.extend(["<", lt, ">"]);
|
|
}
|
|
dst.push_str(" => [");
|
|
let fmt = write_list(
|
|
self.lints.iter().copied(),
|
|
80usize.saturating_sub(dst.len() - line_start),
|
|
list_indent,
|
|
dst,
|
|
);
|
|
if matches!(fmt, ListFmt::MultiLine) {
|
|
dst.push_str(list_multi_end);
|
|
}
|
|
dst.push_str(end);
|
|
}
|
|
}
|
|
|
|
fn write_comment_lines(s: &str, prefix: &str, dst: &mut String) -> bool {
|
|
let mut has_doc = false;
|
|
for line in s.split('\n') {
|
|
let line = line.trim_start();
|
|
if !line.is_empty() {
|
|
has_doc = true;
|
|
dst.extend([prefix, line]);
|
|
}
|
|
}
|
|
has_doc
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum ListFmt {
|
|
SingleLine,
|
|
MultiLine,
|
|
}
|
|
|
|
fn write_list<'a>(
|
|
items: impl Iterator<Item = &'a str> + Clone,
|
|
single_line_limit: usize,
|
|
indent: &str,
|
|
dst: &mut String,
|
|
) -> ListFmt {
|
|
let len = items.clone().map(str::len).sum::<usize>();
|
|
if len > single_line_limit {
|
|
for item in items {
|
|
dst.extend(["\n", indent, item, ","]);
|
|
}
|
|
ListFmt::MultiLine
|
|
} else {
|
|
let _ = write!(dst, "{}", items.format(", "));
|
|
ListFmt::SingleLine
|
|
}
|
|
}
|
|
|
|
/// Generates the contents of a lint's source file with all the lint and lint pass
|
|
/// declarations sorted.
|
|
pub fn gen_sorted_lints_file(
|
|
src: &str,
|
|
dst: &mut String,
|
|
lints: &mut [(&str, Range<u32>)],
|
|
passes: &mut [LintPass<'_>],
|
|
ranges: &mut VecBuf<Range<u32>>,
|
|
) {
|
|
ranges.with(|ranges| {
|
|
ranges.extend(lints.iter().map(|&(_, x)| x));
|
|
ranges.extend(passes.iter().map(|x| x.decl_range));
|
|
ranges.sort_unstable_by_key(|x| x.start);
|
|
|
|
lints.sort_unstable_by_key(|&(x, _)| x);
|
|
passes.sort_by_key(|x| x.name);
|
|
|
|
let mut ranges = ranges.iter();
|
|
let pos = if let Some(range) = ranges.next() {
|
|
dst.push_str(&src[..range.start as usize]);
|
|
for &(_, range) in &*lints {
|
|
dst.push_str(&src[range.start as usize..range.end as usize]);
|
|
dst.push_str("\n\n");
|
|
}
|
|
for pass in passes {
|
|
pass.gen_mac(dst);
|
|
dst.push_str("\n\n");
|
|
}
|
|
range.end
|
|
} else {
|
|
dst.push_str(src);
|
|
return;
|
|
};
|
|
|
|
let pos = ranges.fold(pos, |start, range| {
|
|
let s = &src[start as usize..range.start as usize];
|
|
dst.push_str(if s.trim_start().is_empty() {
|
|
// Only whitespace between this and the previous item. No need to keep that.
|
|
""
|
|
} else if src[..pos as usize].ends_with("\n\n")
|
|
&& let Some(s) = s.strip_prefix("\n\n")
|
|
{
|
|
// Empty line before and after. Remove one of them.
|
|
s
|
|
} else {
|
|
// Remove only full lines unless something is in the way.
|
|
s.strip_prefix('\n').unwrap_or(s)
|
|
});
|
|
range.end
|
|
});
|
|
|
|
// Since we always generate an empty line at the end, make sure to always skip it.
|
|
let s = &src[pos as usize..];
|
|
dst.push_str(s.strip_prefix('\n').map_or(s, |s| s.strip_prefix('\n').unwrap_or(s)));
|
|
});
|
|
}
|