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 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
66const 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
99const 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}
277impl<'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 M::take(self.elem.borrow_mut().properties.remove(self.pos))
286 }
287}
288
289type DirFound<'a, E> = PropFound<'a, E, Directive<'a>>;
290
291pub 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
408pub 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 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}