js_source_scopes/lib.rs
1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use std::fmt::Display;
5use std::ops::Range;
6
7mod name_resolver;
8mod scope_index;
9mod scope_name;
10mod source;
11mod swc;
12
13pub use name_resolver::NameResolver;
14pub use scope_index::{ScopeIndex, ScopeIndexError, ScopeLookupResult};
15pub use scope_name::{NameComponent, ScopeName};
16pub use source::{SourceContext, SourceContextError, SourcePosition};
17use swc_common::Spanned;
18
19/// The Scopes extracted from a piece of JS Code.
20pub type Scopes = Vec<(Range<u32>, Option<ScopeName>)>;
21
22/// Extracts function scopes from the given JS-like `src`.
23///
24/// The returned Vec includes the [`Range`] of the function scope, in byte offsets
25/// inside the `src`, and the corresponding function name. `None` in this case
26/// denotes a function scope for which no name could be inferred from the
27/// surrounding code, which can mostly happen for anonymous or arrow functions
28/// used as immediate callbacks.
29///
30/// The range includes the whole range of the function expression, including the
31/// leading `function` keyword, function argument parentheses and trailing brace
32/// in case there is one.
33/// The returned vector does not have a guaranteed sorting order, and is
34/// implementation dependent.
35///
36/// # Examples
37///
38/// ```
39/// let src = "const arrowFnExpr = (a) => a; function namedFnDecl() {}";
40/// // arrowFnExpr -^------^ ^------namedFnDecl------^
41/// let mut scopes: Vec<_> = js_source_scopes::extract_scope_names(src)
42/// .unwrap()
43/// .into_iter()
44/// .map(|res| {
45/// let components = res.1.map(|n| n.components().map(|c| {
46/// (c.text().to_string(), c.range())
47/// }).collect::<Vec<_>>());
48/// (res.0, components)
49/// }).collect();
50/// scopes.sort_by_key(|s| s.0.start);
51///
52/// let expected = vec![
53/// (20..28, Some(vec![(String::from("arrowFnExpr"), Some(6..17))])),
54/// (30..55, Some(vec![(String::from("namedFnDecl"),Some(39..50))])),
55/// ];
56/// assert_eq!(scopes, expected);
57/// ```
58#[tracing::instrument(level = "trace", skip_all)]
59pub fn extract_scope_names(src: &str) -> Result<Scopes, ParseError> {
60 let mut scopes = swc::parse_with_swc(src).map_err(|e| ParseError { inner: e })?;
61
62 // filter out empty names
63 for scope in &mut scopes {
64 if let Some(ref name) = scope.1 {
65 if name.components.is_empty() {
66 scope.1 = None;
67 }
68 }
69 }
70
71 Ok(scopes)
72}
73
74/// An error parsing the JS Source provided to [`extract_scope_names`].
75#[derive(Debug)]
76pub struct ParseError {
77 inner: swc::ParseError,
78}
79
80impl Display for ParseError {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 let span = self.inner.span();
83 f.write_fmt(format_args!(
84 "{}:{}:{}",
85 span.lo.0,
86 span.hi.0,
87 self.inner.kind().msg()
88 ))
89 }
90}
91
92impl std::error::Error for ParseError {}