mirror of
https://github.com/astral-sh/ruff.git
synced 2025-12-10 12:10:32 +00:00
[ty] Respect notebook cell boundaries when adding an auto import (#21322)
This commit is contained in:
parent
d49c326309
commit
f9cc26aa12
10 changed files with 637 additions and 23 deletions
|
|
@ -1,5 +1,7 @@
|
|||
use insta::assert_json_snapshot;
|
||||
use lsp_types::{NotebookCellKind, Position, Range};
|
||||
use lsp_types::{CompletionResponse, CompletionTriggerKind, NotebookCellKind, Position, Range};
|
||||
use ruff_db::system::SystemPath;
|
||||
use ty_server::ClientOptions;
|
||||
|
||||
use crate::{TestServer, TestServerBuilder};
|
||||
|
||||
|
|
@ -276,6 +278,142 @@ fn swap_cells() -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import() -> anyhow::Result<()> {
|
||||
let mut server = TestServerBuilder::new()?
|
||||
.with_workspace(
|
||||
SystemPath::new("src"),
|
||||
Some(ClientOptions::default().with_experimental_auto_import(true)),
|
||||
)?
|
||||
.build()?
|
||||
.wait_until_workspaces_are_initialized()?;
|
||||
|
||||
server.initialization_result().unwrap();
|
||||
|
||||
let mut builder = NotebookBuilder::virtual_file("src/test.ipynb");
|
||||
|
||||
builder.add_python_cell(
|
||||
r#"from typing import TYPE_CHECKING
|
||||
"#,
|
||||
);
|
||||
|
||||
let second_cell = builder.add_python_cell(
|
||||
r#"# leading comment
|
||||
b: Litera
|
||||
"#,
|
||||
);
|
||||
|
||||
builder.open(&mut server);
|
||||
|
||||
server.collect_publish_diagnostic_notifications(2)?;
|
||||
|
||||
let completions = literal_completions(&mut server, &second_cell, Position::new(1, 9))?;
|
||||
|
||||
assert_json_snapshot!(completions);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import_same_cell() -> anyhow::Result<()> {
|
||||
let mut server = TestServerBuilder::new()?
|
||||
.with_workspace(
|
||||
SystemPath::new("src"),
|
||||
Some(ClientOptions::default().with_experimental_auto_import(true)),
|
||||
)?
|
||||
.build()?
|
||||
.wait_until_workspaces_are_initialized()?;
|
||||
|
||||
server.initialization_result().unwrap();
|
||||
|
||||
let mut builder = NotebookBuilder::virtual_file("src/test.ipynb");
|
||||
|
||||
let first_cell = builder.add_python_cell(
|
||||
r#"from typing import TYPE_CHECKING
|
||||
b: Litera
|
||||
"#,
|
||||
);
|
||||
|
||||
builder.open(&mut server);
|
||||
|
||||
server.collect_publish_diagnostic_notifications(1)?;
|
||||
|
||||
let completions = literal_completions(&mut server, &first_cell, Position::new(1, 9))?;
|
||||
|
||||
assert_json_snapshot!(completions);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import_from_future() -> anyhow::Result<()> {
|
||||
let mut server = TestServerBuilder::new()?
|
||||
.with_workspace(
|
||||
SystemPath::new("src"),
|
||||
Some(ClientOptions::default().with_experimental_auto_import(true)),
|
||||
)?
|
||||
.build()?
|
||||
.wait_until_workspaces_are_initialized()?;
|
||||
|
||||
server.initialization_result().unwrap();
|
||||
|
||||
let mut builder = NotebookBuilder::virtual_file("src/test.ipynb");
|
||||
|
||||
builder.add_python_cell(r#"from typing import TYPE_CHECKING"#);
|
||||
|
||||
let second_cell = builder.add_python_cell(
|
||||
r#"from __future__ import annotations
|
||||
b: Litera
|
||||
"#,
|
||||
);
|
||||
|
||||
builder.open(&mut server);
|
||||
|
||||
server.collect_publish_diagnostic_notifications(2)?;
|
||||
|
||||
let completions = literal_completions(&mut server, &second_cell, Position::new(1, 9))?;
|
||||
|
||||
assert_json_snapshot!(completions);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_import_docstring() -> anyhow::Result<()> {
|
||||
let mut server = TestServerBuilder::new()?
|
||||
.with_workspace(
|
||||
SystemPath::new("src"),
|
||||
Some(ClientOptions::default().with_experimental_auto_import(true)),
|
||||
)?
|
||||
.build()?
|
||||
.wait_until_workspaces_are_initialized()?;
|
||||
|
||||
server.initialization_result().unwrap();
|
||||
|
||||
let mut builder = NotebookBuilder::virtual_file("src/test.ipynb");
|
||||
|
||||
builder.add_python_cell(
|
||||
r#"from typing import TYPE_CHECKING
|
||||
"#,
|
||||
);
|
||||
|
||||
let second_cell = builder.add_python_cell(
|
||||
r#""""A cell level docstring"""
|
||||
b: Litera
|
||||
"#,
|
||||
);
|
||||
|
||||
builder.open(&mut server);
|
||||
|
||||
server.collect_publish_diagnostic_notifications(2)?;
|
||||
|
||||
let completions = literal_completions(&mut server, &second_cell, Position::new(1, 9))?;
|
||||
|
||||
assert_json_snapshot!(completions);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn semantic_tokens_full_for_cell(
|
||||
server: &mut TestServer,
|
||||
cell_uri: &lsp_types::Url,
|
||||
|
|
@ -359,3 +497,37 @@ impl NotebookBuilder {
|
|||
self.notebook_url
|
||||
}
|
||||
}
|
||||
|
||||
fn literal_completions(
|
||||
server: &mut TestServer,
|
||||
cell: &lsp_types::Url,
|
||||
position: Position,
|
||||
) -> crate::Result<Vec<lsp_types::CompletionItem>> {
|
||||
let completions_id =
|
||||
server.send_request::<lsp_types::request::Completion>(lsp_types::CompletionParams {
|
||||
text_document_position: lsp_types::TextDocumentPositionParams {
|
||||
text_document: lsp_types::TextDocumentIdentifier { uri: cell.clone() },
|
||||
position,
|
||||
},
|
||||
work_done_progress_params: lsp_types::WorkDoneProgressParams::default(),
|
||||
partial_result_params: lsp_types::PartialResultParams::default(),
|
||||
context: Some(lsp_types::CompletionContext {
|
||||
trigger_kind: CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS,
|
||||
trigger_character: None,
|
||||
}),
|
||||
});
|
||||
|
||||
// There are a ton of imports we don't care about in here...
|
||||
// The import bit is that an edit is always restricted to the current cell. That means,
|
||||
// we can't add `Literal` to the `from typing import TYPE_CHECKING` import in cell 1
|
||||
let completions = server.await_response::<lsp_types::request::Completion>(&completions_id)?;
|
||||
let mut items = match completions {
|
||||
Some(CompletionResponse::Array(array)) => array,
|
||||
Some(CompletionResponse::List(lsp_types::CompletionList { items, .. })) => items,
|
||||
None => return Ok(vec![]),
|
||||
};
|
||||
|
||||
items.retain(|item| item.label.starts_with("Litera"));
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
source: crates/ty_server/tests/e2e/notebook.rs
|
||||
expression: completions
|
||||
---
|
||||
[
|
||||
{
|
||||
"label": "Literal (import typing)",
|
||||
"kind": 6,
|
||||
"sortText": " 43",
|
||||
"insertText": "Literal",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing import Literal\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Literal (import typing_extensions)",
|
||||
"kind": 6,
|
||||
"sortText": " 44",
|
||||
"insertText": "Literal",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing_extensions import Literal\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "LiteralString (import typing)",
|
||||
"kind": 6,
|
||||
"sortText": " 45",
|
||||
"insertText": "LiteralString",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing import LiteralString\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "LiteralString (import typing_extensions)",
|
||||
"kind": 6,
|
||||
"sortText": " 46",
|
||||
"insertText": "LiteralString",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing_extensions import LiteralString\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
source: crates/ty_server/tests/e2e/notebook.rs
|
||||
expression: completions
|
||||
---
|
||||
[
|
||||
{
|
||||
"label": "Literal (import typing)",
|
||||
"kind": 6,
|
||||
"sortText": " 43",
|
||||
"insertText": "Literal",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing import Literal\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Literal (import typing_extensions)",
|
||||
"kind": 6,
|
||||
"sortText": " 44",
|
||||
"insertText": "Literal",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing_extensions import Literal\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "LiteralString (import typing)",
|
||||
"kind": 6,
|
||||
"sortText": " 45",
|
||||
"insertText": "LiteralString",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing import LiteralString\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "LiteralString (import typing_extensions)",
|
||||
"kind": 6,
|
||||
"sortText": " 46",
|
||||
"insertText": "LiteralString",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing_extensions import LiteralString\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
source: crates/ty_server/tests/e2e/notebook.rs
|
||||
expression: completions
|
||||
---
|
||||
[
|
||||
{
|
||||
"label": "Literal (import typing)",
|
||||
"kind": 6,
|
||||
"sortText": " 43",
|
||||
"insertText": "Literal",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing import Literal\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Literal (import typing_extensions)",
|
||||
"kind": 6,
|
||||
"sortText": " 44",
|
||||
"insertText": "Literal",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing_extensions import Literal\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "LiteralString (import typing)",
|
||||
"kind": 6,
|
||||
"sortText": " 45",
|
||||
"insertText": "LiteralString",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing import LiteralString\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "LiteralString (import typing_extensions)",
|
||||
"kind": 6,
|
||||
"sortText": " 46",
|
||||
"insertText": "LiteralString",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing_extensions import LiteralString\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
source: crates/ty_server/tests/e2e/notebook.rs
|
||||
expression: completions
|
||||
---
|
||||
[
|
||||
{
|
||||
"label": "Literal (import typing)",
|
||||
"kind": 6,
|
||||
"sortText": " 43",
|
||||
"insertText": "Literal",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 32
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 32
|
||||
}
|
||||
},
|
||||
"newText": ", Literal"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Literal (import typing_extensions)",
|
||||
"kind": 6,
|
||||
"sortText": " 44",
|
||||
"insertText": "Literal",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing_extensions import Literal\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "LiteralString (import typing)",
|
||||
"kind": 6,
|
||||
"sortText": " 45",
|
||||
"insertText": "LiteralString",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 32
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 32
|
||||
}
|
||||
},
|
||||
"newText": ", LiteralString"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "LiteralString (import typing_extensions)",
|
||||
"kind": 6,
|
||||
"sortText": " 46",
|
||||
"insertText": "LiteralString",
|
||||
"additionalTextEdits": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
}
|
||||
},
|
||||
"newText": "from typing_extensions import LiteralString\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue