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