vue_compiler_core/
util.rs

1use super::{
2    converter::{BaseConvertInfo, JsExpr as Js, VNodeIR},
3    flags::RuntimeHelper,
4    parser::{Directive, DirectiveArg, ElemProp, Element},
5    scanner::Attribute,
6};
7use std::{
8    borrow::{Borrow, BorrowMut},
9    cell::UnsafeCell,
10    marker::PhantomData,
11    ops::Deref,
12};
13
14pub mod rslint;
15mod v_str;
16pub use v_str::VStr;
17
18pub fn non_whitespace(c: char) -> bool {
19    !c.is_ascii_whitespace()
20}
21
22pub fn get_core_component(tag: &str) -> Option<RuntimeHelper> {
23    use RuntimeHelper as RH;
24    Some(match tag {
25        "Teleport" | "teleport" => RH::Teleport,
26        "Suspense" | "suspense" => RH::Suspense,
27        "KeepAlive" | "keep-alive" => RH::KeepAlive,
28        "BaseTransition" | "base-transition" => RH::BaseTransition,
29        _ => return None,
30    })
31}
32
33pub fn is_core_component(tag: &str) -> bool {
34    get_core_component(tag).is_some()
35}
36
37fn is_event_prop(prop: &str) -> bool {
38    let bytes = prop.as_bytes();
39    // equivalent to /^on[^a-z]/
40    bytes.len() > 2 && bytes.starts_with(b"on") && !bytes[3].is_ascii_lowercase()
41}
42
43pub fn is_mergeable_prop(prop: &str) -> bool {
44    prop == "class" || prop == "style" || is_event_prop(prop)
45}
46
47#[inline]
48fn not_js_identifier(c: char) -> bool {
49    !c.is_alphanumeric() && c != '$' && c != '_'
50}
51
52pub fn is_simple_identifier(s: VStr) -> bool {
53    let is_ident = |c| !not_js_identifier(c);
54    let raw = s.raw;
55    raw.chars().all(is_ident) && !raw.starts_with(|c: char| c.is_ascii_digit())
56}
57
58macro_rules! make_list {
59    ( $($id: ident),* ) => {
60        &[
61            $(stringify!($id)),*
62        ]
63    }
64}
65
66// use simple contains for small str array
67// benchmark shows linear scan takes at most 10ns
68// while phf or bsearch takes 30ns
69const ALLOWED_GLOBALS: &[&str] = make_list![
70    Infinity,
71    undefined,
72    NaN,
73    isFinite,
74    isNaN,
75    parseFloat,
76    parseInt,
77    decodeURI,
78    decodeURIComponent,
79    encodeURI,
80    encodeURIComponent,
81    Math,
82    Number,
83    Date,
84    Array,
85    Object,
86    Boolean,
87    String,
88    RegExp,
89    Map,
90    Set,
91    JSON,
92    Intl,
93    BigInt
94];
95pub fn is_global_allow_listed(s: &str) -> bool {
96    ALLOWED_GLOBALS.contains(&s)
97}
98
99// https://github.com/vuejs/rfcs/blob/master/active-rfcs/0008-render-function-api-change.md#special-reserved-props
100const RESERVED: &[&str] = make_list![
101    key,
102    ref,
103    onVnodeMounted,
104    onVnodeUpdated,
105    onVnodeUnmounted,
106    onVnodeBeforeMount,
107    onVnodeBeforeUpdate,
108    onVnodeBeforeUnmount
109];
110
111#[inline]
112pub fn is_reserved_prop(tag: &str) -> bool {
113    RESERVED.contains(&tag)
114}
115
116pub fn is_component_tag(tag: &str) -> bool {
117    tag == "component" || tag == "Component"
118}
119
120pub const fn yes(_: &str) -> bool {
121    true
122}
123pub const fn no(_: &str) -> bool {
124    false
125}
126
127pub fn get_vnode_call_helper(v: &VNodeIR<BaseConvertInfo>) -> RuntimeHelper {
128    use RuntimeHelper as RH;
129    if v.is_block {
130        return if v.is_component {
131            RH::CreateBlock
132        } else {
133            RH::CreateElementBlock
134        };
135    }
136    if v.is_component {
137        RH::CreateVNode
138    } else {
139        RH::CreateElementVNode
140    }
141}
142
143pub fn is_builtin_symbol(tag: &Js, helper: RuntimeHelper) -> bool {
144    if let Js::Symbol(r) = tag {
145        r == &helper
146    } else {
147        false
148    }
149}
150
151pub trait PropPattern {
152    fn matches(&self, name: &str) -> bool;
153}
154impl PropPattern for &str {
155    fn matches(&self, name: &str) -> bool {
156        name == *self
157    }
158}
159
160impl<F> PropPattern for F
161where
162    F: Fn(&str) -> bool,
163{
164    fn matches(&self, name: &str) -> bool {
165        self(name)
166    }
167}
168
169impl<const N: usize> PropPattern for [&'static str; N] {
170    fn matches(&self, name: &str) -> bool {
171        self.contains(&name)
172    }
173}
174
175type NameExp<'a> = Option<(&'a str, Option<VStr<'a>>)>;
176pub trait PropMatcher<'a> {
177    fn get_name_and_exp(prop: &ElemProp<'a>) -> NameExp<'a>;
178    fn get_ref<'b>(prop: &'b ElemProp<'a>) -> &'b Self;
179    fn take(prop: ElemProp<'a>) -> Self;
180    fn is_match<P>(p: &ElemProp<'a>, pat: &P, allow_empty: bool) -> bool
181    where
182        P: PropPattern,
183    {
184        Self::get_name_and_exp(p).map_or(false, |(name, exp)| {
185            pat.matches(name) && (allow_empty || !exp.map_or(true, |v| v.is_empty()))
186        })
187    }
188}
189
190pub fn is_bind_key<'a>(arg: &Option<DirectiveArg<'a>>, name: &str) -> bool {
191    get_bind_key(arg).map_or(false, |v| v == name)
192}
193
194fn get_bind_key<'a>(arg: &Option<DirectiveArg<'a>>) -> Option<&'a str> {
195    if let DirectiveArg::Static(name) = arg.as_ref()? {
196        Some(name)
197    } else {
198        None
199    }
200}
201
202impl<'a> PropMatcher<'a> for ElemProp<'a> {
203    fn get_name_and_exp(prop: &ElemProp<'a>) -> NameExp<'a> {
204        match prop {
205            ElemProp::Attr(Attribute { name, value, .. }) => {
206                let exp = value.as_ref().map(|v| v.content);
207                Some((name, exp))
208            }
209            ElemProp::Dir(dir @ Directive { name: "bind", .. }) => {
210                let name = get_bind_key(&dir.argument)?;
211                let exp = dir.expression.as_ref().map(|v| v.content);
212                Some((name, exp))
213            }
214            _ => None,
215        }
216    }
217    fn get_ref<'b>(prop: &'b ElemProp<'a>) -> &'b Self {
218        prop
219    }
220    fn take(prop: ElemProp<'a>) -> Self {
221        prop
222    }
223}
224
225impl<'a> PropMatcher<'a> for Directive<'a> {
226    fn get_name_and_exp(prop: &ElemProp<'a>) -> NameExp<'a> {
227        if let ElemProp::Dir(Directive {
228            name, expression, ..
229        }) = prop
230        {
231            let exp = expression.as_ref().map(|v| v.content);
232            Some((name, exp))
233        } else {
234            None
235        }
236    }
237    fn get_ref<'b>(prop: &'b ElemProp<'a>) -> &'b Self {
238        if let ElemProp::Dir(dir) = prop {
239            return dir;
240        }
241        unreachable!("invalid call")
242    }
243    fn take(prop: ElemProp<'a>) -> Self {
244        if let ElemProp::Dir(dir) = prop {
245            return dir;
246        }
247        unreachable!("invalid call")
248    }
249}
250
251pub struct PropFound<'a, E, M>
252where
253    E: Borrow<Element<'a>>,
254    M: PropMatcher<'a>,
255{
256    elem: E,
257    pos: usize,
258    m: PhantomData<&'a M>,
259}
260
261impl<'a, E, M> PropFound<'a, E, M>
262where
263    E: Borrow<Element<'a>>,
264    M: PropMatcher<'a>,
265{
266    fn new(elem: E, pos: usize) -> Option<Self> {
267        Some(Self {
268            elem,
269            pos,
270            m: PhantomData,
271        })
272    }
273    pub fn get_ref(&self) -> &M {
274        M::get_ref(&self.elem.borrow().properties[self.pos])
275    }
276}
277// take is only available when access is mutable
278impl<'a, E, M> PropFound<'a, E, M>
279where
280    E: BorrowMut<Element<'a>>,
281    M: PropMatcher<'a>,
282{
283    pub fn take(mut self) -> M {
284        // TODO: avoid O(n) behavior
285        M::take(self.elem.borrow_mut().properties.remove(self.pos))
286    }
287}
288
289type DirFound<'a, E> = PropFound<'a, E, Directive<'a>>;
290
291// sometimes mutable access to the element is not available so
292// Borrow is used to refine PropFound so `take` is optional
293pub fn dir_finder<'a, E, P>(elem: E, pat: P) -> PropFinder<'a, E, P, Directive<'a>>
294where
295    E: Borrow<Element<'a>>,
296    P: PropPattern,
297{
298    PropFinder::new(elem, pat)
299}
300
301pub fn find_dir<'a, E, P>(elem: E, pat: P) -> Option<DirFound<'a, E>>
302where
303    E: Borrow<Element<'a>>,
304    P: PropPattern,
305{
306    PropFinder::new(elem, pat).find()
307}
308
309pub fn find_dir_empty<'a, E, P>(elem: E, pat: P) -> Option<DirFound<'a, E>>
310where
311    E: Borrow<Element<'a>>,
312    P: PropPattern,
313{
314    PropFinder::new(elem, pat).allow_empty().find()
315}
316
317pub struct PropFinder<'a, E, P, M = ElemProp<'a>>
318where
319    E: Borrow<Element<'a>>,
320    P: PropPattern,
321    M: PropMatcher<'a>,
322{
323    elem: E,
324    pat: P,
325    allow_empty: bool,
326    filter: fn(&ElemProp<'a>) -> bool,
327    m: PhantomData<&'a M>,
328}
329
330impl<'a, E, P, M> PropFinder<'a, E, P, M>
331where
332    E: Borrow<Element<'a>>,
333    P: PropPattern,
334    M: PropMatcher<'a>,
335{
336    fn new(elem: E, pat: P) -> Self {
337        Self {
338            elem,
339            pat,
340            allow_empty: false,
341            filter: |_| true,
342            m: PhantomData,
343        }
344    }
345    fn is_match(&self, p: &ElemProp<'a>) -> bool {
346        M::is_match(p, &self.pat, self.allow_empty)
347    }
348    pub fn dynamic_only(self) -> Self {
349        Self {
350            filter: |p| matches!(p, ElemProp::Dir(..)),
351            ..self
352        }
353    }
354    pub fn find(self) -> Option<PropFound<'a, E, M>> {
355        let pos = self
356            .elem
357            .borrow()
358            .properties
359            .iter()
360            .position(|p| self.is_match(p) && (self.filter)(p))?;
361        PropFound::new(self.elem, pos)
362    }
363    pub fn allow_empty(self) -> Self {
364        Self {
365            allow_empty: true,
366            ..self
367        }
368    }
369}
370
371impl<'a, P> PropFinder<'a, Element<'a>, P, ElemProp<'a>>
372where
373    P: PropPattern + Copy,
374{
375    pub fn find_all(self) -> impl Iterator<Item = Result<ElemProp<'a>, ElemProp<'a>>> {
376        let PropFinder {
377            elem,
378            pat,
379            allow_empty,
380            ..
381        } = self;
382        elem.properties.into_iter().map(move |p| {
383            if ElemProp::is_match(&p, &pat, allow_empty) {
384                Ok(p)
385            } else {
386                Err(p)
387            }
388        })
389    }
390}
391
392pub fn find_prop<'a, E, P>(elem: E, pat: P) -> Option<PropFound<'a, E, ElemProp<'a>>>
393where
394    E: Borrow<Element<'a>>,
395    P: PropPattern,
396{
397    PropFinder::new(elem, pat).find()
398}
399
400pub fn prop_finder<'a, E, P>(elem: E, pat: P) -> PropFinder<'a, E, P>
401where
402    E: Borrow<Element<'a>>,
403    P: PropPattern,
404{
405    PropFinder::new(elem, pat)
406}
407
408// since std::lazy::Lazy is not stable
409// it is not thread safe, not Sync.
410// it is Send if F and T is Send
411pub struct Lazy<T, F = fn() -> T>(UnsafeCell<Result<T, Option<F>>>)
412where
413    F: FnOnce() -> T;
414
415impl<T, F> Lazy<T, F>
416where
417    F: FnOnce() -> T,
418{
419    pub fn new(f: F) -> Self {
420        Self(UnsafeCell::new(Err(Some(f))))
421    }
422}
423
424impl<T, F> Deref for Lazy<T, F>
425where
426    F: FnOnce() -> T,
427{
428    type Target = T;
429    fn deref(&self) -> &Self::Target {
430        let m = unsafe { &mut *self.0.get() };
431        let f = match m {
432            Ok(t) => return t,
433            Err(f) => f,
434        };
435        *m = Ok(f.take().unwrap()());
436        match m {
437            Ok(t) => t,
438            _ => panic!("unwrap Ok"),
439        }
440    }
441}
442
443#[cfg(test)]
444mod test {
445    use super::*;
446    use crate::parser::test::mock_element;
447
448    #[test]
449    fn test_find_dir() {
450        let e = mock_element("<p v-if=true/>");
451        let found = find_dir(&e, "if");
452        let found = found.expect("should found directive");
453        assert_eq!(found.get_ref().name, "if");
454        assert_eq!(e.properties.len(), 1);
455    }
456
457    #[test]
458    fn test_find_dir_mut() {
459        let mut e = mock_element("<p v-if=true/>");
460        let found = find_dir(&mut e, "if");
461        let found = found.expect("should found directive");
462        assert_eq!(found.get_ref().name, "if");
463        assert_eq!(found.take().name, "if");
464        assert!(e.properties.is_empty());
465    }
466
467    #[test]
468    fn test_find_empty_dir() {
469        let e = mock_element("<p v-if=true v-for>");
470        assert!(find_dir(&e, "if").is_some());
471        assert!(find_dir(&e, "for").is_none());
472        let found = dir_finder(&e, "for").allow_empty().find();
473        assert!(found.is_some());
474    }
475
476    #[test]
477    fn test_find_prop() {
478        let mut e = mock_element("<p :name=foo name=bar/>");
479        assert!(find_dir(&e, "name").is_none());
480        assert!(find_dir(&e, "bind").is_some());
481        // prop only looks at attr and v-bind
482        assert!(find_prop(&e, "bind").is_none());
483        find_prop(&mut e, "name").unwrap().take();
484        assert!(find_prop(&e, "bind").is_none());
485        find_prop(&mut e, "name").unwrap().take();
486        assert!(find_prop(&e, "name").is_none());
487    }
488
489    #[test]
490    fn find_prop_ignore_dynamic_bind() {
491        let e = mock_element("<p :[name]=foo/>");
492        assert!(find_dir(&e, "name").is_none());
493        assert!(find_dir(&e, "bind").is_some());
494        assert!(find_prop(&e, "name").is_none());
495    }
496    #[test]
497    fn find_dynamic_only_prop() {
498        let e = mock_element("<p name=foo/>");
499        assert!(prop_finder(&e, "name").dynamic_only().find().is_none());
500        let e = mock_element("<p v-bind:name=foo/>");
501        assert!(prop_finder(&e, "name").dynamic_only().find().is_some());
502        let e = mock_element("<p :name=foo/>");
503        assert!(prop_finder(&e, "name").dynamic_only().find().is_some());
504        let e = mock_element("<p :[name]=foo/>");
505        assert!(prop_finder(&e, "name").dynamic_only().find().is_none());
506    }
507    #[test]
508    fn prop_find_all() {
509        let e = mock_element("<p :name=foo name=bar :[name]=baz/>");
510        let a: Vec<_> = prop_finder(e, "name").find_all().collect();
511        assert_eq!(a.len(), 3);
512        assert!(a[0].is_ok());
513        assert!(a[1].is_ok());
514        assert!(a[2].is_err());
515    }
516
517    #[test]
518    fn layman_lazy() {
519        let mut test = 0;
520        let l = Lazy::new(|| {
521            test += 1;
522            (0..=100).sum::<i32>()
523        });
524        assert_eq!(*l, 5050);
525        assert_eq!(*l, 5050);
526        assert_eq!(test, 1);
527    }
528}