mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-17 19:27:11 +00:00
[ty] suppress invalid suggestions in import statements (#21484)
<!-- Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull requests.) - Does this pull request include references to any relevant issues? --> ## Summary <!-- What's the purpose of the change? What does it do, and why? --> Partially addresses https://github.com/astral-sh/ty/issues/1562 Only suggest the keyword "as" in import statements when the user have written `import foo a<CURSOR>` or `from foo import bar a<CURSOR>` as no other suggestion makes sense here. Re-uses the existing pattern for incomplete `import from` statements to determine incomplete import alias statements and make the suggestions more sane in those cases. There was a potential suggestion from @BurntSushi in https://github.com/astral-sh/ty/issues/1562#issue-3626853513 to move the handling of import statements into one unified state machine but I acted on the side of caution and fixed this with already established patterns, pending a potential bigger re-write down the line. ## Test Plan Added new tests and checked that it behaved reasonable in the playground. <!-- How was it tested? -->
This commit is contained in:
parent
c16ef709f6
commit
d063c71177
1 changed files with 118 additions and 0 deletions
|
|
@ -358,6 +358,9 @@ fn only_keyword_completion<'db>(tokens: &[Token], typed: Option<&str>) -> Option
|
|||
if is_import_from_incomplete(tokens, typed) {
|
||||
return Some(Completion::keyword("import"));
|
||||
}
|
||||
if is_import_alias_incomplete(tokens, typed) {
|
||||
return Some(Completion::keyword("as"));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
|
@ -914,6 +917,59 @@ fn is_import_from_incomplete(tokens: &[Token], typed: Option<&str>) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
/// Detects `import <name> <CURSOR>` statements with a potentially incomplete
|
||||
/// `as` clause.
|
||||
///
|
||||
/// Note that this works for `from <module> import <name> <CURSOR>` as well.
|
||||
///
|
||||
/// If found, `true` is returned.
|
||||
fn is_import_alias_incomplete(tokens: &[Token], typed: Option<&str>) -> bool {
|
||||
use TokenKind as TK;
|
||||
|
||||
const LIMIT: usize = 1_000;
|
||||
|
||||
/// A state used to "parse" the tokens preceding the user's cursor,
|
||||
/// in reverse, to detect a "import <name> as" statement.
|
||||
enum S {
|
||||
Start,
|
||||
As,
|
||||
Name,
|
||||
}
|
||||
|
||||
if typed.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut state = S::Start;
|
||||
for token in tokens.iter().rev().take(LIMIT) {
|
||||
state = match (state, token.kind()) {
|
||||
(S::Start, TK::Name | TK::Unknown | TK::As) => S::As,
|
||||
(S::As, TK::Name | TK::Case | TK::Match | TK::Type | TK::Unknown) => S::Name,
|
||||
(
|
||||
S::Name,
|
||||
TK::Name
|
||||
| TK::Dot
|
||||
| TK::Ellipsis
|
||||
| TK::Case
|
||||
| TK::Match
|
||||
| TK::Type
|
||||
| TK::Unknown
|
||||
| TK::Comma
|
||||
| TK::As
|
||||
| TK::Newline
|
||||
| TK::NonLogicalNewline
|
||||
| TK::Lpar
|
||||
| TK::Rpar,
|
||||
) => S::Name,
|
||||
// Once we reach the `import` token we know we're in
|
||||
// `import name <CURSOR>`.
|
||||
(S::Name, TK::Import) => return true,
|
||||
_ => return false,
|
||||
};
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Looks for the text typed immediately before the cursor offset
|
||||
/// given.
|
||||
///
|
||||
|
|
@ -4690,6 +4746,68 @@ from collections import defaultdict as f<CURSOR>
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_missing_alias_suggests_as() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
import collections a<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(builder.build().snapshot(), @"as");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_dotted_module_missing_alias_suggests_as() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
import collections.abc a<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(builder.build().snapshot(), @"as");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_multiple_modules_missing_alias_suggests_as() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
import collections.abc as c, typing a<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(builder.build().snapshot(), @"as");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_import_missing_alias_suggests_as() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
from collections.abc import Mapping a<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(builder.build().snapshot(), @"as");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_import_parenthesized_missing_alias_suggests_as() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
from typing import (
|
||||
NamedTuple a<CURSOR>
|
||||
)
|
||||
",
|
||||
);
|
||||
assert_snapshot!(builder.build().snapshot(), @"as");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_relative_import_missing_alias_suggests_as() {
|
||||
let builder = completion_test_builder(
|
||||
"\
|
||||
from ...foo import bar a<CURSOR>
|
||||
",
|
||||
);
|
||||
assert_snapshot!(builder.build().snapshot(), @"as");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_completions_in_with_alias() {
|
||||
let builder = completion_test_builder(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue