swc_common/
syntax_pos.rs

1use std::{
2    borrow::Cow,
3    cmp, fmt,
4    hash::{Hash, Hasher},
5    ops::{Add, Sub},
6    path::PathBuf,
7    sync::{atomic::AtomicU32, Mutex},
8};
9
10use bytes_str::BytesStr;
11use serde::{Deserialize, Serialize};
12use url::Url;
13
14use self::hygiene::MarkData;
15pub use self::hygiene::{Mark, SyntaxContext};
16use crate::{cache::CacheCell, rustc_data_structures::stable_hasher::StableHasher, sync::Lrc};
17
18mod analyze_source_file;
19pub mod hygiene;
20
21/// Spans represent a region of code, used for error reporting.
22///
23/// Positions in
24/// spans are *absolute* positions from the beginning of the `source_map`, not
25/// positions relative to `SourceFile`s. Methods on the `SourceMap` can be used
26/// to relate spans back to the original source.
27/// You must be careful if the span crosses more than one file - you will not be
28/// able to use many of the functions on spans in `source_map` and you cannot
29/// assume that the length of the `span = hi - lo`; there may be space in the
30/// `BytePos` range between files.
31#[derive(Clone, Copy, Hash, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)]
32#[cfg_attr(
33    any(feature = "rkyv-impl"),
34    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
35)]
36#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
37#[cfg_attr(feature = "rkyv-impl", repr(C))]
38#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
39pub struct Span {
40    #[serde(rename = "start")]
41    #[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))]
42    pub lo: BytePos,
43    #[serde(rename = "end")]
44    #[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))]
45    pub hi: BytePos,
46}
47
48impl std::fmt::Debug for Span {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        write!(f, "{}..{}", self.lo.0, self.hi.0,)
51    }
52}
53
54impl From<(BytePos, BytePos)> for Span {
55    #[inline]
56    fn from(sp: (BytePos, BytePos)) -> Self {
57        Span::new(sp.0, sp.1)
58    }
59}
60
61impl From<Span> for (BytePos, BytePos) {
62    #[inline]
63    fn from(sp: Span) -> Self {
64        (sp.lo, sp.hi)
65    }
66}
67
68#[cfg(feature = "arbitrary")]
69#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
70impl<'a> arbitrary::Arbitrary<'a> for Span {
71    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
72        let lo = u.arbitrary::<BytePos>()?;
73        let hi = u.arbitrary::<BytePos>()?;
74
75        Ok(Self::new(lo, hi))
76    }
77}
78
79/// Dummy span, both position and length are zero, syntax context is zero as
80/// well.
81pub const DUMMY_SP: Span = Span {
82    lo: BytePos::DUMMY,
83    hi: BytePos::DUMMY,
84};
85
86/// PURE span, will emit `/* #__PURE__ */` comment in codegen.
87pub const PURE_SP: Span = Span {
88    lo: BytePos::PURE,
89    hi: BytePos::PURE,
90};
91
92/// Used for some special cases. e.g. mark the generated AST.
93pub const PLACEHOLDER_SP: Span = Span {
94    lo: BytePos::PLACEHOLDER,
95    hi: BytePos::PLACEHOLDER,
96};
97
98pub struct Globals {
99    hygiene_data: Mutex<hygiene::HygieneData>,
100    #[allow(unused)]
101    dummy_cnt: AtomicU32,
102    #[allow(unused)]
103    marks: Mutex<Vec<MarkData>>,
104}
105
106const DUMMY_RESERVE: u32 = u32::MAX - 2_u32.pow(16);
107
108impl Default for Globals {
109    fn default() -> Self {
110        Self::new()
111    }
112}
113
114impl Globals {
115    pub fn new() -> Globals {
116        Globals {
117            hygiene_data: Mutex::new(hygiene::HygieneData::new()),
118            marks: Mutex::new(vec![MarkData {
119                parent: Mark::root(),
120            }]),
121            dummy_cnt: AtomicU32::new(DUMMY_RESERVE),
122        }
123    }
124
125    /// Clone the data from the current globals.
126    ///
127    /// Do not use this unless you know what you are doing.
128    pub fn clone_data(&self) -> Self {
129        Globals {
130            hygiene_data: Mutex::new(self.hygiene_data.lock().unwrap().clone()),
131            marks: Mutex::new(self.marks.lock().unwrap().clone()),
132            dummy_cnt: AtomicU32::new(self.dummy_cnt.load(std::sync::atomic::Ordering::SeqCst)),
133        }
134    }
135}
136
137better_scoped_tls::scoped_tls!(
138
139    /// Storage for span hygiene data.
140    ///
141    /// This variable is used to manage identifiers or to identify nodes.
142    /// Note that it's stored as a thread-local storage, but actually it's shared
143    /// between threads.
144    ///
145    /// # Usages
146    ///
147    /// ## Configuring
148    ///
149    /// ```rust
150    /// use swc_common::GLOBALS;
151    ///
152    /// GLOBALS.set(&Default::default(), || {
153    ///     // Do operations that require span hygiene
154    /// });
155    /// ```
156    ///
157    /// ## Span hygiene
158    ///
159    /// [Mark]s are stored in this variable.
160    ///
161    /// You can see the document how swc uses the span hygiene info at
162    /// https://rustdoc.swc.rs/swc_ecma_transforms_base/resolver/fn.resolver_with_mark.html
163    pub static GLOBALS: Globals
164);
165
166#[cfg_attr(
167    any(feature = "rkyv-impl"),
168    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
169)]
170#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
171#[cfg_attr(feature = "rkyv-impl", repr(u32))]
172#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
173pub enum FileName {
174    Real(
175        #[cfg_attr(
176            any(feature = "rkyv-impl"),
177            rkyv(with = crate::source_map::EncodePathBuf)
178        )]
179        PathBuf,
180    ),
181    /// A macro. This includes the full name of the macro, so that there are no
182    /// clashes.
183    Macros(String),
184    /// call to `quote!`
185    QuoteExpansion,
186    /// Command line
187    Anon,
188    /// Hack in src/libsyntax/parse.rs
189    MacroExpansion,
190    ProcMacroSourceCode,
191    Url(#[cfg_attr(any(feature = "rkyv-impl"), rkyv(with = crate::source_map::EncodeUrl))] Url),
192    Internal(String),
193    /// Custom sources for explicit parser calls from plugins and drivers
194    Custom(String),
195}
196
197/// A wrapper that attempts to convert a type to and from UTF-8.
198///
199/// Types like `OsString` and `PathBuf` aren't guaranteed to be encoded as
200/// UTF-8, but they usually are anyway. Using this wrapper will archive them as
201/// if they were regular `String`s.
202///
203/// There is built-in `AsString` supports PathBuf but it requires custom
204/// serializer wrapper to handle conversion errors. This wrapper is simplified
205/// version accepts errors
206#[cfg(feature = "rkyv-impl")]
207#[derive(Debug, Clone, Copy)]
208#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
209#[cfg_attr(feature = "rkyv-impl", repr(C))]
210pub struct EncodePathBuf;
211
212#[cfg(feature = "rkyv-impl")]
213impl rkyv::with::ArchiveWith<PathBuf> for EncodePathBuf {
214    type Archived = rkyv::string::ArchivedString;
215    type Resolver = rkyv::string::StringResolver;
216
217    #[inline]
218    fn resolve_with(field: &PathBuf, resolver: Self::Resolver, out: rkyv::Place<Self::Archived>) {
219        // It's safe to unwrap here because if the OsString wasn't valid UTF-8 it would
220        // have failed to serialize
221        rkyv::string::ArchivedString::resolve_from_str(field.to_str().unwrap(), resolver, out);
222    }
223}
224
225#[cfg(feature = "rkyv-impl")]
226impl<S> rkyv::with::SerializeWith<PathBuf, S> for EncodePathBuf
227where
228    S: ?Sized + rancor::Fallible + rkyv::ser::Writer,
229    S::Error: rancor::Source,
230{
231    #[inline]
232    fn serialize_with(field: &PathBuf, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
233        let s = field.to_str().unwrap_or_default();
234        rkyv::string::ArchivedString::serialize_from_str(s, serializer)
235    }
236}
237
238#[cfg(feature = "rkyv-impl")]
239impl<D> rkyv::with::DeserializeWith<rkyv::string::ArchivedString, PathBuf, D> for EncodePathBuf
240where
241    D: ?Sized + rancor::Fallible,
242{
243    #[inline]
244    fn deserialize_with(
245        field: &rkyv::string::ArchivedString,
246        _: &mut D,
247    ) -> Result<PathBuf, D::Error> {
248        Ok(<PathBuf as std::str::FromStr>::from_str(field.as_str()).unwrap())
249    }
250}
251
252/// A wrapper that attempts to convert a Url to and from String.
253#[cfg(feature = "rkyv-impl")]
254#[derive(Debug, Clone, Copy)]
255#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
256#[cfg_attr(feature = "rkyv-impl", repr(C))]
257pub struct EncodeUrl;
258
259#[cfg(feature = "rkyv-impl")]
260impl rkyv::with::ArchiveWith<Url> for EncodeUrl {
261    type Archived = rkyv::string::ArchivedString;
262    type Resolver = rkyv::string::StringResolver;
263
264    #[inline]
265    fn resolve_with(field: &Url, resolver: Self::Resolver, out: rkyv::Place<Self::Archived>) {
266        rkyv::string::ArchivedString::resolve_from_str(field.as_str(), resolver, out);
267    }
268}
269
270#[cfg(feature = "rkyv-impl")]
271impl<S> rkyv::with::SerializeWith<Url, S> for EncodeUrl
272where
273    S: ?Sized + rancor::Fallible + rkyv::ser::Writer,
274    S::Error: rancor::Source,
275{
276    #[inline]
277    fn serialize_with(field: &Url, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
278        let field = field.as_str();
279        rkyv::string::ArchivedString::serialize_from_str(field, serializer)
280    }
281}
282
283#[cfg(feature = "rkyv-impl")]
284impl<D> rkyv::with::DeserializeWith<rkyv::Archived<String>, Url, D> for EncodeUrl
285where
286    D: ?Sized + rancor::Fallible,
287{
288    #[inline]
289    fn deserialize_with(field: &rkyv::string::ArchivedString, _: &mut D) -> Result<Url, D::Error> {
290        Ok(Url::parse(field.as_str()).unwrap())
291    }
292}
293
294impl std::fmt::Display for FileName {
295    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296        match *self {
297            FileName::Real(ref path) => write!(fmt, "{}", path.display()),
298            FileName::Macros(ref name) => write!(fmt, "<{name} macros>"),
299            FileName::QuoteExpansion => write!(fmt, "<quote expansion>"),
300            FileName::MacroExpansion => write!(fmt, "<macro expansion>"),
301            FileName::Anon => write!(fmt, "<anon>"),
302            FileName::ProcMacroSourceCode => write!(fmt, "<proc-macro source code>"),
303            FileName::Url(ref u) => write!(fmt, "{u}"),
304            FileName::Custom(ref s) => {
305                write!(fmt, "{s}")
306            }
307            FileName::Internal(ref s) => write!(fmt, "<{s}>"),
308        }
309    }
310}
311
312impl From<PathBuf> for FileName {
313    fn from(p: PathBuf) -> Self {
314        assert!(!p.to_string_lossy().ends_with('>'));
315        FileName::Real(p)
316    }
317}
318
319impl From<Url> for FileName {
320    fn from(url: Url) -> Self {
321        FileName::Url(url)
322    }
323}
324
325impl FileName {
326    pub fn is_real(&self) -> bool {
327        match *self {
328            FileName::Real(_) => true,
329            FileName::Macros(_)
330            | FileName::Anon
331            | FileName::MacroExpansion
332            | FileName::ProcMacroSourceCode
333            | FileName::Custom(_)
334            | FileName::QuoteExpansion
335            | FileName::Internal(_)
336            | FileName::Url(_) => false,
337        }
338    }
339
340    pub fn is_macros(&self) -> bool {
341        match *self {
342            FileName::Real(_)
343            | FileName::Anon
344            | FileName::MacroExpansion
345            | FileName::ProcMacroSourceCode
346            | FileName::Custom(_)
347            | FileName::QuoteExpansion
348            | FileName::Internal(_)
349            | FileName::Url(_) => false,
350            FileName::Macros(_) => true,
351        }
352    }
353}
354
355#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
356#[cfg_attr(
357    feature = "diagnostic-serde",
358    derive(serde::Serialize, serde::Deserialize)
359)]
360#[cfg_attr(
361    any(feature = "rkyv-impl"),
362    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
363)]
364#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
365#[cfg_attr(feature = "rkyv-impl", repr(C))]
366pub struct PrimarySpanLabel(pub Span, pub String);
367
368/// A collection of spans. Spans have two orthogonal attributes:
369///
370/// - they can be *primary spans*. In this case they are the locus of the error,
371///   and would be rendered with `^^^`.
372/// - they can have a *label*. In this case, the label is written next to the
373///   mark in the snippet when we render.
374#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
375#[cfg_attr(
376    feature = "diagnostic-serde",
377    derive(serde::Serialize, serde::Deserialize)
378)]
379#[cfg_attr(
380    any(feature = "rkyv-impl"),
381    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
382)]
383#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
384#[cfg_attr(feature = "rkyv-impl", repr(C))]
385pub struct MultiSpan {
386    primary_spans: Vec<Span>,
387    span_labels: Vec<PrimarySpanLabel>,
388}
389
390extern "C" {
391    fn __span_dummy_with_cmt_proxy() -> u32;
392}
393
394impl Span {
395    #[inline]
396    pub fn lo(self) -> BytePos {
397        self.lo
398    }
399
400    #[inline]
401    pub fn new(mut lo: BytePos, mut hi: BytePos) -> Self {
402        if lo > hi {
403            std::mem::swap(&mut lo, &mut hi);
404        }
405
406        Span { lo, hi }
407    }
408
409    #[inline]
410    #[track_caller]
411    pub fn new_with_checked(lo: BytePos, hi: BytePos) -> Self {
412        debug_assert!(lo <= hi, "lo: {lo:#?}, hi: {hi:#?}");
413        Span { lo, hi }
414    }
415
416    #[inline]
417    pub fn with_lo(&self, lo: BytePos) -> Span {
418        Span::new(lo, self.hi)
419    }
420
421    #[inline(always)]
422    pub fn hi(self) -> BytePos {
423        self.hi
424    }
425
426    #[inline]
427    pub fn with_hi(&self, hi: BytePos) -> Span {
428        Span::new(self.lo, hi)
429    }
430
431    /// Returns `true` if this is a dummy span with any hygienic context.
432    #[inline]
433    pub fn is_dummy(self) -> bool {
434        self.lo.0 == 0 && self.hi.0 == 0 || self.lo.0 >= DUMMY_RESERVE
435    }
436
437    #[inline]
438    pub fn is_pure(self) -> bool {
439        self.lo.is_pure()
440    }
441
442    #[inline]
443    pub fn is_placeholder(self) -> bool {
444        self.lo.is_placeholder()
445    }
446
447    /// Returns `true` if this is a dummy span with any hygienic context.
448    #[inline]
449    pub fn is_dummy_ignoring_cmt(self) -> bool {
450        self.lo.0 == 0 && self.hi.0 == 0
451    }
452
453    /// Returns a new span representing an empty span at the beginning of this
454    /// span
455    #[inline]
456    pub fn shrink_to_lo(self) -> Span {
457        self.with_hi(self.lo)
458    }
459
460    /// Returns a new span representing an empty span at the end of this span
461    #[inline]
462    pub fn shrink_to_hi(self) -> Span {
463        self.with_lo(self.hi)
464    }
465
466    /// Returns `self` if `self` is not the dummy span, and `other` otherwise.
467    pub fn substitute_dummy(self, other: Span) -> Span {
468        if self.is_dummy() {
469            other
470        } else {
471            self
472        }
473    }
474
475    /// Return true if `self` fully encloses `other`.
476    pub fn contains(self, other: Span) -> bool {
477        self.lo <= other.lo && other.hi <= self.hi
478    }
479
480    /// Return true if the spans are equal with regards to the source text.
481    ///
482    /// Use this instead of `==` when either span could be generated code,
483    /// and you only care that they point to the same bytes of source text.
484    pub fn source_equal(self, other: Span) -> bool {
485        self.lo == other.lo && self.hi == other.hi
486    }
487
488    /// Returns `Some(span)`, where the start is trimmed by the end of `other`
489    pub fn trim_start(self, other: Span) -> Option<Span> {
490        if self.hi > other.hi {
491            Some(self.with_lo(cmp::max(self.lo, other.hi)))
492        } else {
493            None
494        }
495    }
496
497    /// Return a `Span` that would enclose both `self` and `end`.
498    pub fn to(self, end: Span) -> Span {
499        let span_data = self;
500        let end_data = end;
501        // FIXME(jseyfried): self.ctxt should always equal end.ctxt here (c.f. issue
502        // #23480) Return the macro span on its own to avoid weird diagnostic
503        // output. It is preferable to have an incomplete span than a completely
504        // nonsensical one.
505
506        Span::new(
507            cmp::min(span_data.lo, end_data.lo),
508            cmp::max(span_data.hi, end_data.hi),
509        )
510    }
511
512    /// Return a `Span` between the end of `self` to the beginning of `end`.
513    pub fn between(self, end: Span) -> Span {
514        let span = self;
515        Span::new(span.hi, end.lo)
516    }
517
518    /// Return a `Span` between the beginning of `self` to the beginning of
519    /// `end`.
520    pub fn until(self, end: Span) -> Span {
521        let span = self;
522        Span::new(span.lo, end.lo)
523    }
524
525    pub fn from_inner_byte_pos(self, start: usize, end: usize) -> Span {
526        let span = self;
527        Span::new(
528            span.lo + BytePos::from_usize(start),
529            span.lo + BytePos::from_usize(end),
530        )
531    }
532
533    /// Dummy span, both position are extremely large numbers so they would be
534    /// ignore by sourcemap, but can still have comments
535    pub fn dummy_with_cmt() -> Self {
536        #[cfg(all(feature = "__plugin_mode", target_arch = "wasm32"))]
537        {
538            let lo = BytePos(unsafe { __span_dummy_with_cmt_proxy() });
539
540            return Span { lo, hi: lo };
541        }
542
543        #[cfg(not(all(any(feature = "__plugin_mode"), target_arch = "wasm32")))]
544        return GLOBALS.with(|globals| {
545            let lo = BytePos(
546                globals
547                    .dummy_cnt
548                    .fetch_add(1, std::sync::atomic::Ordering::SeqCst),
549            );
550            Span { lo, hi: lo }
551        });
552    }
553}
554
555#[derive(Clone, Debug)]
556pub struct SpanLabel {
557    /// The span we are going to include in the final snippet.
558    pub span: Span,
559
560    /// Is this a primary span? This is the "locus" of the message,
561    /// and is indicated with a `^^^^` underline, versus `----`.
562    pub is_primary: bool,
563
564    /// What label should we attach to this span (if any)?
565    pub label: Option<String>,
566}
567
568impl Default for Span {
569    fn default() -> Self {
570        DUMMY_SP
571    }
572}
573
574impl MultiSpan {
575    #[inline]
576    pub fn new() -> MultiSpan {
577        Self::default()
578    }
579
580    pub fn from_span(primary_span: Span) -> MultiSpan {
581        MultiSpan {
582            primary_spans: vec![primary_span],
583            span_labels: Vec::new(),
584        }
585    }
586
587    pub fn from_spans(vec: Vec<Span>) -> MultiSpan {
588        MultiSpan {
589            primary_spans: vec,
590            span_labels: Vec::new(),
591        }
592    }
593
594    pub fn push_span_label(&mut self, span: Span, label: String) {
595        self.span_labels.push(PrimarySpanLabel(span, label));
596    }
597
598    /// Selects the first primary span (if any)
599    pub fn primary_span(&self) -> Option<Span> {
600        self.primary_spans.first().cloned()
601    }
602
603    /// Returns all primary spans.
604    pub fn primary_spans(&self) -> &[Span] {
605        &self.primary_spans
606    }
607
608    /// Returns `true` if this contains only a dummy primary span with any
609    /// hygienic context.
610    pub fn is_dummy(&self) -> bool {
611        let mut is_dummy = true;
612        for span in &self.primary_spans {
613            if !span.is_dummy() {
614                is_dummy = false;
615            }
616        }
617        is_dummy
618    }
619
620    /// Replaces all occurrences of one Span with another. Used to move Spans in
621    /// areas that don't display well (like std macros). Returns true if
622    /// replacements occurred.
623    pub fn replace(&mut self, before: Span, after: Span) -> bool {
624        let mut replacements_occurred = false;
625        for primary_span in &mut self.primary_spans {
626            if *primary_span == before {
627                *primary_span = after;
628                replacements_occurred = true;
629            }
630        }
631        for span_label in &mut self.span_labels {
632            if span_label.0 == before {
633                span_label.0 = after;
634                replacements_occurred = true;
635            }
636        }
637        replacements_occurred
638    }
639
640    /// Returns the strings to highlight. We always ensure that there
641    /// is an entry for each of the primary spans -- for each primary
642    /// span P, if there is at least one label with span P, we return
643    /// those labels (marked as primary). But otherwise we return
644    /// `SpanLabel` instances with empty labels.
645    pub fn span_labels(&self) -> Vec<SpanLabel> {
646        let is_primary = |span| self.primary_spans.contains(&span);
647
648        let mut span_labels = self
649            .span_labels
650            .iter()
651            .map(|&PrimarySpanLabel(span, ref label)| SpanLabel {
652                span,
653                is_primary: is_primary(span),
654                label: Some(label.clone()),
655            })
656            .collect::<Vec<_>>();
657
658        for &span in &self.primary_spans {
659            if !span_labels.iter().any(|sl| sl.span == span) {
660                span_labels.push(SpanLabel {
661                    span,
662                    is_primary: true,
663                    label: None,
664                });
665            }
666        }
667
668        span_labels
669    }
670}
671
672impl From<Span> for MultiSpan {
673    fn from(span: Span) -> MultiSpan {
674        MultiSpan::from_span(span)
675    }
676}
677
678impl From<Vec<Span>> for MultiSpan {
679    fn from(spans: Vec<Span>) -> MultiSpan {
680        MultiSpan::from_spans(spans)
681    }
682}
683
684pub const NO_EXPANSION: SyntaxContext = SyntaxContext::empty();
685
686/// Identifies an offset of a multi-byte character in a SourceFile
687#[cfg_attr(
688    any(feature = "rkyv-impl"),
689    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
690)]
691#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
692#[cfg_attr(feature = "rkyv-impl", repr(C))]
693#[derive(Copy, Clone, Eq, PartialEq, Debug)]
694pub struct MultiByteChar {
695    /// The absolute offset of the character in the SourceMap
696    pub pos: BytePos,
697    /// The number of bytes, >=2
698    pub bytes: u8,
699}
700
701impl MultiByteChar {
702    /// Computes the extra number of UTF-8 bytes necessary to encode a code
703    /// point, compared to UTF-16 encoding.
704    ///
705    /// 1, 2, and 3 UTF-8 bytes encode into 1 UTF-16 char, but 4 UTF-8 bytes
706    /// encode into 2.
707    pub fn byte_to_char_diff(&self) -> u8 {
708        if self.bytes == 4 {
709            2
710        } else {
711            self.bytes - 1
712        }
713    }
714}
715
716/// Identifies an offset of a non-narrow character in a SourceFile
717#[cfg_attr(
718    any(feature = "rkyv-impl"),
719    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
720)]
721#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
722#[cfg_attr(feature = "rkyv-impl", repr(u32))]
723#[derive(Copy, Clone, Eq, PartialEq, Debug)]
724pub enum NonNarrowChar {
725    /// Represents a zero-width character
726    ZeroWidth(BytePos),
727    /// Represents a wide (fullwidth) character
728    Wide(BytePos, usize),
729    /// Represents a tab character, represented visually with a width of 4
730    /// characters
731    Tab(BytePos),
732}
733
734impl NonNarrowChar {
735    fn new(pos: BytePos, width: usize) -> Self {
736        match width {
737            0 => NonNarrowChar::ZeroWidth(pos),
738            4 => NonNarrowChar::Tab(pos),
739            w => NonNarrowChar::Wide(pos, w),
740        }
741    }
742
743    /// Returns the absolute offset of the character in the SourceMap
744    pub fn pos(self) -> BytePos {
745        match self {
746            NonNarrowChar::ZeroWidth(p) | NonNarrowChar::Wide(p, _) | NonNarrowChar::Tab(p) => p,
747        }
748    }
749
750    /// Returns the width of the character, 0 (zero-width) or 2 (wide)
751    pub fn width(self) -> usize {
752        match self {
753            NonNarrowChar::ZeroWidth(_) => 0,
754            NonNarrowChar::Wide(_, width) => width,
755            NonNarrowChar::Tab(_) => 4,
756        }
757    }
758}
759
760impl Add<BytePos> for NonNarrowChar {
761    type Output = Self;
762
763    fn add(self, rhs: BytePos) -> Self {
764        match self {
765            NonNarrowChar::ZeroWidth(pos) => NonNarrowChar::ZeroWidth(pos + rhs),
766            NonNarrowChar::Wide(pos, width) => NonNarrowChar::Wide(pos + rhs, width),
767            NonNarrowChar::Tab(pos) => NonNarrowChar::Tab(pos + rhs),
768        }
769    }
770}
771
772impl Sub<BytePos> for NonNarrowChar {
773    type Output = Self;
774
775    fn sub(self, rhs: BytePos) -> Self {
776        match self {
777            NonNarrowChar::ZeroWidth(pos) => NonNarrowChar::ZeroWidth(pos - rhs),
778            NonNarrowChar::Wide(pos, width) => NonNarrowChar::Wide(pos - rhs, width),
779            NonNarrowChar::Tab(pos) => NonNarrowChar::Tab(pos - rhs),
780        }
781    }
782}
783
784/// A single source in the SourceMap.
785#[cfg_attr(
786    any(feature = "rkyv-impl"),
787    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
788)]
789#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
790#[cfg_attr(feature = "rkyv-impl", repr(C))]
791#[derive(Clone)]
792pub struct SourceFile {
793    /// The name of the file that the source came from. Source that doesn't
794    /// originate from files has names between angle brackets by convention,
795    /// e.g. `<anon>`
796    pub name: Lrc<FileName>,
797    /// True if the `name` field above has been modified by
798    /// `--remap-path-prefix`
799    pub name_was_remapped: bool,
800    /// The unmapped path of the file that the source came from.
801    /// Set to `None` if the `SourceFile` was imported from an external crate.
802    pub unmapped_path: Option<Lrc<FileName>>,
803    /// Indicates which crate this `SourceFile` was imported from.
804    pub crate_of_origin: u32,
805    /// The complete source code
806    pub src: BytesStr,
807    /// The source code's hash
808    pub src_hash: u128,
809    /// The start position of this source in the `SourceMap`
810    pub start_pos: BytePos,
811    /// The end position of this source in the `SourceMap`
812    pub end_pos: BytePos,
813    /// A hash of the filename, used for speeding up the incr. comp. hashing.
814    pub name_hash: u128,
815
816    lazy: CacheCell<SourceFileAnalysis>,
817}
818
819#[cfg_attr(
820    any(feature = "rkyv-impl"),
821    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
822)]
823#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
824#[cfg_attr(feature = "rkyv-impl", repr(C))]
825#[derive(Clone)]
826pub struct SourceFileAnalysis {
827    /// Locations of lines beginnings in the source code
828    pub lines: Vec<BytePos>,
829    /// Locations of multi-byte characters in the source code
830    pub multibyte_chars: Vec<MultiByteChar>,
831    /// Width of characters that are not narrow in the source code
832    pub non_narrow_chars: Vec<NonNarrowChar>,
833}
834
835impl fmt::Debug for SourceFile {
836    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
837        write!(fmt, "SourceFile({})", self.name)
838    }
839}
840
841impl SourceFile {
842    /// `src` should not have UTF8 BOM
843    pub fn new(
844        name: Lrc<FileName>,
845        name_was_remapped: bool,
846        unmapped_path: Lrc<FileName>,
847        src: BytesStr,
848        start_pos: BytePos,
849    ) -> SourceFile {
850        debug_assert_ne!(
851            start_pos,
852            BytePos::DUMMY,
853            "BytePos::DUMMY is reserved and `SourceFile` should not use it"
854        );
855
856        let src_hash = {
857            let mut hasher: StableHasher = StableHasher::new();
858            hasher.write(src.as_bytes());
859            hasher.finish()
860        };
861        let name_hash = {
862            let mut hasher: StableHasher = StableHasher::new();
863            name.hash(&mut hasher);
864            hasher.finish()
865        };
866        let end_pos = start_pos.to_usize() + src.len();
867
868        SourceFile {
869            name,
870            name_was_remapped,
871            unmapped_path: Some(unmapped_path),
872            crate_of_origin: 0,
873            src,
874            src_hash,
875            start_pos,
876            end_pos: SmallPos::from_usize(end_pos),
877            name_hash,
878            lazy: CacheCell::new(),
879        }
880    }
881
882    /// Return the BytePos of the beginning of the current line.
883    pub fn line_begin_pos(&self, pos: BytePos) -> BytePos {
884        let line_index = self.lookup_line(pos).unwrap();
885        let analysis = self.analyze();
886        analysis.lines[line_index]
887    }
888
889    /// Get a line from the list of pre-computed line-beginnings.
890    /// The line number here is 0-based.
891    pub fn get_line(&self, line_number: usize) -> Option<Cow<'_, str>> {
892        fn get_until_newline(src: &str, begin: usize) -> &str {
893            // We can't use `lines.get(line_number+1)` because we might
894            // be parsing when we call this function and thus the current
895            // line is the last one we have line info for.
896            let slice = &src[begin..];
897            match slice.find('\n') {
898                Some(e) => &slice[..e],
899                None => slice,
900            }
901        }
902
903        let begin = {
904            let analysis = self.analyze();
905            let line = analysis.lines.get(line_number)?;
906            let begin: BytePos = *line - self.start_pos;
907            begin.to_usize()
908        };
909
910        Some(Cow::from(get_until_newline(&self.src, begin)))
911    }
912
913    pub fn is_real_file(&self) -> bool {
914        self.name.is_real()
915    }
916
917    pub fn byte_length(&self) -> u32 {
918        self.end_pos.0 - self.start_pos.0
919    }
920
921    pub fn count_lines(&self) -> usize {
922        let analysis = self.analyze();
923        analysis.lines.len()
924    }
925
926    /// Find the line containing the given position. The return value is the
927    /// index into the `lines` array of this SourceFile, not the 1-based line
928    /// number. If the `source_file` is empty or the position is located before
929    /// the first line, `None` is returned.
930    pub fn lookup_line(&self, pos: BytePos) -> Option<usize> {
931        let analysis = self.analyze();
932        if analysis.lines.is_empty() {
933            return None;
934        }
935
936        let line_index = lookup_line(&analysis.lines, pos);
937        assert!(line_index < analysis.lines.len() as isize);
938        if line_index >= 0 {
939            Some(line_index as usize)
940        } else {
941            None
942        }
943    }
944
945    pub fn line_bounds(&self, line_index: usize) -> (BytePos, BytePos) {
946        if self.start_pos == self.end_pos {
947            return (self.start_pos, self.end_pos);
948        }
949
950        let analysis = self.analyze();
951
952        assert!(line_index < analysis.lines.len());
953        if line_index == (analysis.lines.len() - 1) {
954            (analysis.lines[line_index], self.end_pos)
955        } else {
956            (analysis.lines[line_index], analysis.lines[line_index + 1])
957        }
958    }
959
960    #[inline]
961    pub fn contains(&self, byte_pos: BytePos) -> bool {
962        byte_pos >= self.start_pos && byte_pos <= self.end_pos
963    }
964
965    pub fn analyze(&self) -> &SourceFileAnalysis {
966        self.lazy.get_or_init(|| {
967            let (lines, multibyte_chars, non_narrow_chars) =
968                analyze_source_file::analyze_source_file(&self.src[..], self.start_pos);
969            SourceFileAnalysis {
970                lines,
971                multibyte_chars,
972                non_narrow_chars,
973            }
974        })
975    }
976}
977
978// _____________________________________________________________________________
979// Pos, BytePos, CharPos
980//
981
982pub trait SmallPos {
983    fn from_usize(n: usize) -> Self;
984    fn to_usize(&self) -> usize;
985    fn from_u32(n: u32) -> Self;
986    fn to_u32(&self) -> u32;
987}
988
989/// A byte offset. Keep this small (currently 32-bits), as AST contains
990/// a lot of them.
991///
992///
993/// # Reserved
994///
995///  - 0 is reserved for dummy spans. It means `BytePos(0)` means the `BytePos`
996///    is synthesized by the compiler.
997///
998///  - Values larger than `u32::MAX - 2^16` are reserved for the comments.
999///
1000/// `u32::MAX` is special value used to generate source map entries.
1001#[derive(
1002    Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize, Default,
1003)]
1004#[serde(transparent)]
1005#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1006#[cfg_attr(
1007    any(feature = "rkyv-impl"),
1008    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1009)]
1010#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1011#[cfg_attr(feature = "rkyv-impl", repr(C))]
1012#[cfg_attr(feature = "shrink-to-fit", derive(shrink_to_fit::ShrinkToFit))]
1013pub struct BytePos(#[cfg_attr(feature = "__rkyv", rkyv(omit_bounds))] pub u32);
1014
1015impl BytePos {
1016    /// Dummy position. This is reserved for synthesized spans.
1017    pub const DUMMY: Self = BytePos(0);
1018    const MIN_RESERVED: Self = BytePos(DUMMY_RESERVE);
1019    /// Placeholders, commonly used where names are required, but the names are
1020    /// not referenced elsewhere.
1021    pub const PLACEHOLDER: Self = BytePos(u32::MAX - 2);
1022    /// Reserved for PURE comments. e.g. `/* #__PURE__ */`
1023    pub const PURE: Self = BytePos(u32::MAX - 1);
1024    /// Synthesized, but should be stored in a source map.
1025    pub const SYNTHESIZED: Self = BytePos(u32::MAX);
1026
1027    pub const fn is_reserved_for_comments(self) -> bool {
1028        self.0 >= Self::MIN_RESERVED.0 && self.0 != u32::MAX
1029    }
1030
1031    /// Returns `true`` if this is synthesized and has no relevant input source
1032    /// code.
1033    pub const fn is_dummy(self) -> bool {
1034        self.0 == 0
1035    }
1036
1037    pub const fn is_pure(self) -> bool {
1038        self.0 == Self::PURE.0
1039    }
1040
1041    pub const fn is_placeholder(self) -> bool {
1042        self.0 == Self::PLACEHOLDER.0
1043    }
1044
1045    /// Returns `true`` if this is explicitly synthesized or has relevant input
1046    /// source so can have a comment.
1047    pub const fn can_have_comment(self) -> bool {
1048        self.0 != 0
1049    }
1050}
1051
1052/// A character offset. Because of multibyte utf8 characters, a byte offset
1053/// is not equivalent to a character offset. The SourceMap will convert BytePos
1054/// values to CharPos values as necessary.
1055#[cfg_attr(
1056    any(feature = "rkyv-impl"),
1057    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1058)]
1059#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1060#[cfg_attr(feature = "rkyv-impl", repr(C))]
1061#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
1062pub struct CharPos(pub usize);
1063
1064// FIXME: Lots of boilerplate in these impls, but so far my attempts to fix
1065// have been unsuccessful
1066
1067impl SmallPos for BytePos {
1068    #[inline(always)]
1069    fn from_usize(n: usize) -> BytePos {
1070        BytePos(n as u32)
1071    }
1072
1073    #[inline(always)]
1074    fn to_usize(&self) -> usize {
1075        self.0 as usize
1076    }
1077
1078    #[inline(always)]
1079    fn from_u32(n: u32) -> BytePos {
1080        BytePos(n)
1081    }
1082
1083    #[inline(always)]
1084    fn to_u32(&self) -> u32 {
1085        self.0
1086    }
1087}
1088
1089impl Add for BytePos {
1090    type Output = BytePos;
1091
1092    #[inline(always)]
1093    fn add(self, rhs: BytePos) -> BytePos {
1094        BytePos((self.to_usize() + rhs.to_usize()) as u32)
1095    }
1096}
1097
1098impl Sub for BytePos {
1099    type Output = BytePos;
1100
1101    #[inline(always)]
1102    fn sub(self, rhs: BytePos) -> BytePos {
1103        BytePos((self.to_usize() - rhs.to_usize()) as u32)
1104    }
1105}
1106
1107impl SmallPos for CharPos {
1108    #[inline(always)]
1109    fn from_usize(n: usize) -> CharPos {
1110        CharPos(n)
1111    }
1112
1113    #[inline(always)]
1114    fn to_usize(&self) -> usize {
1115        self.0
1116    }
1117
1118    #[inline(always)]
1119    fn from_u32(n: u32) -> CharPos {
1120        CharPos(n as usize)
1121    }
1122
1123    #[inline(always)]
1124    fn to_u32(&self) -> u32 {
1125        self.0 as u32
1126    }
1127}
1128
1129impl Add for CharPos {
1130    type Output = CharPos;
1131
1132    #[inline(always)]
1133    fn add(self, rhs: CharPos) -> CharPos {
1134        CharPos(self.to_usize() + rhs.to_usize())
1135    }
1136}
1137
1138impl Sub for CharPos {
1139    type Output = CharPos;
1140
1141    #[inline(always)]
1142    fn sub(self, rhs: CharPos) -> CharPos {
1143        CharPos(self.to_usize() - rhs.to_usize())
1144    }
1145}
1146
1147// _____________________________________________________________________________
1148// Loc, LocWithOpt, SourceFileAndLine, SourceFileAndBytePos
1149//
1150
1151/// A source code location used for error reporting.
1152///
1153/// Note: This struct intentionally does not implement rkyv's archieve
1154/// to avoid redundant data copy (https://github.com/swc-project/swc/issues/5471)
1155/// source_map_proxy constructs plugin-side Loc instead with shared SourceFile
1156/// instance.
1157#[derive(Debug, Clone)]
1158pub struct Loc {
1159    /// Information about the original source
1160    pub file: Lrc<SourceFile>,
1161    /// The (1-based) line number
1162    pub line: usize,
1163    /// The (0-based) column offset
1164    pub col: CharPos,
1165    /// The (0-based) column offset when displayed
1166    pub col_display: usize,
1167}
1168
1169/// A struct to exchange `Loc` with omitting SourceFile as needed.
1170/// This is internal struct between plugins to the host, not a public interface.
1171#[cfg_attr(
1172    any(feature = "rkyv-impl"),
1173    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Debug, Clone)
1174)]
1175#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1176#[cfg_attr(feature = "rkyv-impl", repr(C))]
1177pub struct PartialLoc {
1178    pub source_file: Option<Lrc<SourceFile>>,
1179    pub line: usize,
1180    pub col: usize,
1181    pub col_display: usize,
1182}
1183
1184/// A source code location used as the result of `lookup_char_pos_adj`
1185// Actually, *none* of the clients use the filename *or* file field;
1186// perhaps they should just be removed.
1187#[derive(Debug)]
1188pub struct LocWithOpt {
1189    pub filename: Lrc<FileName>,
1190    pub line: usize,
1191    pub col: CharPos,
1192    pub file: Option<Lrc<SourceFile>>,
1193}
1194
1195// used to be structural records. Better names, anyone?
1196#[derive(Debug)]
1197pub struct SourceFileAndLine {
1198    pub sf: Lrc<SourceFile>,
1199    pub line: usize,
1200}
1201
1202#[cfg_attr(
1203    any(feature = "rkyv-impl"),
1204    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1205)]
1206#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1207#[cfg_attr(feature = "rkyv-impl", repr(C))]
1208#[derive(Debug)]
1209pub struct SourceFileAndBytePos {
1210    pub sf: Lrc<SourceFile>,
1211    pub pos: BytePos,
1212}
1213
1214#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1215#[cfg_attr(
1216    any(feature = "rkyv-impl"),
1217    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1218)]
1219#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1220#[cfg_attr(feature = "rkyv-impl", repr(C))]
1221pub struct LineInfo {
1222    /// Index of line, starting from 0.
1223    pub line_index: usize,
1224
1225    /// Column in line where span begins, starting from 0.
1226    pub start_col: CharPos,
1227
1228    /// Column in line where span ends, starting from 0, exclusive.
1229    pub end_col: CharPos,
1230}
1231
1232/// Used to create a `.map` file.
1233#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1234pub struct LineCol {
1235    /// Index of line, starting from 0.
1236    pub line: u32,
1237
1238    /// UTF-16 column in line, starting from 0.
1239    pub col: u32,
1240}
1241
1242/// A struct to represent lines of a source file.
1243///
1244/// Note: This struct intentionally does not implement rkyv's archieve
1245/// to avoid redundant data copy (https://github.com/swc-project/swc/issues/5471)
1246/// source_map_proxy constructs plugin-side Loc instead with shared SourceFile
1247/// instance.
1248pub struct FileLines {
1249    pub file: Lrc<SourceFile>,
1250    pub lines: Vec<LineInfo>,
1251}
1252
1253/// A struct to exchange `FileLines` with omitting SourceFile as needed.
1254/// This is internal struct between plugins to the host, not a public interface.
1255#[cfg_attr(
1256    any(feature = "rkyv-impl"),
1257    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Debug, Clone)
1258)]
1259#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1260#[cfg_attr(feature = "rkyv-impl", repr(C))]
1261pub struct PartialFileLines {
1262    pub file: Option<Lrc<SourceFile>>,
1263    pub lines: Vec<LineInfo>,
1264}
1265
1266// _____________________________________________________________________________
1267// SpanLinesError, SpanSnippetError, DistinctSources,
1268// MalformedSourceMapPositions
1269//
1270
1271pub type FileLinesResult = Result<FileLines, Box<SpanLinesError>>;
1272#[cfg(feature = "__plugin")]
1273pub type PartialFileLinesResult = Result<PartialFileLines, Box<SpanLinesError>>;
1274
1275#[derive(Clone, PartialEq, Eq, Debug)]
1276#[cfg_attr(
1277    any(feature = "rkyv-impl"),
1278    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1279)]
1280#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1281#[cfg_attr(feature = "rkyv-impl", repr(u32))]
1282pub enum SpanLinesError {
1283    IllFormedSpan(Span),
1284    DistinctSources(DistinctSources),
1285}
1286
1287#[derive(Clone, PartialEq, Eq, Debug)]
1288#[cfg_attr(
1289    any(feature = "rkyv-impl"),
1290    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1291)]
1292#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1293#[cfg_attr(feature = "rkyv-impl", repr(u32))]
1294pub enum SpanSnippetError {
1295    DummyBytePos,
1296    IllFormedSpan(Span),
1297    DistinctSources(DistinctSources),
1298    MalformedForSourcemap(MalformedSourceMapPositions),
1299    SourceNotAvailable { filename: FileName },
1300    LookupFailed(SourceMapLookupError),
1301}
1302
1303/// An error type for looking up source maps.
1304///
1305///
1306/// This type is small.
1307#[derive(Clone, PartialEq, Eq, Debug)]
1308#[cfg_attr(
1309    any(feature = "rkyv-impl"),
1310    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1311)]
1312#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1313#[cfg_attr(feature = "rkyv-impl", repr(u32))]
1314pub enum SourceMapLookupError {
1315    NoFileFor(BytePos),
1316}
1317
1318#[derive(Clone, PartialEq, Eq, Debug)]
1319#[cfg_attr(
1320    any(feature = "rkyv-impl"),
1321    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1322)]
1323#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1324#[cfg_attr(feature = "rkyv-impl", repr(C))]
1325pub struct FilePos(pub Lrc<FileName>, pub BytePos);
1326
1327#[derive(Clone, PartialEq, Eq, Debug)]
1328#[cfg_attr(
1329    any(feature = "rkyv-impl"),
1330    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1331)]
1332#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1333#[cfg_attr(feature = "rkyv-impl", repr(C))]
1334pub struct DistinctSources {
1335    pub begin: FilePos,
1336    pub end: FilePos,
1337}
1338
1339#[derive(Clone, PartialEq, Eq, Debug)]
1340#[cfg_attr(
1341    any(feature = "rkyv-impl"),
1342    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
1343)]
1344#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
1345#[cfg_attr(feature = "rkyv-impl", repr(C))]
1346pub struct MalformedSourceMapPositions {
1347    pub name: Lrc<FileName>,
1348    pub source_len: usize,
1349    pub begin_pos: BytePos,
1350    pub end_pos: BytePos,
1351}
1352
1353// Given a slice of line start positions and a position, returns the index of
1354// the line the position is on. Returns -1 if the position is located before
1355// the first line.
1356fn lookup_line(lines: &[BytePos], pos: BytePos) -> isize {
1357    match lines.binary_search(&pos) {
1358        Ok(line) => line as isize,
1359        Err(line) => line as isize - 1,
1360    }
1361}
1362
1363impl From<SourceMapLookupError> for Box<SpanSnippetError> {
1364    #[cold]
1365    fn from(err: SourceMapLookupError) -> Self {
1366        Box::new(SpanSnippetError::LookupFailed(err))
1367    }
1368}
1369
1370#[cfg(test)]
1371mod tests {
1372    use super::{lookup_line, BytePos, Span};
1373
1374    #[test]
1375    fn test_lookup_line() {
1376        let lines = &[BytePos(3), BytePos(17), BytePos(28)];
1377
1378        assert_eq!(lookup_line(lines, BytePos(0)), -1);
1379        assert_eq!(lookup_line(lines, BytePos(3)), 0);
1380        assert_eq!(lookup_line(lines, BytePos(4)), 0);
1381
1382        assert_eq!(lookup_line(lines, BytePos(16)), 0);
1383        assert_eq!(lookup_line(lines, BytePos(17)), 1);
1384        assert_eq!(lookup_line(lines, BytePos(18)), 1);
1385
1386        assert_eq!(lookup_line(lines, BytePos(28)), 2);
1387        assert_eq!(lookup_line(lines, BytePos(29)), 2);
1388    }
1389
1390    #[test]
1391    fn size_of_span() {
1392        assert_eq!(std::mem::size_of::<Span>(), 8);
1393    }
1394}