mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:53 +00:00
Infer Flake8 plugins from .flake8 config (#573)
This commit is contained in:
parent
cc915371ca
commit
83dfb5fe8b
7 changed files with 565 additions and 47 deletions
|
@ -31,6 +31,8 @@ fn main() {
|
||||||
.derive("Debug")
|
.derive("Debug")
|
||||||
.derive("PartialEq")
|
.derive("PartialEq")
|
||||||
.derive("Eq")
|
.derive("Eq")
|
||||||
|
.derive("PartialOrd")
|
||||||
|
.derive("Ord")
|
||||||
.derive("Clone")
|
.derive("Clone")
|
||||||
.derive("Serialize")
|
.derive("Serialize")
|
||||||
.derive("Deserialize");
|
.derive("Deserialize");
|
||||||
|
|
|
@ -17,7 +17,7 @@ pip install flake8-to-ruff
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
To run Ruff, try any of the following:
|
To run `flake8-to-ruff`:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
flake8-to-ruff path/to/setup.cfg
|
flake8-to-ruff path/to/setup.cfg
|
||||||
|
@ -25,6 +25,60 @@ flake8-to-ruff path/to/tox.ini
|
||||||
flake8-to-ruff path/to/.flake8
|
flake8-to-ruff path/to/.flake8
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`flake8-to-ruff` will print the relevant `pyproject.toml` sections to standard output, like so:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.ruff]
|
||||||
|
exclude = [
|
||||||
|
'.svn',
|
||||||
|
'CVS',
|
||||||
|
'.bzr',
|
||||||
|
'.hg',
|
||||||
|
'.git',
|
||||||
|
'__pycache__',
|
||||||
|
'.tox',
|
||||||
|
'.idea',
|
||||||
|
'.mypy_cache',
|
||||||
|
'.venv',
|
||||||
|
'node_modules',
|
||||||
|
'_state_machine.py',
|
||||||
|
'test_fstring.py',
|
||||||
|
'bad_coding2.py',
|
||||||
|
'badsyntax_*.py',
|
||||||
|
]
|
||||||
|
select = [
|
||||||
|
'A',
|
||||||
|
'E',
|
||||||
|
'F',
|
||||||
|
'Q',
|
||||||
|
]
|
||||||
|
ignore = []
|
||||||
|
|
||||||
|
[tool.ruff.flake8-quotes]
|
||||||
|
inline-quotes = 'single'
|
||||||
|
|
||||||
|
[tool.ruff.pep8-naming]
|
||||||
|
ignore-names = [
|
||||||
|
'foo',
|
||||||
|
'bar',
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugins
|
||||||
|
|
||||||
|
`flake8-to-ruff` will attempt to infer any activated plugins based on the settings provided in your
|
||||||
|
configuration file.
|
||||||
|
|
||||||
|
For example, if your `.flake8` file includes a `docstring-convention` property, `flake8-to-ruff`
|
||||||
|
will enable the appropriate [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
|
||||||
|
checks.
|
||||||
|
|
||||||
|
Alternatively, you can manually specify plugins on the command-line:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
|
||||||
|
```
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
|
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
|
||||||
|
@ -33,9 +87,6 @@ flake8-to-ruff path/to/.flake8
|
||||||
2. Ruff will omit any error codes that are unimplemented or unsupported by Ruff, including error
|
2. Ruff will omit any error codes that are unimplemented or unsupported by Ruff, including error
|
||||||
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
|
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
|
||||||
for the complete list of supported plugins.)
|
for the complete list of supported plugins.)
|
||||||
3. `flake8-to-ruff` does not auto-detect your Flake8 plugins, so any reliance on Flake8 plugins that
|
|
||||||
implicitly enable third-party checks will be ignored. Instead, add those error codes to your
|
|
||||||
`select` or `extend-select` fields, so that `flake8-to-ruff` can pick them up.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::{BTreeSet, HashMap};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ruff::flake8_quotes::settings::Quote;
|
use ruff::flake8_quotes::settings::Quote;
|
||||||
|
@ -6,13 +6,38 @@ use ruff::settings::options::Options;
|
||||||
use ruff::settings::pyproject::Pyproject;
|
use ruff::settings::pyproject::Pyproject;
|
||||||
use ruff::{flake8_quotes, pep8_naming};
|
use ruff::{flake8_quotes, pep8_naming};
|
||||||
|
|
||||||
use crate::parser;
|
use crate::plugin::Plugin;
|
||||||
|
use crate::{parser, plugin};
|
||||||
|
|
||||||
pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Result<Pyproject> {
|
pub fn convert(
|
||||||
// Extract the Flake8 section.
|
flake8: &HashMap<String, Option<String>>,
|
||||||
let flake8 = config
|
plugins: Option<Vec<Plugin>>,
|
||||||
.get("flake8")
|
) -> Result<Pyproject> {
|
||||||
.expect("Unable to find flake8 section in INI file.");
|
// Check if the user has specified a `select`. If not, we'll add our own
|
||||||
|
// default `select`, and populate it based on user plugins.
|
||||||
|
let mut select = flake8
|
||||||
|
.get("select")
|
||||||
|
.map(|value| {
|
||||||
|
value
|
||||||
|
.as_ref()
|
||||||
|
.map(|value| BTreeSet::from_iter(parser::parse_prefix_codes(value)))
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
plugin::resolve_select(
|
||||||
|
flake8,
|
||||||
|
&plugins.unwrap_or_else(|| plugin::infer_plugins(&flake8)),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let mut ignore = flake8
|
||||||
|
.get("ignore")
|
||||||
|
.map(|value| {
|
||||||
|
value
|
||||||
|
.as_ref()
|
||||||
|
.map(|value| BTreeSet::from_iter(parser::parse_prefix_codes(value)))
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Parse each supported option.
|
// Parse each supported option.
|
||||||
let mut options: Options = Default::default();
|
let mut options: Options = Default::default();
|
||||||
|
@ -27,16 +52,18 @@ pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Resu
|
||||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||||
},
|
},
|
||||||
"select" => {
|
"select" => {
|
||||||
options.select = Some(parser::parse_prefix_codes(value.as_ref()));
|
// No-op (handled above).
|
||||||
}
|
|
||||||
"extend-select" | "extend_select" => {
|
|
||||||
options.extend_select = Some(parser::parse_prefix_codes(value.as_ref()));
|
|
||||||
}
|
}
|
||||||
"ignore" => {
|
"ignore" => {
|
||||||
options.ignore = Some(parser::parse_prefix_codes(value.as_ref()));
|
// No-op (handled above).
|
||||||
|
}
|
||||||
|
"extend-select" | "extend_select" => {
|
||||||
|
// Unlike Flake8, use a single explicit `select`.
|
||||||
|
select.extend(parser::parse_prefix_codes(value.as_ref()));
|
||||||
}
|
}
|
||||||
"extend-ignore" | "extend_ignore" => {
|
"extend-ignore" | "extend_ignore" => {
|
||||||
options.extend_ignore = Some(parser::parse_prefix_codes(value.as_ref()));
|
// Unlike Flake8, use a single explicit `ignore`.
|
||||||
|
ignore.extend(parser::parse_prefix_codes(value.as_ref()));
|
||||||
}
|
}
|
||||||
"exclude" => {
|
"exclude" => {
|
||||||
options.exclude = Some(parser::parse_strings(value.as_ref()));
|
options.exclude = Some(parser::parse_strings(value.as_ref()));
|
||||||
|
@ -92,6 +119,9 @@ pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Resu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deduplicate and sort.
|
||||||
|
options.select = Some(Vec::from_iter(select));
|
||||||
|
options.ignore = Some(Vec::from_iter(ignore));
|
||||||
if flake8_quotes != Default::default() {
|
if flake8_quotes != Default::default() {
|
||||||
options.flake8_quotes = Some(flake8_quotes);
|
options.flake8_quotes = Some(flake8_quotes);
|
||||||
}
|
}
|
||||||
|
@ -108,22 +138,24 @@ mod tests {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use ruff::checks_gen::CheckCodePrefix;
|
||||||
use ruff::flake8_quotes;
|
use ruff::flake8_quotes;
|
||||||
use ruff::settings::options::Options;
|
use ruff::settings::options::Options;
|
||||||
use ruff::settings::pyproject::Pyproject;
|
use ruff::settings::pyproject::Pyproject;
|
||||||
|
|
||||||
use crate::converter::convert;
|
use crate::converter::convert;
|
||||||
|
use crate::plugin::Plugin;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_converts_empty() -> Result<()> {
|
fn it_converts_empty() -> Result<()> {
|
||||||
let actual = convert(HashMap::from([("flake8".to_string(), HashMap::from([]))]))?;
|
let actual = convert(&HashMap::from([]), None)?;
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
line_length: None,
|
line_length: None,
|
||||||
exclude: None,
|
exclude: None,
|
||||||
extend_exclude: None,
|
extend_exclude: None,
|
||||||
select: None,
|
select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]),
|
||||||
extend_select: None,
|
extend_select: None,
|
||||||
ignore: None,
|
ignore: Some(vec![]),
|
||||||
extend_ignore: None,
|
extend_ignore: None,
|
||||||
per_file_ignores: None,
|
per_file_ignores: None,
|
||||||
dummy_variable_rgx: None,
|
dummy_variable_rgx: None,
|
||||||
|
@ -138,17 +170,17 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_converts_dashes() -> Result<()> {
|
fn it_converts_dashes() -> Result<()> {
|
||||||
let actual = convert(HashMap::from([(
|
let actual = convert(
|
||||||
"flake8".to_string(),
|
&HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
|
||||||
HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
|
Some(vec![]),
|
||||||
)]))?;
|
)?;
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
line_length: Some(100),
|
line_length: Some(100),
|
||||||
exclude: None,
|
exclude: None,
|
||||||
extend_exclude: None,
|
extend_exclude: None,
|
||||||
select: None,
|
select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]),
|
||||||
extend_select: None,
|
extend_select: None,
|
||||||
ignore: None,
|
ignore: Some(vec![]),
|
||||||
extend_ignore: None,
|
extend_ignore: None,
|
||||||
per_file_ignores: None,
|
per_file_ignores: None,
|
||||||
dummy_variable_rgx: None,
|
dummy_variable_rgx: None,
|
||||||
|
@ -163,17 +195,17 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_converts_underscores() -> Result<()> {
|
fn it_converts_underscores() -> Result<()> {
|
||||||
let actual = convert(HashMap::from([(
|
let actual = convert(
|
||||||
"flake8".to_string(),
|
&HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
|
||||||
HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
|
Some(vec![]),
|
||||||
)]))?;
|
)?;
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
line_length: Some(100),
|
line_length: Some(100),
|
||||||
exclude: None,
|
exclude: None,
|
||||||
extend_exclude: None,
|
extend_exclude: None,
|
||||||
select: None,
|
select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]),
|
||||||
extend_select: None,
|
extend_select: None,
|
||||||
ignore: None,
|
ignore: Some(vec![]),
|
||||||
extend_ignore: None,
|
extend_ignore: None,
|
||||||
per_file_ignores: None,
|
per_file_ignores: None,
|
||||||
dummy_variable_rgx: None,
|
dummy_variable_rgx: None,
|
||||||
|
@ -188,17 +220,17 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_ignores_parse_errors() -> Result<()> {
|
fn it_ignores_parse_errors() -> Result<()> {
|
||||||
let actual = convert(HashMap::from([(
|
let actual = convert(
|
||||||
"flake8".to_string(),
|
&HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
|
||||||
HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
|
Some(vec![]),
|
||||||
)]))?;
|
)?;
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
line_length: None,
|
line_length: None,
|
||||||
exclude: None,
|
exclude: None,
|
||||||
extend_exclude: None,
|
extend_exclude: None,
|
||||||
select: None,
|
select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]),
|
||||||
extend_select: None,
|
extend_select: None,
|
||||||
ignore: None,
|
ignore: Some(vec![]),
|
||||||
extend_ignore: None,
|
extend_ignore: None,
|
||||||
per_file_ignores: None,
|
per_file_ignores: None,
|
||||||
dummy_variable_rgx: None,
|
dummy_variable_rgx: None,
|
||||||
|
@ -212,18 +244,118 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_converts_extensions() -> Result<()> {
|
fn it_converts_plugin_options() -> Result<()> {
|
||||||
let actual = convert(HashMap::from([(
|
let actual = convert(
|
||||||
"flake8".to_string(),
|
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||||
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
Some(vec![]),
|
||||||
)]))?;
|
)?;
|
||||||
let expected = Pyproject::new(Options {
|
let expected = Pyproject::new(Options {
|
||||||
line_length: None,
|
line_length: None,
|
||||||
exclude: None,
|
exclude: None,
|
||||||
extend_exclude: None,
|
extend_exclude: None,
|
||||||
select: None,
|
select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]),
|
||||||
extend_select: None,
|
extend_select: None,
|
||||||
ignore: None,
|
ignore: Some(vec![]),
|
||||||
|
extend_ignore: None,
|
||||||
|
per_file_ignores: None,
|
||||||
|
dummy_variable_rgx: None,
|
||||||
|
target_version: None,
|
||||||
|
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||||
|
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||||
|
multiline_quotes: None,
|
||||||
|
docstring_quotes: None,
|
||||||
|
avoid_escape: None,
|
||||||
|
}),
|
||||||
|
pep8_naming: None,
|
||||||
|
});
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_converts_docstring_conventions() -> Result<()> {
|
||||||
|
let actual = convert(
|
||||||
|
&HashMap::from([(
|
||||||
|
"docstring-convention".to_string(),
|
||||||
|
Some("numpy".to_string()),
|
||||||
|
)]),
|
||||||
|
Some(vec![Plugin::Flake8Docstrings]),
|
||||||
|
)?;
|
||||||
|
let expected = Pyproject::new(Options {
|
||||||
|
line_length: None,
|
||||||
|
exclude: None,
|
||||||
|
extend_exclude: None,
|
||||||
|
select: Some(vec![
|
||||||
|
CheckCodePrefix::D100,
|
||||||
|
CheckCodePrefix::D101,
|
||||||
|
CheckCodePrefix::D102,
|
||||||
|
CheckCodePrefix::D103,
|
||||||
|
CheckCodePrefix::D104,
|
||||||
|
CheckCodePrefix::D105,
|
||||||
|
CheckCodePrefix::D106,
|
||||||
|
CheckCodePrefix::D200,
|
||||||
|
CheckCodePrefix::D201,
|
||||||
|
CheckCodePrefix::D202,
|
||||||
|
CheckCodePrefix::D204,
|
||||||
|
CheckCodePrefix::D205,
|
||||||
|
CheckCodePrefix::D206,
|
||||||
|
CheckCodePrefix::D207,
|
||||||
|
CheckCodePrefix::D208,
|
||||||
|
CheckCodePrefix::D209,
|
||||||
|
CheckCodePrefix::D210,
|
||||||
|
CheckCodePrefix::D211,
|
||||||
|
CheckCodePrefix::D214,
|
||||||
|
CheckCodePrefix::D215,
|
||||||
|
CheckCodePrefix::D300,
|
||||||
|
CheckCodePrefix::D400,
|
||||||
|
CheckCodePrefix::D403,
|
||||||
|
CheckCodePrefix::D404,
|
||||||
|
CheckCodePrefix::D405,
|
||||||
|
CheckCodePrefix::D406,
|
||||||
|
CheckCodePrefix::D407,
|
||||||
|
CheckCodePrefix::D408,
|
||||||
|
CheckCodePrefix::D409,
|
||||||
|
CheckCodePrefix::D410,
|
||||||
|
CheckCodePrefix::D411,
|
||||||
|
CheckCodePrefix::D412,
|
||||||
|
CheckCodePrefix::D414,
|
||||||
|
CheckCodePrefix::D418,
|
||||||
|
CheckCodePrefix::D419,
|
||||||
|
CheckCodePrefix::E,
|
||||||
|
CheckCodePrefix::F,
|
||||||
|
]),
|
||||||
|
extend_select: None,
|
||||||
|
ignore: Some(vec![]),
|
||||||
|
extend_ignore: None,
|
||||||
|
per_file_ignores: None,
|
||||||
|
dummy_variable_rgx: None,
|
||||||
|
target_version: None,
|
||||||
|
flake8_quotes: None,
|
||||||
|
pep8_naming: None,
|
||||||
|
});
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_infers_plugins_if_omitted() -> Result<()> {
|
||||||
|
let actual = convert(
|
||||||
|
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
let expected = Pyproject::new(Options {
|
||||||
|
line_length: None,
|
||||||
|
exclude: None,
|
||||||
|
extend_exclude: None,
|
||||||
|
select: Some(vec![
|
||||||
|
CheckCodePrefix::E,
|
||||||
|
CheckCodePrefix::F,
|
||||||
|
CheckCodePrefix::Q,
|
||||||
|
]),
|
||||||
|
extend_select: None,
|
||||||
|
ignore: Some(vec![]),
|
||||||
extend_ignore: None,
|
extend_ignore: None,
|
||||||
per_file_ignores: None,
|
per_file_ignores: None,
|
||||||
dummy_variable_rgx: None,
|
dummy_variable_rgx: None,
|
||||||
|
|
|
@ -2,3 +2,4 @@
|
||||||
|
|
||||||
pub mod converter;
|
pub mod converter;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
pub mod plugin;
|
||||||
|
|
|
@ -6,6 +6,7 @@ use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use configparser::ini::Ini;
|
use configparser::ini::Ini;
|
||||||
use flake8_to_ruff::converter;
|
use flake8_to_ruff::converter;
|
||||||
|
use flake8_to_ruff::plugin::Plugin;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
|
@ -17,6 +18,9 @@ struct Cli {
|
||||||
/// '.flake8').
|
/// '.flake8').
|
||||||
#[arg(required = true)]
|
#[arg(required = true)]
|
||||||
file: PathBuf,
|
file: PathBuf,
|
||||||
|
/// List of plugins to enable.
|
||||||
|
#[arg(long, value_delimiter = ',')]
|
||||||
|
plugin: Option<Vec<Plugin>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
@ -27,8 +31,13 @@ fn main() -> Result<()> {
|
||||||
ini.set_multiline(true);
|
ini.set_multiline(true);
|
||||||
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
|
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
|
||||||
|
|
||||||
|
// Extract the Flake8 section.
|
||||||
|
let flake8 = config
|
||||||
|
.get("flake8")
|
||||||
|
.expect("Unable to find flake8 section in INI file.");
|
||||||
|
|
||||||
// Create the pyproject.toml.
|
// Create the pyproject.toml.
|
||||||
let pyproject = converter::convert(config)?;
|
let pyproject = converter::convert(flake8, cli.plugin)?;
|
||||||
println!("{}", toml::to_string_pretty(&pyproject)?);
|
println!("{}", toml::to_string_pretty(&pyproject)?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
321
flake8_to_ruff/src/plugin.rs
Normal file
321
flake8_to_ruff/src/plugin.rs
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
use std::collections::{BTreeSet, HashMap};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use ruff::checks_gen::CheckCodePrefix;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
pub enum Plugin {
|
||||||
|
Flake8Bugbear,
|
||||||
|
Flake8Builtins,
|
||||||
|
Flake8Comprehensions,
|
||||||
|
Flake8Docstrings,
|
||||||
|
Flake8Print,
|
||||||
|
Flake8Quotes,
|
||||||
|
PEP8Naming,
|
||||||
|
Pyupgrade,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Plugin {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||||
|
match string {
|
||||||
|
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
|
||||||
|
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
|
||||||
|
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
|
||||||
|
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
|
||||||
|
"flake8-print" => Ok(Plugin::Flake8Print),
|
||||||
|
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
|
||||||
|
"pep8-naming" => Ok(Plugin::PEP8Naming),
|
||||||
|
"pyupgrade" => Ok(Plugin::Pyupgrade),
|
||||||
|
_ => Err(anyhow!("Unknown plugin: {}", string)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin {
|
||||||
|
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
|
||||||
|
match self {
|
||||||
|
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
|
||||||
|
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
|
||||||
|
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C],
|
||||||
|
Plugin::Flake8Docstrings => {
|
||||||
|
// Use the user-provided docstring.
|
||||||
|
for key in ["docstring-convention", "docstring_convention"] {
|
||||||
|
if let Some(Some(value)) = flake8.get(key) {
|
||||||
|
match DocstringConvention::from_str(value) {
|
||||||
|
Ok(convention) => return convention.select(),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Unexpected '{key}' value: {value} ({e}");
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Default to PEP8.
|
||||||
|
DocstringConvention::PEP8.select()
|
||||||
|
}
|
||||||
|
Plugin::Flake8Print => vec![CheckCodePrefix::T],
|
||||||
|
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||||
|
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
|
||||||
|
Plugin::Pyupgrade => vec![CheckCodePrefix::U],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DocstringConvention {
|
||||||
|
All,
|
||||||
|
PEP8,
|
||||||
|
NumPy,
|
||||||
|
Google,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DocstringConvention {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||||
|
match string {
|
||||||
|
"all" => Ok(DocstringConvention::All),
|
||||||
|
"pep8" => Ok(DocstringConvention::PEP8),
|
||||||
|
"numpy" => Ok(DocstringConvention::NumPy),
|
||||||
|
"google" => Ok(DocstringConvention::Google),
|
||||||
|
_ => Err(anyhow!("Unknown docstring convention: {}", string)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocstringConvention {
|
||||||
|
fn select(&self) -> Vec<CheckCodePrefix> {
|
||||||
|
match self {
|
||||||
|
DocstringConvention::All => vec![CheckCodePrefix::D],
|
||||||
|
DocstringConvention::PEP8 => vec![
|
||||||
|
// All errors except D203, D212, D213, D214, D215, D404, D405, D406, D407, D408,
|
||||||
|
// D409, D410, D411, D413, D415, D416 and D417.
|
||||||
|
CheckCodePrefix::D100,
|
||||||
|
CheckCodePrefix::D101,
|
||||||
|
CheckCodePrefix::D102,
|
||||||
|
CheckCodePrefix::D103,
|
||||||
|
CheckCodePrefix::D104,
|
||||||
|
CheckCodePrefix::D105,
|
||||||
|
CheckCodePrefix::D106,
|
||||||
|
CheckCodePrefix::D107,
|
||||||
|
CheckCodePrefix::D200,
|
||||||
|
CheckCodePrefix::D201,
|
||||||
|
CheckCodePrefix::D202,
|
||||||
|
// CheckCodePrefix::D203,
|
||||||
|
CheckCodePrefix::D204,
|
||||||
|
CheckCodePrefix::D205,
|
||||||
|
CheckCodePrefix::D206,
|
||||||
|
CheckCodePrefix::D207,
|
||||||
|
CheckCodePrefix::D208,
|
||||||
|
CheckCodePrefix::D209,
|
||||||
|
CheckCodePrefix::D210,
|
||||||
|
CheckCodePrefix::D211,
|
||||||
|
// CheckCodePrefix::D212,
|
||||||
|
// CheckCodePrefix::D213,
|
||||||
|
// CheckCodePrefix::D214,
|
||||||
|
// CheckCodePrefix::D215,
|
||||||
|
CheckCodePrefix::D300,
|
||||||
|
CheckCodePrefix::D400,
|
||||||
|
CheckCodePrefix::D402,
|
||||||
|
CheckCodePrefix::D403,
|
||||||
|
// CheckCodePrefix::D404,
|
||||||
|
// CheckCodePrefix::D405,
|
||||||
|
// CheckCodePrefix::D406,
|
||||||
|
// CheckCodePrefix::D407,
|
||||||
|
// CheckCodePrefix::D408,
|
||||||
|
// CheckCodePrefix::D409,
|
||||||
|
// CheckCodePrefix::D410,
|
||||||
|
// CheckCodePrefix::D411,
|
||||||
|
CheckCodePrefix::D412,
|
||||||
|
// CheckCodePrefix::D413,
|
||||||
|
CheckCodePrefix::D414,
|
||||||
|
// CheckCodePrefix::D415,
|
||||||
|
// CheckCodePrefix::D416,
|
||||||
|
// CheckCodePrefix::D417,
|
||||||
|
CheckCodePrefix::D418,
|
||||||
|
CheckCodePrefix::D419,
|
||||||
|
],
|
||||||
|
DocstringConvention::NumPy => vec![
|
||||||
|
// All errors except D107, D203, D212, D213, D402, D413, D415, D416, and D417.
|
||||||
|
CheckCodePrefix::D100,
|
||||||
|
CheckCodePrefix::D101,
|
||||||
|
CheckCodePrefix::D102,
|
||||||
|
CheckCodePrefix::D103,
|
||||||
|
CheckCodePrefix::D104,
|
||||||
|
CheckCodePrefix::D105,
|
||||||
|
CheckCodePrefix::D106,
|
||||||
|
// CheckCodePrefix::D107,
|
||||||
|
CheckCodePrefix::D200,
|
||||||
|
CheckCodePrefix::D201,
|
||||||
|
CheckCodePrefix::D202,
|
||||||
|
// CheckCodePrefix::D203,
|
||||||
|
CheckCodePrefix::D204,
|
||||||
|
CheckCodePrefix::D205,
|
||||||
|
CheckCodePrefix::D206,
|
||||||
|
CheckCodePrefix::D207,
|
||||||
|
CheckCodePrefix::D208,
|
||||||
|
CheckCodePrefix::D209,
|
||||||
|
CheckCodePrefix::D210,
|
||||||
|
CheckCodePrefix::D211,
|
||||||
|
// CheckCodePrefix::D212,
|
||||||
|
// CheckCodePrefix::D213,
|
||||||
|
CheckCodePrefix::D214,
|
||||||
|
CheckCodePrefix::D215,
|
||||||
|
CheckCodePrefix::D300,
|
||||||
|
CheckCodePrefix::D400,
|
||||||
|
// CheckCodePrefix::D402,
|
||||||
|
CheckCodePrefix::D403,
|
||||||
|
CheckCodePrefix::D404,
|
||||||
|
CheckCodePrefix::D405,
|
||||||
|
CheckCodePrefix::D406,
|
||||||
|
CheckCodePrefix::D407,
|
||||||
|
CheckCodePrefix::D408,
|
||||||
|
CheckCodePrefix::D409,
|
||||||
|
CheckCodePrefix::D410,
|
||||||
|
CheckCodePrefix::D411,
|
||||||
|
CheckCodePrefix::D412,
|
||||||
|
// CheckCodePrefix::D413,
|
||||||
|
CheckCodePrefix::D414,
|
||||||
|
// CheckCodePrefix::D415,
|
||||||
|
// CheckCodePrefix::D416,
|
||||||
|
// CheckCodePrefix::D417,
|
||||||
|
CheckCodePrefix::D418,
|
||||||
|
CheckCodePrefix::D419,
|
||||||
|
],
|
||||||
|
DocstringConvention::Google => vec![
|
||||||
|
// All errors except D203, D204, D213, D215, D400, D401, D404, D406, D407, D408,
|
||||||
|
// D409 and D413.
|
||||||
|
CheckCodePrefix::D100,
|
||||||
|
CheckCodePrefix::D101,
|
||||||
|
CheckCodePrefix::D102,
|
||||||
|
CheckCodePrefix::D103,
|
||||||
|
CheckCodePrefix::D104,
|
||||||
|
CheckCodePrefix::D105,
|
||||||
|
CheckCodePrefix::D106,
|
||||||
|
CheckCodePrefix::D107,
|
||||||
|
CheckCodePrefix::D200,
|
||||||
|
CheckCodePrefix::D201,
|
||||||
|
CheckCodePrefix::D202,
|
||||||
|
// CheckCodePrefix::D203,
|
||||||
|
// CheckCodePrefix::D204,
|
||||||
|
CheckCodePrefix::D205,
|
||||||
|
CheckCodePrefix::D206,
|
||||||
|
CheckCodePrefix::D207,
|
||||||
|
CheckCodePrefix::D208,
|
||||||
|
CheckCodePrefix::D209,
|
||||||
|
CheckCodePrefix::D210,
|
||||||
|
CheckCodePrefix::D211,
|
||||||
|
CheckCodePrefix::D212,
|
||||||
|
// CheckCodePrefix::D213,
|
||||||
|
CheckCodePrefix::D214,
|
||||||
|
// CheckCodePrefix::D215,
|
||||||
|
CheckCodePrefix::D300,
|
||||||
|
// CheckCodePrefix::D400,
|
||||||
|
CheckCodePrefix::D402,
|
||||||
|
CheckCodePrefix::D403,
|
||||||
|
// CheckCodePrefix::D404,
|
||||||
|
CheckCodePrefix::D405,
|
||||||
|
// CheckCodePrefix::D406,
|
||||||
|
// CheckCodePrefix::D407,
|
||||||
|
// CheckCodePrefix::D408,
|
||||||
|
// CheckCodePrefix::D409,
|
||||||
|
CheckCodePrefix::D410,
|
||||||
|
CheckCodePrefix::D411,
|
||||||
|
CheckCodePrefix::D412,
|
||||||
|
// CheckCodePrefix::D413,
|
||||||
|
CheckCodePrefix::D414,
|
||||||
|
CheckCodePrefix::D415,
|
||||||
|
CheckCodePrefix::D416,
|
||||||
|
CheckCodePrefix::D417,
|
||||||
|
CheckCodePrefix::D418,
|
||||||
|
CheckCodePrefix::D419,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Infer the enabled plugins based on user-provided settings.
|
||||||
|
pub fn infer_plugins(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
|
||||||
|
let mut plugins = BTreeSet::new();
|
||||||
|
for (key, _) in flake8 {
|
||||||
|
match key.as_str() {
|
||||||
|
// flake8-docstrings
|
||||||
|
"docstring-convention" | "docstring_convention" => {
|
||||||
|
plugins.insert(Plugin::Flake8Docstrings);
|
||||||
|
}
|
||||||
|
// flake8-builtins
|
||||||
|
"builtins-ignorelist" | "builtins_ignorelist" => {
|
||||||
|
plugins.insert(Plugin::Flake8Builtins);
|
||||||
|
}
|
||||||
|
// flake8-quotes
|
||||||
|
"quotes" | "inline-quotes" | "inline_quotes" => {
|
||||||
|
plugins.insert(Plugin::Flake8Quotes);
|
||||||
|
}
|
||||||
|
"multiline-quotes" | "multiline_quotes" => {
|
||||||
|
plugins.insert(Plugin::Flake8Quotes);
|
||||||
|
}
|
||||||
|
"docstring-quotes" | "docstring_quotes" => {
|
||||||
|
plugins.insert(Plugin::Flake8Quotes);
|
||||||
|
}
|
||||||
|
"avoid-escape" | "avoid_escape" => {
|
||||||
|
plugins.insert(Plugin::Flake8Quotes);
|
||||||
|
}
|
||||||
|
// pep8-naming
|
||||||
|
"ignore-names" | "ignore_names" => {
|
||||||
|
plugins.insert(Plugin::PEP8Naming);
|
||||||
|
}
|
||||||
|
"classmethod-decorators" | "classmethod_decorators" => {
|
||||||
|
plugins.insert(Plugin::PEP8Naming);
|
||||||
|
}
|
||||||
|
"staticmethod-decorators" | "staticmethod_decorators" => {
|
||||||
|
plugins.insert(Plugin::PEP8Naming);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vec::from_iter(plugins)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve the set of enabled `CheckCodePrefix` values for the given plugins.
|
||||||
|
pub fn resolve_select(
|
||||||
|
flake8: &HashMap<String, Option<String>>,
|
||||||
|
plugins: &[Plugin],
|
||||||
|
) -> BTreeSet<CheckCodePrefix> {
|
||||||
|
// Include default Pyflakes and pycodestyle checks.
|
||||||
|
let mut select = BTreeSet::from([CheckCodePrefix::E, CheckCodePrefix::F]);
|
||||||
|
|
||||||
|
// Add prefix codes for every plugin.
|
||||||
|
for plugin in plugins {
|
||||||
|
for prefix in plugin.select(flake8) {
|
||||||
|
select.insert(prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::plugin::{infer_plugins, Plugin};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_infers_plugins() {
|
||||||
|
let actual = infer_plugins(&HashMap::from([(
|
||||||
|
"inline-quotes".to_string(),
|
||||||
|
Some("single".to_string()),
|
||||||
|
)]));
|
||||||
|
let expected = vec![Plugin::Flake8Quotes];
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
|
||||||
|
let actual = infer_plugins(&HashMap::from([(
|
||||||
|
"staticmethod-decorators".to_string(),
|
||||||
|
Some("[]".to_string()),
|
||||||
|
)]));
|
||||||
|
let expected = vec![Plugin::PEP8Naming];
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,9 @@ use strum_macros::{AsRefStr, EnumString};
|
||||||
|
|
||||||
use crate::checks::CheckCode;
|
use crate::checks::CheckCode;
|
||||||
|
|
||||||
#[derive(AsRefStr, EnumString, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
AsRefStr, EnumString, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
pub enum CheckCodePrefix {
|
pub enum CheckCodePrefix {
|
||||||
A,
|
A,
|
||||||
A0,
|
A0,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue