diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/__init__.py b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/direct.py b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/direct.py new file mode 100644 index 0000000000000..a56f27fe63323 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/direct.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from collections.abc import Sequence + + +class MyBaseClass: + pass + + +class Foo(MyBaseClass): + foo: Sequence diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/import.py b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/import.py new file mode 100644 index 0000000000000..5189bc49687b4 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/import.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from collections.abc import Sequence + +from module.direct import MyBaseClass + + +class Foo(MyBaseClass): + foo: Sequence diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/undefined.py b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/undefined.py new file mode 100644 index 0000000000000..657a3c35b491c --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/module/undefined.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from collections.abc import Sequence + + +class Foo(MyBaseClass): + foo: Sequence diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/runtime_evaluated_base_classes_5.py b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/runtime_evaluated_base_classes_5.py new file mode 100644 index 0000000000000..0f2ad5bdf7600 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_type_checking/runtime_evaluated_base_classes_5.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from collections.abc import Sequence # TCH003 + + +class MyBaseClass: + pass + + +class Foo(MyBaseClass): + foo: Sequence diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs b/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs index 8bda8ec66dd6f..cbea3cfacf60f 100644 --- a/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_type_checking/mod.rs @@ -144,6 +144,28 @@ mod tests { Ok(()) } + #[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("module/direct.py"))] + #[test_case(Rule::TypingOnlyStandardLibraryImport, Path::new("module/import.py"))] + #[test_case( + Rule::TypingOnlyStandardLibraryImport, + Path::new("module/undefined.py") + )] + fn base_class_same_file(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy()); + let diagnostics = test_path( + Path::new("flake8_type_checking").join(path).as_path(), + &settings::LinterSettings { + flake8_type_checking: super::settings::Settings { + runtime_evaluated_base_classes: vec!["module.direct.MyBaseClass".to_string()], + ..Default::default() + }, + ..settings::LinterSettings::for_rule(rule_code) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } + #[test_case( r#" from __future__ import annotations diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_module__direct.py.snap b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_module__direct.py.snap new file mode 100644 index 0000000000000..6c5ead27428ce --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_module__direct.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_module__import.py.snap b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_module__import.py.snap new file mode 100644 index 0000000000000..6c5ead27428ce --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_module__import.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs +--- + diff --git a/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_module__undefined.py.snap b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_module__undefined.py.snap new file mode 100644 index 0000000000000..b7f049eaa86ff --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_type_checking/snapshots/ruff_linter__rules__flake8_type_checking__tests__typing-only-standard-library-import_module__undefined.py.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs +--- +undefined.py:3:29: TCH003 [*] Move standard library import `collections.abc.Sequence` into a type-checking block + | +1 | from __future__ import annotations +2 | +3 | from collections.abc import Sequence + | ^^^^^^^^ TCH003 + | + = help: Move into type-checking block + +ℹ Unsafe fix +1 1 | from __future__ import annotations +2 2 | +3 |-from collections.abc import Sequence + 3 |+from typing import TYPE_CHECKING + 4 |+ + 5 |+if TYPE_CHECKING: + 6 |+ from collections.abc import Sequence +4 7 | +5 8 | +6 9 | class Foo(MyBaseClass): + + diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 48006dba9e2c3..28b09e653fd0c 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -686,6 +686,16 @@ impl<'a> SemanticModel<'a> { Some(resolved) } BindingKind::Builtin => Some(smallvec!["", head.id.as_str()]), + BindingKind::ClassDefinition(_) | BindingKind::FunctionDefinition(_) => { + let value_path = collect_call_path(value)?; + let resolved: CallPath = self + .module_path? + .iter() + .map(String::as_str) + .chain(value_path) + .collect(); + Some(resolved) + } _ => None, } }