Repository: rust-analyzer/rust-analyzer
Pull request #6123 (2020-10-02 15:50:04)

Reduce duplication in fixtures
bors r+
🤖


Patch:
diff --git a/crates/base_db/src/change.rs b/crates/base_db/src/change.rs
new file mode 100644
index 0000000000..043e03bba5
--- /dev/null
+++ b/crates/base_db/src/change.rs
@@ -0,0 +1,97 @@
+//! Defines a unit of change that can applied to the database to get the next
+//! state. Changes are transactional.
+
+use std::{fmt, sync::Arc};
+
+use rustc_hash::FxHashSet;
+use salsa::Durability;
+use vfs::FileId;
+
+use crate::{CrateGraph, SourceDatabaseExt, SourceRoot, SourceRootId};
+
+/// Encapsulate a bunch of raw `.set` calls on the database.
+#[derive(Default)]
+pub struct Change {
+    pub roots: Option<Vec<SourceRoot>>,
+    pub files_changed: Vec<(FileId, Option<Arc<String>>)>,
+    pub crate_graph: Option<CrateGraph>,
+}
+
+impl fmt::Debug for Change {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        let mut d = fmt.debug_struct("AnalysisChange");
+        if let Some(roots) = &self.roots {
+            d.field("roots", roots);
+        }
+        if !self.files_changed.is_empty() {
+            d.field("files_changed", &self.files_changed.len());
+        }
+        if self.crate_graph.is_some() {
+            d.field("crate_graph", &self.crate_graph);
+        }
+        d.finish()
+    }
+}
+
+impl Change {
+    pub fn new() -> Change {
+        Change::default()
+    }
+
+    pub fn set_roots(&mut self, roots: Vec<SourceRoot>) {
+        self.roots = Some(roots);
+    }
+
+    pub fn change_file(&mut self, file_id: FileId, new_text: Option<Arc<String>>) {
+        self.files_changed.push((file_id, new_text))
+    }
+
+    pub fn set_crate_graph(&mut self, graph: CrateGraph) {
+        self.crate_graph = Some(graph);
+    }
+
+    pub fn apply(self, db: &mut dyn SourceDatabaseExt) {
+        let _p = profile::span("RootDatabase::apply_change");
+        // db.request_cancellation();
+        // log::info!("apply_change {:?}", change);
+        if let Some(roots) = self.roots {
+            let mut local_roots = FxHashSet::default();
+            let mut library_roots = FxHashSet::default();
+            for (idx, root) in roots.into_iter().enumerate() {
+                let root_id = SourceRootId(idx as u32);
+                let durability = durability(&root);
+                if root.is_library {
+                    library_roots.insert(root_id);
+                } else {
+                    local_roots.insert(root_id);
+                }
+                for file_id in root.iter() {
+                    db.set_file_source_root_with_durability(file_id, root_id, durability);
+                }
+                db.set_source_root_with_durability(root_id, Arc::new(root), durability);
+            }
+            // db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
+            // db.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH);
+        }
+
+        for (file_id, text) in self.files_changed {
+            let source_root_id = db.file_source_root(file_id);
+            let source_root = db.source_root(source_root_id);
+            let durability = durability(&source_root);
+            // XXX: can't actually remove the file, just reset the text
+            let text = text.unwrap_or_default();
+            db.set_file_text_with_durability(file_id, text, durability)
+        }
+        if let Some(crate_graph) = self.crate_graph {
+            db.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH)
+        }
+    }
+}
+
+fn durability(source_root: &SourceRoot) -> Durability {
+    if source_root.is_library {
+        Durability::HIGH
+    } else {
+        Durability::LOW
+    }
+}
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs
index 5ff8ead0e5..b7286fc7d0 100644
--- a/crates/base_db/src/fixture.rs
+++ b/crates/base_db/src/fixture.rs
@@ -65,24 +65,26 @@ use test_utils::{extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER}
 use vfs::{file_set::FileSet, VfsPath};
 
 use crate::{
-    input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, SourceDatabaseExt,
-    SourceRoot, SourceRootId,
+    input::CrateName, Change, CrateGraph, CrateId, Edition, Env, FileId, FilePosition,
+    SourceDatabaseExt, SourceRoot, SourceRootId,
 };
 
 pub const WORKSPACE: SourceRootId = SourceRootId(0);
 
 pub trait WithFixture: Default + SourceDatabaseExt + 'static {
     fn with_single_file(text: &str) -> (Self, FileId) {
+        let fixture = ChangeFixture::parse(text);
         let mut db = Self::default();
-        let (_, files) = with_files(&mut db, text);
-        assert_eq!(files.len(), 1);
-        (db, files[0])
+        fixture.change.apply(&mut db);
+        assert_eq!(fixture.files.len(), 1);
+        (db, fixture.files[0])
     }
 
     fn with_files(ra_fixture: &str) -> Self {
+        let fixture = ChangeFixture::parse(ra_fixture);
         let mut db = Self::default();
-        let (pos, _) = with_files(&mut db, ra_fixture);
-        assert!(pos.is_none());
+        fixture.change.apply(&mut db);
+        assert!(fixture.file_position.is_none());
         db
     }
 
@@ -96,9 +98,10 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
     }
 
     fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) {
+        let fixture = ChangeFixture::parse(ra_fixture);
         let mut db = Self::default();
-        let (pos, _) = with_files(&mut db, ra_fixture);
-        let (file_id, range_or_offset) = pos.unwrap();
+        fixture.change.apply(&mut db);
+        let (file_id, range_or_offset) = fixture.file_position.unwrap();
         (db, file_id, range_or_offset)
     }
 
@@ -113,89 +116,95 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
 
 impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {}
 
-fn with_files(
-    db: &mut dyn SourceDatabaseExt,
-    fixture: &str,
-) -> (Option<(FileId, RangeOrOffset)>, Vec<FileId>) {
-    let fixture = Fixture::parse(fixture);
-
-    let mut files = Vec::new();
-    let mut crate_graph = CrateGraph::default();
-    let mut crates = FxHashMap::default();
-    let mut crate_deps = Vec::new();
-    let mut default_crate_root: Option<FileId> = None;
-
-    let mut file_set = FileSet::default();
-    let source_root_id = WORKSPACE;
-    let source_root_prefix = "/".to_string();
-    let mut file_id = FileId(0);
-
-    let mut file_position = None;
-
-    for entry in fixture {
-        let text = if entry.text.contains(CURSOR_MARKER) {
-            let (range_or_offset, text) = extract_range_or_offset(&entry.text);
-            assert!(file_position.is_none());
-            file_position = Some((file_id, range_or_offset));
-            text.to_string()
-        } else {
-            entry.text.clone()
-        };
+pub struct ChangeFixture {
+    pub file_position: Option<(FileId, RangeOrOffset)>,
+    pub files: Vec<FileId>,
+    pub change: Change,
+}
 
-        let meta = FileMeta::from(entry);
-        assert!(meta.path.starts_with(&source_root_prefix));
+impl ChangeFixture {
+    pub fn parse(ra_fixture: &str) -> ChangeFixture {
+        let fixture = Fixture::parse(ra_fixture);
+        let mut change = Change::new();
+
+        let mut files = Vec::new();
+        let mut crate_graph = CrateGraph::default();
+        let mut crates = FxHashMap::default();
+        let mut crate_deps = Vec::new();
+        let mut default_crate_root: Option<FileId> = None;
+        let mut default_cfg = CfgOptions::default();
+
+        let mut file_set = FileSet::default();
+        let source_root_prefix = "/".to_string();
+        let mut file_id = FileId(0);
+
+        let mut file_position = None;
+
+        for entry in fixture {
+            let text = if entry.text.contains(CURSOR_MARKER) {
+                let (range_or_offset, text) = extract_range_or_offset(&entry.text);
+                assert!(file_position.is_none());
+                file_position = Some((file_id, range_or_offset));
+                text.to_string()
+            } else {
+                entry.text.clone()
+            };
+
+            let meta = FileMeta::from(entry);
+            assert!(meta.path.starts_with(&source_root_prefix));
+
+            if let Some(krate) = meta.krate {
+                let crate_id = crate_graph.add_crate_root(
+                    file_id,
+                    meta.edition,
+                    Some(krate.clone()),
+                    meta.cfg,
+                    meta.env,
+                    Default::default(),
+                );
+                let crate_name = CrateName::new(&krate).unwrap();
+                let prev = crates.insert(crate_name.clone(), crate_id);
+                assert!(prev.is_none());
+                for dep in meta.deps {
+                    let dep = CrateName::new(&dep).unwrap();
+                    crate_deps.push((crate_name.clone(), dep))
+                }
+            } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
+                assert!(default_crate_root.is_none());
+                default_crate_root = Some(file_id);
+                default_cfg = meta.cfg;
+            }
+
+            change.change_file(file_id, Some(Arc::new(text)));
+            let path = VfsPath::new_virtual_path(meta.path);
+            file_set.insert(file_id, path.into());
+            files.push(file_id);
+            file_id.0 += 1;
+        }
 
-        if let Some(krate) = meta.krate {
-            let crate_id = crate_graph.add_crate_root(
-                file_id,
-                meta.edition,
-                Some(krate.clone()),
-                meta.cfg,
-                meta.env,
+        if crates.is_empty() {
+            let crate_root = default_crate_root.unwrap();
+            crate_graph.add_crate_root(
+                crate_root,
+                Edition::Edition2018,
+                Some("test".to_string()),
+                default_cfg,
+                Env::default(),
                 Default::default(),
             );
-            let crate_name = CrateName::new(&krate).unwrap();
-            let prev = crates.insert(crate_name.clone(), crate_id);
-            assert!(prev.is_none());
-            for dep in meta.deps {
-                let dep = CrateName::new(&dep).unwrap();
-                crate_deps.push((crate_name.clone(), dep))
+        } else {
+            for (from, to) in crate_deps {
+                let from_id = crates[&from];
+                let to_id = crates[&to];
+                crate_graph.add_dep(from_id, CrateName::new(&to).unwrap(), to_id).unwrap();
             }
-        } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
-            assert!(default_crate_root.is_none());
-            default_crate_root = Some(file_id);
         }
 
-        db.set_file_text(file_id, Arc::new(text));
-        db.set_file_source_root(file_id, source_root_id);
-        let path = VfsPath::new_virtual_path(meta.path);
-        file_set.insert(file_id, path.into());
-        files.push(file_id);
-        file_id.0 += 1;
-    }
+        change.set_roots(vec![SourceRoot::new_local(file_set)]);
+        change.set_crate_graph(crate_graph);
 
-    if crates.is_empty() {
-        let crate_root = default_crate_root.unwrap();
-        crate_graph.add_crate_root(
-            crate_root,
-            Edition::Edition2018,
-            None,
-            CfgOptions::default(),
-            Env::default(),
-            Default::default(),
-        );
-    } else {
-        for (from, to) in crate_deps {
-            let from_id = crates[&from];
-            let to_id = crates[&to];
-            crate_graph.add_dep(from_id, CrateName::new(&to).unwrap(), to_id).unwrap();
-        }
+        ChangeFixture { file_position, files, change }
     }
-
-    db.set_source_root(source_root_id, Arc::new(SourceRoot::new_local(file_set)));
-    db.set_crate_graph(Arc::new(crate_graph));
-
-    (file_position, files)
 }
 
 struct FileMeta {
diff --git a/crates/base_db/src/lib.rs b/crates/base_db/src/lib.rs
index ee34158506..e38aa72575 100644
--- a/crates/base_db/src/lib.rs
+++ b/crates/base_db/src/lib.rs
@@ -1,6 +1,7 @@
 //! base_db defines basic database traits. The concrete DB is defined by ide.
 mod cancellation;
 mod input;
+mod change;
 pub mod fixture;
 
 use std::{panic, sync::Arc};
@@ -10,6 +11,7 @@ use syntax::{ast, Parse, SourceFile, TextRange, TextSize};
 
 pub use crate::{
     cancellation::Canceled,
+    change::Change,
     input::{
         CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, ProcMacroId,
         SourceRoot, SourceRootId,
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index 58e26b94ca..d2cf2cc7df 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/crates/ide/src/call_hierarchy.rs
@@ -139,7 +139,7 @@ impl CallLocations {
 mod tests {
     use base_db::FilePosition;
 
-    use crate::mock_analysis::analysis_and_position;
+    use crate::fixture;
 
     fn check_hierarchy(
         ra_fixture: &str,
@@ -147,7 +147,7 @@ mod tests {
         expected_incoming: &[&str],
         expected_outgoing: &[&str],
     ) {
-        let (analysis, pos) = analysis_and_position(ra_fixture);
+        let (analysis, pos) = fixture::position(ra_fixture);
 
         let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
         assert_eq!(navs.len(), 1);
@@ -181,8 +181,8 @@ fn caller() {
     call<|>ee();
 }
 "#,
-            "callee FN FileId(1) 0..14 3..9",
-            &["caller FN FileId(1) 15..44 18..24 : [33..39]"],
+            "callee FN FileId(0) 0..14 3..9",
+            &["caller FN FileId(0) 15..44 18..24 : [33..39]"],
             &[],
         );
     }
@@ -197,8 +197,8 @@ fn caller() {
     callee();
 }
 "#,
-            "callee FN FileId(1) 0..14 3..9",
-            &["caller FN FileId(1) 15..44 18..24 : [33..39]"],
+            "callee FN FileId(0) 0..14 3..9",
+            &["caller FN FileId(0) 15..44 18..24 : [33..39]"],
             &[],
         );
     }
@@ -214,8 +214,8 @@ fn caller() {
     callee();
 }
 "#,
-            "callee FN FileId(1) 0..14 3..9",
-            &["caller FN FileId(1) 15..58 18..24 : [33..39, 47..53]"],
+            "callee FN FileId(0) 0..14 3..9",
+            &["caller FN FileId(0) 15..58 18..24 : [33..39, 47..53]"],
             &[],
         );
     }
@@ -234,10 +234,10 @@ fn caller2() {
     callee();
 }
 "#,
-            "callee FN FileId(1) 0..14 3..9",
+            "callee FN FileId(0) 0..14 3..9",
             &[
-                "caller1 FN FileId(1) 15..45 18..25 : [34..40]",
-                "caller2 FN FileId(1) 47..77 50..57 : [66..72]",
+                "caller1 FN FileId(0) 15..45 18..25 : [34..40]",
+                "caller2 FN FileId(0) 47..77 50..57 : [66..72]",
             ],
             &[],
         );
@@ -263,10 +263,10 @@ mod tests {
     }
 }
 "#,
-            "callee FN FileId(1) 0..14 3..9",
+            "callee FN FileId(0) 0..14 3..9",
             &[
-                "caller1 FN FileId(1) 15..45 18..25 : [34..40]",
-                "test_caller FN FileId(1) 95..149 110..121 : [134..140]",
+                "caller1 FN FileId(0) 15..45 18..25 : [34..40]",
+                "test_caller FN FileId(0) 95..149 110..121 : [134..140]",
             ],
             &[],
         );
@@ -287,8 +287,8 @@ fn caller() {
 //- /foo/mod.rs
 pub fn callee() {}
 "#,
-            "callee FN FileId(2) 0..18 7..13",
-            &["caller FN FileId(1) 27..56 30..36 : [45..51]"],
+            "callee FN FileId(1) 0..18 7..13",
+            &["caller FN FileId(0) 27..56 30..36 : [45..51]"],
             &[],
         );
     }
@@ -304,9 +304,9 @@ fn call<|>er() {
     callee();
 }
 "#,
-            "caller FN FileId(1) 15..58 18..24",
+            "caller FN FileId(0) 15..58 18..24",
             &[],
-            &["callee FN FileId(1) 0..14 3..9 : [33..39, 47..53]"],
+            &["callee FN FileId(0) 0..14 3..9 : [33..39, 47..53]"],
         );
     }
 
@@ -325,9 +325,9 @@ fn call<|>er() {
 //- /foo/mod.rs
 pub fn callee() {}
 "#,
-            "caller FN FileId(1) 27..56 30..36",
+            "caller FN FileId(0) 27..56 30..36",
             &[],
-            &["callee FN FileId(2) 0..18 7..13 : [45..51]"],
+            &["callee FN FileId(1) 0..18 7..13 : [45..51]"],
         );
     }
 
@@ -348,9 +348,9 @@ fn caller3() {
 
 }
 "#,
-            "caller2 FN FileId(1) 33..64 36..43",
-            &["caller1 FN FileId(1) 0..31 3..10 : [19..26]"],
-            &["caller3 FN FileId(1) 66..83 69..76 : [52..59]"],
+            "caller2 FN FileId(0) 33..64 36..43",
+            &["caller1 FN FileId(0) 0..31 3..10 : [19..26]"],
+            &["caller3 FN FileId(0) 66..83 69..76 : [52..59]"],
         );
     }
 
@@ -368,9 +368,9 @@ fn main() {
     a<|>()
 }
 "#,
-            "a FN FileId(1) 0..18 3..4",
-            &["main FN FileId(1) 31..52 34..38 : [47..48]"],
-            &["b FN FileId(1) 20..29 23..24 : [13..14]"],
+            "a FN FileId(0) 0..18 3..4",
+            &["main FN FileId(0) 31..52 34..38 : [47..48]"],
+            &["b FN FileId(0) 20..29 23..24 : [13..14]"],
         );
 
         check_hierarchy(
@@ -385,8 +385,8 @@ fn main() {
     a()
 }
 "#,
-            "b FN FileId(1) 20..29 23..24",
-            &["a FN FileId(1) 0..18 3..4 : [13..14]"],
+            "b FN FileId(0) 20..29 23..24",
+            &["a FN FileId(0) 0..18 3..4 : [13..14]"],
             &[],
         );
     }
diff --git a/crates/ide/src/call_info.rs b/crates/ide/src/call_info.rs
index 7e99c6b720..d7b2b926e7 100644
--- a/crates/ide/src/call_info.rs
+++ b/crates/ide/src/call_info.rs
@@ -232,10 +232,10 @@ mod tests {
     use expect_test::{expect, Expect};
     use test_utils::mark;
 
-    use crate::mock_analysis::analysis_and_position;
+    use crate::fixture;
 
     fn check(ra_fixture: &str, expect: Expect) {
-        let (analysis, position) = analysis_and_position(ra_fixture);
+        let (analysis, position) = fixture::position(ra_fixture);
         let call_info = analysis.call_info(position).unwrap();
         let actual = match call_info {
             Some(call_info) => {
diff --git a/crates/ide/src/completion.rs b/crates/ide/src/completion.rs
index daea2aa958..697f691b04 100644
--- a/crates/ide/src/completion.rs
+++ b/crates/ide/src/completion.rs
@@ -133,7 +133,7 @@ pub(crate) fn completions(
 #[cfg(test)]
 mod tests {
     use crate::completion::completion_config::CompletionConfig;
-    use crate::mock_analysis::analysis_and_position;
+    use crate::fixture;
 
     struct DetailAndDocumentation<'a> {
         detail: &'a str,
@@ -141,7 +141,7 @@ mod tests {
     }
 
     fn check_detail_and_documentation(ra_fixture: &str, expected: DetailAndDocumentation) {
-        let (analysis, position) = analysis_and_position(ra_fixture);
+        let (analysis, position) = fixture::position(ra_fixture);
         let config = CompletionConfig::default();
         let completions = analysis.completions(&config, position).unwrap().unwrap();
         for item in completions {
diff --git a/crates/ide/src/completion/complete_keyword.rs b/crates/ide/src/completion/complete_keyword.rs
index 5645b41fa4..e597470958 100644
--- a/crates/ide/src/completion/complete_keyword.rs
+++ b/crates/ide/src/completion/complete_keyword.rs
@@ -495,13 +495,13 @@ Some multi-line comment<|>
     fn test_completion_await_impls_future() {
         check(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:std
 use std::future::*;
 struct A {}
 impl Future for A {}
 fn foo(a: A) { a.<|> }
 
-//- /std/lib.rs
+//- /std/lib.rs crate:std
 pub mod future {
     #[lang = "future_trait"]
     pub trait Future {}
@@ -514,14 +514,14 @@ pub mod future {
 
         check(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:std
 use std::future::*;
 fn foo() {
     let a = async {};
     a.<|>
 }
 
-//- /std/lib.rs
+//- /std/lib.rs crate:std
 pub mod future {
     #[lang = "future_trait"]
     pub trait Future {
diff --git a/crates/ide/src/completion/complete_mod.rs b/crates/ide/src/completion/complete_mod.rs
index 3cfc2e1314..c7a99bdc33 100644
--- a/crates/ide/src/completion/complete_mod.rs
+++ b/crates/ide/src/completion/complete_mod.rs
@@ -300,7 +300,7 @@ mod tests {
     //     "#,
     //         expect![[r#"
     //             md bar;
-    //         "#]],
+    //         "#]],foo
     //     );
     // }
 
@@ -308,7 +308,7 @@ mod tests {
     fn already_declared_bin_module_completion_omitted() {
         check(
             r#"
-            //- /src/bin.rs
+            //- /src/bin.rs crate:main
             fn main() {}
             //- /src/bin/foo.rs
             mod <|>
diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/ide/src/completion/complete_qualified_path.rs
index 00e89f0fdf..2fafedd47c 100644
--- a/crates/ide/src/completion/complete_qualified_path.rs
+++ b/crates/ide/src/completion/complete_qualified_path.rs
@@ -422,10 +422,10 @@ fn foo() { let _ = U::<|> }
     fn completes_use_paths_across_crates() {
         check(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:foo
 use foo::<|>;
 
-//- /foo/lib.rs
+//- /foo/lib.rs crate:foo
 pub mod bar { pub struct S; }
 "#,
             expect![[r#"
diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/ide/src/completion/complete_unqualified_path.rs
index 8eda4b64d4..2010d9a2fd 100644
--- a/crates/ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ide/src/completion/complete_unqualified_path.rs
@@ -271,10 +271,10 @@ fn quux() { <|> }
     fn completes_extern_prelude() {
         check(
             r#"
-//- /lib.rs
+//- /lib.rs crate:main deps:other_crate
 use <|>;
 
-//- /other_crate/lib.rs
+//- /other_crate/lib.rs crate:other_crate
 // nothing here
 "#,
             expect![[r#"
@@ -350,10 +350,10 @@ fn foo() {
     fn completes_prelude() {
         check(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:std
 fn foo() { let x: <|> }
 
-//- /std/lib.rs
+//- /std/lib.rs crate:std
 #[prelude_import]
 use prelude::*;
 
@@ -371,16 +371,16 @@ mod prelude { struct Option; }
     fn completes_std_prelude_if_core_is_defined() {
         check(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:core,std
 fn foo() { let x: <|> }
 
-//- /core/lib.rs
+//- /core/lib.rs crate:core
 #[prelude_import]
 use prelude::*;
 
 mod prelude { struct Option; }
 
-//- /std/lib.rs
+//- /std/lib.rs crate:std deps:core
 #[prelude_import]
 use prelude::*;
 
diff --git a/crates/ide/src/completion/presentation.rs b/crates/ide/src/completion/presentation.rs
index 987cbfa7a8..a5172b87ec 100644
--- a/crates/ide/src/completion/presentation.rs
+++ b/crates/ide/src/completion/presentation.rs
@@ -1172,9 +1172,9 @@ fn foo(xs: Vec<i128>)
         check_edit(
             "frobnicate!",
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:foo
 use foo::<|>;
-//- /foo/lib.rs
+//- /foo/lib.rs crate:foo
 #[macro_export]
 macro_rules frobnicate { () => () }
 "#,
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index dc815a4838..f5d627b6ef 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -218,10 +218,7 @@ mod tests {
     use stdx::trim_indent;
     use test_utils::assert_eq_text;
 
-    use crate::{
-        mock_analysis::{analysis_and_position, single_file, MockAnalysis},
-        DiagnosticsConfig,
-    };
+    use crate::{fixture, DiagnosticsConfig};
 
     /// Takes a multi-file input fixture with annotated cursor positions,
     /// and checks that:
@@ -231,7 +228,7 @@ mod tests {
     fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
         let after = trim_indent(ra_fixture_after);
 
-        let (analysis, file_position) = analysis_and_position(ra_fixture_before);
+        let (analysis, file_position) = fixture::position(ra_fixture_before);
         let diagnostic = analysis
             .diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
             .unwrap()
@@ -260,7 +257,7 @@ mod tests {
     /// which has a fix that can apply to other files.
     fn check_apply_diagnostic_fix_in_other_file(ra_fixture_before: &str, ra_fixture_after: &str) {
         let ra_fixture_after = &trim_indent(ra_fixture_after);
-        let (analysis, file_pos) = analysis_and_position(ra_fixture_before);
+        let (analysis, file_pos) = fixture::position(ra_fixture_before);
         let current_file_id = file_pos.file_id;
         let diagnostic = analysis
             .diagnostics(&DiagnosticsConfig::default(), current_file_id)
@@ -282,9 +279,7 @@ mod tests {
     /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
     /// apply to the file containing the cursor.
     fn check_no_diagnostics(ra_fixture: &str) {
-        let mock = MockAnalysis::with_files(ra_fixture);
-        let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>();
-        let analysis = mock.analysis();
+        let (analysis, files) = fixture::files(ra_fixture);
         let diagnostics = files
             .into_iter()
             .flat_map(|file_id| {
@@ -295,7 +290,7 @@ mod tests {
     }
 
     fn check_expect(ra_fixture: &str, expect: Expect) {
-        let (analysis, file_id) = single_file(ra_fixture);
+        let (analysis, file_id) = fixture::file(ra_fixture);
         let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
         expect.assert_debug_eq(&diagnostics)
     }
@@ -304,7 +299,7 @@ mod tests {
     fn test_wrap_return_type() {
         check_fix(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:core
 use core::result::Result::{self, Ok, Err};
 
 fn div(x: i32, y: i32) -> Result<i32, ()> {
@@ -313,7 +308,7 @@ fn div(x: i32, y: i32) -> Result<i32, ()> {
     }
     x / y<|>
 }
-//- /core/lib.rs
+//- /core/lib.rs crate:core
 pub mod result {
     pub enum Result<T, E> { Ok(T), Err(E) }
 }
@@ -335,7 +330,7 @@ fn div(x: i32, y: i32) -> Result<i32, ()> {
     fn test_wrap_return_type_handles_generic_functions() {
         check_fix(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:core
 use core::result::Result::{self, Ok, Err};
 
 fn div<T>(x: T) -> Result<T, i32> {
@@ -344,7 +339,7 @@ fn div<T>(x: T) -> Result<T, i32> {
     }
     <|>x
 }
-//- /core/lib.rs
+//- /core/lib.rs crate:core
 pub mod result {
     pub enum Result<T, E> { Ok(T), Err(E) }
 }
@@ -366,7 +361,7 @@ fn div<T>(x: T) -> Result<T, i32> {
     fn test_wrap_return_type_handles_type_aliases() {
         check_fix(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:core
 use core::result::Result::{self, Ok, Err};
 
 type MyResult<T> = Result<T, ()>;
@@ -377,7 +372,7 @@ fn div(x: i32, y: i32) -> MyResult<i32> {
     }
     x <|>/ y
 }
-//- /core/lib.rs
+//- /core/lib.rs crate:core
 pub mod result {
     pub enum Result<T, E> { Ok(T), Err(E) }
 }
@@ -401,12 +396,12 @@ fn div(x: i32, y: i32) -> MyResult<i32> {
     fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
         check_no_diagnostics(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:core
 use core::result::Result::{self, Ok, Err};
 
 fn foo() -> Result<(), i32> { 0 }
 
-//- /core/lib.rs
+//- /core/lib.rs crate:core
 pub mod result {
     pub enum Result<T, E> { Ok(T), Err(E) }
 }
@@ -418,14 +413,14 @@ pub mod result {
     fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
         check_no_diagnostics(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:core
 use core::result::Result::{self, Ok, Err};
 
 enum SomeOtherEnum { Ok(i32), Err(String) }
 
 fn foo() -> SomeOtherEnum { 0 }
 
-//- /core/lib.rs
+//- /core/lib.rs crate:core
 pub mod result {
     pub enum Result<T, E> { Ok(T), Err(E) }
 }
@@ -567,7 +562,7 @@ fn test_fn() {
                                     file_system_edits: [
                                         CreateFile {
                                             anchor: FileId(
-                                                1,
+                                                0,
                                             ),
                                             dst: "foo.rs",
                                         },
@@ -787,7 +782,7 @@ struct Foo {
         let mut config = DiagnosticsConfig::default();
         config.disabled.insert("unresolved-module".into());
 
-        let (analysis, file_id) = single_file(r#"mod foo;"#);
+        let (analysis, file_id) = fixture::file(r#"mod foo;"#);
 
         let diagnostics = analysis.diagnostics(&config, file_id).unwrap();
         assert!(diagnostics.is_empty());
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index 1ee80c2dd4..cf9d617dc0 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -423,11 +423,11 @@ pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) ->
 mod tests {
     use expect_test::expect;
 
-    use crate::{mock_analysis::single_file, Query};
+    use crate::{fixture, Query};
 
     #[test]
     fn test_nav_for_symbol() {
-        let (analysis, _) = single_file(
+        let (analysis, _) = fixture::file(
             r#"
 enum FooInner { }
 fn foo() { enum FooInner { } }
@@ -439,7 +439,7 @@ fn foo() { enum FooInner { } }
             [
                 NavigationTarget {
                     file_id: FileId(
-                        1,
+                        0,
                     ),
                     full_range: 0..17,
                     focus_range: Some(
@@ -455,7 +455,7 @@ fn foo() { enum FooInner { } }
                 },
                 NavigationTarget {
                     file_id: FileId(
-                        1,
+                        0,
                     ),
                     full_range: 29..46,
                     focus_range: Some(
@@ -478,7 +478,7 @@ fn foo() { enum FooInner { } }
 
     #[test]
     fn test_world_symbols_are_case_sensitive() {
-        let (analysis, _) = single_file(
+        let (analysis, _) = fixture::file(
             r#"
 fn foo() {}
 struct Foo;
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index 8a285bcf7a..8d75e0f05c 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -122,10 +122,10 @@ fn insert_whitespaces(syn: SyntaxNode) -> String {
 mod tests {
     use expect_test::{expect, Expect};
 
-    use crate::mock_analysis::analysis_and_position;
+    use crate::fixture;
 
     fn check(ra_fixture: &str, expect: Expect) {
-        let (analysis, pos) = analysis_and_position(ra_fixture);
+        let (analysis, pos) = fixture::position(ra_fixture);
         let expansion = analysis.expand_macro(pos).unwrap().unwrap();
         let actual = format!("{}\n{}", expansion.name, expansion.expansion);
         expect.assert_eq(&actual);
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 34563a0267..3ee0af8ad4 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -315,12 +315,12 @@ fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
 
 #[cfg(test)]
 mod tests {
-    use crate::mock_analysis::analysis_and_position;
+    use crate::fixture;
 
     use super::*;
 
     fn do_check(before: &str, afters: &[&str]) {
-        let (analysis, position) = analysis_and_position(&before);
+        let (analysis, position) = fixture::position(&before);
         let before = analysis.file_text(position.file_id).unwrap();
         let range = TextRange::empty(position.offset);
         let mut frange = FileRange { file_id: position.file_id, range };
diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs
new file mode 100644
index 0000000000..ed06689f0c
--- /dev/null
+++ b/crates/ide/src/fixture.rs
@@ -0,0 +1,70 @@
+//! Utilities for creating `Analysis` instances for tests.
+use base_db::fixture::ChangeFixture;
+use test_utils::{extract_annotations, RangeOrOffset};
+
+use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange};
+
+/// Creates analysis for a single file.
+pub(crate) fn file(ra_fixture: &str) -> (Analysis, FileId) {
+    let mut host = AnalysisHost::default();
+    let change_fixture = ChangeFixture::parse(ra_fixture);
+    host.db.apply_change(change_fixture.change);
+    (host.analysis(), change_fixture.files[0])
+}
+
+/// Creates analysis for many files.
+pub(crate) fn files(ra_fixture: &str) -> (Analysis, Vec<FileId>) {
+    let mut host = AnalysisHost::default();
+    let change_fixture = ChangeFixture::parse(ra_fixture);
+    host.db.apply_change(change_fixture.change);
+    (host.analysis(), change_fixture.files)
+}
+
+/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
+pub(crate) fn position(ra_fixture: &str) -> (Analysis, FilePosition) {
+    let mut host = AnalysisHost::default();
+    let change_fixture = ChangeFixture::parse(ra_fixture);
+    host.db.apply_change(change_fixture.change);
+    let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker (<|>)");
+    let offset = match range_or_offset {
+        RangeOrOffset::Range(_) => panic!(),
+        RangeOrOffset::Offset(it) => it,
+    };
+    (host.analysis(), FilePosition { file_id, offset })
+}
+
+/// Creates analysis for a single file, returns range marked with a pair of <|>.
+pub(crate) fn range(ra_fixture: &str) -> (Analysis, FileRange) {
+    let mut host = AnalysisHost::default();
+    let change_fixture = ChangeFixture::parse(ra_fixture);
+    host.db.apply_change(change_fixture.change);
+    let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker (<|>)");
+    let range = match range_or_offset {
+        RangeOrOffset::Range(it) => it,
+        RangeOrOffset::Offset(_) => panic!(),
+    };
+    (host.analysis(), FileRange { file_id, range })
+}
+
+/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
+pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(FileRange, String)>) {
+    let mut host = AnalysisHost::default();
+    let change_fixture = ChangeFixture::parse(ra_fixture);
+    host.db.apply_change(change_fixture.change);
+    let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker (<|>)");
+    let offset = match range_or_offset {
+        RangeOrOffset::Range(_) => panic!(),
+        RangeOrOffset::Offset(it) => it,
+    };
+
+    let annotations = change_fixture
+        .files
+        .iter()
+        .flat_map(|&file_id| {
+            let file_text = host.analysis().file_text(file_id).unwrap();
+            let annotations = extract_annotations(&file_text);
+            annotations.into_iter().map(move |(range, data)| (FileRange { file_id, range }, data))
+        })
+        .collect();
+    (host.analysis(), FilePosition { file_id, offset }, annotations)
+}
diff --git a/crates/ide/src/fn_references.rs b/crates/ide/src/fn_references.rs
index 1989a562be..459f201ed8 100644
--- a/crates/ide/src/fn_references.rs
+++ b/crates/ide/src/fn_references.rs
@@ -25,15 +25,14 @@ fn method_range(item: SyntaxNode, file_id: FileId) -> Option<FileRange> {
 
 #[cfg(test)]
 mod tests {
-    use crate::mock_analysis::analysis_and_position;
+    use crate::fixture;
     use crate::{FileRange, TextSize};
     use std::ops::RangeInclusive;
 
     #[test]
     fn test_find_all_methods() {
-        let (analysis, pos) = analysis_and_position(
+        let (analysis, pos) = fixture::position(
             r#"
-            //- /lib.rs
             fn private_fn() {<|>}
 
             pub fn pub_fn() {}
@@ -48,9 +47,8 @@ mod tests {
 
     #[test]
     fn test_find_trait_methods() {
-        let (analysis, pos) = analysis_and_position(
+        let (analysis, pos) = fixture::position(
             r#"
-            //- /lib.rs
             trait Foo {
                 fn bar() {<|>}
                 fn baz() {}
@@ -64,7 +62,7 @@ mod tests {
 
     #[test]
     fn test_skip_tests() {
-        let (analysis, pos) = analysis_and_position(
+        let (analysis, pos) = fixture::position(
             r#"
             //- /lib.rs
             #[test]
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 15e9b7fad8..582bf4837d 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -103,12 +103,11 @@ mod tests {
     use base_db::FileRange;
     use syntax::{TextRange, TextSize};
 
-    use crate::mock_analysis::MockAnalysis;
+    use crate::fixture;
 
     fn check(ra_fixture: &str) {
-        let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
-        let (mut expected, data) = mock.annotation();
-        let analysis = mock.analysis();
+        let (analysis, position, mut annotations) = fixture::annotations(ra_fixture);
+        let (mut expected, data) = annotations.pop().unwrap();
         match data.as_str() {
             "" => (),
             "file" => {
@@ -133,9 +132,9 @@ mod tests {
     fn goto_def_for_extern_crate() {
         check(
             r#"
-            //- /main.rs
+            //- /main.rs crate:main deps:std
             extern crate std<|>;
-            //- /std/lib.rs
+            //- /std/lib.rs crate:std
             // empty
             //^ file
             "#,
@@ -146,9 +145,9 @@ mod tests {
     fn goto_def_for_renamed_extern_crate() {
         check(
             r#"
-            //- /main.rs
+            //- /main.rs crate:main deps:std
             extern crate std as abc<|>;
-            //- /std/lib.rs
+            //- /std/lib.rs crate:std
             // empty
             //^ file
             "#,
@@ -342,10 +341,10 @@ fn bar() {
     fn goto_def_for_use_alias() {
         check(
             r#"
-//- /lib.rs
+//- /lib.rs crate:main deps:foo
 use foo as bar<|>;
 
-//- /foo/lib.rs
+//- /foo/lib.rs crate:foo
 // empty
 //^ file
 "#,
@@ -356,10 +355,10 @@ use foo as bar<|>;
     fn goto_def_for_use_alias_foo_macro() {
         check(
             r#"
-//- /lib.rs
+//- /lib.rs crate:main deps:foo
 use foo::foo as bar<|>;
 
-//- /foo/lib.rs
+//- /foo/lib.rs crate:foo
 #[macro_export]
 macro_rules! foo { () => { () } }
            //^^^
@@ -371,7 +370,6 @@ macro_rules! foo { () => { () } }
     fn goto_def_for_methods() {
         check(
             r#"
-//- /lib.rs
 struct Foo;
 impl Foo {
     fn frobnicate(&self) { }
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index f503f4ec5f..6c586bbd12 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -76,12 +76,10 @@ fn impls_for_trait(
 mod tests {
     use base_db::FileRange;
 
-    use crate::mock_analysis::MockAnalysis;
+    use crate::fixture;
 
     fn check(ra_fixture: &str) {
-        let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
-        let annotations = mock.annotations();
-        let analysis = mock.analysis();
+        let (analysis, position, annotations) = fixture::annotations(ra_fixture);
 
         let navs = analysis.goto_implementation(position).unwrap().unwrap().info;
 
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
index 4a151b1506..6d0df04dd0 100644
--- a/crates/ide/src/goto_type_definition.rs
+++ b/crates/ide/src/goto_type_definition.rs
@@ -56,13 +56,12 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
 mod tests {
     use base_db::FileRange;
 
-    use crate::mock_analysis::MockAnalysis;
+    use crate::fixture;
 
     fn check(ra_fixture: &str) {
-        let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
-        let (expected, data) = mock.annotation();
+        let (analysis, position, mut annotations) = fixture::annotations(ra_fixture);
+        let (expected, data) = annotations.pop().unwrap();
         assert!(data.is_empty());
-        let analysis = mock.analysis();
 
         let mut navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
         assert_eq!(navs.len(), 1);
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index bb9f12cd37..9cf02f0a32 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -377,17 +377,17 @@ mod tests {
     use base_db::FileLoader;
     use expect_test::{expect, Expect};
 
-    use crate::mock_analysis::analysis_and_position;
+    use crate::fixture;
 
     use super::*;
 
     fn check_hover_no_result(ra_fixture: &str) {
-        let (analysis, position) = analysis_and_position(ra_fixture);
+        let (analysis, position) = fixture::position(ra_fixture);
         assert!(analysis.hover(position, true).unwrap().is_none());
     }
 
     fn check(ra_fixture: &str, expect: Expect) {
-        let (analysis, position) = analysis_and_position(ra_fixture);
+        let (analysis, position) = fixture::position(ra_fixture);
         let hover = analysis.hover(position, true).unwrap().unwrap();
 
         let content = analysis.db.file_text(position.file_id);
@@ -398,7 +398,7 @@ mod tests {
     }
 
     fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
-        let (analysis, position) = analysis_and_position(ra_fixture);
+        let (analysis, position) = fixture::position(ra_fixture);
         let hover = analysis.hover(position, false).unwrap().unwrap();
 
         let content = analysis.db.file_text(position.file_id);
@@ -409,7 +409,7 @@ mod tests {
     }
 
     fn check_actions(ra_fixture: &str, expect: Expect) {
-        let (analysis, position) = analysis_and_position(ra_fixture);
+        let (analysis, position) = fixture::position(ra_fixture);
         let hover = analysis.hover(position, true).unwrap().unwrap();
         expect.assert_debug_eq(&hover.info.actions)
     }
@@ -963,7 +963,7 @@ impl Thing {
             "#]],
         )
     } /* FIXME: revive these tests
-              let (analysis, position) = analysis_and_position(
+              let (analysis, position) = fixture::position(
                   "
                   struct Thing { x: u32 }
                   impl Thing {
@@ -977,7 +977,7 @@ impl Thing {
               let hover = analysis.hover(position).unwrap().unwrap();
               assert_eq!(trim_markup(&hover.info.markup.as_str()), ("Thing"));
 
-              let (analysis, position) = analysis_and_position(
+              let (analysis, position) = fixture::position(
                   "
                   enum Thing { A }
                   impl Thing {
@@ -990,7 +990,7 @@ impl Thing {
               let hover = analysis.hover(position).unwrap().unwrap();
               assert_eq!(trim_markup(&hover.info.markup.as_str()), ("enum Thing"));
 
-              let (analysis, position) = analysis_and_position(
+              let (analysis, position) = fixture::position(
                   "
                   enum Thing { A }
                   impl Thing {
@@ -1275,7 +1275,7 @@ fn bar() { fo<|>o(); }
                     Implementaion(
                         FilePosition {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             offset: 13,
                         },
@@ -1289,9 +1289,9 @@ fn bar() { fo<|>o(); }
     fn test_hover_extern_crate() {
         check(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:std
 extern crate st<|>d;
-//- /std/lib.rs
+//- /std/lib.rs crate:std
 //! Standard library for this test
 //!
 //! Printed?
@@ -1307,9 +1307,9 @@ extern crate st<|>d;
         );
         check(
             r#"
-//- /main.rs
+//- /main.rs crate:main deps:std
 extern crate std as ab<|>c;
-//- /std/lib.rs
+//- /std/lib.rs crate:std
 //! Standard library for this test
 //!
 //! Printed?
@@ -1989,7 +1989,7 @@ fn foo() { let bar = Bar; bar.fo<|>o(); }
                     Implementaion(
                         FilePosition {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             offset: 6,
                         },
@@ -2008,7 +2008,7 @@ fn foo() { let bar = Bar; bar.fo<|>o(); }
                     Implementaion(
                         FilePosition {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             offset: 7,
                         },
@@ -2027,7 +2027,7 @@ fn foo() { let bar = Bar; bar.fo<|>o(); }
                     Implementaion(
                         FilePosition {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             offset: 6,
                         },
@@ -2046,7 +2046,7 @@ fn foo() { let bar = Bar; bar.fo<|>o(); }
                     Implementaion(
                         FilePosition {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             offset: 5,
                         },
@@ -2069,7 +2069,7 @@ fn foo_<|>test() {}
                         Runnable {
                             nav: NavigationTarget {
                                 file_id: FileId(
-                                    1,
+                                    0,
                                 ),
                                 full_range: 0..24,
                                 focus_range: Some(
@@ -2112,7 +2112,7 @@ mod tests<|> {
                         Runnable {
                             nav: NavigationTarget {
                                 file_id: FileId(
-                                    1,
+                                    0,
                                 ),
                                 full_range: 0..46,
                                 focus_range: Some(
@@ -2151,7 +2151,7 @@ fn main() { let s<|>t = S{ f1:0 }; }
                                 mod_path: "test::S",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..19,
                                     focus_range: Some(
@@ -2190,7 +2190,7 @@ fn main() { let s<|>t = S{ f1:Arg(0) }; }
                                 mod_path: "test::S",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 17..37,
                                     focus_range: Some(
@@ -2209,7 +2209,7 @@ fn main() { let s<|>t = S{ f1:Arg(0) }; }
                                 mod_path: "test::Arg",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..16,
                                     focus_range: Some(
@@ -2248,7 +2248,7 @@ fn main() { let s<|>t = S{ f1: S{ f1: Arg(0) } }; }
                                 mod_path: "test::S",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 17..37,
                                     focus_range: Some(
@@ -2267,7 +2267,7 @@ fn main() { let s<|>t = S{ f1: S{ f1: Arg(0) } }; }
                                 mod_path: "test::Arg",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..16,
                                     focus_range: Some(
@@ -2309,7 +2309,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); }
                                 mod_path: "test::A",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..14,
                                     focus_range: Some(
@@ -2328,7 +2328,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); }
                                 mod_path: "test::B",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 15..29,
                                     focus_range: Some(
@@ -2347,7 +2347,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); }
                                 mod_path: "test::M::C",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 42..60,
                                     focus_range: Some(
@@ -2386,7 +2386,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..12,
                                     focus_range: Some(
@@ -2426,7 +2426,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..15,
                                     focus_range: Some(
@@ -2445,7 +2445,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::S",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 16..25,
                                     focus_range: Some(
@@ -2485,7 +2485,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..12,
                                     focus_range: Some(
@@ -2504,7 +2504,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::Bar",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 13..25,
                                     focus_range: Some(
@@ -2547,7 +2547,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..15,
                                     focus_range: Some(
@@ -2566,7 +2566,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::Bar",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 16..31,
                                     focus_range: Some(
@@ -2585,7 +2585,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::S1",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 32..44,
                                     focus_range: Some(
@@ -2604,7 +2604,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::S2",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 45..57,
                                     focus_range: Some(
@@ -2641,7 +2641,7 @@ fn foo(ar<|>g: &impl Foo) {}
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..12,
                                     focus_range: Some(
@@ -2681,7 +2681,7 @@ fn foo(ar<|>g: &impl Foo + Bar<S>) {}
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..12,
                                     focus_range: Some(
@@ -2700,7 +2700,7 @@ fn foo(ar<|>g: &impl Foo + Bar<S>) {}
                                 mod_path: "test::Bar",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 13..28,
                                     focus_range: Some(
@@ -2719,7 +2719,7 @@ fn foo(ar<|>g: &impl Foo + Bar<S>) {}
                                 mod_path: "test::S",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 29..39,
                                     focus_range: Some(
@@ -2764,7 +2764,7 @@ mod future {
                                 mod_path: "test::future::Future",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 101..163,
                                     focus_range: Some(
@@ -2783,7 +2783,7 @@ mod future {
                                 mod_path: "test::S",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..9,
                                     focus_range: Some(
@@ -2821,7 +2821,7 @@ fn foo(ar<|>g: &impl Foo<S>) {}
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..15,
                                     focus_range: Some(
@@ -2840,7 +2840,7 @@ fn foo(ar<|>g: &impl Foo<S>) {}
                                 mod_path: "test::S",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 16..27,
                                     focus_range: Some(
@@ -2883,7 +2883,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::B",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 42..55,
                                     focus_range: Some(
@@ -2902,7 +2902,7 @@ fn main() { let s<|>t = foo(); }
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..12,
                                     focus_range: Some(
@@ -2939,7 +2939,7 @@ fn foo(ar<|>g: &dyn Foo) {}
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..12,
                                     focus_range: Some(
@@ -2977,7 +2977,7 @@ fn foo(ar<|>g: &dyn Foo<S>) {}
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..15,
                                     focus_range: Some(
@@ -2996,7 +2996,7 @@ fn foo(ar<|>g: &dyn Foo<S>) {}
                                 mod_path: "test::S",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 16..27,
                                     focus_range: Some(
@@ -3037,7 +3037,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
                                 mod_path: "test::ImplTrait",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..21,
                                     focus_range: Some(
@@ -3056,7 +3056,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
                                 mod_path: "test::B",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 43..57,
                                     focus_range: Some(
@@ -3075,7 +3075,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
                                 mod_path: "test::DynTrait",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 22..42,
                                     focus_range: Some(
@@ -3094,7 +3094,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
                                 mod_path: "test::S",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 58..69,
                                     focus_range: Some(
@@ -3142,7 +3142,7 @@ fn main() { let s<|>t = test().get(); }
                                 mod_path: "test::Foo",
                                 nav: NavigationTarget {
                                     file_id: FileId(
-                                        1,
+                                        0,
                                     ),
                                     full_range: 0..62,
                                     focus_range: Some(
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 583f39d85b..0afe5f8fd2 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -339,14 +339,14 @@ mod tests {
     use expect_test::{expect, Expect};
     use test_utils::extract_annotations;
 
-    use crate::{inlay_hints::InlayHintsConfig, mock_analysis::single_file};
+    use crate::{fixture, inlay_hints::InlayHintsConfig};
 
     fn check(ra_fixture: &str) {
         check_with_config(InlayHintsConfig::default(), ra_fixture);
     }
 
     fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
-        let (analysis, file_id) = single_file(ra_fixture);
+        let (analysis, file_id) = fixture::file(ra_fixture);
         let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
         let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
         let actual =
@@ -355,7 +355,7 @@ mod tests {
     }
 
     fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
-        let (analysis, file_id) = single_file(ra_fixture);
+        let (analysis, file_id) = fixture::file(ra_fixture);
         let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
         expect.assert_debug_eq(&inlay_hints)
     }
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index bab3ec1ff1..1aa673cf8a 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -16,7 +16,7 @@ macro_rules! eprintln {
 }
 
 #[cfg(test)]
-mod mock_analysis;
+mod fixture;
 
 mod markup;
 mod prime_caches;
@@ -87,12 +87,11 @@ pub use assists::{
     utils::MergeBehaviour, Assist, AssistConfig, AssistId, AssistKind, ResolvedAssist,
 };
 pub use base_db::{
-    Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot,
+    Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot,
     SourceRootId,
 };
 pub use hir::{Documentation, Semantics};
 pub use ide_db::{
-    change::AnalysisChange,
     label::Label,
     line_index::{LineCol, LineIndex},
     search::SearchScope,
@@ -141,7 +140,7 @@ impl AnalysisHost {
 
     /// Applies changes to the current state of the world. If there are
     /// outstanding snapshots, they will be canceled.
-    pub fn apply_change(&mut self, change: AnalysisChange) {
+    pub fn apply_change(&mut self, change: Change) {
         self.db.apply_change(change)
     }
 
@@ -195,7 +194,7 @@ impl Analysis {
         file_set.insert(file_id, VfsPath::new_virtual_path("/main.rs".to_string()));
         let source_root = SourceRoot::new_local(file_set);
 
-        let mut change = AnalysisChange::new();
+        let mut change = Change::new();
         change.set_roots(vec![source_root]);
         let mut crate_graph = CrateGraph::default();
         // FIXME: cfg options
diff --git a/crates/ide/src/mock_analysis.rs b/crates/ide/src/mock_analysis.rs
deleted file mode 100644
index 327cdf91eb..0000000000
--- a/crates/ide/src/mock_analysis.rs
+++ /dev/null
@@ -1,176 +0,0 @@
-//! FIXME: write short doc here
-use std::sync::Arc;
-
-use base_db::{CrateName, FileSet, SourceRoot, VfsPath};
-use cfg::CfgOptions;
-use test_utils::{
-    extract_annotations, extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER,
-};
-
-use crate::{
-    Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange,
-};
-
-/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
-/// from a set of in-memory files.
-#[derive(Debug, Default)]
-pub(crate) struct MockAnalysis {
-    files: Vec<Fixture>,
-}
-
-impl MockAnalysis {
-    /// Creates `MockAnalysis` using a fixture data in the following format:
-    ///
-    /// ```not_rust
-    /// //- /main.rs
-    /// mod foo;
-    /// fn main() {}
-    ///
-    /// //- /foo.rs
-    /// struct Baz;
-    /// ```
-    pub(crate) fn with_files(ra_fixture: &str) -> MockAnalysis {
-        let (res, pos) = MockAnalysis::with_fixture(ra_fixture);
-        assert!(pos.is_none());
-        res
-    }
-
-    /// Same as `with_files`, but requires that a single file contains a `<|>` marker,
-    /// whose position is also returned.
-    pub(crate) fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) {
-        let (res, position) = MockAnalysis::with_fixture(fixture);
-        let (file_id, range_or_offset) = position.expect("expected a marker (<|>)");
-        let offset = match range_or_offset {
-            RangeOrOffset::Range(_) => panic!(),
-            RangeOrOffset::Offset(it) => it,
-        };
-        (res, FilePosition { file_id, offset })
-    }
-
-    fn with_fixture(fixture: &str) -> (MockAnalysis, Option<(FileId, RangeOrOffset)>) {
-        let mut position = None;
-        let mut res = MockAnalysis::default();
-        for mut entry in Fixture::parse(fixture) {
-            if entry.text.contains(CURSOR_MARKER) {
-                assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
-                let (range_or_offset, text) = extract_range_or_offset(&entry.text);
-                entry.text = text;
-                let file_id = res.add_file_fixture(entry);
-                position = Some((file_id, range_or_offset));
-            } else {
-                res.add_file_fixture(entry);
-            }
-        }
-        (res, position)
-    }
-
-    fn add_file_fixture(&mut self, fixture: Fixture) -> FileId {
-        let file_id = FileId((self.files.len() + 1) as u32);
-        self.files.push(fixture);
-        file_id
-    }
-
-    pub(crate) fn id_of(&self, path: &str) -> FileId {
-        let (file_id, _) =
-            self.files().find(|(_, data)| path == data.path).expect("no file in this mock");
-        file_id
-    }
-    pub(crate) fn annotations(&self) -> Vec<(FileRange, String)> {
-        self.files()
-            .flat_map(|(file_id, fixture)| {
-                let annotations = extract_annotations(&fixture.text);
-                annotations
-                    .into_iter()
-                    .map(move |(range, data)| (FileRange { file_id, range }, data))
-            })
-            .collect()
-    }
-    pub(crate) fn files(&self) -> impl Iterator<Item = (FileId, &Fixture)> + '_ {
-        self.files.iter().enumerate().map(|(idx, fixture)| (FileId(idx as u32 + 1), fixture))
-    }
-    pub(crate) fn annotation(&self) -> (FileRange, String) {
-        let mut all = self.annotations();
-        assert_eq!(all.len(), 1);
-        all.pop().unwrap()
-    }
-    pub(crate) fn analysis_host(self) -> AnalysisHost {
-        let mut host = AnalysisHost::default();
-        let mut change = AnalysisChange::new();
-        let mut file_set = FileSet::default();
-        let mut crate_graph = CrateGraph::default();
-        let mut root_crate = None;
-        for (i, data) in self.files.into_iter().enumerate() {
-            let path = data.path;
-            assert!(path.starts_with('/'));
-
-            let mut cfg = CfgOptions::default();
-            data.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into()));
-            data.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into()));
-            let edition: Edition =
-                data.edition.and_then(|it| it.parse().ok()).unwrap_or(Edition::Edition2018);
-
-            let file_id = FileId(i as u32 + 1);
-            let env = data.env.into_iter().collect();
-            if path == "/lib.rs" || path == "/main.rs" {
-                root_crate = Some(crate_graph.add_crate_root(
-                    file_id,
-                    edition,
-                    Some("test".to_string()),
-                    cfg,
-                    env,
-                    Default::default(),
-                ));
-            } else if path.ends_with("/lib.rs") {
-                let base = &path[..path.len() - "/lib.rs".len()];
-                let crate_name = &base[base.rfind('/').unwrap() + '/'.len_utf8()..];
-                let other_crate = crate_graph.add_crate_root(
-                    file_id,
-                    edition,
-                    Some(crate_name.to_string()),
-                    cfg,
-                    env,
-                    Default::default(),
-                );
-                if let Some(root_crate) = root_crate {
-                    crate_graph
-                        .add_dep(root_crate, CrateName::new(crate_name).unwrap(), other_crate)
-                        .unwrap();
-                }
-            }
-            let path = VfsPath::new_virtual_path(path.to_string());
-            file_set.insert(file_id, path);
-            change.change_file(file_id, Some(Arc::new(data.text).to_owned()));
-        }
-        change.set_crate_graph(crate_graph);
-        change.set_roots(vec![SourceRoot::new_local(file_set)]);
-        host.apply_change(change);
-        host
-    }
-    pub(crate) fn analysis(self) -> Analysis {
-        self.analysis_host().analysis()
-    }
-}
-
-/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
-pub(crate) fn analysis_and_position(ra_fixture: &str) -> (Analysis, FilePosition) {
-    let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
-    (mock.analysis(), position)
-}
-
-/// Creates analysis for a single file.
-pub(crate) fn single_file(ra_fixture: &str) -> (Analysis, FileId) {
-    let mock = MockAnalysis::with_files(ra_fixture);
-    let file_id = mock.id_of("/main.rs");
-    (mock.analysis(), file_id)
-}
-
-/// Creates analysis for a single file, returns range marked with a pair of <|>.
-pub(crate) fn analysis_and_range(ra_fixture: &str) -> (Analysis, FileRange) {
-    let (res, position) = MockAnalysis::with_fixture(ra_fixture);
-    let (file_id, range_or_offset) = position.expect("expected a marker (<|>)");
-    let range = match range_or_offset {
-        RangeOrOffset::Range(it) => it,
-        RangeOrOffset::Offset(_) => panic!(),
-    };
-    (res.analysis(), FileRange { file_id, range })
-}
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs
index 59ed2967cf..ef94acfecd 100644
--- a/crates/ide/src/parent_module.rs
+++ b/crates/ide/src/parent_module.rs
@@ -63,19 +63,13 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
 
 #[cfg(test)]
 mod tests {
-    use base_db::Env;
-    use cfg::CfgOptions;
     use test_utils::mark;
 
-    use crate::{
-        mock_analysis::{analysis_and_position, MockAnalysis},
-        AnalysisChange, CrateGraph,
-        Edition::Edition2018,
-    };
+    use crate::fixture::{self};
 
     #[test]
     fn test_resolve_parent_module() {
-        let (analysis, pos) = analysis_and_position(
+        let (analysis, pos) = fixture::position(
             "
             //- /lib.rs
             mod foo;
@@ -84,13 +78,13 @@ mod tests {
             ",
         );
         let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
-        nav.assert_match("foo MODULE FileId(1) 0..8");
+        nav.assert_match("foo MODULE FileId(0) 0..8");
     }
 
     #[test]
     fn test_resolve_parent_module_on_module_decl() {
         mark::check!(test_resolve_parent_module_on_module_decl);
-        let (analysis, pos) = analysis_and_position(
+        let (analysis, pos) = fixture::position(
             "
             //- /lib.rs
             mod foo;
@@ -103,12 +97,12 @@ mod tests {
             ",
         );
         let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
-        nav.assert_match("foo MODULE FileId(1) 0..8");
+        nav.assert_match("foo MODULE FileId(0) 0..8");
     }
 
     #[test]
     fn test_resolve_parent_module_for_inline() {
-        let (analysis, pos) = analysis_and_position(
+        let (analysis, pos) = fixture::position(
             "
             //- /lib.rs
             mod foo {
@@ -119,37 +113,19 @@ mod tests {
             ",
         );
         let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
-        nav.assert_match("baz MODULE FileId(1) 32..44");
+        nav.assert_match("baz MODULE FileId(0) 32..44");
     }
 
     #[test]
     fn test_resolve_crate_root() {
-        let mock = MockAnalysis::with_files(
+        let (analysis, file_id) = fixture::file(
             r#"
-//- /bar.rs
+//- /main.rs
 mod foo;
 //- /foo.rs
-// empty
+<|>
 "#,
         );
-        let root_file = mock.id_of("/bar.rs");
-        let mod_file = mock.id_of("/foo.rs");
-        let mut host = mock.analysis_host();
-        assert!(host.analysis().crate_for(mod_file).unwrap().is_empty());
-
-        let mut crate_graph = CrateGraph::default();
-        let crate_id = crate_graph.add_crate_root(
-            root_file,
-            Edition2018,
-            None,
-            CfgOptions::default(),
-            Env::default(),
-            Default::default(),
-        );
-        let mut change = AnalysisChange::new();
-        change.set_crate_graph(crate_graph);
-        host.apply_change(change);
-
-        assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]);
+        assert_eq!(analysis.crate_for(file_id).unwrap().len(), 1);
     }
 }
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 1e3e944e94..e0830eb4fc 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -190,10 +190,11 @@ fn get_struct_def_name_for_struct_literal_search(
 
 #[cfg(test)]
 mod tests {
+    use base_db::FileId;
     use expect_test::{expect, Expect};
     use stdx::format_to;
 
-    use crate::{mock_analysis::MockAnalysis, SearchScope};
+    use crate::{fixture, SearchScope};
 
     #[test]
     fn test_struct_literal_after_space() {
@@ -211,9 +212,9 @@ fn main() {
 }
 "#,
             expect![[r#"
-                Foo STRUCT FileId(1) 0..26 7..10 Other
+                Foo STRUCT FileId(0) 0..26 7..10 Other
 
-                FileId(1) 101..104 StructLiteral
+                FileId(0) 101..104 StructLiteral
             "#]],
         );
     }
@@ -229,10 +230,10 @@ struct Foo<|> {}
 }
 "#,
             expect![[r#"
-                Foo STRUCT FileId(1) 0..13 7..10 Other
+                Foo STRUCT FileId(0) 0..13 7..10 Other
 
-                FileId(1) 41..44 Other
-                FileId(1) 54..57 StructLiteral
+                FileId(0) 41..44 Other
+                FileId(0) 54..57 StructLiteral
             "#]],
         );
     }
@@ -248,9 +249,9 @@ struct Foo<T> <|>{}
 }
 "#,
             expect![[r#"
-                Foo STRUCT FileId(1) 0..16 7..10 Other
+                Foo STRUCT FileId(0) 0..16 7..10 Other
 
-                FileId(1) 64..67 StructLiteral
+                FileId(0) 64..67 StructLiteral
             "#]],
         );
     }
@@ -267,9 +268,9 @@ fn main() {
 }
 "#,
             expect![[r#"
-                Foo STRUCT FileId(1) 0..16 7..10 Other
+                Foo STRUCT FileId(0) 0..16 7..10 Other
 
-                FileId(1) 54..57 StructLiteral
+                FileId(0) 54..57 StructLiteral
             "#]],
         );
     }
@@ -290,12 +291,12 @@ fn main() {
     i = 5;
 }"#,
             expect![[r#"
-                i IDENT_PAT FileId(1) 24..25 Other Write
+                i IDENT_PAT FileId(0) 24..25 Other Write
 
-                FileId(1) 50..51 Other Write
-                FileId(1) 54..55 Other Read
-                FileId(1) 76..77 Other Write
-                FileId(1) 94..95 Other Write
+                FileId(0) 50..51 Other Write
+                FileId(0) 54..55 Other Read
+                FileId(0) 76..77 Other Write
+                FileId(0) 94..95 Other Write
             "#]],
         );
     }
@@ -314,10 +315,10 @@ fn bar() {
 }
 "#,
             expect![[r#"
-                spam IDENT_PAT FileId(1) 19..23 Other
+                spam IDENT_PAT FileId(0) 19..23 Other
 
-                FileId(1) 34..38 Other Read
-                FileId(1) 41..45 Other Read
+                FileId(0) 34..38 Other Read
+                FileId(0) 41..45 Other Read
             "#]],
         );
     }
@@ -329,9 +330,9 @@ fn bar() {
 fn foo(i : u32) -> u32 { i<|> }
 "#,
             expect![[r#"
-                i IDENT_PAT FileId(1) 7..8 Other
+                i IDENT_PAT FileId(0) 7..8 Other
 
-                FileId(1) 25..26 Other Read
+                FileId(0) 25..26 Other Read
             "#]],
         );
     }
@@ -343,9 +344,9 @@ fn foo(i : u32) -> u32 { i<|> }
 fn foo(i<|> : u32) -> u32 { i }
 "#,
             expect![[r#"
-                i IDENT_PAT FileId(1) 7..8 Other
+                i IDENT_PAT FileId(0) 7..8 Other
 
-                FileId(1) 25..26 Other Read
+                FileId(0) 25..26 Other Read
             "#]],
         );
     }
@@ -364,9 +365,9 @@ fn main(s: Foo) {
 }
 "#,
             expect![[r#"
-                spam RECORD_FIELD FileId(1) 17..30 21..25 Other
+                spam RECORD_FIELD FileId(0) 17..30 21..25 Other
 
-                FileId(1) 67..71 Other Read
+                FileId(0) 67..71 Other Read
             "#]],
         );
     }
@@ -381,7 +382,7 @@ impl Foo {
 }
 "#,
             expect![[r#"
-                f FN FileId(1) 27..43 30..31 Other
+                f FN FileId(0) 27..43 30..31 Other
 
             "#]],
         );
@@ -398,7 +399,7 @@ enum Foo {
 }
 "#,
             expect![[r#"
-                B VARIANT FileId(1) 22..23 22..23 Other
+                B VARIANT FileId(0) 22..23 22..23 Other
 
             "#]],
         );
@@ -439,10 +440,10 @@ fn f() {
 }
 "#,
             expect![[r#"
-                Foo STRUCT FileId(2) 17..51 28..31 Other
+                Foo STRUCT FileId(1) 17..51 28..31 Other
 
-                FileId(1) 53..56 StructLiteral
-                FileId(3) 79..82 StructLiteral
+                FileId(0) 53..56 StructLiteral
+                FileId(2) 79..82 StructLiteral
             "#]],
         );
     }
@@ -469,9 +470,9 @@ pub struct Foo {
 }
 "#,
             expect![[r#"
-                foo SOURCE_FILE FileId(2) 0..35 Other
+                foo SOURCE_FILE FileId(1) 0..35 Other
 
-                FileId(1) 14..17 Other
+                FileId(0) 14..17 Other
             "#]],
         );
     }
@@ -497,10 +498,10 @@ pub(super) struct Foo<|> {
 }
 "#,
             expect![[r#"
-                Foo STRUCT FileId(3) 0..41 18..21 Other
+                Foo STRUCT FileId(2) 0..41 18..21 Other
 
-                FileId(2) 20..23 Other
-                FileId(2) 47..50 StructLiteral
+                FileId(1) 20..23 Other
+                FileId(1) 47..50 StructLiteral
             "#]],
         );
     }
@@ -525,20 +526,20 @@ pub(super) struct Foo<|> {
             code,
             None,
             expect![[r#"
-                quux FN FileId(1) 19..35 26..30 Other
+                quux FN FileId(0) 19..35 26..30 Other
 
+                FileId(1) 16..20 StructLiteral
                 FileId(2) 16..20 StructLiteral
-                FileId(3) 16..20 StructLiteral
             "#]],
         );
 
         check_with_scope(
             code,
-            Some("/bar.rs"),
+            Some(SearchScope::single_file(FileId(2))),
             expect![[r#"
-                quux FN FileId(1) 19..35 26..30 Other
+                quux FN FileId(0) 19..35 26..30 Other
 
-                FileId(3) 16..20 StructLiteral
+                FileId(2) 16..20 StructLiteral
             "#]],
         );
     }
@@ -556,10 +557,10 @@ fn foo() {
 }
 "#,
             expect![[r#"
-                m1 MACRO_CALL FileId(1) 0..46 29..31 Other
+                m1 MACRO_CALL FileId(0) 0..46 29..31 Other
 
-                FileId(1) 63..65 StructLiteral
-                FileId(1) 73..75 StructLiteral
+                FileId(0) 63..65 StructLiteral
+                FileId(0) 73..75 StructLiteral
             "#]],
         );
     }
@@ -574,10 +575,10 @@ fn foo() {
 }
 "#,
             expect![[r#"
-                i IDENT_PAT FileId(1) 23..24 Other Write
+                i IDENT_PAT FileId(0) 23..24 Other Write
 
-                FileId(1) 34..35 Other Write
-                FileId(1) 38..39 Other Read
+                FileId(0) 34..35 Other Write
+                FileId(0) 38..39 Other Read
             "#]],
         );
     }
@@ -596,10 +597,10 @@ fn foo() {
 }
 "#,
             expect![[r#"
-                f RECORD_FIELD FileId(1) 15..21 15..16 Other
+                f RECORD_FIELD FileId(0) 15..21 15..16 Other
 
-                FileId(1) 55..56 Other Read
-                FileId(1) 68..69 Other Write
+                FileId(0) 55..56 Other Read
+                FileId(0) 68..69 Other Write
             "#]],
         );
     }
@@ -614,9 +615,9 @@ fn foo() {
 }
 "#,
             expect![[r#"
-                i IDENT_PAT FileId(1) 19..20 Other
+                i IDENT_PAT FileId(0) 19..20 Other
 
-                FileId(1) 26..27 Other Write
+                FileId(0) 26..27 Other Write
             "#]],
         );
     }
@@ -638,9 +639,9 @@ fn main() {
 }
 "#,
             expect![[r#"
-                new FN FileId(1) 54..81 61..64 Other
+                new FN FileId(0) 54..81 61..64 Other
 
-                FileId(1) 126..129 StructLiteral
+                FileId(0) 126..129 StructLiteral
             "#]],
         );
     }
@@ -660,10 +661,10 @@ use crate::f;
 fn g() { f(); }
 "#,
             expect![[r#"
-                f FN FileId(1) 22..31 25..26 Other
+                f FN FileId(0) 22..31 25..26 Other
 
-                FileId(2) 11..12 Other
-                FileId(2) 24..25 StructLiteral
+                FileId(1) 11..12 Other
+                FileId(1) 24..25 StructLiteral
             "#]],
         );
     }
@@ -672,11 +673,8 @@ fn g() { f(); }
         check_with_scope(ra_fixture, None, expect)
     }
 
-    fn check_with_scope(ra_fixture: &str, search_scope: Option<&str>, expect: Expect) {
-        let (mock_analysis, pos) = MockAnalysis::with_files_and_position(ra_fixture);
-        let search_scope =
-            search_scope.map(|path| SearchScope::single_file(mock_analysis.id_of(path)));
-        let analysis = mock_analysis.analysis();
+    fn check_with_scope(ra_fixture: &str, search_scope: Option<SearchScope>, expect: Expect) {
+        let (analysis, pos) = fixture::position(ra_fixture);
         let refs = analysis.find_all_refs(pos, search_scope).unwrap().unwrap();
 
         let mut actual = String::new();
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 3016297633..8cbe1ae5a2 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -275,11 +275,11 @@ mod tests {
     use test_utils::{assert_eq_text, mark};
     use text_edit::TextEdit;
 
-    use crate::{mock_analysis::analysis_and_position, FileId};
+    use crate::{fixture, FileId};
 
     fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
         let ra_fixture_after = &trim_indent(ra_fixture_after);
-        let (analysis, position) = analysis_and_position(ra_fixture_before);
+        let (analysis, position) = fixture::position(ra_fixture_before);
         let source_change = analysis.rename(position, new_name).unwrap();
         let mut text_edit_builder = TextEdit::builder();
         let mut file_id: Option<FileId> = None;
@@ -297,7 +297,7 @@ mod tests {
     }
 
     fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
-        let (analysis, position) = analysis_and_position(ra_fixture);
+        let (analysis, position) = fixture::position(ra_fixture);
         let source_change = analysis.rename(position, new_name).unwrap().unwrap();
         expect.assert_debug_eq(&source_change)
     }
@@ -314,7 +314,7 @@ mod tests {
 
     #[test]
     fn test_rename_to_invalid_identifier() {
-        let (analysis, position) = analysis_and_position(r#"fn main() { let i<|> = 1; }"#);
+        let (analysis, position) = fixture::position(r#"fn main() { let i<|> = 1; }"#);
         let new_name = "invalid!";
         let source_change = analysis.rename(position, new_name).unwrap();
         assert!(source_change.is_none());
@@ -602,7 +602,7 @@ mod foo<|>;
                         source_file_edits: [
                             SourceFileEdit {
                                 file_id: FileId(
-                                    2,
+                                    1,
                                 ),
                                 edit: TextEdit {
                                     indels: [
@@ -617,10 +617,10 @@ mod foo<|>;
                         file_system_edits: [
                             MoveFile {
                                 src: FileId(
-                                    3,
+                                    2,
                                 ),
                                 anchor: FileId(
-                                    3,
+                                    2,
                                 ),
                                 dst: "foo2.rs",
                             },
@@ -655,7 +655,7 @@ use crate::foo<|>::FooContent;
                         source_file_edits: [
                             SourceFileEdit {
                                 file_id: FileId(
-                                    1,
+                                    0,
                                 ),
                                 edit: TextEdit {
                                     indels: [
@@ -668,7 +668,7 @@ use crate::foo<|>::FooContent;
                             },
                             SourceFileEdit {
                                 file_id: FileId(
-                                    3,
+                                    2,
                                 ),
                                 edit: TextEdit {
                                     indels: [
@@ -683,10 +683,10 @@ use crate::foo<|>::FooContent;
                         file_system_edits: [
                             MoveFile {
                                 src: FileId(
-                                    2,
+                                    1,
                                 ),
                                 anchor: FileId(
-                                    2,
+                                    1,
                                 ),
                                 dst: "quux.rs",
                             },
@@ -715,7 +715,7 @@ mod fo<|>o;
                         source_file_edits: [
                             SourceFileEdit {
                                 file_id: FileId(
-                                    1,
+                                    0,
                                 ),
                                 edit: TextEdit {
                                     indels: [
@@ -730,10 +730,10 @@ mod fo<|>o;
                         file_system_edits: [
                             MoveFile {
                                 src: FileId(
-                                    2,
+                                    1,
                                 ),
                                 anchor: FileId(
-                                    2,
+                                    1,
                                 ),
                                 dst: "../foo2/mod.rs",
                             },
@@ -763,7 +763,7 @@ mod outer { mod fo<|>o; }
                         source_file_edits: [
                             SourceFileEdit {
                                 file_id: FileId(
-                                    1,
+                                    0,
                                 ),
                                 edit: TextEdit {
                                     indels: [
@@ -778,10 +778,10 @@ mod outer { mod fo<|>o; }
                         file_system_edits: [
                             MoveFile {
                                 src: FileId(
-                                    2,
+                                    1,
                                 ),
                                 anchor: FileId(
-                                    2,
+                                    1,
                                 ),
                                 dst: "bar.rs",
                             },
@@ -834,7 +834,7 @@ pub mod foo<|>;
                         source_file_edits: [
                             SourceFileEdit {
                                 file_id: FileId(
-                                    2,
+                                    1,
                                 ),
                                 edit: TextEdit {
                                     indels: [
@@ -847,7 +847,7 @@ pub mod foo<|>;
                             },
                             SourceFileEdit {
                                 file_id: FileId(
-                                    1,
+                                    0,
                                 ),
                                 edit: TextEdit {
                                     indels: [
@@ -862,10 +862,10 @@ pub mod foo<|>;
                         file_system_edits: [
                             MoveFile {
                                 src: FileId(
-                                    3,
+                                    2,
                                 ),
                                 anchor: FileId(
-                                    3,
+                                    2,
                                 ),
                                 dst: "foo2.rs",
                             },
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index cfeff40c1a..752ef2f219 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -292,7 +292,7 @@ fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool {
 mod tests {
     use expect_test::{expect, Expect};
 
-    use crate::mock_analysis::analysis_and_position;
+    use crate::fixture;
 
     use super::{RunnableAction, BENCH, BIN, DOCTEST, TEST};
 
@@ -302,7 +302,7 @@ mod tests {
         actions: &[&RunnableAction],
         expect: Expect,
     ) {
-        let (analysis, position) = analysis_and_position(ra_fixture);
+        let (analysis, position) = fixture::position(ra_fixture);
         let runnables = analysis.runnables(position.file_id).unwrap();
         expect.assert_debug_eq(&runnables);
         assert_eq!(
@@ -335,7 +335,7 @@ fn bench() {}
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 1..13,
                             focus_range: Some(
@@ -353,7 +353,7 @@ fn bench() {}
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 15..39,
                             focus_range: Some(
@@ -378,7 +378,7 @@ fn bench() {}
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 41..75,
                             focus_range: Some(
@@ -403,7 +403,7 @@ fn bench() {}
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 77..99,
                             focus_range: Some(
@@ -494,7 +494,7 @@ fn should_have_no_runnable_6() {}
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 1..13,
                             focus_range: Some(
@@ -512,7 +512,7 @@ fn should_have_no_runnable_6() {}
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 15..74,
                             focus_range: None,
@@ -532,7 +532,7 @@ fn should_have_no_runnable_6() {}
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 76..148,
                             focus_range: None,
@@ -552,7 +552,7 @@ fn should_have_no_runnable_6() {}
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 150..254,
                             focus_range: None,
@@ -596,7 +596,7 @@ impl Data {
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 1..13,
                             focus_range: Some(
@@ -614,7 +614,7 @@ impl Data {
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 44..98,
                             focus_range: None,
@@ -653,7 +653,7 @@ mod test_mod {
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 1..51,
                             focus_range: Some(
@@ -673,7 +673,7 @@ mod test_mod {
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 20..49,
                             focus_range: Some(
@@ -733,7 +733,7 @@ mod root_tests {
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 22..323,
                             focus_range: Some(
@@ -753,7 +753,7 @@ mod root_tests {
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 51..192,
                             focus_range: Some(
@@ -773,7 +773,7 @@ mod root_tests {
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 84..126,
                             focus_range: Some(
@@ -798,7 +798,7 @@ mod root_tests {
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 140..182,
                             focus_range: Some(
@@ -823,7 +823,7 @@ mod root_tests {
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 202..286,
                             focus_range: Some(
@@ -843,7 +843,7 @@ mod root_tests {
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 235..276,
                             focus_range: Some(
@@ -886,7 +886,7 @@ fn test_foo1() {}
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 1..50,
                             focus_range: Some(
@@ -934,7 +934,7 @@ fn test_foo1() {}
                     Runnable {
                         nav: NavigationTarget {
                             file_id: FileId(
-                                1,
+                                0,
                             ),
                             full_range: 1..72,
                             focus_range: Some(
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
index f800449596..0eed2dbd78 100644
--- a/crates/ide/src/syntax_tree.rs
+++ b/crates/ide/src/syntax_tree.rs
@@ -104,12 +104,12 @@ fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<St
 mod tests {
     use test_utils::assert_eq_text;
 
-    use crate::mock_analysis::{analysis_and_range, single_file};
+    use crate::fixture;
 
     #[test]
     fn test_syntax_tree_without_range() {
         // Basic syntax
-        let (analysis, file_id) = single_file(r#"fn foo() {}"#);
+        let (analysis, file_id) = fixture::file(r#"fn foo() {}"#);
         let syn = analysis.syntax_tree(file_id, None).unwrap();
 
         assert_eq_text!(
@@ -132,7 +132,7 @@ SOURCE_FILE@0..11
             .trim()
         );
 
-        let (analysis, file_id) = single_file(
+        let (analysis, file_id) = fixture::file(
             r#"
 fn test() {
     assert!("
@@ -184,7 +184,7 @@ SOURCE_FILE@0..60
 
     #[test]
     fn test_syntax_tree_with_range() {
-        let (analysis, range) = analysis_and_range(r#"<|>fn foo() {}<|>"#.trim());
+        let (analysis, range) = fixture::range(r#"<|>fn foo() {}<|>"#.trim());
         let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
 
         assert_eq_text!(
@@ -206,7 +206,7 @@ FN@0..11
             .trim()
         );
 
-        let (analysis, range) = analysis_and_range(
+        let (analysis, range) = fixture::range(
             r#"fn test() {
     <|>assert!("
     fn foo() {
@@ -242,7 +242,7 @@ EXPR_STMT@16..58
 
     #[test]
     fn test_syntax_tree_inside_string() {
-        let (analysis, range) = analysis_and_range(
+        let (analysis, range) = fixture::range(
             r#"fn test() {
     assert!("
 <|>fn foo() {
@@ -276,7 +276,7 @@ SOURCE_FILE@0..12
         );
 
         // With a raw string
-        let (analysis, range) = analysis_and_range(
+        let (analysis, range) = fixture::range(
             r###"fn test() {
     assert!(r#"
 <|>fn foo() {
@@ -310,7 +310,7 @@ SOURCE_FILE@0..12
         );
 
         // With a raw string
-        let (analysis, range) = analysis_and_range(
+        let (analysis, range) = fixture::range(
             r###"fn test() {
     assert!(r<|>#"
 fn foo() {
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index f7d46146c5..a0dc4b9df6 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -109,10 +109,10 @@ mod tests {
     use stdx::trim_indent;
     use test_utils::{assert_eq_text, mark};
 
-    use crate::mock_analysis::analysis_and_position;
+    use crate::fixture;
 
     fn apply_on_enter(before: &str) -> Option<String> {
-        let (analysis, position) = analysis_and_position(&before);
+        let (analysis, position) = fixture::position(&before);
         let result = analysis.on_enter(position).unwrap()?;
 
         let mut actual = analysis.file_text(position.file_id).unwrap().to_string();
diff --git a/crates/ide_db/src/change.rs b/crates/ide_db/src/apply_change.rs
similarity index 75%
rename from crates/ide_db/src/change.rs
rename to crates/ide_db/src/apply_change.rs
index 7f98111c48..da16fa21dc 100644
--- a/crates/ide_db/src/change.rs
+++ b/crates/ide_db/src/apply_change.rs
@@ -1,58 +1,16 @@
-//! Defines a unit of change that can applied to a state of IDE to get the next
-//! state. Changes are transactional.
+//! Applies changes to the IDE state transactionally.
 
 use std::{fmt, sync::Arc};
 
 use base_db::{
     salsa::{Database, Durability, SweepStrategy},
-    CrateGraph, FileId, SourceDatabase, SourceDatabaseExt, SourceRoot, SourceRootId,
+    Change, FileId, SourceRootId,
 };
 use profile::{memory_usage, Bytes};
 use rustc_hash::FxHashSet;
 
 use crate::{symbol_index::SymbolsDatabase, RootDatabase};
 
-#[derive(Default)]
-pub struct AnalysisChange {
-    roots: Option<Vec<SourceRoot>>,
-    files_changed: Vec<(FileId, Option<Arc<String>>)>,
-    crate_graph: Option<CrateGraph>,
-}
-
-impl fmt::Debug for AnalysisChange {
-    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
-        let mut d = fmt.debug_struct("AnalysisChange");
-        if let Some(roots) = &self.roots {
-            d.field("roots", roots);
-        }
-        if !self.files_changed.is_empty() {
-            d.field("files_changed", &self.files_changed.len());
-        }
-        if self.crate_graph.is_some() {
-            d.field("crate_graph", &self.crate_graph);
-        }
-        d.finish()
-    }
-}
-
-impl AnalysisChange {
-    pub fn new() -> AnalysisChange {
-        AnalysisChange::default()
-    }
-
-    pub fn set_roots(&mut self, roots: Vec<SourceRoot>) {
-        self.roots = Some(roots);
-    }
-
-    pub fn change_file(&mut self, file_id: FileId, new_text: Option<Arc<String>>) {
-        self.files_changed.push((file_id, new_text))
-    }
-
-    pub fn set_crate_graph(&mut self, graph: CrateGraph) {
-        self.crate_graph = Some(graph);
-    }
-}
-
 #[derive(Debug)]
 struct AddFile {
     file_id: FileId,
@@ -87,41 +45,25 @@ impl RootDatabase {
         self.salsa_runtime_mut().synthetic_write(Durability::LOW);
     }
 
-    pub fn apply_change(&mut self, change: AnalysisChange) {
+    pub fn apply_change(&mut self, change: Change) {
         let _p = profile::span("RootDatabase::apply_change");
         self.request_cancellation();
         log::info!("apply_change {:?}", change);
-        if let Some(roots) = change.roots {
+        if let Some(roots) = &change.roots {
             let mut local_roots = FxHashSet::default();
             let mut library_roots = FxHashSet::default();
-            for (idx, root) in roots.into_iter().enumerate() {
+            for (idx, root) in roots.iter().enumerate() {
                 let root_id = SourceRootId(idx as u32);
-                let durability = durability(&root);
                 if root.is_library {
                     library_roots.insert(root_id);
                 } else {
                     local_roots.insert(root_id);
                 }
-                for file_id in root.iter() {
-                    self.set_file_source_root_with_durability(file_id, root_id, durability);
-                }
-                self.set_source_root_with_durability(root_id, Arc::new(root), durability);
             }
             self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
             self.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH);
         }
-
-        for (file_id, text) in change.files_changed {
-            let source_root_id = self.file_source_root(file_id);
-            let source_root = self.source_root(source_root_id);
-            let durability = durability(&source_root);
-            // XXX: can't actually remove the file, just reset the text
-            let text = text.unwrap_or_default();
-            self.set_file_text_with_durability(file_id, text, durability)
-        }
-        if let Some(crate_graph) = change.crate_graph {
-            self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH)
-        }
+        change.apply(self);
     }
 
     pub fn collect_garbage(&mut self) {
@@ -295,11 +237,3 @@ impl RootDatabase {
         acc
     }
 }
-
-fn durability(source_root: &SourceRoot) -> Durability {
-    if source_root.is_library {
-        Durability::HIGH
-    } else {
-        Durability::LOW
-    }
-}
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs
index 0d209c6ec0..7eff247c73 100644
--- a/crates/ide_db/src/lib.rs
+++ b/crates/ide_db/src/lib.rs
@@ -2,10 +2,10 @@
 //!
 //! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search.
 
+mod apply_change;
 pub mod label;
 pub mod line_index;
 pub mod symbol_index;
-pub mod change;
 pub mod defs;
 pub mod search;
 pub mod imports_locator;
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index c312e0a2e8..d1c095ba5c 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -8,8 +8,7 @@ use base_db::{
     FileId,
 };
 use ide::{
-    Analysis, AnalysisChange, AnalysisHost, CompletionConfig, DiagnosticsConfig, FilePosition,
-    LineCol,
+    Analysis, AnalysisHost, Change, CompletionConfig, DiagnosticsConfig, FilePosition, LineCol,
 };
 use vfs::AbsPathBuf;
 
@@ -143,7 +142,7 @@ fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, w
         {
             let mut text = host.analysis().file_text(file_id).unwrap().to_string();
             text.push_str("\n/* Hello world */\n");
-            let mut change = AnalysisChange::new();
+            let mut change = Change::new();
             change.change_file(file_id, Some(Arc::new(text)));
             host.apply_change(change);
         }
@@ -156,7 +155,7 @@ fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, w
         {
             let mut text = host.analysis().file_text(file_id).unwrap().to_string();
             text.push_str("\npub fn _dummy() {}\n");
-            let mut change = AnalysisChange::new();
+            let mut change = Change::new();
             change.change_file(file_id, Some(Arc::new(text)));
             host.apply_change(change);
         }
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index c47cf6ef3e..7ae1c90552 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -5,7 +5,7 @@ use std::{path::Path, sync::Arc};
 use anyhow::Result;
 use base_db::CrateGraph;
 use crossbeam_channel::{unbounded, Receiver};
-use ide::{AnalysisChange, AnalysisHost};
+use ide::{AnalysisHost, Change};
 use project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace};
 use vfs::{loader::Handle, AbsPath, AbsPathBuf};
 
@@ -62,7 +62,7 @@ fn load(
 ) -> AnalysisHost {
     let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
     let mut host = AnalysisHost::new(lru_cap);
-    let mut analysis_change = AnalysisChange::new();
+    let mut analysis_change = Change::new();
 
     // wait until Vfs has loaded all roots
     for task in receiver {
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 96313aaec0..dafab6a6a7 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -8,7 +8,7 @@ use std::{sync::Arc, time::Instant};
 use base_db::{CrateId, VfsPath};
 use crossbeam_channel::{unbounded, Receiver, Sender};
 use flycheck::FlycheckHandle;
-use ide::{Analysis, AnalysisChange, AnalysisHost, FileId};
+use ide::{Analysis, AnalysisHost, Change, FileId};
 use lsp_types::{SemanticTokens, Url};
 use parking_lot::{Mutex, RwLock};
 use project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
@@ -139,7 +139,7 @@ impl GlobalState {
         let mut has_fs_changes = false;
 
         let change = {
-            let mut change = AnalysisChange::new();
+            let mut change = Change::new();
             let (vfs, line_endings_map) = &mut *self.vfs.write();
             let changed_files = vfs.take_changes();
             if changed_files.is_empty() {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index de0dbcad49..f7215f1290 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -3,7 +3,7 @@ use std::{mem, sync::Arc};
 
 use base_db::{CrateGraph, SourceRoot, VfsPath};
 use flycheck::{FlycheckConfig, FlycheckHandle};
-use ide::AnalysisChange;
+use ide::Change;
 use project_model::{ProcMacroClient, ProjectWorkspace};
 use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
 
@@ -171,7 +171,7 @@ impl GlobalState {
             );
         }
 
-        let mut change = AnalysisChange::new();
+        let mut change = Change::new();
 
         let project_folders = ProjectFolders::new(&workspaces);
 


Test patch:
diff --git a/crates/ide/src/completion/test_utils.rs b/crates/ide/src/completion/test_utils.rs
index 1452d7e9e5..feb8cd2a6e 100644
--- a/crates/ide/src/completion/test_utils.rs
+++ b/crates/ide/src/completion/test_utils.rs
@@ -8,8 +8,7 @@ use test_utils::assert_eq_text;
 
 use crate::{
     completion::{completion_item::CompletionKind, CompletionConfig},
-    mock_analysis::analysis_and_position,
-    CompletionItem,
+    fixture, CompletionItem,
 };
 
 pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
@@ -80,7 +79,7 @@ pub(crate) fn check_edit_with_config(
     ra_fixture_after: &str,
 ) {
     let ra_fixture_after = trim_indent(ra_fixture_after);
-    let (analysis, position) = analysis_and_position(ra_fixture_before);
+    let (analysis, position) = fixture::position(ra_fixture_before);
     let completions: Vec<CompletionItem> =
         analysis.completions(&config, position).unwrap().unwrap().into();
     let (completion,) = completions
@@ -94,7 +93,7 @@ pub(crate) fn check_edit_with_config(
 }
 
 pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
-    let (analysis, pos) = analysis_and_position(code);
+    let (analysis, pos) = fixture::position(code);
     analysis
         .with_db(|db| {
             let sema = Semantics::new(db);
@@ -109,6 +108,6 @@ pub(crate) fn get_all_completion_items(
     config: CompletionConfig,
     code: &str,
 ) -> Vec<CompletionItem> {
-    let (analysis, position) = analysis_and_position(code);
+    let (analysis, position) = fixture::position(code);
     analysis.completions(&config, position).unwrap().unwrap().into()
 }
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 211e62ea1f..694c4b7fa1 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -3,7 +3,7 @@ use std::fs;
 use expect_test::{expect_file, ExpectFile};
 use test_utils::project_dir;
 
-use crate::{mock_analysis::single_file, FileRange, TextRange};
+use crate::{fixture, FileRange, TextRange};
 
 #[test]
 fn test_highlighting() {
@@ -178,7 +178,7 @@ fn accidentally_quadratic() {
     let file = project_dir().join("crates/syntax/test_data/accidentally_quadratic");
     let src = fs::read_to_string(file).unwrap();
 
-    let (analysis, file_id) = single_file(&src);
+    let (analysis, file_id) = fixture::file(&src);
 
     // let t = std::time::Instant::now();
     let _ = analysis.highlight(file_id).unwrap();
@@ -187,7 +187,7 @@ fn accidentally_quadratic() {
 
 #[test]
 fn test_ranges() {
-    let (analysis, file_id) = single_file(
+    let (analysis, file_id) = fixture::file(
         r#"
 #[derive(Clone, Debug)]
 struct Foo {
@@ -228,7 +228,7 @@ fn main() {
 
 #[test]
 fn ranges_sorted() {
-    let (analysis, file_id) = single_file(
+    let (analysis, file_id) = fixture::file(
         r#"
 #[foo(bar = "bar")]
 macro_rules! test {}
@@ -462,12 +462,12 @@ macro_rules! noop {
 fn test_extern_crate() {
     check_highlighting(
         r#"
-        //- /main.rs
+        //- /main.rs crate:main deps:std,alloc
         extern crate std;
         extern crate alloc as abc;
-        //- /std/lib.rs
+        //- /std/lib.rs crate:std
         pub struct S;
-        //- /alloc/lib.rs
+        //- /alloc/lib.rs crate:alloc
         pub struct A
         "#,
         expect_file!["./test_data/highlight_extern_crate.html"],
@@ -479,7 +479,7 @@ fn test_extern_crate() {
 /// result as HTML, and compares it with the HTML file given as `snapshot`.
 /// Note that the `snapshot` file is overwritten by the rendered HTML.
 fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) {
-    let (analysis, file_id) = single_file(ra_fixture);
+    let (analysis, file_id) = fixture::file(ra_fixture);
     let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
     expect.assert_eq(actual_html)
 }