]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide/src/mock_analysis.rs
ad78d2d93fd4d105b91d1673db85ab32b04581d5
[rust.git] / crates / ra_ide / src / mock_analysis.rs
1 //! FIXME: write short doc here
2
3 use std::str::FromStr;
4 use std::sync::Arc;
5
6 use ra_cfg::CfgOptions;
7 use ra_db::{CrateName, Env, RelativePathBuf};
8 use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER};
9
10 use crate::{
11     Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange,
12     SourceRootId,
13 };
14
15 #[derive(Debug)]
16 enum MockFileData {
17     Plain { path: String, content: String },
18     Fixture(FixtureEntry),
19 }
20
21 impl MockFileData {
22     fn new(path: String, content: String) -> Self {
23         // `Self::Plain` causes a false warning: 'variant is never constructed: `Plain` '
24         // see https://github.com/rust-lang/rust/issues/69018
25         MockFileData::Plain { path, content }
26     }
27
28     fn path(&self) -> &str {
29         match self {
30             MockFileData::Plain { path, .. } => path.as_str(),
31             MockFileData::Fixture(f) => f.meta.path().as_str(),
32         }
33     }
34
35     fn content(&self) -> &str {
36         match self {
37             MockFileData::Plain { content, .. } => content,
38             MockFileData::Fixture(f) => f.text.as_str(),
39         }
40     }
41
42     fn cfg_options(&self) -> CfgOptions {
43         match self {
44             MockFileData::Fixture(f) => {
45                 f.meta.cfg_options().map_or_else(Default::default, |o| o.clone())
46             }
47             _ => CfgOptions::default(),
48         }
49     }
50
51     fn edition(&self) -> Edition {
52         match self {
53             MockFileData::Fixture(f) => {
54                 f.meta.edition().map_or(Edition::Edition2018, |v| Edition::from_str(v).unwrap())
55             }
56             _ => Edition::Edition2018,
57         }
58     }
59
60     fn env(&self) -> Env {
61         match self {
62             MockFileData::Fixture(f) => Env::from(f.meta.env()),
63             _ => Env::default(),
64         }
65     }
66 }
67
68 impl From<FixtureEntry> for MockFileData {
69     fn from(fixture: FixtureEntry) -> Self {
70         Self::Fixture(fixture)
71     }
72 }
73
74 /// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
75 /// from a set of in-memory files.
76 #[derive(Debug, Default)]
77 pub struct MockAnalysis {
78     files: Vec<MockFileData>,
79 }
80
81 impl MockAnalysis {
82     pub fn new() -> MockAnalysis {
83         MockAnalysis::default()
84     }
85     /// Creates `MockAnalysis` using a fixture data in the following format:
86     ///
87     /// ```not_rust
88     /// //- /main.rs
89     /// mod foo;
90     /// fn main() {}
91     ///
92     /// //- /foo.rs
93     /// struct Baz;
94     /// ```
95     pub fn with_files(fixture: &str) -> MockAnalysis {
96         let mut res = MockAnalysis::new();
97         for entry in parse_fixture(fixture) {
98             res.add_file_fixture(entry);
99         }
100         res
101     }
102
103     /// Same as `with_files`, but requires that a single file contains a `<|>` marker,
104     /// whose position is also returned.
105     pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) {
106         let mut position = None;
107         let mut res = MockAnalysis::new();
108         for entry in parse_fixture(fixture) {
109             if entry.text.contains(CURSOR_MARKER) {
110                 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
111                 position = Some(res.add_file_fixture_with_position(entry));
112             } else {
113                 res.add_file_fixture(entry);
114             }
115         }
116         let position = position.expect("expected a marker (<|>)");
117         (res, position)
118     }
119
120     pub fn add_file_fixture(&mut self, fixture: FixtureEntry) -> FileId {
121         let file_id = self.next_id();
122         self.files.push(MockFileData::from(fixture));
123         file_id
124     }
125
126     pub fn add_file_fixture_with_position(&mut self, mut fixture: FixtureEntry) -> FilePosition {
127         let (offset, text) = extract_offset(&fixture.text);
128         fixture.text = text;
129         let file_id = self.next_id();
130         self.files.push(MockFileData::from(fixture));
131         FilePosition { file_id, offset }
132     }
133
134     pub fn add_file(&mut self, path: &str, text: &str) -> FileId {
135         let file_id = self.next_id();
136         self.files.push(MockFileData::new(path.to_string(), text.to_string()));
137         file_id
138     }
139     pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition {
140         let (offset, text) = extract_offset(text);
141         let file_id = self.next_id();
142         self.files.push(MockFileData::new(path.to_string(), text));
143         FilePosition { file_id, offset }
144     }
145     pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange {
146         let (range, text) = extract_range(text);
147         let file_id = self.next_id();
148         self.files.push(MockFileData::new(path.to_string(), text));
149         FileRange { file_id, range }
150     }
151     pub fn id_of(&self, path: &str) -> FileId {
152         let (idx, _) = self
153             .files
154             .iter()
155             .enumerate()
156             .find(|(_, data)| path == data.path())
157             .expect("no file in this mock");
158         FileId(idx as u32 + 1)
159     }
160     pub fn analysis_host(self) -> AnalysisHost {
161         let mut host = AnalysisHost::default();
162         let source_root = SourceRootId(0);
163         let mut change = AnalysisChange::new();
164         change.add_root(source_root, true);
165         let mut crate_graph = CrateGraph::default();
166         let mut root_crate = None;
167         for (i, data) in self.files.into_iter().enumerate() {
168             let path = data.path();
169             assert!(path.starts_with('/'));
170             let path = RelativePathBuf::from_path(&path[1..]).unwrap();
171             let cfg_options = data.cfg_options();
172             let file_id = FileId(i as u32 + 1);
173             let edition = data.edition();
174             let env = data.env();
175             if path == "/lib.rs" || path == "/main.rs" {
176                 root_crate = Some(crate_graph.add_crate_root(
177                     file_id,
178                     edition,
179                     None,
180                     cfg_options,
181                     env,
182                     Default::default(),
183                     Default::default(),
184                 ));
185             } else if path.ends_with("/lib.rs") {
186                 let crate_name = path.parent().unwrap().file_name().unwrap();
187                 let other_crate = crate_graph.add_crate_root(
188                     file_id,
189                     edition,
190                     Some(CrateName::new(crate_name).unwrap()),
191                     cfg_options,
192                     env,
193                     Default::default(),
194                     Default::default(),
195                 );
196                 if let Some(root_crate) = root_crate {
197                     crate_graph
198                         .add_dep(root_crate, CrateName::new(crate_name).unwrap(), other_crate)
199                         .unwrap();
200                 }
201             }
202             change.add_file(source_root, file_id, path, Arc::new(data.content().to_owned()));
203         }
204         change.set_crate_graph(crate_graph);
205         host.apply_change(change);
206         host
207     }
208     pub fn analysis(self) -> Analysis {
209         self.analysis_host().analysis()
210     }
211
212     fn next_id(&self) -> FileId {
213         FileId((self.files.len() + 1) as u32)
214     }
215 }
216
217 /// Creates analysis from a multi-file fixture, returns positions marked with <|>.
218 pub fn analysis_and_position(ra_fixture: &str) -> (Analysis, FilePosition) {
219     let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
220     (mock.analysis(), position)
221 }
222
223 /// Creates analysis for a single file.
224 pub fn single_file(ra_fixture: &str) -> (Analysis, FileId) {
225     let mut mock = MockAnalysis::new();
226     let file_id = mock.add_file("/main.rs", ra_fixture);
227     (mock.analysis(), file_id)
228 }
229
230 /// Creates analysis for a single file, returns position marked with <|>.
231 pub fn single_file_with_position(ra_fixture: &str) -> (Analysis, FilePosition) {
232     let mut mock = MockAnalysis::new();
233     let pos = mock.add_file_with_position("/main.rs", ra_fixture);
234     (mock.analysis(), pos)
235 }
236
237 /// Creates analysis for a single file, returns range marked with a pair of <|>.
238 pub fn single_file_with_range(ra_fixture: &str) -> (Analysis, FileRange) {
239     let mut mock = MockAnalysis::new();
240     let pos = mock.add_file_with_range("/main.rs", ra_fixture);
241     (mock.analysis(), pos)
242 }