Merge pull request #18466 from ChayimFriedman2/proper-lint-severity

Properly handle different defaults for severity of lints
This commit is contained in:
Laurențiu Nicola 2024-12-11 20:05:39 +00:00 committed by GitHub
commit b707b5a117
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 6630 additions and 1438 deletions

6
Cargo.lock generated
View file

@ -389,6 +389,10 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1"
[[package]]
name = "edition"
version = "0.0.0"
[[package]]
name = "either"
version = "1.13.0"
@ -1266,6 +1270,7 @@ name = "parser"
version = "0.0.0"
dependencies = [
"drop_bomb",
"edition",
"expect-test",
"limit",
"ra-ap-rustc_lexer",
@ -2662,6 +2667,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"directories",
"edition",
"either",
"flate2",
"itertools",

View file

@ -83,6 +83,7 @@ toolchain = { path = "./crates/toolchain", version = "0.0.0" }
tt = { path = "./crates/tt", version = "0.0.0" }
vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
vfs = { path = "./crates/vfs", version = "0.0.0" }
edition = { path = "./crates/edition", version = "0.0.0" }
ra-ap-rustc_lexer = { version = "0.85", default-features = false }
ra-ap-rustc_parse_format = { version = "0.85", default-features = false }

13
crates/edition/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "edition"
version = "0.0.0"
rust-version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
[dependencies]
[lints]
workspace = true

View file

@ -1,6 +1,5 @@
//! The edition of the Rust language used in a crate.
// Ideally this would be defined in the span crate, but the dependency chain is all over the place
// wrt to span, parser and syntax.
// This should live in a separate crate because we use it in both actual code and codegen.
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]

View file

@ -11,7 +11,7 @@ pub(super) fn complete_lint(
existing_lints: &[ast::Path],
lints_completions: &[Lint],
) {
for &Lint { label, description } in lints_completions {
for &Lint { label, description, .. } in lints_completions {
let (qual, name) = {
// FIXME: change `Lint`'s label to not store a path in it but split the prefix off instead?
let mut parts = label.split("::");

File diff suppressed because it is too large Load diff

View file

@ -327,3 +327,11 @@ impl<'a> Ranker<'a> {
| ((no_tt_parent as usize) << 3)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Severity {
Error,
Warning,
WeakWarning,
Allow,
}

View file

@ -586,14 +586,47 @@ fn main() {
}
#[test]
fn unsafe_op_in_unsafe_fn_allowed_by_default() {
fn unsafe_op_in_unsafe_fn_allowed_by_default_in_edition_2021() {
check_diagnostics(
r#"
//- /lib.rs crate:foo edition:2021
unsafe fn foo(p: *mut i32) {
*p = 123;
}
"#,
)
);
check_diagnostics(
r#"
//- /lib.rs crate:foo edition:2021
#![deny(warnings)]
unsafe fn foo(p: *mut i32) {
*p = 123;
}
"#,
);
}
#[test]
fn unsafe_op_in_unsafe_fn_warn_by_default_in_edition_2024() {
check_diagnostics(
r#"
//- /lib.rs crate:foo edition:2024
unsafe fn foo(p: *mut i32) {
*p = 123;
//^^💡 warn: dereference of raw pointer is unsafe and requires an unsafe function or block
}
"#,
);
check_diagnostics(
r#"
//- /lib.rs crate:foo edition:2024
#![deny(warnings)]
unsafe fn foo(p: *mut i32) {
*p = 123;
//^^💡 error: dereference of raw pointer is unsafe and requires an unsafe function or block
}
"#,
);
}
#[test]

View file

@ -84,12 +84,12 @@ use hir::{db::ExpandDatabase, diagnostics::AnyDiagnostic, Crate, HirFileId, InFi
use ide_db::{
assists::{Assist, AssistId, AssistKind, AssistResolveStrategy},
base_db::SourceDatabase,
generated::lints::{LintGroup, CLIPPY_LINT_GROUPS, DEFAULT_LINT_GROUPS},
generated::lints::{Lint, LintGroup, CLIPPY_LINT_GROUPS, DEFAULT_LINTS, DEFAULT_LINT_GROUPS},
imports::insert_use::InsertUseConfig,
label::Label,
source_change::SourceChange,
syntax_helpers::node_ext::parse_tt_as_comma_sep_paths,
EditionedFileId, FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, SnippetCap,
EditionedFileId, FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, Severity, SnippetCap,
};
use itertools::Itertools;
use syntax::{
@ -210,14 +210,6 @@ impl Diagnostic {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Severity {
Error,
Warning,
WeakWarning,
Allow,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExprFillDefaultMode {
Todo,
@ -568,26 +560,35 @@ fn handle_diag_from_macros(
// `__RA_EVERY_LINT` is a fake lint group to allow every lint in proc macros
static RUSTC_LINT_GROUPS_DICT: LazyLock<FxHashMap<&str, Vec<&str>>> =
LazyLock::new(|| build_group_dict(DEFAULT_LINT_GROUPS, &["warnings", "__RA_EVERY_LINT"], ""));
struct BuiltLint {
lint: &'static Lint,
groups: Vec<&'static str>,
}
static CLIPPY_LINT_GROUPS_DICT: LazyLock<FxHashMap<&str, Vec<&str>>> =
LazyLock::new(|| build_group_dict(CLIPPY_LINT_GROUPS, &["__RA_EVERY_LINT"], "clippy::"));
static RUSTC_LINTS: LazyLock<FxHashMap<&str, BuiltLint>> =
LazyLock::new(|| build_lints_map(DEFAULT_LINTS, DEFAULT_LINT_GROUPS, ""));
static CLIPPY_LINTS: LazyLock<FxHashMap<&str, BuiltLint>> = LazyLock::new(|| {
build_lints_map(ide_db::generated::lints::CLIPPY_LINTS, CLIPPY_LINT_GROUPS, "clippy::")
});
// FIXME: Autogenerate this instead of enumerating by hand.
static LINTS_TO_REPORT_IN_EXTERNAL_MACROS: LazyLock<FxHashSet<&str>> =
LazyLock::new(|| FxHashSet::from_iter([]));
fn build_group_dict(
fn build_lints_map(
lints: &'static [Lint],
lint_group: &'static [LintGroup],
all_groups: &'static [&'static str],
prefix: &'static str,
) -> FxHashMap<&'static str, Vec<&'static str>> {
let mut map_with_prefixes: FxHashMap<&str, Vec<&str>> = FxHashMap::default();
) -> FxHashMap<&'static str, BuiltLint> {
let mut map_with_prefixes: FxHashMap<_, _> = lints
.iter()
.map(|lint| (lint.label, BuiltLint { lint, groups: vec![lint.label, "__RA_EVERY_LINT"] }))
.collect();
for g in lint_group {
let mut add_children = |label: &'static str| {
for child in g.children {
map_with_prefixes.entry(child).or_default().push(label);
map_with_prefixes.get_mut(child).unwrap().groups.push(label);
}
};
add_children(g.lint.label);
@ -597,18 +598,9 @@ fn build_group_dict(
add_children("bad_style");
}
}
for (lint, groups) in map_with_prefixes.iter_mut() {
groups.push(lint);
groups.extend_from_slice(all_groups);
}
map_with_prefixes.into_iter().map(|(k, v)| (k.strip_prefix(prefix).unwrap(), v)).collect()
}
/// Thd default severity for lints that are not warn by default.
// FIXME: Autogenerate this instead of write manually.
static LINTS_DEFAULT_SEVERITY: LazyLock<FxHashMap<&str, Severity>> =
LazyLock::new(|| FxHashMap::from_iter([("unsafe_op_in_unsafe_fn", Severity::Allow)]));
fn handle_lints(
sema: &Semantics<'_, RootDatabase>,
cache: &mut FxHashMap<HirFileId, FxHashMap<SmolStr, SeverityAttr>>,
@ -618,10 +610,12 @@ fn handle_lints(
) {
for (node, diag) in diagnostics {
let lint = match diag.code {
DiagnosticCode::RustcLint(lint) | DiagnosticCode::Clippy(lint) => lint,
DiagnosticCode::RustcLint(lint) => RUSTC_LINTS[lint].lint,
DiagnosticCode::Clippy(lint) => CLIPPY_LINTS[lint].lint,
_ => panic!("non-lint passed to `handle_lints()`"),
};
if let Some(&default_severity) = LINTS_DEFAULT_SEVERITY.get(lint) {
let default_severity = default_lint_severity(lint, edition);
if !(default_severity == Severity::Allow && diag.severity == Severity::WeakWarning) {
diag.severity = default_severity;
}
@ -639,6 +633,16 @@ fn handle_lints(
}
}
fn default_lint_severity(lint: &Lint, edition: Edition) -> Severity {
if lint.deny_since.is_some_and(|e| edition >= e) {
Severity::Error
} else if lint.warn_since.is_some_and(|e| edition >= e) {
Severity::Warning
} else {
lint.default_severity
}
}
fn find_outline_mod_lint_severity(
sema: &Semantics<'_, RootDatabase>,
node: &InFile<SyntaxNode>,
@ -654,14 +658,14 @@ fn find_outline_mod_lint_severity(
let mod_def = sema.to_module_def(&mod_node)?;
let module_source_file = sema.module_definition_node(mod_def);
let mut result = None;
let lint_groups = lint_groups(&diag.code);
let lint_groups = lint_groups(&diag.code, edition);
lint_attrs(
sema,
ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"),
edition,
)
.for_each(|(lint, severity)| {
if lint_groups.contains(&&*lint) {
if lint_groups.contains(&lint) {
result = Some(severity);
}
});
@ -737,9 +741,9 @@ fn fill_lint_attrs(
}
});
let all_matching_groups = lint_groups(&diag.code)
let all_matching_groups = lint_groups(&diag.code, edition)
.iter()
.filter_map(|lint_group| cached.get(&**lint_group));
.filter_map(|lint_group| cached.get(lint_group));
let cached_severity =
all_matching_groups.min_by_key(|it| it.depth).map(|it| it.severity);
@ -751,7 +755,7 @@ fn fill_lint_attrs(
// Insert this node's descendants' attributes into any outline descendant, but not including this node.
// This must come before inserting this node's own attributes to preserve order.
collected_lint_attrs.drain().for_each(|(lint, severity)| {
if diag_severity.is_none() && lint_groups(&diag.code).contains(&&*lint) {
if diag_severity.is_none() && lint_groups(&diag.code, edition).contains(&lint) {
diag_severity = Some(severity.severity);
}
@ -774,7 +778,7 @@ fn fill_lint_attrs(
if let Some(ancestor) = ast::AnyHasAttrs::cast(ancestor) {
// Insert this node's attributes into any outline descendant, including this node.
lint_attrs(sema, ancestor, edition).for_each(|(lint, severity)| {
if diag_severity.is_none() && lint_groups(&diag.code).contains(&&*lint) {
if diag_severity.is_none() && lint_groups(&diag.code, edition).contains(&lint) {
diag_severity = Some(severity);
}
@ -804,7 +808,7 @@ fn fill_lint_attrs(
return diag_severity;
} else if let Some(ancestor) = ast::AnyHasAttrs::cast(ancestor) {
lint_attrs(sema, ancestor, edition).for_each(|(lint, severity)| {
if diag_severity.is_none() && lint_groups(&diag.code).contains(&&*lint) {
if diag_severity.is_none() && lint_groups(&diag.code, edition).contains(&lint) {
diag_severity = Some(severity);
}
@ -905,16 +909,37 @@ fn cfg_attr_lint_attrs(
}
}
fn lint_groups(lint: &DiagnosticCode) -> &'static [&'static str] {
match lint {
#[derive(Debug)]
struct LintGroups {
groups: &'static [&'static str],
inside_warnings: bool,
}
impl LintGroups {
fn contains(&self, group: &str) -> bool {
self.groups.contains(&group) || (self.inside_warnings && group == "warnings")
}
fn iter(&self) -> impl Iterator<Item = &'static str> {
self.groups.iter().copied().chain(self.inside_warnings.then_some("warnings"))
}
}
fn lint_groups(lint: &DiagnosticCode, edition: Edition) -> LintGroups {
let (groups, inside_warnings) = match lint {
DiagnosticCode::RustcLint(name) => {
RUSTC_LINT_GROUPS_DICT.get(name).map(|it| &**it).unwrap_or_default()
let lint = &RUSTC_LINTS[name];
let inside_warnings = default_lint_severity(lint.lint, edition) == Severity::Warning;
(&lint.groups, inside_warnings)
}
DiagnosticCode::Clippy(name) => {
CLIPPY_LINT_GROUPS_DICT.get(name).map(|it| &**it).unwrap_or_default()
}
_ => &[],
let lint = &CLIPPY_LINTS[name];
let inside_warnings = default_lint_severity(lint.lint, edition) == Severity::Warning;
(&lint.groups, inside_warnings)
}
_ => panic!("non-lint passed to `handle_lints()`"),
};
LintGroups { groups, inside_warnings }
}
fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {

View file

@ -6413,7 +6413,7 @@ fn hover_feature() {
by the codegen backend, but not the MIR inliner.
```rust
#![feature(rustc_attrs)]
#![feature(intrinsics)]
#![allow(internal_features)]
#[rustc_intrinsic]
@ -6423,7 +6423,7 @@ fn hover_feature() {
Since these are just regular functions, it is perfectly ok to create the intrinsic twice:
```rust
#![feature(rustc_attrs)]
#![feature(intrinsics)]
#![allow(internal_features)]
#[rustc_intrinsic]

View file

@ -132,11 +132,9 @@ pub use ide_db::{
search::{ReferenceCategory, SearchScope},
source_change::{FileSystemEdit, SnippetEdit, SourceChange},
symbol_index::Query,
FileId, FilePosition, FileRange, RootDatabase, SymbolKind,
};
pub use ide_diagnostics::{
Diagnostic, DiagnosticCode, DiagnosticsConfig, ExprFillDefaultMode, Severity,
FileId, FilePosition, FileRange, RootDatabase, Severity, SymbolKind,
};
pub use ide_diagnostics::{Diagnostic, DiagnosticCode, DiagnosticsConfig, ExprFillDefaultMode};
pub use ide_ssr::SsrError;
pub use span::Edition;
pub use syntax::{TextRange, TextSize};

View file

@ -18,6 +18,8 @@ ra-ap-rustc_lexer.workspace = true
limit.workspace = true
tracing = { workspace = true, optional = true }
edition.workspace = true
[dev-dependencies]
expect-test = "1.4.0"

View file

@ -25,7 +25,6 @@ extern crate ra_ap_rustc_lexer as rustc_lexer;
#[cfg(feature = "in-rust-tree")]
extern crate rustc_lexer;
mod edition;
mod event;
mod grammar;
mod input;
@ -41,8 +40,9 @@ mod tests;
pub(crate) use token_set::TokenSet;
pub use edition::Edition;
pub use crate::{
edition::Edition,
input::Input,
lexed_str::LexedStr,
output::{Output, Step},

View file

@ -21,6 +21,7 @@ quote = "1.0.20"
ungrammar = "1.16.1"
either.workspace = true
itertools.workspace = true
edition.workspace = true
# Avoid adding more dependencies to this crate
[lints]

View file

@ -1,7 +1,15 @@
//! Generates descriptor structures for unstable features from the unstable book
//! and lints from rustc, rustdoc, and clippy.
use std::{borrow::Cow, fs, path::Path};
#![allow(clippy::disallowed_types)]
use std::{
collections::{hash_map, HashMap},
fs,
path::Path,
str::FromStr,
};
use edition::Edition;
use stdx::format_to;
use xshell::{cmd, Shell};
@ -36,10 +44,17 @@ pub(crate) fn generate(check: bool) {
let mut contents = String::from(
r"
use span::Edition;
use crate::Severity;
#[derive(Clone)]
pub struct Lint {
pub label: &'static str,
pub description: &'static str,
pub default_severity: Severity,
pub warn_since: Option<Edition>,
pub deny_since: Option<Edition>,
}
pub struct LintGroup {
@ -68,7 +83,7 @@ pub struct LintGroup {
let lints_json = project_root().join("./target/clippy_lints.json");
cmd!(
sh,
"curl https://rust-lang.github.io/rust-clippy/master/lints.json --output {lints_json}"
"curl https://rust-lang.github.io/rust-clippy/stable/lints.json --output {lints_json}"
)
.run()
.unwrap();
@ -85,6 +100,48 @@ pub struct LintGroup {
);
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Severity {
Allow,
Warn,
Deny,
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Severity::{}",
match self {
Severity::Allow => "Allow",
Severity::Warn => "Warning",
Severity::Deny => "Error",
}
)
}
}
impl FromStr for Severity {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"allow" => Ok(Self::Allow),
"warn" => Ok(Self::Warn),
"deny" => Ok(Self::Deny),
_ => Err("invalid severity"),
}
}
}
#[derive(Debug)]
struct Lint {
description: String,
default_severity: Severity,
warn_since: Option<Edition>,
deny_since: Option<Edition>,
}
/// Parses the output of `rustdoc -Whelp` and prints `Lint` and `LintGroup` constants into `buf`.
///
/// As of writing, the output of `rustc -Whelp` (not rustdoc) has the following format:
@ -108,52 +165,203 @@ pub struct LintGroup {
/// `rustdoc -Whelp` (and any other custom `rustc` driver) adds another two
/// tables after the `rustc` ones, with a different title but the same format.
fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
let stdout = cmd!(sh, "rustdoc -Whelp").read().unwrap();
fn get_lints_as_text(
stdout: &str,
) -> (
impl Iterator<Item = (String, &str, Severity)> + '_,
impl Iterator<Item = (String, Lint, impl Iterator<Item = String> + '_)> + '_,
impl Iterator<Item = (String, &str, Severity)> + '_,
impl Iterator<Item = (String, Lint, impl Iterator<Item = String> + '_)> + '_,
) {
let lints_pat = "---- ------- -------\n";
let lint_groups_pat = "---- ---------\n";
let lints = find_and_slice(&stdout, lints_pat);
let lints = find_and_slice(stdout, lints_pat);
let lint_groups = find_and_slice(lints, lint_groups_pat);
let lints_rustdoc = find_and_slice(lint_groups, lints_pat);
let lint_groups_rustdoc = find_and_slice(lints_rustdoc, lint_groups_pat);
buf.push_str(r#"pub const DEFAULT_LINTS: &[Lint] = &["#);
buf.push('\n');
let lints = lints.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
let (_default_level, description) = rest.trim().split_once(char::is_whitespace).unwrap();
(name.trim(), Cow::Borrowed(description.trim()), vec![])
let (severity, description) = rest.trim().split_once(char::is_whitespace).unwrap();
(name.trim().replace('-', "_"), description.trim(), severity.parse().unwrap())
});
let lint_groups = lint_groups.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
(
name.trim(),
format!("lint group for: {}", lints.trim()).into(),
lints
let label = name.trim().replace('-', "_");
let lint = Lint {
description: format!("lint group for: {}", lints.trim()),
default_severity: Severity::Allow,
warn_since: None,
deny_since: None,
};
let children = lints
.split_ascii_whitespace()
.map(|s| s.trim().trim_matches(',').replace('-', "_"))
.collect(),
)
.map(|s| s.trim().trim_matches(',').replace('-', "_"));
(label, lint, children)
});
let mut lints = lints.chain(lint_groups).collect::<Vec<_>>();
lints.sort_by(|(ident, ..), (ident2, ..)| ident.cmp(ident2));
let lints_rustdoc = lints_rustdoc.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
let (severity, description) = rest.trim().split_once(char::is_whitespace).unwrap();
(name.trim().replace('-', "_"), description.trim(), severity.parse().unwrap())
});
let lint_groups_rustdoc =
lint_groups_rustdoc.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
let label = name.trim().replace('-', "_");
let lint = Lint {
description: format!("lint group for: {}", lints.trim()),
default_severity: Severity::Allow,
warn_since: None,
deny_since: None,
};
let children = lints
.split_ascii_whitespace()
.map(|s| s.trim().trim_matches(',').replace('-', "_"));
(label, lint, children)
});
for (name, description, ..) in &lints {
push_lint_completion(buf, &name.replace('-', "_"), description);
(lints, lint_groups, lints_rustdoc, lint_groups_rustdoc)
}
fn insert_lints<'a>(
edition: Edition,
lints_map: &mut HashMap<String, Lint>,
lint_groups_map: &mut HashMap<String, (Lint, Vec<String>)>,
lints: impl Iterator<Item = (String, &'a str, Severity)>,
lint_groups: impl Iterator<Item = (String, Lint, impl Iterator<Item = String>)>,
) {
for (lint_name, lint_description, lint_severity) in lints {
let lint = lints_map.entry(lint_name).or_insert_with(|| Lint {
description: lint_description.to_owned(),
default_severity: Severity::Allow,
warn_since: None,
deny_since: None,
});
if lint_severity == Severity::Warn
&& lint.warn_since.is_none()
&& lint.default_severity < Severity::Warn
{
lint.warn_since = Some(edition);
}
if lint_severity == Severity::Deny
&& lint.deny_since.is_none()
&& lint.default_severity < Severity::Deny
{
lint.deny_since = Some(edition);
}
}
for (group_name, lint, children) in lint_groups {
match lint_groups_map.entry(group_name) {
hash_map::Entry::Vacant(entry) => {
entry.insert((lint, Vec::from_iter(children)));
}
hash_map::Entry::Occupied(mut entry) => {
// Overwrite, because some groups (such as edition incompatibility) are changed.
*entry.get_mut() = (lint, Vec::from_iter(children));
}
}
}
}
fn get_lints(
sh: &Shell,
edition: Edition,
lints_map: &mut HashMap<String, Lint>,
lint_groups_map: &mut HashMap<String, (Lint, Vec<String>)>,
lints_rustdoc_map: &mut HashMap<String, Lint>,
lint_groups_rustdoc_map: &mut HashMap<String, (Lint, Vec<String>)>,
) {
let edition_str = edition.to_string();
let stdout = cmd!(sh, "rustdoc +nightly -Whelp -Zunstable-options --edition={edition_str}")
.read()
.unwrap();
let (lints, lint_groups, lints_rustdoc, lint_groups_rustdoc) = get_lints_as_text(&stdout);
insert_lints(edition, lints_map, lint_groups_map, lints, lint_groups);
insert_lints(
edition,
lints_rustdoc_map,
lint_groups_rustdoc_map,
lints_rustdoc,
lint_groups_rustdoc,
);
}
let basic_lints = cmd!(sh, "rustdoc +nightly -Whelp --edition=2015").read().unwrap();
let (lints, lint_groups, lints_rustdoc, lint_groups_rustdoc) = get_lints_as_text(&basic_lints);
let mut lints = lints
.map(|(label, description, severity)| {
(
label,
Lint {
description: description.to_owned(),
default_severity: severity,
warn_since: None,
deny_since: None,
},
)
})
.collect::<HashMap<_, _>>();
let mut lint_groups = lint_groups
.map(|(label, lint, children)| (label, (lint, Vec::from_iter(children))))
.collect::<HashMap<_, _>>();
let mut lints_rustdoc = lints_rustdoc
.map(|(label, description, severity)| {
(
label,
Lint {
description: description.to_owned(),
default_severity: severity,
warn_since: None,
deny_since: None,
},
)
})
.collect::<HashMap<_, _>>();
let mut lint_groups_rustdoc = lint_groups_rustdoc
.map(|(label, lint, children)| (label, (lint, Vec::from_iter(children))))
.collect::<HashMap<_, _>>();
for edition in Edition::iter().skip(1) {
get_lints(
sh,
edition,
&mut lints,
&mut lint_groups,
&mut lints_rustdoc,
&mut lint_groups_rustdoc,
);
}
let mut lints = Vec::from_iter(lints);
lints.sort_unstable_by(|a, b| a.0.cmp(&b.0));
let mut lint_groups = Vec::from_iter(lint_groups);
lint_groups.sort_unstable_by(|a, b| a.0.cmp(&b.0));
let mut lints_rustdoc = Vec::from_iter(lints_rustdoc);
lints_rustdoc.sort_unstable_by(|a, b| a.0.cmp(&b.0));
let mut lint_groups_rustdoc = Vec::from_iter(lint_groups_rustdoc);
lint_groups_rustdoc.sort_unstable_by(|a, b| a.0.cmp(&b.0));
buf.push_str(r#"pub const DEFAULT_LINTS: &[Lint] = &["#);
buf.push('\n');
for (name, lint) in &lints {
push_lint_completion(buf, name, lint);
}
for (name, (group, _)) in &lint_groups {
push_lint_completion(buf, name, group);
}
buf.push_str("];\n\n");
buf.push_str(r#"pub const DEFAULT_LINT_GROUPS: &[LintGroup] = &["#);
for (name, description, children) in &lints {
if !children.is_empty() {
// HACK: warnings is emitted with a general description, not with its members
if name == &"warnings" {
push_lint_group(buf, name, description, &Vec::new());
for (name, (lint, children)) in &lint_groups {
if name == "warnings" {
continue;
}
push_lint_group(buf, &name.replace('-', "_"), description, children);
}
push_lint_group(buf, name, lint, children);
}
buf.push('\n');
buf.push_str("];\n");
@ -164,37 +372,17 @@ fn generate_lint_descriptor(sh: &Shell, buf: &mut String) {
buf.push_str(r#"pub const RUSTDOC_LINTS: &[Lint] = &["#);
buf.push('\n');
let lints_rustdoc = lints_rustdoc.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, rest) = line.trim().split_once(char::is_whitespace).unwrap();
let (_default_level, description) = rest.trim().split_once(char::is_whitespace).unwrap();
(name.trim(), Cow::Borrowed(description.trim()), vec![])
});
let lint_groups_rustdoc =
lint_groups_rustdoc.lines().take_while(|l| !l.is_empty()).map(|line| {
let (name, lints) = line.trim().split_once(char::is_whitespace).unwrap();
(
name.trim(),
format!("lint group for: {}", lints.trim()).into(),
lints
.split_ascii_whitespace()
.map(|s| s.trim().trim_matches(',').replace('-', "_"))
.collect(),
)
});
let mut lints_rustdoc = lints_rustdoc.chain(lint_groups_rustdoc).collect::<Vec<_>>();
lints_rustdoc.sort_by(|(ident, ..), (ident2, ..)| ident.cmp(ident2));
for (name, description, ..) in &lints_rustdoc {
push_lint_completion(buf, &name.replace('-', "_"), description)
for (name, lint) in &lints_rustdoc {
push_lint_completion(buf, name, lint);
}
for (name, (group, _)) in &lint_groups_rustdoc {
push_lint_completion(buf, name, group);
}
buf.push_str("];\n\n");
buf.push_str(r#"pub const RUSTDOC_LINT_GROUPS: &[LintGroup] = &["#);
for (name, description, children) in &lints_rustdoc {
if !children.is_empty() {
push_lint_group(buf, &name.replace('-', "_"), description, children);
}
for (name, (lint, children)) in &lint_groups_rustdoc {
push_lint_group(buf, name, lint, children);
}
buf.push('\n');
buf.push_str("];\n");
@ -228,13 +416,19 @@ fn generate_feature_descriptor(buf: &mut String, src_dir: &Path) {
buf.push_str(r#"pub const FEATURES: &[Lint] = &["#);
for (feature_ident, doc) in features.into_iter() {
push_lint_completion(buf, &feature_ident, &doc)
let lint = Lint {
description: doc,
default_severity: Severity::Allow,
warn_since: None,
deny_since: None,
};
push_lint_completion(buf, &feature_ident, &lint);
}
buf.push('\n');
buf.push_str("];\n");
}
#[derive(Default)]
#[derive(Debug, Default)]
struct ClippyLint {
help: String,
id: String,
@ -295,8 +489,14 @@ fn generate_descriptor_clippy(buf: &mut String, path: &Path) {
buf.push('\n');
for clippy_lint in clippy_lints.into_iter() {
let lint_ident = format!("clippy::{}", clippy_lint.id);
let doc = clippy_lint.help;
push_lint_completion(buf, &lint_ident, &doc);
let lint = Lint {
description: clippy_lint.help,
// Allow clippy lints by default, not all users want them.
default_severity: Severity::Allow,
warn_since: None,
deny_since: None,
};
push_lint_completion(buf, &lint_ident, &lint);
}
buf.push_str("];\n");
@ -306,33 +506,59 @@ fn generate_descriptor_clippy(buf: &mut String, path: &Path) {
if !children.is_empty() {
let lint_ident = format!("clippy::{id}");
let description = format!("lint group for: {}", children.join(", "));
push_lint_group(buf, &lint_ident, &description, &children);
let lint = Lint {
description,
default_severity: Severity::Allow,
warn_since: None,
deny_since: None,
};
push_lint_group(buf, &lint_ident, &lint, &children);
}
}
buf.push('\n');
buf.push_str("];\n");
}
fn push_lint_completion(buf: &mut String, label: &str, description: &str) {
fn push_lint_completion(buf: &mut String, name: &str, lint: &Lint) {
format_to!(
buf,
r###" Lint {{
label: "{}",
description: r##"{}"##,
}},"###,
label,
description,
default_severity: {},
warn_since: "###,
name,
lint.description,
lint.default_severity,
);
match lint.warn_since {
Some(edition) => format_to!(buf, "Some(Edition::Edition{edition})"),
None => buf.push_str("None"),
}
format_to!(
buf,
r###",
deny_since: "###
);
match lint.deny_since {
Some(edition) => format_to!(buf, "Some(Edition::Edition{edition})"),
None => buf.push_str("None"),
}
format_to!(
buf,
r###",
}},"###
);
}
fn push_lint_group(buf: &mut String, label: &str, description: &str, children: &[String]) {
fn push_lint_group(buf: &mut String, name: &str, lint: &Lint, children: &[String]) {
buf.push_str(
r###" LintGroup {
lint:
"###,
);
push_lint_completion(buf, label, description);
push_lint_completion(buf, name, lint);
let children = format!(
"&[{}]",