]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/tests/slow-tests/main.rs
clippy::redudant_borrow
[rust.git] / crates / rust-analyzer / tests / slow-tests / main.rs
1 //! The most high-level integrated tests for rust-analyzer.
2 //!
3 //! This tests run a full LSP event loop, spawn cargo and process stdlib from
4 //! sysroot. For this reason, the tests here are very slow, and should be
5 //! avoided unless absolutely necessary.
6 //!
7 //! In particular, it's fine *not* to test that client & server agree on
8 //! specific JSON shapes here -- there's little value in such tests, as we can't
9 //! be sure without a real client anyway.
10
11 mod testdir;
12 mod support;
13
14 use std::{collections::HashMap, path::PathBuf, time::Instant};
15
16 use expect_test::expect;
17 use lsp_types::{
18     notification::DidOpenTextDocument,
19     request::{
20         CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
21         WillRenameFiles,
22     },
23     CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
24     DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
25     PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem,
26     TextDocumentPositionParams, WorkDoneProgressParams,
27 };
28 use rust_analyzer::lsp_ext::{OnEnter, Runnables, RunnablesParams};
29 use serde_json::json;
30 use test_utils::skip_slow_tests;
31
32 use crate::{
33     support::{project, Project},
34     testdir::TestDir,
35 };
36
37 const PROFILE: &str = "";
38 // const PROFILE: &'static str = "*@3>100";
39
40 #[test]
41 fn completes_items_from_standard_library() {
42     if skip_slow_tests() {
43         return;
44     }
45
46     let server = Project::with_fixture(
47         r#"
48 //- /Cargo.toml
49 [package]
50 name = "foo"
51 version = "0.0.0"
52
53 //- /src/lib.rs
54 use std::collections::Spam;
55 "#,
56     )
57     .with_config(serde_json::json!({
58         "cargo": { "noSysroot": false }
59     }))
60     .server()
61     .wait_until_workspace_is_loaded();
62
63     let res = server.send_request::<Completion>(CompletionParams {
64         text_document_position: TextDocumentPositionParams::new(
65             server.doc_id("src/lib.rs"),
66             Position::new(0, 23),
67         ),
68         context: None,
69         partial_result_params: PartialResultParams::default(),
70         work_done_progress_params: WorkDoneProgressParams::default(),
71     });
72     assert!(res.to_string().contains("HashMap"));
73 }
74
75 #[test]
76 fn test_runnables_project() {
77     if skip_slow_tests() {
78         return;
79     }
80
81     let server = Project::with_fixture(
82         r#"
83 //- /foo/Cargo.toml
84 [package]
85 name = "foo"
86 version = "0.0.0"
87
88 //- /foo/src/lib.rs
89 pub fn foo() {}
90
91 //- /foo/tests/spam.rs
92 #[test]
93 fn test_eggs() {}
94
95 //- /bar/Cargo.toml
96 [package]
97 name = "bar"
98 version = "0.0.0"
99
100 //- /bar/src/main.rs
101 fn main() {}
102 "#,
103     )
104     .root("foo")
105     .root("bar")
106     .server()
107     .wait_until_workspace_is_loaded();
108
109     server.request::<Runnables>(
110         RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
111         json!([
112           {
113             "args": {
114               "cargoArgs": ["test", "--package", "foo", "--test", "spam"],
115               "executableArgs": ["test_eggs", "--exact", "--nocapture"],
116               "cargoExtraArgs": [],
117               "overrideCargo": null,
118               "workspaceRoot": server.path().join("foo")
119             },
120             "kind": "cargo",
121             "label": "test test_eggs",
122             "location": {
123               "targetRange": {
124                 "end": { "character": 17, "line": 1 },
125                 "start": { "character": 0, "line": 0 }
126               },
127               "targetSelectionRange": {
128                 "end": { "character": 12, "line": 1 },
129                 "start": { "character": 3, "line": 1 }
130               },
131               "targetUri": "file:///[..]/tests/spam.rs"
132             }
133           },
134           {
135             "args": {
136               "cargoArgs": ["check", "--package", "foo", "--all-targets"],
137               "executableArgs": [],
138               "cargoExtraArgs": [],
139               "overrideCargo": null,
140               "workspaceRoot": server.path().join("foo")
141             },
142             "kind": "cargo",
143             "label": "cargo check -p foo --all-targets"
144           },
145           {
146             "args": {
147               "cargoArgs": ["test", "--package", "foo", "--all-targets"],
148               "executableArgs": [],
149               "cargoExtraArgs": [],
150               "overrideCargo": null,
151               "workspaceRoot": server.path().join("foo")
152             },
153             "kind": "cargo",
154             "label": "cargo test -p foo --all-targets"
155           }
156         ]),
157     );
158 }
159
160 #[test]
161 fn test_format_document() {
162     if skip_slow_tests() {
163         return;
164     }
165
166     let server = project(
167         r#"
168 //- /Cargo.toml
169 [package]
170 name = "foo"
171 version = "0.0.0"
172
173 //- /src/lib.rs
174 mod bar;
175
176 fn main() {
177 }
178
179 pub use std::collections::HashMap;
180 "#,
181     )
182     .wait_until_workspace_is_loaded();
183
184     server.request::<Formatting>(
185         DocumentFormattingParams {
186             text_document: server.doc_id("src/lib.rs"),
187             options: FormattingOptions {
188                 tab_size: 4,
189                 insert_spaces: false,
190                 insert_final_newline: None,
191                 trim_final_newlines: None,
192                 trim_trailing_whitespace: None,
193                 properties: HashMap::new(),
194             },
195             work_done_progress_params: WorkDoneProgressParams::default(),
196         },
197         json!([
198             {
199                 "newText": "",
200                 "range": {
201                     "end": { "character": 0, "line": 3 },
202                     "start": { "character": 11, "line": 2 }
203                 }
204             }
205         ]),
206     );
207 }
208
209 #[test]
210 fn test_format_document_2018() {
211     if skip_slow_tests() {
212         return;
213     }
214
215     let server = project(
216         r#"
217 //- /Cargo.toml
218 [package]
219 name = "foo"
220 version = "0.0.0"
221 edition = "2018"
222
223 //- /src/lib.rs
224 mod bar;
225
226 async fn test() {
227 }
228
229 fn main() {
230 }
231
232 pub use std::collections::HashMap;
233 "#,
234     )
235     .wait_until_workspace_is_loaded();
236
237     server.request::<Formatting>(
238         DocumentFormattingParams {
239             text_document: server.doc_id("src/lib.rs"),
240             options: FormattingOptions {
241                 tab_size: 4,
242                 insert_spaces: false,
243                 properties: HashMap::new(),
244                 insert_final_newline: None,
245                 trim_final_newlines: None,
246                 trim_trailing_whitespace: None,
247             },
248             work_done_progress_params: WorkDoneProgressParams::default(),
249         },
250         json!([
251             {
252                 "newText": "",
253                 "range": {
254                     "end": { "character": 0, "line": 3 },
255                     "start": { "character": 17, "line": 2 }
256                 }
257             },
258             {
259                 "newText": "",
260                 "range": {
261                     "end": { "character": 0, "line": 6 },
262                     "start": { "character": 11, "line": 5 }
263                 }
264             }
265         ]),
266     );
267 }
268
269 #[test]
270 fn test_format_document_unchanged() {
271     if skip_slow_tests() {
272         return;
273     }
274
275     let server = project(
276         r#"
277 //- /Cargo.toml
278 [package]
279 name = "foo"
280 version = "0.0.0"
281
282 //- /src/lib.rs
283 fn main() {}
284 "#,
285     )
286     .wait_until_workspace_is_loaded();
287
288     server.request::<Formatting>(
289         DocumentFormattingParams {
290             text_document: server.doc_id("src/lib.rs"),
291             options: FormattingOptions {
292                 tab_size: 4,
293                 insert_spaces: false,
294                 insert_final_newline: None,
295                 trim_final_newlines: None,
296                 trim_trailing_whitespace: None,
297                 properties: HashMap::new(),
298             },
299             work_done_progress_params: WorkDoneProgressParams::default(),
300         },
301         json!(null),
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     .wait_until_workspace_is_loaded();
325
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: CodeActionContext::default(),
331             partial_result_params: PartialResultParams::default(),
332             work_done_progress_params: WorkDoneProgressParams::default(),
333         },
334         json!([{
335             "edit": {
336               "documentChanges": [
337                 {
338                   "kind": "create",
339                   "uri": "file:///[..]/src/bar.rs"
340                 }
341               ]
342             },
343             "kind": "quickfix",
344             "title": "Create module"
345         }]),
346     );
347
348     server.request::<CodeActionRequest>(
349         CodeActionParams {
350             text_document: server.doc_id("src/lib.rs"),
351             range: Range::new(Position::new(2, 4), Position::new(2, 7)),
352             context: CodeActionContext::default(),
353             partial_result_params: PartialResultParams::default(),
354             work_done_progress_params: WorkDoneProgressParams::default(),
355         },
356         json!([]),
357     );
358 }
359
360 #[test]
361 fn test_missing_module_code_action_in_json_project() {
362     if skip_slow_tests() {
363         return;
364     }
365
366     let tmp_dir = TestDir::new();
367
368     let path = tmp_dir.path();
369
370     let project = json!({
371         "roots": [path],
372         "crates": [ {
373             "root_module": path.join("src/lib.rs"),
374             "deps": [],
375             "edition": "2015",
376             "cfg": [ "cfg_atom_1", "feature=\"cfg_1\""],
377         } ]
378     });
379
380     let code = format!(
381         r#"
382 //- /rust-project.json
383 {PROJECT}
384
385 //- /src/lib.rs
386 mod bar;
387
388 fn main() {{}}
389 "#,
390         PROJECT = project.to_string(),
391     );
392
393     let server =
394         Project::with_fixture(&code).tmp_dir(tmp_dir).server().wait_until_workspace_is_loaded();
395
396     server.request::<CodeActionRequest>(
397         CodeActionParams {
398             text_document: server.doc_id("src/lib.rs"),
399             range: Range::new(Position::new(0, 4), Position::new(0, 7)),
400             context: CodeActionContext::default(),
401             partial_result_params: PartialResultParams::default(),
402             work_done_progress_params: WorkDoneProgressParams::default(),
403         },
404         json!([{
405             "edit": {
406               "documentChanges": [
407                 {
408                   "kind": "create",
409                   "uri": "file://[..]/src/bar.rs"
410                 }
411               ]
412             },
413             "kind": "quickfix",
414             "title": "Create module"
415         }]),
416     );
417
418     server.request::<CodeActionRequest>(
419         CodeActionParams {
420             text_document: server.doc_id("src/lib.rs"),
421             range: Range::new(Position::new(2, 4), Position::new(2, 7)),
422             context: CodeActionContext::default(),
423             partial_result_params: PartialResultParams::default(),
424             work_done_progress_params: WorkDoneProgressParams::default(),
425         },
426         json!([]),
427     );
428 }
429
430 #[test]
431 fn diagnostics_dont_block_typing() {
432     if skip_slow_tests() {
433         return;
434     }
435
436     let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect();
437     let libs: String = (0..10).map(|i| format!("//- /src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
438     let server = Project::with_fixture(&format!(
439         r#"
440 //- /Cargo.toml
441 [package]
442 name = "foo"
443 version = "0.0.0"
444
445 //- /src/lib.rs
446 {}
447
448 {}
449
450 fn main() {{}}
451 "#,
452         librs, libs
453     ))
454     .with_config(serde_json::json!({
455         "cargo": { "noSysroot": false }
456     }))
457     .server()
458     .wait_until_workspace_is_loaded();
459
460     for i in 0..10 {
461         server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
462             text_document: TextDocumentItem {
463                 uri: server.doc_id(&format!("src/m{}.rs", i)).uri,
464                 language_id: "rust".to_string(),
465                 version: 0,
466                 text: "/// Docs\nfn foo() {}".to_string(),
467             },
468         });
469     }
470     let start = Instant::now();
471     server.request::<OnEnter>(
472         TextDocumentPositionParams {
473             text_document: server.doc_id("src/m0.rs"),
474             position: Position { line: 0, character: 5 },
475         },
476         json!([{
477             "insertTextFormat": 2,
478             "newText": "\n/// $0",
479             "range": {
480             "end": { "character": 5, "line": 0 },
481             "start": { "character": 5, "line": 0 }
482             }
483         }]),
484     );
485     let elapsed = start.elapsed();
486     assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
487 }
488
489 #[test]
490 fn preserves_dos_line_endings() {
491     if skip_slow_tests() {
492         return;
493     }
494
495     let server = Project::with_fixture(
496         "
497 //- /Cargo.toml
498 [package]
499 name = \"foo\"
500 version = \"0.0.0\"
501
502 //- /src/main.rs
503 /// Some Docs\r\nfn main() {}
504 ",
505     )
506     .server()
507     .wait_until_workspace_is_loaded();
508
509     server.request::<OnEnter>(
510         TextDocumentPositionParams {
511             text_document: server.doc_id("src/main.rs"),
512             position: Position { line: 0, character: 8 },
513         },
514         json!([{
515             "insertTextFormat": 2,
516             "newText": "\r\n/// $0",
517             "range": {
518             "end": { "line": 0, "character": 8 },
519             "start": { "line": 0, "character": 8 }
520             }
521         }]),
522     );
523 }
524
525 #[test]
526 fn out_dirs_check() {
527     if skip_slow_tests() {
528         return;
529     }
530
531     let server = Project::with_fixture(
532         r###"
533 //- /Cargo.toml
534 [package]
535 name = "foo"
536 version = "0.0.0"
537
538 //- /build.rs
539 use std::{env, fs, path::Path};
540
541 fn main() {
542     let out_dir = env::var_os("OUT_DIR").unwrap();
543     let dest_path = Path::new(&out_dir).join("hello.rs");
544     fs::write(
545         &dest_path,
546         r#"pub fn message() -> &'static str { "Hello, World!" }"#,
547     )
548     .unwrap();
549     println!("cargo:rustc-cfg=atom_cfg");
550     println!("cargo:rustc-cfg=featlike=\"set\"");
551     println!("cargo:rerun-if-changed=build.rs");
552 }
553 //- /src/main.rs
554 #[rustc_builtin_macro] macro_rules! include {}
555 #[rustc_builtin_macro] macro_rules! include_str {}
556 #[rustc_builtin_macro] macro_rules! concat {}
557 #[rustc_builtin_macro] macro_rules! env {}
558
559 include!(concat!(env!("OUT_DIR"), "/hello.rs"));
560
561 #[cfg(atom_cfg)]
562 struct A;
563 #[cfg(bad_atom_cfg)]
564 struct A;
565 #[cfg(featlike = "set")]
566 struct B;
567 #[cfg(featlike = "not_set")]
568 struct B;
569
570 fn main() {
571     let va = A;
572     let vb = B;
573     let should_be_str = message();
574     let another_str = include_str!("main.rs");
575 }
576 "###,
577     )
578     .with_config(serde_json::json!({
579         "cargo": {
580             "loadOutDirsFromCheck": true,
581             "noSysroot": true,
582         }
583     }))
584     .server()
585     .wait_until_workspace_is_loaded();
586
587     let res = server.send_request::<HoverRequest>(HoverParams {
588         text_document_position_params: TextDocumentPositionParams::new(
589             server.doc_id("src/main.rs"),
590             Position::new(19, 10),
591         ),
592         work_done_progress_params: Default::default(),
593     });
594     assert!(res.to_string().contains("&str"));
595
596     let res = server.send_request::<HoverRequest>(HoverParams {
597         text_document_position_params: TextDocumentPositionParams::new(
598             server.doc_id("src/main.rs"),
599             Position::new(20, 10),
600         ),
601         work_done_progress_params: Default::default(),
602     });
603     assert!(res.to_string().contains("&str"));
604
605     server.request::<GotoTypeDefinition>(
606         GotoDefinitionParams {
607             text_document_position_params: TextDocumentPositionParams::new(
608                 server.doc_id("src/main.rs"),
609                 Position::new(17, 9),
610             ),
611             work_done_progress_params: Default::default(),
612             partial_result_params: Default::default(),
613         },
614         json!([{
615             "originSelectionRange": {
616                 "end": { "character": 10, "line": 17 },
617                 "start": { "character": 8, "line": 17 }
618             },
619             "targetRange": {
620                 "end": { "character": 9, "line": 8 },
621                 "start": { "character": 0, "line": 7 }
622             },
623             "targetSelectionRange": {
624                 "end": { "character": 8, "line": 8 },
625                 "start": { "character": 7, "line": 8 }
626             },
627             "targetUri": "file:///[..]src/main.rs"
628         }]),
629     );
630
631     server.request::<GotoTypeDefinition>(
632         GotoDefinitionParams {
633             text_document_position_params: TextDocumentPositionParams::new(
634                 server.doc_id("src/main.rs"),
635                 Position::new(18, 9),
636             ),
637             work_done_progress_params: Default::default(),
638             partial_result_params: Default::default(),
639         },
640         json!([{
641             "originSelectionRange": {
642                 "end": { "character": 10, "line": 18 },
643                 "start": { "character": 8, "line": 18 }
644             },
645             "targetRange": {
646                 "end": { "character": 9, "line": 12 },
647                 "start": { "character": 0, "line":11 }
648             },
649             "targetSelectionRange": {
650                 "end": { "character": 8, "line": 12 },
651                 "start": { "character": 7, "line": 12 }
652             },
653             "targetUri": "file:///[..]src/main.rs"
654         }]),
655     );
656 }
657
658 #[test]
659 fn resolve_proc_macro() {
660     if skip_slow_tests() {
661         return;
662     }
663
664     let server = Project::with_fixture(
665         r###"
666 //- /foo/Cargo.toml
667 [package]
668 name = "foo"
669 version = "0.0.0"
670 edition = "2018"
671 [dependencies]
672 bar = {path = "../bar"}
673
674 //- /foo/src/main.rs
675 use bar::Bar;
676 trait Bar {
677   fn bar();
678 }
679 #[derive(Bar)]
680 struct Foo {}
681 fn main() {
682   Foo::bar();
683 }
684
685 //- /bar/Cargo.toml
686 [package]
687 name = "bar"
688 version = "0.0.0"
689 edition = "2018"
690
691 [lib]
692 proc-macro = true
693
694 //- /bar/src/lib.rs
695 extern crate proc_macro;
696 use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
697 macro_rules! t {
698     ($n:literal) => {
699         TokenTree::from(Ident::new($n, Span::call_site()))
700     };
701     ({}) => {
702         TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
703     };
704     (()) => {
705         TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
706     };
707 }
708 #[proc_macro_derive(Bar)]
709 pub fn foo(_input: TokenStream) -> TokenStream {
710     // We hard code the output here for preventing to use any deps
711     let mut res = TokenStream::new();
712
713     // ill behaved proc-macro will use the stdout
714     // we should ignore it
715     println!("I am bad guy");
716
717     // impl Bar for Foo { fn bar() {} }
718     let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")];
719     let mut fn_stream = TokenStream::new();
720     fn_stream.extend(vec![t!("fn"), t!("bar"), t!(()), t!({})]);
721     tokens.push(Group::new(Delimiter::Brace, fn_stream).into());
722     res.extend(tokens);
723     res
724 }
725
726 "###,
727     )
728     .with_config(serde_json::json!({
729         "cargo": {
730             "loadOutDirsFromCheck": true,
731             "noSysroot": true,
732         },
733         "procMacro": {
734             "enable": true,
735             "server": PathBuf::from(env!("CARGO_BIN_EXE_rust-analyzer")),
736         }
737     }))
738     .root("foo")
739     .root("bar")
740     .server()
741     .wait_until_workspace_is_loaded();
742
743     let res = server.send_request::<HoverRequest>(HoverParams {
744         text_document_position_params: TextDocumentPositionParams::new(
745             server.doc_id("foo/src/main.rs"),
746             Position::new(7, 9),
747         ),
748         work_done_progress_params: Default::default(),
749     });
750     let value = res.get("contents").unwrap().get("value").unwrap().as_str().unwrap();
751
752     expect![[r#"
753
754         ```rust
755         foo::Bar
756         ```
757
758         ```rust
759         fn bar()
760         ```"#]]
761     .assert_eq(value);
762 }
763
764 #[test]
765 fn test_will_rename_files_same_level() {
766     if skip_slow_tests() {
767         return;
768     }
769
770     let tmp_dir = TestDir::new();
771     let tmp_dir_path = tmp_dir.path().to_owned();
772     let tmp_dir_str = tmp_dir_path.to_str().unwrap();
773     let base_path = PathBuf::from(format!("file://{}", tmp_dir_str));
774
775     let code = r#"
776 //- /Cargo.toml
777 [package]
778 name = "foo"
779 version = "0.0.0"
780
781 //- /src/lib.rs
782 mod old_file;
783 mod from_mod;
784 mod to_mod;
785 mod old_folder;
786 fn main() {}
787
788 //- /src/old_file.rs
789
790 //- /src/old_folder/mod.rs
791
792 //- /src/from_mod/mod.rs
793
794 //- /src/to_mod/foo.rs
795
796 "#;
797     let server =
798         Project::with_fixture(code).tmp_dir(tmp_dir).server().wait_until_workspace_is_loaded();
799
800     //rename same level file
801     server.request::<WillRenameFiles>(
802         RenameFilesParams {
803             files: vec![FileRename {
804                 old_uri: base_path.join("src/old_file.rs").to_str().unwrap().to_string(),
805                 new_uri: base_path.join("src/new_file.rs").to_str().unwrap().to_string(),
806             }],
807         },
808         json!({
809           "documentChanges": [
810             {
811               "textDocument": {
812                 "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").to_str().unwrap().to_string().replace("C:\\", "/c:/").replace("\\", "/")),
813                 "version": null
814               },
815               "edits": [
816                 {
817                   "range": {
818                     "start": {
819                       "line": 0,
820                       "character": 4
821                     },
822                     "end": {
823                       "line": 0,
824                       "character": 12
825                     }
826                   },
827                   "newText": "new_file"
828                 }
829               ]
830             }
831           ]
832         }),
833     );
834
835     //rename file from mod.rs to foo.rs
836     server.request::<WillRenameFiles>(
837         RenameFilesParams {
838             files: vec![FileRename {
839                 old_uri: base_path.join("src/from_mod/mod.rs").to_str().unwrap().to_string(),
840                 new_uri: base_path.join("src/from_mod/foo.rs").to_str().unwrap().to_string(),
841             }],
842         },
843         json!(null),
844     );
845
846     //rename file from foo.rs to mod.rs
847     server.request::<WillRenameFiles>(
848         RenameFilesParams {
849             files: vec![FileRename {
850                 old_uri: base_path.join("src/to_mod/foo.rs").to_str().unwrap().to_string(),
851                 new_uri: base_path.join("src/to_mod/mod.rs").to_str().unwrap().to_string(),
852             }],
853         },
854         json!(null),
855     );
856
857     //rename same level file
858     server.request::<WillRenameFiles>(
859         RenameFilesParams {
860             files: vec![FileRename {
861                 old_uri: base_path.join("src/old_folder").to_str().unwrap().to_string(),
862                 new_uri: base_path.join("src/new_folder").to_str().unwrap().to_string(),
863             }],
864         },
865         json!({
866           "documentChanges": [
867             {
868               "textDocument": {
869                 "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").to_str().unwrap().to_string().replace("C:\\", "/c:/").replace("\\", "/")),
870                 "version": null
871               },
872               "edits": [
873                 {
874                   "range": {
875                     "start": {
876                       "line": 3,
877                       "character": 4
878                     },
879                     "end": {
880                       "line": 3,
881                       "character": 14
882                     }
883                   },
884                   "newText": "new_folder"
885                 }
886               ]
887             }
888           ]
889         }),
890     );
891 }