3 use std::{collections::HashMap, path::PathBuf, time::Instant};
6 CodeActionContext, DidOpenTextDocumentParams, DocumentFormattingParams, FormattingOptions,
7 GotoDefinitionParams, HoverParams, PartialResultParams, Position, Range, TextDocumentItem,
8 TextDocumentPositionParams, WorkDoneProgressParams,
10 use rust_analyzer::req::{
11 CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
12 Formatting, GotoDefinition, HoverRequest, OnEnter, Runnables, RunnablesParams,
15 use tempfile::TempDir;
16 use test_utils::skip_slow_tests;
18 use crate::support::{project, Project};
20 const PROFILE: &str = "";
21 // const PROFILE: &'static str = "*@3>100";
24 fn completes_items_from_standard_library() {
25 if skip_slow_tests() {
29 let project_start = Instant::now();
30 let server = Project::with_fixture(
38 use std::collections::Spam;
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"),
52 partial_result_params: PartialResultParams::default(),
53 work_done_progress_params: WorkDoneProgressParams::default(),
55 assert!(format!("{}", res).contains("HashMap"));
56 eprintln!("completion took {:?}", completion_start.elapsed());
60 fn test_runnables_no_project() {
61 if skip_slow_tests() {
73 server.wait_until_workspace_is_loaded();
74 server.request::<Runnables>(
75 RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
79 "extraArgs": [ "foo", "--nocapture" ],
81 "env": { "RUST_BACKTRACE": "short" },
85 "end": { "character": 1, "line": 2 },
86 "start": { "character": 0, "line": 0 }
90 "args": ["check", "--workspace"],
95 "label": "cargo check --workspace",
97 "end": { "character": 0, "line": 0 },
98 "start": { "character": 0, "line": 0 }
106 fn test_runnables_project() {
107 if skip_slow_tests() {
120 //- foo/tests/spam.rs
133 let server = Project::with_fixture(code).root("foo").root("bar").server();
135 server.wait_until_workspace_is_loaded();
136 server.request::<Runnables>(
137 RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
140 "args": [ "test", "--package", "foo", "--test", "spam" ],
141 "extraArgs": [ "test_eggs", "--exact", "--nocapture" ],
143 "env": { "RUST_BACKTRACE": "short" },
144 "label": "test test_eggs",
146 "end": { "character": 17, "line": 1 },
147 "start": { "character": 0, "line": 0 }
149 "cwd": server.path().join("foo")
152 "args": [ "check", "--package", "foo", "--test", "spam" ],
156 "label": "cargo check -p foo",
158 "end": { "character": 0, "line": 0 },
159 "start": { "character": 0, "line": 0 }
161 "cwd": server.path().join("foo")
164 "args": [ "test", "--package", "foo", "--test", "spam" ],
168 "label": "cargo test -p foo",
170 "end": { "character": 0, "line": 0 },
171 "start": { "character": 0, "line": 0 }
173 "cwd": server.path().join("foo")
180 fn test_format_document() {
181 if skip_slow_tests() {
185 let server = project(
198 pub use std::collections::HashMap;
201 server.wait_until_workspace_is_loaded();
203 server.request::<Formatting>(
204 DocumentFormattingParams {
205 text_document: server.doc_id("src/lib.rs"),
206 options: FormattingOptions {
208 insert_spaces: false,
209 insert_final_newline: None,
210 trim_final_newlines: None,
211 trim_trailing_whitespace: None,
212 properties: HashMap::new(),
214 work_done_progress_params: WorkDoneProgressParams::default(),
218 "newText": r#"mod bar;
222 pub use std::collections::HashMap;
240 fn test_format_document_2018() {
241 if skip_slow_tests() {
245 let server = project(
262 pub use std::collections::HashMap;
265 server.wait_until_workspace_is_loaded();
267 server.request::<Formatting>(
268 DocumentFormattingParams {
269 text_document: server.doc_id("src/lib.rs"),
270 options: FormattingOptions {
272 insert_spaces: false,
273 properties: HashMap::new(),
274 insert_final_newline: None,
275 trim_final_newlines: None,
276 trim_trailing_whitespace: None,
278 work_done_progress_params: WorkDoneProgressParams::default(),
282 "newText": r#"mod bar;
288 pub use std::collections::HashMap;
306 fn test_missing_module_code_action() {
307 if skip_slow_tests() {
311 let server = project(
324 server.wait_until_workspace_is_loaded();
325 let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
326 server.request::<CodeActionRequest>(
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(),
339 "cursorPosition": null,
340 "label": "create module",
345 "uri": "file:///[..]/src/bar.rs"
351 "command": "rust-analyzer.applySourceChange",
352 "title": "create module"
354 "title": "create module"
359 server.request::<CodeActionRequest>(
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(),
372 fn test_missing_module_code_action_in_json_project() {
373 if skip_slow_tests() {
377 let tmp_dir = TempDir::new().unwrap();
379 let path = tmp_dir.path();
381 let project = json!({
384 "root_module": path.join("src/lib.rs"),
387 "cfg": [ "cfg_atom_1", "feature=cfg_1"],
388 "atom_cfgs": ["atom_2"],
389 "key_value_cfgs": { "feature": "key_value_feature", "other": "value"}
395 //- rust-project.json
403 PROJECT = project.to_string(),
406 let server = Project::with_fixture(&code).tmp_dir(tmp_dir).server();
408 server.wait_until_workspace_is_loaded();
409 let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
410 server.request::<CodeActionRequest>(
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(),
423 "cursorPosition": null,
424 "label": "create module",
429 "uri": "file:///[..]/src/bar.rs"
435 "command": "rust-analyzer.applySourceChange",
436 "title": "create module"
438 "title": "create module"
443 server.request::<CodeActionRequest>(
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(),
456 fn diagnostics_dont_block_typing() {
457 if skip_slow_tests() {
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!(
482 server.wait_until_workspace_is_loaded();
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(),
489 text: "/// Docs\nfn foo() {}".to_string(),
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 },
501 "position": { "character": 4, "line": 1 },
502 "textDocument": { "uri": "file:///[..]src/m0.rs" }
512 "end": { "character": 5, "line": 0 },
513 "start": { "character": 5, "line": 0 }
517 "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null }
523 let elapsed = start.elapsed();
524 assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
528 fn preserves_dos_line_endings() {
529 if skip_slow_tests() {
533 let server = Project::with_fixture(
541 /// Some Docs\r\nfn main() {}
546 server.request::<OnEnter>(
547 TextDocumentPositionParams {
548 text_document: server.doc_id("src/main.rs"),
549 position: Position { line: 0, character: 8 },
553 "position": { "line": 1, "character": 4 },
554 "textDocument": { "uri": "file:///[..]src/main.rs" }
562 "newText": "\r\n/// ",
564 "end": { "line": 0, "character": 8 },
565 "start": { "line": 0, "character": 8 }
569 "textDocument": { "uri": "file:///[..]src/main.rs", "version": null }
578 fn resolve_include_concat_env() {
579 if skip_slow_tests() {
583 let server = Project::with_fixture(
591 use std::{env, fs, path::Path};
594 let out_dir = env::var_os("OUT_DIR").unwrap();
595 let dest_path = Path::new(&out_dir).join("hello.rs");
598 r#"pub fn message() -> &'static str { "Hello, World!" }"#,
601 println!("cargo:rerun-if-changed=build.rs");
604 include!(concat!(env!("OUT_DIR"), "/hello.rs"));
606 fn main() { message(); }
609 .with_config(|config| {
610 config.cargo.load_out_dirs_from_check = true;
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),
619 work_done_progress_params: Default::default(),
620 partial_result_params: Default::default(),
622 assert!(format!("{}", res).contains("hello.rs"));
626 fn resolve_proc_macro() {
627 if skip_slow_tests() {
630 let server = Project::with_fixture(
638 bar = {path = "../bar"}
661 extern crate proc_macro;
662 use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
665 TokenTree::from(Ident::new($n, Span::call_site()))
668 TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
671 TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
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();
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());
690 .with_config(|config| {
691 let macro_srv_path = PathBuf::from(env!("CARGO_BIN_EXE_rust-analyzer"));
693 config.cargo.load_out_dirs_from_check = true;
694 config.proc_macro_srv = Some((macro_srv_path, vec!["proc-macro".into()]));
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"),
705 work_done_progress_params: Default::default(),
708 let value = res.get("contents").unwrap().get("value").unwrap().to_string();
709 assert_eq!(value, r#""```rust\nfoo::Bar\nfn bar()\n```""#)