Lintcheck: Construct links for sources

This commit is contained in:
xFrednet 2024-07-10 00:13:33 +02:00
parent 601a61fe27
commit 0e3d19799c
No known key found for this signature in database
GPG key ID: F5C59D0E669E5302
7 changed files with 83 additions and 10 deletions

View file

@ -1,6 +1,6 @@
[crates]
# some of these are from cargotest
cargo = {name = "cargo", version = '0.64.0'}
cargo = {name = "cargo", version = '0.64.0', online_link = 'https://docs.rs/cargo/{version}/src/{file}.html#{line}'}
iron = {name = "iron", version = '0.6.1'}
ripgrep = {name = "ripgrep", version = '12.1.1'}
xsv = {name = "xsv", version = '0.13.0'}

View file

@ -11,6 +11,7 @@ use std::{env, mem};
fn run_clippy(addr: &str) -> Option<i32> {
let driver_info = DriverInfo {
package_name: env::var("CARGO_PKG_NAME").ok()?,
version: env::var("CARGO_PKG_VERSION").ok()?,
};
let mut stream = BufReader::new(TcpStream::connect(addr).unwrap());

View file

@ -10,6 +10,10 @@ use walkdir::{DirEntry, WalkDir};
use crate::{Crate, LINTCHECK_DOWNLOADS, LINTCHECK_SOURCES};
const DEFAULT_DOCS_LINK: &str = "https://docs.rs/{krate}/{version}/src/{krate}/{file}.html#{line}";
const DEFAULT_GITHUB_LINK: &str = "{url}/blob/{hash}/src/{file}#L{line}";
const DEFAULT_PATH_LINK: &str = "{path}/src/{file}:{line}";
/// List of sources to check, loaded from a .toml file
#[derive(Debug, Deserialize)]
pub struct SourceList {
@ -33,6 +37,39 @@ struct TomlCrate {
git_hash: Option<String>,
path: Option<String>,
options: Option<Vec<String>>,
/// Magic values:
/// * `{krate}` will be replaced by `self.name`
/// * `{version}` will be replaced by `self.version`
/// * `{url}` will be replaced with `self.git_url`
/// * `{hash}` will be replaced with `self.git_hash`
/// * `{path}` will be replaced with `self.path`
/// * `{file}` will be replaced by the path after `src/`
/// * `{line}` will be replaced by the line
///
/// If unset, this will be filled by [`read_crates`] since it depends on
/// the source.
online_link: Option<String>,
}
impl TomlCrate {
fn file_link(&self, default: &str) -> String {
let mut link = self.online_link.clone().unwrap_or_else(|| default.to_string());
link = link.replace("{krate}", &self.name);
if let Some(version) = &self.version {
link = link.replace("{version}", version);
}
if let Some(url) = &self.git_url {
link = link.replace("{url}", url);
}
if let Some(hash) = &self.git_hash {
link = link.replace("{hash}", hash);
}
if let Some(path) = &self.path {
link = link.replace("{path}", path);
}
link
}
}
/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder
@ -41,6 +78,7 @@ struct TomlCrate {
pub struct CrateWithSource {
pub name: String,
pub source: CrateSource,
pub file_link: String,
pub options: Option<Vec<String>>,
}
@ -70,6 +108,7 @@ pub fn read_crates(toml_path: &Path) -> (Vec<CrateWithSource>, RecursiveOptions)
source: CrateSource::Path {
path: PathBuf::from(path),
},
file_link: tk.file_link(DEFAULT_PATH_LINK),
options: tk.options.clone(),
});
} else if let Some(ref version) = tk.version {
@ -78,6 +117,7 @@ pub fn read_crates(toml_path: &Path) -> (Vec<CrateWithSource>, RecursiveOptions)
source: CrateSource::CratesIo {
version: version.to_string(),
},
file_link: tk.file_link(DEFAULT_DOCS_LINK),
options: tk.options.clone(),
});
} else if tk.git_url.is_some() && tk.git_hash.is_some() {
@ -88,6 +128,7 @@ pub fn read_crates(toml_path: &Path) -> (Vec<CrateWithSource>, RecursiveOptions)
url: tk.git_url.clone().unwrap(),
commit: tk.git_hash.clone().unwrap(),
},
file_link: tk.file_link(DEFAULT_GITHUB_LINK),
options: tk.options.clone(),
});
} else {
@ -141,6 +182,7 @@ impl CrateWithSource {
}
let name = &self.name;
let options = &self.options;
let file_link = &self.file_link;
match &self.source {
CrateSource::CratesIo { version } => {
let extract_dir = PathBuf::from(LINTCHECK_SOURCES);
@ -173,6 +215,7 @@ impl CrateWithSource {
name: name.clone(),
path: extract_dir.join(format!("{name}-{version}/")),
options: options.clone(),
base_url: file_link.clone(),
}
},
CrateSource::Git { url, commit } => {
@ -214,6 +257,7 @@ impl CrateWithSource {
name: name.clone(),
path: repo_path,
options: options.clone(),
base_url: file_link.clone(),
}
},
CrateSource::Path { path } => {
@ -253,6 +297,7 @@ impl CrateWithSource {
name: name.clone(),
path: dest_crate_root,
options: options.clone(),
base_url: file_link.clone(),
}
},
}

View file

@ -11,6 +11,7 @@ struct LintJson {
lint: String,
file_name: String,
byte_pos: (u32, u32),
file_link: String,
rendered: String,
}
@ -29,6 +30,7 @@ pub(crate) fn output(clippy_warnings: Vec<ClippyWarning>) -> String {
LintJson {
file_name: span.file_name.clone(),
byte_pos: (span.byte_start, span.byte_end),
file_link: warning.url,
lint: warning.lint,
rendered: warning.diag.rendered.unwrap(),
}
@ -50,11 +52,12 @@ fn print_warnings(title: &str, warnings: &[LintJson]) {
}
println!("### {title}");
println!("```");
for warning in warnings {
println!("{title} `{}` at {}", warning.lint, warning.file_link);
println!("```");
print!("{}", warning.rendered);
println!("```");
}
println!("```");
}
fn print_changed_diff(changed: &[(LintJson, LintJson)]) {
@ -63,8 +66,9 @@ fn print_changed_diff(changed: &[(LintJson, LintJson)]) {
}
println!("### Changed");
println!("```diff");
for (old, new) in changed {
println!("Changed `{}` at {}", new.lint, new.file_link);
println!("```diff");
for change in diff::lines(&old.rendered, &new.rendered) {
use diff::Result::{Both, Left, Right};
@ -80,8 +84,8 @@ fn print_changed_diff(changed: &[(LintJson, LintJson)]) {
},
}
}
println!("```");
}
println!("```");
}
pub(crate) fn diff(old_path: &Path, new_path: &Path) {

View file

@ -53,6 +53,7 @@ struct Crate {
// path to the extracted sources that clippy can check
path: PathBuf,
options: Option<Vec<String>>,
base_url: String,
}
impl Crate {
@ -185,7 +186,7 @@ impl Crate {
// get all clippy warnings and ICEs
let mut entries: Vec<ClippyCheckOutput> = Message::parse_stream(stdout.as_bytes())
.filter_map(|msg| match msg {
Ok(Message::CompilerMessage(message)) => ClippyWarning::new(message.message),
Ok(Message::CompilerMessage(message)) => ClippyWarning::new(message.message, &self.base_url),
_ => None,
})
.map(ClippyCheckOutput::ClippyWarning)

View file

@ -53,11 +53,12 @@ impl RustcIce {
pub struct ClippyWarning {
pub lint: String,
pub diag: Diagnostic,
/// The URL that points to the file and line of the lint emission
pub url: String,
}
#[allow(unused)]
impl ClippyWarning {
pub fn new(mut diag: Diagnostic) -> Option<Self> {
pub fn new(mut diag: Diagnostic, base_url: &str) -> Option<Self> {
let lint = diag.code.clone()?.code;
if !(lint.contains("clippy") || diag.message.contains("clippy"))
|| diag.message.contains("could not read cargo metadata")
@ -69,7 +70,20 @@ impl ClippyWarning {
let rendered = diag.rendered.as_mut().unwrap();
*rendered = strip_ansi_escapes::strip_str(&rendered);
Some(Self { lint, diag })
let span = diag.spans.iter().find(|span| span.is_primary).unwrap();
let file = &span.file_name;
let url = if let Some(src_split) = file.find("/src/") {
// This removes the inital `target/lintcheck/sources/<crate>-<version>/`
let src_split = src_split + "/src/".len();
let (_, file) = file.split_at(src_split);
let line_no = span.line_start;
base_url.replace("{file}", file).replace("{line}", &line_no.to_string())
} else {
file.clone()
};
Some(Self { lint, diag, url })
}
pub fn span(&self) -> &DiagnosticSpan {

View file

@ -20,6 +20,7 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize)]
pub(crate) struct DriverInfo {
pub package_name: String,
pub version: String,
}
pub(crate) fn serialize_line<T, W>(value: &T, writer: &mut W)
@ -61,10 +62,17 @@ fn process_stream(
let mut stderr = String::new();
stream.read_to_string(&mut stderr).unwrap();
// It's 99% likely that dependencies compiled with recursive mode are on crates.io
// and therefore on docs.rs. This links to the sources directly, do avoid invalid
// links due to remaped paths. See rust-lang/docs.rs#2551 for more details.
let base_url = format!(
"https://docs.rs/crate/{}/{}/source/src/{{file}}#{{line}}",
driver_info.package_name, driver_info.version
);
let messages = stderr
.lines()
.filter_map(|json_msg| serde_json::from_str::<Diagnostic>(json_msg).ok())
.filter_map(ClippyWarning::new);
.filter_map(|diag| ClippyWarning::new(diag, &base_url));
for message in messages {
sender.send(message).unwrap();