1 //! A set of high-level utility fixture methods to use in tests.
2 use std::{mem, str::FromStr, sync::Arc};
5 use rustc_hash::FxHashMap;
7 extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER,
10 use vfs::{file_set::FileSet, VfsPath};
13 input::{CrateName, CrateOrigin},
14 Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition,
15 FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, SourceDatabaseExt,
16 SourceRoot, SourceRootId,
19 pub const WORKSPACE: SourceRootId = SourceRootId(0);
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])
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());
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());
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 })
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 })
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
64 .expect("Could not find file position in fixture. Did you forget to add an `$0`?");
65 (db, file_id, range_or_offset)
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());
77 impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {}
79 pub struct ChangeFixture {
80 pub file_position: Option<(FileId, RangeOrOffset)>,
81 pub files: Vec<FileId>,
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();
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();
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();
103 let mut file_position = None;
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)
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));
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")
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)),
131 current_source_root_kind = *kind;
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(
139 Some(crate_name.clone().into()),
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))
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;
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);
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(
173 Some(CrateName::new("test").unwrap().into()),
182 for (from, to, prelude) in crate_deps {
183 let from_id = crates[&from];
184 let to_id = crates[&to];
188 Dependency::with_prelude(CrateName::new(&to).unwrap(), to_id, prelude),
194 if let Some(mini_core) = mini_core {
195 let core_file = file_id;
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));
202 change.change_file(core_file, Some(Arc::new(mini_core.source_code())));
204 let all_crates = crate_graph.crates_in_topological_order();
206 let core_crate = crate_graph.add_crate_root(
208 Edition::Edition2021,
209 Some(CrateDisplayName::from_canonical_name("core".to_string())),
211 CfgOptions::default(),
212 CfgOptions::default(),
218 for krate in all_crates {
220 .add_dep(krate, Dependency::new(CrateName::new("core").unwrap(), core_crate))
225 if !proc_macros.is_empty() {
226 let proc_lib_file = file_id;
229 let (proc_macro, source) = test_proc_macros(&proc_macros);
230 let mut fs = FileSet::default();
233 VfsPath::new_virtual_path("/sysroot/proc_macros/lib.rs".to_string()),
235 roots.push(SourceRoot::new_library(fs));
237 change.change_file(proc_lib_file, Some(Arc::new(source)));
239 let all_crates = crate_graph.crates_in_topological_order();
241 let proc_macros_crate = crate_graph.add_crate_root(
243 Edition::Edition2021,
244 Some(CrateDisplayName::from_canonical_name("proc_macros".to_string())),
246 CfgOptions::default(),
247 CfgOptions::default(),
253 for krate in all_crates {
257 Dependency::new(CrateName::new("proc_macros").unwrap(), proc_macros_crate),
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)),
268 change.set_roots(roots);
269 change.set_crate_graph(crate_graph);
271 ChangeFixture { file_position, files, change }
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.
278 #[proc_macro_attribute]
279 pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
282 #[proc_macro_derive(DeriveIdentity)]
283 pub fn derive_identity(item: TokenStream) -> TokenStream {
286 #[proc_macro_attribute]
287 pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
291 pub fn mirror(input: TokenStream) -> TokenStream {
297 name: "identity".into(),
298 kind: crate::ProcMacroKind::Attr,
299 expander: Arc::new(IdentityProcMacroExpander),
302 name: "DeriveIdentity".into(),
303 kind: crate::ProcMacroKind::CustomDerive,
304 expander: Arc::new(IdentityProcMacroExpander),
307 name: "input_replace".into(),
308 kind: crate::ProcMacroKind::Attr,
309 expander: Arc::new(AttributeInputReplaceProcMacroExpander),
312 name: "mirror".into(),
313 kind: crate::ProcMacroKind::FuncLike,
314 expander: Arc::new(MirrorProcMacroExpander),
318 .filter(|pm| proc_macros.iter().any(|name| name == &stdx::to_lower_snake_case(&pm.name)))
320 (proc_macros, source.into())
323 #[derive(Debug, Clone, Copy)]
324 enum SourceRootKind {
332 krate: Option<(String, CrateOrigin, Option<String>)>,
334 extern_prelude: Vec<String>,
338 introduce_new_source_root: Option<SourceRootKind>,
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()) })
348 _ => panic!("Bad crates.io parameter: {}", data),
350 _ => panic!("Bad string for crate origin: {}", b),
352 (a.to_owned(), origin, Some(version.to_string()))
354 (crate_str, CrateOrigin::Unknown, None)
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()));
366 krate: f.krate.map(parse_crate),
367 extern_prelude: f.extern_prelude.unwrap_or_else(|| deps.clone()),
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),
383 struct IdentityProcMacroExpander;
384 impl ProcMacroExpander for IdentityProcMacroExpander {
390 ) -> Result<Subtree, ProcMacroExpansionError> {
395 // Pastes the attribute input as its output
397 struct AttributeInputReplaceProcMacroExpander;
398 impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander {
402 attrs: Option<&Subtree>,
404 ) -> Result<Subtree, ProcMacroExpansionError> {
407 .ok_or_else(|| ProcMacroExpansionError::Panic("Expected attribute input".into()))
412 struct MirrorProcMacroExpander;
413 impl ProcMacroExpander for MirrorProcMacroExpander {
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() {
425 tt::TokenTree::Leaf(leaf) => tt::TokenTree::Leaf(leaf.clone()),
426 tt::TokenTree::Subtree(sub) => tt::TokenTree::Subtree(traverse(sub)),
428 res.token_trees.push(tt);