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 #[cfg(debug_assertions)]
361 debug!(
362 "byte pos {:?} is on the line at byte pos {:?}",
363 pos, linebpos
364 );
365 #[cfg(debug_assertions)]
366 debug!(
367 "char pos {:?} is on the line at char pos {:?}",
368 chpos, linechpos
369 );
370 #[cfg(debug_assertions)]
371 debug!("byte is on line: {}", line);
372 }
373 Ok(Loc {
375 file: f,
376 line,
377 col,
378 col_display,
379 })
380 }
381 Err(f) => {
382 let analysis = f.analyze();
383 let chpos = self.bytepos_to_file_charpos(pos)?;
384
385 let col_display = {
386 let end_width_idx = analysis
387 .non_narrow_chars
388 .binary_search_by_key(&pos, |x| x.pos())
389 .unwrap_or_else(|x| x);
390 let non_narrow: usize = analysis.non_narrow_chars[0..end_width_idx]
391 .iter()
392 .map(|x| x.width())
393 .sum();
394 chpos.0 - end_width_idx + non_narrow
395 };
396 Ok(Loc {
397 file: f,
398 line: 0,
399 col: chpos,
400 col_display,
401 })
402 }
403 }
404 }
405
406 pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Lrc<SourceFile>> {
408 let f = self.try_lookup_source_file(pos).unwrap().unwrap();
409
410 self.lookup_line_with(f, pos)
411 }
412
413 #[doc(hidden)]
418 pub fn lookup_line_with(
419 &self,
420 f: Lrc<SourceFile>,
421 pos: BytePos,
422 ) -> Result<SourceFileAndLine, Lrc<SourceFile>> {
423 match f.lookup_line(pos) {
424 Some(line) => Ok(SourceFileAndLine { sf: f, line }),
425 None => Err(f),
426 }
427 }
428
429 pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt {
430 let loc = self.lookup_char_pos(pos);
431 LocWithOpt {
432 filename: loc.file.name.clone(),
433 line: loc.line,
434 col: loc.col,
435 file: Some(loc.file),
436 }
437 }
438
439 pub fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
448 let lhs_end = match self.lookup_line(sp_lhs.hi()) {
449 Ok(x) => x,
450 Err(_) => return None,
451 };
452 let rhs_begin = match self.lookup_line(sp_rhs.lo()) {
453 Ok(x) => x,
454 Err(_) => return None,
455 };
456
457 if lhs_end.line != rhs_begin.line {
459 return None;
460 }
461
462 if (sp_lhs.lo() <= sp_rhs.lo()) && (sp_lhs.hi() <= sp_rhs.lo()) {
464 Some(sp_lhs.to(sp_rhs))
465 } else {
466 None
467 }
468 }
469
470 pub fn span_to_string(&self, sp: Span) -> String {
471 if self.files.borrow().source_files.is_empty() && sp.is_dummy() {
472 return "no-location".to_string();
473 }
474
475 let lo = self.lookup_char_pos_adj(sp.lo());
476 let hi = self.lookup_char_pos_adj(sp.hi());
477 format!(
478 "{}:{}:{}: {}:{}",
479 lo.filename,
480 lo.line,
481 lo.col.to_usize() + 1,
482 hi.line,
483 hi.col.to_usize() + 1
484 )
485 }
486
487 pub fn span_to_filename(&self, sp: Span) -> Lrc<FileName> {
488 self.lookup_char_pos(sp.lo()).file.name.clone()
489 }
490
491 pub fn span_to_unmapped_path(&self, sp: Span) -> Lrc<FileName> {
492 self.lookup_char_pos(sp.lo())
493 .file
494 .unmapped_path
495 .clone()
496 .expect("SourceMap::span_to_unmapped_path called for imported SourceFile?")
497 }
498
499 pub fn is_multiline(&self, sp: Span) -> bool {
500 let lo = self.lookup_char_pos(sp.lo());
501 let hi = self.lookup_char_pos(sp.hi());
502 lo.line != hi.line
503 }
504
505 pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
506 if cfg!(feature = "debug") {
507 #[cfg(debug_assertions)]
508 debug!("span_to_lines(sp={:?})", sp);
509 }
510
511 if sp.lo() > sp.hi() {
512 return Err(Box::new(SpanLinesError::IllFormedSpan(sp)));
513 }
514
515 let lo = self.lookup_char_pos(sp.lo());
516 if cfg!(feature = "debug") {
517 #[cfg(debug_assertions)]
518 debug!("span_to_lines: lo={:?}", lo);
519 }
520 let hi = self.lookup_char_pos(sp.hi());
521 if cfg!(feature = "debug") {
522 #[cfg(debug_assertions)]
523 debug!("span_to_lines: hi={:?}", hi);
524 }
525
526 if lo.file.start_pos != hi.file.start_pos {
527 return Err(Box::new(SpanLinesError::DistinctSources(DistinctSources {
528 begin: FilePos(lo.file.name.clone(), lo.file.start_pos),
529 end: FilePos(hi.file.name.clone(), hi.file.start_pos),
530 })));
531 }
532 assert!(hi.line >= lo.line);
533
534 if lo.file.src.is_empty() {
536 return Ok(FileLines {
537 file: lo.file,
538 lines: Vec::new(),
539 });
540 }
541
542 let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
543
544 let mut start_col = lo.col;
547
548 for line_index in lo.line - 1..hi.line - 1 {
553 let line_len = lo
554 .file
555 .get_line(line_index)
556 .map(|s| s.chars().count())
557 .unwrap_or(0);
558 lines.push(LineInfo {
559 line_index,
560 start_col,
561 end_col: CharPos::from_usize(line_len),
562 });
563 start_col = CharPos::from_usize(0);
564 }
565
566 lines.push(LineInfo {
568 line_index: hi.line - 1,
569 start_col,
570 end_col: hi.col,
571 });
572
573 Ok(FileLines {
574 file: lo.file,
575 lines,
576 })
577 }
578
579 fn span_to_source<F, Ret>(
585 &self,
586 sp: Span,
587 extract_source: F,
588 ) -> Result<Ret, Box<SpanSnippetError>>
589 where
590 F: FnOnce(&str, usize, usize) -> Ret,
591 {
592 if sp.lo() > sp.hi() {
593 return Err(Box::new(SpanSnippetError::IllFormedSpan(sp)));
594 }
595 if sp.lo.is_dummy() || sp.hi.is_dummy() {
596 return Err(Box::new(SpanSnippetError::DummyBytePos));
597 }
598
599 let local_begin = self.try_lookup_byte_offset(sp.lo())?;
600 let local_end = self.try_lookup_byte_offset(sp.hi())?;
601
602 if local_begin.sf.start_pos != local_end.sf.start_pos {
603 Err(Box::new(SpanSnippetError::DistinctSources(
604 DistinctSources {
605 begin: FilePos(local_begin.sf.name.clone(), local_begin.sf.start_pos),
606 end: FilePos(local_end.sf.name.clone(), local_end.sf.start_pos),
607 },
608 )))
609 } else {
610 let start_index = local_begin.pos.to_usize();
611 let end_index = local_end.pos.to_usize();
612 let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
613
614 if start_index > end_index || end_index > source_len {
615 return Err(Box::new(SpanSnippetError::MalformedForSourcemap(
616 MalformedSourceMapPositions {
617 name: local_begin.sf.name.clone(),
618 source_len,
619 begin_pos: local_begin.pos,
620 end_pos: local_end.pos,
621 },
622 )));
623 }
624
625 let src = &local_begin.sf.src;
626 Ok(extract_source(src, start_index, end_index))
627 }
628 }
629
630 pub fn with_snippet_of_span<F, Ret>(
632 &self,
633 sp: Span,
634 op: F,
635 ) -> Result<Ret, Box<SpanSnippetError>>
636 where
637 F: FnOnce(&str) -> Ret,
638 {
639 self.span_to_source(sp, |src, start_index, end_index| {
640 op(&src[start_index..end_index])
641 })
642 }
643
644 pub fn span_to_margin(&self, sp: Span) -> Option<usize> {
645 match self.span_to_prev_source(sp) {
646 Err(_) => None,
647 Ok(source) => source
648 .split('\n')
649 .next_back()
650 .map(|last_line| last_line.len() - last_line.trim_start().len()),
651 }
652 }
653
654 pub fn with_span_to_prev_source<F, Ret>(
656 &self,
657 sp: Span,
658 op: F,
659 ) -> Result<Ret, Box<SpanSnippetError>>
660 where
661 F: FnOnce(&str) -> Ret,
662 {
663 self.span_to_source(sp, |src, start_index, _| op(&src[..start_index]))
664 }
665
666 pub fn span_to_prev_source(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
668 self.with_span_to_prev_source(sp, |s| s.to_string())
669 }
670
671 pub fn with_span_to_next_source<F, Ret>(
673 &self,
674 sp: Span,
675 op: F,
676 ) -> Result<Ret, Box<SpanSnippetError>>
677 where
678 F: FnOnce(&str) -> Ret,
679 {
680 self.span_to_source(sp, |src, _, end_index| op(&src[end_index..]))
681 }
682
683 pub fn span_to_next_source(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
685 self.with_span_to_next_source(sp, |s| s.to_string())
686 }
687
688 pub fn span_extend_to_prev_char(&self, sp: Span, c: char) -> Span {
692 if let Ok(prev_source) = self.span_to_prev_source(sp) {
693 let prev_source = prev_source.rsplit(c).next().unwrap_or("").trim_start();
694 if !prev_source.is_empty() && !prev_source.contains('\n') {
695 return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
696 }
697 }
698
699 sp
700 }
701
702 pub fn span_extend_to_prev_str(&self, sp: Span, pat: &str, accept_newlines: bool) -> Span {
707 for ws in &[" ", "\t", "\n"] {
712 let pat = pat.to_owned() + ws;
713 if let Ok(prev_source) = self.span_to_prev_source(sp) {
714 let prev_source = prev_source.rsplit(&pat).next().unwrap_or("").trim_start();
715 if !prev_source.is_empty() && (!prev_source.contains('\n') || accept_newlines) {
716 return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
717 }
718 }
719 }
720
721 sp
722 }
723
724 pub fn span_extend_to_next_char(&self, sp: Span, c: char) -> Span {
728 if let Ok(next_source) = self.span_to_next_source(sp) {
729 let next_source = next_source.split(c).next().unwrap_or("").trim_end();
730 if !next_source.is_empty() && !next_source.contains('\n') {
731 return sp.with_hi(BytePos(sp.hi().0 + next_source.len() as u32));
732 }
733 }
734
735 sp
736 }
737
738 pub fn span_extend_to_next_str(&self, sp: Span, pat: &str, accept_newlines: bool) -> Span {
743 for ws in &[" ", "\t", "\n"] {
744 let pat = pat.to_owned() + ws;
745 if let Ok(next_source) = self.span_to_next_source(sp) {
746 let next_source = next_source.split(&pat).next().unwrap_or("").trim_end();
747 if !next_source.is_empty() && (!next_source.contains('\n') || accept_newlines) {
748 return sp.with_hi(BytePos(sp.hi().0 + next_source.len() as u32));
749 }
750 }
751 }
752
753 sp
754 }
755
756 pub fn span_until_char(&self, sp: Span, c: char) -> Span {
764 if sp.is_dummy() {
765 return sp;
766 }
767
768 let v = self.span_to_source(sp, |src, start_index, end_index| {
769 let snippet = &src[start_index..end_index];
770 let snippet = snippet.split(c).next().unwrap_or("").trim_end();
771 if !snippet.is_empty() && !snippet.contains('\n') {
772 sp.with_hi(BytePos(sp.lo().0 + snippet.len() as u32))
773 } else {
774 sp
775 }
776 });
777 match v {
778 Ok(v) => v,
779 Err(_) => sp,
780 }
781 }
782
783 pub fn span_through_char(&self, sp: Span, c: char) -> Span {
790 if sp.is_dummy() {
791 return sp;
792 }
793
794 if let Ok(snippet) = self.span_to_snippet(sp) {
795 if let Some(offset) = snippet.find(c) {
796 return sp.with_hi(BytePos(sp.lo().0 + (offset + c.len_utf8()) as u32));
797 }
798 }
799 sp
800 }
801
802 pub fn span_until_non_whitespace(&self, sp: Span) -> Span {
808 let mut whitespace_found = false;
809
810 self.span_take_while(sp, |c| {
811 if !whitespace_found && c.is_whitespace() {
812 whitespace_found = true;
813 }
814
815 !whitespace_found || c.is_whitespace()
816 })
817 }
818
819 pub fn span_until_whitespace(&self, sp: Span) -> Span {
825 self.span_take_while(sp, |c| !c.is_whitespace())
826 }
827
828 pub fn span_take_while<P>(&self, sp: Span, mut predicate: P) -> Span
830 where
831 P: for<'r> FnMut(&'r char) -> bool,
832 {
833 self.span_to_source(sp, |src, start_index, end_index| {
834 let snippet = &src[start_index..end_index];
835
836 let offset = snippet
837 .chars()
838 .take_while(&mut predicate)
839 .map(|c| c.len_utf8())
840 .sum::<usize>();
841
842 sp.with_hi(BytePos(sp.lo().0 + (offset as u32)))
843 })
844 .unwrap_or(sp)
845 }
846
847 pub fn def_span(&self, sp: Span) -> Span {
848 self.span_until_char(sp, '{')
849 }
850
851 pub fn start_point(&self, sp: Span) -> Span {
853 let pos = sp.lo().0;
854 let width = self.find_width_of_character_at_span(sp, false);
855 let corrected_start_position = pos.checked_add(width).unwrap_or(pos);
856 let end_point = BytePos(cmp::max(corrected_start_position, sp.lo().0));
857 sp.with_hi(end_point)
858 }
859
860 pub fn end_point(&self, sp: Span) -> Span {
862 let pos = sp.hi().0;
863
864 let width = self.find_width_of_character_at_span(sp, false);
865 let corrected_end_position = pos.checked_sub(width).unwrap_or(pos);
866
867 let end_point = BytePos(cmp::max(corrected_end_position, sp.lo().0));
868 sp.with_lo(end_point)
869 }
870
871 pub fn next_point(&self, sp: Span) -> Span {
874 let start_of_next_point = sp.hi().0;
875
876 let width = self.find_width_of_character_at_span(sp, true);
877 let end_of_next_point = start_of_next_point
882 .checked_add(width - 1)
883 .unwrap_or(start_of_next_point);
884
885 let end_of_next_point = BytePos(cmp::max(sp.lo().0 + 1, end_of_next_point));
886 Span::new(BytePos(start_of_next_point), end_of_next_point)
887 }
888
889 fn find_width_of_character_at_span(&self, sp: Span, forwards: bool) -> u32 {
892 if sp.lo() >= sp.hi() {
894 #[cfg(debug_assertions)]
895 debug!("find_width_of_character_at_span: early return malformed span");
896 return 1;
897 }
898
899 let local_begin = self.lookup_byte_offset(sp.lo());
900 let local_end = self.lookup_byte_offset(sp.hi());
901 #[cfg(debug_assertions)]
902 debug!(
903 "find_width_of_character_at_span: local_begin=`{:?}`, local_end=`{:?}`",
904 local_begin, local_end
905 );
906
907 let start_index = local_begin.pos.to_usize();
908 let end_index = local_end.pos.to_usize();
909 #[cfg(debug_assertions)]
910 debug!(
911 "find_width_of_character_at_span: start_index=`{:?}`, end_index=`{:?}`",
912 start_index, end_index
913 );
914
915 if (!forwards && end_index == usize::MIN) || (forwards && start_index == usize::MAX) {
918 #[cfg(debug_assertions)]
919 debug!("find_width_of_character_at_span: start or end of span, cannot be multibyte");
920 return 1;
921 }
922
923 let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
924 #[cfg(debug_assertions)]
925 debug!(
926 "find_width_of_character_at_span: source_len=`{:?}`",
927 source_len
928 );
929 if start_index > end_index || end_index > source_len {
931 #[cfg(debug_assertions)]
932 debug!("find_width_of_character_at_span: source indexes are malformed");
933 return 1;
934 }
935
936 let src = &local_begin.sf.src;
940 let snippet = {
941 let len = src.len();
942 &src[start_index..len]
943 };
944 #[cfg(debug_assertions)]
945 debug!("find_width_of_character_at_span: snippet=`{:?}`", snippet);
946
947 let mut target = if forwards {
948 end_index + 1
949 } else {
950 end_index - 1
951 };
952 #[cfg(debug_assertions)]
953 debug!(
954 "find_width_of_character_at_span: initial target=`{:?}`",
955 target
956 );
957
958 while !snippet.is_char_boundary(target - start_index) && target < source_len {
959 target = if forwards {
960 target + 1
961 } else {
962 match target.checked_sub(1) {
963 Some(target) => target,
964 None => {
965 break;
966 }
967 }
968 };
969 #[cfg(debug_assertions)]
970 debug!("find_width_of_character_at_span: target=`{:?}`", target);
971 }
972 #[cfg(debug_assertions)]
973 debug!(
974 "find_width_of_character_at_span: final target=`{:?}`",
975 target
976 );
977
978 if forwards {
979 (target - end_index) as u32
980 } else {
981 (end_index - target) as u32
982 }
983 }
984
985 pub fn get_source_file(&self, filename: &FileName) -> Option<Lrc<SourceFile>> {
986 for sf in self.files.borrow().source_files.iter() {
987 if *filename == *sf.name {
988 return Some(sf.clone());
989 }
990 }
991 None
992 }
993
994 pub fn lookup_byte_offset(&self, bpos: BytePos) -> SourceFileAndBytePos {
997 self.try_lookup_byte_offset(bpos).unwrap()
998 }
999
1000 pub fn try_lookup_byte_offset(
1003 &self,
1004 bpos: BytePos,
1005 ) -> Result<SourceFileAndBytePos, SourceMapLookupError> {
1006 let sf = self.try_lookup_source_file(bpos)?.unwrap();
1007 let offset = bpos - sf.start_pos;
1008 Ok(SourceFileAndBytePos { sf, pos: offset })
1009 }
1010
1011 fn bytepos_to_file_charpos(&self, bpos: BytePos) -> Result<CharPos, SourceMapLookupError> {
1013 let map = self.try_lookup_source_file(bpos)?.unwrap();
1014
1015 Ok(self.bytepos_to_file_charpos_with(&map, bpos))
1016 }
1017
1018 fn bytepos_to_file_charpos_with(&self, map: &SourceFile, bpos: BytePos) -> CharPos {
1019 let total_extra_bytes = calc_utf16_offset(map, bpos, &mut Default::default());
1020 assert!(
1021 map.start_pos.to_u32() + total_extra_bytes <= bpos.to_u32(),
1022 "map.start_pos = {:?}; total_extra_bytes = {}; bpos = {:?}",
1023 map.start_pos,
1024 total_extra_bytes,
1025 bpos,
1026 );
1027 CharPos(bpos.to_usize() - map.start_pos.to_usize() - total_extra_bytes as usize)
1028 }
1029
1030 pub fn span_to_char_offset(&self, file: &SourceFile, span: Span) -> (u32, u32) {
1033 let start_offset = file.start_pos;
1035
1036 let mut state = ByteToCharPosState::default();
1037 let start =
1038 span.lo.to_u32() - start_offset.to_u32() - calc_utf16_offset(file, span.lo, &mut state);
1039 let end =
1040 span.hi.to_u32() - start_offset.to_u32() - calc_utf16_offset(file, span.hi, &mut state);
1041
1042 (start, end)
1043 }
1044
1045 #[doc(hidden)]
1050 pub fn lookup_source_file_in(
1051 files: &[Lrc<SourceFile>],
1052 pos: BytePos,
1053 ) -> Option<Lrc<SourceFile>> {
1054 if pos.is_dummy() {
1055 return None;
1056 }
1057
1058 let count = files.len();
1059
1060 let mut a = 0;
1062 let mut b = count;
1063 while b - a > 1 {
1064 let m = (a + b) / 2;
1065 if files[m].start_pos > pos {
1066 b = m;
1067 } else {
1068 a = m;
1069 }
1070 }
1071
1072 if a >= count {
1073 return None;
1074 }
1075
1076 Some(files[a].clone())
1077 }
1078
1079 #[doc(hidden)]
1083 pub fn lookup_source_file(&self, pos: BytePos) -> Lrc<SourceFile> {
1084 self.try_lookup_source_file(pos).unwrap().unwrap()
1085 }
1086
1087 #[doc(hidden)]
1091 pub fn try_lookup_source_file(
1092 &self,
1093 pos: BytePos,
1094 ) -> Result<Option<Lrc<SourceFile>>, SourceMapLookupError> {
1095 let files = self.files.borrow();
1096 let files = &files.source_files;
1097 let fm = Self::lookup_source_file_in(files, pos);
1098 match fm {
1099 Some(fm) => Ok(Some(fm)),
1100 None => Err(SourceMapLookupError::NoFileFor(pos)),
1101 }
1102 }
1103
1104 pub fn count_lines(&self) -> usize {
1105 self.files().iter().fold(0, |a, f| a + f.count_lines())
1106 }
1107
1108 pub fn generate_fn_name_span(&self, span: Span) -> Option<Span> {
1109 let prev_span = self.span_extend_to_prev_str(span, "fn", true);
1110 self.span_to_snippet(prev_span)
1111 .map(|snippet| {
1112 let len = snippet
1113 .find(|c: char| !c.is_alphanumeric() && c != '_')
1114 .expect("no label after fn");
1115 prev_span.with_hi(BytePos(prev_span.lo().0 + len as u32))
1116 })
1117 .ok()
1118 }
1119
1120 pub fn generate_local_type_param_snippet(&self, span: Span) -> Option<(Span, String)> {
1141 let sugg_span = self.span_extend_to_prev_str(span, "fn", false);
1144 if sugg_span != span {
1145 if let Ok(snippet) = self.span_to_snippet(sugg_span) {
1146 let mut offset = snippet
1148 .find(|c: char| !c.is_alphanumeric() && c != '_')
1149 .expect("no label after fn");
1150
1151 let mut bracket_counter = 0;
1153 let mut last_char = None;
1154 for c in snippet[offset..].chars() {
1155 match c {
1156 '<' => bracket_counter += 1,
1157 '>' => bracket_counter -= 1,
1158 '(' if bracket_counter == 0 => break,
1159 _ => {}
1160 }
1161 offset += c.len_utf8();
1162 last_char = Some(c);
1163 }
1164
1165 let sugg_span = sugg_span.with_hi(BytePos(sugg_span.lo().0 + offset as u32));
1167
1168 let mut new_snippet = if last_char == Some('>') {
1171 format!("{}, ", &snippet[..(offset - '>'.len_utf8())])
1172 } else {
1173 format!("{}<", &snippet[..offset])
1174 };
1175 new_snippet.push_str(
1176 &self
1177 .span_to_snippet(span)
1178 .unwrap_or_else(|_| "T".to_string()),
1179 );
1180 new_snippet.push('>');
1181
1182 return Some((sugg_span, new_snippet));
1183 }
1184 }
1185
1186 None
1187 }
1188
1189 #[allow(clippy::ptr_arg)]
1190 #[cfg(feature = "sourcemap")]
1191 #[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
1192 pub fn build_source_map(
1193 &self,
1194 mappings: &[(BytePos, LineCol)],
1195 orig: Option<swc_sourcemap::SourceMap>,
1196 config: impl SourceMapGenConfig,
1197 ) -> swc_sourcemap::SourceMap {
1198 build_source_map(self, mappings, orig, &config)
1199 }
1200}
1201
1202fn remove_bom(src: &mut BytesStr) {
1204 if src.starts_with('\u{feff}') {
1205 src.advance(3);
1206 }
1207}
1208
1209fn calc_utf16_offset(file: &SourceFile, bpos: BytePos, state: &mut ByteToCharPosState) -> u32 {
1212 let mut total_extra_bytes = state.total_extra_bytes;
1213 let mut index = state.mbc_index;
1214 let analysis = file.analyze();
1215 if bpos >= state.pos {
1216 let range = index..analysis.multibyte_chars.len();
1217 for i in range {
1218 let mbc = &analysis.multibyte_chars[i];
1219 #[cfg(debug_assertions)]
1220 debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
1221 if mbc.pos >= bpos {
1222 break;
1223 }
1224 total_extra_bytes += mbc.byte_to_char_diff() as u32;
1225 debug_assert!(
1228 bpos.to_u32() >= mbc.pos.to_u32() + mbc.bytes as u32,
1229 "bpos = {:?}, mbc.pos = {:?}, mbc.bytes = {:?}",
1230 bpos,
1231 mbc.pos,
1232 mbc.bytes
1233 );
1234 index += 1;
1235 }
1236 } else {
1237 let range = 0..index;
1238 for i in range.rev() {
1239 let mbc = &analysis.multibyte_chars[i];
1240 #[cfg(debug_assertions)]
1241 debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
1242 if mbc.pos < bpos {
1243 break;
1244 }
1245 total_extra_bytes -= mbc.byte_to_char_diff() as u32;
1246 debug_assert!(
1249 bpos.to_u32() <= mbc.pos.to_u32(),
1250 "bpos = {:?}, mbc.pos = {:?}",
1251 bpos,
1252 mbc.pos,
1253 );
1254 index -= 1;
1255 }
1256 }
1257
1258 state.pos = bpos;
1259 state.total_extra_bytes = total_extra_bytes;
1260 state.mbc_index = index;
1261
1262 total_extra_bytes
1263}
1264
1265pub trait Files {
1266 fn map_raw_pos(&self, raw_pos: BytePos) -> BytePos {
1272 raw_pos
1273 }
1274
1275 fn is_in_file(&self, f: &Lrc<SourceFile>, raw_pos: BytePos) -> bool {
1280 f.start_pos <= raw_pos && raw_pos < f.end_pos
1281 }
1282
1283 fn try_lookup_source_file(
1286 &self,
1287 raw_pos: BytePos,
1288 ) -> Result<Option<Lrc<SourceFile>>, SourceMapLookupError>;
1289}
1290
1291impl Files for SourceMap {
1292 fn try_lookup_source_file(
1293 &self,
1294 pos: BytePos,
1295 ) -> Result<Option<Lrc<SourceFile>>, SourceMapLookupError> {
1296 self.try_lookup_source_file(pos)
1297 }
1298}
1299
1300#[allow(clippy::ptr_arg)]
1301#[cfg(feature = "sourcemap")]
1302#[cfg_attr(docsrs, doc(cfg(feature = "sourcemap")))]
1303pub fn build_source_map(
1304 files: &impl Files,
1305 mappings: &[(BytePos, LineCol)],
1306 orig: Option<swc_sourcemap::SourceMap>,
1307 config: &impl SourceMapGenConfig,
1308) -> swc_sourcemap::SourceMap {
1309 let mut builder = SourceMapBuilder::new(None);
1310 if orig.is_none() {
1311 config.for_each_additional_name(&mut |name| {
1312 builder.add_name(unsafe {
1313 BytesStr::from_utf8_slice_unchecked(name.as_bytes())
1315 });
1316 });
1317 }
1318
1319 let mut src_id = 0u32;
1320
1321 let mut cur_file: Option<Lrc<SourceFile>> = None;
1325
1326 let mut prev_dst_line = u32::MAX;
1327
1328 let mut ch_state = ByteToCharPosState::default();
1329 let mut line_state = ByteToCharPosState::default();
1330
1331 for (raw_pos, lc) in mappings.iter() {
1332 let pos = files.map_raw_pos(*raw_pos);
1333
1334 if pos.is_reserved_for_comments() {
1335 continue;
1336 }
1337
1338 let lc = *lc;
1339
1340 if lc.line == 0 && lc.col == 0 && pos.is_dummy() {
1343 continue;
1344 }
1345
1346 if pos == BytePos(u32::MAX) {
1347 builder.add_raw(lc.line, lc.col, 0, 0, Some(src_id), None, false);
1348 continue;
1349 }
1350
1351 let f;
1352 let f = match cur_file {
1353 Some(ref f) if files.is_in_file(f, *raw_pos) => f,
1354 _ => {
1355 let source_file = files.try_lookup_source_file(*raw_pos).unwrap();
1356 if let Some(source_file) = source_file {
1357 f = source_file;
1358 } else {
1359 continue;
1360 }
1361 if config.skip(&f.name) {
1362 continue;
1363 }
1364 src_id = builder.add_source(config.file_name_to_source(&f.name).into());
1365 if orig.is_none() && config.ignore_list(&f.name) {
1367 builder.add_to_ignore_list(src_id);
1368 }
1369
1370 let inline_sources_content =
1372 orig.is_none() && config.inline_sources_content(&f.name);
1373 if inline_sources_content {
1374 builder.set_source_contents(src_id, Some(f.src.clone()));
1375 }
1376
1377 ch_state = ByteToCharPosState::default();
1378 line_state = ByteToCharPosState::default();
1379
1380 cur_file = Some(f.clone());
1381 &f
1382 }
1383 };
1384 if config.skip(&f.name) {
1385 continue;
1386 }
1387
1388 let emit_columns = config.emit_columns(&f.name);
1389
1390 if !emit_columns && lc.line == prev_dst_line {
1391 continue;
1392 }
1393
1394 let line = match f.lookup_line(pos) {
1395 Some(line) => line as u32,
1396 None => continue,
1397 };
1398
1399 let analysis = f.analyze();
1400 let linebpos = analysis.lines[line as usize];
1401 debug_assert!(
1402 pos >= linebpos,
1403 "{}: bpos = {:?}; linebpos = {:?};",
1404 f.name,
1405 pos,
1406 linebpos,
1407 );
1408
1409 let linechpos = linebpos.to_u32() - calc_utf16_offset(f, linebpos, &mut line_state);
1410 let chpos = pos.to_u32() - calc_utf16_offset(f, pos, &mut ch_state);
1411
1412 debug_assert!(
1413 chpos >= linechpos,
1414 "{}: chpos = {:?}; linechpos = {:?};",
1415 f.name,
1416 chpos,
1417 linechpos,
1418 );
1419
1420 let col = chpos - linechpos;
1421 let name = None;
1422
1423 let name_idx = if orig.is_none() {
1424 name.or_else(|| config.name_for_bytepos(pos)).map(|name| {
1425 builder.add_name(unsafe {
1426 BytesStr::from_utf8_slice_unchecked(name.as_bytes())
1428 })
1429 })
1430 } else {
1431 None
1433 };
1434
1435 builder.add_raw(lc.line, lc.col, line, col, Some(src_id), name_idx, false);
1436 prev_dst_line = lc.line;
1437 }
1438
1439 let map = builder.into_sourcemap();
1440
1441 if let Some(mut orig) = orig {
1442 orig.adjust_mappings(&map);
1443 return orig;
1444 }
1445
1446 map
1447}
1448
1449impl SourceMapper for SourceMap {
1450 fn lookup_char_pos(&self, pos: BytePos) -> Loc {
1451 self.lookup_char_pos(pos)
1452 }
1453
1454 fn span_to_lines(&self, sp: Span) -> FileLinesResult {
1455 self.span_to_lines(sp)
1456 }
1457
1458 fn span_to_string(&self, sp: Span) -> String {
1459 self.span_to_string(sp)
1460 }
1461
1462 fn span_to_filename(&self, sp: Span) -> Lrc<FileName> {
1463 self.span_to_filename(sp)
1464 }
1465
1466 fn span_to_snippet(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
1468 self.span_to_source(sp, |src, start_index, end_index| {
1469 src[start_index..end_index].to_string()
1470 })
1471 }
1472
1473 fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
1474 self.merge_spans(sp_lhs, sp_rhs)
1475 }
1476
1477 fn call_span_if_macro(&self, sp: Span) -> Span {
1478 sp
1479 }
1480
1481 fn doctest_offset_line(&self, line: usize) -> usize {
1482 self.doctest_offset_line(line)
1483 }
1484}
1485
1486#[derive(Clone, Default)]
1487pub struct FilePathMapping {
1488 mapping: Vec<(PathBuf, PathBuf)>,
1489}
1490
1491impl FilePathMapping {
1492 pub fn empty() -> FilePathMapping {
1493 FilePathMapping {
1494 mapping: Vec::new(),
1495 }
1496 }
1497
1498 pub fn new(mapping: Vec<(PathBuf, PathBuf)>) -> FilePathMapping {
1499 FilePathMapping { mapping }
1500 }
1501
1502 pub fn map_prefix(&self, path: &Path) -> (PathBuf, bool) {
1506 for (from, to) in self.mapping.iter().rev() {
1510 if let Ok(rest) = path.strip_prefix(from) {
1511 return (to.join(rest), true);
1512 }
1513 }
1514
1515 (path.to_path_buf(), false)
1516 }
1517}
1518
1519pub trait SourceMapGenConfig {
1520 fn file_name_to_source(&self, f: &FileName) -> String;
1526
1527 fn name_for_bytepos(&self, _bpos: BytePos) -> Option<&str> {
1529 None
1530 }
1531
1532 fn for_each_additional_name(&self, _op: &mut dyn FnMut(&str)) {}
1536
1537 fn inline_sources_content(&self, f: &FileName) -> bool {
1539 !matches!(
1540 f,
1541 FileName::Real(..) | FileName::Custom(..) | FileName::Url(..)
1542 )
1543 }
1544
1545 fn emit_columns(&self, _f: &FileName) -> bool {
1547 true
1548 }
1549
1550 fn skip(&self, f: &FileName) -> bool {
1552 matches!(f, FileName::Internal(..))
1553 }
1554
1555 fn ignore_list(&self, f: &FileName) -> bool {
1571 matches!(f, FileName::Anon | FileName::Internal(..))
1572 }
1573}
1574
1575#[derive(Debug, Clone)]
1576pub struct DefaultSourceMapGenConfig;
1577
1578macro_rules! impl_ref {
1579 ($TP:ident, $T:ty) => {
1580 impl<$TP> SourceMapGenConfig for $T
1581 where
1582 $TP: SourceMapGenConfig,
1583 {
1584 fn file_name_to_source(&self, f: &FileName) -> String {
1585 (**self).file_name_to_source(f)
1586 }
1587 }
1588 };
1589}
1590
1591impl_ref!(T, &'_ T);
1592impl_ref!(T, Box<T>);
1593impl_ref!(T, std::rc::Rc<T>);
1594impl_ref!(T, std::sync::Arc<T>);
1595
1596impl SourceMapGenConfig for DefaultSourceMapGenConfig {
1597 fn file_name_to_source(&self, f: &FileName) -> String {
1598 f.to_string()
1599 }
1600}
1601
1602#[derive(Debug, Clone, Default)]
1604pub struct ByteToCharPosState {
1605 pos: BytePos,
1607
1608 total_extra_bytes: u32,
1610
1611 mbc_index: usize,
1614}
1615
1616#[cfg(test)]
1621mod tests {
1622 use super::*;
1623
1624 fn init_source_map() -> SourceMap {
1625 let sm = SourceMap::new(FilePathMapping::empty());
1626 sm.new_source_file(
1627 Lrc::new(PathBuf::from("blork.rs").into()),
1628 "first line.\nsecond line",
1629 );
1630 sm.new_source_file(Lrc::new(PathBuf::from("empty.rs").into()), BytesStr::new());
1631 sm.new_source_file(
1632 Lrc::new(PathBuf::from("blork2.rs").into()),
1633 "first line.\nsecond line",
1634 );
1635 sm
1636 }
1637
1638 #[test]
1639 fn t3() {
1640 let sm = init_source_map();
1642
1643 let srcfbp1 = sm.lookup_byte_offset(BytePos(24));
1644 assert_eq!(*srcfbp1.sf.name, PathBuf::from("blork.rs").into());
1645 assert_eq!(srcfbp1.pos, BytePos(23));
1646
1647 let srcfbp1 = sm.lookup_byte_offset(BytePos(25));
1648 assert_eq!(*srcfbp1.sf.name, PathBuf::from("empty.rs").into());
1649 assert_eq!(srcfbp1.pos, BytePos(0));
1650
1651 let srcfbp2 = sm.lookup_byte_offset(BytePos(26));
1652 assert_eq!(*srcfbp2.sf.name, PathBuf::from("blork2.rs").into());
1653 assert_eq!(srcfbp2.pos, BytePos(0));
1654 }
1655
1656 #[test]
1657 fn t4() {
1658 let sm = init_source_map();
1660
1661 let cp1 = sm.bytepos_to_file_charpos(BytePos(23)).unwrap();
1662 assert_eq!(cp1, CharPos(22));
1663
1664 let cp2 = sm.bytepos_to_file_charpos(BytePos(26)).unwrap();
1665 assert_eq!(cp2, CharPos(0));
1666 }
1667
1668 #[test]
1669 fn t5() {
1670 let sm = init_source_map();
1672
1673 let loc1 = sm.lookup_char_pos(BytePos(23));
1674 assert_eq!(*loc1.file.name, PathBuf::from("blork.rs").into());
1675 assert_eq!(loc1.line, 2);
1676 assert_eq!(loc1.col, CharPos(10));
1677
1678 let loc2 = sm.lookup_char_pos(BytePos(26));
1679 assert_eq!(*loc2.file.name, PathBuf::from("blork2.rs").into());
1680 assert_eq!(loc2.line, 1);
1681 assert_eq!(loc2.col, CharPos(0));
1682 }
1683
1684 fn init_source_map_mbc() -> SourceMap {
1685 let sm = SourceMap::new(FilePathMapping::empty());
1686 sm.new_source_file(
1688 Lrc::new(PathBuf::from("blork.rs").into()),
1689 "fir€st €€€€ line.\nsecond line",
1690 );
1691 sm.new_source_file(
1692 Lrc::new(PathBuf::from("blork2.rs").into()),
1693 "first line€€.\n€ second line",
1694 );
1695 sm
1696 }
1697
1698 #[test]
1699 fn t6() {
1700 let sm = init_source_map_mbc();
1702
1703 let cp1 = sm.bytepos_to_file_charpos(BytePos(4)).unwrap();
1704 assert_eq!(cp1, CharPos(3));
1705
1706 let cp2 = sm.bytepos_to_file_charpos(BytePos(7)).unwrap();
1707 assert_eq!(cp2, CharPos(4));
1708
1709 let cp3 = sm.bytepos_to_file_charpos(BytePos(57)).unwrap();
1710 assert_eq!(cp3, CharPos(12));
1711
1712 let cp4 = sm.bytepos_to_file_charpos(BytePos(62)).unwrap();
1713 assert_eq!(cp4, CharPos(15));
1714 }
1715
1716 #[test]
1717 fn t7() {
1718 let sm = init_source_map();
1720 let span = Span::new(BytePos(13), BytePos(24));
1721 let file_lines = sm.span_to_lines(span).unwrap();
1722
1723 assert_eq!(*file_lines.file.name, PathBuf::from("blork.rs").into());
1724 assert_eq!(file_lines.lines.len(), 1);
1725 assert_eq!(file_lines.lines[0].line_index, 1);
1726 }
1727
1728 fn span_from_selection(input: &str, selection: &str) -> Span {
1733 assert_eq!(input.len(), selection.len());
1734 let left_index = (selection.find('~').unwrap() + 1) as u32;
1736 let right_index = selection
1737 .rfind('~')
1738 .map(|x| {
1739 (x + 1) as u32
1741 })
1742 .unwrap_or(left_index);
1743 Span::new(BytePos(left_index), BytePos(right_index + 1))
1744 }
1745
1746 #[test]
1749 fn span_to_snippet_and_lines_spanning_multiple_lines() {
1750 let sm = SourceMap::new(FilePathMapping::empty());
1751 let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
1752 let selection = " \n ~~\n~~~\n~~~~~ \n \n";
1753 sm.new_source_file(
1754 Lrc::new(Path::new("blork.rs").to_path_buf().into()),
1755 inputtext,
1756 );
1757 let span = span_from_selection(inputtext, selection);
1758
1759 assert_eq!(&sm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD");
1761
1762 let lines = sm.span_to_lines(span).unwrap();
1765 let expected = vec![
1766 LineInfo {
1767 line_index: 1,
1768 start_col: CharPos(4),
1769 end_col: CharPos(6),
1770 },
1771 LineInfo {
1772 line_index: 2,
1773 start_col: CharPos(0),
1774 end_col: CharPos(3),
1775 },
1776 LineInfo {
1777 line_index: 3,
1778 start_col: CharPos(0),
1779 end_col: CharPos(5),
1780 },
1781 ];
1782 assert_eq!(lines.lines, expected);
1783 }
1784
1785 #[test]
1786 fn t8() {
1787 let sm = init_source_map();
1789 let span = Span::new(BytePos(13), BytePos(24));
1790 let snippet = sm.span_to_snippet(span);
1791
1792 assert_eq!(snippet, Ok("second line".to_string()));
1793 }
1794
1795 #[test]
1796 fn t9() {
1797 let sm = init_source_map();
1799 let span = Span::new(BytePos(13), BytePos(24));
1800 let sstr = sm.span_to_string(span);
1801
1802 assert_eq!(sstr, "blork.rs:2:1: 2:12");
1803 }
1804
1805 #[test]
1806 fn t10() {
1807 let sm = SourceMap::new(FilePathMapping::empty());
1809 sm.new_source_file(Lrc::new(PathBuf::from("blork.rs").into()), "");
1810 let span = Span::new(BytePos(1), BytePos(1));
1811 let file_lines = sm.span_to_lines(span).unwrap();
1812
1813 assert_eq!(*file_lines.file.name, PathBuf::from("blork.rs").into());
1814 assert_eq!(file_lines.lines.len(), 0);
1815 }
1816
1817 #[test]
1819 fn span_merging_fail() {
1820 let sm = SourceMap::new(FilePathMapping::empty());
1821 let inputtext = "bbbb BB\ncc CCC\n";
1822 let selection1 = " ~~\n \n";
1823 let selection2 = " \n ~~~\n";
1824 sm.new_source_file(Lrc::new(Path::new("blork.rs").to_owned().into()), inputtext);
1825 let span1 = span_from_selection(inputtext, selection1);
1826 let span2 = span_from_selection(inputtext, selection2);
1827
1828 assert!(sm.merge_spans(span1, span2).is_none());
1829 }
1830
1831 #[test]
1832 fn test_calc_utf16_offset() {
1833 let input = "t¢e∆s💩t";
1834 let sm = SourceMap::new(FilePathMapping::empty());
1835 let file = sm.new_source_file(Lrc::new(PathBuf::from("blork.rs").into()), input);
1836
1837 let mut state = ByteToCharPosState::default();
1838 let mut bpos = file.start_pos;
1839 let mut cpos = CharPos(bpos.to_usize());
1840 for c in input.chars() {
1841 let actual = bpos.to_u32() - calc_utf16_offset(&file, bpos, &mut state);
1842
1843 assert_eq!(actual, cpos.to_u32());
1844
1845 bpos = bpos + BytePos(c.len_utf8() as u32);
1846 cpos = cpos + CharPos(c.len_utf16());
1847 }
1848
1849 for c in input.chars().rev() {
1850 bpos = bpos - BytePos(c.len_utf8() as u32);
1851 cpos = cpos - CharPos(c.len_utf16());
1852
1853 let actual = bpos.to_u32() - calc_utf16_offset(&file, bpos, &mut state);
1854
1855 assert_eq!(actual, cpos.to_u32());
1856 }
1857 }
1858
1859 #[test]
1860 fn bytepos_to_charpos() {
1861 let input = "t¢e∆s💩t";
1862 let sm = SourceMap::new(FilePathMapping::empty());
1863 let file = sm.new_source_file(Lrc::new(PathBuf::from("blork.rs").into()), input);
1864
1865 let mut bpos = file.start_pos;
1866 let mut cpos = CharPos(0);
1867 for c in input.chars() {
1868 let actual = sm.bytepos_to_file_charpos_with(&file, bpos);
1869
1870 assert_eq!(actual, cpos);
1871
1872 bpos = bpos + BytePos(c.len_utf8() as u32);
1873 cpos = cpos + CharPos(c.len_utf16());
1874 }
1875 }
1876}