]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/tests/heavy_tests/main.rs
Begin transition to new fields for JsonProject crate cfgs
[rust.git] / crates / rust-analyzer / tests / heavy_tests / main.rs
1 mod support;
2
3 use std::{collections::HashMap, path::PathBuf, time::Instant};
4
5 use lsp_types::{
6     CodeActionContext, DidOpenTextDocumentParams, DocumentFormattingParams, FormattingOptions,
7     GotoDefinitionParams, HoverParams, PartialResultParams, Position, Range, TextDocumentItem,
8     TextDocumentPositionParams, WorkDoneProgressParams,
9 };
10 use rust_analyzer::req::{
11     CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
12     Formatting, GotoDefinition, HoverRequest, OnEnter, Runnables, RunnablesParams,
13 };
14 use serde_json::json;
15 use tempfile::TempDir;
16 use test_utils::skip_slow_tests;
17
18 use crate::support::{project, Project};
19
20 const PROFILE: &str = "";
21 // const PROFILE: &'static str = "*@3>100";
22
23 #[test]
24 fn completes_items_from_standard_library() {
25     if skip_slow_tests() {
26         return;
27     }
28
29     let project_start = Instant::now();
30     let server = Project::with_fixture(
31         r#"
32 //- Cargo.toml
33 [package]
34 name = "foo"
35 version = "0.0.0"
36
37 //- src/lib.rs
38 use std::collections::Spam;
39 "#,
40     )
41     .with_sysroot(true)
42     .server();
43     server.wait_until_workspace_is_loaded();
44     eprintln!("loading took    {:?}", project_start.elapsed());
45     let completion_start = Instant::now();
46     let res = server.send_request::<Completion>(CompletionParams {
47         text_document_position: TextDocumentPositionParams::new(
48             server.doc_id("src/lib.rs"),
49             Position::new(0, 23),
50         ),
51         context: None,
52         partial_result_params: PartialResultParams::default(),
53         work_done_progress_params: WorkDoneProgressParams::default(),
54     });
55     assert!(format!("{}", res).contains("HashMap"));
56     eprintln!("completion took {:?}", completion_start.elapsed());
57 }
58
59 #[test]
60 fn test_runnables_no_project() {
61     if skip_slow_tests() {
62         return;
63     }
64
65     let server = project(
66         r"
67 //- lib.rs
68 #[test]
69 fn foo() {
70 }
71 ",
72     );
73     server.wait_until_workspace_is_loaded();
74     server.request::<Runnables>(
75         RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
76         json!([
77           {
78             "args": [ "test" ],
79             "extraArgs": [ "foo", "--nocapture" ],
80             "bin": "cargo",
81             "env": { "RUST_BACKTRACE": "short" },
82             "cwd": null,
83             "label": "test foo",
84             "range": {
85               "end": { "character": 1, "line": 2 },
86               "start": { "character": 0, "line": 0 }
87             }
88           },
89           {
90             "args": ["check", "--workspace"],
91             "extraArgs": [],
92             "bin": "cargo",
93             "env": {},
94             "cwd": null,
95             "label": "cargo check --workspace",
96             "range": {
97               "end": { "character": 0, "line": 0 },
98               "start": { "character": 0, "line": 0 }
99             }
100           }
101         ]),
102     );
103 }
104
105 #[test]
106 fn test_runnables_project() {
107     if skip_slow_tests() {
108         return;
109     }
110
111     let code = r#"
112 //- foo/Cargo.toml
113 [package]
114 name = "foo"
115 version = "0.0.0"
116
117 //- foo/src/lib.rs
118 pub fn foo() {}
119
120 //- foo/tests/spam.rs
121 #[test]
122 fn test_eggs() {}
123
124 //- bar/Cargo.toml
125 [package]
126 name = "bar"
127 version = "0.0.0"
128
129 //- bar/src/main.rs
130 fn main() {}
131 "#;
132
133     let server = Project::with_fixture(code).root("foo").root("bar").server();
134
135     server.wait_until_workspace_is_loaded();
136     server.request::<Runnables>(
137         RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
138         json!([
139             {
140               "args": [ "test", "--package", "foo", "--test", "spam" ],
141               "extraArgs": [ "test_eggs", "--exact", "--nocapture" ],
142               "bin": "cargo",
143               "env": { "RUST_BACKTRACE": "short" },
144               "label": "test test_eggs",
145               "range": {
146                 "end": { "character": 17, "line": 1 },
147                 "start": { "character": 0, "line": 0 }
148               },
149               "cwd": server.path().join("foo")
150             },
151             {
152               "args": [ "check", "--package", "foo", "--test", "spam" ],
153               "extraArgs": [],
154               "bin": "cargo",
155               "env": {},
156               "label": "cargo check -p foo",
157               "range": {
158                 "end": { "character": 0, "line": 0 },
159                 "start": { "character": 0, "line": 0 }
160               },
161               "cwd": server.path().join("foo")
162             },
163             {
164               "args": [ "test", "--package", "foo", "--test", "spam" ],
165               "extraArgs": [],
166               "bin": "cargo",
167               "env": {},
168               "label": "cargo test -p foo",
169               "range": {
170                 "end": { "character": 0, "line": 0 },
171                 "start": { "character": 0, "line": 0 }
172               },
173               "cwd": server.path().join("foo")
174             }
175         ]),
176     );
177 }
178
179 #[test]
180 fn test_format_document() {
181     if skip_slow_tests() {
182         return;
183     }
184
185     let server = project(
186         r#"
187 //- Cargo.toml
188 [package]
189 name = "foo"
190 version = "0.0.0"
191
192 //- src/lib.rs
193 mod bar;
194
195 fn main() {
196 }
197
198 pub use std::collections::HashMap;
199 "#,
200     );
201     server.wait_until_workspace_is_loaded();
202
203     server.request::<Formatting>(
204         DocumentFormattingParams {
205             text_document: server.doc_id("src/lib.rs"),
206             options: FormattingOptions {
207                 tab_size: 4,
208                 insert_spaces: false,
209                 insert_final_newline: None,
210                 trim_final_newlines: None,
211                 trim_trailing_whitespace: None,
212                 properties: HashMap::new(),
213             },
214             work_done_progress_params: WorkDoneProgressParams::default(),
215         },
216         json!([
217             {
218                 "newText": r#"mod bar;
219
220 fn main() {}
221
222 pub use std::collections::HashMap;
223 "#,
224                 "range": {
225                     "end": {
226                         "character": 0,
227                         "line": 7
228                     },
229                     "start": {
230                         "character": 0,
231                         "line": 0
232                     }
233                 }
234             }
235         ]),
236     );
237 }
238
239 #[test]
240 fn test_format_document_2018() {
241     if skip_slow_tests() {
242         return;
243     }
244
245     let server = project(
246         r#"
247 //- Cargo.toml
248 [package]
249 name = "foo"
250 version = "0.0.0"
251 edition = "2018"
252
253 //- src/lib.rs
254 mod bar;
255
256 async fn test() {
257 }
258
259 fn main() {
260 }
261
262 pub use std::collections::HashMap;
263 "#,
264     );
265     server.wait_until_workspace_is_loaded();
266
267     server.request::<Formatting>(
268         DocumentFormattingParams {
269             text_document: server.doc_id("src/lib.rs"),
270             options: FormattingOptions {
271                 tab_size: 4,
272                 insert_spaces: false,
273                 properties: HashMap::new(),
274                 insert_final_newline: None,
275                 trim_final_newlines: None,
276                 trim_trailing_whitespace: None,
277             },
278             work_done_progress_params: WorkDoneProgressParams::default(),
279         },
280         json!([
281             {
282                 "newText": r#"mod bar;
283
284 async fn test() {}
285
286 fn main() {}
287
288 pub use std::collections::HashMap;
289 "#,
290                 "range": {
291                     "end": {
292                         "character": 0,
293                         "line": 10
294                     },
295                     "start": {
296                         "character": 0,
297                         "line": 0
298                     }
299                 }
300             }
301         ]),
302     );
303 }
304
305 #[test]
306 fn test_missing_module_code_action() {
307     if skip_slow_tests() {
308         return;
309     }
310
311     let server = project(
312         r#"
313 //- Cargo.toml
314 [package]
315 name = "foo"
316 version = "0.0.0"
317
318 //- src/lib.rs
319 mod bar;
320
321 fn main() {}
322 "#,
323     );
324     server.wait_until_workspace_is_loaded();
325     let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
326     server.request::<CodeActionRequest>(
327         CodeActionParams {
328             text_document: server.doc_id("src/lib.rs"),
329             range: Range::new(Position::new(0, 4), Position::new(0, 7)),
330             context: empty_context(),
331             partial_result_params: PartialResultParams::default(),
332             work_done_progress_params: WorkDoneProgressParams::default(),
333         },
334         json!([
335           {
336             "command": {
337               "arguments": [
338                 {
339                   "cursorPosition": null,
340                   "label": "create module",
341                   "workspaceEdit": {
342                     "documentChanges": [
343                       {
344                         "kind": "create",
345                         "uri": "file:///[..]/src/bar.rs"
346                       }
347                     ]
348                   }
349                 }
350               ],
351               "command": "rust-analyzer.applySourceChange",
352               "title": "create module"
353             },
354             "title": "create module"
355           }
356         ]),
357     );
358
359     server.request::<CodeActionRequest>(
360         CodeActionParams {
361             text_document: server.doc_id("src/lib.rs"),
362             range: Range::new(Position::new(2, 4), Position::new(2, 7)),
363             context: empty_context(),
364             partial_result_params: PartialResultParams::default(),
365             work_done_progress_params: WorkDoneProgressParams::default(),
366         },
367         json!([]),
368     );
369 }
370
371 #[test]
372 fn test_missing_module_code_action_in_json_project() {
373     if skip_slow_tests() {
374         return;
375     }
376
377     let tmp_dir = TempDir::new().unwrap();
378
379     let path = tmp_dir.path();
380
381     let project = json!({
382         "roots": [path],
383         "crates": [ {
384             "root_module": path.join("src/lib.rs"),
385             "deps": [],
386             "edition": "2015",
387             "cfg": [ "cfg_atom_1", "feature=cfg_1"],
388             "atom_cfgs": ["atom_2"],
389             "key_value_cfgs": { "feature": "key_value_feature", "other": "value"}
390         } ]
391     });
392
393     let code = format!(
394         r#"
395 //- rust-project.json
396 {PROJECT}
397
398 //- src/lib.rs
399 mod bar;
400
401 fn main() {{}}
402 "#,
403         PROJECT = project.to_string(),
404     );
405
406     let server = Project::with_fixture(&code).tmp_dir(tmp_dir).server();
407
408     server.wait_until_workspace_is_loaded();
409     let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
410     server.request::<CodeActionRequest>(
411         CodeActionParams {
412             text_document: server.doc_id("src/lib.rs"),
413             range: Range::new(Position::new(0, 4), Position::new(0, 7)),
414             context: empty_context(),
415             partial_result_params: PartialResultParams::default(),
416             work_done_progress_params: WorkDoneProgressParams::default(),
417         },
418         json!([
419           {
420             "command": {
421               "arguments": [
422                 {
423                   "cursorPosition": null,
424                   "label": "create module",
425                   "workspaceEdit": {
426                     "documentChanges": [
427                       {
428                         "kind": "create",
429                         "uri": "file:///[..]/src/bar.rs"
430                       }
431                     ]
432                   }
433                 }
434               ],
435               "command": "rust-analyzer.applySourceChange",
436               "title": "create module"
437             },
438             "title": "create module"
439           }
440         ]),
441     );
442
443     server.request::<CodeActionRequest>(
444         CodeActionParams {
445             text_document: server.doc_id("src/lib.rs"),
446             range: Range::new(Position::new(2, 4), Position::new(2, 7)),
447             context: empty_context(),
448             partial_result_params: PartialResultParams::default(),
449             work_done_progress_params: WorkDoneProgressParams::default(),
450         },
451         json!([]),
452     );
453 }
454
455 #[test]
456 fn diagnostics_dont_block_typing() {
457     if skip_slow_tests() {
458         return;
459     }
460
461     let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect();
462     let libs: String = (0..10).map(|i| format!("//- src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
463     let server = Project::with_fixture(&format!(
464         r#"
465 //- Cargo.toml
466 [package]
467 name = "foo"
468 version = "0.0.0"
469
470 //- src/lib.rs
471 {}
472
473 {}
474
475 fn main() {{}}
476 "#,
477         librs, libs
478     ))
479     .with_sysroot(true)
480     .server();
481
482     server.wait_until_workspace_is_loaded();
483     for i in 0..10 {
484         server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
485             text_document: TextDocumentItem {
486                 uri: server.doc_id(&format!("src/m{}.rs", i)).uri,
487                 language_id: "rust".to_string(),
488                 version: 0,
489                 text: "/// Docs\nfn foo() {}".to_string(),
490             },
491         });
492     }
493     let start = std::time::Instant::now();
494     server.request::<OnEnter>(
495         TextDocumentPositionParams {
496             text_document: server.doc_id("src/m0.rs"),
497             position: Position { line: 0, character: 5 },
498         },
499         json!({
500           "cursorPosition": {
501             "position": { "character": 4, "line": 1 },
502             "textDocument": { "uri": "file:///[..]src/m0.rs" }
503           },
504           "label": "on enter",
505           "workspaceEdit": {
506             "documentChanges": [
507               {
508                 "edits": [
509                   {
510                     "newText": "\n/// ",
511                     "range": {
512                       "end": { "character": 5, "line": 0 },
513                       "start": { "character": 5, "line": 0 }
514                     }
515                   }
516                 ],
517                 "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null }
518               }
519             ]
520           }
521         }),
522     );
523     let elapsed = start.elapsed();
524     assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
525 }
526
527 #[test]
528 fn preserves_dos_line_endings() {
529     if skip_slow_tests() {
530         return;
531     }
532
533     let server = Project::with_fixture(
534         &"
535 //- Cargo.toml
536 [package]
537 name = \"foo\"
538 version = \"0.0.0\"
539
540 //- src/main.rs
541 /// Some Docs\r\nfn main() {}
542 ",
543     )
544     .server();
545
546     server.request::<OnEnter>(
547         TextDocumentPositionParams {
548             text_document: server.doc_id("src/main.rs"),
549             position: Position { line: 0, character: 8 },
550         },
551         json!({
552           "cursorPosition": {
553             "position": { "line": 1, "character": 4 },
554             "textDocument": { "uri": "file:///[..]src/main.rs" }
555           },
556           "label": "on enter",
557           "workspaceEdit": {
558             "documentChanges": [
559               {
560                 "edits": [
561                   {
562                     "newText": "\r\n/// ",
563                     "range": {
564                       "end": { "line": 0, "character": 8 },
565                       "start": { "line": 0, "character": 8 }
566                     }
567                   }
568                 ],
569                 "textDocument": { "uri": "file:///[..]src/main.rs", "version": null }
570               }
571             ]
572           }
573         }),
574     );
575 }
576
577 #[test]
578 fn resolve_include_concat_env() {
579     if skip_slow_tests() {
580         return;
581     }
582
583     let server = Project::with_fixture(
584         r###"
585 //- Cargo.toml
586 [package]
587 name = "foo"
588 version = "0.0.0"
589
590 //- build.rs
591 use std::{env, fs, path::Path};
592
593 fn main() {
594     let out_dir = env::var_os("OUT_DIR").unwrap();
595     let dest_path = Path::new(&out_dir).join("hello.rs");
596     fs::write(
597         &dest_path,
598         r#"pub fn message() -> &'static str { "Hello, World!" }"#,
599     )
600     .unwrap();
601     println!("cargo:rerun-if-changed=build.rs");
602 }
603 //- src/main.rs
604 include!(concat!(env!("OUT_DIR"), "/hello.rs"));
605
606 fn main() { message(); }
607 "###,
608     )
609     .with_config(|config| {
610         config.cargo.load_out_dirs_from_check = true;
611     })
612     .server();
613     server.wait_until_workspace_is_loaded();
614     let res = server.send_request::<GotoDefinition>(GotoDefinitionParams {
615         text_document_position_params: TextDocumentPositionParams::new(
616             server.doc_id("src/main.rs"),
617             Position::new(2, 15),
618         ),
619         work_done_progress_params: Default::default(),
620         partial_result_params: Default::default(),
621     });
622     assert!(format!("{}", res).contains("hello.rs"));
623 }
624
625 #[test]
626 fn resolve_proc_macro() {
627     if skip_slow_tests() {
628         return;
629     }
630     let server = Project::with_fixture(
631         r###"
632 //- foo/Cargo.toml
633 [package]
634 name = "foo"
635 version = "0.0.0"
636 edition = "2018"
637 [dependencies]
638 bar = {path = "../bar"}
639
640 //- foo/src/main.rs
641 use bar::Bar;
642 trait Bar {
643   fn bar();
644 }
645 #[derive(Bar)]
646 struct Foo {}
647 fn main() {
648   Foo::bar();
649 }
650
651 //- bar/Cargo.toml
652 [package]
653 name = "bar"
654 version = "0.0.0"
655 edition = "2018"
656
657 [lib]
658 proc-macro = true
659
660 //- bar/src/lib.rs
661 extern crate proc_macro;
662 use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
663 macro_rules! t {
664     ($n:literal) => {
665         TokenTree::from(Ident::new($n, Span::call_site()))
666     };
667     ({}) => {
668         TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
669     };
670     (()) => {
671         TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
672     };
673 }
674 #[proc_macro_derive(Bar)]
675 pub fn foo(_input: TokenStream) -> TokenStream {
676     // We hard code the output here for preventing to use any deps
677     let mut res = TokenStream::new();
678
679     // impl Bar for Foo { fn bar() {} }
680     let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")];
681     let mut fn_stream = TokenStream::new();
682     fn_stream.extend(vec![t!("fn"), t!("bar"), t!(()), t!({})]);
683     tokens.push(Group::new(Delimiter::Brace, fn_stream).into());
684     res.extend(tokens);
685     res
686 }
687
688 "###,
689     )
690     .with_config(|config| {
691         let macro_srv_path = PathBuf::from(env!("CARGO_BIN_EXE_rust-analyzer"));
692
693         config.cargo.load_out_dirs_from_check = true;
694         config.proc_macro_srv = Some((macro_srv_path, vec!["proc-macro".into()]));
695     })
696     .root("foo")
697     .root("bar")
698     .server();
699     server.wait_until_workspace_is_loaded();
700     let res = server.send_request::<HoverRequest>(HoverParams {
701         text_document_position_params: TextDocumentPositionParams::new(
702             server.doc_id("foo/src/main.rs"),
703             Position::new(7, 9),
704         ),
705         work_done_progress_params: Default::default(),
706     });
707
708     let value = res.get("contents").unwrap().get("value").unwrap().to_string();
709     assert_eq!(value, r#""```rust\nfoo::Bar\nfn bar()\n```""#)
710 }