Implement exempt-modules setting from flake8-type-checking (#2230)

This commit is contained in:
Charlie Marsh
2023-01-26 16:55:32 -05:00
committed by GitHub
parent 291239b9f1
commit 4d52ea87ef
7 changed files with 123 additions and 1 deletions

View File

@@ -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

View 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

View File

@@ -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": [

View File

@@ -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)
},
)?;

View File

@@ -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));

View File

@@ -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),
}
}
}

View File

@@ -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: ~