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