Skip to content

Commit

Permalink
Add pyflakes.extend-generics setting (#4677)
Browse files Browse the repository at this point in the history
  • Loading branch information
JonathanPlasse authored and konstin committed Jun 13, 2023
1 parent c8166f2 commit e2ac0cd
Show file tree
Hide file tree
Showing 14 changed files with 156 additions and 8 deletions.
9 changes: 9 additions & 0 deletions crates/ruff/resources/test/fixtures/pyflakes/F401_15.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from typing import TYPE_CHECKING
from django.db.models import ForeignKey

if TYPE_CHECKING:
from pathlib import Path


class Foo:
var = ForeignKey["Path"]()
1 change: 1 addition & 0 deletions crates/ruff/src/checkers/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3884,6 +3884,7 @@ where
value,
&self.semantic_model,
self.settings.typing_modules.iter().map(String::as_str),
&self.settings.pyflakes.extend_generics,
) {
Some(subscript) => {
match subscript {
Expand Down
20 changes: 19 additions & 1 deletion crates/ruff/src/rules/pyflakes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub(crate) mod cformat;
pub(crate) mod fixes;
pub(crate) mod format;
pub(crate) mod rules;
pub mod settings;

#[cfg(test)]
mod tests {
Expand All @@ -19,7 +20,7 @@ mod tests {

use crate::linter::{check_path, LinterResult};
use crate::registry::{AsRule, Linter, Rule};
use crate::settings::flags;
use crate::settings::{flags, Settings};
use crate::test::test_path;
use crate::{assert_messages, directives, settings};

Expand All @@ -38,6 +39,7 @@ mod tests {
#[test_case(Rule::UnusedImport, Path::new("F401_12.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_13.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_14.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_15.py"))]
#[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.py"))]
#[test_case(Rule::UndefinedLocalWithImportStar, Path::new("F403.py"))]
#[test_case(Rule::LateFutureImport, Path::new("F404.py"))]
Expand Down Expand Up @@ -252,6 +254,22 @@ mod tests {
Ok(())
}

#[test]
fn extend_generics() -> Result<()> {
let snapshot = "extend_immutable_calls".to_string();
let diagnostics = test_path(
Path::new("pyflakes/F401_15.py"),
&Settings {
pyflakes: super::settings::Settings {
extend_generics: vec!["django.db.models.ForeignKey".to_string()],
},
..Settings::for_rules(vec![Rule::UnusedImport])
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}

/// A re-implementation of the Pyflakes test runner.
/// Note that all tests marked with `#[ignore]` should be considered TODOs.
fn flakes(contents: &str, expected: &[Rule]) {
Expand Down
4 changes: 4 additions & 0 deletions crates/ruff/src/rules/pyflakes/rules/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub(crate) enum UnusedImportContext {
/// If an import statement is used to check for the availability or existence
/// of a module, consider using `importlib.util.find_spec` instead.
///
/// ## Options
///
/// - `pyflakes.extend-generics`
///
/// ## Example
/// ```python
/// import numpy as np # unused import
Expand Down
47 changes: 47 additions & 0 deletions crates/ruff/src/rules/pyflakes/settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Settings for the `Pyflakes` plugin.
use serde::{Deserialize, Serialize};

use ruff_macros::{CacheKey, CombineOptions, ConfigurationOptions};

#[derive(
Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions, CombineOptions,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "PyflakesOptions"
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Options {
#[option(
default = r#"[]"#,
value_type = "list[str]",
example = "extend-generics = [\"django.db.models.ForeignKey\"]"
)]
/// Additional functions or classes to consider generic, such that any
/// subscripts should be treated as type annotation (e.g., `ForeignKey` in
/// `django.db.models.ForeignKey["User"]`.
pub extend_generics: Option<Vec<String>>,
}

#[derive(Debug, Default, CacheKey)]
pub struct Settings {
pub extend_generics: Vec<String>,
}

impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
extend_generics: options.extend_generics.unwrap_or_default(),
}
}
}

impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
extend_generics: Some(settings.extend_generics),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
---
F401_15.py:5:25: F401 [*] `pathlib.Path` imported but unused
|
5 | if TYPE_CHECKING:
6 | from pathlib import Path
| ^^^^ F401
|
= help: Remove unused import: `pathlib.Path`

Suggested fix
2 2 | from django.db.models import ForeignKey
3 3 |
4 4 | if TYPE_CHECKING:
5 |- from pathlib import Path
5 |+ pass
6 6 |
7 7 |
8 8 | class Foo:


Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/pyflakes/mod.rs
---

5 changes: 4 additions & 1 deletion crates/ruff/src/settings/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint,
};
use crate::settings::options::Options;
use crate::settings::types::{
Expand Down Expand Up @@ -89,6 +89,7 @@ pub struct Configuration {
pub pep8_naming: Option<pep8_naming::settings::Options>,
pub pycodestyle: Option<pycodestyle::settings::Options>,
pub pydocstyle: Option<pydocstyle::settings::Options>,
pub pyflakes: Option<pyflakes::settings::Options>,
pub pylint: Option<pylint::settings::Options>,
}

Expand Down Expand Up @@ -241,6 +242,7 @@ impl Configuration {
pep8_naming: options.pep8_naming,
pycodestyle: options.pycodestyle,
pydocstyle: options.pydocstyle,
pyflakes: options.pyflakes,
pylint: options.pylint,
})
}
Expand Down Expand Up @@ -326,6 +328,7 @@ impl Configuration {
pep8_naming: self.pep8_naming.combine(config.pep8_naming),
pycodestyle: self.pycodestyle.combine(config.pycodestyle),
pydocstyle: self.pydocstyle.combine(config.pydocstyle),
pyflakes: self.pyflakes.combine(config.pyflakes),
pylint: self.pylint.combine(config.pylint),
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/ruff/src/settings/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint,
};
use crate::settings::types::FilePatternSet;

Expand Down Expand Up @@ -110,6 +110,7 @@ impl Default for Settings {
pep8_naming: pep8_naming::settings::Settings::default(),
pycodestyle: pycodestyle::settings::Settings::default(),
pydocstyle: pydocstyle::settings::Settings::default(),
pyflakes: pyflakes::settings::Settings::default(),
pylint: pylint::settings::Settings::default(),
}
}
Expand Down
4 changes: 3 additions & 1 deletion crates/ruff/src/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint,
};
use crate::settings::configuration::Configuration;
use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion, SerializationFormat};
Expand Down Expand Up @@ -126,6 +126,7 @@ pub struct Settings {
pub pep8_naming: pep8_naming::settings::Settings,
pub pycodestyle: pycodestyle::settings::Settings,
pub pydocstyle: pydocstyle::settings::Settings,
pub pyflakes: pyflakes::settings::Settings,
pub pylint: pylint::settings::Settings,
}

Expand Down Expand Up @@ -231,6 +232,7 @@ impl Settings {
pep8_naming: config.pep8_naming.map(Into::into).unwrap_or_default(),
pycodestyle: config.pycodestyle.map(Into::into).unwrap_or_default(),
pydocstyle: config.pydocstyle.map(Into::into).unwrap_or_default(),
pyflakes: config.pyflakes.map(Into::into).unwrap_or_default(),
pylint: config.pylint.map(Into::into).unwrap_or_default(),
})
}
Expand Down
5 changes: 4 additions & 1 deletion crates/ruff/src/settings/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint,
};
use crate::settings::types::{PythonVersion, SerializationFormat, Version};

Expand Down Expand Up @@ -539,6 +539,9 @@ pub struct Options {
/// Options for the `pydocstyle` plugin.
pub pydocstyle: Option<pydocstyle::settings::Options>,
#[option_group]
/// Options for the `pyflakes` plugin.
pub pyflakes: Option<pyflakes::settings::Options>,
#[option_group]
/// Options for the `pylint` plugin.
pub pylint: Option<pylint::settings::Options>,
// Tables are required to go last.
Expand Down
10 changes: 8 additions & 2 deletions crates/ruff_python_semantic/src/analyze/typing.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use rustpython_parser::ast::{self, Constant, Expr, Operator};

use num_traits::identities::Zero;
use ruff_python_ast::call_path::{from_unqualified_name, CallPath};
use ruff_python_ast::call_path::{from_qualified_name, from_unqualified_name, CallPath};
use ruff_python_stdlib::typing::{
IMMUTABLE_GENERIC_TYPES, IMMUTABLE_TYPES, PEP_585_GENERICS, PEP_593_SUBSCRIPTS, SUBSCRIPTS,
};
Expand Down Expand Up @@ -29,6 +29,7 @@ pub fn match_annotated_subscript<'a>(
expr: &Expr,
semantic_model: &SemanticModel,
typing_modules: impl Iterator<Item = &'a str>,
extend_generics: &[String],
) -> Option<SubscriptKind> {
if !matches!(expr, Expr::Name(_) | Expr::Attribute(_)) {
return None;
Expand All @@ -37,7 +38,12 @@ pub fn match_annotated_subscript<'a>(
semantic_model
.resolve_call_path(expr)
.and_then(|call_path| {
if SUBSCRIPTS.contains(&call_path.as_slice()) {
if SUBSCRIPTS.contains(&call_path.as_slice())
|| extend_generics
.iter()
.map(|target| from_qualified_name(target))
.any(|target| call_path == target)
{
return Some(SubscriptKind::AnnotatedSubscript);
}
if PEP_593_SUBSCRIPTS.contains(&call_path.as_slice()) {
Expand Down
3 changes: 2 additions & 1 deletion crates/ruff_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use ruff::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint,
};
use ruff::settings::configuration::Configuration;
use ruff::settings::options::Options;
Expand Down Expand Up @@ -158,6 +158,7 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
pep8_naming: Some(pep8_naming::settings::Settings::default().into()),
pycodestyle: Some(pycodestyle::settings::Settings::default().into()),
pydocstyle: Some(pydocstyle::settings::Settings::default().into()),
pyflakes: Some(pyflakes::settings::Settings::default().into()),
pylint: Some(pylint::settings::Settings::default().into()),
})?)
}
Expand Down
27 changes: 27 additions & 0 deletions ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e2ac0cd

Please sign in to comment.