1 use std::fs;
2 use std::path::Path;
3 use std::time::Duration;
4
5 use futures::future;
6 use lsp_types::{notification::*, request::*, *};
7 use serde::de::Deserialize;
8 use serde_json::json;
9
10 use crate::support::project_builder::{project, ProjectBuilder};
11 use crate::support::{basic_bin_manifest, fixtures_dir};
12
13 #[allow(dead_code)]
14 mod support;
15
initialize_params(root_path: &Path) -> InitializeParams16 fn initialize_params(root_path: &Path) -> InitializeParams {
17 InitializeParams {
18 process_id: None,
19 root_uri: None,
20 root_path: Some(root_path.display().to_string()),
21 initialization_options: None,
22 capabilities: ClientCapabilities {
23 workspace: None,
24 window: Some(WindowClientCapabilities { progress: Some(true) }),
25 text_document: None,
26 experimental: None,
27 },
28 trace: None,
29 workspace_folders: None,
30 }
31 }
32
initialize_params_with_opts(root_path: &Path, opts: serde_json::Value) -> InitializeParams33 fn initialize_params_with_opts(root_path: &Path, opts: serde_json::Value) -> InitializeParams {
34 InitializeParams { initialization_options: Some(opts), ..initialize_params(root_path) }
35 }
36
37 #[test]
client_test_infer_bin()38 fn client_test_infer_bin() {
39 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("infer_bin")).unwrap().build();
40 let root_path = p.root();
41 let mut rls = p.spawn_rls_async();
42
43 rls.request::<Initialize>(0, initialize_params(root_path));
44
45 let diag = rls.wait_for_diagnostics();
46
47 assert!(diag.uri.as_str().ends_with("src/main.rs"));
48 assert!(diag.diagnostics[0].message.contains("struct is never constructed: `UnusedBin`"));
49 }
50
51 #[test]
client_test_infer_lib()52 fn client_test_infer_lib() {
53 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("infer_lib")).unwrap().build();
54 let root_path = p.root();
55 let mut rls = p.spawn_rls_async();
56
57 rls.request::<Initialize>(0, initialize_params(root_path));
58
59 let diag = rls.wait_for_diagnostics();
60
61 assert!(diag.uri.as_str().ends_with("src/lib.rs"));
62 assert!(diag.diagnostics[0].message.contains("struct is never constructed: `UnusedLib`"));
63 }
64
65 #[test]
client_test_infer_custom_bin()66 fn client_test_infer_custom_bin() {
67 let p =
68 ProjectBuilder::try_from_fixture(fixtures_dir().join("infer_custom_bin")).unwrap().build();
69 let root_path = p.root();
70 let mut rls = p.spawn_rls_async();
71
72 rls.request::<Initialize>(0, initialize_params(root_path));
73
74 let diag = rls.wait_for_diagnostics();
75
76 assert!(diag.uri.as_str().ends_with("src/custom_bin.rs"));
77 assert!(diag.diagnostics[0].message.contains("struct is never constructed: `UnusedCustomBin`"));
78 }
79
80 /// Test includes window/progress regression testing
81 #[test]
client_test_simple_workspace()82 fn client_test_simple_workspace() {
83 let p = project("simple_workspace")
84 .file(
85 "Cargo.toml",
86 r#"
87 [workspace]
88 members = [
89 "member_lib",
90 "member_bin",
91 ]
92 "#,
93 )
94 .file(
95 "Cargo.lock",
96 r#"
97 [root]
98 name = "member_lib"
99 version = "0.1.0"
100
101 [[package]]
102 name = "member_bin"
103 version = "0.1.0"
104 dependencies = [
105 "member_lib 0.1.0",
106 ]
107 "#,
108 )
109 .file(
110 "member_bin/Cargo.toml",
111 r#"
112 [package]
113 name = "member_bin"
114 version = "0.1.0"
115 authors = ["Igor Matuszewski <Xanewok@gmail.com>"]
116
117 [dependencies]
118 member_lib = { path = "../member_lib" }
119 "#,
120 )
121 .file(
122 "member_bin/src/main.rs",
123 r#"
124 extern crate member_lib;
125
126 fn main() {
127 let a = member_lib::MemberLibStruct;
128 }
129 "#,
130 )
131 .file(
132 "member_lib/Cargo.toml",
133 r#"
134 [package]
135 name = "member_lib"
136 version = "0.1.0"
137 authors = ["Igor Matuszewski <Xanewok@gmail.com>"]
138
139 [dependencies]
140 "#,
141 )
142 .file(
143 "member_lib/src/lib.rs",
144 r#"
145 pub struct MemberLibStruct;
146
147 struct Unused;
148
149 #[cfg(test)]
150 mod tests {
151 #[test]
152 fn it_works() {
153 }
154 }
155 "#,
156 )
157 .build();
158
159 let root_path = p.root();
160 let mut rls = p.spawn_rls_async();
161
162 rls.request::<Initialize>(0, initialize_params(root_path));
163
164 rls.wait_for_indexing();
165
166 // Check if we built member_lib and member_bin + their cfg(test) variants
167 let count = rls
168 .messages()
169 .iter()
170 .filter(|msg| msg["method"] == "window/progress")
171 .filter(|msg| msg["params"]["title"] == "Building")
172 .filter(|msg| {
173 msg["params"]["message"].as_str().map(|x| x.starts_with("member_")).unwrap_or(false)
174 })
175 .count();
176 assert_eq!(count, 4);
177 }
178
179 #[test]
client_changing_workspace_lib_retains_diagnostics()180 fn client_changing_workspace_lib_retains_diagnostics() {
181 let p = project("simple_workspace")
182 .file(
183 "Cargo.toml",
184 r#"
185 [workspace]
186 members = [
187 "library",
188 "binary",
189 ]
190 "#,
191 )
192 .file(
193 "library/Cargo.toml",
194 r#"
195 [package]
196 name = "library"
197 version = "0.1.0"
198 authors = ["Example <rls@example.com>"]
199 "#,
200 )
201 .file(
202 "library/src/lib.rs",
203 r#"
204 pub fn fetch_u32() -> u32 {
205 let unused = ();
206 42
207 }
208 #[cfg(test)]
209 mod test {
210 #[test]
211 fn my_test() {
212 let test_val: u32 = super::fetch_u32();
213 }
214 }
215 "#,
216 )
217 .file(
218 "binary/Cargo.toml",
219 r#"
220 [package]
221 name = "binary"
222 version = "0.1.0"
223 authors = ["Igor Matuszewski <Xanewok@gmail.com>"]
224
225 [dependencies]
226 library = { path = "../library" }
227 "#,
228 )
229 .file(
230 "binary/src/main.rs",
231 r#"
232 extern crate library;
233
234 fn main() {
235 let val: u32 = library::fetch_u32();
236 }
237 "#,
238 )
239 .build();
240
241 let root_path = p.root();
242 let mut rls = p.spawn_rls_async();
243
244 rls.request::<Initialize>(0, initialize_params(root_path));
245
246 let lib = rls.future_diagnostics("library/src/lib.rs");
247 let bin = rls.future_diagnostics("binary/src/main.rs");
248 let (lib, bin) = rls.block_on(future::join(lib, bin)).unwrap();
249 let (lib, bin) = (lib.unwrap(), bin.unwrap());
250
251 assert!(lib.diagnostics.iter().any(|m| m.message.contains("unused variable: `test_val`")));
252 assert!(lib.diagnostics.iter().any(|m| m.message.contains("unused variable: `unused`")));
253 assert!(bin.diagnostics[0].message.contains("unused variable: `val`"));
254
255 rls.notify::<DidChangeTextDocument>(DidChangeTextDocumentParams {
256 content_changes: vec![TextDocumentContentChangeEvent {
257 range: Some(Range {
258 start: Position { line: 1, character: 38 },
259 end: Position { line: 1, character: 41 },
260 }),
261 range_length: Some(3),
262 text: "u64".to_string(),
263 }],
264 text_document: VersionedTextDocumentIdentifier {
265 uri: Url::from_file_path(p.root().join("library/src/lib.rs")).unwrap(),
266 version: Some(0),
267 },
268 });
269
270 let lib = rls.future_diagnostics("library/src/lib.rs");
271 let bin = rls.future_diagnostics("binary/src/main.rs");
272 let (lib, bin) = rls.block_on(future::join(lib, bin)).unwrap();
273 let (lib, bin) = (lib.unwrap(), bin.unwrap());
274
275 // lib unit tests have compile errors
276 assert!(lib.diagnostics.iter().any(|m| m.message.contains("unused variable: `unused`")));
277 assert!(lib.diagnostics.iter().any(|m| m.message.contains("expected `u32`, found `u64`")));
278 // bin depending on lib picks up type mismatch
279 assert!(bin.diagnostics[0].message.contains("mismatched types\n\nexpected `u32`, found `u64`"));
280
281 rls.notify::<DidChangeTextDocument>(DidChangeTextDocumentParams {
282 content_changes: vec![TextDocumentContentChangeEvent {
283 range: Some(Range {
284 start: Position { line: 1, character: 38 },
285 end: Position { line: 1, character: 41 },
286 }),
287 range_length: Some(3),
288 text: "u32".to_string(),
289 }],
290 text_document: VersionedTextDocumentIdentifier {
291 uri: Url::from_file_path(p.root().join("library/src/lib.rs")).unwrap(),
292 version: Some(1),
293 },
294 });
295
296 let lib = rls.future_diagnostics("library/src/lib.rs");
297 let bin = rls.future_diagnostics("binary/src/main.rs");
298 let (lib, bin) = rls.block_on(future::join(lib, bin)).unwrap();
299 let (lib, bin) = (lib.unwrap(), bin.unwrap());
300
301 assert!(lib.diagnostics.iter().any(|m| m.message.contains("unused variable: `test_val`")));
302 assert!(lib.diagnostics.iter().any(|m| m.message.contains("unused variable: `unused`")));
303 assert!(bin.diagnostics[0].message.contains("unused variable: `val`"));
304 }
305
306 #[test]
client_implicit_workspace_pick_up_lib_changes()307 fn client_implicit_workspace_pick_up_lib_changes() {
308 let p = project("simple_workspace")
309 .file(
310 "Cargo.toml",
311 r#"
312 [package]
313 name = "binary"
314 version = "0.1.0"
315 authors = ["Example <rls@example.com>"]
316
317 [dependencies]
318 inner = { path = "inner" }
319 "#,
320 )
321 .file(
322 "src/main.rs",
323 r#"
324 extern crate inner;
325
326 fn main() {
327 let val = inner::foo();
328 }
329 "#,
330 )
331 .file(
332 "inner/Cargo.toml",
333 r#"
334 [package]
335 name = "inner"
336 version = "0.1.0"
337 authors = ["Example <rls@example.com>"]
338 "#,
339 )
340 .file(
341 "inner/src/lib.rs",
342 r#"
343 pub fn foo() -> u32 { 42 }
344 "#,
345 )
346 .build();
347
348 let root_path = p.root();
349 let mut rls = p.spawn_rls_async();
350
351 rls.request::<Initialize>(0, initialize_params(root_path));
352
353 let bin = rls.future_diagnostics("src/main.rs");
354 let bin = rls.block_on(bin).unwrap();
355 let bin = bin.unwrap();
356 assert!(bin.diagnostics[0].message.contains("unused variable: `val`"));
357
358 rls.notify::<DidChangeTextDocument>(DidChangeTextDocumentParams {
359 content_changes: vec![TextDocumentContentChangeEvent {
360 range: Some(Range {
361 start: Position { line: 1, character: 23 },
362 end: Position { line: 1, character: 26 },
363 }),
364 range_length: Some(3),
365 text: "bar".to_string(),
366 }],
367 text_document: VersionedTextDocumentIdentifier {
368 uri: Url::from_file_path(p.root().join("inner/src/lib.rs")).unwrap(),
369 version: Some(0),
370 },
371 });
372
373 // bin depending on lib picks up type mismatch
374 let bin = rls.future_diagnostics("src/main.rs");
375 let bin = rls.block_on(bin).unwrap();
376 let bin = bin.unwrap();
377 assert!(bin.diagnostics[0].message.contains("cannot find function `foo`"));
378
379 rls.notify::<DidChangeTextDocument>(DidChangeTextDocumentParams {
380 content_changes: vec![TextDocumentContentChangeEvent {
381 range: Some(Range {
382 start: Position { line: 1, character: 23 },
383 end: Position { line: 1, character: 26 },
384 }),
385 range_length: Some(3),
386 text: "foo".to_string(),
387 }],
388 text_document: VersionedTextDocumentIdentifier {
389 uri: Url::from_file_path(p.root().join("inner/src/lib.rs")).unwrap(),
390 version: Some(1),
391 },
392 });
393
394 let bin = rls.future_diagnostics("src/main.rs");
395 let bin = rls.block_on(bin).unwrap();
396 let bin = bin.unwrap();
397 assert!(bin.diagnostics[0].message.contains("unused variable: `val`"));
398 }
399
400 #[test]
client_test_complete_self_crate_name()401 fn client_test_complete_self_crate_name() {
402 let p = project("ws_with_test_dir")
403 .file(
404 "Cargo.toml",
405 r#"
406 [workspace]
407 members = ["library"]
408 "#,
409 )
410 .file(
411 "library/Cargo.toml",
412 r#"
413 [package]
414 name = "library"
415 version = "0.1.0"
416 authors = ["Example <rls@example.com>"]
417 "#,
418 )
419 .file(
420 "library/src/lib.rs",
421 r#"
422 pub fn function() -> usize { 5 }
423 "#,
424 )
425 .file(
426 "library/tests/test.rs",
427 r#"
428 extern crate library;
429 use library::~
430 "#,
431 )
432 .build();
433
434 let root_path = p.root();
435 let mut rls = p.spawn_rls_async();
436
437 rls.request::<Initialize>(0, initialize_params(root_path));
438
439 let diag = rls.wait_for_diagnostics();
440 assert!(diag.diagnostics[0].message.contains("expected identifier"));
441
442 let response = rls.request::<Completion>(
443 100,
444 CompletionParams {
445 context: Some(CompletionContext {
446 trigger_character: Some(":".to_string()),
447 trigger_kind: CompletionTriggerKind::TriggerCharacter,
448 }),
449 text_document_position: TextDocumentPositionParams {
450 position: Position::new(2, 32),
451 text_document: TextDocumentIdentifier {
452 uri: Url::from_file_path(p.root().join("library/tests/test.rs")).unwrap(),
453 },
454 },
455 },
456 );
457
458 let items = match response {
459 Some(CompletionResponse::Array(items)) => items,
460 Some(CompletionResponse::List(CompletionList { items, .. })) => items,
461 _ => Vec::new(),
462 };
463
464 let item = items.into_iter().nth(0).expect("Racer autocompletion failed");
465 assert_eq!(item.detail.unwrap(), "pub fn function() -> usize");
466 }
467
468 // Spurious in Rust CI, e.g.
469 // https://github.com/rust-lang/rust/pull/60730
470 // https://github.com/rust-lang/rust/pull/61771
471 // https://github.com/rust-lang/rust/pull/61932
472 #[ignore]
473 #[test]
client_completion_suggests_arguments_in_statements()474 fn client_completion_suggests_arguments_in_statements() {
475 let p = project("ws_with_test_dir")
476 .file(
477 "Cargo.toml",
478 r#"
479 [workspace]
480 members = ["library"]
481 "#,
482 )
483 .file(
484 "library/Cargo.toml",
485 r#"
486 [package]
487 name = "library"
488 version = "0.1.0"
489 authors = ["Example <rls@example.com>"]
490 "#,
491 )
492 .file(
493 "library/src/lib.rs",
494 r#"
495 pub fn function() -> usize { 5 }
496 "#,
497 )
498 .file(
499 "library/tests/test.rs",
500 r#"
501 extern crate library;
502 fn magic() {
503 let a = library::f~
504 }
505 "#,
506 )
507 .build();
508
509 let root_path = p.root();
510 let mut rls = p.spawn_rls_async();
511
512 rls.request::<Initialize>(
513 0,
514 lsp_types::InitializeParams {
515 process_id: None,
516 root_uri: None,
517 root_path: Some(root_path.display().to_string()),
518 initialization_options: None,
519 capabilities: lsp_types::ClientCapabilities {
520 workspace: None,
521 window: Some(WindowClientCapabilities { progress: Some(true) }),
522 text_document: Some(TextDocumentClientCapabilities {
523 completion: Some(CompletionCapability {
524 completion_item: Some(CompletionItemCapability {
525 snippet_support: Some(true),
526 ..CompletionItemCapability::default()
527 }),
528 ..CompletionCapability::default()
529 }),
530 ..TextDocumentClientCapabilities::default()
531 }),
532 experimental: None,
533 },
534 trace: None,
535 workspace_folders: None,
536 },
537 );
538
539 let diag = rls.wait_for_diagnostics();
540 assert!(diag.diagnostics[0].message.contains("expected one of"));
541
542 let response = rls.request::<Completion>(
543 100,
544 CompletionParams {
545 context: Some(CompletionContext {
546 trigger_character: Some("f".to_string()),
547 trigger_kind: CompletionTriggerKind::TriggerCharacter,
548 }),
549 text_document_position: TextDocumentPositionParams {
550 position: Position::new(3, 41),
551 text_document: TextDocumentIdentifier {
552 uri: Url::from_file_path(p.root().join("library/tests/test.rs")).unwrap(),
553 },
554 },
555 },
556 );
557
558 let items = match response {
559 Some(CompletionResponse::Array(items)) => items,
560 Some(CompletionResponse::List(CompletionList { items, .. })) => items,
561 _ => Vec::new(),
562 };
563
564 let item = items.into_iter().nth(0).expect("Racer autocompletion failed");
565 assert_eq!(item.insert_text.unwrap(), "function()");
566 }
567
568 #[test]
client_use_statement_completion_doesnt_suggest_arguments()569 fn client_use_statement_completion_doesnt_suggest_arguments() {
570 let p = project("ws_with_test_dir")
571 .file(
572 "Cargo.toml",
573 r#"
574 [workspace]
575 members = ["library"]
576 "#,
577 )
578 .file(
579 "library/Cargo.toml",
580 r#"
581 [package]
582 name = "library"
583 version = "0.1.0"
584 authors = ["Example <rls@example.com>"]
585 "#,
586 )
587 .file(
588 "library/src/lib.rs",
589 r#"
590 pub fn function() -> usize { 5 }
591 "#,
592 )
593 .file(
594 "library/tests/test.rs",
595 r#"
596 extern crate library;
597 use library::~;
598 "#,
599 )
600 .build();
601
602 let root_path = p.root();
603 let mut rls = p.spawn_rls_async();
604
605 rls.request::<Initialize>(0, initialize_params(root_path));
606
607 let diag = rls.wait_for_diagnostics();
608 assert!(diag.diagnostics[0].message.contains("expected identifier"));
609
610 let response = rls.request::<Completion>(
611 100,
612 CompletionParams {
613 context: Some(CompletionContext {
614 trigger_character: Some(":".to_string()),
615 trigger_kind: CompletionTriggerKind::TriggerCharacter,
616 }),
617 text_document_position: TextDocumentPositionParams {
618 position: Position::new(2, 32),
619 text_document: TextDocumentIdentifier {
620 uri: Url::from_file_path(p.root().join("library/tests/test.rs")).unwrap(),
621 },
622 },
623 },
624 );
625
626 let items = match response {
627 Some(CompletionResponse::Array(items)) => items,
628 Some(CompletionResponse::List(CompletionList { items, .. })) => items,
629 _ => Vec::new(),
630 };
631
632 let item = items.into_iter().nth(0).expect("Racer autocompletion failed");
633 assert_eq!(item.insert_text.unwrap(), "function");
634 }
635
636 /// Test simulates typing in a dependency wrongly in a couple of ways before finally getting it
637 /// right. Rls should provide Cargo.toml diagnostics.
638 ///
639 /// ```
640 /// [dependencies]
641 /// auto-cfg = "0.5555"
642 /// ```
643 ///
644 /// * Firstly "auto-cfg" doesn't exist, it should be "autocfg"
645 /// * Secondly version 0.5555 of "autocfg" doesn't exist.
646 #[test]
client_dependency_typo_and_fix()647 fn client_dependency_typo_and_fix() {
648 let manifest_with_dependency = |dep: &str| {
649 format!(
650 r#"
651 [package]
652 name = "dependency_typo"
653 version = "0.1.0"
654 authors = ["alexheretic@gmail.com"]
655
656 [dependencies]
657 {}
658 "#,
659 dep
660 )
661 };
662
663 let p = project("dependency_typo")
664 .file("Cargo.toml", &manifest_with_dependency(r#"auto-cfg = "0.5555""#))
665 .file(
666 "src/main.rs",
667 r#"
668 fn main() {
669 println!("Hello world!");
670 }
671 "#,
672 )
673 .build();
674 let root_path = p.root();
675 let mut rls = p.spawn_rls_async();
676
677 rls.request::<Initialize>(0, initialize_params(root_path));
678
679 let diag = rls.wait_for_diagnostics();
680 assert_eq!(diag.diagnostics.len(), 1);
681 assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Error));
682 assert!(diag.diagnostics[0].message.contains("no matching package found"));
683
684 let change_manifest = |contents: &str| {
685 std::fs::write(root_path.join("Cargo.toml"), contents).unwrap();
686 };
687
688 // fix naming typo, we now expect a version error diagnostic
689 change_manifest(&manifest_with_dependency(r#"autocfg = "0.5555""#));
690 rls.notify::<DidChangeWatchedFiles>(DidChangeWatchedFilesParams {
691 changes: vec![FileEvent {
692 uri: Url::from_file_path(p.root().join("Cargo.toml")).unwrap(),
693 typ: FileChangeType::Changed,
694 }],
695 });
696
697 let diag = rls.wait_for_diagnostics();
698 assert_eq!(diag.diagnostics.len(), 1);
699 assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Error));
700 assert!(diag.diagnostics[0].message.contains("^0.5555"));
701
702 // Fix version issue so no error diagnostics occur.
703 // This is kinda slow as cargo will compile the dependency, though I
704 // chose autocfg to minimise this as it is a very small dependency.
705 change_manifest(&manifest_with_dependency(r#"autocfg = "1""#));
706 rls.notify::<DidChangeWatchedFiles>(DidChangeWatchedFilesParams {
707 changes: vec![FileEvent {
708 uri: Url::from_file_path(p.root().join("Cargo.toml")).unwrap(),
709 typ: FileChangeType::Changed,
710 }],
711 });
712
713 let diag = rls.wait_for_diagnostics();
714 assert_eq!(
715 diag.diagnostics.iter().find(|d| d.severity == Some(DiagnosticSeverity::Error)),
716 None
717 );
718 }
719
720 /// Tests correct positioning of a toml parse error, use of `==` instead of `=`.
721 #[test]
client_invalid_toml_manifest()722 fn client_invalid_toml_manifest() {
723 let p = project("invalid_toml")
724 .file(
725 "Cargo.toml",
726 r#"[package]
727 name = "probably_valid"
728 version == "0.1.0"
729 authors = ["alexheretic@gmail.com"]
730 "#,
731 )
732 .file(
733 "src/main.rs",
734 r#"
735 fn main() {
736 println!("Hello world!");
737 }
738 "#,
739 )
740 .build();
741 let root_path = p.root();
742 let mut rls = p.spawn_rls_async();
743
744 rls.request::<Initialize>(0, initialize_params(root_path));
745
746 let diag: PublishDiagnosticsParams = rls.wait_for_diagnostics();
747
748 assert!(diag.uri.as_str().ends_with("invalid_toml/Cargo.toml"));
749 assert_eq!(diag.diagnostics.len(), 1);
750 assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Error));
751 assert!(diag.diagnostics[0].message.contains("failed to parse manifest"));
752
753 assert_eq!(
754 diag.diagnostics[0].range,
755 Range {
756 start: Position { line: 2, character: 21 },
757 end: Position { line: 2, character: 22 },
758 }
759 );
760 }
761
762 /// Tests correct file highlighting of workspace member manifest with invalid path dependency.
763 #[test]
client_invalid_member_toml_manifest()764 fn client_invalid_member_toml_manifest() {
765 let project = project("invalid_member_toml")
766 .file(
767 "Cargo.toml",
768 r#"[package]
769 name = "root_is_fine"
770 version = "0.1.0"
771 authors = ["alexheretic@gmail.com"]
772
773 [dependencies]
774 member_a = { path = "member_a" }
775
776 [workspace]
777 "#,
778 )
779 .file("src/main.rs", "fn main() {}")
780 .file(
781 "member_a/Cargo.toml",
782 r#"[package]
783 name = "member_a"
784 version = "0.0.3"
785 authors = ["alexheretic@gmail.com"]
786
787 [dependencies]
788 dodgy_member = { path = "dodgy_member" }
789 "#,
790 )
791 .file("member_a/src/lib.rs", "fn ma() {}")
792 .file(
793 "member_a/dodgy_member/Cargo.toml",
794 r#"[package]
795 name = "dodgy_member"
796 version = "0.5.0"
797 authors = ["alexheretic@gmail.com"]
798
799 [dependencies]
800 nosuch = { path = "not-exist" }
801 "#,
802 )
803 .file("member_a/dodgy_member/src/lib.rs", "fn dm() {}")
804 .build();
805 let root_path = project.root();
806 let mut rls = project.spawn_rls_async();
807
808 rls.request::<Initialize>(0, initialize_params(root_path));
809
810 let diag: PublishDiagnosticsParams = rls.wait_for_diagnostics();
811
812 assert!(diag.uri.as_str().ends_with("invalid_member_toml/member_a/dodgy_member/Cargo.toml"));
813
814 assert_eq!(diag.diagnostics.len(), 1);
815 assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Error));
816 assert!(diag.diagnostics[0].message.contains("failed to load manifest"));
817 }
818
819 #[test]
client_invalid_member_dependency_resolution()820 fn client_invalid_member_dependency_resolution() {
821 let project = project("invalid_member_resolution")
822 .file(
823 "Cargo.toml",
824 r#"[package]
825 name = "root_is_fine"
826 version = "0.1.0"
827 authors = ["alexheretic@gmail.com"]
828
829 [dependencies]
830 member_a = { path = "member_a" }
831
832 [workspace]
833 "#,
834 )
835 .file("src/main.rs", "fn main() {}")
836 .file(
837 "member_a/Cargo.toml",
838 r#"[package]
839 name = "member_a"
840 version = "0.0.5"
841 authors = ["alexheretic@gmail.com"]
842
843 [dependencies]
844 dodgy_member = { path = "dodgy_member" }
845 "#,
846 )
847 .file("member_a/src/lib.rs", "fn ma() {}")
848 .file(
849 "member_a/dodgy_member/Cargo.toml",
850 r#"[package]
851 name = "dodgy_member"
852 version = "0.6.0"
853 authors = ["alexheretic@gmail.com"]
854
855 [dependencies]
856 nosuchdep123 = "1.2.4"
857 "#,
858 )
859 .file("member_a/dodgy_member/src/lib.rs", "fn dm() {}")
860 .build();
861 let root_path = project.root();
862 let mut rls = project.spawn_rls_async();
863
864 rls.request::<Initialize>(0, initialize_params(root_path));
865
866 let diag: PublishDiagnosticsParams = rls.wait_for_diagnostics();
867
868 assert!(diag
869 .uri
870 .as_str()
871 .ends_with("invalid_member_resolution/member_a/dodgy_member/Cargo.toml"));
872
873 assert_eq!(diag.diagnostics.len(), 1);
874 assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Error));
875 assert!(diag.diagnostics[0].message.contains("no matching package named `nosuchdep123`"));
876 }
877
878 #[test]
client_handle_utf16_unit_text_edits()879 fn client_handle_utf16_unit_text_edits() {
880 let p = project("client_handle_utf16_unit_text_edits")
881 .file(
882 "Cargo.toml",
883 r#"[package]
884 name = "client_handle_utf16_unit_text_edits"
885 version = "0.1.0"
886 authors = ["example@example.com"]
887 "#,
888 )
889 .file("src/main.rs", "fn main() {}")
890 .file("src/some.rs", "")
891 .build();
892 let root_path = p.root();
893 let mut rls = p.spawn_rls_async();
894
895 rls.request::<Initialize>(0, initialize_params(root_path));
896
897 rls.wait_for_indexing();
898
899 rls.notify::<DidChangeTextDocument>(DidChangeTextDocumentParams {
900 text_document: VersionedTextDocumentIdentifier {
901 uri: Url::from_file_path(p.root().join("src/some.rs")).unwrap(),
902 version: Some(0),
903 },
904 // "" -> ""
905 content_changes: vec![TextDocumentContentChangeEvent {
906 range: Some(Range {
907 start: Position { line: 0, character: 0 },
908 end: Position { line: 0, character: 2 },
909 }),
910 range_length: Some(2),
911 text: "".to_string(),
912 }],
913 });
914 }
915
916 /// Ensures that wide characters do not prevent RLS from calculating correct
917 /// 'whole file' LSP range.
918 #[test]
client_format_utf16_range()919 fn client_format_utf16_range() {
920 let p = project("client_format_utf16_range")
921 .file(
922 "Cargo.toml",
923 r#"[package]
924 name = "client_format_utf16_range"
925 version = "0.1.0"
926 authors = ["example@example.com"]
927 "#,
928 )
929 .file("src/main.rs", "/* */ fn main() { }")
930 .build();
931 let root_path = p.root();
932 let mut rls = p.spawn_rls_async();
933
934 rls.request::<Initialize>(0, initialize_params(root_path));
935
936 rls.wait_for_indexing();
937
938 let result = rls.request::<Formatting>(
939 66,
940 DocumentFormattingParams {
941 text_document: TextDocumentIdentifier {
942 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
943 },
944 options: FormattingOptions {
945 tab_size: 4,
946 insert_spaces: true,
947 properties: Default::default(),
948 },
949 },
950 );
951
952 let new_text: Vec<_> =
953 result.unwrap().iter().map(|edit| edit.new_text.as_str().replace('\r', "")).collect();
954 // Actual formatting isn't important - what is, is that the buffer isn't
955 // malformed and code stays semantically equivalent.
956 assert_eq!(new_text, vec!["/* */\nfn main() {}\n"]);
957 }
958
959 #[test]
client_lens_run()960 fn client_lens_run() {
961 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("lens_run")).unwrap().build();
962 let root_path = p.root();
963 let mut rls = p.spawn_rls_async();
964
965 rls.request::<Initialize>(
966 0,
967 lsp_types::InitializeParams {
968 process_id: None,
969 root_uri: None,
970 root_path: Some(root_path.display().to_string()),
971 initialization_options: Some(json!({ "cmdRun": true})),
972 capabilities: Default::default(),
973 trace: None,
974 workspace_folders: None,
975 },
976 );
977
978 rls.wait_for_indexing();
979 assert!(rls.messages().iter().count() >= 7);
980
981 let lens = rls.request::<CodeLensRequest>(
982 1,
983 CodeLensParams {
984 text_document: TextDocumentIdentifier {
985 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
986 },
987 },
988 );
989
990 let expected = CodeLens {
991 command: Some(Command {
992 command: "rls.run".to_string(),
993 title: "Run test".to_string(),
994 arguments: Some(vec![json!({
995 "args": [ "test", "--", "--nocapture", "test_foo" ],
996 "binary": "cargo",
997 "env": { "RUST_BACKTRACE": "short" }
998 })]),
999 }),
1000 data: None,
1001 range: Range {
1002 start: Position { line: 4, character: 3 },
1003 end: Position { line: 4, character: 11 },
1004 },
1005 };
1006
1007 assert_eq!(lens, Some(vec![expected]));
1008 }
1009
1010 #[test]
1011 #[ignore] // Spurious in Rust CI, https://github.com/rust-lang/rust/issues/62225
client_find_definitions()1012 fn client_find_definitions() {
1013 const SRC: &str = r#"
1014 struct Foo {
1015 }
1016
1017 impl Foo {
1018 fn new() {
1019 }
1020 }
1021
1022 fn main() {
1023 Foo::new();
1024 }
1025 "#;
1026
1027 let p = project("simple_workspace")
1028 .file("Cargo.toml", &basic_bin_manifest("bar"))
1029 .file("src/main.rs", SRC)
1030 .build();
1031
1032 let root_path = p.root();
1033 let mut rls = p.spawn_rls_async();
1034
1035 // FIXME: Without `all_targets=false`, this test will randomly fail.
1036 let opts = json!({"settings": {"rust": {"racer_completion": false, "all_targets": false } } });
1037 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1038
1039 rls.wait_for_indexing();
1040
1041 let mut results = vec![];
1042 for (line_index, line) in SRC.lines().enumerate() {
1043 for i in 0..line.len() {
1044 let id = (line_index * 100 + i) as u64;
1045 let result = rls.request::<GotoDefinition>(
1046 id,
1047 TextDocumentPositionParams {
1048 position: Position { line: line_index as u64, character: i as u64 },
1049 text_document: TextDocumentIdentifier {
1050 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1051 },
1052 },
1053 );
1054
1055 let ranges: Vec<_> = result
1056 .into_iter()
1057 .flat_map(|x| match x {
1058 GotoDefinitionResponse::Scalar(loc) => vec![loc].into_iter(),
1059 GotoDefinitionResponse::Array(locs) => locs.into_iter(),
1060 _ => unreachable!(),
1061 })
1062 .map(|x| x.range)
1063 .collect();
1064
1065 if !ranges.is_empty() {
1066 results.push((line_index, i, ranges));
1067 }
1068 }
1069 }
1070
1071 // Foo
1072 let foo_definition = Range {
1073 start: Position { line: 1, character: 15 },
1074 end: Position { line: 1, character: 18 },
1075 };
1076
1077 // Foo::new
1078 let foo_new_definition = Range {
1079 start: Position { line: 5, character: 15 },
1080 end: Position { line: 5, character: 18 },
1081 };
1082
1083 // main
1084 let main_definition = Range {
1085 start: Position { line: 9, character: 11 },
1086 end: Position { line: 9, character: 15 },
1087 };
1088
1089 let expected = [
1090 // struct Foo
1091 (1, 15, vec![foo_definition]),
1092 (1, 16, vec![foo_definition]),
1093 (1, 17, vec![foo_definition]),
1094 (1, 18, vec![foo_definition]),
1095 // impl Foo
1096 (4, 13, vec![foo_definition]),
1097 (4, 14, vec![foo_definition]),
1098 (4, 15, vec![foo_definition]),
1099 (4, 16, vec![foo_definition]),
1100 // fn new
1101 (5, 15, vec![foo_new_definition]),
1102 (5, 16, vec![foo_new_definition]),
1103 (5, 17, vec![foo_new_definition]),
1104 (5, 18, vec![foo_new_definition]),
1105 // fn main
1106 (9, 11, vec![main_definition]),
1107 (9, 12, vec![main_definition]),
1108 (9, 13, vec![main_definition]),
1109 (9, 14, vec![main_definition]),
1110 (9, 15, vec![main_definition]),
1111 // Foo::new()
1112 (10, 12, vec![foo_definition]),
1113 (10, 13, vec![foo_definition]),
1114 (10, 14, vec![foo_definition]),
1115 (10, 15, vec![foo_definition]),
1116 (10, 17, vec![foo_new_definition]),
1117 (10, 18, vec![foo_new_definition]),
1118 (10, 19, vec![foo_new_definition]),
1119 (10, 20, vec![foo_new_definition]),
1120 ];
1121
1122 if results.len() != expected.len() {
1123 panic!(
1124 "Got different amount of completions than expected: {} vs. {}: {:#?}",
1125 results.len(),
1126 expected.len(),
1127 results
1128 )
1129 }
1130
1131 for (i, (actual, expected)) in results.iter().zip(expected.iter()).enumerate() {
1132 if actual != expected {
1133 panic!(
1134 "Found different definition at index {}. Got {:#?}, expected {:#?}",
1135 i, actual, expected
1136 )
1137 }
1138 }
1139 }
1140
1141 #[test]
1142 #[ignore] // Spurious in Rust CI, https://github.com/rust-lang/rust/pull/62805
client_deglob()1143 fn client_deglob() {
1144 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("deglob")).unwrap().build();
1145 let root_path = p.root();
1146 let mut rls = p.spawn_rls_async();
1147
1148 rls.request::<Initialize>(0, initialize_params(root_path));
1149
1150 rls.wait_for_indexing();
1151
1152 // Test a single swglob
1153 let commands = rls
1154 .request::<CodeActionRequest>(
1155 100,
1156 CodeActionParams {
1157 text_document: TextDocumentIdentifier {
1158 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1159 },
1160 range: Range { start: Position::new(2, 0), end: Position::new(2, 0) },
1161 context: CodeActionContext { diagnostics: vec![], only: None },
1162 },
1163 )
1164 .expect("No code actions returned for line 2");
1165
1166 // Right now we only support deglobbing via commands. Please update this
1167 // test if we move to making text edits via CodeAction (which we should for
1168 // deglobbing);
1169 let Command { title, command, arguments, .. } = match commands.into_iter().nth(0).unwrap() {
1170 CodeActionOrCommand::Command(commands) => commands,
1171 CodeActionOrCommand::CodeAction(_) => unimplemented!(),
1172 };
1173
1174 let arguments = arguments.expect("Missing command arguments");
1175
1176 assert_eq!(title, "Deglob import".to_string());
1177 assert!(command.starts_with("rls.deglobImports-"));
1178
1179 assert!(arguments[0]["new_text"].as_str() == Some("{Stdin, Stdout}"));
1180 assert_eq!(
1181 serde_json::from_value::<Location>(arguments[0]["location"].clone()).unwrap(),
1182 Location {
1183 range: Range { start: Position::new(2, 13), end: Position::new(2, 14) },
1184 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1185 }
1186 );
1187
1188 rls.request::<ExecuteCommand>(200, ExecuteCommandParams { command, arguments });
1189 // Right now the execute command returns an empty response and sends
1190 // appropriate apply edit request via a side-channel
1191 let result = rls
1192 .messages()
1193 .iter()
1194 .rfind(|msg| msg["method"] == ApplyWorkspaceEdit::METHOD)
1195 .unwrap()
1196 .clone();
1197 let params = <ApplyWorkspaceEdit as Request>::Params::deserialize(&result["params"])
1198 .expect("Couldn't deserialize params");
1199
1200 let (url, edits) = params.edit.changes.unwrap().drain().nth(0).unwrap();
1201 assert_eq!(url, Url::from_file_path(p.root().join("src/main.rs")).unwrap());
1202 assert_eq!(
1203 edits,
1204 vec![TextEdit {
1205 range: Range { start: Position::new(2, 13), end: Position::new(2, 14) },
1206 new_text: "{Stdin, Stdout}".to_string(),
1207 }]
1208 );
1209
1210 // Test a deglob for double wildcard
1211 let commands = rls
1212 .request::<CodeActionRequest>(
1213 1100,
1214 CodeActionParams {
1215 text_document: TextDocumentIdentifier {
1216 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1217 },
1218 range: Range { start: Position::new(5, 0), end: Position::new(5, 0) },
1219 context: CodeActionContext { diagnostics: vec![], only: None },
1220 },
1221 )
1222 .expect("No code actions returned for line 12");
1223
1224 // Right now we only support deglobbing via commands. Please update this
1225 // test if we move to making text edits via CodeAction (which we should for
1226 // deglobbing);
1227 let Command { title, command, arguments, .. } = match commands.into_iter().nth(0).unwrap() {
1228 CodeActionOrCommand::Command(commands) => commands,
1229 CodeActionOrCommand::CodeAction(_) => unimplemented!(),
1230 };
1231
1232 let arguments = arguments.expect("Missing command arguments");
1233
1234 assert_eq!(title, "Deglob imports".to_string());
1235 assert!(command.starts_with("rls.deglobImports-"));
1236 let expected = [(14, 15, "size_of"), (31, 32, "max")];
1237 for i in 0..2 {
1238 assert!(arguments[i]["new_text"].as_str() == Some(expected[i].2));
1239 assert_eq!(
1240 serde_json::from_value::<Location>(arguments[i]["location"].clone()).unwrap(),
1241 Location {
1242 range: Range {
1243 start: Position::new(5, expected[i].0),
1244 end: Position::new(5, expected[i].1),
1245 },
1246 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1247 }
1248 );
1249 }
1250
1251 rls.request::<ExecuteCommand>(1200, ExecuteCommandParams { command, arguments });
1252 // Right now the execute command returns an empty response and sends
1253 // appropriate apply edit request via a side-channel
1254 let result = rls
1255 .messages()
1256 .iter()
1257 .rfind(|msg| msg["method"] == ApplyWorkspaceEdit::METHOD)
1258 .unwrap()
1259 .clone();
1260 let params = <ApplyWorkspaceEdit as Request>::Params::deserialize(&result["params"])
1261 .expect("Couldn't deserialize params");
1262
1263 let (url, edits) = params.edit.changes.unwrap().drain().nth(0).unwrap();
1264 assert_eq!(url, Url::from_file_path(p.root().join("src/main.rs")).unwrap());
1265 assert_eq!(
1266 edits,
1267 expected
1268 .iter()
1269 .map(|e| TextEdit {
1270 range: Range { start: Position::new(5, e.0), end: Position::new(5, e.1) },
1271 new_text: e.2.to_string()
1272 })
1273 .collect::<Vec<_>>()
1274 );
1275 }
1276
is_notification_for_unknown_config(msg: &serde_json::Value) -> bool1277 fn is_notification_for_unknown_config(msg: &serde_json::Value) -> bool {
1278 msg["method"] == ShowMessage::METHOD
1279 && msg["params"]["message"].as_str().unwrap().contains("Unknown")
1280 }
1281
is_notification_for_deprecated_config(msg: &serde_json::Value) -> bool1282 fn is_notification_for_deprecated_config(msg: &serde_json::Value) -> bool {
1283 msg["method"] == ShowMessage::METHOD
1284 && msg["params"]["message"].as_str().unwrap().contains("is deprecated")
1285 }
1286
is_notification_for_duplicated_config(msg: &serde_json::Value) -> bool1287 fn is_notification_for_duplicated_config(msg: &serde_json::Value) -> bool {
1288 msg["method"] == ShowMessage::METHOD
1289 && msg["params"]["message"].as_str().unwrap().contains("Duplicate")
1290 }
1291
1292 #[test]
client_init_duplicated_and_unknown_settings()1293 fn client_init_duplicated_and_unknown_settings() {
1294 let p = project("simple_workspace")
1295 .file("Cargo.toml", &basic_bin_manifest("foo"))
1296 .file(
1297 "src/main.rs",
1298 r#"
1299 struct UnusedBin;
1300 fn main() {
1301 println!("Hello world!");
1302 }
1303 "#,
1304 )
1305 .build();
1306 let root_path = p.root();
1307 let mut rls = p.spawn_rls_async();
1308
1309 let opts = json!({
1310 "settings": {
1311 "rust": {
1312 "features": ["some_feature"],
1313 "all_targets": false,
1314 "unknown1": 1,
1315 "unknown2": false,
1316 "dup_val": 1,
1317 "dup_val": false,
1318 "dup_licated": "dup_lacated",
1319 "DupLicated": "DupLicated",
1320 "dup-licated": "dup-licated",
1321 // Deprecated
1322 "use_crate_blacklist": true
1323 }
1324 }
1325 });
1326
1327 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1328
1329 assert!(rls.messages().iter().any(is_notification_for_unknown_config));
1330 assert!(
1331 rls.messages().iter().any(is_notification_for_deprecated_config),
1332 "`use_crate_blacklist` should be marked as deprecated"
1333 );
1334 assert!(rls.messages().iter().any(is_notification_for_duplicated_config));
1335 }
1336
1337 #[test]
client_did_change_configuration_duplicated_and_unknown_settings()1338 fn client_did_change_configuration_duplicated_and_unknown_settings() {
1339 let p = project("simple_workspace")
1340 .file("Cargo.toml", &basic_bin_manifest("foo"))
1341 .file(
1342 "src/main.rs",
1343 r#"
1344 struct UnusedBin;
1345 fn main() {
1346 println!("Hello world!");
1347 }
1348 "#,
1349 )
1350 .build();
1351 let root_path = p.root();
1352 let mut rls = p.spawn_rls_async();
1353
1354 rls.request::<Initialize>(0, initialize_params(root_path));
1355
1356 assert!(!rls.messages().iter().any(is_notification_for_unknown_config));
1357 assert!(!rls.messages().iter().any(is_notification_for_duplicated_config));
1358 let settings = json!({
1359 "rust": {
1360 "features": ["some_feature"],
1361 "all_targets": false,
1362 "unknown1": 1,
1363 "unknown2": false,
1364 "dup_val": 1,
1365 "dup_val": false,
1366 "dup_licated": "dup_lacated",
1367 "DupLicated": "DupLicated",
1368 "dup-licated": "dup-licated"
1369 }
1370 });
1371 rls.notify::<DidChangeConfiguration>(DidChangeConfigurationParams { settings });
1372
1373 rls.wait_for_message(is_notification_for_unknown_config);
1374 if !rls.messages().iter().any(is_notification_for_duplicated_config) {
1375 rls.wait_for_message(is_notification_for_duplicated_config);
1376 }
1377 }
1378
1379 #[test]
client_shutdown()1380 fn client_shutdown() {
1381 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
1382 let root_path = p.root();
1383 let mut rls = p.spawn_rls_async();
1384
1385 rls.request::<Initialize>(0, initialize_params(root_path));
1386 }
1387
1388 #[test]
client_goto_def()1389 fn client_goto_def() {
1390 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
1391 let root_path = p.root();
1392 let mut rls = p.spawn_rls_async();
1393
1394 rls.request::<Initialize>(0, initialize_params(root_path));
1395
1396 rls.wait_for_indexing();
1397
1398 let result = rls.request::<GotoDefinition>(
1399 11,
1400 TextDocumentPositionParams {
1401 position: Position { line: 12, character: 27 },
1402 text_document: TextDocumentIdentifier {
1403 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1404 },
1405 },
1406 );
1407
1408 let ranges: Vec<_> = result
1409 .into_iter()
1410 .flat_map(|x| match x {
1411 GotoDefinitionResponse::Scalar(loc) => vec![loc].into_iter(),
1412 GotoDefinitionResponse::Array(locs) => locs.into_iter(),
1413 _ => unreachable!(),
1414 })
1415 .map(|x| x.range)
1416 .collect();
1417
1418 assert!(ranges.iter().any(|r| r.start == Position { line: 11, character: 8 }));
1419 }
1420
1421 #[test]
client_hover()1422 fn client_hover() {
1423 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
1424 let root_path = p.root();
1425 let mut rls = p.spawn_rls_async();
1426
1427 // FIXME: Without `all_targets=false`, this test will randomly fail.
1428 let opts = json!({"settings": {"rust": { "all_targets": false } } });
1429 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1430
1431 rls.wait_for_indexing();
1432
1433 let result = rls
1434 .request::<HoverRequest>(
1435 11,
1436 TextDocumentPositionParams {
1437 position: Position { line: 12, character: 27 },
1438 text_document: TextDocumentIdentifier {
1439 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1440 },
1441 },
1442 )
1443 .unwrap();
1444
1445 let contents = ["&str", "let world = \"world\";"];
1446 let mut contents: Vec<_> = contents.iter().map(ToString::to_string).collect();
1447 let contents =
1448 contents.drain(..).map(|value| LanguageString { language: "rust".to_string(), value });
1449 let contents = contents.map(MarkedString::LanguageString).collect();
1450
1451 assert_eq!(result.contents, HoverContents::Array(contents));
1452 }
1453
1454 /// Test hover continues to work after the source has moved line
1455 #[ignore] // FIXME(#1265): Spurious failure - sometimes we lose the semantic information from Rust - why?
1456 #[test]
client_hover_after_src_line_change()1457 fn client_hover_after_src_line_change() {
1458 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
1459 let root_path = p.root();
1460 let mut rls = p.spawn_rls_async();
1461
1462 let opts = json!({"settings": {"rust": {"racer_completion": false } } });
1463 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1464
1465 rls.wait_for_indexing();
1466
1467 let world_src_pos = Position { line: 12, character: 27 };
1468 let world_src_pos_after = Position { line: 13, character: 27 };
1469
1470 let result = rls
1471 .request::<HoverRequest>(
1472 11,
1473 TextDocumentPositionParams {
1474 position: world_src_pos,
1475 text_document: TextDocumentIdentifier {
1476 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1477 },
1478 },
1479 )
1480 .unwrap();
1481
1482 let contents = ["&str", "let world = \"world\";"];
1483 let contents: Vec<_> = contents
1484 .iter()
1485 .map(|value| LanguageString { language: "rust".to_string(), value: (*value).to_string() })
1486 .map(MarkedString::LanguageString)
1487 .collect();
1488
1489 assert_eq!(result.contents, HoverContents::Array(contents.clone()));
1490
1491 rls.notify::<DidChangeTextDocument>(DidChangeTextDocumentParams {
1492 content_changes: vec![TextDocumentContentChangeEvent {
1493 range: Some(Range {
1494 start: Position { line: 10, character: 15 },
1495 end: Position { line: 10, character: 15 },
1496 }),
1497 range_length: Some(0),
1498 text: "\n ".to_string(),
1499 }],
1500 text_document: VersionedTextDocumentIdentifier {
1501 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1502 version: Some(2),
1503 },
1504 });
1505
1506 rls.wait_for_indexing();
1507
1508 let result = rls
1509 .request::<HoverRequest>(
1510 11,
1511 TextDocumentPositionParams {
1512 position: world_src_pos_after,
1513 text_document: TextDocumentIdentifier {
1514 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1515 },
1516 },
1517 )
1518 .unwrap();
1519
1520 assert_eq!(result.contents, HoverContents::Array(contents));
1521 }
1522
1523 #[test]
client_workspace_symbol()1524 fn client_workspace_symbol() {
1525 let p =
1526 ProjectBuilder::try_from_fixture(fixtures_dir().join("workspace_symbol")).unwrap().build();
1527 let root_path = p.root();
1528 let mut rls = p.spawn_rls_async();
1529
1530 let opts = json!({"settings": {"rust": { "cfg_test": true } } });
1531 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1532
1533 rls.wait_for_indexing();
1534
1535 let symbols = rls
1536 .request::<WorkspaceSymbol>(42, WorkspaceSymbolParams { query: "nemo".to_owned() })
1537 .unwrap();
1538
1539 let mut nemos = vec![
1540 ("src/main.rs", "nemo", SymbolKind::Function, 1, 11, 1, 15, Some("x")),
1541 ("src/foo.rs", "nemo", SymbolKind::Module, 0, 4, 0, 8, Some("foo")),
1542 ];
1543
1544 for (file, name, kind, start_l, start_c, end_l, end_c, container_name) in nemos.drain(..) {
1545 let sym = SymbolInformation {
1546 name: name.to_string(),
1547 kind,
1548 container_name: container_name.map(ToString::to_string),
1549 location: Location {
1550 uri: Url::from_file_path(p.root().join(file)).unwrap(),
1551 range: Range {
1552 start: Position { line: start_l, character: start_c },
1553 end: Position { line: end_l, character: end_c },
1554 },
1555 },
1556 deprecated: None,
1557 };
1558 dbg!(&sym);
1559 assert!(symbols.iter().any(|s| *s == sym));
1560 }
1561 }
1562
1563 #[test]
client_workspace_symbol_duplicates()1564 fn client_workspace_symbol_duplicates() {
1565 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("workspace_symbol_duplicates"))
1566 .unwrap()
1567 .build();
1568 let root_path = p.root();
1569 let mut rls = p.spawn_rls_async();
1570
1571 let opts = json!({"settings": {"rust": { "cfg_test": true } } });
1572 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1573
1574 rls.wait_for_indexing();
1575
1576 let symbols = rls
1577 .request::<WorkspaceSymbol>(42, WorkspaceSymbolParams { query: "Frobnicator".to_owned() })
1578 .unwrap();
1579
1580 let symbol = SymbolInformation {
1581 name: "Frobnicator".to_string(),
1582 kind: SymbolKind::Struct,
1583 container_name: Some("a".to_string()),
1584 location: Location {
1585 uri: Url::from_file_path(p.root().join("src/shared.rs")).unwrap(),
1586 range: Range {
1587 start: Position { line: 1, character: 7 },
1588 end: Position { line: 1, character: 18 },
1589 },
1590 },
1591 deprecated: None,
1592 };
1593
1594 assert_eq!(symbols, vec![symbol]);
1595 }
1596
1597 #[ignore] // FIXME(#1265): This is spurious (we don't pick up reference under #[cfg(test)])-ed code - why?
1598 #[test]
client_find_all_refs_test()1599 fn client_find_all_refs_test() {
1600 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
1601 let root_path = p.root();
1602 let mut rls = p.spawn_rls_async();
1603
1604 let opts = json!({"settings": {"rust": {"all_targets": true } } });
1605 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1606
1607 rls.wait_for_indexing();
1608
1609 let result = rls
1610 .request::<References>(
1611 42,
1612 ReferenceParams {
1613 text_document_position: TextDocumentPositionParams {
1614 text_document: TextDocumentIdentifier {
1615 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1616 },
1617 position: Position { line: 0, character: 7 },
1618 },
1619 context: ReferenceContext { include_declaration: true },
1620 },
1621 )
1622 .unwrap();
1623
1624 let ranges = [((0, 7), (0, 10)), ((6, 14), (6, 17)), ((14, 15), (14, 18))];
1625 for ((sl, sc), (el, ec)) in &ranges {
1626 let range = Range {
1627 start: Position { line: *sl, character: *sc },
1628 end: Position { line: *el, character: *ec },
1629 };
1630
1631 dbg!(range);
1632 assert!(result.iter().any(|x| x.range == range));
1633 }
1634 }
1635
1636 #[test]
client_find_all_refs_no_cfg_test()1637 fn client_find_all_refs_no_cfg_test() {
1638 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("find_all_refs_no_cfg_test"))
1639 .unwrap()
1640 .build();
1641 let root_path = p.root();
1642 let mut rls = p.spawn_rls_async();
1643
1644 let opts = json!({"settings": {"rust": { "all_targets": false } } });
1645 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1646
1647 rls.wait_for_indexing();
1648
1649 let result = rls
1650 .request::<References>(
1651 42,
1652 ReferenceParams {
1653 text_document_position: TextDocumentPositionParams {
1654 text_document: TextDocumentIdentifier {
1655 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1656 },
1657 position: Position { line: 0, character: 7 },
1658 },
1659 context: ReferenceContext { include_declaration: true },
1660 },
1661 )
1662 .unwrap();
1663
1664 let ranges = [((0, 7), (0, 10)), ((13, 15), (13, 18))];
1665 for ((sl, sc), (el, ec)) in &ranges {
1666 let range = Range {
1667 start: Position { line: *sl, character: *sc },
1668 end: Position { line: *el, character: *ec },
1669 };
1670
1671 dbg!(range);
1672 assert!(result.iter().any(|x| x.range == range));
1673 }
1674 }
1675
1676 #[test]
client_borrow_error()1677 fn client_borrow_error() {
1678 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("borrow_error")).unwrap().build();
1679 let root_path = p.root();
1680 let mut rls = p.spawn_rls_async();
1681
1682 rls.request::<Initialize>(0, initialize_params(root_path));
1683
1684 let diag = rls.wait_for_diagnostics();
1685
1686 let msg = "cannot borrow `x` as mutable more than once at a time";
1687 assert!(diag.diagnostics.iter().any(|diag| diag.message.contains(msg)));
1688 }
1689
1690 #[test]
client_highlight()1691 fn client_highlight() {
1692 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
1693 let root_path = p.root();
1694 let mut rls = p.spawn_rls_async();
1695
1696 // FIXME: Without `all_targets=false`, this test will randomly fail.
1697 let opts = json!({"settings": {"rust": { "all_targets": false } } });
1698 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1699
1700 rls.wait_for_indexing();
1701
1702 let result = rls
1703 .request::<DocumentHighlightRequest>(
1704 42,
1705 TextDocumentPositionParams {
1706 position: Position { line: 12, character: 27 },
1707 text_document: TextDocumentIdentifier {
1708 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1709 },
1710 },
1711 )
1712 .unwrap();
1713
1714 let ranges = [((11, 8), (11, 13)), ((12, 27), (12, 32))];
1715 for ((sl, sc), (el, ec)) in &ranges {
1716 let range = Range {
1717 start: Position { line: *sl, character: *sc },
1718 end: Position { line: *el, character: *ec },
1719 };
1720
1721 dbg!(range);
1722 assert!(result.iter().any(|x| x.range == range));
1723 }
1724 }
1725
1726 #[test]
client_rename()1727 fn client_rename() {
1728 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
1729 let root_path = p.root();
1730 let mut rls = p.spawn_rls_async();
1731
1732 // FIXME: Without `all_targets=false`, this test will randomly fail.
1733 let opts = json!({"settings": {"rust": { "all_targets": false } } });
1734 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1735
1736 rls.wait_for_indexing();
1737
1738 let result = rls
1739 .request::<Rename>(
1740 42,
1741 RenameParams {
1742 text_document_position: TextDocumentPositionParams {
1743 position: Position { line: 12, character: 27 },
1744 text_document: TextDocumentIdentifier {
1745 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1746 },
1747 },
1748 new_name: "foo".to_owned(),
1749 },
1750 )
1751 .unwrap();
1752
1753 dbg!(&result);
1754
1755 let uri = Url::from_file_path(p.root().join("src/main.rs")).unwrap();
1756 let ranges = [((11, 8), (11, 13)), ((12, 27), (12, 32))];
1757 let ranges = ranges
1758 .iter()
1759 .map(|((sl, sc), (el, ec))| Range {
1760 start: Position { line: *sl, character: *sc },
1761 end: Position { line: *el, character: *ec },
1762 })
1763 .map(|range| TextEdit { range, new_text: "foo".to_string() });
1764
1765 let changes = std::iter::once((uri, ranges.collect())).collect();
1766
1767 assert_eq!(result.changes, Some(changes));
1768 }
1769
1770 #[test]
client_reformat()1771 fn client_reformat() {
1772 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("reformat")).unwrap().build();
1773 let root_path = p.root();
1774 let mut rls = p.spawn_rls_async();
1775
1776 rls.request::<Initialize>(0, initialize_params(root_path));
1777
1778 rls.wait_for_indexing();
1779
1780 let result = rls.request::<Formatting>(
1781 42,
1782 DocumentFormattingParams {
1783 text_document: TextDocumentIdentifier {
1784 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1785 },
1786 options: FormattingOptions {
1787 tab_size: 4,
1788 insert_spaces: true,
1789 properties: Default::default(),
1790 },
1791 },
1792 );
1793
1794 assert_eq!(result.unwrap()[0], TextEdit {
1795 range: Range {
1796 start: Position { line: 0, character: 0 },
1797 end: Position { line: 2, character: 0 },
1798 },
1799 new_text: "pub mod foo;\npub fn main() {\n let world = \"world\";\n println!(\"Hello, {}!\", world);\n}\n".to_string(),
1800 });
1801 }
1802
1803 #[test]
client_reformat_with_range()1804 fn client_reformat_with_range() {
1805 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("reformat_with_range"))
1806 .unwrap()
1807 .build();
1808 let root_path = p.root();
1809 let mut rls = p.spawn_rls_async();
1810
1811 rls.request::<Initialize>(0, initialize_params(root_path));
1812
1813 rls.wait_for_indexing();
1814
1815 let result = rls.request::<RangeFormatting>(
1816 42,
1817 DocumentRangeFormattingParams {
1818 text_document: TextDocumentIdentifier {
1819 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
1820 },
1821 range: Range {
1822 start: Position { line: 1, character: 0 },
1823 end: Position { line: 2, character: 0 },
1824 },
1825 options: FormattingOptions {
1826 tab_size: 4,
1827 insert_spaces: true,
1828 properties: Default::default(),
1829 },
1830 },
1831 );
1832
1833 let newline = if cfg!(windows) { "\r\n" } else { "\n" };
1834 let formatted = r#"pub fn main() {
1835 let world1 = "world";
1836 println!("Hello, {}!", world1);
1837 "#
1838 .replace("\r", "")
1839 .replace("\n", newline);
1840
1841 let edits = result.unwrap();
1842 assert_eq!(edits.len(), 2);
1843 assert_eq!(edits[0].new_text, formatted);
1844 assert_eq!(edits[1].new_text, newline);
1845 }
1846
1847 #[test]
client_multiple_binaries()1848 fn client_multiple_binaries() {
1849 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("multiple_bins")).unwrap().build();
1850 let root_path = p.root();
1851 let mut rls = p.spawn_rls_async();
1852
1853 let opts = json!({"settings": {"rust": { "build_bin": "bin2" } } });
1854 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1855
1856 rls.wait_for_indexing();
1857
1858 {
1859 let msgs = rls.messages();
1860 let diags = msgs
1861 .iter()
1862 .filter(|x| x["method"] == PublishDiagnostics::METHOD)
1863 .flat_map(|msg| msg["params"]["diagnostics"].as_array().unwrap())
1864 .map(|diag| diag["message"].as_str().unwrap())
1865 .collect::<Vec<&str>>();
1866
1867 for i in 1..3 {
1868 let msg = &format!("unused variable: `bin_name{}`", i);
1869 assert!(diags.iter().any(|message| message.starts_with(msg)));
1870 }
1871 }
1872 }
1873
1874 #[ignore] // Requires `rust-src` component, which isn't available in Rust CI.
1875 #[test]
client_completion()1876 fn client_completion() {
1877 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
1878 let root_path = p.root();
1879 let mut rls = p.spawn_rls_async();
1880
1881 rls.request::<Initialize>(0, initialize_params(root_path));
1882
1883 rls.wait_for_indexing();
1884
1885 let text_document =
1886 TextDocumentIdentifier { uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap() };
1887
1888 let completions = |x: CompletionResponse| match x {
1889 CompletionResponse::Array(items) => items,
1890 CompletionResponse::List(CompletionList { items, .. }) => items,
1891 };
1892
1893 macro_rules! item_eq {
1894 ($item:expr, $expected:expr) => {{
1895 let (label, kind, detail) = $expected;
1896 ($item.label == *label && $item.kind == *kind && $item.detail == *detail)
1897 }};
1898 }
1899
1900 let expected = [
1901 // FIXME(https://github.com/rust-lang/rls/issues/1205) - empty " " string
1902 ("world", &Some(CompletionItemKind::Variable), &Some("let world = \" \";".to_string())),
1903 ("x", &Some(CompletionItemKind::Field), &Some("x: u64".to_string())),
1904 ];
1905
1906 let result = rls.request::<Completion>(
1907 11,
1908 CompletionParams {
1909 text_document_position: TextDocumentPositionParams {
1910 text_document: text_document.clone(),
1911 position: Position { line: 12, character: 30 },
1912 },
1913 context: None,
1914 },
1915 );
1916 let items = completions(result.unwrap());
1917 assert!(items.iter().any(|item| item_eq!(item, expected[0])));
1918 let result = rls.request::<Completion>(
1919 11,
1920 CompletionParams {
1921 text_document_position: TextDocumentPositionParams {
1922 text_document,
1923 position: Position { line: 15, character: 30 },
1924 },
1925 context: None,
1926 },
1927 );
1928 let items = completions(result.unwrap());
1929 assert!(items.iter().any(|item| item_eq!(item, expected[1])));
1930 }
1931
1932 #[test]
client_bin_lib_project()1933 fn client_bin_lib_project() {
1934 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("bin_lib")).unwrap().build();
1935 let root_path = p.root();
1936 let mut rls = p.spawn_rls_async();
1937
1938 let opts = json!({"settings": {"rust": { "cfg_test": true, "build_bin": "bin_lib" } } });
1939 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
1940
1941 let diag: PublishDiagnosticsParams = rls.wait_for_diagnostics();
1942
1943 assert!(diag.uri.as_str().ends_with("bin_lib/tests/tests.rs"));
1944 assert_eq!(diag.diagnostics.len(), 1);
1945 assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Warning));
1946 assert!(diag.diagnostics[0].message.contains("unused variable: `unused_var`"));
1947 }
1948
1949 #[test]
client_infer_lib()1950 fn client_infer_lib() {
1951 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("infer_lib")).unwrap().build();
1952 let root_path = p.root();
1953 let mut rls = p.spawn_rls_async();
1954
1955 rls.request::<Initialize>(0, initialize_params(root_path));
1956
1957 let diag = rls.wait_for_diagnostics();
1958
1959 assert!(diag.uri.as_str().ends_with("src/lib.rs"));
1960 assert_eq!(diag.diagnostics.len(), 1);
1961 assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Warning));
1962 assert!(diag.diagnostics[0].message.contains("struct is never constructed: `UnusedLib`"));
1963 }
1964
1965 #[test]
client_omit_init_build()1966 fn client_omit_init_build() {
1967 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
1968 let root_path = p.root();
1969 let mut rls = p.spawn_rls_async();
1970
1971 const ID: u64 = 1337;
1972 let response = rls.future_msg(|msg| msg["id"] == json!(ID));
1973
1974 let opts = json!({ "omitInitBuild": true });
1975 rls.request::<Initialize>(ID, initialize_params_with_opts(root_path, opts));
1976
1977 // We need to assert that no other messages are received after a short
1978 // period of time (e.g. no build progress messages).
1979 std::thread::sleep(std::time::Duration::from_secs(1));
1980 rls.block_on(response).unwrap().unwrap();
1981
1982 assert_eq!(rls.messages().iter().count(), 1);
1983 }
1984
1985 #[test]
client_find_impls()1986 fn client_find_impls() {
1987 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("find_impls")).unwrap().build();
1988 let root_path = p.root();
1989 let mut rls = p.spawn_rls_async();
1990
1991 rls.request::<Initialize>(0, initialize_params(root_path));
1992
1993 rls.wait_for_indexing();
1994
1995 let uri = Url::from_file_path(p.root().join("src/main.rs")).unwrap();
1996
1997 let locations = |result: Option<GotoDefinitionResponse>| match result.unwrap() {
1998 GotoDefinitionResponse::Scalar(loc) => vec![loc],
1999 GotoDefinitionResponse::Array(locations) => locations,
2000 GotoDefinitionResponse::Link(mut links) => {
2001 links.drain(..).map(|l| Location { uri: l.target_uri, range: l.target_range }).collect()
2002 }
2003 };
2004
2005 let result = rls.request::<GotoImplementation>(
2006 1,
2007 TextDocumentPositionParams {
2008 text_document: TextDocumentIdentifier::new(uri.clone()),
2009 position: Position { line: 3, character: 7 }, // "Bar"
2010 },
2011 );
2012 let expected = [(9, 15, 9, 18), (10, 12, 10, 15)];
2013 let expected = expected.iter().map(|(a, b, c, d)| Location {
2014 uri: uri.clone(),
2015 range: Range {
2016 start: Position { line: *a, character: *b },
2017 end: Position { line: *c, character: *d },
2018 },
2019 });
2020 let locs = locations(result);
2021 for exp in expected {
2022 assert!(locs.iter().any(|x| *x == exp));
2023 }
2024
2025 let result = rls.request::<GotoImplementation>(
2026 1,
2027 TextDocumentPositionParams {
2028 text_document: TextDocumentIdentifier::new(uri.clone()),
2029 position: Position { line: 6, character: 6 }, // "Super"
2030 },
2031 );
2032 let expected = [(9, 15, 9, 18), (13, 15, 13, 18)];
2033 let expected = expected.iter().map(|(a, b, c, d)| Location {
2034 uri: uri.clone(),
2035 range: Range {
2036 start: Position { line: *a, character: *b },
2037 end: Position { line: *c, character: *d },
2038 },
2039 });
2040 let locs = locations(result);
2041 for exp in expected {
2042 assert!(locs.iter().any(|x| *x == exp));
2043 }
2044 }
2045
2046 #[test]
client_features()2047 fn client_features() {
2048 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("features")).unwrap().build();
2049 let root_path = p.root();
2050 let mut rls = p.spawn_rls_async();
2051
2052 let opts = json!({"settings": {"rust": {"features": ["bar", "baz"] } } });
2053 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
2054
2055 let diag = rls.wait_for_diagnostics();
2056
2057 assert_eq!(diag.diagnostics.len(), 1);
2058 assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Error));
2059 let msg = "cannot find struct, variant or union type `Foo` in this scope";
2060 assert!(diag.diagnostics[0].message.contains(msg));
2061 }
2062
2063 #[test]
client_all_features()2064 fn client_all_features() {
2065 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("features")).unwrap().build();
2066 let root_path = p.root();
2067 let mut rls = p.spawn_rls_async();
2068
2069 let opts = json!({"settings": {"rust": {"all_features": true } } });
2070 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
2071
2072 rls.wait_for_indexing();
2073
2074 assert_eq!(
2075 rls.messages().iter().filter(|x| x["method"] == PublishDiagnostics::METHOD).count(),
2076 0
2077 );
2078 }
2079
2080 #[test]
client_no_default_features()2081 fn client_no_default_features() {
2082 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("features")).unwrap().build();
2083 let root_path = p.root();
2084 let mut rls = p.spawn_rls_async();
2085
2086 let opts = json!({"settings": {"rust":
2087 { "no_default_features": true, "features": ["foo", "bar"] } } });
2088 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
2089
2090 let diag = rls.wait_for_diagnostics();
2091
2092 let diagnostics: Vec<_> =
2093 diag.diagnostics.iter().filter(|d| d.severity == Some(DiagnosticSeverity::Error)).collect();
2094 assert_eq!(diagnostics.len(), 1);
2095 assert_eq!(diagnostics[0].severity, Some(DiagnosticSeverity::Error));
2096 let msg = "cannot find struct, variant or union type `Baz` in this scope";
2097 assert!(diagnostics[0].message.contains(msg));
2098 }
2099
2100 #[test]
client_all_targets()2101 fn client_all_targets() {
2102 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("bin_lib")).unwrap().build();
2103 let root_path = p.root();
2104 let mut rls = p.spawn_rls_async();
2105
2106 let opts = json!({"settings": {"rust": { "cfg_test": true, "all_targets": true } } });
2107 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
2108
2109 let diag: PublishDiagnosticsParams = rls.wait_for_diagnostics();
2110
2111 assert!(diag.uri.as_str().ends_with("bin_lib/tests/tests.rs"));
2112 assert_eq!(diag.diagnostics.len(), 1);
2113 assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Warning));
2114 assert!(diag.diagnostics[0].message.contains("unused variable: `unused_var`"));
2115 }
2116
2117 /// Handle receiving a notification before the `initialize` request by ignoring and
2118 /// continuing to run
2119 #[test]
client_ignore_uninitialized_notification()2120 fn client_ignore_uninitialized_notification() {
2121 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
2122 let root_path = p.root();
2123 let mut rls = p.spawn_rls_async();
2124
2125 rls.notify::<DidChangeConfiguration>(DidChangeConfigurationParams { settings: json!({}) });
2126 rls.request::<Initialize>(0, initialize_params(root_path));
2127
2128 rls.wait_for_indexing();
2129 }
2130
2131 /// Handle receiving requests before the `initialize` request by returning an error response
2132 /// and continuing to run
2133 #[test]
client_fail_uninitialized_request()2134 fn client_fail_uninitialized_request() {
2135 let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build();
2136 let mut rls = p.spawn_rls_async();
2137
2138 const ID: u64 = 1337;
2139
2140 rls.request::<GotoDefinition>(
2141 ID,
2142 TextDocumentPositionParams {
2143 text_document: TextDocumentIdentifier {
2144 uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(),
2145 },
2146 position: Position { line: 0, character: 0 },
2147 },
2148 );
2149
2150 rls.block_on(async { tokio::time::sleep(Duration::from_secs(1)).await }).unwrap();
2151
2152 let err = jsonrpc_core::Failure::deserialize(rls.messages().last().unwrap()).unwrap();
2153 assert_eq!(err.id, jsonrpc_core::Id::Num(ID));
2154 assert_eq!(err.error.code, jsonrpc_core::ErrorCode::ServerError(-32002));
2155 assert_eq!(err.error.message, "not yet received `initialize` request");
2156 }
2157
2158 // Test that RLS can accept configuration with config keys in 4 different cases:
2159 // - mixedCase
2160 // - CamelCase
2161 // - snake_case
2162 // - kebab-case
client_init_impl(convert_case: fn(&str) -> String)2163 fn client_init_impl(convert_case: fn(&str) -> String) {
2164 let p = project("config_cases")
2165 .file("Cargo.toml", &basic_bin_manifest("foo"))
2166 .file(
2167 "src/main.rs",
2168 r#"
2169 #![allow(dead_code)]
2170 struct NonCfg;
2171 #[cfg(test)]
2172 struct CfgTest { inner: PathBuf }
2173 fn main() {}
2174 "#,
2175 )
2176 .build();
2177
2178 let root_path = p.root();
2179 let mut rls = p.spawn_rls_async();
2180
2181 let opts = json!({ "settings": { "rust": { convert_case("all_targets"): true } } });
2182 rls.request::<Initialize>(0, initialize_params_with_opts(root_path, opts));
2183
2184 let diag = rls.wait_for_diagnostics();
2185
2186 assert_eq!(diag.diagnostics.len(), 1);
2187 assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Error));
2188 let msg = "cannot find type `PathBuf` in this scope";
2189 assert!(diag.diagnostics[0].message.contains(msg));
2190 }
2191
2192 #[test]
client_init_with_configuration_mixed_case()2193 fn client_init_with_configuration_mixed_case() {
2194 client_init_impl(heck::MixedCase::to_mixed_case);
2195 }
2196
2197 #[test]
client_init_with_configuration_camel_case()2198 fn client_init_with_configuration_camel_case() {
2199 client_init_impl(heck::CamelCase::to_camel_case);
2200 }
2201
2202 #[test]
client_init_with_configuration_snake_case()2203 fn client_init_with_configuration_snake_case() {
2204 client_init_impl(heck::SnakeCase::to_snake_case);
2205 }
2206
2207 #[test]
client_init_with_configuration_kebab_case()2208 fn client_init_with_configuration_kebab_case() {
2209 client_init_impl(heck::KebabCase::to_kebab_case);
2210 }
2211
2212 #[test]
client_parse_error_on_malformed_input()2213 fn client_parse_error_on_malformed_input() {
2214 use crate::support::rls_exe;
2215 use std::io::{Read, Write};
2216 use std::process::{Command, Stdio};
2217
2218 let mut cmd = Command::new(rls_exe())
2219 .stdin(Stdio::piped())
2220 .stdout(Stdio::piped())
2221 .stderr(Stdio::null())
2222 .spawn()
2223 .unwrap();
2224
2225 cmd.stdin.take().unwrap().write_all(b"Malformed input").unwrap();
2226 let mut output = vec![];
2227 cmd.stdout.take().unwrap().read_to_end(&mut output).unwrap();
2228 let output = String::from_utf8(output).unwrap();
2229
2230 assert_eq!(output, "Content-Length: 75\r\n\r\n{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}");
2231
2232 // Right now parse errors shutdown the RLS, which we might want to revisit
2233 // to provide better fault tolerance.
2234 cmd.wait().unwrap();
2235 }
2236
2237 #[test]
client_cargo_target_directory_is_excluded_from_backups()2238 fn client_cargo_target_directory_is_excluded_from_backups() {
2239 // This is to make sure that if it's rls that crates target/ directory the directory is
2240 // excluded from backups just as if it was created by cargo itself. See a comment in
2241 // run_cargo_ws() or rust-lang/cargo@cf3bfc9/rust-lang/cargo#8378 for more information.
2242 let p = project("backup_exclusion_workspace")
2243 .file("Cargo.toml", &basic_bin_manifest("foo"))
2244 .file(
2245 "src/main.rs",
2246 r#"
2247 fn main() {
2248 println!("Hello world!");
2249 }
2250 "#,
2251 )
2252 .build();
2253 let root_path = p.root();
2254 let mut rls = p.spawn_rls_async();
2255 rls.request::<Initialize>(0, initialize_params(root_path));
2256 let _ = rls.wait_for_indexing();
2257 let cachedir_tag = p.root().join("target").join("CACHEDIR.TAG");
2258 assert!(cachedir_tag.is_file());
2259 assert!(fs::read_to_string(&cachedir_tag)
2260 .unwrap()
2261 .starts_with("Signature: 8a477f597d28d172789f06886806bc55"));
2262 }
2263