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, Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env,
14 FileId, FilePosition, FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError,
15 SourceDatabaseExt, SourceRoot, SourceRootId,
18 pub const WORKSPACE: SourceRootId = SourceRootId(0);
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])
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());
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());
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 })
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 })
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
63 .expect("Could not find file position in fixture. Did you forget to add an `$0`?");
64 (db, file_id, range_or_offset)
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());
76 impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {}
78 pub struct ChangeFixture {
79 pub file_position: Option<(FileId, RangeOrOffset)>,
80 pub files: Vec<FileId>,
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();
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();
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();
102 let mut file_position = None;
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)
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));
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")
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)),
130 current_source_root_kind = *kind;
133 if let Some(krate) = meta.krate {
134 let crate_name = CrateName::normalize_dashes(&krate);
135 let crate_id = crate_graph.add_crate_root(
138 Some(crate_name.clone().into()),
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))
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;
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);
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(
170 Some(CrateName::new("test").unwrap().into()),
177 for (from, to, prelude) in crate_deps {
178 let from_id = crates[&from];
179 let to_id = crates[&to];
183 Dependency::with_prelude(CrateName::new(&to).unwrap(), to_id, prelude),
189 if let Some(mini_core) = mini_core {
190 let core_file = file_id;
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));
197 change.change_file(core_file, Some(Arc::new(mini_core.source_code())));
199 let all_crates = crate_graph.crates_in_topological_order();
201 let core_crate = crate_graph.add_crate_root(
203 Edition::Edition2021,
204 Some(CrateDisplayName::from_canonical_name("core".to_string())),
205 CfgOptions::default(),
206 CfgOptions::default(),
211 for krate in all_crates {
213 .add_dep(krate, Dependency::new(CrateName::new("core").unwrap(), core_crate))
218 if !proc_macros.is_empty() {
219 let proc_lib_file = file_id;
222 let (proc_macro, source) = test_proc_macros(&proc_macros);
223 let mut fs = FileSet::default();
226 VfsPath::new_virtual_path("/sysroot/proc_macros/lib.rs".to_string()),
228 roots.push(SourceRoot::new_library(fs));
230 change.change_file(proc_lib_file, Some(Arc::new(source)));
232 let all_crates = crate_graph.crates_in_topological_order();
234 let proc_macros_crate = crate_graph.add_crate_root(
236 Edition::Edition2021,
237 Some(CrateDisplayName::from_canonical_name("proc_macros".to_string())),
238 CfgOptions::default(),
239 CfgOptions::default(),
244 for krate in all_crates {
248 Dependency::new(CrateName::new("proc_macros").unwrap(), proc_macros_crate),
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)),
259 change.set_roots(roots);
260 change.set_crate_graph(crate_graph);
262 ChangeFixture { file_position, files, change }
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.
269 #[proc_macro_attribute]
270 pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
273 #[proc_macro_derive(derive_identity)]
274 pub fn derive_identity(item: TokenStream) -> TokenStream {
277 #[proc_macro_attribute]
278 pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream {
282 pub fn mirror(input: TokenStream) -> TokenStream {
288 name: "identity".into(),
289 kind: crate::ProcMacroKind::Attr,
290 expander: Arc::new(IdentityProcMacroExpander),
293 name: "derive_identity".into(),
294 kind: crate::ProcMacroKind::CustomDerive,
295 expander: Arc::new(IdentityProcMacroExpander),
298 name: "input_replace".into(),
299 kind: crate::ProcMacroKind::Attr,
300 expander: Arc::new(AttributeInputReplaceProcMacroExpander),
303 name: "mirror".into(),
304 kind: crate::ProcMacroKind::FuncLike,
305 expander: Arc::new(MirrorProcMacroExpander),
309 .filter(|pm| proc_macros.iter().any(|name| name == pm.name))
311 (proc_macros, source.into())
314 #[derive(Debug, Clone, Copy)]
315 enum SourceRootKind {
323 krate: Option<String>,
325 extern_prelude: Vec<String>,
329 introduce_new_source_root: Option<SourceRootKind>,
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()));
342 extern_prelude: f.extern_prelude.unwrap_or_else(|| deps.clone()),
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),
358 struct IdentityProcMacroExpander;
359 impl ProcMacroExpander for IdentityProcMacroExpander {
365 ) -> Result<Subtree, ProcMacroExpansionError> {
370 // Pastes the attribute input as its output
372 struct AttributeInputReplaceProcMacroExpander;
373 impl ProcMacroExpander for AttributeInputReplaceProcMacroExpander {
377 attrs: Option<&Subtree>,
379 ) -> Result<Subtree, ProcMacroExpansionError> {
382 .ok_or_else(|| ProcMacroExpansionError::Panic("Expected attribute input".into()))
387 struct MirrorProcMacroExpander;
388 impl ProcMacroExpander for MirrorProcMacroExpander {
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() {
400 tt::TokenTree::Leaf(leaf) => tt::TokenTree::Leaf(leaf.clone()),
401 tt::TokenTree::Subtree(sub) => tt::TokenTree::Subtree(traverse(sub)),
403 res.token_trees.push(tt);