1use std::hash::{BuildHasher, Hash};
4
5use hir::{CfgExpr, FilePositionWrapper, FileRangeWrapper, Semantics, Symbol};
6use smallvec::SmallVec;
7use span::{TextRange, TextSize};
8use syntax::{
9 AstToken, SmolStr,
10 ast::{self, IsString},
11};
12
13use crate::{
14 MiniCore, RootDatabase, SymbolKind, active_parameter::ActiveParameter,
15 documentation::Documentation, range_mapper::RangeMapper, search::ReferenceCategory,
16};
17
18pub use span::FileId;
19
20impl RootDatabase {
21 fn from_ra_fixture(
22 text: &str,
23 minicore: MiniCore<'_>,
24 ) -> Result<(RootDatabase, Vec<(FileId, usize)>, Vec<FileId>), ()> {
25 std::panic::catch_unwind(|| {
27 let mut db = RootDatabase::default();
28 let fixture =
29 test_fixture::ChangeFixture::parse_with_proc_macros(text, minicore.0, Vec::new());
30 db.apply_change(fixture.change);
31 let files = fixture
32 .files
33 .into_iter()
34 .zip(fixture.file_lines)
35 .map(|(file_id, range)| (file_id.file_id(), range))
36 .collect();
37 (db, files, fixture.sysroot_files)
38 })
39 .map_err(|error| {
40 tracing::error!(
41 "cannot crate the crate graph: {}\nCrate graph:\n{}\n",
42 if let Some(&s) = error.downcast_ref::<&'static str>() {
43 s
44 } else if let Some(s) = error.downcast_ref::<String>() {
45 s.as_str()
46 } else {
47 "Box<dyn Any>"
48 },
49 text,
50 );
51 })
52 }
53}
54
55pub struct RaFixtureAnalysis {
56 pub db: RootDatabase,
57 tmp_file_ids: Vec<(FileId, usize)>,
58 line_offsets: Vec<TextSize>,
59 virtual_file_id_to_line: Vec<usize>,
60 mapper: RangeMapper,
61 literal: ast::String,
62 sysroot_files: Vec<FileId>,
64 combined_len: TextSize,
65}
66
67impl RaFixtureAnalysis {
68 pub fn analyze_ra_fixture(
69 sema: &Semantics<'_, RootDatabase>,
70 literal: ast::String,
71 expanded: &ast::String,
72 minicore: MiniCore<'_>,
73 on_cursor: &mut dyn FnMut(TextRange),
74 ) -> Option<RaFixtureAnalysis> {
75 if !literal.is_raw() {
76 return None;
77 }
78
79 let active_parameter = ActiveParameter::at_token(sema, expanded.syntax().clone())?;
80 let has_rust_fixture_attr = active_parameter.attrs().is_some_and(|attrs| {
81 attrs.filter_map(|attr| attr.as_simple_path()).any(|path| {
82 path.segments()
83 .zip(["rust_analyzer", "rust_fixture"])
84 .all(|(seg, name)| seg.name_ref().map_or(false, |nr| nr.text() == name))
85 })
86 });
87 if !has_rust_fixture_attr {
88 return None;
89 }
90 let value = literal.value().ok()?;
91
92 let mut mapper = RangeMapper::default();
93
94 let mut offset_with_indent = TextSize::new(0);
97 let mut offset_without_indent = TextSize::new(0);
103
104 let mut text = &*value;
105 if let Some(t) = text.strip_prefix('\n') {
106 offset_with_indent += TextSize::of("\n");
107 text = t;
108 }
109 let mut line_offsets = Vec::new();
111 for mut line in text.split_inclusive('\n') {
112 line_offsets.push(offset_without_indent);
113
114 if line.starts_with("@@") {
115 mapper.add("//", TextRange::at(offset_with_indent, TextSize::of("@@")));
118 line = &line["@@".len()..];
119 offset_with_indent += TextSize::of("@@");
120 offset_without_indent += TextSize::of("@@");
121 }
122
123 let mut unindented_line = line.trim_start();
126 if unindented_line.is_empty() {
127 unindented_line = "\n";
129 }
130 offset_with_indent += TextSize::of(line) - TextSize::of(unindented_line);
131
132 let marker = "$0";
133 match unindented_line.find(marker) {
134 Some(marker_pos) => {
135 let (before_marker, after_marker) = unindented_line.split_at(marker_pos);
136 let after_marker = &after_marker[marker.len()..];
137
138 mapper.add(
139 before_marker,
140 TextRange::at(offset_with_indent, TextSize::of(before_marker)),
141 );
142 offset_with_indent += TextSize::of(before_marker);
143 offset_without_indent += TextSize::of(before_marker);
144
145 if let Some(marker_range) = literal
146 .map_range_up(TextRange::at(offset_with_indent, TextSize::of(marker)))
147 {
148 on_cursor(marker_range);
149 }
150 offset_with_indent += TextSize::of(marker);
151
152 mapper.add(
153 after_marker,
154 TextRange::at(offset_with_indent, TextSize::of(after_marker)),
155 );
156 offset_with_indent += TextSize::of(after_marker);
157 offset_without_indent += TextSize::of(after_marker);
158 }
159 None => {
160 mapper.add(
161 unindented_line,
162 TextRange::at(offset_with_indent, TextSize::of(unindented_line)),
163 );
164 offset_with_indent += TextSize::of(unindented_line);
165 offset_without_indent += TextSize::of(unindented_line);
166 }
167 }
168 }
169
170 let combined = mapper.take_text();
171 let combined_len = TextSize::of(&combined);
172 let (analysis, tmp_file_ids, sysroot_files) =
173 RootDatabase::from_ra_fixture(&combined, minicore).ok()?;
174
175 let mut virtual_file_id_to_line = Vec::new();
177 for &(file_id, line) in &tmp_file_ids {
178 virtual_file_id_to_line.resize(file_id.index() as usize + 1, usize::MAX);
179 virtual_file_id_to_line[file_id.index() as usize] = line;
180 }
181
182 Some(RaFixtureAnalysis {
183 db: analysis,
184 tmp_file_ids,
185 line_offsets,
186 virtual_file_id_to_line,
187 mapper,
188 literal,
189 sysroot_files,
190 combined_len,
191 })
192 }
193
194 pub fn files(&self) -> impl Iterator<Item = FileId> {
195 self.tmp_file_ids.iter().map(|(file, _)| *file)
196 }
197
198 fn virtual_file_id_to_line(&self, file_id: FileId) -> Option<usize> {
200 if self.is_sysroot_file(file_id) {
201 None
202 } else {
203 Some(self.virtual_file_id_to_line[file_id.index() as usize])
204 }
205 }
206
207 pub fn map_offset_down(&self, offset: TextSize) -> Option<(FileId, TextSize)> {
208 let inside_literal_range = self.literal.map_offset_down(offset)?;
209 let combined_offset = self.mapper.map_offset_down(inside_literal_range)?;
210 let (_, &(file_id, file_line)) =
212 self.tmp_file_ids.iter().enumerate().find(|&(idx, &(_, file_line))| {
213 let file_start = self.line_offsets[file_line];
214 let file_end = self
215 .tmp_file_ids
216 .get(idx + 1)
217 .map(|&(_, next_file_line)| self.line_offsets[next_file_line])
218 .unwrap_or_else(|| self.combined_len);
219 TextRange::new(file_start, file_end).contains(combined_offset)
220 })?;
221 let file_line_offset = self.line_offsets[file_line];
222 let file_offset = combined_offset - file_line_offset;
223 Some((file_id, file_offset))
224 }
225
226 pub fn map_range_down(&self, range: TextRange) -> Option<(FileId, TextRange)> {
227 let (start_file_id, start_offset) = self.map_offset_down(range.start())?;
228 let (end_file_id, end_offset) = self.map_offset_down(range.end())?;
229 if start_file_id != end_file_id {
230 None
231 } else {
232 Some((start_file_id, TextRange::new(start_offset, end_offset)))
233 }
234 }
235
236 pub fn map_range_up(
237 &self,
238 virtual_file: FileId,
239 range: TextRange,
240 ) -> impl Iterator<Item = TextRange> {
241 self.virtual_file_id_to_line(virtual_file)
243 .and_then(|line| self.line_offsets.get(line))
244 .into_iter()
245 .flat_map(move |&tmp_file_offset| {
246 let range = range + tmp_file_offset;
248 self.mapper.map_range_up(range)
250 })
251 .filter_map(|range| self.literal.map_range_up(range))
253 }
254
255 pub fn map_offset_up(&self, virtual_file: FileId, offset: TextSize) -> Option<TextSize> {
256 self.map_range_up(virtual_file, TextRange::empty(offset)).next().map(|range| range.start())
257 }
258
259 pub fn is_sysroot_file(&self, file_id: FileId) -> bool {
260 self.sysroot_files.contains(&file_id)
261 }
262}
263
264pub trait UpmapFromRaFixture: Sized {
265 fn upmap_from_ra_fixture(
266 self,
267 analysis: &RaFixtureAnalysis,
268 virtual_file_id: FileId,
269 real_file_id: FileId,
270 ) -> Result<Self, ()>;
271}
272
273trait IsEmpty {
274 fn is_empty(&self) -> bool;
275}
276
277impl<T> IsEmpty for Vec<T> {
278 fn is_empty(&self) -> bool {
279 self.is_empty()
280 }
281}
282
283impl<T, const N: usize> IsEmpty for SmallVec<[T; N]> {
284 fn is_empty(&self) -> bool {
285 self.is_empty()
286 }
287}
288
289#[allow(clippy::disallowed_types)]
290impl<K, V, S> IsEmpty for std::collections::HashMap<K, V, S> {
291 fn is_empty(&self) -> bool {
292 self.is_empty()
293 }
294}
295
296fn upmap_collection<T, Collection>(
297 collection: Collection,
298 analysis: &RaFixtureAnalysis,
299 virtual_file_id: FileId,
300 real_file_id: FileId,
301) -> Result<Collection, ()>
302where
303 T: UpmapFromRaFixture,
304 Collection: IntoIterator<Item = T> + FromIterator<T> + IsEmpty,
305{
306 if collection.is_empty() {
307 return Ok(collection);
309 }
310 let result = collection
311 .into_iter()
312 .filter_map(|item| item.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id).ok())
313 .collect::<Collection>();
314 if result.is_empty() {
315 Err(())
317 } else {
318 Ok(result)
319 }
320}
321
322impl<T: UpmapFromRaFixture> UpmapFromRaFixture for Option<T> {
323 fn upmap_from_ra_fixture(
324 self,
325 analysis: &RaFixtureAnalysis,
326 virtual_file_id: FileId,
327 real_file_id: FileId,
328 ) -> Result<Self, ()> {
329 Ok(match self {
330 Some(it) => Some(it.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?),
331 None => None,
332 })
333 }
334}
335
336impl<T: UpmapFromRaFixture> UpmapFromRaFixture for Vec<T> {
337 fn upmap_from_ra_fixture(
338 self,
339 analysis: &RaFixtureAnalysis,
340 virtual_file_id: FileId,
341 real_file_id: FileId,
342 ) -> Result<Self, ()> {
343 upmap_collection(self, analysis, virtual_file_id, real_file_id)
344 }
345}
346
347impl<T: UpmapFromRaFixture, const N: usize> UpmapFromRaFixture for SmallVec<[T; N]> {
348 fn upmap_from_ra_fixture(
349 self,
350 analysis: &RaFixtureAnalysis,
351 virtual_file_id: FileId,
352 real_file_id: FileId,
353 ) -> Result<Self, ()> {
354 upmap_collection(self, analysis, virtual_file_id, real_file_id)
355 }
356}
357
358#[allow(clippy::disallowed_types)]
359impl<K: UpmapFromRaFixture + Hash + Eq, V: UpmapFromRaFixture, S: BuildHasher + Default>
360 UpmapFromRaFixture for std::collections::HashMap<K, V, S>
361{
362 fn upmap_from_ra_fixture(
363 self,
364 analysis: &RaFixtureAnalysis,
365 virtual_file_id: FileId,
366 real_file_id: FileId,
367 ) -> Result<Self, ()> {
368 upmap_collection(self, analysis, virtual_file_id, real_file_id)
369 }
370}
371
372#[allow(clippy::disallowed_types)]
374impl<V: UpmapFromRaFixture, S: BuildHasher + Default> UpmapFromRaFixture
375 for std::collections::HashMap<FileId, V, S>
376{
377 fn upmap_from_ra_fixture(
378 self,
379 analysis: &RaFixtureAnalysis,
380 _virtual_file_id: FileId,
381 real_file_id: FileId,
382 ) -> Result<Self, ()> {
383 if self.is_empty() {
384 return Ok(self);
385 }
386 let result = self
387 .into_iter()
388 .filter_map(|(virtual_file_id, value)| {
389 Some((
390 real_file_id,
391 value.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id).ok()?,
392 ))
393 })
394 .collect::<std::collections::HashMap<_, _, _>>();
395 if result.is_empty() { Err(()) } else { Ok(result) }
396 }
397}
398
399macro_rules! impl_tuple {
400 () => {}; ( $first:ident, $( $rest:ident, )* ) => {
402 impl<
403 $first: UpmapFromRaFixture,
404 $( $rest: UpmapFromRaFixture, )*
405 > UpmapFromRaFixture for ( $first, $( $rest, )* ) {
406 fn upmap_from_ra_fixture(
407 self,
408 analysis: &RaFixtureAnalysis,
409 virtual_file_id: FileId,
410 real_file_id: FileId,
411 ) -> Result<Self, ()> {
412 #[allow(non_snake_case)]
413 let ( $first, $($rest,)* ) = self;
414 Ok((
415 $first.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
416 $( $rest.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?, )*
417 ))
418 }
419 }
420
421 impl_tuple!( $($rest,)* );
422 };
423}
424impl_tuple!(A, B, C, D, E,);
425
426impl UpmapFromRaFixture for TextSize {
427 fn upmap_from_ra_fixture(
428 self,
429 analysis: &RaFixtureAnalysis,
430 virtual_file_id: FileId,
431 _real_file_id: FileId,
432 ) -> Result<Self, ()> {
433 analysis.map_offset_up(virtual_file_id, self).ok_or(())
434 }
435}
436
437impl UpmapFromRaFixture for TextRange {
438 fn upmap_from_ra_fixture(
439 self,
440 analysis: &RaFixtureAnalysis,
441 virtual_file_id: FileId,
442 _real_file_id: FileId,
443 ) -> Result<Self, ()> {
444 analysis.map_range_up(virtual_file_id, self).next().ok_or(())
445 }
446}
447
448impl UpmapFromRaFixture for FilePositionWrapper<FileId> {
462 fn upmap_from_ra_fixture(
463 self,
464 analysis: &RaFixtureAnalysis,
465 _virtual_file_id: FileId,
466 real_file_id: FileId,
467 ) -> Result<Self, ()> {
468 Ok(FilePositionWrapper {
469 file_id: real_file_id,
470 offset: self.offset.upmap_from_ra_fixture(analysis, self.file_id, real_file_id)?,
471 })
472 }
473}
474
475impl UpmapFromRaFixture for FileRangeWrapper<FileId> {
476 fn upmap_from_ra_fixture(
477 self,
478 analysis: &RaFixtureAnalysis,
479 _virtual_file_id: FileId,
480 real_file_id: FileId,
481 ) -> Result<Self, ()> {
482 Ok(FileRangeWrapper {
483 file_id: real_file_id,
484 range: self.range.upmap_from_ra_fixture(analysis, self.file_id, real_file_id)?,
485 })
486 }
487}
488
489#[macro_export]
490macro_rules! impl_empty_upmap_from_ra_fixture {
491 ( $( $ty:ty ),* $(,)? ) => {
492 $(
493 impl $crate::ra_fixture::UpmapFromRaFixture for $ty {
494 fn upmap_from_ra_fixture(
495 self,
496 _analysis: &$crate::ra_fixture::RaFixtureAnalysis,
497 _virtual_file_id: $crate::ra_fixture::FileId,
498 _real_file_id: $crate::ra_fixture::FileId,
499 ) -> Result<Self, ()> {
500 Ok(self)
501 }
502 }
503 )*
504 };
505}
506
507impl_empty_upmap_from_ra_fixture!(
508 bool,
509 i8,
510 i16,
511 i32,
512 i64,
513 i128,
514 u8,
515 u16,
516 u32,
517 u64,
518 u128,
519 f32,
520 f64,
521 &str,
522 String,
523 Symbol,
524 SmolStr,
525 Documentation<'_>,
526 SymbolKind,
527 CfgExpr,
528 ReferenceCategory,
529);