From 9cbd17778a6587804edf38c38221dbabc6ae5b44 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 12 Jan 2025 03:20:09 +0000 Subject: [PATCH 1/2] Detect unstable lint docs that dont enable their feature --- src/tools/lint-docs/src/lib.rs | 38 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/tools/lint-docs/src/lib.rs b/src/tools/lint-docs/src/lib.rs index 8c7ff08ccd72c..5aae50aebc3d6 100644 --- a/src/tools/lint-docs/src/lib.rs +++ b/src/tools/lint-docs/src/lib.rs @@ -480,26 +480,44 @@ impl<'a> LintExtractor<'a> { .map(|msg| msg["rendered"].as_str().expect("rendered field should exist").to_string()) .collect(); if matches.is_empty() { - // Some lints override their code to something else (E0566). - // Try to find something that looks like it could be our lint. - let matches: Vec<_> = msgs.iter().filter(|msg| - matches!(&msg["rendered"], serde_json::Value::String(s) if s.contains(name))) - .map(|msg| msg["rendered"].as_str().expect("rendered field should exist").to_string()) - .collect(); - if matches.is_empty() { + // Try to detect if an unstable lint forgot to enable a `#![feature(..)]`. + if name != "test_unstable_lint" && msgs.iter().any(|msg| { + matches!(&msg["code"]["code"], serde_json::Value::String(s) if s=="unknown_lints") + && matches!(&msg["message"], serde_json::Value::String(s) if s.contains(name)) + }) { let rendered: Vec<&str> = msgs.iter().filter_map(|msg| msg["rendered"].as_str()).collect(); let non_json: Vec<&str> = stderr.lines().filter(|line| !line.starts_with('{')).collect(); Err(format!( - "did not find lint `{}` in output of example, got:\n{}\n{}", + "lint `{}` is unstable and must be enabled in example. see:\n{}\n{}", name, + rendered.join("\n"), non_json.join("\n"), - rendered.join("\n") ) .into()) } else { - Ok(matches.join("\n")) + // Some lints override their code to something else (E0566). + // Try to find something that looks like it could be our lint. + let matches: Vec<_> = msgs.iter().filter(|msg| + matches!(&msg["rendered"], serde_json::Value::String(s) if s.contains(name))) + .map(|msg| msg["rendered"].as_str().expect("rendered field should exist").to_string()) + .collect(); + if matches.is_empty() { + let rendered: Vec<&str> = + msgs.iter().filter_map(|msg| msg["rendered"].as_str()).collect(); + let non_json: Vec<&str> = + stderr.lines().filter(|line| !line.starts_with('{')).collect(); + Err(format!( + "did not find lint `{}` in output of example, got:\n{}\n{}", + name, + non_json.join("\n"), + rendered.join("\n") + ) + .into()) + } else { + Ok(matches.join("\n")) + } } } else { Ok(matches.join("\n")) From 3d9dcf46362148e453822b5b54719918061aef40 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 12 Jan 2025 03:23:34 +0000 Subject: [PATCH 2/2] Un-spaghettify the control flow of generate_lint_output Co-authored-by: Eric Huss --- src/tools/lint-docs/src/lib.rs | 89 +++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/src/tools/lint-docs/src/lib.rs b/src/tools/lint-docs/src/lib.rs index 5aae50aebc3d6..f6e844657807f 100644 --- a/src/tools/lint-docs/src/lib.rs +++ b/src/tools/lint-docs/src/lib.rs @@ -473,55 +473,64 @@ impl<'a> LintExtractor<'a> { .filter(|line| line.starts_with('{')) .map(serde_json::from_str) .collect::, _>>()?; + // First try to find the messages with the `code` field set to our lint. let matches: Vec<_> = msgs .iter() .filter(|msg| matches!(&msg["code"]["code"], serde_json::Value::String(s) if s==name)) .map(|msg| msg["rendered"].as_str().expect("rendered field should exist").to_string()) .collect(); - if matches.is_empty() { - // Try to detect if an unstable lint forgot to enable a `#![feature(..)]`. - if name != "test_unstable_lint" && msgs.iter().any(|msg| { + if !matches.is_empty() { + return Ok(matches.join("\n")); + } + + // Try to detect if an unstable lint forgot to enable a `#![feature(..)]`. + // Specifically exclude `test_unstable_lint` which exercises this on purpose. + if name != "test_unstable_lint" + && msgs.iter().any(|msg| { matches!(&msg["code"]["code"], serde_json::Value::String(s) if s=="unknown_lints") && matches!(&msg["message"], serde_json::Value::String(s) if s.contains(name)) - }) { - let rendered: Vec<&str> = - msgs.iter().filter_map(|msg| msg["rendered"].as_str()).collect(); - let non_json: Vec<&str> = - stderr.lines().filter(|line| !line.starts_with('{')).collect(); - Err(format!( - "lint `{}` is unstable and must be enabled in example. see:\n{}\n{}", - name, - rendered.join("\n"), - non_json.join("\n"), - ) - .into()) - } else { - // Some lints override their code to something else (E0566). - // Try to find something that looks like it could be our lint. - let matches: Vec<_> = msgs.iter().filter(|msg| - matches!(&msg["rendered"], serde_json::Value::String(s) if s.contains(name))) - .map(|msg| msg["rendered"].as_str().expect("rendered field should exist").to_string()) - .collect(); - if matches.is_empty() { - let rendered: Vec<&str> = - msgs.iter().filter_map(|msg| msg["rendered"].as_str()).collect(); - let non_json: Vec<&str> = - stderr.lines().filter(|line| !line.starts_with('{')).collect(); - Err(format!( - "did not find lint `{}` in output of example, got:\n{}\n{}", - name, - non_json.join("\n"), - rendered.join("\n") - ) - .into()) - } else { - Ok(matches.join("\n")) - } - } - } else { - Ok(matches.join("\n")) + }) + { + let rendered: Vec<&str> = + msgs.iter().filter_map(|msg| msg["rendered"].as_str()).collect(); + let non_json: Vec<&str> = + stderr.lines().filter(|line| !line.starts_with('{')).collect(); + return Err(format!( + "did not find lint `{}` in output of example (got unknown_lints)\n\ + Is the lint possibly misspelled, or does it need a `#![feature(...)]`?\n\ + Output was:\n\ + {}\n{}", + name, + rendered.join("\n"), + non_json.join("\n"), + ) + .into()); } + + // Some lints override their code to something else (E0566). + // Try to find something that looks like it could be our lint. + let matches: Vec<_> = msgs + .iter() + .filter( + |msg| matches!(&msg["rendered"], serde_json::Value::String(s) if s.contains(name)), + ) + .map(|msg| msg["rendered"].as_str().expect("rendered field should exist").to_string()) + .collect(); + if !matches.is_empty() { + return Ok(matches.join("\n")); + } + + // Otherwise, give a descriptive error. + let rendered: Vec<&str> = msgs.iter().filter_map(|msg| msg["rendered"].as_str()).collect(); + let non_json: Vec<&str> = stderr.lines().filter(|line| !line.starts_with('{')).collect(); + Err(format!( + "did not find lint `{}` in output of example, got:\n{}\n{}", + name, + non_json.join("\n"), + rendered.join("\n") + ) + .into()) } /// Saves the mdbook lint chapters at the given path.