Skip to main content

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