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