Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,17 @@ disallowed-names = ["bar", ".."] # -> ["bar", "foo", "baz", "quux"]

> **Note**
>
> `clippy.toml` or `.clippy.toml` cannot be used to allow/deny lints.
> To configure lint levels in `clippy.toml`, use the `[lints.clippy]` table,
> which supports the same format as `Cargo.toml`. For example:
>
> ```toml
> [lints.clippy]
> needless_return = "allow"
> single_match = { level = "warn", priority = 5 }
>
> [lints.rust]
> dead_code = "allow"
> ```

To deactivate the “for further information visit *lint-link*” message you can
define the `CLIPPY_DISABLE_DOCS_LINKS` environment variable.
Expand Down
19 changes: 19 additions & 0 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ enum_glob_use = "deny"

For more details and options, refer to the Cargo documentation.

#### Lints Section in `clippy.toml`

Lint levels can also be configured directly in `clippy.toml` using a `[lints]`
table with the same format as `Cargo.toml`. This allows you to keep lint
configuration separate from dependency management.

```toml
[lints.clippy]
needless_return = "allow"
single_match = { level = "warn", priority = 5 }

[lints.rust]
dead_code = "allow"
```

The supported lint level values are `"allow"`, `"warn"`, `"deny"`, and
`"forbid"`. Each entry can either be a plain string (the level) or a table
with `level` and an optional `priority` field (an integer, defaulting to 0).

### Specifying the minimum supported Rust version

Projects that intend to support old versions of Rust can disable lints pertaining to newer features by specifying the
Expand Down
88 changes: 85 additions & 3 deletions clippy_config/src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,60 @@ use rustc_span::edit_distance::edit_distance;
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::fmt::{Debug, Display, Formatter};
use std::ops::Range;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::OnceLock;
use std::{cmp, env, fmt, fs, io};
use toml::Spanned;

#[derive(Deserialize, Serialize, Debug)]
pub struct LintConfigTable {
level: String,
priority: Option<i64>,
}

#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub enum LintConfig {
Level(String),
Table(LintConfigTable),
}

impl LintConfig {
pub fn level(&self) -> &str {
match self {
LintConfig::Level(level) => level,
LintConfig::Table(table) => &table.level,
}
}

pub fn priority(&self) -> i64 {
match self {
LintConfig::Level(_) => 0,
LintConfig::Table(table) => table.priority.unwrap_or(0),
}
}
}

type LintTable = BTreeMap<Spanned<String>, Spanned<LintConfig>>;

#[derive(Deserialize, Debug, Default)]
pub struct Lints {
#[serde(default)]
pub clippy: LintTable,
}

// Lightweight representation of the `[lints]` table for eager reads that don't
// require span information. This allows the driver to merge CLI flags with
// clippy.toml settings before the rustc `Session` exists.
#[derive(Deserialize, Debug, Default)]
pub struct LintsPlain {
#[serde(default)]
pub clippy: BTreeMap<String, LintConfig>,
}

#[rustfmt::skip]
const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
Expand Down Expand Up @@ -86,6 +133,7 @@ struct TryConf {
value_spans: HashMap<String, Range<usize>>,
errors: Vec<ConfError>,
warnings: Vec<ConfError>,
lints: Option<Lints>,
}

impl TryConf {
Expand All @@ -95,6 +143,7 @@ impl TryConf {
value_spans: HashMap::default(),
errors: vec![ConfError::from_toml(file, error)],
warnings: vec![],
lints: None,
}
}
}
Expand Down Expand Up @@ -249,7 +298,7 @@ macro_rules! define_Conf {
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "kebab-case")]
#[expect(non_camel_case_types)]
enum Field { $($name,)* third_party, }
enum Field { $($name,)* lints, third_party, }

struct ConfVisitor<'a>(&'a SourceFile);

Expand All @@ -264,6 +313,7 @@ macro_rules! define_Conf {
let mut value_spans = HashMap::new();
let mut errors = Vec::new();
let mut warnings = Vec::new();
let mut lints = None;

// Declare a local variable for each field available to a configuration file.
$(let mut $name = None;)*
Expand Down Expand Up @@ -302,12 +352,16 @@ macro_rules! define_Conf {
None => $new_conf = $name.clone(),
})?
})*
Field::lints => {
let lints_value = map.next_value::<toml::Spanned<Lints>>()?;
lints = Some(lints_value.into_inner());
}
// ignore contents of the third_party key
Field::third_party => drop(map.next_value::<IgnoredAny>())
}
}
let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
Ok(TryConf { conf, value_spans, errors, warnings })
Ok(TryConf { conf, value_spans, errors, warnings, lints })
}
}

Expand Down Expand Up @@ -1036,6 +1090,8 @@ fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
}
}

static CLIPPY_TOML_LINTS: OnceLock<Option<Lints>> = OnceLock::new();

impl Conf {
pub fn read(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> &'static Conf {
static CONF: OnceLock<Conf> = OnceLock::new();
Expand All @@ -1060,6 +1116,7 @@ impl Conf {
value_spans: _,
errors,
warnings,
lints,
} = match path {
Ok((Some(path), _)) => match sess.source_map().load_file(path) {
Ok(file) => deserialize(&file),
Expand All @@ -1073,6 +1130,9 @@ impl Conf {

conf.msrv.read_cargo(sess);

// Store the lints configuration for use in the driver
CLIPPY_TOML_LINTS.set(lints).ok();

// all conf errors are non-fatal, we just use the default conf in case of error
for error in errors {
let mut diag = sess.dcx().struct_span_err(
Expand Down Expand Up @@ -1262,3 +1322,25 @@ mod tests {
);
}
}

pub fn clippy_toml_lints() -> &'static Option<Lints> {
CLIPPY_TOML_LINTS.get().unwrap_or(&None)
}

/// Reads only the `[lints]` table from the configuration file without requiring a `Session`.
/// Returns `None` if no config file was found or if the file does not contain a `[lints]` table.
pub fn read_lints_from_conf_path(path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Option<LintsPlain> {
let (Some(conf_path), _) = path.as_ref().ok()? else {
return None;
};
let Ok(src) = fs::read_to_string(conf_path) else {
return None;
};
let Ok(doc) = src.parse::<toml::Value>() else {
return None;
};
let Some(lints_val) = doc.get("lints") else {
return None;
};
lints_val.clone().try_into().ok()
}
5 changes: 4 additions & 1 deletion clippy_config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ mod conf;
mod metadata;
pub mod types;

pub use conf::{Conf, get_configuration_metadata, lookup_conf_file, sanitize_explanation};
pub use conf::{
Conf, Lints, LintsPlain, clippy_toml_lints, get_configuration_metadata, lookup_conf_file,
read_lints_from_conf_path, sanitize_explanation,
};
pub use metadata::ClippyConfiguration;
Loading
Loading