vue_compiler_core/util/
rslint.rs

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    // range should be equal after removing trailing trivia(comment/whitespace)
20    // otherwise the text is not a single expression
21    parsed
22        .syntax()
23        .try_to()
24        .filter(|n: &Expr| is_sole_child(n, text.trim().len()))
25}
26
27// difference from descendants_with:
28// 1. has enter and exit to enable scop analysis
29// 2. enter/exit never stop walking. 「止まるんじゃねぇぞ…💃」
30pub 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
77// just allocate if complex expressions are used
78// users should not abuse expression in template
79// dont have time to optimize it :(
80struct 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        // arrow func has single param without parenthesis
159        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        // function expression has name property
166        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
190// only visit free variable, not bound ones like identifiers
191// declared in the scope/func param list
192pub 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}
214// TODO: thread local in Rust isn't that fast
215thread_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
228// copied from parse_expr
229fn 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    // TODO: set is TS
235    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, // key value pattern requires special handle
254    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        // kv.name() also contains Name, we need skip
273        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
283/// returns param and default argument's range in text
284pub 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        // assert!(parse_js_expr("{a = 4}").is_none()); // TODO
346    }
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            // arrow
369            ("() => {let a = 123}", vec![]),
370            ("() => {let {a} = b;}", vec!["b"]),
371            ("(c) => {let {a} = b;}", vec!["b"]),
372            // nested
373            ("(c) => { ((a) => {b})(); a; }", vec!["b", "a"]),
374            // fn expr
375            ("function (a) {}", vec![]),
376            ("function test(a) {test; foo;}", vec!["foo"]),
377            // object key shorthand
378            ("{a, b, c}", vec!["a", "b", "c"]),
379            // computed
380            ("{[a]: b, c: 1}", vec!["a", "b"]),
381            // rest
382            ("{...a, ...b}", vec!["a", "b"]),
383            // method
384            ("{test(a) {a; b}}", vec!["b"]),
385            ("{test: a => {a; b}}", vec!["b"]),
386            // getter, setter
387            ("{get test(a) {a; b}}", vec!["b"]),
388            ("{set test(a) {a; b}}", vec!["b"]),
389            // keyword
390            ("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}", vec!["a"]), // need https://github.com/rslint/rslint/issues/120
428            ("[a, b, c]", vec!["a", "b", "c"]),
429            // ts annotation
430            ("a: A", vec!["a"]),
431            // object destruct
432            ("{a: b = c}", vec!["b"]),
433            ("{a: b}", vec!["b"]),
434            // array
435            ("[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}