Skip to main content

js_source_scopes/
name_resolver.rs

1use sourcemap::DecodedMap;
2
3use crate::{NameComponent, ScopeName, SourceContext};
4
5/// A structure for resolving [`ScopeName`]s in minified code to their original names
6/// using information contained in a [`DecodedMap`].
7pub struct NameResolver<'a, T> {
8    ctx: &'a SourceContext<T>,
9    sourcemap: &'a DecodedMap,
10}
11
12impl<'a, T: AsRef<str>> NameResolver<'a, T> {
13    /// Construct a new [`NameResolver`] from a [`SourceContext`] (for the minified source) and a [`DecodedMap`].
14    pub fn new(ctx: &'a SourceContext<T>, sourcemap: &'a DecodedMap) -> Self {
15        Self { ctx, sourcemap }
16    }
17
18    /// Resolves the given minified [`ScopeName`] to the original name.
19    ///
20    /// This tries to resolve each [`NameComponent`] by looking up its source
21    /// range in the [`DecodedMap`], using the token's `name` (as defined in the
22    /// sourcemap `names`) when possible.
23    pub fn resolve_name(&self, name: &ScopeName) -> String {
24        name.components()
25            .map(|c| self.try_map_token(c).unwrap_or_else(|| c.text()))
26            .collect::<String>()
27    }
28
29    fn try_map_token(&self, c: &NameComponent) -> Option<&str> {
30        let range = c.range()?;
31        let source_position = self.ctx.offset_to_position(range.start)?;
32        let token = self
33            .sourcemap
34            .lookup_token(source_position.line, source_position.column)?;
35
36        let is_exactish_match = token.get_dst_line() == source_position.line
37            && token.get_dst_col() >= source_position.column.saturating_sub(1);
38
39        if is_exactish_match {
40            if let Some(name) = token.get_name() {
41                return Some(name);
42            }
43
44            // If the token at the identifier position has no name, check the
45            // immediately preceding token. Some source map generators (e.g.
46            // TypeScript) attach the original function name to the `function`
47            // keyword token rather than the identifier that follows it.
48            // We only use the preceding token's name if it maps to the same
49            // original source position, indicating it's part of the same mapping.
50            if token.get_dst_col() > 0 {
51                if let Some(prev_token) = self
52                    .sourcemap
53                    .lookup_token(token.get_dst_line(), token.get_dst_col() - 1)
54                {
55                    if prev_token.get_src_id() == token.get_src_id()
56                        && prev_token.get_src_line() == token.get_src_line()
57                        && prev_token.get_src_col() == token.get_src_col()
58                    {
59                        return prev_token.get_name();
60                    }
61                }
62            }
63        }
64
65        None
66    }
67}