Skip to content

Commit eccbd65

Browse files
committed
feat: Handle getters/setters/non-ident properties
1 parent 88a796b commit eccbd65

File tree

4 files changed

+176
-17
lines changed

4 files changed

+176
-17
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub type Scopes = Vec<(Range<u32>, Option<ScopeName>)>;
3939
/// let src = "const arrowFnExpr = (a) => a; function namedFnDecl() {}";
4040
/// // arrowFnExpr -^------^ ^------namedFnDecl------^
4141
/// let mut scopes: Vec<_> = js_source_scopes::extract_scope_names(src)
42+
/// .unwrap()
4243
/// .into_iter()
4344
/// .map(|res| {
4445
/// let components = res.1.map(|n| n.components().map(|c| {

src/scope_name.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use std::collections::VecDeque;
23
use std::fmt::Display;
34
use std::ops::Range;
@@ -60,9 +61,9 @@ impl NameComponent {
6061
}
6162
}
6263

63-
pub(crate) fn interp(s: &'static str) -> Self {
64+
pub(crate) fn interp(s: impl Into<Cow<'static, str>>) -> Self {
6465
Self {
65-
inner: NameComponentInner::Interpolation(s),
66+
inner: NameComponentInner::Interpolation(s.into()),
6667
}
6768
}
6869
pub(crate) fn ident(ident: ast::Ident) -> Self {
@@ -74,6 +75,6 @@ impl NameComponent {
7475

7576
#[derive(Debug)]
7677
pub(crate) enum NameComponentInner {
77-
Interpolation(&'static str),
78+
Interpolation(Cow<'static, str>),
7879
SourceIdentifierToken(ast::Ident),
7980
}

src/swc.rs

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ use crate::Scopes;
1010

1111
pub(crate) use swc_ecma_parser::error::Error as ParseError;
1212

13-
// TODO:
14-
// - getters / setters
15-
// - maybe even computed properties?
16-
1713
pub fn parse_with_swc(src: &str) -> Result<Scopes, ParseError> {
1814
let syntax = tracing::trace_span!("parsing source").in_scope(|| {
1915
let input = StringInput::new(src, BytePos(0), BytePos(src.len() as u32));
@@ -116,6 +112,46 @@ impl VisitAstPath for ScopeCollector {
116112

117113
node.visit_children_with_path(self, path);
118114
}
115+
116+
fn visit_getter_prop<'ast: 'r, 'r>(
117+
&mut self,
118+
node: &'ast ast::GetterProp,
119+
path: &mut AstNodePath<'r>,
120+
) {
121+
let mut name = match infer_name_from_ctx(path) {
122+
Some(mut scope_name) => {
123+
scope_name.components.push_back(NameComponent::interp("."));
124+
scope_name
125+
}
126+
None => ScopeName::new(),
127+
};
128+
name.components.push_back(prop_name_to_component(&node.key));
129+
name.components.push_front(NameComponent::interp("get "));
130+
131+
self.scopes.push((convert_span(node.span), Some(name)));
132+
133+
node.visit_children_with_path(self, path);
134+
}
135+
136+
fn visit_setter_prop<'ast: 'r, 'r>(
137+
&mut self,
138+
node: &'ast ast::SetterProp,
139+
path: &mut AstNodePath<'r>,
140+
) {
141+
let mut name = match infer_name_from_ctx(path) {
142+
Some(mut scope_name) => {
143+
scope_name.components.push_back(NameComponent::interp("."));
144+
scope_name
145+
}
146+
None => ScopeName::new(),
147+
};
148+
name.components.push_back(prop_name_to_component(&node.key));
149+
name.components.push_front(NameComponent::interp("set "));
150+
151+
self.scopes.push((convert_span(node.span), Some(name)));
152+
153+
node.visit_children_with_path(self, path);
154+
}
119155
}
120156

121157
/// Uses either the provided [`ast::Ident`] or infers the name from the `path`.
@@ -133,6 +169,7 @@ fn name_from_ident_or_ctx(ident: Option<ast::Ident>, path: &AstNodePath) -> Opti
133169
/// Tries to infer a name by walking up the path of ancestors.
134170
fn infer_name_from_ctx(path: &AstNodePath) -> Option<ScopeName> {
135171
let mut scope_name = ScopeName::new();
172+
let mut kind = ast::MethodKind::Method;
136173

137174
fn push_sep(name: &mut ScopeName) {
138175
if !name.components.is_empty() {
@@ -150,27 +187,33 @@ fn infer_name_from_ctx(path: &AstNodePath) -> Option<ScopeName> {
150187
// have part of the name.
151188
Parent::ClassExpr(class_expr, _) => {
152189
if let Some(ident) = &class_expr.ident {
190+
push_sep(&mut scope_name);
153191
scope_name
154192
.components
155193
.push_front(NameComponent::ident(ident.clone()));
194+
195+
prefix_getters_setters(kind, &mut scope_name);
196+
197+
return Some(scope_name);
156198
}
157199
}
158200
Parent::ClassDecl(class_decl, _) => {
159201
push_sep(&mut scope_name);
160202
scope_name
161203
.components
162204
.push_front(NameComponent::ident(class_decl.ident.clone()));
205+
206+
prefix_getters_setters(kind, &mut scope_name);
207+
163208
return Some(scope_name);
164209
}
165210

166211
// An object literal member:
167212
// `{ $name() ... }`
168213
Parent::MethodProp(method, _) => {
169-
if let Some(ident) = method.key.as_ident() {
170-
scope_name
171-
.components
172-
.push_front(NameComponent::ident(ident.clone()));
173-
}
214+
scope_name
215+
.components
216+
.push_front(prop_name_to_component(&method.key));
174217
}
175218

176219
// An object literal property:
@@ -186,11 +229,11 @@ fn infer_name_from_ctx(path: &AstNodePath) -> Option<ScopeName> {
186229
// A class method:
187230
// `class { $name() ... }`
188231
Parent::ClassMethod(method, _) => {
189-
if let Some(ident) = method.key.as_ident() {
190-
scope_name
191-
.components
192-
.push_front(NameComponent::ident(ident.clone()));
193-
}
232+
scope_name
233+
.components
234+
.push_front(prop_name_to_component(&method.key));
235+
236+
kind = method.kind;
194237
}
195238

196239
// A private class method:
@@ -210,6 +253,9 @@ fn infer_name_from_ctx(path: &AstNodePath) -> Option<ScopeName> {
210253
scope_name
211254
.components
212255
.push_front(NameComponent::ident(ident.id.clone()));
256+
257+
prefix_getters_setters(kind, &mut scope_name);
258+
213259
return Some(scope_name);
214260
}
215261
}
@@ -224,6 +270,8 @@ fn infer_name_from_ctx(path: &AstNodePath) -> Option<ScopeName> {
224270
expr_name.components.append(&mut scope_name.components);
225271
scope_name.components = expr_name.components;
226272

273+
prefix_getters_setters(kind, &mut scope_name);
274+
227275
return Some(scope_name);
228276
}
229277
}
@@ -233,6 +281,9 @@ fn infer_name_from_ctx(path: &AstNodePath) -> Option<ScopeName> {
233281
scope_name
234282
.components
235283
.push_front(NameComponent::ident(ident.id.clone()));
284+
285+
prefix_getters_setters(kind, &mut scope_name);
286+
236287
return Some(scope_name);
237288
}
238289
ast::Pat::Expr(expr) => {
@@ -242,6 +293,8 @@ fn infer_name_from_ctx(path: &AstNodePath) -> Option<ScopeName> {
242293
expr_name.components.append(&mut scope_name.components);
243294
scope_name.components = expr_name.components;
244295

296+
prefix_getters_setters(kind, &mut scope_name);
297+
245298
return Some(scope_name);
246299
}
247300
}
@@ -256,6 +309,18 @@ fn infer_name_from_ctx(path: &AstNodePath) -> Option<ScopeName> {
256309
None
257310
}
258311

312+
fn prefix_getters_setters(kind: ast::MethodKind, scope_name: &mut ScopeName) {
313+
match kind {
314+
ast::MethodKind::Getter => scope_name
315+
.components
316+
.push_front(NameComponent::interp("get ")),
317+
ast::MethodKind::Setter => scope_name
318+
.components
319+
.push_front(NameComponent::interp("set ")),
320+
_ => {}
321+
}
322+
}
323+
259324
/// Returns a [`ScopeName`] corresponding to the given [`ast::Expr`].
260325
///
261326
/// This is only possible if the expression is an identifier or a member expression.
@@ -291,3 +356,13 @@ fn infer_name_from_expr(mut expr: &ast::Expr) -> Option<ScopeName> {
291356
}
292357
}
293358
}
359+
360+
fn prop_name_to_component(prop: &ast::PropName) -> NameComponent {
361+
match prop {
362+
ast::PropName::Ident(ref i) => NameComponent::ident(i.clone()),
363+
ast::PropName::Str(s) => NameComponent::interp(format!("<\"{}\">", s.value)),
364+
ast::PropName::Num(n) => NameComponent::interp(format!("<{}>", n)),
365+
ast::PropName::Computed(_) => NameComponent::interp("<computed property name>"),
366+
ast::PropName::BigInt(i) => NameComponent::interp(format!("<{}n>", i.value)),
367+
}
368+
}

tests/extract.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,85 @@ fn extract_method_names() {
121121
];
122122
assert_eq!(scopes, expected);
123123
}
124+
125+
#[test]
126+
fn extract_class_getter_setter() {
127+
let src = r#"
128+
class A {
129+
get foo() {}
130+
set foo(x) {}
131+
}
132+
"#;
133+
134+
let scopes = extract_scope_names(src).unwrap();
135+
let scopes = scope_strs(scopes);
136+
137+
let expected = [
138+
(7..67, Some("new A".into())),
139+
(25..37, Some("get A.foo".into())),
140+
(46..59, Some("set A.foo".into())),
141+
];
142+
assert_eq!(scopes, expected);
143+
}
144+
145+
#[test]
146+
fn extract_object_getter_setter() {
147+
let src = r#"
148+
a = {
149+
get foo() {},
150+
set foo(x) {}
151+
}
152+
"#;
153+
154+
let scopes = extract_scope_names(src).unwrap();
155+
let scopes = scope_strs(scopes);
156+
157+
let expected = [
158+
(21..33, Some("get a.foo".into())),
159+
(43..56, Some("set a.foo".into())),
160+
];
161+
assert_eq!(scopes, expected);
162+
}
163+
164+
#[test]
165+
fn extract_object_weird_properties() {
166+
let src = r#"
167+
a = {
168+
["foo" + 123]() {},
169+
1.7() {},
170+
"bar"() {},
171+
1n() {}
172+
}
173+
"#;
174+
175+
let scopes = extract_scope_names(src).unwrap();
176+
let scopes = scope_strs(scopes);
177+
178+
let expected = [
179+
(21..39, Some("a.<computed property name>".into())),
180+
(49..57, Some("a.<1.7>".into())),
181+
(67..77, Some("a.<\"bar\">".into())),
182+
(87..94, Some("a.<1n>".into())),
183+
];
184+
assert_eq!(scopes, expected);
185+
}
186+
187+
#[test]
188+
fn extract_named_class_expr() {
189+
let src = r#"
190+
a = class B {
191+
foo() {}
192+
get bar() {}
193+
}
194+
"#;
195+
196+
let scopes = extract_scope_names(src).unwrap();
197+
let scopes = scope_strs(scopes);
198+
199+
let expected = [
200+
(11..68, Some("new B".into())),
201+
(30..38, Some("B.foo".into())),
202+
(48..60, Some("get B.bar".into())),
203+
];
204+
assert_eq!(scopes, expected);
205+
}

0 commit comments

Comments
 (0)