aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/mock_analysis.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/mock_analysis.rs')
-rw-r--r--crates/ide/src/mock_analysis.rs176
1 files changed, 176 insertions, 0 deletions
diff --git a/crates/ide/src/mock_analysis.rs b/crates/ide/src/mock_analysis.rs
new file mode 100644
index 000000000..235796dbc
--- /dev/null
+++ b/crates/ide/src/mock_analysis.rs
@@ -0,0 +1,176 @@
1//! FIXME: write short doc here
2use std::sync::Arc;
3
4use base_db::{CrateName, FileSet, SourceRoot, VfsPath};
5use cfg::CfgOptions;
6use test_utils::{
7 extract_annotations, extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER,
8};
9
10use crate::{
11 Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange,
12};
13
14/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
15/// from a set of in-memory files.
16#[derive(Debug, Default)]
17pub struct MockAnalysis {
18 files: Vec<Fixture>,
19}
20
21impl MockAnalysis {
22 /// Creates `MockAnalysis` using a fixture data in the following format:
23 ///
24 /// ```not_rust
25 /// //- /main.rs
26 /// mod foo;
27 /// fn main() {}
28 ///
29 /// //- /foo.rs
30 /// struct Baz;
31 /// ```
32 pub fn with_files(ra_fixture: &str) -> MockAnalysis {
33 let (res, pos) = MockAnalysis::with_fixture(ra_fixture);
34 assert!(pos.is_none());
35 res
36 }
37
38 /// Same as `with_files`, but requires that a single file contains a `<|>` marker,
39 /// whose position is also returned.
40 pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) {
41 let (res, position) = MockAnalysis::with_fixture(fixture);
42 let (file_id, range_or_offset) = position.expect("expected a marker (<|>)");
43 let offset = match range_or_offset {
44 RangeOrOffset::Range(_) => panic!(),
45 RangeOrOffset::Offset(it) => it,
46 };
47 (res, FilePosition { file_id, offset })
48 }
49
50 fn with_fixture(fixture: &str) -> (MockAnalysis, Option<(FileId, RangeOrOffset)>) {
51 let mut position = None;
52 let mut res = MockAnalysis::default();
53 for mut entry in Fixture::parse(fixture) {
54 if entry.text.contains(CURSOR_MARKER) {
55 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
56 let (range_or_offset, text) = extract_range_or_offset(&entry.text);
57 entry.text = text;
58 let file_id = res.add_file_fixture(entry);
59 position = Some((file_id, range_or_offset));
60 } else {
61 res.add_file_fixture(entry);
62 }
63 }
64 (res, position)
65 }
66
67 fn add_file_fixture(&mut self, fixture: Fixture) -> FileId {
68 let file_id = FileId((self.files.len() + 1) as u32);
69 self.files.push(fixture);
70 file_id
71 }
72
73 pub fn id_of(&self, path: &str) -> FileId {
74 let (file_id, _) =
75 self.files().find(|(_, data)| path == data.path).expect("no file in this mock");
76 file_id
77 }
78 pub fn annotations(&self) -> Vec<(FileRange, String)> {
79 self.files()
80 .flat_map(|(file_id, fixture)| {
81 let annotations = extract_annotations(&fixture.text);
82 annotations
83 .into_iter()
84 .map(move |(range, data)| (FileRange { file_id, range }, data))
85 })
86 .collect()
87 }
88 pub fn files(&self) -> impl Iterator<Item = (FileId, &Fixture)> + '_ {
89 self.files.iter().enumerate().map(|(idx, fixture)| (FileId(idx as u32 + 1), fixture))
90 }
91 pub fn annotation(&self) -> (FileRange, String) {
92 let mut all = self.annotations();
93 assert_eq!(all.len(), 1);
94 all.pop().unwrap()
95 }
96 pub fn analysis_host(self) -> AnalysisHost {
97 let mut host = AnalysisHost::default();
98 let mut change = AnalysisChange::new();
99 let mut file_set = FileSet::default();
100 let mut crate_graph = CrateGraph::default();
101 let mut root_crate = None;
102 for (i, data) in self.files.into_iter().enumerate() {
103 let path = data.path;
104 assert!(path.starts_with('/'));
105
106 let mut cfg = CfgOptions::default();
107 data.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into()));
108 data.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into()));
109 let edition: Edition =
110 data.edition.and_then(|it| it.parse().ok()).unwrap_or(Edition::Edition2018);
111
112 let file_id = FileId(i as u32 + 1);
113 let env = data.env.into_iter().collect();
114 if path == "/lib.rs" || path == "/main.rs" {
115 root_crate = Some(crate_graph.add_crate_root(
116 file_id,
117 edition,
118 Some("test".to_string()),
119 cfg,
120 env,
121 Default::default(),
122 ));
123 } else if path.ends_with("/lib.rs") {
124 let base = &path[..path.len() - "/lib.rs".len()];
125 let crate_name = &base[base.rfind('/').unwrap() + '/'.len_utf8()..];
126 let other_crate = crate_graph.add_crate_root(
127 file_id,
128 edition,
129 Some(crate_name.to_string()),
130 cfg,
131 env,
132 Default::default(),
133 );
134 if let Some(root_crate) = root_crate {
135 crate_graph
136 .add_dep(root_crate, CrateName::new(crate_name).unwrap(), other_crate)
137 .unwrap();
138 }
139 }
140 let path = VfsPath::new_virtual_path(path.to_string());
141 file_set.insert(file_id, path);
142 change.change_file(file_id, Some(Arc::new(data.text).to_owned()));
143 }
144 change.set_crate_graph(crate_graph);
145 change.set_roots(vec![SourceRoot::new_local(file_set)]);
146 host.apply_change(change);
147 host
148 }
149 pub fn analysis(self) -> Analysis {
150 self.analysis_host().analysis()
151 }
152}
153
154/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
155pub fn analysis_and_position(ra_fixture: &str) -> (Analysis, FilePosition) {
156 let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
157 (mock.analysis(), position)
158}
159
160/// Creates analysis for a single file.
161pub fn single_file(ra_fixture: &str) -> (Analysis, FileId) {
162 let mock = MockAnalysis::with_files(ra_fixture);
163 let file_id = mock.id_of("/main.rs");
164 (mock.analysis(), file_id)
165}
166
167/// Creates analysis for a single file, returns range marked with a pair of <|>.
168pub fn analysis_and_range(ra_fixture: &str) -> (Analysis, FileRange) {
169 let (res, position) = MockAnalysis::with_fixture(ra_fixture);
170 let (file_id, range_or_offset) = position.expect("expected a marker (<|>)");
171 let range = match range_or_offset {
172 RangeOrOffset::Range(it) => it,
173 RangeOrOffset::Offset(_) => panic!(),
174 };
175 (res.analysis(), FileRange { file_id, range })
176}