turbopack_node/source_map/
trace.rs

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