diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 349e7e0887df99..1e3d76a7aa2feb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -105,6 +105,8 @@ You may also want to add the new configuration option to the `flake8-to-ruff` to responsible for converting `flake8` configuration files to Ruff's TOML format. This logic lives in `flake8_to_ruff/src/converter.rs`. +To update the documentation for supported configuration options, run `cargo dev generate-options`. + ## Release process As of now, Ruff has an ad hoc release process: releases are cut with high frequency via GitHub diff --git a/README.md b/README.md index 8d6b0325324cdd..53c9aa6cabc12f 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,13 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" # Assume Python 3.10. target-version = "py310" +[tool.ruff.flake8-import-conventions.aliases] +altair = "alt" +"matplotlib.pyplot" = "plt" +numpy = "np" +pandas = "pd" +seaborn = "sns" + [tool.ruff.mccabe] # Unlike Flake8, default to a complexity level of 10. max-complexity = 10 @@ -786,6 +793,12 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI. | PLR1722 | ConsiderUsingSysExit | Consider using `sys.exit()` | 🛠 | | PLW0120 | UselessElseOnLoop | Else clause on loop without a break statement, remove the else and de-indent all the code inside it | | +### flake8-import-conventions + +| Code | Name | Message | Fix | +| ---- | ---- | ------- | --- | +| ICN001 | ImportAliasIsNotConventional | `...` should be imported as `...` | | + ### Ruff-specific rules | Code | Name | Message | Fix | @@ -940,6 +953,7 @@ natively, including: - [`flake8-debugger`](https://pypi.org/project/flake8-debugger/) - [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) - [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/) +- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions) - [`flake8-print`](https://pypi.org/project/flake8-print/) - [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-return`](https://pypi.org/project/flake8-return/) @@ -988,6 +1002,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [`flake8-debugger`](https://pypi.org/project/flake8-debugger/) - [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) - [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/) +- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions) - [`flake8-print`](https://pypi.org/project/flake8-print/) - [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-return`](https://pypi.org/project/flake8-return/) @@ -1758,6 +1773,48 @@ extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"] --- +### `flake8-import-conventions` + +#### [`aliases`](#aliases) + +The conventional aliases for imports. These aliases can be extended by the `extend_aliases` option. + +**Default value**: `{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}` + +**Type**: `FxHashMap` + +**Example usage**: + +```toml +[tool.ruff.flake8-import-conventions] +# Declare the default aliases. +altair = "alt" +matplotlib.pyplot = "plt" +numpy = "np" +pandas = "pd" +seaborn = "sns" +``` + +--- + +#### [`extend-aliases`](#extend-aliases) + +A mapping of modules to their conventional import aliases. These aliases will be added to the `aliases` mapping. + +**Default value**: `{}` + +**Type**: `FxHashMap` + +**Example usage**: + +```toml +[tool.ruff.flake8-import-conventions] +# Declare a custom alias for the `matplotlib` module. +"dask.dataframe" = "dd" +``` + +--- + ### `flake8-quotes` #### [`avoid-escape`](#avoid-escape) diff --git a/flake8_to_ruff/src/converter.rs b/flake8_to_ruff/src/converter.rs index 6235f7c78f5db5..7a7f974de89ec4 100644 --- a/flake8_to_ruff/src/converter.rs +++ b/flake8_to_ruff/src/converter.rs @@ -270,6 +270,7 @@ mod tests { flake8_bugbear: None, flake8_quotes: None, flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -314,6 +315,7 @@ mod tests { flake8_bugbear: None, flake8_quotes: None, flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -358,6 +360,7 @@ mod tests { flake8_bugbear: None, flake8_quotes: None, flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -402,6 +405,7 @@ mod tests { flake8_bugbear: None, flake8_quotes: None, flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -451,6 +455,7 @@ mod tests { avoid_escape: None, }), flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -533,6 +538,7 @@ mod tests { flake8_bugbear: None, flake8_quotes: None, flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -583,6 +589,7 @@ mod tests { avoid_escape: None, }), flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, diff --git a/resources/test/fixtures/flake8_import_conventions/custom.py b/resources/test/fixtures/flake8_import_conventions/custom.py new file mode 100644 index 00000000000000..cf6743106d3d9a --- /dev/null +++ b/resources/test/fixtures/flake8_import_conventions/custom.py @@ -0,0 +1,25 @@ +import math # not checked + +import altair # unconventional +import dask.array # unconventional +import dask.dataframe # unconventional +import matplotlib.pyplot # unconventional +import numpy # unconventional +import pandas # unconventional +import seaborn # unconventional + +import altair as altr # unconventional +import matplotlib.pyplot as plot # unconventional +import dask.array as darray # unconventional +import dask.dataframe as ddf # unconventional +import numpy as nmp # unconventional +import pandas as pdas # unconventional +import seaborn as sbrn # unconventional + +import altair as alt # conventional +import dask.array as da # conventional +import dask.dataframe as dd # conventional +import matplotlib.pyplot as plt # conventional +import numpy as np # conventional +import pandas as pd # conventional +import seaborn as sns # conventional diff --git a/resources/test/fixtures/flake8_import_conventions/defaults.py b/resources/test/fixtures/flake8_import_conventions/defaults.py new file mode 100644 index 00000000000000..277b6ca10bc079 --- /dev/null +++ b/resources/test/fixtures/flake8_import_conventions/defaults.py @@ -0,0 +1,19 @@ +import math # not checked + +import altair # unconventional +import matplotlib.pyplot # unconventional +import numpy # unconventional +import pandas # unconventional +import seaborn # unconventional + +import altair as altr # unconventional +import matplotlib.pyplot as plot # unconventional +import numpy as nmp # unconventional +import pandas as pdas # unconventional +import seaborn as sbrn # unconventional + +import altair as alt # conventional +import matplotlib.pyplot as plt # conventional +import numpy as np # conventional +import pandas as pd # conventional +import seaborn as sns # conventional diff --git a/resources/test/fixtures/flake8_import_conventions/override_default.py b/resources/test/fixtures/flake8_import_conventions/override_default.py new file mode 100644 index 00000000000000..dd3c325792ba1f --- /dev/null +++ b/resources/test/fixtures/flake8_import_conventions/override_default.py @@ -0,0 +1,19 @@ +import math # not checked + +import altair # unconventional +import matplotlib.pyplot # unconventional +import numpy # unconventional +import pandas # unconventional +import seaborn # unconventional + +import altair as altr # unconventional +import matplotlib.pyplot as plot # unconventional +import numpy as np # unconventional +import pandas as pdas # unconventional +import seaborn as sbrn # unconventional + +import altair as alt # conventional +import matplotlib.pyplot as plt # conventional +import numpy as nmp # conventional +import pandas as pd # conventional +import seaborn as sns # conventional diff --git a/resources/test/fixtures/flake8_import_conventions/remove_default.py b/resources/test/fixtures/flake8_import_conventions/remove_default.py new file mode 100644 index 00000000000000..492c401fbca59e --- /dev/null +++ b/resources/test/fixtures/flake8_import_conventions/remove_default.py @@ -0,0 +1,19 @@ +import math # not checked + +import altair # unconventional +import matplotlib.pyplot # unconventional +import numpy # not checked +import pandas # unconventional +import seaborn # unconventional + +import altair as altr # unconventional +import matplotlib.pyplot as plot # unconventional +import numpy as nmp # not checked +import pandas as pdas # unconventional +import seaborn as sbrn # unconventional + +import altair as alt # conventional +import matplotlib.pyplot as plt # conventional +import numpy as np # not checked +import pandas as pd # conventional +import seaborn as sns # conventional diff --git a/resources/test/fixtures/pyproject.toml b/resources/test/fixtures/pyproject.toml index e5d9d0945b12c1..78d5fd8cc6b697 100644 --- a/resources/test/fixtures/pyproject.toml +++ b/resources/test/fixtures/pyproject.toml @@ -41,3 +41,9 @@ staticmethod-decorators = ["staticmethod"] [tool.ruff.flake8-tidy-imports] ban-relative-imports = "parents" + +[tool.ruff.flake8-import-conventions.aliases] +pandas = "pd" + +[tool.ruff.flake8-import-conventions.extend-aliases] +"dask.dataframe" = "dd" diff --git a/src/check_ast.rs b/src/check_ast.rs index fb66ec4f922718..778342149cfe0b 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -35,8 +35,8 @@ use crate::visibility::{module_visibility, transition_scope, Modifier, Visibilit use crate::{ docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger, - flake8_print, flake8_return, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, - pyflakes, pygrep_hooks, pylint, pyupgrade, + flake8_import_conventions, flake8_print, flake8_return, flake8_tidy_imports, mccabe, + pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, }; const GLOBAL_SCOPE_INDEX: usize = 0; @@ -727,6 +727,19 @@ where } } } + + if self.settings.enabled.contains(&CheckCode::ICN001) { + if let Some(check) = + flake8_import_conventions::checks::check_conventional_import( + stmt, + &alias.node.name, + alias.node.asname.as_deref(), + &self.settings.flake8_import_conventions.aliases, + ) + { + self.add_check(check); + } + } } } StmtKind::ImportFrom { diff --git a/src/checks.rs b/src/checks.rs index 58e5fa8bad2af1..9ae0c323146641 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -290,6 +290,8 @@ pub enum CheckCode { FBT001, FBT002, FBT003, + // flake8-import-conventions + ICN001, // Ruff RUF001, RUF002, @@ -324,6 +326,7 @@ pub enum CheckCategory { Eradicate, PygrepHooks, Pylint, + ImportConventions, Ruff, } @@ -367,6 +370,7 @@ impl CheckCategory { CheckCategory::PygrepHooks => "pygrep-hooks", CheckCategory::Pylint => "Pylint", CheckCategory::Pyupgrade => "pyupgrade", + CheckCategory::ImportConventions => "flake8-import-conventions", CheckCategory::Ruff => "Ruff-specific rules", } } @@ -459,6 +463,7 @@ impl CheckCategory { CheckCategory::Pyupgrade => { Some(("https://pypi.org/project/pyupgrade/3.2.0/", &Platform::PyPI)) } + CheckCategory::ImportConventions => None, CheckCategory::Ruff => None, } } @@ -774,6 +779,8 @@ pub enum CheckKind { BooleanPositionalValueInFunctionCall, // pygrep-hooks NoEval, + // flake8-import-conventions + ImportAliasIsNotConventional(String, String), // Ruff AmbiguousUnicodeCharacterString(char, char), AmbiguousUnicodeCharacterDocstring(char, char), @@ -1113,6 +1120,10 @@ impl CheckCode { CheckCode::FBT003 => CheckKind::BooleanPositionalValueInFunctionCall, // pygrep-hooks CheckCode::PGH001 => CheckKind::NoEval, + // flake8-import-conventions + CheckCode::ICN001 => { + CheckKind::ImportAliasIsNotConventional("...".to_string(), "...".to_string()) + } // Ruff CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'), CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'), @@ -1365,6 +1376,7 @@ impl CheckCode { CheckCode::YTT301 => CheckCategory::Flake82020, CheckCode::YTT302 => CheckCategory::Flake82020, CheckCode::YTT303 => CheckCategory::Flake82020, + CheckCode::ICN001 => CheckCategory::ImportConventions, } } } @@ -1633,6 +1645,8 @@ impl CheckKind { CheckKind::BooleanPositionalValueInFunctionCall => &CheckCode::FBT003, // pygrep-hooks CheckKind::NoEval => &CheckCode::PGH001, + // flake8-import-conventions + CheckKind::ImportAliasIsNotConventional(..) => &CheckCode::ICN001, // Ruff CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001, CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002, @@ -2419,6 +2433,10 @@ impl CheckKind { } // pygrep-hooks CheckKind::NoEval => "No builtin `eval()` allowed".to_string(), + // flake8-import-conventions + CheckKind::ImportAliasIsNotConventional(name, asname) => { + format!("`{name}` should be imported as `{asname}`") + } // Ruff CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => { format!( diff --git a/src/checks_gen.rs b/src/checks_gen.rs index 173ff8e7dd1b88..20c8c62d428736 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -256,6 +256,10 @@ pub enum CheckCodePrefix { I2, I25, I252, + ICN, + ICN0, + ICN00, + ICN001, N, N8, N80, @@ -1138,6 +1142,10 @@ impl CheckCodePrefix { CheckCodePrefix::I2 => vec![CheckCode::I252], CheckCodePrefix::I25 => vec![CheckCode::I252], CheckCodePrefix::I252 => vec![CheckCode::I252], + CheckCodePrefix::ICN => vec![CheckCode::ICN001], + CheckCodePrefix::ICN0 => vec![CheckCode::ICN001], + CheckCodePrefix::ICN00 => vec![CheckCode::ICN001], + CheckCodePrefix::ICN001 => vec![CheckCode::ICN001], CheckCodePrefix::N => vec![ CheckCode::N801, CheckCode::N802, @@ -1907,6 +1915,10 @@ impl CheckCodePrefix { CheckCodePrefix::I2 => SuffixLength::One, CheckCodePrefix::I25 => SuffixLength::Two, CheckCodePrefix::I252 => SuffixLength::Three, + CheckCodePrefix::ICN => SuffixLength::Zero, + CheckCodePrefix::ICN0 => SuffixLength::One, + CheckCodePrefix::ICN00 => SuffixLength::Two, + CheckCodePrefix::ICN001 => SuffixLength::Three, CheckCodePrefix::N => SuffixLength::Zero, CheckCodePrefix::N8 => SuffixLength::One, CheckCodePrefix::N80 => SuffixLength::Two, @@ -2087,6 +2099,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[ CheckCodePrefix::F, CheckCodePrefix::FBT, CheckCodePrefix::I, + CheckCodePrefix::ICN, CheckCodePrefix::N, CheckCodePrefix::PGH, CheckCodePrefix::PLC, diff --git a/src/flake8_import_conventions/checks.rs b/src/flake8_import_conventions/checks.rs new file mode 100644 index 00000000000000..08c3c7642305c1 --- /dev/null +++ b/src/flake8_import_conventions/checks.rs @@ -0,0 +1,37 @@ +use std::collections::BTreeMap; + +use rustpython_ast::Stmt; + +use crate::ast::types::Range; +use crate::checks::{Check, CheckKind}; + +/// ICN001 +pub fn check_conventional_import( + import_from: &Stmt, + name: &str, + asname: Option<&str>, + conventions: &BTreeMap, +) -> Option { + let mut is_valid_import = true; + if let Some(expected_alias) = conventions.get(name) { + if !expected_alias.is_empty() { + if let Some(alias) = asname { + if expected_alias != alias { + is_valid_import = false; + } + } else { + is_valid_import = false; + } + } + if !is_valid_import { + return Some(Check::new( + CheckKind::ImportAliasIsNotConventional( + name.to_string(), + expected_alias.to_string(), + ), + Range::from_located(import_from), + )); + } + } + None +} diff --git a/src/flake8_import_conventions/mod.rs b/src/flake8_import_conventions/mod.rs new file mode 100644 index 00000000000000..aebe43c2a60faa --- /dev/null +++ b/src/flake8_import_conventions/mod.rs @@ -0,0 +1,100 @@ +pub mod checks; +pub mod settings; + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + use std::path::Path; + + use anyhow::Result; + + use crate::checks::CheckCode; + use crate::linter::test_path; + use crate::{flake8_import_conventions, Settings}; + + #[test] + fn defaults() -> Result<()> { + let mut checks = test_path( + Path::new("./resources/test/fixtures/flake8_import_conventions/defaults.py"), + &Settings::for_rule(CheckCode::ICN001), + true, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!("defaults", checks); + Ok(()) + } + + #[test] + fn custom() -> Result<()> { + let mut checks = test_path( + Path::new("./resources/test/fixtures/flake8_import_conventions/custom.py"), + &Settings { + flake8_import_conventions: + flake8_import_conventions::settings::Settings::from_options( + flake8_import_conventions::settings::Options { + aliases: None, + extend_aliases: Some(BTreeMap::from([ + ("dask.array".to_string(), "da".to_string()), + ("dask.dataframe".to_string(), "dd".to_string()), + ])), + }, + ), + ..Settings::for_rule(CheckCode::ICN001) + }, + true, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!("custom", checks); + Ok(()) + } + + #[test] + fn remove_defaults() -> Result<()> { + let mut checks = test_path( + Path::new("./resources/test/fixtures/flake8_import_conventions/remove_default.py"), + &Settings { + flake8_import_conventions: + flake8_import_conventions::settings::Settings::from_options( + flake8_import_conventions::settings::Options { + aliases: Some(BTreeMap::from([ + ("altair".to_string(), "alt".to_string()), + ("matplotlib.pyplot".to_string(), "plt".to_string()), + ("pandas".to_string(), "pd".to_string()), + ("seaborn".to_string(), "sns".to_string()), + ])), + extend_aliases: None, + }, + ), + ..Settings::for_rule(CheckCode::ICN001) + }, + true, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!("remove_default", checks); + Ok(()) + } + + #[test] + fn override_defaults() -> Result<()> { + let mut checks = test_path( + Path::new("./resources/test/fixtures/flake8_import_conventions/override_default.py"), + &Settings { + flake8_import_conventions: + flake8_import_conventions::settings::Settings::from_options( + flake8_import_conventions::settings::Options { + aliases: None, + extend_aliases: Some(BTreeMap::from([( + "numpy".to_string(), + "nmp".to_string(), + )])), + }, + ), + ..Settings::for_rule(CheckCode::ICN001) + }, + true, + )?; + checks.sort_by_key(|check| check.location); + insta::assert_yaml_snapshot!("override_default", checks); + Ok(()) + } +} diff --git a/src/flake8_import_conventions/settings.rs b/src/flake8_import_conventions/settings.rs new file mode 100644 index 00000000000000..db1517c4954584 --- /dev/null +++ b/src/flake8_import_conventions/settings.rs @@ -0,0 +1,84 @@ +//! Settings for import conventions. + +use std::collections::BTreeMap; + +use ruff_macros::ConfigurationOptions; +use serde::{Deserialize, Serialize}; + +const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[ + ("altair", "alt"), + ("matplotlib.pyplot", "plt"), + ("numpy", "np"), + ("pandas", "pd"), + ("seaborn", "sns"), +]; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +pub struct Options { + #[option( + doc = "The conventional aliases for imports. These aliases can be extended by the \ + `extend_aliases` option.", + default = r#"{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}"#, + value_type = "BTreeMap", + example = r#" + # Declare the default aliases. + altair = "alt" + matplotlib.pyplot = "plt" + numpy = "np" + pandas = "pd" + seaborn = "sns" + "# + )] + pub aliases: Option>, + #[option( + doc = "A mapping of modules to their conventional import aliases. These aliases will be \ + added to the `aliases` mapping.", + default = r#"{}"#, + value_type = "BTreeMap", + example = r#" + # Declare a custom alias for the `matplotlib` module. + "dask.dataframe" = "dd" + "# + )] + pub extend_aliases: Option>, +} + +#[derive(Debug, Hash)] +pub struct Settings { + pub aliases: BTreeMap, +} + +fn default_aliases() -> BTreeMap { + CONVENTIONAL_ALIASES + .iter() + .map(|(k, v)| ((*k).to_string(), (*v).to_string())) + .collect::>() +} + +fn resolve_aliases(options: Options) -> BTreeMap { + let mut aliases = match options.aliases { + Some(options_aliases) => options_aliases, + None => default_aliases(), + }; + if let Some(extend_aliases) = options.extend_aliases { + aliases.extend(extend_aliases); + } + aliases +} + +impl Settings { + pub fn from_options(options: Options) -> Self { + Self { + aliases: resolve_aliases(options), + } + } +} + +impl Default for Settings { + fn default() -> Self { + Self { + aliases: default_aliases(), + } + } +} diff --git a/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__custom.snap b/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__custom.snap new file mode 100644 index 00000000000000..2fe95293d490b4 --- /dev/null +++ b/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__custom.snap @@ -0,0 +1,159 @@ +--- +source: src/flake8_import_conventions/mod.rs +expression: checks +--- +- kind: + ImportAliasIsNotConventional: + - altair + - alt + location: + row: 3 + column: 0 + end_location: + row: 3 + column: 13 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - dask.array + - da + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 17 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - dask.dataframe + - dd + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 21 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - matplotlib.pyplot + - plt + location: + row: 6 + column: 0 + end_location: + row: 6 + column: 24 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - numpy + - np + location: + row: 7 + column: 0 + end_location: + row: 7 + column: 12 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - pandas + - pd + location: + row: 8 + column: 0 + end_location: + row: 8 + column: 13 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - seaborn + - sns + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 14 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - altair + - alt + location: + row: 11 + column: 0 + end_location: + row: 11 + column: 21 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - matplotlib.pyplot + - plt + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 32 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - dask.array + - da + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 27 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - dask.dataframe + - dd + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 28 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - numpy + - np + location: + row: 15 + column: 0 + end_location: + row: 15 + column: 19 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - pandas + - pd + location: + row: 16 + column: 0 + end_location: + row: 16 + column: 21 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - seaborn + - sns + location: + row: 17 + column: 0 + end_location: + row: 17 + column: 22 + fix: ~ + diff --git a/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__defaults.snap b/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__defaults.snap new file mode 100644 index 00000000000000..41154fb5d53316 --- /dev/null +++ b/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__defaults.snap @@ -0,0 +1,115 @@ +--- +source: src/flake8_import_conventions/mod.rs +expression: checks +--- +- kind: + ImportAliasIsNotConventional: + - altair + - alt + location: + row: 3 + column: 0 + end_location: + row: 3 + column: 13 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - matplotlib.pyplot + - plt + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 24 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - numpy + - np + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 12 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - pandas + - pd + location: + row: 6 + column: 0 + end_location: + row: 6 + column: 13 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - seaborn + - sns + location: + row: 7 + column: 0 + end_location: + row: 7 + column: 14 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - altair + - alt + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 21 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - matplotlib.pyplot + - plt + location: + row: 10 + column: 0 + end_location: + row: 10 + column: 32 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - numpy + - np + location: + row: 11 + column: 0 + end_location: + row: 11 + column: 19 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - pandas + - pd + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 21 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - seaborn + - sns + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 22 + fix: ~ + diff --git a/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__override_default.snap b/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__override_default.snap new file mode 100644 index 00000000000000..67244e8aa7c63d --- /dev/null +++ b/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__override_default.snap @@ -0,0 +1,115 @@ +--- +source: src/flake8_import_conventions/mod.rs +expression: checks +--- +- kind: + ImportAliasIsNotConventional: + - altair + - alt + location: + row: 3 + column: 0 + end_location: + row: 3 + column: 13 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - matplotlib.pyplot + - plt + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 24 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - numpy + - nmp + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 12 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - pandas + - pd + location: + row: 6 + column: 0 + end_location: + row: 6 + column: 13 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - seaborn + - sns + location: + row: 7 + column: 0 + end_location: + row: 7 + column: 14 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - altair + - alt + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 21 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - matplotlib.pyplot + - plt + location: + row: 10 + column: 0 + end_location: + row: 10 + column: 32 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - numpy + - nmp + location: + row: 11 + column: 0 + end_location: + row: 11 + column: 18 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - pandas + - pd + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 21 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - seaborn + - sns + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 22 + fix: ~ + diff --git a/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__remove_default.snap b/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__remove_default.snap new file mode 100644 index 00000000000000..efd7dff6cda435 --- /dev/null +++ b/src/flake8_import_conventions/snapshots/ruff__flake8_import_conventions__tests__remove_default.snap @@ -0,0 +1,93 @@ +--- +source: src/flake8_import_conventions/mod.rs +expression: checks +--- +- kind: + ImportAliasIsNotConventional: + - altair + - alt + location: + row: 3 + column: 0 + end_location: + row: 3 + column: 13 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - matplotlib.pyplot + - plt + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 24 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - pandas + - pd + location: + row: 6 + column: 0 + end_location: + row: 6 + column: 13 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - seaborn + - sns + location: + row: 7 + column: 0 + end_location: + row: 7 + column: 14 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - altair + - alt + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 21 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - matplotlib.pyplot + - plt + location: + row: 10 + column: 0 + end_location: + row: 10 + column: 32 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - pandas + - pd + location: + row: 12 + column: 0 + end_location: + row: 12 + column: 21 + fix: ~ +- kind: + ImportAliasIsNotConventional: + - seaborn + - sns + location: + row: 13 + column: 0 + end_location: + row: 13 + column: 22 + fix: ~ + diff --git a/src/lib.rs b/src/lib.rs index a0afae2ba18d16..3ed63d3c7c2496 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,7 @@ pub mod flake8_bugbear; mod flake8_builtins; mod flake8_comprehensions; mod flake8_debugger; +mod flake8_import_conventions; mod flake8_print; pub mod flake8_quotes; mod flake8_return; diff --git a/src/mccabe/settings.rs b/src/mccabe/settings.rs index a6ab8b1f9c5782..d00c607ce741d0 100644 --- a/src/mccabe/settings.rs +++ b/src/mccabe/settings.rs @@ -24,8 +24,7 @@ pub struct Settings { } impl Settings { - #[allow(clippy::needless_pass_by_value)] - pub fn from_options(options: Options) -> Self { + pub fn from_options(options: &Options) -> Self { Self { max_complexity: options.max_complexity.unwrap_or_default(), } diff --git a/src/settings/configuration.rs b/src/settings/configuration.rs index 55823e59855557..9fd867a5c39ada 100644 --- a/src/settings/configuration.rs +++ b/src/settings/configuration.rs @@ -14,8 +14,8 @@ use crate::checks_gen::{CheckCodePrefix, CATEGORIES}; use crate::settings::pyproject::load_options; use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat}; use crate::{ - flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe, - pep8_naming, pyupgrade, + flake8_annotations, flake8_bugbear, flake8_import_conventions, flake8_quotes, + flake8_tidy_imports, fs, isort, mccabe, pep8_naming, pyupgrade, }; #[derive(Debug)] @@ -42,6 +42,7 @@ pub struct Configuration { // Plugins pub flake8_annotations: flake8_annotations::settings::Settings, pub flake8_bugbear: flake8_bugbear::settings::Settings, + pub flake8_import_conventions: flake8_import_conventions::settings::Settings, pub flake8_quotes: flake8_quotes::settings::Settings, pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings, pub isort: isort::settings::Settings, @@ -152,6 +153,10 @@ impl Configuration { .flake8_bugbear .map(flake8_bugbear::settings::Settings::from_options) .unwrap_or_default(), + flake8_import_conventions: options + .flake8_import_conventions + .map(flake8_import_conventions::settings::Settings::from_options) + .unwrap_or_default(), flake8_quotes: options .flake8_quotes .map(flake8_quotes::settings::Settings::from_options) @@ -166,6 +171,7 @@ impl Configuration { .unwrap_or_default(), mccabe: options .mccabe + .as_ref() .map(mccabe::settings::Settings::from_options) .unwrap_or_default(), pep8_naming: options diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 96aef24a33b082..629ed8247de6b7 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -17,8 +17,8 @@ use crate::checks_gen::{CheckCodePrefix, SuffixLength}; use crate::settings::configuration::Configuration; use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat}; use crate::{ - flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe, - pep8_naming, pyupgrade, + flake8_annotations, flake8_bugbear, flake8_import_conventions, flake8_quotes, + flake8_tidy_imports, fs, isort, mccabe, pep8_naming, pyupgrade, }; pub mod configuration; @@ -46,6 +46,7 @@ pub struct Settings { // Plugins pub flake8_annotations: flake8_annotations::settings::Settings, pub flake8_bugbear: flake8_bugbear::settings::Settings, + pub flake8_import_conventions: flake8_import_conventions::settings::Settings, pub flake8_quotes: flake8_quotes::settings::Settings, pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings, pub isort: isort::settings::Settings, @@ -81,6 +82,7 @@ impl Settings { format: config.format, flake8_annotations: config.flake8_annotations, flake8_bugbear: config.flake8_bugbear, + flake8_import_conventions: config.flake8_import_conventions, flake8_quotes: config.flake8_quotes, flake8_tidy_imports: config.flake8_tidy_imports, ignore_init_module_imports: config.ignore_init_module_imports, @@ -114,6 +116,7 @@ impl Settings { target_version: PythonVersion::Py310, flake8_annotations: flake8_annotations::settings::Settings::default(), flake8_bugbear: flake8_bugbear::settings::Settings::default(), + flake8_import_conventions: flake8_import_conventions::settings::Settings::default(), flake8_quotes: flake8_quotes::settings::Settings::default(), flake8_tidy_imports: flake8_tidy_imports::settings::Settings::default(), isort: isort::settings::Settings::default(), @@ -141,6 +144,7 @@ impl Settings { target_version: PythonVersion::Py310, flake8_annotations: flake8_annotations::settings::Settings::default(), flake8_bugbear: flake8_bugbear::settings::Settings::default(), + flake8_import_conventions: flake8_import_conventions::settings::Settings::default(), flake8_quotes: flake8_quotes::settings::Settings::default(), flake8_tidy_imports: flake8_tidy_imports::settings::Settings::default(), isort: isort::settings::Settings::default(), @@ -176,6 +180,7 @@ impl Hash for Settings { // Add plugin properties in alphabetical order. self.flake8_annotations.hash(state); self.flake8_bugbear.hash(state); + self.flake8_import_conventions.hash(state); self.flake8_quotes.hash(state); self.flake8_tidy_imports.hash(state); self.isort.hash(state); diff --git a/src/settings/options.rs b/src/settings/options.rs index aabeb7a2c7a44a..f03485353cbb59 100644 --- a/src/settings/options.rs +++ b/src/settings/options.rs @@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize}; use crate::checks_gen::CheckCodePrefix; use crate::settings::types::{PythonVersion, SerializationFormat}; use crate::{ - flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, mccabe, - pep8_naming, pyupgrade, + flake8_annotations, flake8_bugbear, flake8_import_conventions, flake8_quotes, + flake8_tidy_imports, isort, mccabe, pep8_naming, pyupgrade, }; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)] @@ -261,6 +261,8 @@ pub struct Options { #[option_group] pub flake8_tidy_imports: Option, #[option_group] + pub flake8_import_conventions: Option, + #[option_group] pub isort: Option, #[option_group] pub mccabe: Option, diff --git a/src/settings/pyproject.rs b/src/settings/pyproject.rs index a136e9dad442c1..0b2dead26b3877 100644 --- a/src/settings/pyproject.rs +++ b/src/settings/pyproject.rs @@ -96,6 +96,7 @@ pub fn load_options(pyproject: Option<&PathBuf>) -> Result { #[cfg(test)] mod tests { + use std::collections::BTreeMap; use std::env::current_dir; use std::path::PathBuf; use std::str::FromStr; @@ -110,7 +111,10 @@ mod tests { find_project_root, find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools, }; use crate::settings::types::PatternPrefixPair; - use crate::{flake8_bugbear, flake8_quotes, flake8_tidy_imports, mccabe, pep8_naming}; + use crate::{ + flake8_bugbear, flake8_import_conventions, flake8_quotes, flake8_tidy_imports, mccabe, + pep8_naming, + }; #[test] fn deserialize() -> Result<()> { @@ -157,6 +161,7 @@ mod tests { flake8_bugbear: None, flake8_quotes: None, flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -199,6 +204,7 @@ line-length = 79 flake8_bugbear: None, flake8_quotes: None, flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -241,6 +247,7 @@ exclude = ["foo.py"] flake8_bugbear: None, flake8_quotes: None, flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -283,6 +290,7 @@ select = ["E501"] flake8_bugbear: None, flake8_quotes: None, flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -326,6 +334,7 @@ ignore = ["E501"] flake8_bugbear: None, flake8_quotes: None, flake8_tidy_imports: None, + flake8_import_conventions: None, isort: None, mccabe: None, pep8_naming: None, @@ -422,6 +431,13 @@ other-attribute = 1 flake8_tidy_imports: Some(flake8_tidy_imports::settings::Options { ban_relative_imports: Some(Strictness::Parents) }), + flake8_import_conventions: Some(flake8_import_conventions::settings::Options { + aliases: Some(BTreeMap::from([("pandas".to_string(), "pd".to_string(),)])), + extend_aliases: Some(BTreeMap::from([( + "dask.dataframe".to_string(), + "dd".to_string(), + )])), + }), isort: None, mccabe: Some(mccabe::settings::Options { max_complexity: Some(10),