Skip to content

Verify that versions in the changelog match the lint definitions #14821

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1428,8 +1428,6 @@ Released 2023-03-09

* [`permissions_set_readonly_false`]
[#10063](https://github.com/rust-lang/rust-clippy/pull/10063)
* [`almost_complete_range`]
[#10043](https://github.com/rust-lang/rust-clippy/pull/10043)
* [`size_of_ref`]
[#10098](https://github.com/rust-lang/rust-clippy/pull/10098)
* [`semicolon_outside_block`]
Expand All @@ -1449,6 +1447,8 @@ Released 2023-03-09
[#10115](https://github.com/rust-lang/rust-clippy/pull/10115)
* Renamed `derive_hash_xor_eq` to [`derived_hash_with_manual_eq`]
[#10184](https://github.com/rust-lang/rust-clippy/pull/10184)
* Renamed `almost_complete_letter_range` to [`almost_complete_range`] and extended it to check digits
[#10043](https://github.com/rust-lang/rust-clippy/pull/10043)

### Enhancements

Expand Down
11 changes: 5 additions & 6 deletions book/src/development/infrastructure/changelog_update.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ somewhat coherent.

The sections order should roughly be:

<!-- If this format changes please also update tests/lint-definitions.rs -->
```
### New Lints
* Added [`LINT`] to `GROUP`
Expand Down Expand Up @@ -99,16 +100,14 @@ that label in the changelog. If you can, remove the `beta-accepted` labels
### 5. Update `clippy::version` attributes

Next, make sure to check that the `#[clippy::version]` attributes for the added
lints contain the correct version.
In order to find lints that need a version update, go through the lints in the
"New Lints" section and run the following command for each lint name:
lints contain the correct version:

```
grep -rB1 "pub $LINT_NAME" .
cargo test --test lint-definitions
```

The version shown should match the version of the release the changelog is
written for. If not, update the version to the changelog version.
If a lint definition's version doesn't match the changelog you will see an error
pointing to the definition that needs changing.

[changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md
[forge]: https://forge.rust-lang.org/
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/almost_complete_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ declare_clippy_lint! {
/// ```no_run
/// let _ = 'a'..='z';
/// ```
#[clippy::version = "1.68.0"]
#[clippy::version = "1.63.0"]
pub ALMOST_COMPLETE_RANGE,
suspicious,
"almost complete range"
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/unicode.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![expect(clippy::invisible_characters, clippy::non_ascii_literal)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this now necessary 🤔

Copy link
Member Author

@Alexendoo Alexendoo Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file!() string literal has a strange span (e.g. clippy_lints/src/unicode.rs:56:1: 73:2 (#0)), it points to the whole declare_clippy_lint! { ... } callsite. The lints pick up that snippet and see the characters in the doc comment

Before it was part of a concat rather than its own expression


use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_lint_allowed;
use clippy_utils::macros::span_is_local;
Expand Down
19 changes: 16 additions & 3 deletions declare_clippy_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,10 @@ pub struct LintInfo {
pub lint: &'static Lint,
pub category: LintCategory,
pub explanation: &'static str,
/// e.g. `clippy_lints/src/absolute_paths.rs#43`
pub location: &'static str,
/// e.g. `clippy_lints/src/absolute_paths.rs`
pub file: &'static str,
/// The line number in `file`
pub line: u32,
pub version: &'static str,
}

Expand All @@ -116,6 +118,16 @@ impl LintInfo {
pub fn name_lower(&self) -> String {
self.lint.name.strip_prefix("clippy::").unwrap().to_ascii_lowercase()
}

#[must_use]
pub fn location_terminal(&self) -> String {
format!("{}:{}", self.file, self.line)
}

#[must_use]
pub fn location_github(&self) -> String {
format!("{}#L{}", self.file, self.line)
}
}

#[macro_export]
Expand Down Expand Up @@ -143,7 +155,8 @@ macro_rules! declare_clippy_lint_inner {
lint: $lint_name,
category: $crate::LintCategory::$category,
explanation: concat!($($docs,"\n",)*),
location: concat!(file!(), "#L", line!()),
file: file!(),
line: line!(),
version: $version,
};
};
Expand Down
4 changes: 2 additions & 2 deletions tests/compile-test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ impl Flag for DiagnosticCollector {
#[derive(Debug)]
struct LintMetadata {
id: String,
id_location: Option<&'static str>,
id_location: Option<String>,
group: &'static str,
level: &'static str,
docs: String,
Expand Down Expand Up @@ -601,7 +601,7 @@ impl LintMetadata {
}
Self {
id: name,
id_location: Some(lint.location),
id_location: Some(lint.location_github()),
group: lint.category.name(),
level: lint.lint.default_level.as_str(),
docs,
Expand Down
79 changes: 79 additions & 0 deletions tests/lint-definitions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#![feature(rustc_private)]

use std::collections::HashMap;
use std::fs;

use clippy_lints::declared_lints::LINTS;
use clippy_lints::deprecated_lints::RENAMED;
use pulldown_cmark::{Event, HeadingLevel, Parser, Tag, TagEnd};
use test_utils::IS_RUSTC_TEST_SUITE;

mod test_utils;

#[test]
fn versions_match_changelog() {
if IS_RUSTC_TEST_SUITE {
return;
}

let changelog = fs::read_to_string("CHANGELOG.md").unwrap();

let mut versions_by_name: HashMap<_, _> = LINTS.iter().map(|&lint| (lint.name_lower(), lint)).collect();

for (from, to) in RENAMED {
let from = from.strip_prefix("clippy::").unwrap();
if let Some(to) = to.strip_prefix("clippy::") {
versions_by_name.insert(from.to_owned(), versions_by_name[to]);
}
}

let mut heading = None;
let mut changelog_version = None;
let mut in_new_lints = true;
let mut checked = 0;

for event in Parser::new(&changelog) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parsing seems a bit brittle. It relies on the changelog format never changing. Do you have ideas how we can make this more robust? For example, add a cargo dev command that inserts changelog headings automatically.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a note next to the template in the docs, it should be visible in the review diff if the New Lints part changes at least

match event {
Event::Start(Tag::Heading { level, .. }) => {
in_new_lints = false;
heading = Some(level);
},
Event::End(TagEnd::Heading(_)) => heading = None,
Event::Text(text) => match heading {
Some(HeadingLevel::H2) => {
if let Some(v) = text.strip_prefix("Rust ") {
changelog_version = Some(v.to_owned());
}
},
Some(HeadingLevel::H3) => {
in_new_lints = text.eq_ignore_ascii_case("new lints");
},
_ => {},
},
Event::Start(Tag::Link { id, .. }) if in_new_lints => {
if let Some(name) = id.strip_prefix('`')
&& let Some(name) = name.strip_suffix('`')
&& let Some(&lint) = versions_by_name.get(name)
{
let lint_version = lint.version.strip_suffix(".0").unwrap();
let changelog_version = changelog_version.as_deref().unwrap();
assert_eq!(
lint_version,
changelog_version,
"{name} has version {lint_version} but appears in the changelog for {changelog_version}\n\
\n\
update {} to `#[clippy::version = \"{changelog_version}.0\"]`",
lint.location_terminal(),
);
checked += 1;
}
},
_ => {},
}
}

assert!(
checked > 400,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this magic number coming from? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was the number at the time rounded down to the nearest 100, just a basic check that if the format of the entire changelog ever changed or it became a stub linking to a site or something like that then the test would fail

"only checked {checked} versions, did the changelog format change?"
);
}