Skip to content

Commit 4cdde6a

Browse files
authored
Fix span offsets in additional_text_edits in completions (#884)
This bug was exposed while testing multi-file projects. Plus, some test utilities for testing language service features with multiple sources.
1 parent c3010d7 commit 4cdde6a

File tree

8 files changed

+253
-178
lines changed

8 files changed

+253
-178
lines changed

language_service/src/completion.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use std::rc::Rc;
88

99
use crate::compilation::{Compilation, CompilationKind};
1010
use crate::display::CodeDisplay;
11-
use crate::protocol::{self, CompletionItem, CompletionItemKind, CompletionList};
12-
use crate::qsc_utils::span_contains;
11+
use crate::protocol::{CompletionItem, CompletionItemKind, CompletionList};
12+
use crate::qsc_utils::{protocol_span, span_contains};
1313
use qsc::ast::visit::{self, Visitor};
1414
use qsc::hir::{ItemKind, Package, PackageId};
1515

@@ -373,7 +373,13 @@ impl CompletionListBuilder {
373373
None => match start_of_namespace {
374374
Some(start) => {
375375
additional_edits.push((
376-
protocol::Span { start, end: start },
376+
protocol_span(
377+
qsc::Span {
378+
lo: start,
379+
hi: start,
380+
},
381+
&compilation.user_unit().sources,
382+
),
377383
format!(
378384
"open {};{}",
379385
namespace.name.clone(),

language_service/src/completion/tests.rs

+71-5
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,35 @@ use expect_test::{expect, Expect};
77

88
use super::{get_completions, CompletionItem};
99
use crate::test_utils::{
10-
compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib,
11-
get_source_and_marker_offsets,
10+
compile_notebook_with_fake_stdlib_and_markers, compile_project_with_fake_stdlib_and_markers,
11+
compile_with_fake_stdlib_and_markers,
1212
};
1313
use indoc::indoc;
1414

1515
fn check(source_with_cursor: &str, completions_to_check: &[&str], expect: &Expect) {
16-
let (source, cursor_offset, _) = get_source_and_marker_offsets(source_with_cursor);
17-
let compilation = compile_with_fake_stdlib("<source>", &source);
18-
let actual_completions = get_completions(&compilation, "<source>", cursor_offset[0]);
16+
let (compilation, cursor_offset, _) = compile_with_fake_stdlib_and_markers(source_with_cursor);
17+
let actual_completions = get_completions(&compilation, "<source>", cursor_offset);
18+
let checked_completions: Vec<Option<&CompletionItem>> = completions_to_check
19+
.iter()
20+
.map(|comp| {
21+
actual_completions
22+
.items
23+
.iter()
24+
.find(|item| item.label == **comp)
25+
})
26+
.collect();
27+
28+
expect.assert_debug_eq(&checked_completions);
29+
}
30+
31+
fn check_project(
32+
sources_with_markers: &[(&str, &str)],
33+
completions_to_check: &[&str],
34+
expect: &Expect,
35+
) {
36+
let (compilation, cursor_uri, cursor_offset, _) =
37+
compile_project_with_fake_stdlib_and_markers(sources_with_markers);
38+
let actual_completions = get_completions(&compilation, &cursor_uri, cursor_offset);
1939
let checked_completions: Vec<Option<&CompletionItem>> = completions_to_check
2040
.iter()
2141
.map(|comp| {
@@ -241,6 +261,52 @@ fn in_block_from_other_namespace() {
241261
);
242262
}
243263

264+
#[test]
265+
fn auto_open_multiple_files() {
266+
check_project(
267+
&[
268+
(
269+
"foo.qs",
270+
indoc! {r#"namespace Foo { operation FooOperation() : Unit {} }
271+
"#},
272+
),
273+
(
274+
"bar.qs",
275+
indoc! {r#"namespace Bar { operation BarOperation() : Unit { ↘ } }
276+
"#},
277+
),
278+
],
279+
&["FooOperation"],
280+
&expect![[r#"
281+
[
282+
Some(
283+
CompletionItem {
284+
label: "FooOperation",
285+
kind: Function,
286+
sort_text: Some(
287+
"0500FooOperation",
288+
),
289+
detail: Some(
290+
"operation FooOperation() : Unit",
291+
),
292+
additional_text_edits: Some(
293+
[
294+
(
295+
Span {
296+
start: 16,
297+
end: 16,
298+
},
299+
"open Foo;\n ",
300+
),
301+
],
302+
),
303+
},
304+
),
305+
]
306+
"#]],
307+
);
308+
}
309+
244310
#[ignore = "nested callables are not currently supported for completions"]
245311
#[test]
246312
fn in_block_nested_op() {

language_service/src/definition/tests.rs

+6-10
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,17 @@ use super::get_definition;
99
use crate::{
1010
protocol::Location,
1111
test_utils::{
12-
compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib,
13-
get_source_and_marker_offsets, target_offsets_to_spans,
12+
compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib_and_markers,
1413
},
1514
};
1615

1716
/// Asserts that the definition given at the cursor position matches the expected range.
1817
/// The cursor position is indicated by a `↘` marker in the source text.
1918
/// The expected definition range is indicated by `◉` markers in the source text.
2019
fn assert_definition(source_with_markers: &str) {
21-
let (source, cursor_offsets, target_offsets) =
22-
get_source_and_marker_offsets(source_with_markers);
23-
let target_spans = target_offsets_to_spans(&target_offsets);
24-
let compilation = compile_with_fake_stdlib("<source>", &source);
25-
let actual_definition = get_definition(&compilation, "<source>", cursor_offsets[0]);
20+
let (compilation, cursor_offset, target_spans) =
21+
compile_with_fake_stdlib_and_markers(source_with_markers);
22+
let actual_definition = get_definition(&compilation, "<source>", cursor_offset);
2623
let expected_definition = if target_spans.is_empty() {
2724
None
2825
} else {
@@ -50,9 +47,8 @@ fn assert_definition_notebook(cells_with_markers: &[(&str, &str)]) {
5047
}
5148

5249
fn check(source_with_markers: &str, expect: &Expect) {
53-
let (source, cursor_offsets, _) = get_source_and_marker_offsets(source_with_markers);
54-
let compilation = compile_with_fake_stdlib("<source>", &source);
55-
let actual_definition = get_definition(&compilation, "<source>", cursor_offsets[0]);
50+
let (compilation, cursor_offset, _) = compile_with_fake_stdlib_and_markers(source_with_markers);
51+
let actual_definition = get_definition(&compilation, "<source>", cursor_offset);
5652
expect.assert_debug_eq(&actual_definition);
5753
}
5854

language_service/src/hover/tests.rs

+8-20
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@
44
#![allow(clippy::needless_raw_string_hashes)]
55

66
use super::get_hover;
7-
use crate::{
8-
protocol,
9-
test_utils::{
10-
compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib,
11-
get_source_and_marker_offsets,
12-
},
7+
use crate::test_utils::{
8+
compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib_and_markers,
139
};
1410
use expect_test::{expect, Expect};
1511
use indoc::indoc;
@@ -18,25 +14,17 @@ use indoc::indoc;
1814
/// The cursor position is indicated by a `↘` marker in the source text.
1915
/// The expected hover span is indicated by two `◉` markers in the source text.
2016
fn check(source_with_markers: &str, expect: &Expect) {
21-
let (source, cursor_offsets, target_offsets) =
22-
get_source_and_marker_offsets(source_with_markers);
23-
let compilation = compile_with_fake_stdlib("<source>", &source);
24-
let actual = get_hover(&compilation, "<source>", cursor_offsets[0]).expect("Expected a hover.");
25-
assert_eq!(
26-
&actual.span,
27-
&protocol::Span {
28-
start: target_offsets[0],
29-
end: target_offsets[1],
30-
}
31-
);
17+
let (compilation, cursor_offset, target_spans) =
18+
compile_with_fake_stdlib_and_markers(source_with_markers);
19+
let actual = get_hover(&compilation, "<source>", cursor_offset).expect("Expected a hover.");
20+
assert_eq!(&actual.span, &target_spans[0]);
3221
expect.assert_eq(&actual.contents);
3322
}
3423

3524
/// Asserts that there is no hover for the given test case.
3625
fn check_none(source_with_markers: &str) {
37-
let (source, cursor_offsets, _) = get_source_and_marker_offsets(source_with_markers);
38-
let compilation = compile_with_fake_stdlib("<source>", &source);
39-
let actual = get_hover(&compilation, "<source>", cursor_offsets[0]);
26+
let (compilation, cursor_offset, _) = compile_with_fake_stdlib_and_markers(source_with_markers);
27+
let actual = get_hover(&compilation, "<source>", cursor_offset);
4028
assert!(actual.is_none());
4129
}
4230

language_service/src/references/tests.rs

+9-18
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ use super::get_references;
77
use crate::{
88
protocol,
99
test_utils::{
10-
compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib,
11-
get_source_and_marker_offsets, target_offsets_to_spans,
10+
compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib_and_markers,
1211
},
1312
};
1413
use expect_test::{expect, Expect};
@@ -17,29 +16,21 @@ use indoc::indoc;
1716
/// Asserts that the reference locations given at the cursor position matches the expected reference locations.
1817
/// The cursor position is indicated by a `↘` marker in the source text.
1918
fn check_with_std(source_with_markers: &str, expect: &Expect) {
20-
let (source, cursor_offsets, _) = get_source_and_marker_offsets(source_with_markers);
21-
let compilation = compile_with_fake_stdlib("<source>", &source);
22-
let actual = get_references(&compilation, "<source>", cursor_offsets[0], true);
19+
let (compilation, cursor_offset, _) = compile_with_fake_stdlib_and_markers(source_with_markers);
20+
let actual = get_references(&compilation, "<source>", cursor_offset, true);
2321
expect.assert_debug_eq(&actual);
2422
}
2523

2624
/// Asserts that the reference locations given at the cursor position matches the expected reference locations.
2725
/// The cursor position is indicated by a `↘` marker in the source text.
2826
/// The expected reference location ranges are indicated by `◉` markers in the source text.
2927
fn check(source_with_markers: &str, include_declaration: bool) {
30-
let (source, cursor_offsets, target_offsets) =
31-
get_source_and_marker_offsets(source_with_markers);
32-
let target_spans = target_offsets_to_spans(&target_offsets);
33-
let compilation = compile_with_fake_stdlib("<source>", &source);
34-
let actual = get_references(
35-
&compilation,
36-
"<source>",
37-
cursor_offsets[0],
38-
include_declaration,
39-
)
40-
.into_iter()
41-
.map(|l| l.span)
42-
.collect::<Vec<_>>();
28+
let (compilation, cursor_offset, target_spans) =
29+
compile_with_fake_stdlib_and_markers(source_with_markers);
30+
let actual = get_references(&compilation, "<source>", cursor_offset, include_declaration)
31+
.into_iter()
32+
.map(|l| l.span)
33+
.collect::<Vec<_>>();
4334
for target in &target_spans {
4435
assert!(
4536
actual.contains(target),

language_service/src/rename/tests.rs

+6-10
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,17 @@
55

66
use super::{get_rename, prepare_rename};
77
use crate::test_utils::{
8-
compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib,
9-
get_source_and_marker_offsets, target_offsets_to_spans,
8+
compile_notebook_with_fake_stdlib_and_markers, compile_with_fake_stdlib_and_markers,
109
};
1110
use expect_test::{expect, Expect};
1211

1312
/// Asserts that the rename locations given at the cursor position matches the expected rename locations.
1413
/// The cursor position is indicated by a `↘` marker in the source text.
1514
/// The expected rename location ranges are indicated by `◉` markers in the source text.
1615
fn check(source_with_markers: &str) {
17-
let (source, cursor_offsets, target_offsets) =
18-
get_source_and_marker_offsets(source_with_markers);
19-
let target_spans = target_offsets_to_spans(&target_offsets);
20-
let compilation = compile_with_fake_stdlib("<source>", &source);
21-
let actual = get_rename(&compilation, "<source>", cursor_offsets[0])
16+
let (compilation, cursor_offset, target_spans) =
17+
compile_with_fake_stdlib_and_markers(source_with_markers);
18+
let actual = get_rename(&compilation, "<source>", cursor_offset)
2219
.into_iter()
2320
.map(|l| l.span)
2421
.collect::<Vec<_>>();
@@ -31,9 +28,8 @@ fn check(source_with_markers: &str) {
3128
/// Asserts that the prepare rename given at the cursor position returns None.
3229
/// The cursor position is indicated by a `↘` marker in the source text.
3330
fn assert_no_rename(source_with_markers: &str) {
34-
let (source, cursor_offsets, _) = get_source_and_marker_offsets(source_with_markers);
35-
let compilation = compile_with_fake_stdlib("<source>", &source);
36-
let actual = prepare_rename(&compilation, "<source>", cursor_offsets[0]);
31+
let (compilation, cursor_offset, _) = compile_with_fake_stdlib_and_markers(source_with_markers);
32+
let actual = prepare_rename(&compilation, "<source>", cursor_offset);
3733
assert!(actual.is_none());
3834
}
3935

language_service/src/signature_help/tests.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@
44
#![allow(clippy::needless_raw_string_hashes)]
55

66
use super::get_signature_help;
7-
use crate::test_utils::{compile_with_fake_stdlib, get_source_and_marker_offsets};
7+
use crate::test_utils::compile_with_fake_stdlib_and_markers;
88
use expect_test::{expect, Expect};
99
use indoc::indoc;
1010

1111
/// Asserts that the signature help given at the cursor position matches the expected signature help.
1212
/// The cursor position is indicated by a `↘` marker in the source text.
1313
fn check(source_with_markers: &str, expect: &Expect) {
14-
let (source, cursor_offsets, _) = get_source_and_marker_offsets(source_with_markers);
15-
let compilation = compile_with_fake_stdlib("<source>", &source);
16-
let actual = get_signature_help(&compilation, "<source>", cursor_offsets[0])
14+
let (compilation, cursor_offset, _) = compile_with_fake_stdlib_and_markers(source_with_markers);
15+
let actual = get_signature_help(&compilation, "<source>", cursor_offset)
1716
.expect("Expected a signature help.");
1817
expect.assert_debug_eq(&actual);
1918
}

0 commit comments

Comments
 (0)