diff options
author | Dmitry <[email protected]> | 2020-08-14 19:32:05 +0100 |
---|---|---|
committer | Dmitry <[email protected]> | 2020-08-14 19:32:05 +0100 |
commit | 178c3e135a2a249692f7784712492e7884ae0c00 (patch) | |
tree | ac6b769dbf7162150caa0c1624786a4dd79ff3be /crates/ide/src/mock_analysis.rs | |
parent | 06ff8e6c760ff05f10e868b5d1f9d79e42fbb49c (diff) | |
parent | c2594daf2974dbd4ce3d9b7ec72481764abaceb5 (diff) |
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'crates/ide/src/mock_analysis.rs')
-rw-r--r-- | crates/ide/src/mock_analysis.rs | 176 |
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..363e6d27e --- /dev/null +++ b/crates/ide/src/mock_analysis.rs | |||
@@ -0,0 +1,176 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | use std::sync::Arc; | ||
3 | |||
4 | use base_db::{CrateName, FileSet, SourceRoot, VfsPath}; | ||
5 | use cfg::CfgOptions; | ||
6 | use test_utils::{ | ||
7 | extract_annotations, extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, | ||
8 | }; | ||
9 | |||
10 | use 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)] | ||
17 | pub struct MockAnalysis { | ||
18 | files: Vec<Fixture>, | ||
19 | } | ||
20 | |||
21 | impl 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 | None, | ||
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 <|>. | ||
155 | pub 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. | ||
161 | pub 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 <|>. | ||
168 | pub 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 | } | ||