turbopack_node/source_map/
trace.rs

1use std::{borrow::Cow, fmt::Display};
2
3use bincode::{
4    Decode, Encode,
5    de::Decoder,
6    enc::Encoder,
7    error::{DecodeError, EncodeError},
8    impl_borrow_decode,
9};
10use serde::{Deserialize, Serialize};
11use turbopack_core::source_map::{SourceMap, Token};
12use turbopack_ecmascript::magic_identifier::unmangle_identifiers;
13
14/// An individual stack frame, as parsed by the stacktrace-parser npm module.
15///
16/// Line and column can be None if the frame is anonymous.
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
18pub struct StackFrame<'a> {
19    pub file: Cow<'a, str>,
20    #[serde(rename = "lineNumber")]
21    pub line: Option<u32>,
22    pub column: Option<u32>,
23    #[serde(rename = "methodName")]
24    pub name: Option<Cow<'a, str>>,
25}
26
27impl<'a> StackFrame<'a> {
28    pub fn unmangle_identifiers<T: Display>(&self, magic: impl Fn(String) -> T) -> StackFrame<'_> {
29        StackFrame {
30            file: Cow::Borrowed(self.file.as_ref()),
31            line: self.line,
32            column: self.column,
33            name: self
34                .name
35                .as_ref()
36                .map(|n| unmangle_identifiers(n.as_ref(), magic)),
37        }
38    }
39
40    pub fn with_path<'b>(&'a self, path: &'b str) -> StackFrame<'b>
41    where
42        'a: 'b,
43    {
44        StackFrame {
45            file: Cow::Borrowed(path),
46            line: self.line,
47            column: self.column,
48            name: self.name.as_ref().map(|n| Cow::Borrowed(n.as_ref())),
49        }
50    }
51
52    pub fn with_name<'b>(&'a self, name: Option<&'b str>) -> StackFrame<'b>
53    where
54        'a: 'b,
55    {
56        StackFrame {
57            file: Cow::Borrowed(self.file.as_ref()),
58            line: self.line,
59            column: self.column,
60            name: name.map(Cow::Borrowed),
61        }
62    }
63
64    pub fn get_pos(&self) -> Option<(u32, u32)> {
65        self.line.zip(self.column)
66    }
67}
68
69impl Display for StackFrame<'_> {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        match self.get_pos() {
72            Some((l, c)) => match &self.name.as_deref() {
73                None | Some("<unknown>") | Some("(anonymous)") => {
74                    write!(f, "{}:{}:{}", self.file, l, c)
75                }
76                Some(n) => write!(f, "{} ({}:{}:{})", n, self.file, l, c),
77            },
78            None => write!(f, "{}", self.file),
79        }
80    }
81}
82
83impl Encode for StackFrame<'_> {
84    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
85        self.file.encode(encoder)?;
86        self.line.encode(encoder)?;
87        self.column.encode(encoder)?;
88        self.name.encode(encoder)?;
89        Ok(())
90    }
91}
92
93// needs a manual implementation because the derive macro doesn't handle the lifetime correctly
94impl<Context> Decode<Context> for StackFrame<'_> {
95    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
96        Ok(Self {
97            file: Decode::decode(decoder)?,
98            line: Decode::decode(decoder)?,
99            column: Decode::decode(decoder)?,
100            name: Decode::decode(decoder)?,
101        })
102    }
103}
104
105impl_borrow_decode!(StackFrame<'_>);
106
107/// The result of performing a source map trace.
108#[derive(Debug)]
109pub enum TraceResult {
110    NotFound,
111    Found(StackFrame<'static>),
112}
113
114/// Traces the line/column through the source map into its original
115/// position.
116///
117/// This method is god-awful slow. We're getting the content
118/// of a .map file, which means we're serializing all of the individual
119/// sections into a string and concatenating, taking that and
120/// deserializing into a DecodedMap, and then querying it. Besides being a
121/// memory hog, it'd be so much faster if we could just directly access
122/// the individual sections of the JS file's map without the
123/// serialization.
124pub fn trace_source_map(
125    map: &SourceMap,
126    line: u32,
127    column: u32,
128    name: Option<&str>,
129) -> TraceResult {
130    let token = map.lookup_token(line.saturating_sub(1), column.saturating_sub(1));
131    match token {
132        Token::Original(t) => TraceResult::Found(StackFrame {
133            file: Cow::Owned(t.original_file.into_owned()),
134            line: Some(t.original_line.saturating_add(1)),
135            column: Some(t.original_column.saturating_add(1)),
136            name: t
137                .name
138                .clone()
139                .map(|v| v.into_owned())
140                .or_else(|| name.map(ToString::to_string))
141                .map(Cow::Owned),
142        }),
143        _ => TraceResult::NotFound,
144    }
145}