1use super::{
18 error::{CompilationError, CompilationErrorKind as ErrorKind, ErrorHandler},
19 scanner::{Attribute, AttributeValue, Tag, TextMode, Token, TokenSource},
20 util::{find_dir, is_core_component, no, non_whitespace, yes, VStr},
21 Name, Namespace, SourceLocation,
22};
23#[cfg(feature = "serde")]
24use serde::Serialize;
25use smallvec::{smallvec, SmallVec};
26use std::ops::Deref;
27
28#[cfg_attr(feature = "serde", derive(Serialize))]
29pub enum AstNode<'a> {
30 Element(Element<'a>),
31 Text(TextNode<'a>),
32 Interpolation(SourceNode<'a>),
33 Comment(SourceNode<'a>),
34}
35
36impl<'a> AstNode<'a> {
37 pub fn get_element(&self) -> Option<&Element<'a>> {
38 match self {
39 AstNode::Element(e) => Some(e),
40 _ => None,
41 }
42 }
43 pub fn get_element_mut(&mut self) -> Option<&mut Element<'a>> {
44 match self {
45 AstNode::Element(e) => Some(e),
46 _ => None,
47 }
48 }
49 pub fn into_element(self) -> Element<'a> {
50 match self {
51 AstNode::Element(e) => e,
52 _ => panic!("call into_element on non-element AstNode"),
53 }
54 }
55 pub fn get_location(&self) -> &SourceLocation {
56 match self {
57 Self::Element(e) => &e.location,
58 Self::Text(t) => &t.location,
59 Self::Interpolation(i) => &i.location,
60 Self::Comment(c) => &c.location,
61 }
62 }
63}
64
65#[cfg_attr(feature = "serde", derive(Serialize))]
66pub struct SourceNode<'a> {
67 pub source: &'a str,
68 pub location: SourceLocation,
69}
70
71pub struct TextNode<'a> {
72 pub text: SmallVec<[VStr<'a>; 1]>,
73 pub location: SourceLocation,
74}
75#[cfg(feature = "serde")]
76impl<'a> Serialize for TextNode<'a> {
77 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78 where
79 S: serde::Serializer,
80 {
81 use serde::ser::SerializeStruct;
82 let mut state = serializer.serialize_struct("TextNode", 2)?;
83 let s = self.text.iter().map(|&s| s.into_string());
84 let s: String = s.collect();
85 state.serialize_field("text", &s)?;
86 state.serialize_field("location", &self.location)?;
87 state.end()
88 }
89}
90
91impl<'a> Deref for TextNode<'a> {
92 type Target = str;
93 fn deref(&self) -> &Self::Target {
94 debug_assert!(self.text.len() == 1);
95 &self.text[0]
96 }
97}
98
99impl<'a> TextNode<'a> {
100 pub fn is_all_whitespace(&self) -> bool {
101 self.text.iter().all(|s| !s.chars().any(non_whitespace))
102 }
103 pub fn trim_leading_newline(&mut self) {
104 if self.text.is_empty() {
105 return;
106 }
107 let first = &self.text[0];
108 let offset = if first.starts_with('\n') {
109 1
110 } else if first.starts_with("\r\n") {
111 2
112 } else {
113 return;
114 };
115 if first.len() > offset {
116 self.text[0] = VStr {
117 raw: &first.raw[offset..],
118 ops: first.ops,
119 };
120 } else {
121 self.text.remove(0);
122 }
123 }
124}
125
126#[cfg_attr(feature = "serde", derive(Serialize))]
127pub enum ElemProp<'a> {
128 Attr(Attribute<'a>),
129 Dir(Directive<'a>),
130}
131
132#[derive(PartialEq, Eq)]
133#[cfg_attr(feature = "serde", derive(Serialize))]
134pub enum ElementType {
135 Plain,
136 Component,
137 Template,
138 SlotOutlet,
139}
140
141#[cfg_attr(feature = "serde", derive(Serialize))]
142pub struct Element<'a> {
143 pub tag_name: Name<'a>,
144 pub tag_type: ElementType,
145 pub namespace: Namespace,
146 pub properties: Vec<ElemProp<'a>>,
147 pub children: Vec<AstNode<'a>>,
148 pub location: SourceLocation,
149}
150
151impl<'a> Element<'a> {
152 #[inline]
153 pub fn is_component(&self) -> bool {
154 self.tag_type == ElementType::Component
155 }
156}
157
158#[cfg_attr(feature = "serde", derive(Serialize))]
161pub enum DirectiveArg<'a> {
162 Static(Name<'a>),
164 Dynamic(Name<'a>), }
166
167#[derive(Default)]
170#[cfg_attr(feature = "serde", derive(Serialize))]
171pub struct Directive<'a> {
172 pub name: &'a str,
173 pub argument: Option<DirectiveArg<'a>>,
174 pub modifiers: Vec<&'a str>,
175 pub expression: Option<AttributeValue<'a>>,
176 pub head_loc: SourceLocation,
177 pub location: SourceLocation,
178}
179
180impl<'a> Directive<'a> {
181 pub fn has_empty_expr(&self) -> bool {
182 self.expression
183 .as_ref()
184 .map_or(true, |v| !v.content.contains(non_whitespace))
185 }
186 pub fn check_empty_expr(&self, kind: ErrorKind) -> Option<CompilationError> {
187 if !self.has_empty_expr() {
188 return None;
189 }
190 let loc = self
191 .expression
192 .as_ref()
193 .map_or(self.head_loc.clone(), |v| v.location.clone());
194 Some(CompilationError::new(kind).with_location(loc))
195 }
196}
197
198#[cfg_attr(feature = "serde", derive(Serialize))]
199pub struct AstRoot<'a> {
200 pub children: Vec<AstNode<'a>>,
201 pub location: SourceLocation,
202}
203
204#[derive(Clone)]
205pub enum WhitespaceStrategy {
206 Preserve,
207 Condense,
208}
209impl Default for WhitespaceStrategy {
210 fn default() -> Self {
211 WhitespaceStrategy::Condense
212 }
213}
214
215#[derive(Clone)]
218pub struct ParseOption {
219 pub whitespace: WhitespaceStrategy,
220 pub preserve_comment: bool,
221 pub get_namespace: fn(&str, &Vec<Element<'_>>) -> Namespace,
222 pub get_text_mode: fn(&str) -> TextMode,
223 pub is_void_tag: fn(&str) -> bool,
225 pub is_pre_tag: fn(&str) -> bool,
228 pub is_custom_element: fn(&str) -> bool,
230 pub is_builtin_component: fn(&str) -> bool,
233 pub is_native_element: fn(&str) -> bool,
235}
236
237impl Default for ParseOption {
238 fn default() -> Self {
239 Self {
240 whitespace: WhitespaceStrategy::Condense,
241 preserve_comment: true,
242 get_namespace: |_, _| Namespace::Html,
243 get_text_mode: |_| TextMode::Data,
244 is_void_tag: no,
245 is_pre_tag: |s| s == "pre",
246 is_custom_element: no,
247 is_builtin_component: no,
248 is_native_element: yes,
249 }
250 }
251}
252
253pub struct Parser {
254 option: ParseOption,
255}
256
257impl Parser {
258 pub fn new(option: ParseOption) -> Self {
259 Self { option }
260 }
261
262 pub fn parse<'a, Ts, E>(&self, tokens: Ts, err_handle: E) -> AstRoot<'a>
263 where
264 Ts: TokenSource<'a>,
265 E: ErrorHandler,
266 {
267 let need_flag_namespace = tokens.need_flag_hint();
268 AstBuilder {
269 tokens,
270 err_handle,
271 option: self.option.clone(),
272 open_elems: vec![],
273 root_nodes: vec![],
274 pre_count: 0,
275 v_pre_index: None,
276 need_flag_namespace,
277 }
278 .build_ast()
279 }
280}
281
282struct AstBuilder<'a, Ts, Eh>
284where
285 Ts: TokenSource<'a>,
286 Eh: ErrorHandler,
287{
288 tokens: Ts,
289 err_handle: Eh,
290 option: ParseOption,
291 open_elems: Vec<Element<'a>>,
292 root_nodes: Vec<AstNode<'a>>,
293 pre_count: usize,
295 v_pre_index: Option<usize>,
298 need_flag_namespace: bool,
299}
300
301impl<'a, Ts, Eh> AstBuilder<'a, Ts, Eh>
303where
304 Ts: TokenSource<'a>,
305 Eh: ErrorHandler,
306{
307 fn insert_node(&mut self, node: AstNode<'a>) {
311 if let Some(elem) = self.open_elems.last_mut() {
312 elem.children.push(node);
313 } else {
314 self.root_nodes.push(node);
315 }
316 }
317
318 fn emit_error(&self, kind: ErrorKind, loc: SourceLocation) {
319 let error = CompilationError::new(kind).with_location(loc);
320 self.err_handle.on_error(error)
321 }
322}
323
324impl<'a, Ts, Eh> AstBuilder<'a, Ts, Eh>
326where
327 Ts: TokenSource<'a>,
328 Eh: ErrorHandler,
329{
330 fn build_ast(mut self) -> AstRoot<'a> {
331 let start = self.tokens.current_position();
332 while let Some(token) = self.tokens.next() {
333 self.parse_token(token);
334 }
335 self.report_unclosed_script_comment();
336 for _ in 0..self.open_elems.len() {
337 self.close_element(false);
338 }
339 debug_assert_eq!(self.pre_count, 0);
340 debug_assert!(self.v_pre_index.is_none());
341 let need_condense = self.need_condense();
342 compress_whitespaces(&mut self.root_nodes, need_condense);
343 let location = self.tokens.get_location_from(start);
344 AstRoot {
345 children: self.root_nodes,
346 location,
347 }
348 }
349
350 fn parse_token(&mut self, token: Token<'a>) {
351 match token {
353 Token::EndTag(s) => self.parse_end_tag(s),
354 Token::Text(text) => self.parse_text(text),
355 Token::StartTag(tag) => self.parse_open_tag(tag),
356 Token::Comment(c) => self.parse_comment(c),
357 Token::Interpolation(i) => self.parse_interpolation(i),
358 };
359 }
360 fn parse_open_tag(&mut self, tag: Tag<'a>) {
361 let Tag {
362 name,
363 self_closing,
364 attributes,
365 } = tag;
366 let props = self.parse_attributes(attributes);
367 let ns = (self.option.get_namespace)(name, &self.open_elems);
368 let elem = Element {
369 tag_name: name,
370 tag_type: ElementType::Plain,
371 namespace: ns,
372 properties: props,
373 children: vec![],
374 location: SourceLocation {
375 start: self.tokens.last_position(),
376 end: self.tokens.current_position(),
377 },
378 };
379 if self_closing || (self.option.is_void_tag)(name) {
380 let node = self.parse_element(elem);
381 self.insert_node(node);
382 } else {
383 self.handle_pre_like(&elem);
386 self.open_elems.push(elem);
387 self.set_scanner_flag();
388 }
389 }
390 fn parse_attributes(&mut self, mut attrs: Vec<Attribute<'a>>) -> Vec<ElemProp<'a>> {
391 if self.v_pre_index.is_some() {
393 return attrs.into_iter().map(ElemProp::Attr).collect();
394 }
395 let mut dir_parser = DirectiveParser::new(&self.err_handle);
396 for i in 0..attrs.len() {
398 if attrs[i].name != "v-pre" {
399 continue;
400 }
401 let dir = dir_parser.parse(attrs.remove(i));
402 let mut ret = vec![ElemProp::Dir(dir)];
403 ret.extend(attrs.into_iter().map(ElemProp::Attr));
404 return ret;
405 }
406 attrs
407 .into_iter()
408 .map(|attr| {
409 if dir_parser.detect_directive(&attr) {
410 ElemProp::Dir(dir_parser.parse(attr))
412 } else {
413 ElemProp::Attr(attr)
414 }
415 })
416 .collect()
417 }
418
419 fn handle_pre_like(&mut self, elem: &Element) {
420 debug_assert!(
421 self.open_elems
422 .last()
423 .map_or(true, |e| e.location != elem.location),
424 "element should not be pushed to stack yet.",
425 );
426 if (self.option.is_pre_tag)(elem.tag_name) {
428 self.pre_count += 1;
429 }
430 if is_v_pre_boundary(elem) {
432 debug_assert!(self.v_pre_index.is_none());
433 self.v_pre_index = Some(self.open_elems.len());
434 }
435 }
436 fn parse_end_tag(&mut self, end_tag: &'a str) {
437 let index = self
439 .open_elems
440 .iter()
441 .enumerate()
442 .rfind(|p| element_matches_end_tag(p.1, end_tag))
443 .map(|p| p.0);
444 if let Some(i) = index {
445 let mut to_close = self.open_elems.len() - i;
446 while to_close > 0 {
447 to_close -= 1;
448 self.close_element(to_close == 0);
449 }
450 debug_assert_eq!(self.open_elems.len(), i);
451 } else {
452 let start = self.tokens.last_position();
453 let loc = self.tokens.get_location_from(start);
454 self.emit_error(ErrorKind::InvalidEndTag, loc);
455 }
456 }
457 fn close_element(&mut self, has_matched_end: bool) {
458 let mut elem = self.open_elems.pop().unwrap();
459 self.set_scanner_flag();
460 let start = elem.location.start;
461 if !has_matched_end {
462 let err_location = SourceLocation {
464 start: start.clone(),
465 end: start.clone(),
466 };
467 self.emit_error(ErrorKind::MissingEndTag, err_location);
468 }
469 let location = self.tokens.get_location_from(start);
470 elem.location = location;
471 if self.pre_count > 0 {
472 self.decrement_pre(&mut elem)
473 } else if (self.option.get_text_mode)(elem.tag_name) == TextMode::Data {
474 compress_whitespaces(&mut elem.children, self.need_condense());
476 }
477 let node = self.parse_element(elem);
478 self.insert_node(node);
479 }
480 fn decrement_pre(&mut self, elem: &mut Element) {
481 debug_assert!(self.pre_count > 0);
482 let pre_boundary = (self.option.is_pre_tag)(elem.tag_name);
483 if !pre_boundary {
486 return;
487 }
488 if let Some(AstNode::Text(tn)) = elem.children.last_mut() {
489 tn.trim_leading_newline();
490 }
491 self.pre_count -= 1;
492 }
493 fn close_v_pre(&mut self) {
494 let idx = self.v_pre_index.unwrap();
495 debug_assert!(idx <= self.open_elems.len());
496 if idx == self.open_elems.len() {
498 self.v_pre_index = None;
499 }
500 }
501 fn parse_element(&mut self, mut elem: Element<'a>) -> AstNode<'a> {
502 debug_assert!(elem.tag_type == ElementType::Plain);
503 if self.v_pre_index.is_some() {
504 debug_assert!({
505 let i = *self.v_pre_index.as_ref().unwrap();
506 i != self.open_elems.len() || is_v_pre_boundary(&elem)
507 });
508 self.close_v_pre();
509 elem.tag_type = ElementType::Plain;
510 } else if elem.tag_name == "slot" {
511 elem.tag_type = ElementType::SlotOutlet;
512 } else if is_template_element(&elem) {
513 elem.tag_type = ElementType::Template;
514 } else if self.is_component(&elem) {
515 elem.tag_type = ElementType::Component;
516 }
517 AstNode::Element(elem)
518 }
519 fn parse_text(&mut self, text: VStr<'a>) {
520 let mut text = smallvec![text];
521 let mut next_token = None;
522 let start = self.tokens.last_position();
523 for token in &mut self.tokens {
524 if let Token::Text(ds) = token {
525 text.push(ds);
526 } else {
527 next_token = Some(token);
528 break;
529 }
530 }
531 let end = self.tokens.last_position();
532 let location = SourceLocation { start, end };
533 let text_node = TextNode { text, location };
534 self.insert_node(AstNode::Text(text_node));
535 if let Some(token) = next_token {
537 self.parse_token(token);
538 }
539 }
540 fn parse_comment(&mut self, c: &'a str) {
541 if !self.option.preserve_comment {
543 return;
544 }
545 let pos = self.tokens.last_position();
546 let source_node = SourceNode {
547 source: c,
548 location: self.tokens.get_location_from(pos),
549 };
550 self.insert_node(AstNode::Comment(source_node));
551 }
552 fn parse_interpolation(&mut self, src: &'a str) {
553 let pos = self.tokens.last_position();
554 let source_node = SourceNode {
555 source: src,
556 location: self.tokens.get_location_from(pos),
557 };
558 self.insert_node(AstNode::Interpolation(source_node));
559 }
560
561 fn report_unclosed_script_comment(&mut self) {
563 debug_assert!(self.tokens.next().is_none());
564 let elem = match self.open_elems.last() {
565 Some(e) => e,
566 None => return,
567 };
568 if !elem.tag_name.eq_ignore_ascii_case("script") {
569 return;
570 }
571 let text = match elem.children.first() {
572 Some(AstNode::Text(text)) => text,
573 _ => return,
574 };
575 if text.contains("<!--") && !text.contains("-->") {
579 let loc = SourceLocation {
580 start: self.tokens.last_position(),
581 end: self.tokens.last_position(),
582 };
583 self.emit_error(ErrorKind::EofInScriptHtmlCommentLikeText, loc);
584 }
585 }
586
587 #[inline]
589 fn set_scanner_flag(&mut self) {
590 if self.need_flag_namespace {
591 return;
592 }
593 let in_html = self
595 .open_elems
596 .last()
597 .map_or(true, |e| e.namespace == Namespace::Html);
598 self.tokens.set_is_in_html(in_html)
599 }
600
601 fn is_component(&self, e: &Element) -> bool {
602 let opt = &self.option;
603 let tag_name = e.tag_name;
604 if (opt.is_custom_element)(tag_name) {
605 return false;
606 }
607 if tag_name == "component"
608 || tag_name.starts_with(|c| matches!(c, 'A'..='Z'))
609 || is_core_component(tag_name)
610 || (opt.is_builtin_component)(tag_name)
611 || !(opt.is_native_element)(tag_name)
612 {
613 return true;
614 }
615 e.properties.iter().any(|prop| match prop {
616 ElemProp::Dir(Directive { name: "is", .. }) => true,
617 ElemProp::Attr(Attribute {
618 name: "is",
619 value: Some(v),
620 ..
621 }) => v.content.starts_with("vue:"),
622 _ => false,
623 })
624 }
625
626 fn need_condense(&self) -> bool {
627 matches!(self.option.whitespace, WhitespaceStrategy::Condense)
628 }
629}
630
631const BIND_CHAR: char = ':';
632const MOD_CHAR: char = '.';
633const ON_CHAR: char = '@';
634const SLOT_CHAR: char = '#';
635const SEP_BYTES: &[u8] = &[BIND_CHAR as u8, MOD_CHAR as u8];
636const SHORTHANDS: &[char] = &[BIND_CHAR, ON_CHAR, SLOT_CHAR, MOD_CHAR];
637const DIR_MARK: &str = "v-";
638
639type StrPair<'a> = (&'a str, &'a str);
640struct DirectiveParser<'a, 'e, Eh: ErrorHandler> {
641 eh: &'e Eh,
642 name_loc: SourceLocation,
643 location: SourceLocation,
644 cached: Option<StrPair<'a>>,
645}
646impl<'a, 'e, Eh: ErrorHandler> DirectiveParser<'a, 'e, Eh> {
647 fn new(eh: &'e Eh) -> Self {
648 Self {
649 eh,
650 name_loc: Default::default(),
651 location: Default::default(),
652 cached: None,
653 }
654 }
655 fn attr_name_err(&self, kind: ErrorKind) {
656 let error = CompilationError::new(kind).with_location(self.name_loc.clone());
657 self.eh.on_error(error);
658 }
659 fn detect_directive(&mut self, attr: &Attribute<'a>) -> bool {
660 debug_assert!(self.cached.is_none());
661 self.cached = self.detect_dir_name(attr);
662 self.cached.is_some()
663 }
664 fn set_location(&mut self, attr: &Attribute<'a>) {
665 self.location = attr.location.clone();
666 self.name_loc = attr.name_loc.clone();
667 }
668
669 fn parse(&mut self, attr: Attribute<'a>) -> Directive<'a> {
670 let (name, prefixed) = self
671 .cached
672 .or_else(|| self.detect_dir_name(&attr))
673 .expect("Parse without detection requires attribute be directive.");
674 let is_prop = attr.name.starts_with('.');
675 let is_v_slot = name == "slot";
676 let (arg_str, mods_str) = self.split_arg_and_mods(prefixed, is_v_slot, is_prop);
677 let argument = self.parse_directive_arg(arg_str);
678 let modifiers = self.parse_directive_mods(mods_str, is_prop);
679 self.cached = None; Directive {
681 name,
682 argument,
683 modifiers,
684 expression: attr.value,
685 head_loc: attr.name_loc,
686 location: attr.location,
687 }
688 }
689 fn detect_dir_name(&mut self, attr: &Attribute<'a>) -> Option<StrPair<'a>> {
691 self.set_location(attr);
692 self.parse_dir_name(attr)
693 }
694 fn parse_dir_name(&self, attr: &Attribute<'a>) -> Option<StrPair<'a>> {
696 let name = attr.name;
697 if !name.starts_with(DIR_MARK) {
698 let ret = match name.chars().next()? {
699 BIND_CHAR | MOD_CHAR => "bind",
700 ON_CHAR => "on",
701 SLOT_CHAR => "slot",
702 _ => return None,
703 };
704 return Some((ret, name));
705 }
706 let n = &name[2..];
707 let ret = n
708 .bytes()
709 .position(|c| SEP_BYTES.contains(&c))
710 .map(|i| n.split_at(i))
711 .unwrap_or((n, ""));
712 if ret.0.is_empty() {
713 self.attr_name_err(ErrorKind::MissingDirectiveName);
714 return None;
715 }
716 Some(ret)
717 }
718 fn split_arg_and_mods(&self, prefixed: &'a str, is_v_slot: bool, is_prop: bool) -> StrPair<'a> {
720 debug_assert!(prefixed.is_empty() || prefixed.starts_with(SHORTHANDS));
722 if prefixed.is_empty() {
723 return ("", "");
724 }
725 if prefixed.len() == 1 {
726 self.attr_name_err(ErrorKind::MissingDirectiveArg);
727 return ("", "");
728 }
729 let remain = &prefixed[1..];
730 if is_v_slot {
733 if prefixed.starts_with(MOD_CHAR) {
734 self.attr_name_err(ErrorKind::InvalidVSlotModifier);
736 ("", prefixed)
737 } else {
738 debug_assert!(prefixed.starts_with(&[SLOT_CHAR, BIND_CHAR][..]));
739 (remain, "")
740 }
741 } else if prefixed.starts_with(MOD_CHAR) && !is_prop {
742 ("", prefixed)
744 } else if remain.starts_with('[') {
745 self.split_dynamic_arg(remain)
746 } else {
747 debug_assert!(!prefixed.starts_with(SLOT_CHAR));
748 remain
750 .bytes()
751 .position(|u| u == MOD_CHAR as u8)
752 .map(|i| remain.split_at(i))
753 .unwrap_or((remain, ""))
754 }
755 }
756 fn split_dynamic_arg(&self, remain: &'a str) -> (&'a str, &'a str) {
757 let bytes = remain.as_bytes();
759 let end = bytes
760 .iter()
761 .position(|b| *b == b']')
762 .map_or(bytes.len(), |i| i + 1);
763 let (arg, mut mods) = remain.split_at(end);
764 if mods.starts_with(|c| c != MOD_CHAR) {
765 self.attr_name_err(ErrorKind::UnexpectedContentAfterDynamicDirective);
766 mods = mods.trim_start_matches(|c| c != MOD_CHAR);
767 }
768 (arg, mods)
769 }
770 fn parse_directive_arg(&self, arg: &'a str) -> Option<DirectiveArg<'a>> {
771 if arg.is_empty() {
772 return None;
773 }
774 Some(if !arg.starts_with('[') {
775 DirectiveArg::Static(arg)
776 } else if let Some(i) = arg.chars().position(|c| c == ']') {
777 debug_assert!(i == arg.len() - 1);
778 DirectiveArg::Dynamic(&arg[1..i])
779 } else {
780 self.attr_name_err(ErrorKind::MissingDynamicDirectiveArgumentEnd);
781 DirectiveArg::Dynamic(&arg[1..])
782 })
783 }
784 fn parse_directive_mods(&self, mods: &'a str, is_prop: bool) -> Vec<&'a str> {
785 debug_assert!(mods.is_empty() || mods.starts_with(MOD_CHAR));
786 let report_missing_mod = |s: &&str| {
787 if s.is_empty() {
788 self.attr_name_err(ErrorKind::MissingDirectiveMod);
789 }
790 };
791 let mut ret = if mods.is_empty() {
792 vec![]
793 } else {
794 mods[1..]
795 .as_bytes()
796 .split(|b| *b == b'.')
797 .map(std::str::from_utf8) .map(Result::unwrap)
799 .inspect(report_missing_mod)
800 .collect()
801 };
802 if is_prop {
803 ret.push("prop")
804 }
805 ret
806 }
807}
808
809fn compress_whitespaces(nodes: &mut Vec<AstNode>, need_condense: bool) {
810 debug_assert!({
812 let no_consecutive_text = |last_is_text, is_text| {
813 if last_is_text && is_text {
814 None
815 } else {
816 Some(is_text)
817 }
818 };
819 nodes
820 .iter()
821 .map(|n| matches!(n, AstNode::Text(_)))
822 .try_fold(false, no_consecutive_text)
823 .is_some()
824 });
825 let mut i = 0;
826 while i < nodes.len() {
827 let should_remove = if let AstNode::Text(child) = &nodes[i] {
828 use AstNode as A;
829 if !child.is_all_whitespace() {
830 if need_condense {
832 compress_text_node(&mut nodes[i]);
833 }
834 false
835 } else if i == nodes.len() - 1 || i == 0 {
836 true
838 } else if !need_condense {
839 false
840 } else {
841 let prev = &nodes[i - 1];
844 let next = &nodes[i + 1];
845 match (prev, next) {
846 (A::Comment(_), A::Comment(_)) => true,
847 _ => is_element(prev) && is_element(next) && child.contains(&['\r', '\n'][..]),
848 }
849 }
850 } else {
851 false
852 };
853 if should_remove {
854 nodes.remove(i);
855 } else {
856 i += 1;
857 }
858 }
859}
860
861#[inline]
862fn is_element(n: &AstNode) -> bool {
863 n.get_element().is_some()
864}
865
866fn compress_text_node(n: &mut AstNode) {
867 if let AstNode::Text(src) = n {
868 for s in src.text.iter_mut() {
869 s.compress_whitespace();
870 }
871 } else {
872 debug_assert!(false, "impossible");
873 }
874}
875
876fn is_special_template_directive(n: &str) -> bool {
877 match n.len() {
879 2 => n == "if",
880 3 => n == "for",
881 4 => n == "else" || n == "slot",
882 7 => n == "else-if",
883 _ => false,
884 }
885}
886
887fn is_template_element(e: &Element) -> bool {
888 e.tag_name == "template" && find_dir(e, is_special_template_directive).is_some()
889}
890
891fn element_matches_end_tag(e: &Element, tag: &str) -> bool {
892 e.tag_name.eq_ignore_ascii_case(tag)
893}
894
895fn is_v_pre_boundary(elem: &Element) -> bool {
896 find_dir(elem, "pre").is_some()
897}
898
899#[cfg(test)]
900pub mod test {
901 use super::*;
902 use crate::{cast, error::test::TestErrorHandler, scanner::test::base_scan};
903
904 #[test]
905 fn test_parse_text() {
906 let case = "hello {{world}}<p/><p/>";
907 let ast = base_parse(case);
908 let mut children = ast.children;
909 assert_eq!(children.len(), 4);
910 children.pop();
911 children.pop();
912 let world = children.pop().unwrap();
913 let hello = children.pop().unwrap();
914 let v = cast!(hello, AstNode::Text);
915 assert_eq!(v.text[0].raw, "hello ");
916 let v = cast!(world, AstNode::Interpolation);
917 assert_eq!(v.source, "world");
918 }
919
920 pub fn base_parse(s: &str) -> AstRoot {
921 let tokens = base_scan(s);
922 let parser = Parser::new(ParseOption {
923 is_native_element: |s| s != "comp",
924 ..Default::default()
925 });
926 let eh = TestErrorHandler;
927 parser.parse(tokens, eh)
928 }
929
930 pub fn mock_element(s: &str) -> Element {
931 let mut m = base_parse(s).children;
932 m.pop().unwrap().into_element()
933 }
934}