From 83dfb5fe8b5da113bb06efd7dad60207bc359af1 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 3 Nov 2022 22:05:46 -0400 Subject: [PATCH] Infer Flake8 plugins from .flake8 config (#573) --- examples/generate_check_code_prefix.rs | 2 + flake8_to_ruff/README.md | 59 ++++- flake8_to_ruff/src/converter.rs | 214 +++++++++++++---- flake8_to_ruff/src/lib.rs | 1 + flake8_to_ruff/src/main.rs | 11 +- flake8_to_ruff/src/plugin.rs | 321 +++++++++++++++++++++++++ src/checks_gen.rs | 4 +- 7 files changed, 565 insertions(+), 47 deletions(-) create mode 100644 flake8_to_ruff/src/plugin.rs diff --git a/examples/generate_check_code_prefix.rs b/examples/generate_check_code_prefix.rs index 1e74f0fafe..fe842d074b 100644 --- a/examples/generate_check_code_prefix.rs +++ b/examples/generate_check_code_prefix.rs @@ -31,6 +31,8 @@ fn main() { .derive("Debug") .derive("PartialEq") .derive("Eq") + .derive("PartialOrd") + .derive("Ord") .derive("Clone") .derive("Serialize") .derive("Deserialize"); diff --git a/flake8_to_ruff/README.md b/flake8_to_ruff/README.md index 2c2fac7b12..a470cb5bb7 100644 --- a/flake8_to_ruff/README.md +++ b/flake8_to_ruff/README.md @@ -17,7 +17,7 @@ pip install flake8-to-ruff ### Usage -To run Ruff, try any of the following: +To run `flake8-to-ruff`: ```shell 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` 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 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 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.) -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 diff --git a/flake8_to_ruff/src/converter.rs b/flake8_to_ruff/src/converter.rs index 2f0891734e..752a7f99af 100644 --- a/flake8_to_ruff/src/converter.rs +++ b/flake8_to_ruff/src/converter.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use anyhow::Result; use ruff::flake8_quotes::settings::Quote; @@ -6,13 +6,38 @@ use ruff::settings::options::Options; use ruff::settings::pyproject::Pyproject; use ruff::{flake8_quotes, pep8_naming}; -use crate::parser; +use crate::plugin::Plugin; +use crate::{parser, plugin}; -pub fn convert(config: HashMap>>) -> Result { - // Extract the Flake8 section. - let flake8 = config - .get("flake8") - .expect("Unable to find flake8 section in INI file."); +pub fn convert( + flake8: &HashMap>, + plugins: Option>, +) -> Result { + // 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. let mut options: Options = Default::default(); @@ -27,16 +52,18 @@ pub fn convert(config: HashMap>>) -> Resu Err(e) => eprintln!("Unable to parse '{key}' property: {e}"), }, "select" => { - options.select = Some(parser::parse_prefix_codes(value.as_ref())); - } - "extend-select" | "extend_select" => { - options.extend_select = Some(parser::parse_prefix_codes(value.as_ref())); + // No-op (handled above). } "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" => { - 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" => { options.exclude = Some(parser::parse_strings(value.as_ref())); @@ -92,6 +119,9 @@ pub fn convert(config: HashMap>>) -> Resu } } + // Deduplicate and sort. + options.select = Some(Vec::from_iter(select)); + options.ignore = Some(Vec::from_iter(ignore)); if flake8_quotes != Default::default() { options.flake8_quotes = Some(flake8_quotes); } @@ -108,22 +138,24 @@ mod tests { use std::collections::HashMap; use anyhow::Result; + use ruff::checks_gen::CheckCodePrefix; use ruff::flake8_quotes; use ruff::settings::options::Options; use ruff::settings::pyproject::Pyproject; use crate::converter::convert; + use crate::plugin::Plugin; #[test] 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 { line_length: None, exclude: None, extend_exclude: None, - select: None, + select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]), extend_select: None, - ignore: None, + ignore: Some(vec![]), extend_ignore: None, per_file_ignores: None, dummy_variable_rgx: None, @@ -138,17 +170,17 @@ mod tests { #[test] fn it_converts_dashes() -> Result<()> { - let actual = convert(HashMap::from([( - "flake8".to_string(), - HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]), - )]))?; + let actual = convert( + &HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]), + Some(vec![]), + )?; let expected = Pyproject::new(Options { line_length: Some(100), exclude: None, extend_exclude: None, - select: None, + select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]), extend_select: None, - ignore: None, + ignore: Some(vec![]), extend_ignore: None, per_file_ignores: None, dummy_variable_rgx: None, @@ -163,17 +195,17 @@ mod tests { #[test] fn it_converts_underscores() -> Result<()> { - let actual = convert(HashMap::from([( - "flake8".to_string(), - HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]), - )]))?; + let actual = convert( + &HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]), + Some(vec![]), + )?; let expected = Pyproject::new(Options { line_length: Some(100), exclude: None, extend_exclude: None, - select: None, + select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]), extend_select: None, - ignore: None, + ignore: Some(vec![]), extend_ignore: None, per_file_ignores: None, dummy_variable_rgx: None, @@ -188,17 +220,17 @@ mod tests { #[test] fn it_ignores_parse_errors() -> Result<()> { - let actual = convert(HashMap::from([( - "flake8".to_string(), - HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]), - )]))?; + let actual = convert( + &HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]), + Some(vec![]), + )?; let expected = Pyproject::new(Options { line_length: None, exclude: None, extend_exclude: None, - select: None, + select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]), extend_select: None, - ignore: None, + ignore: Some(vec![]), extend_ignore: None, per_file_ignores: None, dummy_variable_rgx: None, @@ -212,18 +244,118 @@ mod tests { } #[test] - fn it_converts_extensions() -> Result<()> { - let actual = convert(HashMap::from([( - "flake8".to_string(), - HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]), - )]))?; + fn it_converts_plugin_options() -> Result<()> { + let actual = convert( + &HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]), + Some(vec![]), + )?; let expected = Pyproject::new(Options { line_length: None, exclude: None, extend_exclude: None, - select: None, + select: Some(vec![CheckCodePrefix::E, CheckCodePrefix::F]), 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, per_file_ignores: None, dummy_variable_rgx: None, diff --git a/flake8_to_ruff/src/lib.rs b/flake8_to_ruff/src/lib.rs index 7e49d25b1f..8c093722a1 100644 --- a/flake8_to_ruff/src/lib.rs +++ b/flake8_to_ruff/src/lib.rs @@ -2,3 +2,4 @@ pub mod converter; mod parser; +pub mod plugin; diff --git a/flake8_to_ruff/src/main.rs b/flake8_to_ruff/src/main.rs index b454493f22..879a1aabe3 100644 --- a/flake8_to_ruff/src/main.rs +++ b/flake8_to_ruff/src/main.rs @@ -6,6 +6,7 @@ use anyhow::Result; use clap::Parser; use configparser::ini::Ini; use flake8_to_ruff::converter; +use flake8_to_ruff::plugin::Plugin; #[derive(Parser)] #[command( @@ -17,6 +18,9 @@ struct Cli { /// '.flake8'). #[arg(required = true)] file: PathBuf, + /// List of plugins to enable. + #[arg(long, value_delimiter = ',')] + plugin: Option>, } fn main() -> Result<()> { @@ -27,8 +31,13 @@ fn main() -> Result<()> { ini.set_multiline(true); 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. - let pyproject = converter::convert(config)?; + let pyproject = converter::convert(flake8, cli.plugin)?; println!("{}", toml::to_string_pretty(&pyproject)?); Ok(()) diff --git a/flake8_to_ruff/src/plugin.rs b/flake8_to_ruff/src/plugin.rs new file mode 100644 index 0000000000..3cff1f29c7 --- /dev/null +++ b/flake8_to_ruff/src/plugin.rs @@ -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 { + 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>) -> Vec { + 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 { + 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 { + 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>) -> Vec { + 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>, + plugins: &[Plugin], +) -> BTreeSet { + // 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); + } +} diff --git a/src/checks_gen.rs b/src/checks_gen.rs index 61ab94f3bf..a3dbac4d46 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -5,7 +5,9 @@ use strum_macros::{AsRefStr, EnumString}; 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 { A, A0,