]> git.lizzy.rs Git - rust.git/blob - crates/base_db/src/fixture.rs
Replaced fold with for loop
[rust.git] / crates / base_db / src / fixture.rs
1 //! A set of high-level utility fixture methods to use in tests.
2 use std::{mem, str::FromStr, sync::Arc};
3
4 use cfg::CfgOptions;
5 use rustc_hash::FxHashMap;
6 use test_utils::{
7     extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER,
8 };
9 use tt::Subtree;
10 use vfs::{file_set::FileSet, VfsPath};
11
12 use crate::{
13     input::{CrateName, CrateOrigin},
14     Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition,
15     FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, SourceDatabaseExt,
16     SourceRoot, SourceRootId,
17 };
18
19 pub const WORKSPACE: SourceRootId = SourceRootId(0);
20
21 pub trait WithFixture: Default + SourceDatabaseExt + 'static {
22     fn with_single_file(ra_fixture: &str) -> (Self, FileId) {
23         let fixture = ChangeFixture::parse(ra_fixture);
24         let mut db = Self::default();
25         fixture.change.apply(&mut db);
26         assert_eq!(fixture.files.len(), 1);
27         (db, fixture.files[0])
28     }
29
30     fn with_many_files(ra_fixture: &str) -> (Self, Vec<FileId>) {
31         let fixture = ChangeFixture::parse(ra_fixture);
32         let mut db = Self::default();
33         fixture.change.apply(&mut db);
34         assert!(fixture.file_position.is_none());
35         (db, fixture.files)
36     }
37
38     fn with_files(ra_fixture: &str) -> Self {
39         let fixture = ChangeFixture::parse(ra_fixture);
40         let mut db = Self::default();
41         fixture.change.apply(&mut db);
42         assert!(fixture.file_position.is_none());
43         db
44     }
45
46     fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
47         let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
48         let offset = range_or_offset.expect_offset();
49         (db, FilePosition { file_id, offset })
50     }
51
52     fn with_range(ra_fixture: &str) -> (Self, FileRange) {
53         let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
54         let range = range_or_offset.expect_range();
55         (db, FileRange { file_id, range })
56     }
57
58     fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) {
59         let fixture = ChangeFixture::parse(ra_fixture);
60         let mut db = Self::default();
61         fixture.change.apply(&mut db);
62         let (file_id, range_or_offset) = fixture
63             .file_position
64             .expect("Could not find file position in fixture. Did you forget to add an `$0`?");
65         (db, file_id, range_or_offset)
66     }
67
68     fn test_crate(&self) -> CrateId {
69         let crate_graph = self.crate_graph();
70         let mut it = crate_graph.iter();
71         let res = it.next().unwrap();
72         assert!(it.next().is_none());
73         res
74     }
75 }
76
77 impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {}
78
79 pub struct ChangeFixture {
80     pub file_position: Option<(FileId, RangeOrOffset)>,
81     pub files: Vec<FileId>,
82     pub change: Change,
83 }
84
85 impl ChangeFixture {
86     pub fn parse(ra_fixture: &str) -> ChangeFixture {
87         let (mini_core, proc_macros, fixture) = Fixture::parse(ra_fixture);
88         let mut change = Change::new();
89
90         let mut files = Vec::new();
91         let mut crate_graph = CrateGraph::default();
92         let mut crates = FxHashMap::default();
93         let mut crate_deps = Vec::new();
94         let mut default_crate_root: Option<FileId> = None;
95         let mut default_cfg = CfgOptions::default();
96
97         let mut file_set = FileSet::default();
98         let mut current_source_root_kind = SourceRootKind::Local;
99         let source_root_prefix = "/".to_string();
100         let mut file_id = FileId(0);
101         let mut roots = Vec::new();
102
103         let mut file_position = None;
104
105         for entry in fixture {
106             let text = if entry.text.contains(CURSOR_MARKER) {
107                 if entry.text.contains(ESCAPED_CURSOR_MARKER) {
108                     entry.text.replace(ESCAPED_CURSOR_MARKER, CURSOR_MARKER)
109                 } else {
110                     let (range_or_offset, text) = extract_range_or_offset(&entry.text);
111                     assert!(file_position.is_none());
112                     file_position = Some((file_id, range_or_offset));
113                     text
114                 }
115             } else {
116                 entry.text.clone()
117             };
118
119             let meta = FileMeta::from(entry);
120             assert!(meta.path.starts_with(&source_root_prefix));
121             if !meta.deps.is_empty() {
122                 assert!(meta.krate.is_some(), "can't specify deps without naming the crate")
123             }
124
125             if let Some(kind) = &meta.introduce_new_source_root {
126                 let root = match current_source_root_kind {
127                     SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
128                     SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
129                 };
130                 roots.push(root);
131                 current_source_root_kind = *kind;
132             }
133
134             if let Some((krate, origin, version)) = meta.krate {
135                 let crate_name = CrateName::normalize_dashes(&krate);
136                 let crate_id = crate_graph.add_crate_root(
137                     file_id,
138                     meta.edition,
139                     Some(crate_name.clone().into()),
140                     version,
141                     meta.cfg.clone(),
142                     meta.cfg,
143                     meta.env,
144                     Default::default(),
145                     origin,
146                 );
147                 let prev = crates.insert(crate_name.clone(), crate_id);
148                 assert!(prev.is_none());
149                 for dep in meta.deps {
150                     let prelude = meta.extern_prelude.contains(&dep);
151                     let dep = CrateName::normalize_dashes(&dep);
152                     crate_deps.push((crate_name.clone(), dep, prelude))
153                 }
154             } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
155                 assert!(default_crate_root.is_none());
156                 default_crate_root = Some(file_id);
157                 default_cfg = meta.cfg;
158             }
159
160             change.change_file(file_id, Some(Arc::new(text)));
161             let path = VfsPath::new_virtual_path(meta.path);
162             file_set.insert(file_id, path);
163             files.push(file_id);
164             file_id.0 += 1;
165         }
166
167         if crates.is_empty() {
168             let crate_root = default_crate_root
169                 .expect("missing default crate root, specify a main.rs or lib.rs");
170             crate_graph.add_crate_root(
171                 crate_root,
172                 Edition::CURRENT,
173                 Some(CrateName::new("test").unwrap().into()),
174                 None,
175                 default_cfg.clone(),
176                 default_cfg,
177                 Env::default(),
178                 Default::default(),
179                 Default::default(),
180             );
181         } else {
182             for (from, to, prelude) in crate_deps {
183                 let from_id = crates[&from];
184                 let to_id = crates[&to];
185                 crate_graph
186                     .add_dep(
187                         from_id,
188                         Dependency::with_prelude(CrateName::new(&to).unwrap(), to_id, prelude),
189                     )
190                     .unwrap();
191             }
192         }
193
194         if let Some(mini_core) = mini_core {
195             let core_file = file_id;
196             file_id.0 += 1;
197
198             let mut fs = FileSet::default();
199             fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_string()));
200             roots.push(SourceRoot::new_library(fs));
201
202             change.change_file(core_file, Some(Arc::new(mini_core.source_code())));
203
204             let all_crates = crate_graph.crates_in_topological_order();
205
206             let core_crate = crate_graph.add_crate_root(
207                 core_file,
208                 Edition::Edition2021,
209                 Some(CrateDisplayName::from_canonical_name("core".to_string())),
210                 None,
211                 CfgOptions::default(),
212                 CfgOptions::default(),
213                 Env::default(),
214                 Vec::new(),
215                 CrateOrigin::Lang,
216             );
217
218             for krate in all_crates {
219                 crate_graph
220                     .add_dep(krate, Dependency::new(CrateName::new("core").unwrap(), core_crate))
221                     .unwrap();
222             }
223         }
224
225         if !proc_macros.is_empty() {
226             let proc_lib_file = file_id;
227             file_id.0 += 1;
228
229             let (proc_macro, source) = test_proc_macros(&proc_macros);
230             let mut fs = FileSet::default();
231             fs.insert(
232                 proc_lib_file,
233                 VfsPath::new_virtual_path("/sysroot/proc_macros/lib.rs".to_string()),
234             );
235             roots.push(SourceRoot::new_library(fs));
236
237             change.change_file(proc_lib_file, Some(Arc::new(source)));
238
239             let all_crates = crate_graph.crates_in_topological_order();
240
241             let proc_macros_crate = crate_graph.add_crate_root(
242                 proc_lib_file,
243                 Edition::Edition2021,
244                 Some(CrateDisplayName::from_canonical_name("proc_macros".to_string())),
245                 None,
246                 CfgOptions::default(),
247                 CfgOptions::default(),
248                 Env::default(),
249                 proc_macro,
250                 CrateOrigin::Lang,
251             );
252
253             for krate in all_crates {
254                 crate_graph
255                     .add_dep(
256                         krate,
257                         Dependency::new(CrateName::new("proc_macros").unwrap(), proc_macros_crate),
258                     )
259                     .unwrap();
260             }
261         }
262
263         let root = match current_source_root_kind {
264             SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)),
265             SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)),
266         };
267         roots.push(root);
268         change.set_roots(roots);
269         change.set_crate_graph(crate_graph);
270
271         ChangeFixture { file_position, files, change }
272     }
273 }
274
275 fn test_proc_macros(proc_macros: &[String]) -> (Vec<ProcMacro>, String) {
276     // The source here is only required so that paths to the macros exist and are resolvable.
277     let source = r#"
278 #[proc_macro_attribute]
279 pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
280     item
281 }
282 #[proc_macro_derive(DeriveIdentity)]
283 pub fn derive_identity(item: TokenStream) -> TokenStream {
284     item
285 }
286 #[proc_macro_attribute]
287 pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
288     attr
289 }
290 #[proc_macro]
291 pub fn mirror(input: TokenStream) -> TokenStream {
292     input
293 }
294 "#;
295     let proc_macros = [
296         ProcMacro {
297             name: "identity".into(),
298             kind: crate::ProcMacroKind::Attr,
299             expander: Arc::new(IdentityProcMacroExpander),
300         },
301         ProcMacro {
302             name: "DeriveIdentity".into(),
303             kind: crate::ProcMacroKind::CustomDerive,
304             expander: Arc::new(IdentityProcMacroExpander),
305         },
306         ProcMacro {
307             name: "input_replace".into(),
308             kind: crate::ProcMacroKind::Attr,
309             expander: Arc::new(AttributeInputReplaceProcMacroExpander),
310         },
311         ProcMacro {
312             name: "mirror".into(),
313             kind: crate::ProcMacroKind::FuncLike,
314             expander: Arc::new(MirrorProcMacroExpander),
315         },
316     ]
317     .into_iter()
318     .filter(|pm| proc_macros.iter().any(|name| name == &stdx::to_lower_snake_case(&pm.name)))
319     .collect();
320     (proc_macros, source.into())
321 }
322
323 #[derive(Debug, Clone, Copy)]
324 enum SourceRootKind {
325     Local,
326     Library,
327 }
328
329 #[derive(Debug)]
330 struct FileMeta {
331     path: String,
332     krate: Option<(String, CrateOrigin, Option<String>)>,
333     deps: Vec<String>,
334     extern_prelude: Vec<String>,
335     cfg: CfgOptions,
336     edition: Edition,
337     env: Env,
338     introduce_new_source_root: Option<SourceRootKind>,
339 }
340
341 fn parse_crate(crate_str: String) -> (String, CrateOrigin, Option<String>) {
342     if let Some((a, b)) = crate_str.split_once("@") {
343         let (version, origin) = match b.split_once(":") {
344             Some(("CratesIo", data)) => match data.split_once(",") {
345                 Some((version, url)) => {
346                     (version, CrateOrigin::CratesIo { repo: Some(url.to_owned()) })
347                 }
348                 _ => panic!("Bad crates.io parameter: {}", data),
349             },
350             _ => panic!("Bad string for crate origin: {}", b),
351         };
352         (a.to_owned(), origin, Some(version.to_string()))
353     } else {
354         (crate_str, CrateOrigin::Unknown, None)
355     }
356 }
357
358 impl From<Fixture> for FileMeta {
359     fn from(f: Fixture) -> FileMeta {
360         let mut cfg = CfgOptions::default();
361         f.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into()));
362         f.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into()));
363         let deps = f.deps;
364         FileMeta {
365             path: f.path,
366             krate: f.krate.map(parse_crate),
367             extern_prelude: f.extern_prelude.unwrap_or_else(|| deps.clone()),
368             deps,
369             cfg,
370             edition: f.edition.as_ref().map_or(Edition::CURRENT, |v| Edition::from_str(v).unwrap()),
371             env: f.env.into_iter().collect(),
372             introduce_new_source_root: f.introduce_new_source_root.map(|kind| match &*kind {
373                 "local" => SourceRootKind::Local,
374                 "library" => SourceRootKind::Library,
375                 invalid => panic!("invalid source root kind '{}'", invalid),
376             }),
377         }
378     }
379 }
380
381 // Identity mapping
382 #[derive(Debug)]
383 struct IdentityProcMacroExpander;
384 impl ProcMacroExpander for IdentityProcMacroExpander {
385     fn expand(
386         &self,
387         subtree: &Subtree,
388         _: Option<&Subtree>,
389         _: &Env,
390     ) -> Result<Subtree, ProcMacroExpansionError> {
391         Ok(subtree.clone())
392     }
393 }
394
395 // Pastes the attribute input as its output
396 #[derive(Debug)]
397 struct AttributeInputReplaceProcMacroExpander;
398 impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander {
399     fn expand(
400         &self,
401         _: &Subtree,
402         attrs: Option<&Subtree>,
403         _: &Env,
404     ) -> Result<Subtree, ProcMacroExpansionError> {
405         attrs
406             .cloned()
407             .ok_or_else(|| ProcMacroExpansionError::Panic("Expected attribute input".into()))
408     }
409 }
410
411 #[derive(Debug)]
412 struct MirrorProcMacroExpander;
413 impl ProcMacroExpander for MirrorProcMacroExpander {
414     fn expand(
415         &self,
416         input: &Subtree,
417         _: Option<&Subtree>,
418         _: &Env,
419     ) -> Result<Subtree, ProcMacroExpansionError> {
420         fn traverse(input: &Subtree) -> Subtree {
421             let mut res = Subtree::default();
422             res.delimiter = input.delimiter;
423             for tt in input.token_trees.iter().rev() {
424                 let tt = match tt {
425                     tt::TokenTree::Leaf(leaf) => tt::TokenTree::Leaf(leaf.clone()),
426                     tt::TokenTree::Subtree(sub) => tt::TokenTree::Subtree(traverse(sub)),
427                 };
428                 res.token_trees.push(tt);
429             }
430             res
431         }
432         Ok(traverse(input))
433     }
434 }