1use std::{
23 cmp, env, fs,
24 hash::Hash,
25 io,
26 path::{Path, PathBuf},
27 sync::atomic::{AtomicUsize, Ordering::SeqCst},
28};
29
30use once_cell::sync::Lazy;
31use rustc_hash::FxHashMap;
32#[cfg(feature = "sourcemap")]
33use sourcemap::SourceMapBuilder;
34use tracing::debug;
35
36pub use crate::syntax_pos::*;
37use crate::{
38 errors::SourceMapper,
39 rustc_data_structures::stable_hasher::StableHasher,
40 sync::{Lock, LockGuard, Lrc, MappedLockGuard},
41};
42
43static CURRENT_DIR: Lazy<Option<PathBuf>> = Lazy::new(|| env::current_dir().ok());
44
45pub trait FileLoader {
51 fn file_exists(&self, path: &Path) -> bool;
53
54 fn abs_path(&self, path: &Path) -> Option<PathBuf>;
56
57 fn read_file(&self, path: &Path) -> io::Result<String>;
59}
60
61pub struct RealFileLoader;
63
64impl FileLoader for RealFileLoader {
65 fn file_exists(&self, path: &Path) -> bool {
66 fs::metadata(path).is_ok()
67 }
68
69 fn abs_path(&self, path: &Path) -> Option<PathBuf> {
70 if path.is_absolute() {
71 Some(path.to_path_buf())
72 } else {
73 CURRENT_DIR.as_ref().map(|cwd| cwd.join(path))
74 }
75 }
76
77 fn read_file(&self, path: &Path) -> io::Result<String> {
78 fs::read_to_string(path)
79 }
80}
81
82#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
86pub struct StableSourceFileId(u128);
87
88impl StableSourceFileId {
89 pub fn new(source_file: &SourceFile) -> StableSourceFileId {
90 let mut hasher = StableHasher::new();
91
92 source_file.name.hash(&mut hasher);
93 source_file.name_was_remapped.hash(&mut hasher);
94 source_file.unmapped_path.hash(&mut hasher);
95
96 StableSourceFileId(hasher.finish())
97 }
98}
99
100#[derive(Default)]
105pub(super) struct SourceMapFiles {
106 pub(super) source_files: Vec<Lrc<SourceFile>>,
107 stable_id_to_source_file: FxHashMap<StableSourceFileId, Lrc<SourceFile>>,
108}
109
110pub struct SourceMap {
130 pub(super) files: Lock<SourceMapFiles>,
131 start_pos: AtomicUsize,
132 file_loader: Box<dyn FileLoader + Sync + Send>,
133 path_mapping: FilePathMapping,
136 doctest_offset: Option<(FileName, isize)>,
139}
140
141impl Default for SourceMap {
142 fn default() -> Self {
143 Self::new(FilePathMapping::empty())
144 }
145}
146
147impl SourceMap {
148 pub fn new(path_mapping: FilePathMapping) -> SourceMap {
149 SourceMap {
150 files: Default::default(),
151 start_pos: AtomicUsize::new(1),
152 file_loader: Box::new(RealFileLoader),
153 path_mapping,
154 doctest_offset: None,
155 }
156 }
157
158 pub fn with_file_loader(
159 file_loader: Box<dyn FileLoader + Sync + Send>,
160 path_mapping: FilePathMapping,
161 ) -> SourceMap {
162 SourceMap {
163 files: Default::default(),
164 start_pos: AtomicUsize::new(1),
165 file_loader,
166 path_mapping,
167 doctest_offset: None,
168 }
169 }
170
171 pub fn path_mapping(&self) -> &FilePathMapping {
172 &self.path_mapping
173 }
174
175 pub fn file_exists(&self, path: &Path) -> bool {
176 self.file_loader.file_exists(path)
177 }
178
179 pub fn load_file(&self, path: &Path) -> io::Result<Lrc<SourceFile>> {
180 let src = self.file_loader.read_file(path)?;
181 let filename = Lrc::new(path.to_path_buf().into());
182 Ok(self.new_source_file(filename, src))
183 }
184
185 pub fn files(&self) -> MappedLockGuard<'_, Vec<Lrc<SourceFile>>> {
186 LockGuard::map(self.files.borrow(), |files| &mut files.source_files)
187 }
188
189 pub fn source_file_by_stable_id(
190 &self,
191 stable_id: StableSourceFileId,
192 ) -> Option<Lrc<SourceFile>> {
193 self.files
194 .borrow()
195 .stable_id_to_source_file
196 .get(&stable_id)
197 .cloned()
198 }
199
200 fn next_start_pos(&self, len: usize) -> usize {
201 self.start_pos.fetch_add(len + 1, SeqCst)
204 }
205
206 pub fn new_source_file(&self, filename: Lrc<FileName>, mut src: String) -> Lrc<SourceFile> {
209 remove_bom(&mut src);
210
211 self.new_source_file_from(filename, Lrc::new(src))
212 }
213
214 pub fn new_source_file_from(
219 &self,
220 filename: Lrc<FileName>,
221 src: Lrc<String>,
222 ) -> Lrc<SourceFile> {
223 let unmapped_path = filename.clone();
229
230 let (filename, was_remapped) = match &*filename {
231 FileName::Real(filename) => {
232 let (filename, was_remapped) = self.path_mapping.map_prefix(filename);
233 (Lrc::new(FileName::Real(filename)), was_remapped)
234 }
235 _ => (filename, false),
236 };
237
238 let mut files = self.files.borrow_mut();
241
242 let start_pos = self.next_start_pos(src.len());
243
244 let source_file = Lrc::new(SourceFile::new_from(
245 filename,
246 was_remapped,
247 unmapped_path,
248 src,
249 SmallPos::from_usize(start_pos),
250 ));
251
252 {
253 files.source_files.push(source_file.clone());
254 files
255 .stable_id_to_source_file
256 .insert(StableSourceFileId::new(&source_file), source_file.clone());
257 }
258
259 source_file
260 }
261
262 pub fn mk_substr_filename(&self, sp: Span) -> String {
263 let pos = self.lookup_char_pos(sp.lo());
264 format!(
265 "<{}:{}:{}>",
266 pos.file.name,
267 pos.line,
268 pos.col.to_usize() + 1
269 )
270 }
271
272 pub fn doctest_offset_line(&self, mut orig: usize) -> usize {
274 if let Some((_, line)) = self.doctest_offset {
275 if line >= 0 {
276 orig += line as usize;
277 } else {
278 orig -= (-line) as usize;
279 }
280 }
281 orig
282 }
283
284 pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
286 self.try_lookup_char_pos(pos).unwrap()
287 }
288
289 pub fn try_lookup_char_pos(&self, pos: BytePos) -> Result<Loc, SourceMapLookupError> {
291 let fm = self.try_lookup_source_file(pos)?;
292 self.try_lookup_char_pos_with(fm, pos)
293 }
294
295 #[doc(hidden)]
301 pub fn lookup_char_pos_with(&self, fm: Lrc<SourceFile>, pos: BytePos) -> Loc {
302 self.try_lookup_char_pos_with(fm, pos).unwrap()
303 }
304
305 #[doc(hidden)]
311 pub fn try_lookup_char_pos_with(
312 &self,
313 fm: Lrc<SourceFile>,
314 pos: BytePos,
315 ) -> Result<Loc, SourceMapLookupError> {
316 let line_info = self.lookup_line_with(fm, pos);
317 match line_info {
318 Ok(SourceFileAndLine { sf: f, line: a }) => {
319 let analysis = f.analyze();
320 let chpos = self.bytepos_to_file_charpos_with(&f, pos);
321
322 let line = a + 1; let linebpos = f.analyze().lines[a];
324 assert!(
325 pos >= linebpos,
326 "{}: bpos = {:?}; linebpos = {:?};",
327 f.name,
328 pos,
329 linebpos,
330 );
331
332 let linechpos = self.bytepos_to_file_charpos_with(&f, linebpos);
333 let col = chpos - linechpos;
334
335 let col_display = {
336 let start_width_idx = analysis
337 .non_narrow_chars
338 .binary_search_by_key(&linebpos, |x| x.pos())
339 .unwrap_or_else(|x| x);
340 let end_width_idx = analysis
341 .non_narrow_chars
342 .binary_search_by_key(&pos, |x| x.pos())
343 .unwrap_or_else(|x| x);
344 let special_chars = end_width_idx - start_width_idx;
345 let non_narrow: usize = analysis.non_narrow_chars
346 [start_width_idx..end_width_idx]
347 .iter()
348 .map(|x| x.width())
349 .sum();
350 col.0 - special_chars + non_narrow
351 };
352 if cfg!(feature = "debug") {
353 debug!(
354 "byte pos {:?} is on the line at byte pos {:?}",
355 pos, linebpos
356 );
357 debug!(
358 "char pos {:?} is on the line at char pos {:?}",
359 chpos, linechpos
360 );
361 debug!("byte is on line: {}", line);
362 }
363 Ok(Loc {
365 file: f,
366 line,
367 col,
368 col_display,
369 })
370 }
371 Err(f) => {
372 let analysis = f.analyze();
373 let chpos = self.bytepos_to_file_charpos(pos)?;
374
375 let col_display = {
376 let end_width_idx = analysis
377 .non_narrow_chars
378 .binary_search_by_key(&pos, |x| x.pos())
379 .unwrap_or_else(|x| x);
380 let non_narrow: usize = analysis.non_narrow_chars[0..end_width_idx]
381 .iter()
382 .map(|x| x.width())
383 .sum();
384 chpos.0 - end_width_idx + non_narrow
385 };
386 Ok(Loc {
387 file: f,
388 line: 0,
389 col: chpos,
390 col_display,
391 })
392 }
393 }
394 }
395
396 pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Lrc<SourceFile>> {
398 let f = self.try_lookup_source_file(pos).unwrap();
399
400 self.lookup_line_with(f, pos)
401 }
402
403 #[doc(hidden)]
408 pub fn lookup_line_with(
409 &self,
410 f: Lrc<SourceFile>,
411 pos: BytePos,
412 ) -> Result<SourceFileAndLine, Lrc<SourceFile>> {
413 match f.lookup_line(pos) {
414 Some(line) => Ok(SourceFileAndLine { sf: f, line }),
415 None => Err(f),
416 }
417 }
418
419 pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt {
420 let loc = self.lookup_char_pos(pos);
421 LocWithOpt {
422 filename: loc.file.name.clone(),
423 line: loc.line,
424 col: loc.col,
425 file: Some(loc.file),
426 }
427 }
428
429 pub fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
438 let lhs_end = match self.lookup_line(sp_lhs.hi()) {
439 Ok(x) => x,
440 Err(_) => return None,
441 };
442 let rhs_begin = match self.lookup_line(sp_rhs.lo()) {
443 Ok(x) => x,
444 Err(_) => return None,
445 };
446
447 if lhs_end.line != rhs_begin.line {
449 return None;
450 }
451
452 if (sp_lhs.lo() <= sp_rhs.lo()) && (sp_lhs.hi() <= sp_rhs.lo()) {
454 Some(sp_lhs.to(sp_rhs))
455 } else {
456 None
457 }
458 }
459
460 pub fn span_to_string(&self, sp: Span) -> String {
461 if self.files.borrow().source_files.is_empty() && sp.is_dummy() {
462 return "no-location".to_string();
463 }
464
465 let lo = self.lookup_char_pos_adj(sp.lo());
466 let hi = self.lookup_char_pos_adj(sp.hi());
467 format!(
468 "{}:{}:{}: {}:{}",
469 lo.filename,
470 lo.line,
471 lo.col.to_usize() + 1,
472 hi.line,
473 hi.col.to_usize() + 1
474 )
475 }
476
477 pub fn span_to_filename(&self, sp: Span) -> Lrc<FileName> {
478 self.lookup_char_pos(sp.lo()).file.name.clone()
479 }
480
481 pub fn span_to_unmapped_path(&self, sp: Span) -> Lrc<FileName> {
482 self.lookup_char_pos(sp.lo())
483 .file
484 .unmapped_path
485 .clone()
486 .expect("SourceMap::span_to_unmapped_path called for imported SourceFile?")
487 }
488
489 pub fn is_multiline(&self, sp: Span) -> bool {
490 let lo = self.lookup_char_pos(sp.lo());
491 let hi = self.lookup_char_pos(sp.hi());
492 lo.line != hi.line
493 }
494
495 pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
496 if cfg!(feature = "debug") {
497 debug!("span_to_lines(sp={:?})", sp);
498 }
499
500 if sp.lo() > sp.hi() {
501 return Err(Box::new(SpanLinesError::IllFormedSpan(sp)));
502 }
503
504 let lo = self.lookup_char_pos(sp.lo());
505 if cfg!(feature = "debug") {
506 debug!("span_to_lines: lo={:?}", lo);
507 }
508 let hi = self.lookup_char_pos(sp.hi());
509 if cfg!(feature = "debug") {
510 debug!("span_to_lines: hi={:?}", hi);
511 }
512
513 if lo.file.start_pos != hi.file.start_pos {
514 return Err(Box::new(SpanLinesError::DistinctSources(DistinctSources {
515 begin: FilePos(lo.file.name.clone(), lo.file.start_pos),
516 end: FilePos(hi.file.name.clone(), hi.file.start_pos),
517 })));
518 }
519 assert!(hi.line >= lo.line);
520
521 if lo.file.src.is_empty() {
523 return Ok(FileLines {
524 file: lo.file,
525 lines: Vec::new(),
526 });
527 }
528
529 let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
530
531 let mut start_col = lo.col;
534
535 for line_index in lo.line - 1..hi.line - 1 {
540 let line_len = lo
541 .file
542 .get_line(line_index)
543 .map(|s| s.chars().count())
544 .unwrap_or(0);
545 lines.push(LineInfo {
546 line_index,
547 start_col,
548 end_col: CharPos::from_usize(line_len),
549 });
550 start_col = CharPos::from_usize(0);
551 }
552
553 lines.push(LineInfo {
555 line_index: hi.line - 1,
556 start_col,
557 end_col: hi.col,
558 });
559
560 Ok(FileLines {
561 file: lo.file,
562 lines,
563 })
564 }
565
566 fn span_to_source<F, Ret>(
572 &self,
573 sp: Span,
574 extract_source: F,
575 ) -> Result<Ret, Box<SpanSnippetError>>
576 where
577 F: FnOnce(&str, usize, usize) -> Ret,
578 {
579 if sp.lo() > sp.hi() {
580 return Err(Box::new(SpanSnippetError::IllFormedSpan(sp)));
581 }
582 if sp.lo.is_dummy() || sp.hi.is_dummy() {
583 return Err(Box::new(SpanSnippetError::DummyBytePos));
584 }
585
586 let local_begin = self.try_lookup_byte_offset(sp.lo())?;
587 let local_end = self.try_lookup_byte_offset(sp.hi())?;
588
589 if local_begin.sf.start_pos != local_end.sf.start_pos {
590 Err(Box::new(SpanSnippetError::DistinctSources(
591 DistinctSources {
592 begin: FilePos(local_begin.sf.name.clone(), local_begin.sf.start_pos),
593 end: FilePos(local_end.sf.name.clone(), local_end.sf.start_pos),
594 },
595 )))
596 } else {
597 let start_index = local_begin.pos.to_usize();
598 let end_index = local_end.pos.to_usize();
599 let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
600
601 if start_index > end_index || end_index > source_len {
602 return Err(Box::new(SpanSnippetError::MalformedForSourcemap(
603 MalformedSourceMapPositions {
604 name: local_begin.sf.name.clone(),
605 source_len,
606 begin_pos: local_begin.pos,
607 end_pos: local_end.pos,
608 },
609 )));
610 }
611
612 let src = &local_begin.sf.src;
613 Ok(extract_source(src, start_index, end_index))
614 }
615 }
616
617 pub fn with_snippet_of_span<F, Ret>(
619 &self,
620 sp: Span,
621 op: F,
622 ) -> Result<Ret, Box<SpanSnippetError>>
623 where
624 F: FnOnce(&str) -> Ret,
625 {
626 self.span_to_source(sp, |src, start_index, end_index| {
627 op(&src[start_index..end_index])
628 })
629 }
630
631 pub fn span_to_margin(&self, sp: Span) -> Option<usize> {
632 match self.span_to_prev_source(sp) {
633 Err(_) => None,
634 Ok(source) => source
635 .split('\n')
636 .next_back()
637 .map(|last_line| last_line.len() - last_line.trim_start().len()),
638 }
639 }
640
641 pub fn with_span_to_prev_source<F, Ret>(
643 &self,
644 sp: Span,
645 op: F,
646 ) -> Result<Ret, Box<SpanSnippetError>>
647 where
648 F: FnOnce(&str) -> Ret,
649 {
650 self.span_to_source(sp, |src, start_index, _| op(&src[..start_index]))
651 }
652
653 pub fn span_to_prev_source(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
655 self.with_span_to_prev_source(sp, |s| s.to_string())
656 }
657
658 pub fn with_span_to_next_source<F, Ret>(
660 &self,
661 sp: Span,
662 op: F,
663 ) -> Result<Ret, Box<SpanSnippetError>>
664 where
665 F: FnOnce(&str) -> Ret,
666 {
667 self.span_to_source(sp, |src, _, end_index| op(&src[end_index..]))
668 }
669
670 pub fn span_to_next_source(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
672 self.with_span_to_next_source(sp, |s| s.to_string())
673 }
674
675 pub fn span_extend_to_prev_char(&self, sp: Span, c: char) -> Span {
679 if let Ok(prev_source) = self.span_to_prev_source(sp) {
680 let prev_source = prev_source.rsplit(c).next().unwrap_or("").trim_start();
681 if !prev_source.is_empty() && !prev_source.contains('\n') {
682 return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
683 }
684 }
685
686 sp
687 }
688
689 pub fn span_extend_to_prev_str(&self, sp: Span, pat: &str, accept_newlines: bool) -> Span {
694 for ws in &[" ", "\t", "\n"] {
699 let pat = pat.to_owned() + ws;
700 if let Ok(prev_source) = self.span_to_prev_source(sp) {
701 let prev_source = prev_source.rsplit(&pat).next().unwrap_or("").trim_start();
702 if !prev_source.is_empty() && (!prev_source.contains('\n') || accept_newlines) {
703 return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
704 }
705 }
706 }
707
708 sp
709 }
710
711 pub fn span_extend_to_next_char(&self, sp: Span, c: char) -> Span {
715 if let Ok(next_source) = self.span_to_next_source(sp) {
716 let next_source = next_source.split(c).next().unwrap_or("").trim_end();
717 if !next_source.is_empty() && !next_source.contains('\n') {
718 return sp.with_hi(BytePos(sp.hi().0 + next_source.len() as u32));
719 }
720 }
721
722 sp
723 }
724
725 pub fn span_extend_to_next_str(&self, sp: Span, pat: &str, accept_newlines: bool) -> Span {
730 for ws in &[" ", "\t", "\n"] {
731 let pat = pat.to_owned() + ws;
732 if let Ok(next_source) = self.span_to_next_source(sp) {
733 let next_source = next_source.split(&pat).next().unwrap_or("").trim_end();
734 if !next_source.is_empty() && (!next_source.contains('\n') || accept_newlines) {
735 return sp.with_hi(BytePos(sp.hi().0 + next_source.len() as u32));
736 }
737 }
738 }
739
740 sp
741 }
742
743 pub fn span_until_char(&self, sp: Span, c: char) -> Span {
751 if sp.is_dummy() {
752 return sp;
753 }
754
755 let v = self.span_to_source(sp, |src, start_index, end_index| {
756 let snippet = &src[start_index..end_index];
757 let snippet = snippet.split(c).next().unwrap_or("").trim_end();
758 if !snippet.is_empty() && !snippet.contains('\n') {
759 sp.with_hi(BytePos(sp.lo().0 + snippet.len() as u32))
760 } else {
761 sp
762 }
763 });
764 match v {
765 Ok(v) => v,
766 Err(_) => sp,
767 }
768 }
769
770 pub fn span_through_char(&self, sp: Span, c: char) -> Span {
777 if sp.is_dummy() {
778 return sp;
779 }
780
781 if let Ok(snippet) = self.span_to_snippet(sp) {
782 if let Some(offset) = snippet.find(c) {
783 return sp.with_hi(BytePos(sp.lo().0 + (offset + c.len_utf8()) as u32));
784 }
785 }
786 sp
787 }
788
789 pub fn span_until_non_whitespace(&self, sp: Span) -> Span {
795 let mut whitespace_found = false;
796
797 self.span_take_while(sp, |c| {
798 if !whitespace_found && c.is_whitespace() {
799 whitespace_found = true;
800 }
801
802 !whitespace_found || c.is_whitespace()
803 })
804 }
805
806 pub fn span_until_whitespace(&self, sp: Span) -> Span {
812 self.span_take_while(sp, |c| !c.is_whitespace())
813 }
814
815 pub fn span_take_while<P>(&self, sp: Span, mut predicate: P) -> Span
817 where
818 P: for<'r> FnMut(&'r char) -> bool,
819 {
820 self.span_to_source(sp, |src, start_index, end_index| {
821 let snippet = &src[start_index..end_index];
822
823 let offset = snippet
824 .chars()
825 .take_while(&mut predicate)
826 .map(|c| c.len_utf8())
827 .sum::<usize>();
828
829 sp.with_hi(BytePos(sp.lo().0 + (offset as u32)))
830 })
831 .unwrap_or(sp)
832 }
833
834 pub fn def_span(&self, sp: Span) -> Span {
835 self.span_until_char(sp, '{')
836 }
837
838 pub fn start_point(&self, sp: Span) -> Span {
840 let pos = sp.lo().0;
841 let width = self.find_width_of_character_at_span(sp, false);
842 let corrected_start_position = pos.checked_add(width).unwrap_or(pos);
843 let end_point = BytePos(cmp::max(corrected_start_position, sp.lo().0));
844 sp.with_hi(end_point)
845 }
846
847 pub fn end_point(&self, sp: Span) -> Span {
849 let pos = sp.hi().0;
850
851 let width = self.find_width_of_character_at_span(sp, false);
852 let corrected_end_position = pos.checked_sub(width).unwrap_or(pos);
853
854 let end_point = BytePos(cmp::max(corrected_end_position, sp.lo().0));
855 sp.with_lo(end_point)
856 }
857
858 pub fn next_point(&self, sp: Span) -> Span {
861 let start_of_next_point = sp.hi().0;
862
863 let width = self.find_width_of_character_at_span(sp, true);
864 let end_of_next_point = start_of_next_point
869 .checked_add(width - 1)
870 .unwrap_or(start_of_next_point);
871
872 let end_of_next_point = BytePos(cmp::max(sp.lo().0 + 1, end_of_next_point));
873 Span::new(BytePos(start_of_next_point), end_of_next_point)
874 }
875
876 fn find_width_of_character_at_span(&self, sp: Span, forwards: bool) -> u32 {
879 if sp.lo() >= sp.hi() {
881 debug!("find_width_of_character_at_span: early return malformed span");
882 return 1;
883 }
884
885 let local_begin = self.lookup_byte_offset(sp.lo());
886 let local_end = self.lookup_byte_offset(sp.hi());
887 debug!(
888 "find_width_of_character_at_span: local_begin=`{:?}`, local_end=`{:?}`",
889 local_begin, local_end
890 );
891
892 let start_index = local_begin.pos.to_usize();
893 let end_index = local_end.pos.to_usize();
894 debug!(
895 "find_width_of_character_at_span: start_index=`{:?}`, end_index=`{:?}`",
896 start_index, end_index
897 );
898
899 if (!forwards && end_index == usize::MIN) || (forwards && start_index == usize::MAX) {
902 debug!("find_width_of_character_at_span: start or end of span, cannot be multibyte");
903 return 1;
904 }
905
906 let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
907 debug!(
908 "find_width_of_character_at_span: source_len=`{:?}`",
909 source_len
910 );
911 if start_index > end_index || end_index > source_len {
913 debug!("find_width_of_character_at_span: source indexes are malformed");
914 return 1;
915 }
916
917 let src = &local_begin.sf.src;
921 let snippet = {
922 let len = src.len();
923 &src[start_index..len]
924 };
925 debug!("find_width_of_character_at_span: snippet=`{:?}`", snippet);
926
927 let mut target = if forwards {
928 end_index + 1
929 } else {
930 end_index - 1
931 };
932 debug!(
933 "find_width_of_character_at_span: initial target=`{:?}`",
934 target
935 );
936
937 while !snippet.is_char_boundary(target - start_index) && target < source_len {
938 target = if forwards {
939 target + 1
940 } else {
941 match target.checked_sub(1) {
942 Some(target) => target,
943 None => {
944 break;
945 }
946 }
947 };
948 debug!("find_width_of_character_at_span: target=`{:?}`", target);
949 }
950 debug!(
951 "find_width_of_character_at_span: final target=`{:?}`",
952 target
953 );
954
955 if forwards {
956 (target - end_index) as u32
957 } else {
958 (end_index - target) as u32
959 }
960 }
961
962 pub fn get_source_file(&self, filename: &FileName) -> Option<Lrc<SourceFile>> {
963 for sf in self.files.borrow().source_files.iter() {
964 if *filename == *sf.name {
965 return Some(sf.clone());
966 }
967 }
968 None
969 }
970
971 pub fn lookup_byte_offset(&self, bpos: BytePos) -> SourceFileAndBytePos {
974 self.try_lookup_byte_offset(bpos).unwrap()
975 }
976
977 pub fn try_lookup_byte_offset(
980 &self,
981 bpos: BytePos,
982 ) -> Result<SourceFileAndBytePos, SourceMapLookupError> {
983 let sf = self.try_lookup_source_file(bpos)?;
984 let offset = bpos - sf.start_pos;
985 Ok(SourceFileAndBytePos { sf, pos: offset })
986 }
987
988 fn bytepos_to_file_charpos(&self, bpos: BytePos) -> Result<CharPos, SourceMapLookupError> {
990 let map = self.try_lookup_source_file(bpos)?;
991
992 Ok(self.bytepos_to_file_charpos_with(&map, bpos))
993 }
994
995 fn bytepos_to_file_charpos_with(&self, map: &SourceFile, bpos: BytePos) -> CharPos {
996 let total_extra_bytes = calc_utf16_offset(map, bpos, &mut Default::default());
997 assert!(
998 map.start_pos.to_u32() + total_extra_bytes <= bpos.to_u32(),
999 "map.start_pos = {:?}; total_extra_bytes = {}; bpos = {:?}",
1000 map.start_pos,
1001 total_extra_bytes,
1002 bpos,
1003 );
1004 CharPos(bpos.to_usize() - map.start_pos.to_usize() - total_extra_bytes as usize)
1005 }
1006
1007 pub fn span_to_char_offset(&self, file: &SourceFile, span: Span) -> (u32, u32) {
1010 let start_offset = file.start_pos;
1012
1013 let mut state = ByteToCharPosState::default();
1014 let start =
1015 span.lo.to_u32() - start_offset.to_u32() - calc_utf16_offset(file, span.lo, &mut state);
1016 let end =
1017 span.hi.to_u32() - start_offset.to_u32() - calc_utf16_offset(file, span.hi, &mut state);
1018
1019 (start, end)
1020 }
1021
1022 #[doc(hidden)]
1027 pub fn lookup_source_file_in(
1028 files: &[Lrc<SourceFile>],
1029 pos: BytePos,
1030 ) -> Option<Lrc<SourceFile>> {
1031 if pos.is_dummy() {
1032 return None;
1033 }
1034
1035 let count = files.len();
1036
1037 let mut a = 0;
1039 let mut b = count;
1040 while b - a > 1 {
1041 let m = (a + b) / 2;
1042 if files[m].start_pos > pos {
1043 b = m;
1044 } else {
1045 a = m;
1046 }
1047 }
1048
1049 if a >= count {
1050 return None;
1051 }
1052
1053 Some(files[a].clone())
1054 }
1055
1056 #[doc(hidden)]
1060 pub fn lookup_source_file(&self, pos: BytePos) -> Lrc<SourceFile> {
1061 self.try_lookup_source_file(pos).unwrap()
1062 }
1063
1064 #[doc(hidden)]
1068 pub fn try_lookup_source_file(
1069 &self,
1070 pos: BytePos,
1071 ) -> Result<Lrc<SourceFile>, SourceMapLookupError> {
1072 let files = self.files.borrow();
1073 let files = &files.source_files;
1074 let fm = Self::lookup_source_file_in(files, pos);
1075 match fm {
1076 Some(fm) => Ok(fm),
1077 None => Err(SourceMapLookupError::NoFileFor(pos)),
1078 }
1079 }
1080
1081 pub fn count_lines(&self) -> usize {
1082 self.files().iter().fold(0, |a, f| a + f.count_lines())
1083 }
1084
1085 pub fn generate_fn_name_span(&self, span: Span) -> Option<Span> {
1086 let prev_span = self.span_extend_to_prev_str(span, "fn", true);
1087 self.span_to_snippet(prev_span)
1088 .map(|snippet| {
1089 let len = snippet
1090 .find(|c: char| !c.is_alphanumeric() && c != '_')
1091 .expect("no label after fn");
1092 prev_span.with_hi(BytePos(prev_span.lo().0 + len as u32))
1093 })
1094 .ok()
1095 }
1096
1097 pub fn generate_local_type_param_snippet(&self, span: Span) -> Option<(Span, String)> {
1118 let sugg_span = self.span_extend_to_prev_str(span, "fn", false);
1121 if sugg_span != span {
1122 if let Ok(snippet) = self.span_to_snippet(sugg_span) {
1123 let mut offset = snippet
1125 .find(|c: char| !c.is_alphanumeric() && c != '_')
1126 .expect("no label after fn");
1127
1128 let mut bracket_counter = 0;
1130 let mut last_char = None;
1131 for c in snippet[offset..].chars() {
1132 match c {
1133 '<' => bracket_counter += 1,
1134 '>' => bracket_counter -= 1,
1135 '(' => {
1136 if bracket_counter == 0 {
1137 break;
1138 }
1139 }
1140 _ => {}
1141 }
1142 offset += c.len_utf8();
1143 last_char = Some(c);
1144 }
1145
1146 let sugg_span = sugg_span.with_hi(BytePos(sugg_span.lo().0 + offset as u32));
1148
1149 let mut new_snippet = if last_char == Some('>') {
1152 format!("{}, ", &snippet[..(offset - '>'.len_utf8())])
1153 } else {
1154 format!("{}<", &snippet[..offset])
1155 };
1156 new_snippet.push_str(
1157 &self
1158 .span_to_snippet(span)
1159 .unwrap_or_else(|_| "T".to_string()),
1160 );
1161 new_snippet.push('>');
1162
1163 return Some((sugg_span, new_snippet));
1164 }
1165 }
1166
1167 None
1168 }
1169
1170 #[allow(clippy::ptr_arg)]
1171 #[cfg(feature = "sourcemap")]
1172 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
1173 pub fn build_source_map(
1174 &self,
1175 mappings: &[(BytePos, LineCol)],
1176 orig: Option<sourcemap::SourceMap>,
1177 config: impl SourceMapGenConfig,
1178 ) -> sourcemap::SourceMap {
1179 build_source_map(self, mappings, orig, &config)
1180 }
1181}
1182
1183fn calc_utf16_offset(file: &SourceFile, bpos: BytePos, state: &mut ByteToCharPosState) -> u32 {
1186 let mut total_extra_bytes = state.total_extra_bytes;
1187 let mut index = state.mbc_index;
1188 let analysis = file.analyze();
1189 if bpos >= state.pos {
1190 let range = index..analysis.multibyte_chars.len();
1191 for i in range {
1192 let mbc = &analysis.multibyte_chars[i];
1193 debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
1194 if mbc.pos >= bpos {
1195 break;
1196 }
1197 total_extra_bytes += mbc.byte_to_char_diff() as u32;
1198 debug_assert!(
1201 bpos.to_u32() >= mbc.pos.to_u32() + mbc.bytes as u32,
1202 "bpos = {:?}, mbc.pos = {:?}, mbc.bytes = {:?}",
1203 bpos,
1204 mbc.pos,
1205 mbc.bytes
1206 );
1207 index += 1;
1208 }
1209 } else {
1210 let range = 0..index;
1211 for i in range.rev() {
1212 let mbc = &analysis.multibyte_chars[i];
1213 debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
1214 if mbc.pos < bpos {
1215 break;
1216 }
1217 total_extra_bytes -= mbc.byte_to_char_diff() as u32;
1218 debug_assert!(
1221 bpos.to_u32() <= mbc.pos.to_u32(),
1222 "bpos = {:?}, mbc.pos = {:?}",
1223 bpos,
1224 mbc.pos,
1225 );
1226 index -= 1;
1227 }
1228 }
1229
1230 state.pos = bpos;
1231 state.total_extra_bytes = total_extra_bytes;
1232 state.mbc_index = index;
1233
1234 total_extra_bytes
1235}
1236
1237pub trait Files {
1238 fn try_lookup_source_file(&self, pos: BytePos)
1239 -> Result<Lrc<SourceFile>, SourceMapLookupError>;
1240}
1241
1242impl Files for SourceMap {
1243 fn try_lookup_source_file(
1244 &self,
1245 pos: BytePos,
1246 ) -> Result<Lrc<SourceFile>, SourceMapLookupError> {
1247 self.try_lookup_source_file(pos)
1248 }
1249}
1250
1251#[allow(clippy::ptr_arg)]
1252#[cfg(feature = "sourcemap")]
1253#[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
1254pub fn build_source_map(
1255 files: &impl Files,
1256 mappings: &[(BytePos, LineCol)],
1257 orig: Option<sourcemap::SourceMap>,
1258 config: &impl SourceMapGenConfig,
1259) -> sourcemap::SourceMap {
1260 let mut builder = SourceMapBuilder::new(None);
1261
1262 let mut src_id = 0u32;
1263
1264 let mut cur_file: Option<Lrc<SourceFile>> = None;
1268
1269 let mut prev_dst_line = u32::MAX;
1270
1271 let mut ch_state = ByteToCharPosState::default();
1272 let mut line_state = ByteToCharPosState::default();
1273
1274 for (pos, lc) in mappings.iter() {
1275 let pos = *pos;
1276
1277 if pos.is_reserved_for_comments() {
1278 continue;
1279 }
1280
1281 let lc = *lc;
1282
1283 if lc.line == 0 && lc.col == 0 && pos.is_dummy() {
1286 continue;
1287 }
1288
1289 if pos == BytePos(u32::MAX) {
1290 builder.add_raw(lc.line, lc.col, 0, 0, Some(src_id), None, false);
1291 continue;
1292 }
1293
1294 let f;
1295 let f = match cur_file {
1296 Some(ref f) if f.start_pos <= pos && pos < f.end_pos => f,
1297 _ => {
1298 f = files.try_lookup_source_file(pos).unwrap();
1299 if config.skip(&f.name) {
1300 continue;
1301 }
1302 src_id = builder.add_source(&config.file_name_to_source(&f.name));
1303 if orig.is_none() && config.ignore_list(&f.name) {
1305 builder.add_to_ignore_list(src_id);
1306 }
1307
1308 let inline_sources_content =
1310 orig.is_none() && config.inline_sources_content(&f.name);
1311 if inline_sources_content {
1312 builder.set_source_contents(src_id, Some(&f.src));
1313 }
1314
1315 ch_state = ByteToCharPosState::default();
1316 line_state = ByteToCharPosState::default();
1317
1318 cur_file = Some(f.clone());
1319 &f
1320 }
1321 };
1322 if config.skip(&f.name) {
1323 continue;
1324 }
1325
1326 let emit_columns = config.emit_columns(&f.name);
1327
1328 if !emit_columns && lc.line == prev_dst_line {
1329 continue;
1330 }
1331
1332 let line = match f.lookup_line(pos) {
1333 Some(line) => line as u32,
1334 None => continue,
1335 };
1336
1337 let analysis = f.analyze();
1338 let linebpos = analysis.lines[line as usize];
1339 debug_assert!(
1340 pos >= linebpos,
1341 "{}: bpos = {:?}; linebpos = {:?};",
1342 f.name,
1343 pos,
1344 linebpos,
1345 );
1346
1347 let linechpos = linebpos.to_u32() - calc_utf16_offset(f, linebpos, &mut line_state);
1348 let chpos = pos.to_u32() - calc_utf16_offset(f, pos, &mut ch_state);
1349
1350 debug_assert!(
1351 chpos >= linechpos,
1352 "{}: chpos = {:?}; linechpos = {:?};",
1353 f.name,
1354 chpos,
1355 linechpos,
1356 );
1357
1358 let col = chpos - linechpos;
1359 let name = None;
1360
1361 let name_idx = if orig.is_none() {
1362 name.or_else(|| config.name_for_bytepos(pos))
1363 .map(|name| builder.add_name(name))
1364 } else {
1365 None
1367 };
1368
1369 builder.add_raw(lc.line, lc.col, line, col, Some(src_id), name_idx, false);
1370 prev_dst_line = lc.line;
1371 }
1372
1373 let map = builder.into_sourcemap();
1374
1375 if let Some(mut orig) = orig {
1376 orig.adjust_mappings(&map);
1377 return orig;
1378 }
1379
1380 map
1381}
1382
1383impl SourceMapper for SourceMap {
1384 fn lookup_char_pos(&self, pos: BytePos) -> Loc {
1385 self.lookup_char_pos(pos)
1386 }
1387
1388 fn span_to_lines(&self, sp: Span) -> FileLinesResult {
1389 self.span_to_lines(sp)
1390 }
1391
1392 fn span_to_string(&self, sp: Span) -> String {
1393 self.span_to_string(sp)
1394 }
1395
1396 fn span_to_filename(&self, sp: Span) -> Lrc<FileName> {
1397 self.span_to_filename(sp)
1398 }
1399
1400 fn span_to_snippet(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
1402 self.span_to_source(sp, |src, start_index, end_index| {
1403 src[start_index..end_index].to_string()
1404 })
1405 }
1406
1407 fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
1408 self.merge_spans(sp_lhs, sp_rhs)
1409 }
1410
1411 fn call_span_if_macro(&self, sp: Span) -> Span {
1412 sp
1413 }
1414
1415 fn doctest_offset_line(&self, line: usize) -> usize {
1416 self.doctest_offset_line(line)
1417 }
1418}
1419
1420#[derive(Clone, Default)]
1421pub struct FilePathMapping {
1422 mapping: Vec<(PathBuf, PathBuf)>,
1423}
1424
1425impl FilePathMapping {
1426 pub fn empty() -> FilePathMapping {
1427 FilePathMapping {
1428 mapping: Vec::new(),
1429 }
1430 }
1431
1432 pub fn new(mapping: Vec<(PathBuf, PathBuf)>) -> FilePathMapping {
1433 FilePathMapping { mapping }
1434 }
1435
1436 pub fn map_prefix(&self, path: &Path) -> (PathBuf, bool) {
1440 for (from, to) in self.mapping.iter().rev() {
1444 if let Ok(rest) = path.strip_prefix(from) {
1445 return (to.join(rest), true);
1446 }
1447 }
1448
1449 (path.to_path_buf(), false)
1450 }
1451}
1452
1453pub trait SourceMapGenConfig {
1454 fn file_name_to_source(&self, f: &FileName) -> String;
1460
1461 fn name_for_bytepos(&self, _bpos: BytePos) -> Option<&str> {
1463 None
1464 }
1465
1466 fn inline_sources_content(&self, f: &FileName) -> bool {
1468 !matches!(
1469 f,
1470 FileName::Real(..) | FileName::Custom(..) | FileName::Url(..)
1471 )
1472 }
1473
1474 fn emit_columns(&self, _f: &FileName) -> bool {
1476 true
1477 }
1478
1479 fn skip(&self, f: &FileName) -> bool {
1481 matches!(f, FileName::Internal(..))
1482 }
1483
1484 fn ignore_list(&self, f: &FileName) -> bool {
1500 matches!(f, FileName::Anon | FileName::Internal(..))
1501 }
1502}
1503
1504#[derive(Debug, Clone)]
1505pub struct DefaultSourceMapGenConfig;
1506
1507macro_rules! impl_ref {
1508 ($TP:ident, $T:ty) => {
1509 impl<$TP> SourceMapGenConfig for $T
1510 where
1511 $TP: SourceMapGenConfig,
1512 {
1513 fn file_name_to_source(&self, f: &FileName) -> String {
1514 (**self).file_name_to_source(f)
1515 }
1516 }
1517 };
1518}
1519
1520impl_ref!(T, &'_ T);
1521impl_ref!(T, Box<T>);
1522impl_ref!(T, std::rc::Rc<T>);
1523impl_ref!(T, std::sync::Arc<T>);
1524
1525impl SourceMapGenConfig for DefaultSourceMapGenConfig {
1526 fn file_name_to_source(&self, f: &FileName) -> String {
1527 f.to_string()
1528 }
1529}
1530
1531#[derive(Debug, Clone, Default)]
1533pub struct ByteToCharPosState {
1534 pos: BytePos,
1536
1537 total_extra_bytes: u32,
1539
1540 mbc_index: usize,
1543}
1544
1545#[cfg(test)]
1550mod tests {
1551 use super::*;
1552
1553 fn init_source_map() -> SourceMap {
1554 let sm = SourceMap::new(FilePathMapping::empty());
1555 sm.new_source_file(
1556 Lrc::new(PathBuf::from("blork.rs").into()),
1557 "first line.\nsecond line".to_string(),
1558 );
1559 sm.new_source_file(Lrc::new(PathBuf::from("empty.rs").into()), String::new());
1560 sm.new_source_file(
1561 Lrc::new(PathBuf::from("blork2.rs").into()),
1562 "first line.\nsecond line".to_string(),
1563 );
1564 sm
1565 }
1566
1567 #[test]
1568 fn t3() {
1569 let sm = init_source_map();
1571
1572 let srcfbp1 = sm.lookup_byte_offset(BytePos(24));
1573 assert_eq!(*srcfbp1.sf.name, PathBuf::from("blork.rs").into());
1574 assert_eq!(srcfbp1.pos, BytePos(23));
1575
1576 let srcfbp1 = sm.lookup_byte_offset(BytePos(25));
1577 assert_eq!(*srcfbp1.sf.name, PathBuf::from("empty.rs").into());
1578 assert_eq!(srcfbp1.pos, BytePos(0));
1579
1580 let srcfbp2 = sm.lookup_byte_offset(BytePos(26));
1581 assert_eq!(*srcfbp2.sf.name, PathBuf::from("blork2.rs").into());
1582 assert_eq!(srcfbp2.pos, BytePos(0));
1583 }
1584
1585 #[test]
1586 fn t4() {
1587 let sm = init_source_map();
1589
1590 let cp1 = sm.bytepos_to_file_charpos(BytePos(23)).unwrap();
1591 assert_eq!(cp1, CharPos(22));
1592
1593 let cp2 = sm.bytepos_to_file_charpos(BytePos(26)).unwrap();
1594 assert_eq!(cp2, CharPos(0));
1595 }
1596
1597 #[test]
1598 fn t5() {
1599 let sm = init_source_map();
1601
1602 let loc1 = sm.lookup_char_pos(BytePos(23));
1603 assert_eq!(*loc1.file.name, PathBuf::from("blork.rs").into());
1604 assert_eq!(loc1.line, 2);
1605 assert_eq!(loc1.col, CharPos(10));
1606
1607 let loc2 = sm.lookup_char_pos(BytePos(26));
1608 assert_eq!(*loc2.file.name, PathBuf::from("blork2.rs").into());
1609 assert_eq!(loc2.line, 1);
1610 assert_eq!(loc2.col, CharPos(0));
1611 }
1612
1613 fn init_source_map_mbc() -> SourceMap {
1614 let sm = SourceMap::new(FilePathMapping::empty());
1615 sm.new_source_file(
1617 Lrc::new(PathBuf::from("blork.rs").into()),
1618 "fir€st €€€€ line.\nsecond line".to_string(),
1619 );
1620 sm.new_source_file(
1621 Lrc::new(PathBuf::from("blork2.rs").into()),
1622 "first line€€.\n€ second line".to_string(),
1623 );
1624 sm
1625 }
1626
1627 #[test]
1628 fn t6() {
1629 let sm = init_source_map_mbc();
1631
1632 let cp1 = sm.bytepos_to_file_charpos(BytePos(4)).unwrap();
1633 assert_eq!(cp1, CharPos(3));
1634
1635 let cp2 = sm.bytepos_to_file_charpos(BytePos(7)).unwrap();
1636 assert_eq!(cp2, CharPos(4));
1637
1638 let cp3 = sm.bytepos_to_file_charpos(BytePos(57)).unwrap();
1639 assert_eq!(cp3, CharPos(12));
1640
1641 let cp4 = sm.bytepos_to_file_charpos(BytePos(62)).unwrap();
1642 assert_eq!(cp4, CharPos(15));
1643 }
1644
1645 #[test]
1646 fn t7() {
1647 let sm = init_source_map();
1649 let span = Span::new(BytePos(13), BytePos(24));
1650 let file_lines = sm.span_to_lines(span).unwrap();
1651
1652 assert_eq!(*file_lines.file.name, PathBuf::from("blork.rs").into());
1653 assert_eq!(file_lines.lines.len(), 1);
1654 assert_eq!(file_lines.lines[0].line_index, 1);
1655 }
1656
1657 fn span_from_selection(input: &str, selection: &str) -> Span {
1662 assert_eq!(input.len(), selection.len());
1663 let left_index = (selection.find('~').unwrap() + 1) as u32;
1665 let right_index = selection
1666 .rfind('~')
1667 .map(|x| {
1668 (x + 1) as u32
1670 })
1671 .unwrap_or(left_index);
1672 Span::new(BytePos(left_index), BytePos(right_index + 1))
1673 }
1674
1675 #[test]
1678 fn span_to_snippet_and_lines_spanning_multiple_lines() {
1679 let sm = SourceMap::new(FilePathMapping::empty());
1680 let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
1681 let selection = " \n ~~\n~~~\n~~~~~ \n \n";
1682 sm.new_source_file(
1683 Lrc::new(Path::new("blork.rs").to_path_buf().into()),
1684 inputtext.to_string(),
1685 );
1686 let span = span_from_selection(inputtext, selection);
1687
1688 assert_eq!(&sm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD");
1690
1691 let lines = sm.span_to_lines(span).unwrap();
1694 let expected = vec![
1695 LineInfo {
1696 line_index: 1,
1697 start_col: CharPos(4),
1698 end_col: CharPos(6),
1699 },
1700 LineInfo {
1701 line_index: 2,
1702 start_col: CharPos(0),
1703 end_col: CharPos(3),
1704 },
1705 LineInfo {
1706 line_index: 3,
1707 start_col: CharPos(0),
1708 end_col: CharPos(5),
1709 },
1710 ];
1711 assert_eq!(lines.lines, expected);
1712 }
1713
1714 #[test]
1715 fn t8() {
1716 let sm = init_source_map();
1718 let span = Span::new(BytePos(13), BytePos(24));
1719 let snippet = sm.span_to_snippet(span);
1720
1721 assert_eq!(snippet, Ok("second line".to_string()));
1722 }
1723
1724 #[test]
1725 fn t9() {
1726 let sm = init_source_map();
1728 let span = Span::new(BytePos(13), BytePos(24));
1729 let sstr = sm.span_to_string(span);
1730
1731 assert_eq!(sstr, "blork.rs:2:1: 2:12");
1732 }
1733
1734 #[test]
1735 fn t10() {
1736 let sm = SourceMap::new(FilePathMapping::empty());
1738 sm.new_source_file(Lrc::new(PathBuf::from("blork.rs").into()), "".to_string());
1739 let span = Span::new(BytePos(1), BytePos(1));
1740 let file_lines = sm.span_to_lines(span).unwrap();
1741
1742 assert_eq!(*file_lines.file.name, PathBuf::from("blork.rs").into());
1743 assert_eq!(file_lines.lines.len(), 0);
1744 }
1745
1746 #[test]
1748 fn span_merging_fail() {
1749 let sm = SourceMap::new(FilePathMapping::empty());
1750 let inputtext = "bbbb BB\ncc CCC\n";
1751 let selection1 = " ~~\n \n";
1752 let selection2 = " \n ~~~\n";
1753 sm.new_source_file(
1754 Lrc::new(Path::new("blork.rs").to_owned().into()),
1755 inputtext.to_owned(),
1756 );
1757 let span1 = span_from_selection(inputtext, selection1);
1758 let span2 = span_from_selection(inputtext, selection2);
1759
1760 assert!(sm.merge_spans(span1, span2).is_none());
1761 }
1762
1763 #[test]
1764 fn test_calc_utf16_offset() {
1765 let input = "t¢e∆s💩t";
1766 let sm = SourceMap::new(FilePathMapping::empty());
1767 let file = sm.new_source_file(
1768 Lrc::new(PathBuf::from("blork.rs").into()),
1769 input.to_string(),
1770 );
1771
1772 let mut state = ByteToCharPosState::default();
1773 let mut bpos = file.start_pos;
1774 let mut cpos = CharPos(bpos.to_usize());
1775 for c in input.chars() {
1776 let actual = bpos.to_u32() - calc_utf16_offset(&file, bpos, &mut state);
1777
1778 assert_eq!(actual, cpos.to_u32());
1779
1780 bpos = bpos + BytePos(c.len_utf8() as u32);
1781 cpos = cpos + CharPos(c.len_utf16());
1782 }
1783
1784 for c in input.chars().rev() {
1785 bpos = bpos - BytePos(c.len_utf8() as u32);
1786 cpos = cpos - CharPos(c.len_utf16());
1787
1788 let actual = bpos.to_u32() - calc_utf16_offset(&file, bpos, &mut state);
1789
1790 assert_eq!(actual, cpos.to_u32());
1791 }
1792 }
1793
1794 #[test]
1795 fn bytepos_to_charpos() {
1796 let input = "t¢e∆s💩t";
1797 let sm = SourceMap::new(FilePathMapping::empty());
1798 let file = sm.new_source_file(
1799 Lrc::new(PathBuf::from("blork.rs").into()),
1800 input.to_string(),
1801 );
1802
1803 let mut bpos = file.start_pos;
1804 let mut cpos = CharPos(0);
1805 for c in input.chars() {
1806 let actual = sm.bytepos_to_file_charpos_with(&file, bpos);
1807
1808 assert_eq!(actual, cpos);
1809
1810 bpos = bpos + BytePos(c.len_utf8() as u32);
1811 cpos = cpos + CharPos(c.len_utf16());
1812 }
1813 }
1814}