1use std::{
23 cmp, env, fs,
24 hash::Hash,
25 io,
26 path::{Path, PathBuf},
27 sync::atomic::{AtomicUsize, Ordering::SeqCst},
28};
29
30use bytes_str::BytesStr;
31use once_cell::sync::Lazy;
32use rustc_hash::FxHashMap;
33#[cfg(feature = "sourcemap")]
34use swc_sourcemap::SourceMapBuilder;
35use tracing::debug;
36
37pub use crate::syntax_pos::*;
38use crate::{
39 errors::SourceMapper,
40 rustc_data_structures::stable_hasher::StableHasher,
41 sync::{Lock, LockGuard, Lrc, MappedLockGuard},
42};
43
44static CURRENT_DIR: Lazy<Option<PathBuf>> = Lazy::new(|| env::current_dir().ok());
45
46pub trait FileLoader {
52 fn file_exists(&self, path: &Path) -> bool;
54
55 fn abs_path(&self, path: &Path) -> Option<PathBuf>;
57
58 fn read_file(&self, path: &Path) -> io::Result<BytesStr>;
60}
61
62pub struct RealFileLoader;
64
65impl FileLoader for RealFileLoader {
66 fn file_exists(&self, path: &Path) -> bool {
67 fs::metadata(path).is_ok()
68 }
69
70 fn abs_path(&self, path: &Path) -> Option<PathBuf> {
71 if path.is_absolute() {
72 Some(path.to_path_buf())
73 } else {
74 CURRENT_DIR.as_ref().map(|cwd| cwd.join(path))
75 }
76 }
77
78 fn read_file(&self, path: &Path) -> io::Result<BytesStr> {
79 let bytes = fs::read(path)?;
80 BytesStr::from_utf8(bytes.into()).map_err(|_| {
81 io::Error::new(
82 io::ErrorKind::InvalidData,
83 "Failed to convert bytes to UTF-8",
84 )
85 })
86 }
87}
88
89#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
93pub struct StableSourceFileId(u128);
94
95impl StableSourceFileId {
96 pub fn new(source_file: &SourceFile) -> StableSourceFileId {
97 let mut hasher = StableHasher::new();
98
99 source_file.name.hash(&mut hasher);
100 source_file.name_was_remapped.hash(&mut hasher);
101 source_file.unmapped_path.hash(&mut hasher);
102
103 StableSourceFileId(hasher.finish())
104 }
105}
106
107#[derive(Default)]
112pub(super) struct SourceMapFiles {
113 pub(super) source_files: Vec<Lrc<SourceFile>>,
114 stable_id_to_source_file: FxHashMap<StableSourceFileId, Lrc<SourceFile>>,
115}
116
117pub struct SourceMap {
137 pub(super) files: Lock<SourceMapFiles>,
138 start_pos: AtomicUsize,
139 file_loader: Box<dyn FileLoader + Sync + Send>,
140 path_mapping: FilePathMapping,
143 doctest_offset: Option<(FileName, isize)>,
146}
147
148impl Default for SourceMap {
149 fn default() -> Self {
150 Self::new(FilePathMapping::empty())
151 }
152}
153
154impl SourceMap {
155 pub fn new(path_mapping: FilePathMapping) -> SourceMap {
156 SourceMap {
157 files: Default::default(),
158 start_pos: AtomicUsize::new(1),
159 file_loader: Box::new(RealFileLoader),
160 path_mapping,
161 doctest_offset: None,
162 }
163 }
164
165 pub fn with_file_loader(
166 file_loader: Box<dyn FileLoader + Sync + Send>,
167 path_mapping: FilePathMapping,
168 ) -> SourceMap {
169 SourceMap {
170 files: Default::default(),
171 start_pos: AtomicUsize::new(1),
172 file_loader,
173 path_mapping,
174 doctest_offset: None,
175 }
176 }
177
178 pub fn path_mapping(&self) -> &FilePathMapping {
179 &self.path_mapping
180 }
181
182 pub fn file_exists(&self, path: &Path) -> bool {
183 self.file_loader.file_exists(path)
184 }
185
186 pub fn load_file(&self, path: &Path) -> io::Result<Lrc<SourceFile>> {
187 let src = self.file_loader.read_file(path)?;
188 let filename = Lrc::new(path.to_path_buf().into());
189 Ok(self.new_source_file(filename, src))
190 }
191
192 pub fn files(&self) -> MappedLockGuard<'_, Vec<Lrc<SourceFile>>> {
193 LockGuard::map(self.files.borrow(), |files| &mut files.source_files)
194 }
195
196 pub fn source_file_by_stable_id(
197 &self,
198 stable_id: StableSourceFileId,
199 ) -> Option<Lrc<SourceFile>> {
200 self.files
201 .borrow()
202 .stable_id_to_source_file
203 .get(&stable_id)
204 .cloned()
205 }
206
207 fn next_start_pos(&self, len: usize) -> usize {
208 self.start_pos.fetch_add(len + 1, SeqCst)
211 }
212
213 #[inline(always)]
219 pub fn new_source_file(
220 &self,
221 filename: Lrc<FileName>,
222 src: impl Into<BytesStr>,
223 ) -> Lrc<SourceFile> {
224 self.new_source_file_impl(filename, src.into())
225 }
226
227 fn new_source_file_impl(&self, filename: Lrc<FileName>, mut src: BytesStr) -> Lrc<SourceFile> {
228 remove_bom(&mut src);
229
230 let unmapped_path = filename.clone();
236
237 let (filename, was_remapped) = match &*filename {
238 FileName::Real(filename) => {
239 let (filename, was_remapped) = self.path_mapping.map_prefix(filename);
240 (Lrc::new(FileName::Real(filename)), was_remapped)
241 }
242 _ => (filename, false),
243 };
244
245 let mut files = self.files.borrow_mut();
248
249 let start_pos = self.next_start_pos(src.len());
250
251 let source_file = Lrc::new(SourceFile::new(
252 filename,
253 was_remapped,
254 unmapped_path,
255 src,
256 SmallPos::from_usize(start_pos),
257 ));
258
259 {
260 files.source_files.push(source_file.clone());
261 files
262 .stable_id_to_source_file
263 .insert(StableSourceFileId::new(&source_file), source_file.clone());
264 }
265
266 source_file
267 }
268
269 pub fn mk_substr_filename(&self, sp: Span) -> String {
270 let pos = self.lookup_char_pos(sp.lo());
271 format!(
272 "<{}:{}:{}>",
273 pos.file.name,
274 pos.line,
275 pos.col.to_usize() + 1
276 )
277 }
278
279 pub fn doctest_offset_line(&self, mut orig: usize) -> usize {
281 if let Some((_, line)) = self.doctest_offset {
282 if line >= 0 {
283 orig += line as usize;
284 } else {
285 orig -= (-line) as usize;
286 }
287 }
288 orig
289 }
290
291 pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
293 self.try_lookup_char_pos(pos).unwrap()
294 }
295
296 pub fn try_lookup_char_pos(&self, pos: BytePos) -> Result<Loc, SourceMapLookupError> {
298 let fm = self.try_lookup_source_file(pos)?.unwrap();
299 self.try_lookup_char_pos_with(fm, pos)
300 }
301
302 #[doc(hidden)]
308 pub fn lookup_char_pos_with(&self, fm: Lrc<SourceFile>, pos: BytePos) -> Loc {
309 self.try_lookup_char_pos_with(fm, pos).unwrap()
310 }
311
312 #[doc(hidden)]
318 pub fn try_lookup_char_pos_with(
319 &self,
320 fm: Lrc<SourceFile>,
321 pos: BytePos,
322 ) -> Result<Loc, SourceMapLookupError> {
323 let line_info = self.lookup_line_with(fm, pos);
324 match line_info {
325 Ok(SourceFileAndLine { sf: f, line: a }) => {
326 let analysis = f.analyze();
327 let chpos = self.bytepos_to_file_charpos_with(&f, pos);
328
329 let line = a + 1; let linebpos = f.analyze().lines[a];
331 assert!(
332 pos >= linebpos,
333 "{}: bpos = {:?}; linebpos = {:?};",
334 f.name,
335 pos,
336 linebpos,
337 );
338
339 let linechpos = self.bytepos_to_file_charpos_with(&f, linebpos);
340 let col = chpos - linechpos;
341
342 let col_display = {
343 let start_width_idx = analysis
344 .non_narrow_chars
345 .binary_search_by_key(&linebpos, |x| x.pos())
346 .unwrap_or_else(|x| x);
347 let end_width_idx = analysis
348 .non_narrow_chars
349 .binary_search_by_key(&pos, |x| x.pos())
350 .unwrap_or_else(|x| x);
351 let special_chars = end_width_idx - start_width_idx;
352 let non_narrow: usize = analysis.non_narrow_chars
353 [start_width_idx..end_width_idx]
354 .iter()
355 .map(|x| x.width())
356 .sum();
357 col.0 - special_chars + non_narrow
358 };
359 if cfg!(feature = "debug") {
360 debug!(
361 "byte pos {:?} is on the line at byte pos {:?}",
362 pos, linebpos
363 );
364 debug!(
365 "char pos {:?} is on the line at char pos {:?}",
366 chpos, linechpos
367 );
368 debug!("byte is on line: {}", line);
369 }
370 Ok(Loc {
372 file: f,
373 line,
374 col,
375 col_display,
376 })
377 }
378 Err(f) => {
379 let analysis = f.analyze();
380 let chpos = self.bytepos_to_file_charpos(pos)?;
381
382 let col_display = {
383 let end_width_idx = analysis
384 .non_narrow_chars
385 .binary_search_by_key(&pos, |x| x.pos())
386 .unwrap_or_else(|x| x);
387 let non_narrow: usize = analysis.non_narrow_chars[0..end_width_idx]
388 .iter()
389 .map(|x| x.width())
390 .sum();
391 chpos.0 - end_width_idx + non_narrow
392 };
393 Ok(Loc {
394 file: f,
395 line: 0,
396 col: chpos,
397 col_display,
398 })
399 }
400 }
401 }
402
403 pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Lrc<SourceFile>> {
405 let f = self.try_lookup_source_file(pos).unwrap().unwrap();
406
407 self.lookup_line_with(f, pos)
408 }
409
410 #[doc(hidden)]
415 pub fn lookup_line_with(
416 &self,
417 f: Lrc<SourceFile>,
418 pos: BytePos,
419 ) -> Result<SourceFileAndLine, Lrc<SourceFile>> {
420 match f.lookup_line(pos) {
421 Some(line) => Ok(SourceFileAndLine { sf: f, line }),
422 None => Err(f),
423 }
424 }
425
426 pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt {
427 let loc = self.lookup_char_pos(pos);
428 LocWithOpt {
429 filename: loc.file.name.clone(),
430 line: loc.line,
431 col: loc.col,
432 file: Some(loc.file),
433 }
434 }
435
436 pub fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
445 let lhs_end = match self.lookup_line(sp_lhs.hi()) {
446 Ok(x) => x,
447 Err(_) => return None,
448 };
449 let rhs_begin = match self.lookup_line(sp_rhs.lo()) {
450 Ok(x) => x,
451 Err(_) => return None,
452 };
453
454 if lhs_end.line != rhs_begin.line {
456 return None;
457 }
458
459 if (sp_lhs.lo() <= sp_rhs.lo()) && (sp_lhs.hi() <= sp_rhs.lo()) {
461 Some(sp_lhs.to(sp_rhs))
462 } else {
463 None
464 }
465 }
466
467 pub fn span_to_string(&self, sp: Span) -> String {
468 if self.files.borrow().source_files.is_empty() && sp.is_dummy() {
469 return "no-location".to_string();
470 }
471
472 let lo = self.lookup_char_pos_adj(sp.lo());
473 let hi = self.lookup_char_pos_adj(sp.hi());
474 format!(
475 "{}:{}:{}: {}:{}",
476 lo.filename,
477 lo.line,
478 lo.col.to_usize() + 1,
479 hi.line,
480 hi.col.to_usize() + 1
481 )
482 }
483
484 pub fn span_to_filename(&self, sp: Span) -> Lrc<FileName> {
485 self.lookup_char_pos(sp.lo()).file.name.clone()
486 }
487
488 pub fn span_to_unmapped_path(&self, sp: Span) -> Lrc<FileName> {
489 self.lookup_char_pos(sp.lo())
490 .file
491 .unmapped_path
492 .clone()
493 .expect("SourceMap::span_to_unmapped_path called for imported SourceFile?")
494 }
495
496 pub fn is_multiline(&self, sp: Span) -> bool {
497 let lo = self.lookup_char_pos(sp.lo());
498 let hi = self.lookup_char_pos(sp.hi());
499 lo.line != hi.line
500 }
501
502 pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
503 if cfg!(feature = "debug") {
504 debug!("span_to_lines(sp={:?})", sp);
505 }
506
507 if sp.lo() > sp.hi() {
508 return Err(Box::new(SpanLinesError::IllFormedSpan(sp)));
509 }
510
511 let lo = self.lookup_char_pos(sp.lo());
512 if cfg!(feature = "debug") {
513 debug!("span_to_lines: lo={:?}", lo);
514 }
515 let hi = self.lookup_char_pos(sp.hi());
516 if cfg!(feature = "debug") {
517 debug!("span_to_lines: hi={:?}", hi);
518 }
519
520 if lo.file.start_pos != hi.file.start_pos {
521 return Err(Box::new(SpanLinesError::DistinctSources(DistinctSources {
522 begin: FilePos(lo.file.name.clone(), lo.file.start_pos),
523 end: FilePos(hi.file.name.clone(), hi.file.start_pos),
524 })));
525 }
526 assert!(hi.line >= lo.line);
527
528 if lo.file.src.is_empty() {
530 return Ok(FileLines {
531 file: lo.file,
532 lines: Vec::new(),
533 });
534 }
535
536 let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
537
538 let mut start_col = lo.col;
541
542 for line_index in lo.line - 1..hi.line - 1 {
547 let line_len = lo
548 .file
549 .get_line(line_index)
550 .map(|s| s.chars().count())
551 .unwrap_or(0);
552 lines.push(LineInfo {
553 line_index,
554 start_col,
555 end_col: CharPos::from_usize(line_len),
556 });
557 start_col = CharPos::from_usize(0);
558 }
559
560 lines.push(LineInfo {
562 line_index: hi.line - 1,
563 start_col,
564 end_col: hi.col,
565 });
566
567 Ok(FileLines {
568 file: lo.file,
569 lines,
570 })
571 }
572
573 fn span_to_source<F, Ret>(
579 &self,
580 sp: Span,
581 extract_source: F,
582 ) -> Result<Ret, Box<SpanSnippetError>>
583 where
584 F: FnOnce(&str, usize, usize) -> Ret,
585 {
586 if sp.lo() > sp.hi() {
587 return Err(Box::new(SpanSnippetError::IllFormedSpan(sp)));
588 }
589 if sp.lo.is_dummy() || sp.hi.is_dummy() {
590 return Err(Box::new(SpanSnippetError::DummyBytePos));
591 }
592
593 let local_begin = self.try_lookup_byte_offset(sp.lo())?;
594 let local_end = self.try_lookup_byte_offset(sp.hi())?;
595
596 if local_begin.sf.start_pos != local_end.sf.start_pos {
597 Err(Box::new(SpanSnippetError::DistinctSources(
598 DistinctSources {
599 begin: FilePos(local_begin.sf.name.clone(), local_begin.sf.start_pos),
600 end: FilePos(local_end.sf.name.clone(), local_end.sf.start_pos),
601 },
602 )))
603 } else {
604 let start_index = local_begin.pos.to_usize();
605 let end_index = local_end.pos.to_usize();
606 let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
607
608 if start_index > end_index || end_index > source_len {
609 return Err(Box::new(SpanSnippetError::MalformedForSourcemap(
610 MalformedSourceMapPositions {
611 name: local_begin.sf.name.clone(),
612 source_len,
613 begin_pos: local_begin.pos,
614 end_pos: local_end.pos,
615 },
616 )));
617 }
618
619 let src = &local_begin.sf.src;
620 Ok(extract_source(src, start_index, end_index))
621 }
622 }
623
624 pub fn with_snippet_of_span<F, Ret>(
626 &self,
627 sp: Span,
628 op: F,
629 ) -> Result<Ret, Box<SpanSnippetError>>
630 where
631 F: FnOnce(&str) -> Ret,
632 {
633 self.span_to_source(sp, |src, start_index, end_index| {
634 op(&src[start_index..end_index])
635 })
636 }
637
638 pub fn span_to_margin(&self, sp: Span) -> Option<usize> {
639 match self.span_to_prev_source(sp) {
640 Err(_) => None,
641 Ok(source) => source
642 .split('\n')
643 .next_back()
644 .map(|last_line| last_line.len() - last_line.trim_start().len()),
645 }
646 }
647
648 pub fn with_span_to_prev_source<F, Ret>(
650 &self,
651 sp: Span,
652 op: F,
653 ) -> Result<Ret, Box<SpanSnippetError>>
654 where
655 F: FnOnce(&str) -> Ret,
656 {
657 self.span_to_source(sp, |src, start_index, _| op(&src[..start_index]))
658 }
659
660 pub fn span_to_prev_source(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
662 self.with_span_to_prev_source(sp, |s| s.to_string())
663 }
664
665 pub fn with_span_to_next_source<F, Ret>(
667 &self,
668 sp: Span,
669 op: F,
670 ) -> Result<Ret, Box<SpanSnippetError>>
671 where
672 F: FnOnce(&str) -> Ret,
673 {
674 self.span_to_source(sp, |src, _, end_index| op(&src[end_index..]))
675 }
676
677 pub fn span_to_next_source(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
679 self.with_span_to_next_source(sp, |s| s.to_string())
680 }
681
682 pub fn span_extend_to_prev_char(&self, sp: Span, c: char) -> Span {
686 if let Ok(prev_source) = self.span_to_prev_source(sp) {
687 let prev_source = prev_source.rsplit(c).next().unwrap_or("").trim_start();
688 if !prev_source.is_empty() && !prev_source.contains('\n') {
689 return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
690 }
691 }
692
693 sp
694 }
695
696 pub fn span_extend_to_prev_str(&self, sp: Span, pat: &str, accept_newlines: bool) -> Span {
701 for ws in &[" ", "\t", "\n"] {
706 let pat = pat.to_owned() + ws;
707 if let Ok(prev_source) = self.span_to_prev_source(sp) {
708 let prev_source = prev_source.rsplit(&pat).next().unwrap_or("").trim_start();
709 if !prev_source.is_empty() && (!prev_source.contains('\n') || accept_newlines) {
710 return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
711 }
712 }
713 }
714
715 sp
716 }
717
718 pub fn span_extend_to_next_char(&self, sp: Span, c: char) -> Span {
722 if let Ok(next_source) = self.span_to_next_source(sp) {
723 let next_source = next_source.split(c).next().unwrap_or("").trim_end();
724 if !next_source.is_empty() && !next_source.contains('\n') {
725 return sp.with_hi(BytePos(sp.hi().0 + next_source.len() as u32));
726 }
727 }
728
729 sp
730 }
731
732 pub fn span_extend_to_next_str(&self, sp: Span, pat: &str, accept_newlines: bool) -> Span {
737 for ws in &[" ", "\t", "\n"] {
738 let pat = pat.to_owned() + ws;
739 if let Ok(next_source) = self.span_to_next_source(sp) {
740 let next_source = next_source.split(&pat).next().unwrap_or("").trim_end();
741 if !next_source.is_empty() && (!next_source.contains('\n') || accept_newlines) {
742 return sp.with_hi(BytePos(sp.hi().0 + next_source.len() as u32));
743 }
744 }
745 }
746
747 sp
748 }
749
750 pub fn span_until_char(&self, sp: Span, c: char) -> Span {
758 if sp.is_dummy() {
759 return sp;
760 }
761
762 let v = self.span_to_source(sp, |src, start_index, end_index| {
763 let snippet = &src[start_index..end_index];
764 let snippet = snippet.split(c).next().unwrap_or("").trim_end();
765 if !snippet.is_empty() && !snippet.contains('\n') {
766 sp.with_hi(BytePos(sp.lo().0 + snippet.len() as u32))
767 } else {
768 sp
769 }
770 });
771 match v {
772 Ok(v) => v,
773 Err(_) => sp,
774 }
775 }
776
777 pub fn span_through_char(&self, sp: Span, c: char) -> Span {
784 if sp.is_dummy() {
785 return sp;
786 }
787
788 if let Ok(snippet) = self.span_to_snippet(sp) {
789 if let Some(offset) = snippet.find(c) {
790 return sp.with_hi(BytePos(sp.lo().0 + (offset + c.len_utf8()) as u32));
791 }
792 }
793 sp
794 }
795
796 pub fn span_until_non_whitespace(&self, sp: Span) -> Span {
802 let mut whitespace_found = false;
803
804 self.span_take_while(sp, |c| {
805 if !whitespace_found && c.is_whitespace() {
806 whitespace_found = true;
807 }
808
809 !whitespace_found || c.is_whitespace()
810 })
811 }
812
813 pub fn span_until_whitespace(&self, sp: Span) -> Span {
819 self.span_take_while(sp, |c| !c.is_whitespace())
820 }
821
822 pub fn span_take_while<P>(&self, sp: Span, mut predicate: P) -> Span
824 where
825 P: for<'r> FnMut(&'r char) -> bool,
826 {
827 self.span_to_source(sp, |src, start_index, end_index| {
828 let snippet = &src[start_index..end_index];
829
830 let offset = snippet
831 .chars()
832 .take_while(&mut predicate)
833 .map(|c| c.len_utf8())
834 .sum::<usize>();
835
836 sp.with_hi(BytePos(sp.lo().0 + (offset as u32)))
837 })
838 .unwrap_or(sp)
839 }
840
841 pub fn def_span(&self, sp: Span) -> Span {
842 self.span_until_char(sp, '{')
843 }
844
845 pub fn start_point(&self, sp: Span) -> Span {
847 let pos = sp.lo().0;
848 let width = self.find_width_of_character_at_span(sp, false);
849 let corrected_start_position = pos.checked_add(width).unwrap_or(pos);
850 let end_point = BytePos(cmp::max(corrected_start_position, sp.lo().0));
851 sp.with_hi(end_point)
852 }
853
854 pub fn end_point(&self, sp: Span) -> Span {
856 let pos = sp.hi().0;
857
858 let width = self.find_width_of_character_at_span(sp, false);
859 let corrected_end_position = pos.checked_sub(width).unwrap_or(pos);
860
861 let end_point = BytePos(cmp::max(corrected_end_position, sp.lo().0));
862 sp.with_lo(end_point)
863 }
864
865 pub fn next_point(&self, sp: Span) -> Span {
868 let start_of_next_point = sp.hi().0;
869
870 let width = self.find_width_of_character_at_span(sp, true);
871 let end_of_next_point = start_of_next_point
876 .checked_add(width - 1)
877 .unwrap_or(start_of_next_point);
878
879 let end_of_next_point = BytePos(cmp::max(sp.lo().0 + 1, end_of_next_point));
880 Span::new(BytePos(start_of_next_point), end_of_next_point)
881 }
882
883 fn find_width_of_character_at_span(&self, sp: Span, forwards: bool) -> u32 {
886 if sp.lo() >= sp.hi() {
888 debug!("find_width_of_character_at_span: early return malformed span");
889 return 1;
890 }
891
892 let local_begin = self.lookup_byte_offset(sp.lo());
893 let local_end = self.lookup_byte_offset(sp.hi());
894 debug!(
895 "find_width_of_character_at_span: local_begin=`{:?}`, local_end=`{:?}`",
896 local_begin, local_end
897 );
898
899 let start_index = local_begin.pos.to_usize();
900 let end_index = local_end.pos.to_usize();
901 debug!(
902 "find_width_of_character_at_span: start_index=`{:?}`, end_index=`{:?}`",
903 start_index, end_index
904 );
905
906 if (!forwards && end_index == usize::MIN) || (forwards && start_index == usize::MAX) {
909 debug!("find_width_of_character_at_span: start or end of span, cannot be multibyte");
910 return 1;
911 }
912
913 let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
914 debug!(
915 "find_width_of_character_at_span: source_len=`{:?}`",
916 source_len
917 );
918 if start_index > end_index || end_index > source_len {
920 debug!("find_width_of_character_at_span: source indexes are malformed");
921 return 1;
922 }
923
924 let src = &local_begin.sf.src;
928 let snippet = {
929 let len = src.len();
930 &src[start_index..len]
931 };
932 debug!("find_width_of_character_at_span: snippet=`{:?}`", snippet);
933
934 let mut target = if forwards {
935 end_index + 1
936 } else {
937 end_index - 1
938 };
939 debug!(
940 "find_width_of_character_at_span: initial target=`{:?}`",
941 target
942 );
943
944 while !snippet.is_char_boundary(target - start_index) && target < source_len {
945 target = if forwards {
946 target + 1
947 } else {
948 match target.checked_sub(1) {
949 Some(target) => target,
950 None => {
951 break;
952 }
953 }
954 };
955 debug!("find_width_of_character_at_span: target=`{:?}`", target);
956 }
957 debug!(
958 "find_width_of_character_at_span: final target=`{:?}`",
959 target
960 );
961
962 if forwards {
963 (target - end_index) as u32
964 } else {
965 (end_index - target) as u32
966 }
967 }
968
969 pub fn get_source_file(&self, filename: &FileName) -> Option<Lrc<SourceFile>> {
970 for sf in self.files.borrow().source_files.iter() {
971 if *filename == *sf.name {
972 return Some(sf.clone());
973 }
974 }
975 None
976 }
977
978 pub fn lookup_byte_offset(&self, bpos: BytePos) -> SourceFileAndBytePos {
981 self.try_lookup_byte_offset(bpos).unwrap()
982 }
983
984 pub fn try_lookup_byte_offset(
987 &self,
988 bpos: BytePos,
989 ) -> Result<SourceFileAndBytePos, SourceMapLookupError> {
990 let sf = self.try_lookup_source_file(bpos)?.unwrap();
991 let offset = bpos - sf.start_pos;
992 Ok(SourceFileAndBytePos { sf, pos: offset })
993 }
994
995 fn bytepos_to_file_charpos(&self, bpos: BytePos) -> Result<CharPos, SourceMapLookupError> {
997 let map = self.try_lookup_source_file(bpos)?.unwrap();
998
999 Ok(self.bytepos_to_file_charpos_with(&map, bpos))
1000 }
1001
1002 fn bytepos_to_file_charpos_with(&self, map: &SourceFile, bpos: BytePos) -> CharPos {
1003 let total_extra_bytes = calc_utf16_offset(map, bpos, &mut Default::default());
1004 assert!(
1005 map.start_pos.to_u32() + total_extra_bytes <= bpos.to_u32(),
1006 "map.start_pos = {:?}; total_extra_bytes = {}; bpos = {:?}",
1007 map.start_pos,
1008 total_extra_bytes,
1009 bpos,
1010 );
1011 CharPos(bpos.to_usize() - map.start_pos.to_usize() - total_extra_bytes as usize)
1012 }
1013
1014 pub fn span_to_char_offset(&self, file: &SourceFile, span: Span) -> (u32, u32) {
1017 let start_offset = file.start_pos;
1019
1020 let mut state = ByteToCharPosState::default();
1021 let start =
1022 span.lo.to_u32() - start_offset.to_u32() - calc_utf16_offset(file, span.lo, &mut state);
1023 let end =
1024 span.hi.to_u32() - start_offset.to_u32() - calc_utf16_offset(file, span.hi, &mut state);
1025
1026 (start, end)
1027 }
1028
1029 #[doc(hidden)]
1034 pub fn lookup_source_file_in(
1035 files: &[Lrc<SourceFile>],
1036 pos: BytePos,
1037 ) -> Option<Lrc<SourceFile>> {
1038 if pos.is_dummy() {
1039 return None;
1040 }
1041
1042 let count = files.len();
1043
1044 let mut a = 0;
1046 let mut b = count;
1047 while b - a > 1 {
1048 let m = (a + b) / 2;
1049 if files[m].start_pos > pos {
1050 b = m;
1051 } else {
1052 a = m;
1053 }
1054 }
1055
1056 if a >= count {
1057 return None;
1058 }
1059
1060 Some(files[a].clone())
1061 }
1062
1063 #[doc(hidden)]
1067 pub fn lookup_source_file(&self, pos: BytePos) -> Lrc<SourceFile> {
1068 self.try_lookup_source_file(pos).unwrap().unwrap()
1069 }
1070
1071 #[doc(hidden)]
1075 pub fn try_lookup_source_file(
1076 &self,
1077 pos: BytePos,
1078 ) -> Result<Option<Lrc<SourceFile>>, SourceMapLookupError> {
1079 let files = self.files.borrow();
1080 let files = &files.source_files;
1081 let fm = Self::lookup_source_file_in(files, pos);
1082 match fm {
1083 Some(fm) => Ok(Some(fm)),
1084 None => Err(SourceMapLookupError::NoFileFor(pos)),
1085 }
1086 }
1087
1088 pub fn count_lines(&self) -> usize {
1089 self.files().iter().fold(0, |a, f| a + f.count_lines())
1090 }
1091
1092 pub fn generate_fn_name_span(&self, span: Span) -> Option<Span> {
1093 let prev_span = self.span_extend_to_prev_str(span, "fn", true);
1094 self.span_to_snippet(prev_span)
1095 .map(|snippet| {
1096 let len = snippet
1097 .find(|c: char| !c.is_alphanumeric() && c != '_')
1098 .expect("no label after fn");
1099 prev_span.with_hi(BytePos(prev_span.lo().0 + len as u32))
1100 })
1101 .ok()
1102 }
1103
1104 pub fn generate_local_type_param_snippet(&self, span: Span) -> Option<(Span, String)> {
1125 let sugg_span = self.span_extend_to_prev_str(span, "fn", false);
1128 if sugg_span != span {
1129 if let Ok(snippet) = self.span_to_snippet(sugg_span) {
1130 let mut offset = snippet
1132 .find(|c: char| !c.is_alphanumeric() && c != '_')
1133 .expect("no label after fn");
1134
1135 let mut bracket_counter = 0;
1137 let mut last_char = None;
1138 for c in snippet[offset..].chars() {
1139 match c {
1140 '<' => bracket_counter += 1,
1141 '>' => bracket_counter -= 1,
1142 '(' => {
1143 if bracket_counter == 0 {
1144 break;
1145 }
1146 }
1147 _ => {}
1148 }
1149 offset += c.len_utf8();
1150 last_char = Some(c);
1151 }
1152
1153 let sugg_span = sugg_span.with_hi(BytePos(sugg_span.lo().0 + offset as u32));
1155
1156 let mut new_snippet = if last_char == Some('>') {
1159 format!("{}, ", &snippet[..(offset - '>'.len_utf8())])
1160 } else {
1161 format!("{}<", &snippet[..offset])
1162 };
1163 new_snippet.push_str(
1164 &self
1165 .span_to_snippet(span)
1166 .unwrap_or_else(|_| "T".to_string()),
1167 );
1168 new_snippet.push('>');
1169
1170 return Some((sugg_span, new_snippet));
1171 }
1172 }
1173
1174 None
1175 }
1176
1177 #[allow(clippy::ptr_arg)]
1178 #[cfg(feature = "sourcemap")]
1179 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
1180 pub fn build_source_map(
1181 &self,
1182 mappings: &[(BytePos, LineCol)],
1183 orig: Option<swc_sourcemap::SourceMap>,
1184 config: impl SourceMapGenConfig,
1185 ) -> swc_sourcemap::SourceMap {
1186 build_source_map(self, mappings, orig, &config)
1187 }
1188}
1189
1190fn remove_bom(src: &mut BytesStr) {
1192 if src.starts_with('\u{feff}') {
1193 src.advance(3);
1194 }
1195}
1196
1197fn calc_utf16_offset(file: &SourceFile, bpos: BytePos, state: &mut ByteToCharPosState) -> u32 {
1200 let mut total_extra_bytes = state.total_extra_bytes;
1201 let mut index = state.mbc_index;
1202 let analysis = file.analyze();
1203 if bpos >= state.pos {
1204 let range = index..analysis.multibyte_chars.len();
1205 for i in range {
1206 let mbc = &analysis.multibyte_chars[i];
1207 debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
1208 if mbc.pos >= bpos {
1209 break;
1210 }
1211 total_extra_bytes += mbc.byte_to_char_diff() as u32;
1212 debug_assert!(
1215 bpos.to_u32() >= mbc.pos.to_u32() + mbc.bytes as u32,
1216 "bpos = {:?}, mbc.pos = {:?}, mbc.bytes = {:?}",
1217 bpos,
1218 mbc.pos,
1219 mbc.bytes
1220 );
1221 index += 1;
1222 }
1223 } else {
1224 let range = 0..index;
1225 for i in range.rev() {
1226 let mbc = &analysis.multibyte_chars[i];
1227 debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
1228 if mbc.pos < bpos {
1229 break;
1230 }
1231 total_extra_bytes -= mbc.byte_to_char_diff() as u32;
1232 debug_assert!(
1235 bpos.to_u32() <= mbc.pos.to_u32(),
1236 "bpos = {:?}, mbc.pos = {:?}",
1237 bpos,
1238 mbc.pos,
1239 );
1240 index -= 1;
1241 }
1242 }
1243
1244 state.pos = bpos;
1245 state.total_extra_bytes = total_extra_bytes;
1246 state.mbc_index = index;
1247
1248 total_extra_bytes
1249}
1250
1251pub trait Files {
1252 fn map_raw_pos(&self, raw_pos: BytePos) -> BytePos {
1258 raw_pos
1259 }
1260
1261 fn is_in_file(&self, f: &Lrc<SourceFile>, raw_pos: BytePos) -> bool {
1266 f.start_pos <= raw_pos && raw_pos < f.end_pos
1267 }
1268
1269 fn try_lookup_source_file(
1272 &self,
1273 raw_pos: BytePos,
1274 ) -> Result<Option<Lrc<SourceFile>>, SourceMapLookupError>;
1275}
1276
1277impl Files for SourceMap {
1278 fn try_lookup_source_file(
1279 &self,
1280 pos: BytePos,
1281 ) -> Result<Option<Lrc<SourceFile>>, SourceMapLookupError> {
1282 self.try_lookup_source_file(pos)
1283 }
1284}
1285
1286#[allow(clippy::ptr_arg)]
1287#[cfg(feature = "sourcemap")]
1288#[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
1289pub fn build_source_map(
1290 files: &impl Files,
1291 mappings: &[(BytePos, LineCol)],
1292 orig: Option<swc_sourcemap::SourceMap>,
1293 config: &impl SourceMapGenConfig,
1294) -> swc_sourcemap::SourceMap {
1295 let mut builder = SourceMapBuilder::new(None);
1296
1297 let mut src_id = 0u32;
1298
1299 let mut cur_file: Option<Lrc<SourceFile>> = None;
1303
1304 let mut prev_dst_line = u32::MAX;
1305
1306 let mut ch_state = ByteToCharPosState::default();
1307 let mut line_state = ByteToCharPosState::default();
1308
1309 for (raw_pos, lc) in mappings.iter() {
1310 let pos = files.map_raw_pos(*raw_pos);
1311
1312 if pos.is_reserved_for_comments() {
1313 continue;
1314 }
1315
1316 let lc = *lc;
1317
1318 if lc.line == 0 && lc.col == 0 && pos.is_dummy() {
1321 continue;
1322 }
1323
1324 if pos == BytePos(u32::MAX) {
1325 builder.add_raw(lc.line, lc.col, 0, 0, Some(src_id), None, false);
1326 continue;
1327 }
1328
1329 let f;
1330 let f = match cur_file {
1331 Some(ref f) if files.is_in_file(f, *raw_pos) => f,
1332 _ => {
1333 let source_file = files.try_lookup_source_file(*raw_pos).unwrap();
1334 if let Some(source_file) = source_file {
1335 f = source_file;
1336 } else {
1337 continue;
1338 }
1339 if config.skip(&f.name) {
1340 continue;
1341 }
1342 src_id = builder.add_source(config.file_name_to_source(&f.name).into());
1343 if orig.is_none() && config.ignore_list(&f.name) {
1345 builder.add_to_ignore_list(src_id);
1346 }
1347
1348 let inline_sources_content =
1350 orig.is_none() && config.inline_sources_content(&f.name);
1351 if inline_sources_content {
1352 builder.set_source_contents(src_id, Some(f.src.clone()));
1353 }
1354
1355 ch_state = ByteToCharPosState::default();
1356 line_state = ByteToCharPosState::default();
1357
1358 cur_file = Some(f.clone());
1359 &f
1360 }
1361 };
1362 if config.skip(&f.name) {
1363 continue;
1364 }
1365
1366 let emit_columns = config.emit_columns(&f.name);
1367
1368 if !emit_columns && lc.line == prev_dst_line {
1369 continue;
1370 }
1371
1372 let line = match f.lookup_line(pos) {
1373 Some(line) => line as u32,
1374 None => continue,
1375 };
1376
1377 let analysis = f.analyze();
1378 let linebpos = analysis.lines[line as usize];
1379 debug_assert!(
1380 pos >= linebpos,
1381 "{}: bpos = {:?}; linebpos = {:?};",
1382 f.name,
1383 pos,
1384 linebpos,
1385 );
1386
1387 let linechpos = linebpos.to_u32() - calc_utf16_offset(f, linebpos, &mut line_state);
1388 let chpos = pos.to_u32() - calc_utf16_offset(f, pos, &mut ch_state);
1389
1390 debug_assert!(
1391 chpos >= linechpos,
1392 "{}: chpos = {:?}; linechpos = {:?};",
1393 f.name,
1394 chpos,
1395 linechpos,
1396 );
1397
1398 let col = chpos - linechpos;
1399 let name = None;
1400
1401 let name_idx = if orig.is_none() {
1402 name.or_else(|| config.name_for_bytepos(pos)).map(|name| {
1403 builder.add_name(unsafe {
1404 BytesStr::from_utf8_slice_unchecked(name.as_bytes())
1406 })
1407 })
1408 } else {
1409 None
1411 };
1412
1413 builder.add_raw(lc.line, lc.col, line, col, Some(src_id), name_idx, false);
1414 prev_dst_line = lc.line;
1415 }
1416
1417 let map = builder.into_sourcemap();
1418
1419 if let Some(mut orig) = orig {
1420 orig.adjust_mappings(&map);
1421 return orig;
1422 }
1423
1424 map
1425}
1426
1427impl SourceMapper for SourceMap {
1428 fn lookup_char_pos(&self, pos: BytePos) -> Loc {
1429 self.lookup_char_pos(pos)
1430 }
1431
1432 fn span_to_lines(&self, sp: Span) -> FileLinesResult {
1433 self.span_to_lines(sp)
1434 }
1435
1436 fn span_to_string(&self, sp: Span) -> String {
1437 self.span_to_string(sp)
1438 }
1439
1440 fn span_to_filename(&self, sp: Span) -> Lrc<FileName> {
1441 self.span_to_filename(sp)
1442 }
1443
1444 fn span_to_snippet(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
1446 self.span_to_source(sp, |src, start_index, end_index| {
1447 src[start_index..end_index].to_string()
1448 })
1449 }
1450
1451 fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
1452 self.merge_spans(sp_lhs, sp_rhs)
1453 }
1454
1455 fn call_span_if_macro(&self, sp: Span) -> Span {
1456 sp
1457 }
1458
1459 fn doctest_offset_line(&self, line: usize) -> usize {
1460 self.doctest_offset_line(line)
1461 }
1462}
1463
1464#[derive(Clone, Default)]
1465pub struct FilePathMapping {
1466 mapping: Vec<(PathBuf, PathBuf)>,
1467}
1468
1469impl FilePathMapping {
1470 pub fn empty() -> FilePathMapping {
1471 FilePathMapping {
1472 mapping: Vec::new(),
1473 }
1474 }
1475
1476 pub fn new(mapping: Vec<(PathBuf, PathBuf)>) -> FilePathMapping {
1477 FilePathMapping { mapping }
1478 }
1479
1480 pub fn map_prefix(&self, path: &Path) -> (PathBuf, bool) {
1484 for (from, to) in self.mapping.iter().rev() {
1488 if let Ok(rest) = path.strip_prefix(from) {
1489 return (to.join(rest), true);
1490 }
1491 }
1492
1493 (path.to_path_buf(), false)
1494 }
1495}
1496
1497pub trait SourceMapGenConfig {
1498 fn file_name_to_source(&self, f: &FileName) -> String;
1504
1505 fn name_for_bytepos(&self, _bpos: BytePos) -> Option<&str> {
1507 None
1508 }
1509
1510 fn inline_sources_content(&self, f: &FileName) -> bool {
1512 !matches!(
1513 f,
1514 FileName::Real(..) | FileName::Custom(..) | FileName::Url(..)
1515 )
1516 }
1517
1518 fn emit_columns(&self, _f: &FileName) -> bool {
1520 true
1521 }
1522
1523 fn skip(&self, f: &FileName) -> bool {
1525 matches!(f, FileName::Internal(..))
1526 }
1527
1528 fn ignore_list(&self, f: &FileName) -> bool {
1544 matches!(f, FileName::Anon | FileName::Internal(..))
1545 }
1546}
1547
1548#[derive(Debug, Clone)]
1549pub struct DefaultSourceMapGenConfig;
1550
1551macro_rules! impl_ref {
1552 ($TP:ident, $T:ty) => {
1553 impl<$TP> SourceMapGenConfig for $T
1554 where
1555 $TP: SourceMapGenConfig,
1556 {
1557 fn file_name_to_source(&self, f: &FileName) -> String {
1558 (**self).file_name_to_source(f)
1559 }
1560 }
1561 };
1562}
1563
1564impl_ref!(T, &'_ T);
1565impl_ref!(T, Box<T>);
1566impl_ref!(T, std::rc::Rc<T>);
1567impl_ref!(T, std::sync::Arc<T>);
1568
1569impl SourceMapGenConfig for DefaultSourceMapGenConfig {
1570 fn file_name_to_source(&self, f: &FileName) -> String {
1571 f.to_string()
1572 }
1573}
1574
1575#[derive(Debug, Clone, Default)]
1577pub struct ByteToCharPosState {
1578 pos: BytePos,
1580
1581 total_extra_bytes: u32,
1583
1584 mbc_index: usize,
1587}
1588
1589#[cfg(test)]
1594mod tests {
1595 use super::*;
1596
1597 fn init_source_map() -> SourceMap {
1598 let sm = SourceMap::new(FilePathMapping::empty());
1599 sm.new_source_file(
1600 Lrc::new(PathBuf::from("blork.rs").into()),
1601 "first line.\nsecond line",
1602 );
1603 sm.new_source_file(Lrc::new(PathBuf::from("empty.rs").into()), BytesStr::new());
1604 sm.new_source_file(
1605 Lrc::new(PathBuf::from("blork2.rs").into()),
1606 "first line.\nsecond line",
1607 );
1608 sm
1609 }
1610
1611 #[test]
1612 fn t3() {
1613 let sm = init_source_map();
1615
1616 let srcfbp1 = sm.lookup_byte_offset(BytePos(24));
1617 assert_eq!(*srcfbp1.sf.name, PathBuf::from("blork.rs").into());
1618 assert_eq!(srcfbp1.pos, BytePos(23));
1619
1620 let srcfbp1 = sm.lookup_byte_offset(BytePos(25));
1621 assert_eq!(*srcfbp1.sf.name, PathBuf::from("empty.rs").into());
1622 assert_eq!(srcfbp1.pos, BytePos(0));
1623
1624 let srcfbp2 = sm.lookup_byte_offset(BytePos(26));
1625 assert_eq!(*srcfbp2.sf.name, PathBuf::from("blork2.rs").into());
1626 assert_eq!(srcfbp2.pos, BytePos(0));
1627 }
1628
1629 #[test]
1630 fn t4() {
1631 let sm = init_source_map();
1633
1634 let cp1 = sm.bytepos_to_file_charpos(BytePos(23)).unwrap();
1635 assert_eq!(cp1, CharPos(22));
1636
1637 let cp2 = sm.bytepos_to_file_charpos(BytePos(26)).unwrap();
1638 assert_eq!(cp2, CharPos(0));
1639 }
1640
1641 #[test]
1642 fn t5() {
1643 let sm = init_source_map();
1645
1646 let loc1 = sm.lookup_char_pos(BytePos(23));
1647 assert_eq!(*loc1.file.name, PathBuf::from("blork.rs").into());
1648 assert_eq!(loc1.line, 2);
1649 assert_eq!(loc1.col, CharPos(10));
1650
1651 let loc2 = sm.lookup_char_pos(BytePos(26));
1652 assert_eq!(*loc2.file.name, PathBuf::from("blork2.rs").into());
1653 assert_eq!(loc2.line, 1);
1654 assert_eq!(loc2.col, CharPos(0));
1655 }
1656
1657 fn init_source_map_mbc() -> SourceMap {
1658 let sm = SourceMap::new(FilePathMapping::empty());
1659 sm.new_source_file(
1661 Lrc::new(PathBuf::from("blork.rs").into()),
1662 "fir€st €€€€ line.\nsecond line",
1663 );
1664 sm.new_source_file(
1665 Lrc::new(PathBuf::from("blork2.rs").into()),
1666 "first line€€.\n€ second line",
1667 );
1668 sm
1669 }
1670
1671 #[test]
1672 fn t6() {
1673 let sm = init_source_map_mbc();
1675
1676 let cp1 = sm.bytepos_to_file_charpos(BytePos(4)).unwrap();
1677 assert_eq!(cp1, CharPos(3));
1678
1679 let cp2 = sm.bytepos_to_file_charpos(BytePos(7)).unwrap();
1680 assert_eq!(cp2, CharPos(4));
1681
1682 let cp3 = sm.bytepos_to_file_charpos(BytePos(57)).unwrap();
1683 assert_eq!(cp3, CharPos(12));
1684
1685 let cp4 = sm.bytepos_to_file_charpos(BytePos(62)).unwrap();
1686 assert_eq!(cp4, CharPos(15));
1687 }
1688
1689 #[test]
1690 fn t7() {
1691 let sm = init_source_map();
1693 let span = Span::new(BytePos(13), BytePos(24));
1694 let file_lines = sm.span_to_lines(span).unwrap();
1695
1696 assert_eq!(*file_lines.file.name, PathBuf::from("blork.rs").into());
1697 assert_eq!(file_lines.lines.len(), 1);
1698 assert_eq!(file_lines.lines[0].line_index, 1);
1699 }
1700
1701 fn span_from_selection(input: &str, selection: &str) -> Span {
1706 assert_eq!(input.len(), selection.len());
1707 let left_index = (selection.find('~').unwrap() + 1) as u32;
1709 let right_index = selection
1710 .rfind('~')
1711 .map(|x| {
1712 (x + 1) as u32
1714 })
1715 .unwrap_or(left_index);
1716 Span::new(BytePos(left_index), BytePos(right_index + 1))
1717 }
1718
1719 #[test]
1722 fn span_to_snippet_and_lines_spanning_multiple_lines() {
1723 let sm = SourceMap::new(FilePathMapping::empty());
1724 let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
1725 let selection = " \n ~~\n~~~\n~~~~~ \n \n";
1726 sm.new_source_file(
1727 Lrc::new(Path::new("blork.rs").to_path_buf().into()),
1728 inputtext,
1729 );
1730 let span = span_from_selection(inputtext, selection);
1731
1732 assert_eq!(&sm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD");
1734
1735 let lines = sm.span_to_lines(span).unwrap();
1738 let expected = vec![
1739 LineInfo {
1740 line_index: 1,
1741 start_col: CharPos(4),
1742 end_col: CharPos(6),
1743 },
1744 LineInfo {
1745 line_index: 2,
1746 start_col: CharPos(0),
1747 end_col: CharPos(3),
1748 },
1749 LineInfo {
1750 line_index: 3,
1751 start_col: CharPos(0),
1752 end_col: CharPos(5),
1753 },
1754 ];
1755 assert_eq!(lines.lines, expected);
1756 }
1757
1758 #[test]
1759 fn t8() {
1760 let sm = init_source_map();
1762 let span = Span::new(BytePos(13), BytePos(24));
1763 let snippet = sm.span_to_snippet(span);
1764
1765 assert_eq!(snippet, Ok("second line".to_string()));
1766 }
1767
1768 #[test]
1769 fn t9() {
1770 let sm = init_source_map();
1772 let span = Span::new(BytePos(13), BytePos(24));
1773 let sstr = sm.span_to_string(span);
1774
1775 assert_eq!(sstr, "blork.rs:2:1: 2:12");
1776 }
1777
1778 #[test]
1779 fn t10() {
1780 let sm = SourceMap::new(FilePathMapping::empty());
1782 sm.new_source_file(Lrc::new(PathBuf::from("blork.rs").into()), "");
1783 let span = Span::new(BytePos(1), BytePos(1));
1784 let file_lines = sm.span_to_lines(span).unwrap();
1785
1786 assert_eq!(*file_lines.file.name, PathBuf::from("blork.rs").into());
1787 assert_eq!(file_lines.lines.len(), 0);
1788 }
1789
1790 #[test]
1792 fn span_merging_fail() {
1793 let sm = SourceMap::new(FilePathMapping::empty());
1794 let inputtext = "bbbb BB\ncc CCC\n";
1795 let selection1 = " ~~\n \n";
1796 let selection2 = " \n ~~~\n";
1797 sm.new_source_file(Lrc::new(Path::new("blork.rs").to_owned().into()), inputtext);
1798 let span1 = span_from_selection(inputtext, selection1);
1799 let span2 = span_from_selection(inputtext, selection2);
1800
1801 assert!(sm.merge_spans(span1, span2).is_none());
1802 }
1803
1804 #[test]
1805 fn test_calc_utf16_offset() {
1806 let input = "t¢e∆s💩t";
1807 let sm = SourceMap::new(FilePathMapping::empty());
1808 let file = sm.new_source_file(Lrc::new(PathBuf::from("blork.rs").into()), input);
1809
1810 let mut state = ByteToCharPosState::default();
1811 let mut bpos = file.start_pos;
1812 let mut cpos = CharPos(bpos.to_usize());
1813 for c in input.chars() {
1814 let actual = bpos.to_u32() - calc_utf16_offset(&file, bpos, &mut state);
1815
1816 assert_eq!(actual, cpos.to_u32());
1817
1818 bpos = bpos + BytePos(c.len_utf8() as u32);
1819 cpos = cpos + CharPos(c.len_utf16());
1820 }
1821
1822 for c in input.chars().rev() {
1823 bpos = bpos - BytePos(c.len_utf8() as u32);
1824 cpos = cpos - CharPos(c.len_utf16());
1825
1826 let actual = bpos.to_u32() - calc_utf16_offset(&file, bpos, &mut state);
1827
1828 assert_eq!(actual, cpos.to_u32());
1829 }
1830 }
1831
1832 #[test]
1833 fn bytepos_to_charpos() {
1834 let input = "t¢e∆s💩t";
1835 let sm = SourceMap::new(FilePathMapping::empty());
1836 let file = sm.new_source_file(Lrc::new(PathBuf::from("blork.rs").into()), input);
1837
1838 let mut bpos = file.start_pos;
1839 let mut cpos = CharPos(0);
1840 for c in input.chars() {
1841 let actual = sm.bytepos_to_file_charpos_with(&file, bpos);
1842
1843 assert_eq!(actual, cpos);
1844
1845 bpos = bpos + BytePos(c.len_utf8() as u32);
1846 cpos = cpos + CharPos(c.len_utf16());
1847 }
1848 }
1849}