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