1use rslint_parser::{
2 self as rl,
3 ast::{self, Expr, ParameterList},
4 parse_expr, AstNode, SyntaxKind, SyntaxNodeExt,
5};
6use std::cell::RefCell;
7use std::ops::Range;
8
9fn is_sole_child<N: AstNode>(n: &N, expect_len: usize) -> bool {
10 let r: Range<usize> = Range::from(n.syntax().trimmed_range());
11 r.end - r.start == expect_len
12}
13
14pub fn parse_js_expr(text: &str) -> Option<Expr> {
15 let parsed = parse_expr(text, 0);
16 if !parsed.errors().is_empty() {
17 return None;
18 }
19 parsed
22 .syntax()
23 .try_to()
24 .filter(|n: &Expr| is_sole_child(n, text.trim().len()))
25}
26
27pub trait SyntaxWalker<T> {
31 fn enter(&mut self, n: &rl::SyntaxNode) -> T;
32 fn exit(&mut self, n: &rl::SyntaxNode, i: T);
33 fn walk(&mut self, node: &rl::SyntaxNode) {
34 let t = self.enter(node);
35 for child in node.children() {
36 self.walk(&child);
37 }
38 self.exit(node, t);
39 }
40}
41
42const FN_KINDS: &[SyntaxKind] = &[
43 SyntaxKind::ARROW_EXPR,
44 SyntaxKind::FN_DECL,
45 SyntaxKind::FN_EXPR,
46 SyntaxKind::METHOD,
47 SyntaxKind::GETTER,
48 SyntaxKind::SETTER,
49];
50
51pub enum FreeVar {
52 Ident(rl::SyntaxNode),
53 Shorthand(rl::SyntaxNode),
54}
55impl FreeVar {
56 fn syntax(&self) -> &rl::SyntaxNode {
57 use FreeVar::*;
58 match self {
59 Ident(n) | Shorthand(n) => n,
60 }
61 }
62 pub fn is_shorthand(&self) -> bool {
63 use FreeVar::*;
64 match self {
65 Shorthand(_) => true,
66 Ident(_) => false,
67 }
68 }
69 pub fn text(&self) -> String {
70 self.syntax().trimmed_text().into()
71 }
72 pub fn range(&self) -> Range<usize> {
73 self.syntax().trimmed_range().into()
74 }
75}
76
77struct FreeVarWalker<F: FnMut(FreeVar)> {
81 func: F,
82 bound_vars: Vec<rl::SyntaxText>,
83}
84
85impl<F> SyntaxWalker<usize> for FreeVarWalker<F>
86where
87 F: FnMut(FreeVar),
88{
89 fn enter(&mut self, node: &rl::SyntaxNode) -> usize {
90 use SyntaxKind as SK;
91 let kind = node.kind();
92 if kind == SK::NAME_REF {
93 self.emit_name_ref(node);
94 0
95 } else if kind == SK::IDENT_PROP {
96 self.emit_object_shorthad(node);
97 0
98 } else if kind == SK::BLOCK_STMT {
99 self.track_block_var(&node.to())
100 } else if FN_KINDS.contains(&kind) {
101 self.track_param(node)
102 } else {
103 0
104 }
105 }
106 fn exit(&mut self, node: &rl::SyntaxNode, c: usize) {
107 self.untrack_var(c);
108 }
109}
110impl<F> FreeVarWalker<F>
111where
112 F: FnMut(FreeVar),
113{
114 fn new(func: F) -> Self {
115 Self {
116 func,
117 bound_vars: vec![],
118 }
119 }
120 fn emit_name_ref(&mut self, name_ref: &rl::SyntaxNode) {
121 debug_assert!(name_ref.kind() == SyntaxKind::NAME_REF);
122 if self.bound_vars.contains(&name_ref.trimmed_text()) {
123 return;
124 }
125 (self.func)(FreeVar::Ident(name_ref.to_owned()));
126 }
127 fn emit_object_shorthad(&mut self, prop: &rl::SyntaxNode) {
128 debug_assert!(prop.kind() == SyntaxKind::IDENT_PROP);
129 let prop = prop.first_child().unwrap();
130 if self.bound_vars.contains(&prop.trimmed_text()) {
131 return;
132 }
133 (self.func)(FreeVar::Shorthand(prop));
134 }
135 #[inline(never)]
136 fn track_block_var(&mut self, node: &ast::BlockStmt) -> usize {
137 use ast::Decl;
138 let len = self.bound_vars.len();
139 let decls = node.stmts().filter_map(|s| s.syntax().try_to::<Decl>());
140 let mut collect = |d: &rl::SyntaxNode| {
141 collect_names(d, |n| {
142 self.bound_vars.push(n.syntax().trimmed_text());
143 false
144 })
145 };
146 decls.for_each(|decl| match decl {
147 Decl::VarDecl(v) => {
148 v.declared().for_each(|d| collect(d.syntax()));
149 }
150 decl => collect(decl.syntax()),
151 });
152 self.bound_vars.len() - len
153 }
154 #[inline(never)]
155 fn track_param(&mut self, node: &rl::SyntaxNode) -> usize {
156 debug_assert!(FN_KINDS.contains(&node.kind()));
157 let len = self.bound_vars.len();
158 if node.kind() == SyntaxKind::ARROW_EXPR {
160 let param = node.to::<ast::ArrowExpr>().params();
161 if let Some(ast::ArrowExprParams::Name(n)) = param {
162 self.bound_vars.push(n.syntax().trimmed_text());
163 }
164 }
165 else if node.kind() == SyntaxKind::FN_EXPR {
167 let name = node.to::<ast::FnExpr>().name();
168 if let Some(n) = name {
169 self.bound_vars.push(n.syntax().trimmed_text());
170 }
171 }
172 let list = node.children().find_map(|nd| nd.try_to::<ParameterList>());
173 if let Some(list) = list {
174 collect_names(list.syntax(), |nd| {
175 self.bound_vars.push(nd.syntax().trimmed_text());
176 false
177 });
178 }
179 self.bound_vars.len() - len
180 }
181 fn untrack_var(&mut self, c: usize) {
182 debug_assert!(self.bound_vars.len() >= c);
183 if c > 0 {
184 let new_len = self.bound_vars.len() - c;
185 self.bound_vars.truncate(new_len);
186 }
187 }
188}
189
190pub fn walk_free_variables<F>(root: Expr, func: F)
193where
194 F: FnMut(FreeVar),
195{
196 let mut walker = FreeVarWalker::new(func);
197 walker.walk(root.syntax())
198}
199
200pub fn parse_fn_param(text: &str) -> Option<ParameterList> {
201 let parsed = if text.starts_with('(') {
202 parse_param_impl(text, 0)
203 } else {
204 parse_param_normalized(text, 0)
205 };
206 if !parsed.errors().is_empty() {
207 return None;
208 }
209 parsed
210 .syntax()
211 .try_to()
212 .filter(|p: &ParameterList| is_sole_child(p, text.len() + 2))
213}
214thread_local! {
216 static STR_CACHE: RefCell<String> = RefCell::new(String::with_capacity(50));
217}
218fn parse_param_normalized(text: &str, file_id: usize) -> rl::Parse<ParameterList> {
219 use std::fmt::Write;
220 STR_CACHE.with(|sc| {
221 let mut s = sc.borrow_mut();
222 s.clear();
223 write!(s, "({})", text).unwrap();
224 parse_param_impl(&*s, file_id)
225 })
226}
227
228fn parse_param_impl(text: &str, file_id: usize) -> rl::Parse<ParameterList> {
230 let (tokens, mut errors) = rl::tokenize(text, file_id);
231 let tok_source = rl::TokenSource::new(text, &tokens);
232 let mut tree_sink = rl::LosslessTreeSink::new(text, &tokens);
233
234 let syntax = rl::Syntax {
236 file_kind: rl::FileKind::TypeScript,
237 ..Default::default()
238 };
239 let mut parser = rl::Parser::new(tok_source, file_id, syntax);
240 rl::syntax::decl::formal_parameters(&mut parser);
241 let (events, p_diags) = parser.finish();
242 errors.extend(p_diags);
243 rl::process(&mut tree_sink, events, errors);
244 let (green, parse_errors) = tree_sink.finish();
245 rl::Parse::new(green, parse_errors)
246}
247
248const PATTERNS: &[SyntaxKind] = &[
249 SyntaxKind::OBJECT_PATTERN,
250 SyntaxKind::ARRAY_PATTERN,
251 SyntaxKind::ASSIGN_PATTERN,
252 SyntaxKind::REST_PATTERN,
253 SyntaxKind::KEY_VALUE_PATTERN, SyntaxKind::SINGLE_PATTERN,
255];
256fn collect_names<F>(node: &rl::SyntaxNode, mut f: F)
257where
258 F: FnMut(ast::Name) -> bool,
259{
260 node.descendants_with(&mut |d| collect_one_name(d, &mut f))
261}
262fn collect_one_name<F>(node: &rl::SyntaxNode, mut f: F) -> bool
263where
264 F: FnMut(ast::Name) -> bool,
265{
266 let kind = node.kind();
267 if kind == SyntaxKind::NAME {
268 let parent = match node.parent() {
269 Some(prt) => prt,
270 None => return f(node.to()),
271 };
272 if parent.kind() == SyntaxKind::KEY_VALUE_PATTERN {
274 false
275 } else {
276 f(node.to())
277 }
278 } else {
279 PATTERNS.contains(&kind)
280 }
281}
282
283pub fn walk_param_and_default_arg<F>(list: ParameterList, mut f: F)
285where
286 F: FnMut(Range<usize>, bool),
287{
288 list.syntax().descendants_with(&mut |d| {
289 if d.is::<Expr>() {
290 f(Range::from(d.text_range()), false);
291 false
292 } else {
293 collect_one_name(d, |name| {
294 f(Range::from(name.range()), true);
295 false
296 })
297 }
298 })
299}
300
301#[cfg(test)]
302mod test {
303 use super::*;
304 use crate::cast;
305 use rslint_parser::ast::{BinOp, IfStmt};
306
307 #[test]
308 #[should_panic]
309 fn test_panic_wrong_cast() {
310 let a = parse_expr("a + b", 0);
311 let b = a.syntax().to::<IfStmt>();
312 }
313 #[test]
314 fn test_no_panic() {
315 let a = parse_js_expr("a + b").unwrap();
316 let expr = cast!(a, Expr::BinExpr);
317 let a = expr.lhs().unwrap();
318 let b = expr.rhs().unwrap();
319 assert_eq!(a.syntax().text(), "a");
320 assert_eq!(expr.op().unwrap(), BinOp::Plus);
321 assert_eq!(b.syntax().text(), "b");
322 }
323
324 #[test]
325 fn test_syntax_range() {
326 let s = " a + b";
327 let a = parse_js_expr(s).unwrap();
328 let expr = cast!(a, Expr::BinExpr);
329 let a = expr.lhs().unwrap();
330 let b = expr.rhs().unwrap();
331 assert_eq!(&s[a.range()], "a");
332 assert_eq!(expr.op().unwrap(), BinOp::Plus);
333 assert_eq!(&s[b.range()], "b");
334 }
335 #[test]
336 fn test_invalid_expr() {
337 assert!(parse_js_expr("(a + b + c, d, e,f)").is_some());
338 assert!(parse_js_expr("a..b").is_none());
339 assert!(parse_js_expr("a // b").is_none());
340 assert!(parse_js_expr("a b").is_none());
341 assert!(parse_js_expr(" a + b ").is_some());
342 assert!(parse_js_expr("a **** b").is_none());
343 assert!(parse_js_expr("a; ddd;").is_none());
344 assert!(parse_js_expr("if (a) {b} else {c}").is_none());
345 }
347
348 fn walk_ident(s: &str) -> Vec<String> {
349 let expr = parse_js_expr(s).unwrap();
350 let mut ret = vec![];
351 walk_free_variables(expr, |name_ref| {
352 ret.push(name_ref.text());
353 });
354 ret
355 }
356
357 #[test]
358 fn test_walk_identifier() {
359 let cases = [
360 ("a(b)", vec!["a", "b"]),
361 ("a.call(b)", vec!["a", "b"]),
362 ("a.b", vec!["a"]),
363 ("a || b", vec!["a", "b"]),
364 ("a + b(c.d)", vec!["a", "b", "c"]),
365 ("a ? b : c", vec!["a", "b", "c"]),
366 ("a(b + 1, {c: d})", vec!["a", "b", "d"]),
367 ("a, a, a", vec!["a", "a", "a"]),
368 ("() => {let a = 123}", vec![]),
370 ("() => {let {a} = b;}", vec!["b"]),
371 ("(c) => {let {a} = b;}", vec!["b"]),
372 ("(c) => { ((a) => {b})(); a; }", vec!["b", "a"]),
374 ("function (a) {}", vec![]),
376 ("function test(a) {test; foo;}", vec!["foo"]),
377 ("{a, b, c}", vec!["a", "b", "c"]),
379 ("{[a]: b, c: 1}", vec!["a", "b"]),
381 ("{...a, ...b}", vec!["a", "b"]),
383 ("{test(a) {a; b}}", vec!["b"]),
385 ("{test: a => {a; b}}", vec!["b"]),
386 ("{get test(a) {a; b}}", vec!["b"]),
388 ("{set test(a) {a; b}}", vec!["b"]),
389 ("true, false, null, this", vec![]),
391 ];
392 for (src, expect) in cases {
393 assert_eq!(walk_ident(src), expect);
394 }
395 }
396
397 #[test]
398 fn test_fn_param() {
399 assert!(parse_fn_param("abc").is_some());
400 assert!(parse_fn_param("a + b").is_none());
401 assert!(parse_fn_param("a, b").is_some());
402 assert!(parse_fn_param("{a, b = 123}").is_some());
403 assert!(parse_fn_param("{a, ").is_none());
404 assert!(parse_fn_param(" a={b: 3} ").is_some());
405 assert!(parse_fn_param(" a={b: 3 ").is_none());
406 assert!(parse_fn_param(" ").is_some());
407 }
408
409 fn walk_param(s: &str) -> Vec<String> {
410 let expr = parse_fn_param(s).unwrap();
411 let mut ret = vec![];
412 collect_names(expr.syntax(), |name| {
413 ret.push(name.text());
414 true
415 });
416 ret
417 }
418
419 #[test]
420 fn test_walk_fn_param() {
421 let cases = [
422 ("a={c}", vec!["a"]),
423 ("a, b", vec!["a", "b"]),
424 ("a = (b) => { var a = 123}", vec!["a"]),
425 ("a=b", vec!["a"]),
426 ("{a, b, c}", vec!["a", "b", "c"]),
427 ("[a, b, c]", vec!["a", "b", "c"]),
429 ("a: A", vec!["a"]),
431 ("{a: b = c}", vec!["b"]),
433 ("{a: b}", vec!["b"]),
434 ("[a, b]", vec!["a", "b"]),
436 ("[a=c, b]", vec!["a", "b"]),
437 ];
438 for (src, expect) in cases {
439 assert_eq!(walk_param(src), expect);
440 }
441 }
442}