Add backwards compatible redirect map for U-to-UP rename (#1019)

This commit is contained in:
Charlie Marsh 2022-12-03 22:43:00 -05:00 committed by GitHub
parent 58e7baedd0
commit 8a3f29497b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 342 additions and 16 deletions

1
Cargo.lock generated
View file

@ -1896,6 +1896,7 @@ dependencies = [
"codegen",
"itertools",
"libcst",
"once_cell",
"ruff",
"rustpython-ast",
"rustpython-common",

View file

@ -0,0 +1,9 @@
from typing import Optional
def f(x: Optional[str]) -> None: # noqa: U007
...
def f(x: Optional[str]) -> None: # noqa: UP007
...

View file

@ -9,6 +9,7 @@ clap = { version = "4.0.1", features = ["derive"] }
codegen = { version = "0.2.0" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
once_cell = { version = "1.16.0" }
ruff = { path = ".." }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }

View file

@ -8,7 +8,7 @@ use anyhow::Result;
use clap::Parser;
use codegen::{Scope, Type, Variant};
use itertools::Itertools;
use ruff::checks::CheckCode;
use ruff::checks::{CheckCode, REDIRECTS};
use strum::IntoEnumIterator;
const FILE: &str = "src/checks_gen.rs";
@ -26,18 +26,47 @@ pub fn main(cli: &Cli) -> Result<()> {
// Build up a map from prefix to matching CheckCodes.
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = BTreeMap::default();
for check_code in CheckCode::iter() {
let as_ref: String = check_code.as_ref().to_string();
let prefix_len = as_ref
let code_str: String = check_code.as_ref().to_string();
let code_prefix_len = code_str
.chars()
.take_while(|char| char.is_alphabetic())
.count();
for i in prefix_len..=as_ref.len() {
let prefix = as_ref[..i].to_string();
let code_suffix_len = code_str.len() - code_prefix_len;
for i in 0..=code_suffix_len {
let prefix = code_str[..code_prefix_len + i].to_string();
let entry = prefix_to_codes.entry(prefix).or_default();
entry.insert(check_code.clone());
}
}
// Add any aliases (e.g., "U001" to "UP001").
for (alias, check_code) in REDIRECTS.iter() {
// Compute the length of the prefix and suffix for both codes.
let code_str: String = check_code.as_ref().to_string();
let code_prefix_len = code_str
.chars()
.take_while(|char| char.is_alphabetic())
.count();
let code_suffix_len = code_str.len() - code_prefix_len;
let alias_prefix_len = alias
.chars()
.take_while(|char| char.is_alphabetic())
.count();
let alias_suffix_len = alias.len() - alias_prefix_len;
assert_eq!(code_suffix_len, alias_suffix_len);
for i in 0..=code_suffix_len {
let source = code_str[..code_prefix_len + i].to_string();
let destination = alias[..alias_prefix_len + i].to_string();
prefix_to_codes.insert(
destination,
prefix_to_codes
.get(&source)
.unwrap_or_else(|| panic!("Unknown CheckCode: {source:?}"))
.clone(),
);
}
}
let mut scope = Scope::new();
// Create the `CheckCodePrefix` definition.
@ -81,6 +110,19 @@ pub fn main(cli: &Cli) -> Result<()> {
.line("#[allow(clippy::match_same_arms)]")
.line("match self {");
for (prefix, codes) in &prefix_to_codes {
if let Some(target) = REDIRECTS.get(&prefix.as_str()) {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been renamed to \
`{}`\".bold()); \n vec![{}] }}",
prefix,
target.as_ref(),
codes
.iter()
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
} else {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => vec![{}],",
codes
@ -89,6 +131,7 @@ pub fn main(cli: &Cli) -> Result<()> {
.join(", ")
));
}
}
gen.line("}");
// Create the `match` statement, to map from definition to specificity.
@ -123,6 +166,8 @@ pub fn main(cli: &Cli) -> Result<()> {
.push_str("//! File automatically generated by `examples/generate_check_code_prefix.rs`.");
output.push('\n');
output.push('\n');
output.push_str("use colored::Colorize;");
output.push('\n');
output.push_str("use serde::{{Serialize, Deserialize}};");
output.push('\n');
output.push_str("use strum_macros::EnumString;");

View file

@ -7,7 +7,7 @@ use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::checks::{Check, CheckCode, CheckKind, REDIRECTS};
use crate::noqa;
use crate::noqa::{is_file_exempt, Directive};
use crate::settings::Settings;
@ -70,7 +70,7 @@ pub fn check_lines(
}
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&$check.kind.code().as_ref()) {
if noqa::includes($check.kind.code(), codes) {
matches.push($check.kind.code().as_ref());
if ignore_noqa {
line_checks.push($check);
@ -140,7 +140,7 @@ pub fn check_lines(
ignored.push(index);
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&check.kind.code().as_ref()) {
if noqa::includes(check.kind.code(), codes) {
matches.push(check.kind.code().as_ref());
ignored.push(index);
}
@ -210,6 +210,7 @@ pub fn check_lines(
let mut invalid_codes = vec![];
let mut valid_codes = vec![];
for code in codes {
let code = REDIRECTS.get(code).map_or(code, AsRef::as_ref);
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code.to_string());
} else {

View file

@ -2,6 +2,8 @@ use std::fmt;
use std::str::FromStr;
use itertools::Itertools;
use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
@ -2562,6 +2564,27 @@ impl Check {
}
}
/// A hash map from deprecated to latest `CheckCode`.
pub static REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
FxHashMap::from_iter([
// TODO(charlie): Remove by 2023-01-01.
("U001", CheckCode::UP001),
("U003", CheckCode::UP003),
("U004", CheckCode::UP004),
("U005", CheckCode::UP005),
("U006", CheckCode::UP006),
("U007", CheckCode::UP007),
("U008", CheckCode::UP008),
("U009", CheckCode::UP009),
("U010", CheckCode::UP010),
("U011", CheckCode::UP011),
("U012", CheckCode::UP012),
("U013", CheckCode::UP013),
("U014", CheckCode::UP014),
("U015", CheckCode::UP015),
])
});
#[cfg(test)]
mod tests {
use std::str::FromStr;

View file

@ -1,5 +1,6 @@
//! File automatically generated by `examples/generate_check_code_prefix.rs`.
use colored::Colorize;
use serde::{Deserialize, Serialize};
use strum_macros::EnumString;
@ -348,6 +349,24 @@ pub enum CheckCodePrefix {
T20,
T201,
T203,
U,
U0,
U00,
U001,
U003,
U004,
U005,
U006,
U007,
U008,
U009,
U01,
U010,
U011,
U012,
U013,
U014,
U015,
UP,
UP0,
UP00,
@ -1320,6 +1339,182 @@ impl CheckCodePrefix {
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T201 => vec![CheckCode::T201],
CheckCodePrefix::T203 => vec![CheckCode::T203],
CheckCodePrefix::U => vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::U0 => vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::U00 => vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
],
CheckCodePrefix::U001 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U001` has been renamed to `UP001`".bold()
);
vec![CheckCode::UP001]
}
CheckCodePrefix::U003 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U003` has been renamed to `UP003`".bold()
);
vec![CheckCode::UP003]
}
CheckCodePrefix::U004 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U004` has been renamed to `UP004`".bold()
);
vec![CheckCode::UP004]
}
CheckCodePrefix::U005 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U005` has been renamed to `UP005`".bold()
);
vec![CheckCode::UP005]
}
CheckCodePrefix::U006 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U006` has been renamed to `UP006`".bold()
);
vec![CheckCode::UP006]
}
CheckCodePrefix::U007 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U007` has been renamed to `UP007`".bold()
);
vec![CheckCode::UP007]
}
CheckCodePrefix::U008 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U008` has been renamed to `UP008`".bold()
);
vec![CheckCode::UP008]
}
CheckCodePrefix::U009 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U009` has been renamed to `UP009`".bold()
);
vec![CheckCode::UP009]
}
CheckCodePrefix::U01 => vec![
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::U010 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U010` has been renamed to `UP010`".bold()
);
vec![CheckCode::UP010]
}
CheckCodePrefix::U011 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U011` has been renamed to `UP011`".bold()
);
vec![CheckCode::UP011]
}
CheckCodePrefix::U012 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U012` has been renamed to `UP012`".bold()
);
vec![CheckCode::UP012]
}
CheckCodePrefix::U013 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U013` has been renamed to `UP013`".bold()
);
vec![CheckCode::UP013]
}
CheckCodePrefix::U014 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U014` has been renamed to `UP014`".bold()
);
vec![CheckCode::UP014]
}
CheckCodePrefix::U015 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U015` has been renamed to `UP015`".bold()
);
vec![CheckCode::UP015]
}
CheckCodePrefix::UP => vec![
CheckCode::UP001,
CheckCode::UP003,
@ -1778,6 +1973,24 @@ impl CheckCodePrefix {
CheckCodePrefix::T20 => SuffixLength::Two,
CheckCodePrefix::T201 => SuffixLength::Three,
CheckCodePrefix::T203 => SuffixLength::Three,
CheckCodePrefix::U => SuffixLength::Zero,
CheckCodePrefix::U0 => SuffixLength::One,
CheckCodePrefix::U00 => SuffixLength::Two,
CheckCodePrefix::U001 => SuffixLength::Three,
CheckCodePrefix::U003 => SuffixLength::Three,
CheckCodePrefix::U004 => SuffixLength::Three,
CheckCodePrefix::U005 => SuffixLength::Three,
CheckCodePrefix::U006 => SuffixLength::Three,
CheckCodePrefix::U007 => SuffixLength::Three,
CheckCodePrefix::U008 => SuffixLength::Three,
CheckCodePrefix::U009 => SuffixLength::Three,
CheckCodePrefix::U01 => SuffixLength::Two,
CheckCodePrefix::U010 => SuffixLength::Three,
CheckCodePrefix::U011 => SuffixLength::Three,
CheckCodePrefix::U012 => SuffixLength::Three,
CheckCodePrefix::U013 => SuffixLength::Three,
CheckCodePrefix::U014 => SuffixLength::Three,
CheckCodePrefix::U015 => SuffixLength::Three,
CheckCodePrefix::UP => SuffixLength::Zero,
CheckCodePrefix::UP0 => SuffixLength::One,
CheckCodePrefix::UP00 => SuffixLength::Two,
@ -1847,6 +2060,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::RUF,
CheckCodePrefix::S,
CheckCodePrefix::T,
CheckCodePrefix::U,
CheckCodePrefix::UP,
CheckCodePrefix::W,
CheckCodePrefix::YTT,

View file

@ -8,7 +8,7 @@ use nohash_hasher::IntMap;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::checks::{Check, CheckCode};
use crate::checks::{Check, CheckCode, REDIRECTS};
static NO_QA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
@ -37,6 +37,7 @@ pub enum Directive<'a> {
Codes(usize, usize, usize, Vec<&'a str>),
}
/// Extract the noqa `Directive` from a line of Python source code.
pub fn extract_noqa_directive(line: &str) -> Directive {
match NO_QA_LINE_REGEX.captures(line) {
Some(caps) => match caps.name("spaces") {
@ -64,6 +65,19 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
}
}
/// Returns `true` if the string list of `codes` includes `code` (or an alias
/// thereof).
pub fn includes(needle: &CheckCode, haystack: &[&str]) -> bool {
let needle: &str = needle.as_ref();
haystack.iter().any(|candidate| {
if let Some(candidate) = REDIRECTS.get(candidate) {
needle == candidate.as_ref()
} else {
&needle == candidate
}
})
}
pub fn add_noqa(
path: &Path,
checks: &[Check],

View file

@ -74,4 +74,16 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn redirects() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/ruff/redirects.py"),
&settings::Settings::for_rules(vec![CheckCode::UP007]),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View file

@ -0,0 +1,6 @@
---
source: src/rules/mod.rs
expression: checks
---
[]