mirror of https://github.com/astral-sh/ruff
[ty] Make auto-import ignore symbols in modules starting with a `_`
This applies recursively. So if *any* component of a module name starts with a `_`, then symbols from that module are excluded from auto-import. The exception is when it's a module within first party code. Then we want to include it in auto-import.
This commit is contained in:
parent
2a38395bc8
commit
32f400a457
|
|
@ -36,6 +36,20 @@ pub fn all_symbols<'db>(
|
||||||
let Some(file) = module.file(&*db) else {
|
let Some(file) = module.file(&*db) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
// By convention, modules starting with an underscore
|
||||||
|
// are generally considered unexported. However, we
|
||||||
|
// should consider first party modules fair game.
|
||||||
|
//
|
||||||
|
// Note that we apply this recursively. e.g.,
|
||||||
|
// `numpy._core.multiarray` is considered private
|
||||||
|
// because it's a child of `_core`.
|
||||||
|
if module.name(&*db).components().any(|c| c.starts_with('_'))
|
||||||
|
&& module
|
||||||
|
.search_path(&*db)
|
||||||
|
.is_none_or(|sp| !sp.is_first_party())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// TODO: also make it available in `TYPE_CHECKING` blocks
|
// TODO: also make it available in `TYPE_CHECKING` blocks
|
||||||
// (we'd need https://github.com/astral-sh/ty/issues/1553 to do this well)
|
// (we'd need https://github.com/astral-sh/ty/issues/1553 to do this well)
|
||||||
if !is_typing_extensions_available && module.name(&*db) == &typing_extensions {
|
if !is_typing_extensions_available && module.name(&*db) == &typing_extensions {
|
||||||
|
|
|
||||||
|
|
@ -5979,6 +5979,94 @@ ZQ<CURSOR>
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test confirms current behavior (as of 2025-12-04), but
|
||||||
|
// it's not consistent with auto-import. That is, it doesn't
|
||||||
|
// strictly respect `__all__` on `bar`, but perhaps it should.
|
||||||
|
//
|
||||||
|
// See: https://github.com/astral-sh/ty/issues/1757
|
||||||
|
#[test]
|
||||||
|
fn object_attr_ignores_all() {
|
||||||
|
let snapshot = CursorTest::builder()
|
||||||
|
.source(
|
||||||
|
"main.py",
|
||||||
|
r#"
|
||||||
|
import bar
|
||||||
|
bar.ZQ<CURSOR>
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.source(
|
||||||
|
"bar.py",
|
||||||
|
r#"
|
||||||
|
ZQZQ1 = 1
|
||||||
|
ZQZQ2 = 1
|
||||||
|
__all__ = ['ZQZQ1']
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.completion_test_builder()
|
||||||
|
.auto_import()
|
||||||
|
.module_names()
|
||||||
|
.build()
|
||||||
|
.snapshot();
|
||||||
|
// We specifically do not want `ZQZQ2` here, since
|
||||||
|
// it is not part of `__all__`.
|
||||||
|
assert_snapshot!(snapshot, @r"
|
||||||
|
ZQZQ1 :: <no import required>
|
||||||
|
ZQZQ2 :: <no import required>
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_import_ignores_modules_with_leading_underscore() {
|
||||||
|
let snapshot = CursorTest::builder()
|
||||||
|
.source(
|
||||||
|
"main.py",
|
||||||
|
r#"
|
||||||
|
Quitter<CURSOR>
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.completion_test_builder()
|
||||||
|
.auto_import()
|
||||||
|
.module_names()
|
||||||
|
.build()
|
||||||
|
.snapshot();
|
||||||
|
// There is a `Quitter` in `_sitebuiltins` in the standard
|
||||||
|
// library. But this is skipped by auto-import because it's
|
||||||
|
// 1) not first party and 2) starts with an `_`.
|
||||||
|
assert_snapshot!(snapshot, @"<No completions found>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auto_import_includes_modules_with_leading_underscore_in_first_party() {
|
||||||
|
let snapshot = CursorTest::builder()
|
||||||
|
.source(
|
||||||
|
"main.py",
|
||||||
|
r#"
|
||||||
|
ZQ<CURSOR>
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.source(
|
||||||
|
"bar.py",
|
||||||
|
r#"
|
||||||
|
ZQZQ1 = 1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.source(
|
||||||
|
"_foo.py",
|
||||||
|
r#"
|
||||||
|
ZQZQ1 = 1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.completion_test_builder()
|
||||||
|
.auto_import()
|
||||||
|
.module_names()
|
||||||
|
.build()
|
||||||
|
.snapshot();
|
||||||
|
assert_snapshot!(snapshot, @r"
|
||||||
|
ZQZQ1 :: _foo
|
||||||
|
ZQZQ1 :: bar
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
/// A way to create a simple single-file (named `main.py`) completion test
|
/// A way to create a simple single-file (named `main.py`) completion test
|
||||||
/// builder.
|
/// builder.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -594,7 +594,7 @@ impl SearchPath {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_first_party(&self) -> bool {
|
pub fn is_first_party(&self) -> bool {
|
||||||
matches!(&*self.0, SearchPathInner::FirstParty(_))
|
matches!(&*self.0, SearchPathInner::FirstParty(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ expression: completions
|
||||||
{
|
{
|
||||||
"label": "Literal (import typing)",
|
"label": "Literal (import typing)",
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"sortText": " 58",
|
"sortText": " 35",
|
||||||
"insertText": "Literal",
|
"insertText": "Literal",
|
||||||
"additionalTextEdits": [
|
"additionalTextEdits": [
|
||||||
{
|
{
|
||||||
|
|
@ -27,7 +27,7 @@ expression: completions
|
||||||
{
|
{
|
||||||
"label": "LiteralString (import typing)",
|
"label": "LiteralString (import typing)",
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"sortText": " 59",
|
"sortText": " 36",
|
||||||
"insertText": "LiteralString",
|
"insertText": "LiteralString",
|
||||||
"additionalTextEdits": [
|
"additionalTextEdits": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ expression: completions
|
||||||
{
|
{
|
||||||
"label": "Literal (import typing)",
|
"label": "Literal (import typing)",
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"sortText": " 58",
|
"sortText": " 35",
|
||||||
"insertText": "Literal",
|
"insertText": "Literal",
|
||||||
"additionalTextEdits": [
|
"additionalTextEdits": [
|
||||||
{
|
{
|
||||||
|
|
@ -27,7 +27,7 @@ expression: completions
|
||||||
{
|
{
|
||||||
"label": "LiteralString (import typing)",
|
"label": "LiteralString (import typing)",
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"sortText": " 59",
|
"sortText": " 36",
|
||||||
"insertText": "LiteralString",
|
"insertText": "LiteralString",
|
||||||
"additionalTextEdits": [
|
"additionalTextEdits": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ expression: completions
|
||||||
{
|
{
|
||||||
"label": "Literal (import typing)",
|
"label": "Literal (import typing)",
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"sortText": " 58",
|
"sortText": " 35",
|
||||||
"insertText": "Literal",
|
"insertText": "Literal",
|
||||||
"additionalTextEdits": [
|
"additionalTextEdits": [
|
||||||
{
|
{
|
||||||
|
|
@ -27,7 +27,7 @@ expression: completions
|
||||||
{
|
{
|
||||||
"label": "LiteralString (import typing)",
|
"label": "LiteralString (import typing)",
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"sortText": " 59",
|
"sortText": " 36",
|
||||||
"insertText": "LiteralString",
|
"insertText": "LiteralString",
|
||||||
"additionalTextEdits": [
|
"additionalTextEdits": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ expression: completions
|
||||||
{
|
{
|
||||||
"label": "Literal (import typing)",
|
"label": "Literal (import typing)",
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"sortText": " 58",
|
"sortText": " 35",
|
||||||
"insertText": "Literal",
|
"insertText": "Literal",
|
||||||
"additionalTextEdits": [
|
"additionalTextEdits": [
|
||||||
{
|
{
|
||||||
|
|
@ -27,7 +27,7 @@ expression: completions
|
||||||
{
|
{
|
||||||
"label": "LiteralString (import typing)",
|
"label": "LiteralString (import typing)",
|
||||||
"kind": 6,
|
"kind": 6,
|
||||||
"sortText": " 59",
|
"sortText": " 36",
|
||||||
"insertText": "LiteralString",
|
"insertText": "LiteralString",
|
||||||
"additionalTextEdits": [
|
"additionalTextEdits": [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue