Implement exempt-modules setting from flake8-type-checking (#2230)
This commit is contained in:
18
README.md
18
README.md
@@ -3108,6 +3108,24 @@ and can be circumvented via `eval` or `importlib`.
|
||||
|
||||
### `flake8-type-checking`
|
||||
|
||||
#### [`exempt-modules`](#exempt-modules)
|
||||
|
||||
Exempt certain modules from needing to be moved into type-checking
|
||||
blocks.
|
||||
|
||||
**Default value**: `[]`
|
||||
|
||||
**Type**: `Vec<String>`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-type-checking]
|
||||
exempt-modules = ["typing_extensions"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`strict`](#strict)
|
||||
|
||||
Enforce TC001, TC002, and TC003 rules even when valid runtime imports
|
||||
|
||||
16
resources/test/fixtures/flake8_type_checking/exempt_modules.py
vendored
Normal file
16
resources/test/fixtures/flake8_type_checking/exempt_modules.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
def f():
|
||||
import pandas as pd
|
||||
|
||||
x: pd.DataFrame
|
||||
|
||||
|
||||
def f():
|
||||
import pandas.core.frame as pd
|
||||
|
||||
x: pd.DataFrame
|
||||
|
||||
|
||||
def f():
|
||||
import flask
|
||||
|
||||
x: flask
|
||||
@@ -836,6 +836,16 @@
|
||||
"Flake8TypeCheckingOptions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exempt-modules": {
|
||||
"description": "Exempt certain modules from needing to be moved into type-checking blocks.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"strict": {
|
||||
"description": "Enforce TC001, TC002, and TC003 rules even when valid runtime imports are present for the same module. See: https://github.com/snok/flake8-type-checking#strict.",
|
||||
"type": [
|
||||
|
||||
@@ -47,7 +47,28 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings {
|
||||
flake8_type_checking: super::settings::Settings { strict: true },
|
||||
flake8_type_checking: super::settings::Settings {
|
||||
strict: true,
|
||||
..Default::default()
|
||||
},
|
||||
..settings::Settings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_yaml_snapshot!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("exempt_modules.py"); "exempt_modules")]
|
||||
fn exempt_modules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_type_checking")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings {
|
||||
flake8_type_checking: super::settings::Settings {
|
||||
exempt_modules: vec!["pandas".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..settings::Settings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -95,6 +95,22 @@ fn is_implicit_import(this: &Binding, that: &Binding) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if `name` is exempt from typing-only enforcement.
|
||||
fn is_exempt(name: &str, exempt_modules: &[&str]) -> bool {
|
||||
let mut name = name;
|
||||
loop {
|
||||
if exempt_modules.contains(&name) {
|
||||
return true;
|
||||
}
|
||||
match name.rfind('.') {
|
||||
Some(idx) => {
|
||||
name = &name[..idx];
|
||||
}
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TCH001
|
||||
pub fn typing_only_runtime_import(
|
||||
binding: &Binding,
|
||||
@@ -120,6 +136,18 @@ pub fn typing_only_runtime_import(
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if is_exempt(
|
||||
full_name,
|
||||
&settings
|
||||
.flake8_type_checking
|
||||
.exempt_modules
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.collect::<Vec<_>>(),
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let defined_in_type_checking = blocks
|
||||
.iter()
|
||||
.any(|block| Range::from_located(block).contains(&binding.range));
|
||||
|
||||
@@ -24,17 +24,29 @@ pub struct Options {
|
||||
/// are present for the same module.
|
||||
/// See: https://github.com/snok/flake8-type-checking#strict.
|
||||
pub strict: Option<bool>,
|
||||
#[option(
|
||||
default = "[]",
|
||||
value_type = "Vec<String>",
|
||||
example = r#"
|
||||
exempt-modules = ["typing_extensions"]
|
||||
"#
|
||||
)]
|
||||
/// Exempt certain modules from needing to be moved into type-checking
|
||||
/// blocks.
|
||||
pub exempt_modules: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Default)]
|
||||
pub struct Settings {
|
||||
pub strict: bool,
|
||||
pub exempt_modules: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<Options> for Settings {
|
||||
fn from(options: Options) -> Self {
|
||||
Self {
|
||||
strict: options.strict.unwrap_or_default(),
|
||||
exempt_modules: options.exempt_modules.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +55,7 @@ impl From<Settings> for Options {
|
||||
fn from(settings: Settings) -> Self {
|
||||
Self {
|
||||
strict: Some(settings.strict),
|
||||
exempt_modules: Some(settings.exempt_modules),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
source: src/rules/flake8_type_checking/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
TypingOnlyThirdPartyImport:
|
||||
full_name: flask
|
||||
location:
|
||||
row: 14
|
||||
column: 11
|
||||
end_location:
|
||||
row: 14
|
||||
column: 16
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
Reference in New Issue
Block a user