turbopack_node/source_map/
trace.rs

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