1use std::ops::Range;
2use std::sync::Arc;
3
4use html_escape::decode_html_entities as decode_html_entities_cow;
5use oxc_span::GetSpan;
6use tree_sitter::Node as TsNode;
7
8use super::{
9 AttributeKind, ElementKind, SvelteElementKind, classify_attribute_name, classify_element_name,
10 elements::is_void_element_name, find_first_named_child, is_component_name,
11 parse_identifier_name, parse_modern_attributes, source_location_from_point, text_for_node,
12};
13use crate::SourceLocation;
14use crate::ast::common::NameLocation as LegacyNameLocation;
15use crate::ast::common::{AttributeValueSyntax, ParseError, ParseErrorKind, Span};
16use crate::ast::legacy::Expression as LegacyExpression;
17use crate::ast::modern::*;
18use crate::{SourceId, SourceText};
19
20pub(crate) struct IncrementalHint<'a> {
27 pub changed_ranges: &'a [std::ops::Range<usize>],
28 pub old_source: &'a str,
30 pub old_nodes: &'a [Node],
32 pub old_root: Option<&'a Root>,
34}
35
36fn any_range_overlaps(changed: &[std::ops::Range<usize>], start: usize, end: usize) -> bool {
38 changed.iter().any(|r| r.start < end && r.end > start)
39}
40
41fn try_reuse_node(
47 old_source: &str,
48 new_source: &str,
49 old_nodes: &[Node],
50 cursor: &mut usize,
51 new_start: usize,
52 new_end: usize,
53) -> Option<Node> {
54 let new_len = new_end - new_start;
55 let new_text = new_source.get(new_start..new_end)?;
56 let scan_limit = (*cursor + 4).min(old_nodes.len());
58 for i in *cursor..scan_limit {
59 let old = &old_nodes[i];
60 let old_start = old.start();
61 let old_end = old.end();
62 let old_len = old_end - old_start;
63 if old_len == new_len {
64 if let Some(old_text) = old_source.get(old_start..old_end) {
65 if old_text == new_text {
66 *cursor = i + 1;
67 return Some(old.clone());
68 }
69 }
70 }
71 }
72 None
73}
74
75fn node_child_nodes(node: &Node) -> &[Node] {
77 match node {
78 Node::RegularElement(el) => &el.fragment.nodes,
79 Node::Component(el) => &el.fragment.nodes,
80 Node::SlotElement(el) => &el.fragment.nodes,
81 Node::SvelteHead(el) => &el.fragment.nodes,
82 Node::SvelteBody(el) => &el.fragment.nodes,
83 Node::SvelteWindow(el) => &el.fragment.nodes,
84 Node::SvelteDocument(el) => &el.fragment.nodes,
85 Node::SvelteComponent(el) => &el.fragment.nodes,
86 Node::SvelteElement(el) => &el.fragment.nodes,
87 Node::SvelteSelf(el) => &el.fragment.nodes,
88 Node::SvelteFragment(el) => &el.fragment.nodes,
89 Node::SvelteBoundary(el) => &el.fragment.nodes,
90 Node::TitleElement(el) => &el.fragment.nodes,
91 Node::IfBlock(b) => &b.consequent.nodes,
92 Node::EachBlock(b) => &b.body.nodes,
93 Node::KeyBlock(b) => &b.fragment.nodes,
94 Node::AwaitBlock(_) | Node::SnippetBlock(_) => &[],
95 Node::Text(_) | Node::Comment(_) | Node::ExpressionTag(_) | Node::RenderTag(_)
96 | Node::HtmlTag(_) | Node::ConstTag(_) | Node::DebugTag(_) => &[],
97 }
98}
99
100fn try_reuse_script(source: &str, element: TsNode<'_>, old_root: &Root) -> Option<Script> {
104 let mut tag_cursor = element.walk();
106 let start_tag = element
107 .named_children(&mut tag_cursor)
108 .find(|c| c.kind() == "start_tag")?;
109 let attrs_text = text_for_node(source, start_tag);
110 let new_context = if attrs_text.contains("module")
111 || attrs_text.contains("context=\"module\"")
112 || attrs_text.contains("context='module'")
113 {
114 ScriptContext::Module
115 } else {
116 ScriptContext::Default
117 };
118
119 old_root
120 .scripts
121 .iter()
122 .find(|s| s.context == new_context)
123 .cloned()
124}
125
126fn try_reuse_style(old_root: &Root) -> Option<Css> {
128 old_root.css.clone()
129}
130
131fn find_old_node_by_kind<'a>(
134 old_nodes: &'a [Node],
135 cursor: &mut usize,
136 new_len: usize,
137 _kind: &str,
138) -> Option<&'a Node> {
139 let scan_limit = (*cursor + 4).min(old_nodes.len());
140 for i in *cursor..scan_limit {
141 let old = &old_nodes[i];
142 let old_len = old.end() - old.start();
143 if old_len == new_len {
144 *cursor = i + 1;
145 return Some(old);
146 }
147 }
148 None
149}
150
151fn make_child_hint<'a>(
154 parent_hint: &'a IncrementalHint<'a>,
155 old_node_cursor: &mut usize,
156 child_start: usize,
157 child_end: usize,
158 kind: &str,
159) -> Option<IncrementalHint<'a>> {
160 let old = find_old_node_by_kind(
161 parent_hint.old_nodes,
162 old_node_cursor,
163 child_end - child_start,
164 kind,
165 )?;
166 let children = node_child_nodes(old);
167 if children.is_empty() {
168 return None;
169 }
170 Some(IncrementalHint {
171 changed_ranges: parent_hint.changed_ranges,
172 old_source: parent_hint.old_source,
173 old_nodes: children,
174 old_root: None,
175 })
176}
177
178pub(crate) fn parse_root(source: &str, root: TsNode<'_>, loose: bool) -> Root {
181 parse_root_inner(source, root, loose, None)
182}
183
184pub(crate) fn parse_root_incremental(
185 source: &str,
186 root: TsNode<'_>,
187 loose: bool,
188 old_root: &Root,
189 old_source: &str,
190 changed_ranges: &[Range<usize>],
191) -> Root {
192 let hint = IncrementalHint {
193 changed_ranges,
194 old_source,
195 old_nodes: &old_root.fragment.nodes,
196 old_root: Some(old_root),
197 };
198 parse_root_inner(source, root, loose, Some(hint))
199}
200
201fn parse_root_inner(
202 source: &str,
203 root: TsNode<'_>,
204 loose: bool,
205 hint: Option<IncrementalHint<'_>>,
206) -> Root {
207 let errors = collect_parse_errors(source, root);
208
209 if root.kind() == "ERROR" {
210 let fragment_nodes = recover_modern_error_nodes(source, root, false);
211 return Root {
212 css: None,
213 styles: Box::new([]),
214 js: Box::new([]),
215 scripts: Box::new([]),
216 start: root.start_byte(),
217 end: root.end_byte(),
218 r#type: RootType::Root,
219 fragment: crate::ast::modern::Fragment {
220 r#type: crate::ast::modern::FragmentType::Fragment,
221 nodes: fragment_nodes.into_boxed_slice(),
222 },
223 options: None,
224 module: None,
225 instance: None,
226 comments: None,
227 errors: errors.into_boxed_slice(),
228 };
229 }
230
231 let mut css = None;
232 let mut styles = Vec::new();
233 let mut options = None;
234 let mut module = None;
235 let mut instance = None;
236 let mut js = Vec::new();
237 let mut fragment_nodes = Vec::new();
238 let mut root_comments = Vec::new();
239 let mut pending_script_comment: Option<Arc<str>> = None;
240 let mut previous_child_end = None;
241 let mut old_node_cursor = 0usize;
242
243 let mut cursor = root.walk();
244 for child in root.named_children(&mut cursor) {
245 if let Some(gap_start) = previous_child_end {
246 push_modern_gap_text(source, &mut fragment_nodes, gap_start, child.start_byte());
247 }
248
249 let child_start = child.start_byte();
250 let child_end = child.end_byte();
251
252 if let Some(ref hint) = hint {
255 if !any_range_overlaps(hint.changed_ranges, child_start, child_end) {
256 if child.kind() == "element" {
258 if let Some(name) = modern_element_name(source, child) {
259 match classify_element_name(name.as_ref()) {
260 ElementKind::Script => {
261 if let Some(old_root) = hint.old_root {
262 if let Some(old_script) = try_reuse_script(source, child, old_root) {
263 js.push(old_script.clone());
264 match old_script.context {
265 ScriptContext::Module => {
266 if module.is_none() {
267 module = Some(old_script);
268 }
269 }
270 ScriptContext::Default => {
271 if instance.is_none() {
272 instance = Some(old_script);
273 }
274 }
275 }
276 pending_script_comment = None;
277 previous_child_end = Some(child_end);
278 continue;
279 }
280 }
281 }
282 ElementKind::Style => {
283 if let Some(old_root) = hint.old_root {
284 if let Some(old_style) = try_reuse_style(old_root) {
285 if css.is_none() {
286 css = Some(old_style.clone());
287 }
288 styles.push(old_style);
289 pending_script_comment = None;
290 previous_child_end = Some(child_end);
291 continue;
292 }
293 }
294 }
295 _ => {}
296 }
297 }
298 }
299
300 if let Some(reused) = try_reuse_node(
302 hint.old_source,
303 source,
304 hint.old_nodes,
305 &mut old_node_cursor,
306 child_start,
307 child_end,
308 ) {
309 fragment_nodes.push(reused);
310 previous_child_end = Some(child_end);
311 continue;
312 }
313 }
314 }
315
316 match child.kind() {
317 "text" | "entity" => {
318 let text_node = parse_modern_text(source, child);
319 if text_node.data.chars().all(char::is_whitespace) {
320 push_modern_text_node(&mut fragment_nodes, text_node);
321 } else {
322 pending_script_comment = None;
323 push_modern_text_node(&mut fragment_nodes, text_node);
324 }
325 }
326 "comment" => {
327 let comment = parse_modern_comment(source, child);
328 pending_script_comment = Some(comment.data.clone());
329 fragment_nodes.push(crate::ast::modern::Node::Comment(comment));
330 }
331 "expression" => {
332 let tag = if loose {
333 Some(parse_modern_expression_tag_loose(source, child))
334 } else {
335 parse_modern_expression_tag(source, child)
336 };
337 if let Some(tag) = tag {
338 fragment_nodes.push(crate::ast::modern::Node::ExpressionTag(tag));
339 }
340 }
341 kind if is_typed_block_kind(kind) => {
342 pending_script_comment = None;
343 let child_hint = hint.as_ref().and_then(|h| {
344 make_child_hint(h, &mut old_node_cursor, child_start, child_end, kind)
345 });
346 if let Some(block_node) = parse_modern_block(source, child, child_hint.as_ref()) {
347 fragment_nodes.push(block_node);
348 }
349 }
350 kind if is_typed_tag_kind(kind) => {
351 pending_script_comment = None;
352 if let Some(tag_node) = parse_modern_tag(source, child) {
353 fragment_nodes.push(tag_node);
354 }
355 }
356 "element" => {
357 if let Some((recovered_nodes, recovered_comments)) =
358 parse_modern_collapsed_comment_tag_sequence(source, child)
359 {
360 pending_script_comment = None;
361 fragment_nodes.extend(recovered_nodes);
362 root_comments.extend(recovered_comments);
363 previous_child_end = Some(child_end);
364 continue;
365 }
366
367 if let Some(name) = modern_element_name(source, child) {
368 match classify_element_name(name.as_ref()) {
369 ElementKind::Script => {
370 if let Some(script) = parse_modern_script(
371 source,
372 child,
373 pending_script_comment.as_deref(),
374 ) {
375 js.push(script.clone());
376 match script.context {
377 crate::ast::modern::ScriptContext::Module => {
378 if module.is_none() {
379 module = Some(script);
380 }
381 }
382 crate::ast::modern::ScriptContext::Default => {
383 if instance.is_none() {
384 instance = Some(script);
385 }
386 }
387 }
388 pending_script_comment = None;
389 previous_child_end = Some(child_end);
390 continue;
391 }
392 }
393 ElementKind::Svelte(SvelteElementKind::Options) => {
394 options = parse_modern_options(source, child);
395 pending_script_comment = None;
396 previous_child_end = Some(child_end);
397 continue;
398 }
399 ElementKind::Style => {
400 if let Some(style) = parse_modern_style(source, child) {
401 if css.is_none() {
402 css = Some(style.clone());
403 }
404 styles.push(style);
405 pending_script_comment = None;
406 previous_child_end = Some(child_end);
407 continue;
408 }
409 }
410 _ => {}
411 }
412 }
413
414 pending_script_comment = None;
415 let child_hint = hint.as_ref().and_then(|h| {
416 make_child_hint(h, &mut old_node_cursor, child_start, child_end, "element")
417 });
418 fragment_nodes.push(parse_modern_element_node(
419 source, child, false, false, loose, child_hint.as_ref(),
420 ));
421 }
422 "ERROR" => {
423 pending_script_comment = None;
424 let mut recovered = recover_modern_error_nodes(source, child, false);
425 fragment_nodes.append(&mut recovered);
426 }
427 _ => {}
428 }
429
430 previous_child_end = Some(child_end);
431 }
432
433 root_comments.extend(collect_modern_tag_comments(source, root));
434 root_comments.sort_by_key(|comment| {
435 (
436 comment.start,
437 comment.end,
438 match comment.r#type {
439 RootCommentType::Line => 0u8,
440 RootCommentType::Block => 1u8,
441 },
442 )
443 });
444 root_comments.dedup_by(|left, right| {
445 left.start == right.start
446 && left.end == right.end
447 && left.r#type == right.r#type
448 && left.value == right.value
449 });
450
451 Root {
452 css,
453 styles: styles.into_boxed_slice(),
454 js: Box::new([]),
455 scripts: js.into_boxed_slice(),
456 start: root.start_byte(),
457 end: root.end_byte(),
458 r#type: RootType::Root,
459 fragment: crate::ast::modern::Fragment {
460 r#type: crate::ast::modern::FragmentType::Fragment,
461 nodes: fragment_nodes.into_boxed_slice(),
462 },
463 options,
464 module,
465 instance,
466 comments: (!root_comments.is_empty()).then(|| root_comments.into_boxed_slice()),
467 errors: errors.into_boxed_slice(),
468 }
469}
470
471fn collect_parse_errors(source: &str, root: TsNode<'_>) -> Vec<ParseError> {
472 fn walk(
473 source: &str,
474 node: TsNode<'_>,
475 errors: &mut Vec<ParseError>,
476 parent_kind: Option<&str>,
477 ) {
478 if is_typed_block_kind(node.kind()) {
479 collect_block_parse_errors(source, node, errors);
480 } else if node.kind() == "ERROR" && !parent_kind.is_some_and(is_typed_block_kind) {
481 let error = parse_error_from_error_node(source, node);
482 let checkpoint = errors.len();
483
484 let mut cursor = node.walk();
485 for child in node.named_children(&mut cursor) {
486 walk(source, child, errors, Some(node.kind()));
487 }
488
489 if let Some(error) = error
490 && keep_error(source, node, &error, errors.len() > checkpoint)
491 {
492 errors.push(error);
493 }
494 return;
495 } else if (node.kind() != "orphan_branch" || parent_kind != Some("ERROR"))
496 && let Some(error) = parse_error_from_non_error_node(source, node)
497 {
498 errors.push(error);
499 }
500
501 let mut cursor = node.walk();
502 for child in node.named_children(&mut cursor) {
503 walk(source, child, errors, Some(node.kind()));
504 }
505 }
506
507 fn keep_error(
508 _source: &str,
509 node: TsNode<'_>,
510 error: &ParseError,
511 has_descendant_error: bool,
512 ) -> bool {
513 match error.kind {
514 ParseErrorKind::BlockUnclosed => {
515 if has_descendant_error {
516 return false;
517 }
518
519 let is_snippet = find_direct_named_child(node, "snippet_block").is_some()
521 || find_direct_named_child(node, "snippet_name").is_some();
522 !is_snippet
523 }
524 _ => true,
525 }
526 }
527
528 let mut errors = Vec::new();
529 walk(source, root, &mut errors, None);
530 errors.sort_by_key(|error| (error.start, error.end));
531 errors.dedup_by(|left, right| left.start == right.start && left.end == right.end);
532 errors
533}
534
535fn collect_block_parse_errors(source: &str, block: TsNode<'_>, errors: &mut Vec<ParseError>) {
536 let children = named_children_vec(block);
537 let Some(block_kind) = BlockKind::from_node_kind(block.kind()) else {
538 return;
539 };
540
541 let body_start = match block_kind {
542 BlockKind::If => body_start_index(block, &children, &["expression"]),
543 BlockKind::Each => {
544 body_start_index(block, &children, &["expression", "binding", "index", "key"])
545 }
546 BlockKind::Key => body_start_index(block, &children, &["expression"]),
547 BlockKind::Await => {
548 body_start_index(block, &children, &["expression", "binding", "pending"])
549 }
550 BlockKind::Snippet => {
551 body_start_index(block, &children, &["name", "type_parameters", "parameters"])
552 }
553 };
554
555 let mut previous: Option<TsNode<'_>> = None;
558 if matches!(block_kind, BlockKind::Await)
559 && let Some(pending) = block.child_by_field_name("pending")
560 {
561 if let Some((branch_kind, start)) = branch_in_section_container(source, pending) {
562 let kind = if block_kind.accepts(branch_kind) {
563 ParseErrorKind::BlockInvalidContinuationPlacement
564 } else {
565 block_kind.expected_branch_error()
566 };
567 errors.push(ParseError {
568 kind,
569 start,
570 end: start,
571 });
572 return;
573 }
574 previous = Some(pending);
575 }
576
577 let mut has_end = false;
578 let mut has_specific_error = false;
579
580 let body_has_error = children[body_start..].iter().any(|c| c.has_error());
582
583 for child in children.iter().take(body_start) {
585 if child.kind() == "ERROR"
586 && let Some(branch_kind) = error_branch_kind(source, *child)
587 {
588 has_specific_error = true;
589 let start = branch_start_in_error(source, *child);
590 let kind = if block_kind.accepts(branch_kind) {
591 innermost_unclosed_block_kind(source, *child)
592 .map(BlockKind::expected_branch_error)
593 .or_else(|| scope_aware_branch_error(source, block_kind, branch_kind, None))
594 .unwrap_or(ParseErrorKind::BlockInvalidContinuationPlacement)
595 } else {
596 block_kind.expected_branch_error()
597 };
598 errors.push(ParseError {
599 kind,
600 start,
601 end: start,
602 });
603 }
604 }
605
606 for child in children.into_iter().skip(body_start) {
607 match child.kind() {
608 "text" | "entity"
609 if text_for_node(source, child)
610 .chars()
611 .all(char::is_whitespace) => {}
612 "comment" => {}
613 "block_end" => {
614 has_end = true;
615 break;
616 }
617 "else_if_clause" | "else_clause" => {
618 let branch_kind = match child.kind() {
619 "else_if_clause" => BlockBranchKind::ElseIf,
620 _ => BlockBranchKind::Else,
621 };
622 let kind = scope_aware_branch_error(source, block_kind, branch_kind, previous);
623
624 if let Some(kind) = kind {
625 has_specific_error = true;
626 errors.push(ParseError {
627 kind,
628 start: child.start_byte().saturating_add(1),
629 end: child.start_byte().saturating_add(1),
630 });
631 }
632 }
633 "await_branch" => {
634 let branch_kind = find_first_named_child(child, "branch_kind")
635 .and_then(|n| n.utf8_text(source.as_bytes()).ok())
636 .and_then(|s| s.trim().parse::<BlockBranchKind>().ok());
637 if let Some(branch_kind) = branch_kind {
638 let kind = scope_aware_branch_error(source, block_kind, branch_kind, previous);
639 if let Some(kind) = kind {
640 has_specific_error = true;
641 errors.push(ParseError {
642 kind,
643 start: child.start_byte().saturating_add(1),
644 end: child.start_byte().saturating_add(1),
645 });
646 }
647 }
648 }
649 "orphan_branch" => {
650 if let Some(branch_kind) = branch_kind_from_node(source, child) {
651 let kind = scope_aware_branch_error(source, block_kind, branch_kind, previous)
652 .unwrap_or_else(|| {
653 if block_kind.accepts(branch_kind) {
654 ParseErrorKind::BlockInvalidContinuationPlacement
655 } else {
656 block_kind.expected_branch_error()
657 }
658 });
659 has_specific_error = true;
660 let start = branch_start(child);
661 errors.push(ParseError {
662 kind,
663 start,
664 end: start,
665 });
666 }
667 }
668 "ERROR" => {
669 if let Some(branch_kind) = error_branch_kind(source, child) {
670 has_specific_error = true;
671 let start = branch_start_in_error(source, child);
672 let kind = if block_kind.accepts(branch_kind) {
673 innermost_unclosed_block_kind(source, child)
674 .map(BlockKind::expected_branch_error)
675 .or_else(|| {
676 scope_aware_branch_error(source, block_kind, branch_kind, previous)
677 })
678 .unwrap_or(ParseErrorKind::BlockInvalidContinuationPlacement)
679 } else {
680 block_kind.expected_branch_error()
681 };
682 errors.push(ParseError {
683 kind,
684 start,
685 end: start,
686 });
687 }
688 previous = Some(child);
689 }
690 _ => previous = Some(child),
691 }
692 }
693
694 if !has_end && !has_specific_error {
695 if let Some(pos) = missing_brace_in_block_start(block) {
697 errors.push(ParseError {
698 kind: ParseErrorKind::ExpectedTokenRightBrace,
699 start: pos,
700 end: pos,
701 });
702 return;
703 }
704
705 if body_has_error {
708 return;
709 }
710
711 if let Some((branch, branch_pos)) = next_branch_after_node(source, block) {
712 if !block_kind.accepts(branch) {
713 errors.push(ParseError {
714 kind: block_kind.expected_branch_error(),
715 start: branch_pos,
716 end: branch_pos,
717 });
718 }
719 return;
722 }
723
724 errors.push(ParseError {
725 kind: ParseErrorKind::BlockUnclosed,
726 start: block.start_byte(),
727 end: block.start_byte().saturating_add(1),
728 });
729 }
730}
731
732fn scope_aware_branch_error(
736 source: &str,
737 block_kind: BlockKind,
738 branch_kind: BlockBranchKind,
739 previous: Option<TsNode<'_>>,
740) -> Option<ParseErrorKind> {
741 if block_kind.accepts(branch_kind) {
742 if let Some(prev) = previous
743 && node_leaves_scope_open(source, prev)
744 {
745 innermost_unclosed_block_kind(source, prev)
746 .map(|ik| ik.expected_branch_error())
747 .or(Some(ParseErrorKind::BlockInvalidContinuationPlacement))
748 } else {
749 None
750 }
751 } else {
752 Some(block_kind.expected_branch_error())
753 }
754}
755
756fn innermost_unclosed_block_kind(source: &str, node: TsNode<'_>) -> Option<BlockKind> {
759 if is_typed_block_kind(node.kind()) && !has_named_descendant(node, "block_end") {
760 return BlockKind::from_node_kind(node.kind());
761 }
762 match node.kind() {
763 "await_pending" | "await_branch_children" => last_significant_child(source, node)
764 .and_then(|child| innermost_unclosed_block_kind(source, child)),
765 _ => None,
766 }
767}
768
769fn branch_kind_from_node(source: &str, node: TsNode<'_>) -> Option<BlockBranchKind> {
770 match node.kind() {
771 "else_if_clause" => Some(BlockBranchKind::ElseIf),
772 "else_clause" => Some(BlockBranchKind::Else),
773 "orphan_branch" => node
774 .child_by_field_name("kind")
775 .and_then(|kind| kind.utf8_text(source.as_bytes()).ok())
776 .and_then(|text| text.trim().parse().ok()),
777 "await_branch" => find_first_named_child(node, "branch_kind")
778 .and_then(|kind| kind.utf8_text(source.as_bytes()).ok())
779 .and_then(|text| text.trim().parse().ok()),
780 "branch_kind" | "shorthand_kind" => node
781 .utf8_text(source.as_bytes())
782 .ok()
783 .and_then(|text| text.trim().parse().ok()),
784 _ => None,
785 }
786}
787
788fn branch_start(node: TsNode<'_>) -> usize {
789 node.start_byte().saturating_add(1)
790}
791
792fn missing_brace_in_block_start(block: TsNode<'_>) -> Option<usize> {
795 let mut cursor = block.walk();
796 let all: Vec<_> = block.children(&mut cursor).collect();
797 for (i, child) in all.iter().enumerate() {
798 if child.kind() == "ERROR"
799 && child.is_named()
800 && all[i + 1..]
801 .iter()
802 .any(|next| !next.is_named() && next.kind() == "}")
803 {
804 return Some(child.start_byte());
805 }
806 }
807 None
808}
809
810fn parse_error_from_error_node(source: &str, error: TsNode<'_>) -> Option<ParseError> {
811 {
814 let mut cursor = error.walk();
815 let named_children = error.named_children(&mut cursor).collect::<Vec<_>>();
816 for (index, child) in named_children.iter().copied().enumerate() {
817 let context_kind = match child.kind() {
818 "await_branch" => Some(BlockKind::Await),
819 "else_clause" | "else_if_clause" => Some(BlockKind::If),
820 _ => None,
821 };
822 if let Some(context_kind) = context_kind {
823 for next in named_children.iter().copied().skip(index + 1) {
824 if let Some(inner_branch) = branch_kind_from_node(source, next) {
825 if !context_kind.accepts(inner_branch) {
826 let pos = branch_start(next);
827 return Some(ParseError {
828 kind: context_kind.expected_branch_error(),
829 start: pos,
830 end: pos,
831 });
832 }
833 break;
834 }
835 }
836 }
837 }
838 }
839
840 {
842 let mut cursor = error.walk();
843 if let Some(typed_block) = error
844 .named_children(&mut cursor)
845 .find(|c| is_typed_block_kind(c.kind()))
846 && !has_named_descendant(typed_block, "block_end")
847 {
848 if let Some((branch_kind, start)) = next_branch_after_node(source, typed_block) {
849 let block_kind = BlockKind::from_node_kind(typed_block.kind())
850 .expect("typed block should map to BlockKind");
851 if !block_kind.accepts(branch_kind) {
852 return Some(ParseError {
853 kind: block_kind.expected_branch_error(),
854 start,
855 end: start,
856 });
857 }
858 }
859 return Some(ParseError {
860 kind: ParseErrorKind::BlockUnclosed,
861 start: typed_block.start_byte(),
862 end: typed_block.start_byte().saturating_add(1),
863 });
864 }
865 }
866
867 if let Some(name) = raw_text_error_name(source, error) {
868 let raw_text = find_direct_named_child(error, "raw_text");
869 let kind = match name.as_ref() {
870 "script" if raw_text.is_some_and(|node| node.start_byte() == node.end_byte()) => {
871 ParseErrorKind::UnexpectedEof
872 }
873 "script" => ParseErrorKind::ElementUnclosed { name },
874 "style" if raw_text.is_some_and(|node| node.start_byte() == node.end_byte()) => {
875 ParseErrorKind::ExpectedTokenStyleClose
876 }
877 "style" => ParseErrorKind::CssExpectedIdentifier,
878 _ => {
879 let start_tag = find_direct_named_child(error, "start_tag")?;
880 return Some(ParseError {
881 kind: ParseErrorKind::ElementUnclosed { name },
882 start: start_tag.start_byte(),
883 end: start_tag.start_byte().saturating_add(1),
884 });
885 }
886 };
887
888 let (start, end) = match kind {
889 ParseErrorKind::CssExpectedIdentifier => raw_text
890 .map(|node| (node.start_byte(), node.start_byte()))
891 .unwrap_or((error.end_byte(), error.end_byte())),
892 _ => (error.end_byte(), error.end_byte()),
893 };
894
895 return Some(ParseError { kind, start, end });
896 }
897
898 if let Some(start_tag) = find_direct_named_child(error, "start_tag")
899 && find_direct_named_child(error, "end_tag").is_none()
900 && find_direct_named_child(error, "self_closing_tag").is_none()
901 {
902 let tag_name = find_direct_named_child(start_tag, "tag_name")?;
903 return Some(ParseError {
904 kind: ParseErrorKind::ElementUnclosed {
905 name: text_for_node(source, tag_name),
906 },
907 start: start_tag.start_byte(),
908 end: start_tag.start_byte().saturating_add(1),
909 });
910 }
911
912 if let Some(tag_name) = invalid_closing_tag_name(source, error) {
913 return Some(ParseError {
914 kind: ParseErrorKind::ElementInvalidClosingTag { name: tag_name },
915 start: error.start_byte(),
916 end: error.start_byte(),
917 });
918 }
919
920 let raw = source
921 .get(error.start_byte()..error.end_byte())
922 .unwrap_or_default();
923 if raw.starts_with("<!--") {
924 return Some(ParseError {
925 kind: ParseErrorKind::ExpectedTokenCommentClose,
926 start: error.end_byte(),
927 end: error.end_byte(),
928 });
929 }
930
931 if error_branch_kind(source, error).is_some() {
932 return Some(ParseError {
933 kind: ParseErrorKind::BlockInvalidContinuationPlacement,
934 start: error.start_byte().saturating_add(1),
935 end: error.start_byte().saturating_add(1),
936 });
937 }
938
939 if raw == "<" {
940 return Some(ParseError {
941 kind: ParseErrorKind::UnexpectedEof,
942 start: error.end_byte(),
943 end: error.end_byte(),
944 });
945 }
946
947 None
948}
949
950fn parse_error_from_non_error_node(source: &str, node: TsNode<'_>) -> Option<ParseError> {
951 if let Some(error) = malformed_special_tag_whitespace_error(source, node) {
952 return Some(error);
953 }
954
955 if let Some(error) = malformed_block_whitespace_error(node) {
956 return Some(error);
957 }
958
959 if let Some(error) = special_tag_expression_parse_error(source, node) {
960 return Some(error);
961 }
962
963 if node.kind() == "erroneous_end_tag"
964 && let Some(name) = erroneous_end_tag_name(source, node)
965 {
966 if let Some(reason) = autoclosed_by(source, node, name.as_ref()) {
967 return Some(ParseError {
968 kind: ParseErrorKind::ElementInvalidClosingTagAutoclosed { name, reason },
969 start: node.start_byte(),
970 end: node.start_byte(),
971 });
972 }
973 return Some(ParseError {
974 kind: ParseErrorKind::ElementInvalidClosingTag { name },
975 start: node.start_byte(),
976 end: node.start_byte(),
977 });
978 }
979
980 if node.kind() == "expression"
981 && let Some(missing) = find_missing(node, "}")
982 {
983 let start = missing_right_brace_pos(node).unwrap_or_else(|| missing.start_byte());
984 return Some(ParseError {
985 kind: ParseErrorKind::ExpectedTokenRightBrace,
986 start,
987 end: start,
988 });
989 }
990
991 if node.kind() == "expression" && is_attribute_placement_expression(source, node) {
992 return None;
993 }
994
995 if node.kind() == "orphan_branch" {
996 let start = branch_start(node);
997 return Some(ParseError {
998 kind: ParseErrorKind::BlockInvalidContinuationPlacement,
999 start,
1000 end: start,
1001 });
1002 }
1003
1004 if node.kind() == "expression"
1005 && let Some((start, message)) = parse_modern_expression_error(source, node)
1006 {
1007 let raw = source
1010 .get(node.start_byte()..node.end_byte())
1011 .unwrap_or_default();
1012 if raw.starts_with("{/") && raw.ends_with('}') {
1013 let inner = &raw[2..raw.len() - 1];
1014 if matches!(inner, "if" | "each" | "await" | "key" | "snippet") {
1015 return None;
1016 }
1017 }
1018 return Some(ParseError {
1019 kind: ParseErrorKind::JsParseError { message },
1020 start,
1021 end: start,
1022 });
1023 }
1024
1025 if node.kind() == "self_closing_tag"
1026 && node.end_byte() == source.len()
1027 && find_missing(node, "/>").is_some()
1028 {
1029 return Some(ParseError {
1030 kind: ParseErrorKind::UnexpectedEof,
1031 start: source.len(),
1032 end: source.len(),
1033 });
1034 }
1035
1036 if node.kind() == "element"
1037 && let Some(start_tag) = find_direct_named_child(node, "start_tag")
1038 {
1039 let raw = source
1040 .get(start_tag.start_byte()..start_tag.end_byte())
1041 .unwrap_or_default();
1042 if !raw.contains('>') && start_tag.end_byte() == source.len() {
1043 return Some(ParseError {
1044 kind: ParseErrorKind::UnexpectedEof,
1045 start: source.len(),
1046 end: source.len(),
1047 });
1048 }
1049
1050 if find_direct_named_child(node, "end_tag").is_none()
1052 && find_direct_named_child(node, "self_closing_tag").is_none()
1053 && node.end_byte() == source.len()
1054 && let Some(tag_name) = find_direct_named_child(start_tag, "tag_name")
1055 {
1056 let name = text_for_node(source, tag_name);
1057 if !is_void_element_name(name.as_ref()) {
1058 return Some(ParseError {
1059 kind: ParseErrorKind::ElementUnclosed { name },
1060 start: start_tag.start_byte(),
1061 end: start_tag.start_byte().saturating_add(1),
1062 });
1063 }
1064 }
1065 }
1066
1067 None
1068}
1069
1070fn malformed_special_tag_whitespace_error(_source: &str, node: TsNode<'_>) -> Option<ParseError> {
1071 if !is_typed_tag_kind(node.kind()) {
1072 return None;
1073 }
1074
1075 let has_expression = node.child_by_field_name("expression").is_some();
1078 if has_expression {
1079 return None;
1080 }
1081
1082 let has_error = find_direct_named_child(node, "ERROR").is_some();
1083 if !has_error {
1084 return None;
1085 }
1086
1087 let keyword_len = match node.kind() {
1089 "html_tag" => 4,
1090 "debug_tag" => 5,
1091 "const_tag" => 5,
1092 "render_tag" => 6,
1093 "attach_tag" => 6,
1094 _ => return None,
1095 };
1096
1097 let start = node.start_byte() + 2 + keyword_len;
1099 Some(ParseError {
1100 kind: ParseErrorKind::ExpectedWhitespace,
1101 start,
1102 end: start,
1103 })
1104}
1105
1106fn malformed_block_whitespace_error(node: TsNode<'_>) -> Option<ParseError> {
1107 if node.kind() != "malformed_block" {
1108 return None;
1109 }
1110
1111 let sigil = node.child_by_field_name("kind")?;
1112 Some(ParseError {
1113 kind: ParseErrorKind::BlockUnexpectedCharacter,
1114 start: node.start_byte(),
1115 end: sigil.end_byte(),
1116 })
1117}
1118
1119fn special_tag_expression_parse_error(source: &str, node: TsNode<'_>) -> Option<ParseError> {
1120 if !is_typed_tag_kind(node.kind()) || is_attribute_value_tag(node) {
1121 return None;
1122 }
1123
1124 let expr = special_tag_expression_node(node)?;
1125 let (start, message) = parse_modern_expression_error(source, expr)?;
1126 Some(ParseError {
1127 kind: ParseErrorKind::JsParseError { message },
1128 start,
1129 end: start,
1130 })
1131}
1132
1133fn is_attribute_value_tag(mut node: TsNode<'_>) -> bool {
1134 while let Some(parent) = node.parent() {
1135 match parent.kind() {
1136 "quoted_attribute_value" | "unquoted_attribute_value" => return true,
1137 "attribute" | "start_tag" | "self_closing_tag" | "element" | "document" => {
1138 return false;
1139 }
1140 _ => node = parent,
1141 }
1142 }
1143 false
1144}
1145
1146fn is_attribute_placement_expression(_source: &str, node: TsNode<'_>) -> bool {
1147 if !is_attribute_value_expression(node) {
1148 return false;
1149 }
1150
1151 if !node.has_error() {
1152 return false;
1153 }
1154
1155 let mut cursor = node.walk();
1156 for child in node.named_children(&mut cursor) {
1157 if child.kind() != "ERROR" {
1158 continue;
1159 }
1160
1161 let mut error_cursor = child.walk();
1162 if child.named_children(&mut error_cursor).any(|error_child| {
1163 is_typed_block_kind(error_child.kind()) || is_typed_tag_kind(error_child.kind())
1164 }) {
1165 return true;
1166 }
1167 }
1168
1169 false
1170}
1171
1172fn is_attribute_value_expression(mut node: TsNode<'_>) -> bool {
1173 while let Some(parent) = node.parent() {
1174 match parent.kind() {
1175 "quoted_attribute_value" | "unquoted_attribute_value" => return true,
1176 "attribute" | "start_tag" | "self_closing_tag" | "element" | "document" => {
1177 return false;
1178 }
1179 _ => node = parent,
1180 }
1181 }
1182 false
1183}
1184
1185fn missing_right_brace_pos(node: TsNode<'_>) -> Option<usize> {
1186 let mut current = node;
1187 while let Some(parent) = current.parent() {
1188 if parent.kind() == "self_closing_tag" {
1189 return Some(parent.end_byte().saturating_sub(1));
1190 }
1191 if matches!(parent.kind(), "element" | "document") {
1192 break;
1193 }
1194 current = parent;
1195 }
1196 None
1197}
1198
1199fn autoclosed_by(source: &str, node: TsNode<'_>, name: &str) -> Option<Arc<str>> {
1200 let reason = previous_significant_sibling(source, node)?;
1201 if reason.kind() != "element" {
1202 return None;
1203 }
1204 let reason_name = cst_element_name(source, reason)?;
1205 let current = previous_significant_sibling(source, reason)?;
1206 if current.kind() != "element" || has_cst_end_tag(current) {
1207 return None;
1208 }
1209 let current_name = cst_element_name(source, current)?;
1210 if current_name.as_ref() != name || !closing_tag_omitted(name, reason_name.as_ref()) {
1211 return None;
1212 }
1213 Some(reason_name)
1214}
1215
1216fn previous_significant_sibling<'tree>(source: &str, node: TsNode<'tree>) -> Option<TsNode<'tree>> {
1217 let mut current = node.prev_named_sibling();
1218 while let Some(sibling) = current {
1219 match sibling.kind() {
1220 "comment" => current = sibling.prev_named_sibling(),
1221 "text" | "entity" => {
1222 if text_for_node(source, sibling)
1223 .chars()
1224 .all(char::is_whitespace)
1225 {
1226 current = sibling.prev_named_sibling();
1227 continue;
1228 }
1229 return Some(sibling);
1230 }
1231 _ => return Some(sibling),
1232 }
1233 }
1234 None
1235}
1236
1237fn cst_element_name(source: &str, node: TsNode<'_>) -> Option<Arc<str>> {
1238 let start_tag = find_direct_named_child(node, "start_tag")
1239 .or_else(|| find_direct_named_child(node, "self_closing_tag"))?;
1240 let tag_name = find_direct_named_child(start_tag, "tag_name")?;
1241 Some(text_for_node(source, tag_name))
1242}
1243
1244fn has_cst_end_tag(node: TsNode<'_>) -> bool {
1245 find_direct_named_child(node, "end_tag").is_some()
1246}
1247
1248fn closing_tag_omitted(current: &str, next: &str) -> bool {
1249 matches!(
1250 (current, next),
1251 ("li", "li")
1252 | ("dt", "dt" | "dd")
1253 | ("dd", "dt" | "dd")
1254 | (
1255 "p",
1256 "address"
1257 | "article"
1258 | "aside"
1259 | "blockquote"
1260 | "div"
1261 | "dl"
1262 | "fieldset"
1263 | "footer"
1264 | "form"
1265 | "h1"
1266 | "h2"
1267 | "h3"
1268 | "h4"
1269 | "h5"
1270 | "h6"
1271 | "header"
1272 | "hgroup"
1273 | "hr"
1274 | "main"
1275 | "menu"
1276 | "nav"
1277 | "ol"
1278 | "p"
1279 | "pre"
1280 | "section"
1281 | "table"
1282 | "ul"
1283 )
1284 | ("rt", "rt" | "rp")
1285 | ("rp", "rt" | "rp")
1286 | ("optgroup", "optgroup")
1287 | ("option", "option" | "optgroup")
1288 | ("thead", "tbody" | "tfoot")
1289 | ("tbody", "tbody" | "tfoot")
1290 | ("tfoot", "tbody")
1291 | ("tr", "tr" | "tbody")
1292 | ("td", "td" | "th" | "tr")
1293 | ("th", "td" | "th" | "tr")
1294 )
1295}
1296
1297fn error_branch_kind(source: &str, node: TsNode<'_>) -> Option<BlockBranchKind> {
1298 let children = named_children_vec(node);
1299
1300 for child in &children {
1301 if let Some(kind) = branch_kind_from_node(source, *child) {
1302 return Some(kind);
1303 }
1304 }
1305
1306 if children.len() == 1 && children[0].kind() == "ERROR" {
1307 return error_branch_kind(source, children[0]);
1308 }
1309
1310 None
1311}
1312
1313fn branch_start_in_error(source: &str, node: TsNode<'_>) -> usize {
1314 let children = named_children_vec(node);
1315 for child in &children {
1316 if branch_kind_from_node(source, *child).is_some() {
1317 return branch_start(*child);
1318 }
1319 }
1320
1321 if children.len() == 1 && children[0].kind() == "ERROR" {
1322 return branch_start_in_error(source, children[0]);
1323 }
1324
1325 node.start_byte().saturating_add(1)
1326}
1327
1328fn next_branch_after_node(source: &str, node: TsNode<'_>) -> Option<(BlockBranchKind, usize)> {
1329 let mut current = node.next_named_sibling();
1330 while let Some(sibling) = current {
1331 match sibling.kind() {
1332 "comment" => current = sibling.next_named_sibling(),
1333 "text" | "entity"
1334 if text_for_node(source, sibling)
1335 .chars()
1336 .all(char::is_whitespace) =>
1337 {
1338 current = sibling.next_named_sibling();
1339 }
1340 "ERROR" => {
1341 let kind = error_branch_kind(source, sibling)?;
1342 return Some((kind, branch_start_in_error(source, sibling)));
1343 }
1344 _ => {
1345 let kind = branch_kind_from_node(source, sibling)?;
1346 return Some((kind, branch_start(sibling)));
1347 }
1348 }
1349 }
1350
1351 let parent = node.parent()?;
1352 match parent.kind() {
1353 "await_pending" | "await_branch_children" => next_branch_after_node(source, parent),
1354 _ => None,
1355 }
1356}
1357
1358fn branch_in_section_container(source: &str, node: TsNode<'_>) -> Option<(BlockBranchKind, usize)> {
1359 let child = last_significant_child(source, node)?;
1360 match child.kind() {
1361 "ERROR" => {
1362 let kind = error_branch_kind(source, child)?;
1363 Some((kind, branch_start_in_error(source, child)))
1364 }
1365 _ => {
1366 let kind = branch_kind_from_node(source, child)?;
1367 Some((kind, branch_start(child)))
1368 }
1369 }
1370}
1371
1372fn node_leaves_scope_open(source: &str, node: TsNode<'_>) -> bool {
1373 match node.kind() {
1374 "start_tag" => true,
1375 "element" => {
1376 !has_named_descendant(node, "end_tag")
1377 && !has_named_descendant(node, "self_closing_tag")
1378 }
1379 kind if is_typed_block_kind(kind) => !has_named_descendant(node, "block_end"),
1380 "await_pending" | "await_branch_children" => last_significant_child(source, node)
1381 .is_some_and(|child| node_leaves_scope_open(source, child)),
1382 "ERROR" => true,
1383 _ => false,
1384 }
1385}
1386
1387fn has_named_descendant(node: TsNode<'_>, kind: &str) -> bool {
1388 let mut cursor = node.walk();
1389 node.named_children(&mut cursor)
1390 .any(|child| child.kind() == kind || has_named_descendant(child, kind))
1391}
1392
1393fn has_typed_block_descendant(node: TsNode<'_>) -> bool {
1394 let mut cursor = node.walk();
1395 node.named_children(&mut cursor)
1396 .any(|child| is_typed_block_kind(child.kind()) || has_typed_block_descendant(child))
1397}
1398
1399fn find_missing<'a>(node: TsNode<'a>, kind: &str) -> Option<TsNode<'a>> {
1400 for index in 0..node.child_count() {
1401 let child = node.child(index as u32)?;
1402 if child.is_missing() && child.kind() == kind {
1403 return Some(child);
1404 }
1405 if let Some(found) = find_missing(child, kind) {
1406 return Some(found);
1407 }
1408 }
1409 None
1410}
1411
1412fn invalid_closing_tag_name(source: &str, node: TsNode<'_>) -> Option<Arc<str>> {
1413 if has_named_descendant(node, "start_tag")
1414 || has_named_descendant(node, "self_closing_tag")
1415 || has_typed_block_descendant(node)
1416 {
1417 return None;
1418 }
1419
1420 find_descendant(node, |child| child.kind() == "tag_name").map(|tag| text_for_node(source, tag))
1421}
1422
1423fn erroneous_end_tag_name(source: &str, node: TsNode<'_>) -> Option<Arc<str>> {
1424 find_descendant(node, |child| child.kind() == "erroneous_end_tag_name")
1425 .map(|name| text_for_node(source, name))
1426}
1427
1428fn find_descendant<'a, F>(node: TsNode<'a>, matches: F) -> Option<TsNode<'a>>
1429where
1430 F: Fn(TsNode<'a>) -> bool + Copy,
1431{
1432 if matches(node) {
1433 return Some(node);
1434 }
1435
1436 for index in 0..node.child_count() {
1437 let Some(child) = node.child(index as u32) else {
1438 continue;
1439 };
1440 if let Some(found) = find_descendant(child, matches) {
1441 return Some(found);
1442 }
1443 }
1444
1445 None
1446}
1447
1448fn last_significant_child<'a>(source: &str, node: TsNode<'a>) -> Option<TsNode<'a>> {
1449 named_children_vec(node).into_iter().rev().find(|child| {
1450 !matches!(child.kind(), "comment")
1451 && (!(matches!(child.kind(), "text" | "entity"))
1452 || !text_for_node(source, *child)
1453 .chars()
1454 .all(char::is_whitespace))
1455 })
1456}
1457
1458fn raw_text_error_name(source: &str, error: TsNode<'_>) -> Option<Arc<str>> {
1459 let start_tag = find_direct_named_child(error, "start_tag")?;
1460 let name = find_direct_named_child(start_tag, "tag_name")?;
1461 Some(text_for_node(source, name))
1462}
1463
1464fn find_direct_named_child<'a>(node: TsNode<'a>, kind: &str) -> Option<TsNode<'a>> {
1465 let mut cursor = node.walk();
1466 node.named_children(&mut cursor)
1467 .find(|child| child.kind() == kind)
1468}
1469
1470fn parse_modern_text(source: &str, node: TsNode<'_>) -> Text {
1471 let raw = text_for_node(source, node);
1472 let data = Arc::from(decode_html_entities_cow(raw.as_ref()).into_owned());
1473
1474 Text {
1475 start: node.start_byte(),
1476 end: node.end_byte(),
1477 raw,
1478 data,
1479 }
1480}
1481
1482pub(crate) fn parse_modern_comment(source: &str, node: TsNode<'_>) -> Comment {
1483 let raw = text_for_node(source, node);
1484 let data_raw: &str = raw.as_ref();
1485 let data_raw: &str = if let Some(tail) = data_raw.strip_prefix("<!--") {
1486 tail.strip_suffix("-->").unwrap_or(tail)
1487 } else {
1488 data_raw
1489 };
1490 let data: Arc<str> = Arc::from(data_raw);
1491
1492 Comment {
1493 start: node.start_byte(),
1494 end: node.end_byte(),
1495 data,
1496 }
1497}
1498
1499pub(crate) fn push_modern_text_node(nodes: &mut Vec<Node>, text: Text) {
1500 if let Some(Node::Text(last)) = nodes.last_mut()
1501 && last.end == text.start
1502 {
1503 let merged_raw = format!("{}{}", last.raw, text.raw);
1504 let merged_data = format!("{}{}", last.data, text.data);
1505 last.end = text.end;
1506 last.raw = Arc::from(merged_raw);
1507 last.data = Arc::from(merged_data);
1508 return;
1509 }
1510
1511 nodes.push(Node::Text(text));
1512}
1513
1514pub fn modern_node_start(node: &Node) -> usize {
1515 node.start()
1516}
1517
1518pub fn modern_node_end(node: &Node) -> usize {
1519 node.end()
1520}
1521
1522pub(super) fn parse_modern_script(
1523 source: &str,
1524 element: TsNode<'_>,
1525 _leading_comment: Option<&str>,
1526) -> Option<Script> {
1527 let start_tag = find_first_named_child(element, "start_tag")?;
1528 let end_tag = find_first_named_child(element, "end_tag")?;
1529 let attributes = parse_modern_attributes(source, start_tag, false);
1530
1531 let context = attributes
1532 .iter()
1533 .find_map(|attribute| match attribute {
1534 Attribute::Attribute(NamedAttribute { name, value, .. })
1535 if name.as_ref() == "module" =>
1536 {
1537 Some(ScriptContext::Module)
1538 }
1539 Attribute::Attribute(NamedAttribute { name, value, .. })
1540 if name.as_ref() == "context" && modern_attribute_value_is_module(value) =>
1541 {
1542 Some(ScriptContext::Module)
1543 }
1544 _ => None,
1545 })
1546 .unwrap_or(ScriptContext::Default);
1547
1548 let is_ts = attributes.iter().any(|attribute| {
1549 matches!(
1550 attribute,
1551 Attribute::Attribute(NamedAttribute { name, value, .. })
1552 if name.as_ref() == "lang"
1553 && matches!(
1554 value,
1555 AttributeValueList::Values(values)
1556 if matches!(
1557 values.first(),
1558 Some(AttributeValue::Text(Text { data, .. }))
1559 if data.as_ref() == "ts"
1560 )
1561 )
1562 )
1563 });
1564
1565 let content_start = start_tag.end_byte();
1566 let content_end = end_tag.start_byte();
1567 let content_source = source.get(content_start..content_end).unwrap_or_default();
1568 let content = crate::parse::parse_modern_program_content_with_offsets(
1569 content_source,
1570 content_start,
1571 start_tag.start_position().row + 1,
1572 0,
1573 end_tag.end_position().row + 1,
1574 end_tag.end_position().column,
1575 is_ts,
1576 )
1577 .unwrap_or_else(|| crate::parse::ParsedProgramContent {
1578 parsed: Arc::new(crate::js::ParsedJsProgram::parse(
1579 content_source,
1580 if is_ts {
1581 oxc_span::SourceType::ts().with_module(true)
1582 } else {
1583 oxc_span::SourceType::mjs()
1584 },
1585 )),
1586 });
1587
1588 Some(Script {
1589 r#type: ScriptType::Script,
1590 start: element.start_byte(),
1591 end: element.end_byte(),
1592 content_start,
1593 content_end,
1594 context,
1595 content: content.parsed,
1596 attributes: attributes.into_boxed_slice(),
1597 })
1598}
1599
1600pub(super) fn parse_modern_options(source: &str, element: TsNode<'_>) -> Option<Options> {
1601 let tag_node = find_first_named_child(element, "self_closing_tag")
1602 .or_else(|| find_first_named_child(element, "start_tag"))?;
1603 let attributes = parse_modern_attributes(source, tag_node, false);
1604 let fragment = parse_modern_options_fragment(source, element);
1605
1606 let mut custom_element = None;
1607 let mut runes = None;
1608
1609 for attribute in &attributes {
1610 if let Attribute::Attribute(NamedAttribute {
1611 name,
1612 value,
1613 value_syntax,
1614 ..
1615 }) = attribute
1616 {
1617 if name.as_ref() == "customElement"
1618 && let AttributeValueList::Values(values) = value
1619 && let Some(AttributeValue::Text(Text { data, .. })) = values.first()
1620 {
1621 custom_element = Some(CustomElement { tag: data.clone() });
1622 }
1623
1624 if name.as_ref() == "runes" {
1625 match value_syntax {
1626 AttributeValueSyntax::Boolean => runes = Some(true),
1627 _ if matches!(value, AttributeValueList::ExpressionTag(_)) => {
1628 let AttributeValueList::ExpressionTag(tag) = value else {
1629 unreachable!("checked expression tag");
1630 };
1631 if expression_literal_bool(&tag.expression).is_some() {
1632 runes = expression_literal_bool(&tag.expression);
1633 }
1634 }
1635 _ => {}
1636 }
1637 }
1638 }
1639 }
1640
1641 Some(Options {
1642 start: element.start_byte(),
1643 end: element.end_byte(),
1644 attributes: attributes.into_boxed_slice(),
1645 fragment,
1646 custom_element,
1647 runes,
1648 })
1649}
1650
1651fn parse_modern_options_fragment(source: &str, element: TsNode<'_>) -> Fragment {
1652 let mut nodes = Vec::new();
1653 let mut cursor = element.walk();
1654
1655 for child in element.named_children(&mut cursor) {
1656 match child.kind() {
1657 "start_tag" | "self_closing_tag" | "end_tag" => {}
1658 "text" | "entity" | "raw_text" => {
1659 push_modern_text_node(&mut nodes, parse_modern_text(source, child));
1660 }
1661 "comment" => nodes.push(Node::Comment(parse_modern_comment(source, child))),
1662 "expression" => {
1663 if let Some(tag) = parse_modern_expression_tag(source, child) {
1664 nodes.push(Node::ExpressionTag(tag));
1665 }
1666 }
1667 kind if is_typed_tag_kind(kind) => {
1668 if let Some(tag) = parse_modern_tag(source, child) {
1669 nodes.push(tag);
1670 }
1671 }
1672 kind if is_typed_block_kind(kind) => {
1673 if let Some(block) = parse_modern_block(source, child, None) {
1674 nodes.push(block);
1675 }
1676 }
1677 "element" => nodes.push(parse_modern_element_node(
1678 source, child, false, false, false, None,
1679 )),
1680 "ERROR" => {
1681 let mut recovered = recover_modern_error_nodes(source, child, false);
1682 nodes.append(&mut recovered);
1683 }
1684 _ => {}
1685 }
1686 }
1687
1688 Fragment {
1689 r#type: FragmentType::Fragment,
1690 nodes: nodes.into_boxed_slice(),
1691 }
1692}
1693
1694pub(super) fn parse_modern_style(source: &str, element: TsNode<'_>) -> Option<Css> {
1695 let start_tag = find_first_named_child(element, "start_tag")?;
1696 let end_tag = find_first_named_child(element, "end_tag");
1697 let content_start = start_tag.end_byte();
1698 let content_end = end_tag
1699 .map(|node: TsNode<'_>| node.start_byte())
1700 .unwrap_or(element.end_byte());
1701 let attributes = parse_modern_attributes(source, start_tag, false).into_boxed_slice();
1702
1703 let children = crate::parse::parse_modern_css_nodes(source, content_start, content_end);
1704
1705 Some(Css {
1706 r#type: CssType::StyleSheet,
1707 start: element.start_byte(),
1708 end: element.end_byte(),
1709 attributes,
1710 children: children.into_boxed_slice(),
1711 content: CssContent {
1712 start: content_start,
1713 end: content_end,
1714 styles: Arc::from(source.get(content_start..content_end).unwrap_or_default()),
1715 comment: None,
1716 },
1717 })
1718}
1719
1720fn modern_attribute_value_is_module(value: &AttributeValueList) -> bool {
1721 match value {
1722 AttributeValueList::Boolean(_) => false,
1723 AttributeValueList::Values(values) => values.iter().any(|value| {
1724 matches!(
1725 value,
1726 AttributeValue::Text(Text { data, .. }) if data.as_ref() == "module"
1727 )
1728 }),
1729 AttributeValueList::ExpressionTag(tag) => {
1730 expression_identifier_name(&tag.expression)
1731 .is_some_and(|name| name.as_ref() == "module")
1732 || expression_literal_string(&tag.expression)
1733 .is_some_and(|value| value.as_ref() == "module")
1734 }
1735 }
1736}
1737
1738pub(super) fn modern_element_name(source: &str, element: TsNode<'_>) -> Option<Arc<str>> {
1739 let mut cursor = element.walk();
1740 for child in element.named_children(&mut cursor) {
1741 match child.kind() {
1742 "start_tag" | "self_closing_tag" => {
1743 if let Some(tag_name) = find_first_named_child(child, "tag_name") {
1744 return Some(text_for_node(source, tag_name));
1745 }
1746 }
1747 _ => {}
1748 }
1749 }
1750 None
1751}
1752
1753pub(super) fn recover_modern_error_nodes(
1754 source: &str,
1755 error_node: TsNode<'_>,
1756 in_shadowroot_template: bool,
1757) -> Vec<crate::ast::modern::Node> {
1758 if let Some(block) = recover_malformed_snippet_block(source, error_node) {
1759 return vec![crate::ast::modern::Node::SnippetBlock(block)];
1760 }
1761 let error_children = named_children_vec(error_node);
1762 parse_modern_nodes_slice(source, &error_children, in_shadowroot_template)
1763}
1764
1765fn parse_modern_collapsed_comment_tag_sequence(
1766 source: &str,
1767 node: TsNode<'_>,
1768) -> Option<(Vec<crate::ast::modern::Node>, Vec<RootComment>)> {
1769 if node.kind() != "element" {
1770 return None;
1771 }
1772
1773 let start_tag = find_first_named_child(node, "start_tag")?;
1774 if start_tag.start_byte() != node.start_byte() || start_tag.end_byte() != node.end_byte() {
1775 return None;
1776 }
1777
1778 let raw = text_for_node(source, start_tag);
1779 let raw_ref = raw.as_ref();
1780 if !(raw_ref.contains("//") || raw_ref.contains("/*")) || !raw_ref.contains("</") {
1781 return None;
1782 }
1783
1784 parse_collapsed_tag_sequence_from_text(source, node.start_byte(), raw_ref)
1785}
1786
1787fn parse_collapsed_tag_sequence_from_text(
1788 source: &str,
1789 base: usize,
1790 raw: &str,
1791) -> Option<(Vec<crate::ast::modern::Node>, Vec<RootComment>)> {
1792 let bytes = raw.as_bytes();
1793 let mut index = 0usize;
1794 let mut nodes = Vec::new();
1795 let mut comments = Vec::new();
1796
1797 while index < bytes.len() {
1798 if bytes[index].is_ascii_whitespace() {
1799 let ws_start = index;
1800 while index < bytes.len() && bytes[index].is_ascii_whitespace() {
1801 index += 1;
1802 }
1803 if index > ws_start {
1804 push_modern_text_node(
1805 &mut nodes,
1806 Text {
1807 start: base + ws_start,
1808 end: base + index,
1809 raw: Arc::from(&raw[ws_start..index]),
1810 data: Arc::from(&raw[ws_start..index]),
1811 },
1812 );
1813 }
1814 continue;
1815 }
1816
1817 if bytes.get(index) != Some(&b'<') {
1818 let text_start = index;
1819 while index < bytes.len() && bytes[index] != b'<' {
1820 index += 1;
1821 }
1822 push_modern_text_node(
1823 &mut nodes,
1824 Text {
1825 start: base + text_start,
1826 end: base + index,
1827 raw: Arc::from(&raw[text_start..index]),
1828 data: Arc::from(&raw[text_start..index]),
1829 },
1830 );
1831 continue;
1832 }
1833
1834 let tag_start = index;
1835 index += 1;
1836 if bytes.get(index) == Some(&b'/') {
1837 break;
1838 }
1839
1840 let name_start = index;
1841 while index < bytes.len()
1842 && (bytes[index].is_ascii_alphanumeric()
1843 || bytes[index] == b'-'
1844 || bytes[index] == b':')
1845 {
1846 index += 1;
1847 }
1848 if index == name_start {
1849 return None;
1850 }
1851 let name = &raw[name_start..index];
1852
1853 let mut attributes = Vec::new();
1854 loop {
1855 while index < bytes.len() && bytes[index].is_ascii_whitespace() {
1856 index += 1;
1857 }
1858 if index >= bytes.len() {
1859 return None;
1860 }
1861 if bytes[index] == b'>' {
1862 index += 1;
1863 break;
1864 }
1865
1866 if bytes[index] == b'/' && bytes.get(index + 1) == Some(&b'/') {
1867 let comment_start = index;
1868 index += 2;
1869 let value_start = index;
1870 while index < bytes.len() && bytes[index] != b'\n' {
1871 index += 1;
1872 }
1873 let comment_end = index;
1874 comments.push(modern_root_comment(
1875 source,
1876 RootCommentType::Line,
1877 base + comment_start,
1878 base + comment_end,
1879 Arc::from(&raw[value_start..comment_end]),
1880 ));
1881 continue;
1882 }
1883
1884 if bytes[index] == b'/' && bytes.get(index + 1) == Some(&b'*') {
1885 let comment_start = index;
1886 index += 2;
1887 let value_start = index;
1888 let tail = &raw[index..];
1889 let rel_end = tail.find("*/")?;
1890 let value_end = index + rel_end;
1891 index = value_end + 2;
1892 comments.push(modern_root_comment(
1893 source,
1894 RootCommentType::Block,
1895 base + comment_start,
1896 base + index,
1897 Arc::from(&raw[value_start..value_end]),
1898 ));
1899 continue;
1900 }
1901
1902 let attr_start = index;
1903 while index < bytes.len()
1904 && !bytes[index].is_ascii_whitespace()
1905 && bytes[index] != b'='
1906 && bytes[index] != b'>'
1907 {
1908 index += 1;
1909 }
1910 if index == attr_start {
1911 return None;
1912 }
1913 let attr_name = &raw[attr_start..index];
1914 let name_loc = modern_name_location(source, base + attr_start, base + index);
1915
1916 while index < bytes.len() && bytes[index].is_ascii_whitespace() {
1917 index += 1;
1918 }
1919
1920 let value = if bytes.get(index) == Some(&b'=') {
1921 index += 1;
1922 while index < bytes.len() && bytes[index].is_ascii_whitespace() {
1923 index += 1;
1924 }
1925
1926 if let Some(quote) = bytes
1927 .get(index)
1928 .copied()
1929 .filter(|q| *q == b'"' || *q == b'\'')
1930 {
1931 index += 1;
1932 let value_start = index;
1933 while index < bytes.len() && bytes[index] != quote {
1934 index += 1;
1935 }
1936 let value_end = index;
1937 if bytes.get(index) == Some("e) {
1938 index += 1;
1939 }
1940
1941 AttributeValueList::Values(
1942 vec![AttributeValue::Text(Text {
1943 start: base + value_start,
1944 end: base + value_end,
1945 raw: Arc::from(&raw[value_start..value_end]),
1946 data: Arc::from(&raw[value_start..value_end]),
1947 })]
1948 .into_boxed_slice(),
1949 )
1950 } else {
1951 AttributeValueList::Boolean(true)
1952 }
1953 } else {
1954 AttributeValueList::Boolean(true)
1955 };
1956 let value_syntax = match &value {
1957 AttributeValueList::Boolean(_) => AttributeValueSyntax::Boolean,
1958 AttributeValueList::Values(_) | AttributeValueList::ExpressionTag(_) => {
1959 AttributeValueSyntax::Quoted
1960 }
1961 };
1962
1963 attributes.push(Attribute::Attribute(NamedAttribute {
1964 start: base + attr_start,
1965 end: base + index,
1966 name: Arc::from(attr_name),
1967 name_loc,
1968 value,
1969 value_syntax,
1970 error: None,
1971 }));
1972 }
1973
1974 let close_tag = format!("</{name}>");
1975 let close_rel = raw[index..].find(&close_tag)?;
1976 let close_start = index + close_rel;
1977 let close_end = close_start + close_tag.len();
1978
1979 let name_loc =
1980 modern_name_location(source, base + name_start, base + name_start + name.len());
1981 nodes.push(crate::ast::modern::Node::RegularElement(RegularElement {
1982 start: base + tag_start,
1983 end: base + close_end,
1984 name: Arc::from(name),
1985 name_loc,
1986 self_closing: false,
1987 has_end_tag: true,
1988 attributes: attributes.into_boxed_slice(),
1989 fragment: crate::ast::modern::Fragment {
1990 r#type: crate::ast::modern::FragmentType::Fragment,
1991 nodes: Box::new([]),
1992 },
1993 }));
1994
1995 index = close_end;
1996 }
1997
1998 Some((nodes, comments))
1999}
2000
2001pub(super) fn modern_name_location(source: &str, start: usize, end: usize) -> LegacyNameLocation {
2002 LegacyNameLocation {
2003 start: source_location_at_offset(source, start),
2004 end: source_location_at_offset(source, end),
2005 }
2006}
2007
2008pub(super) fn modern_root_comment(
2009 source: &str,
2010 kind: RootCommentType,
2011 start: usize,
2012 end: usize,
2013 value: Arc<str>,
2014) -> RootComment {
2015 RootComment {
2016 r#type: kind,
2017 start,
2018 end,
2019 value,
2020 loc: LegacyNameLocation {
2021 start: source_location_at_offset(source, start),
2022 end: source_location_at_offset(source, end),
2023 },
2024 }
2025}
2026
2027fn collect_modern_tag_comments(source: &str, root: TsNode<'_>) -> Vec<RootComment> {
2028 let mut out = Vec::new();
2029 let mut stack = vec![root];
2030
2031 while let Some(node) = stack.pop() {
2032 let mut cursor = node.walk();
2033 for child in node.named_children(&mut cursor) {
2034 if child.kind() == "tag_comment"
2035 && let Some(comment) = parse_modern_tag_comment(source, child)
2036 {
2037 out.push(comment);
2038 }
2039 stack.push(child);
2040 }
2041 }
2042
2043 out
2044}
2045
2046fn parse_modern_tag_comment(source: &str, node: TsNode<'_>) -> Option<RootComment> {
2047 let raw = text_for_node(source, node);
2048 let raw_ref = raw.as_ref();
2049
2050 if let Some(value) = raw_ref.strip_prefix("//") {
2051 return Some(modern_root_comment(
2052 source,
2053 RootCommentType::Line,
2054 node.start_byte(),
2055 node.end_byte(),
2056 Arc::from(value),
2057 ));
2058 }
2059
2060 if let Some(tail) = raw_ref.strip_prefix("/*")
2061 && let Some(inner) = tail.strip_suffix("*/")
2062 {
2063 return Some(modern_root_comment(
2064 source,
2065 RootCommentType::Block,
2066 node.start_byte(),
2067 node.end_byte(),
2068 Arc::from(inner),
2069 ));
2070 }
2071
2072 None
2073}
2074
2075fn parse_modern_block(
2076 source: &str,
2077 block: TsNode<'_>,
2078 _hint: Option<&IncrementalHint<'_>>,
2079) -> Option<Node> {
2080 match block.kind() {
2082 "if_block" => parse_modern_if_block(source, block).map(Node::IfBlock),
2083 "each_block" => parse_modern_each_block(source, block).map(Node::EachBlock),
2084 "key_block" => parse_modern_key_block(source, block).map(Node::KeyBlock),
2085 "await_block" => parse_modern_await_block(source, block).map(Node::AwaitBlock),
2086 "snippet_block" => parse_modern_snippet_block(source, block).map(Node::SnippetBlock),
2087 _ => None,
2088 }
2089}
2090
2091fn parse_modern_tag(source: &str, tag: TsNode<'_>) -> Option<Node> {
2092 match tag.kind() {
2093 "render_tag" => Some(Node::RenderTag(RenderTag {
2094 start: tag.start_byte(),
2095 end: tag.end_byte(),
2096 expression: parse_special_tag_expression(source, tag)?,
2097 })),
2098 "html_tag" => Some(Node::HtmlTag(HtmlTag {
2099 start: tag.start_byte(),
2100 end: tag.end_byte(),
2101 expression: parse_special_tag_expression(source, tag)?,
2102 })),
2103 "const_tag" => Some(Node::ConstTag(ConstTag {
2104 start: tag.start_byte(),
2105 end: tag.end_byte(),
2106 declaration: parse_const_tag_declaration(source, tag)
2107 .or_else(|| parse_special_tag_expression(source, tag))?,
2108 })),
2109 "debug_tag" => {
2110 let arguments = parse_modern_debug_tag_arguments(source, tag);
2111 let identifiers = debug_tag_identifiers(&arguments);
2112 Some(Node::DebugTag(DebugTag {
2113 start: tag.start_byte(),
2114 end: tag.end_byte(),
2115 arguments,
2116 identifiers,
2117 }))
2118 }
2119 _ => None,
2120 }
2121}
2122
2123fn special_tag_expression_node(tag: TsNode<'_>) -> Option<TsNode<'_>> {
2124 find_first_named_child(tag, "expression_value")
2125 .or_else(|| find_first_named_child(tag, "expression"))
2126}
2127
2128fn parse_special_tag_expression(source: &str, tag: TsNode<'_>) -> Option<Expression> {
2129 special_tag_expression_node(tag).and_then(|node| parse_modern_expression_field(source, node))
2130}
2131
2132fn parse_const_tag_declaration(source: &str, tag: TsNode<'_>) -> Option<Expression> {
2133 if tag.kind() != "const_tag" || tag.end_byte() <= tag.start_byte() + 3 {
2134 return None;
2135 }
2136
2137 let declaration_source = source.get(tag.start_byte() + 2..tag.end_byte().saturating_sub(1))?;
2138 let program = crate::parse::parse_modern_program_content_with_offsets(
2139 declaration_source,
2140 tag.start_byte() + 2,
2141 tag.start_position().row + 1,
2142 tag.start_position().column + 2,
2143 tag.end_position().row + 1,
2144 tag.end_position().column.saturating_sub(1),
2145 true,
2146 )?;
2147
2148 let [declaration] = program.parsed.program().body.as_slice() else {
2149 return None;
2150 };
2151
2152 let span = declaration.span();
2153 Some(Expression::from_statement(
2154 program.parsed,
2155 0,
2156 tag.start_byte() + 2 + span.start as usize,
2157 tag.start_byte() + 2 + span.end as usize,
2158 ))
2159}
2160
2161fn parse_modern_debug_tag_arguments(source: &str, tag: TsNode<'_>) -> Box<[Expression]> {
2162 let expr_node = special_tag_expression_node(tag);
2163 let Some(expr_node) = expr_node else {
2164 return Box::new([]);
2165 };
2166
2167 parse_modern_expression_field(source, expr_node)
2168 .map(split_debug_tag_arguments)
2169 .unwrap_or_default()
2170}
2171
2172pub(crate) fn split_debug_tag_arguments(expression: Expression) -> Box<[Expression]> {
2173 crate::parse::oxc_query::split_debug_tag_arguments(expression)
2174}
2175
2176fn debug_tag_identifiers(arguments: &[Expression]) -> Box<[Identifier]> {
2177 arguments
2178 .iter()
2179 .filter_map(|argument| modern_identifier_from_expression(argument.clone()))
2180 .collect::<Vec<_>>()
2181 .into_boxed_slice()
2182}
2183
2184fn modern_identifier_from_expression(expression: Expression) -> Option<Identifier> {
2185 let name = crate::parse::oxc_query::expression_identifier_name(&expression)?;
2186 Some(Identifier {
2187 start: expression.start,
2188 end: expression.end,
2189 loc: None,
2190 name,
2191 })
2192}
2193
2194fn parse_modern_if_block(source: &str, block: TsNode<'_>) -> Option<IfBlock> {
2195 let children = named_children_vec(block);
2196
2197 let test_expr = block
2198 .child_by_field_name("expression")
2199 .map(|node| parse_modern_expression_field_or_empty(source, node))
2200 .unwrap_or_else(|| modern_empty_identifier_at_block_tag_end(block));
2201
2202 let end_idx = children
2203 .iter()
2204 .rposition(|c| c.kind() == "block_end")
2205 .unwrap_or(children.len());
2206 let body_start = body_start_index(block, &children, &["expression"]);
2207 let branch_indices: Vec<usize> = children
2208 .iter()
2209 .enumerate()
2210 .filter_map(|(idx, node)| {
2211 matches!(node.kind(), "else_if_clause" | "else_clause").then_some(idx)
2212 })
2213 .collect();
2214
2215 let consequent_end = branch_indices.first().copied().unwrap_or(end_idx);
2216 let consequent = Fragment {
2217 r#type: FragmentType::Fragment,
2218 nodes: parse_modern_nodes_slice(source, &children[body_start..consequent_end], false)
2219 .into_boxed_slice(),
2220 };
2221
2222 let alternate = if branch_indices.is_empty() {
2223 None
2224 } else {
2225 parse_modern_alternate(source, &children, &branch_indices, 0, end_idx).map(Box::new)
2226 };
2227
2228 Some(IfBlock {
2229 elseif: false,
2230 start: block.start_byte(),
2231 end: block.end_byte(),
2232 test: test_expr,
2233 consequent,
2234 alternate,
2235 })
2236}
2237
2238fn parse_modern_each_block(source: &str, block: TsNode<'_>) -> Option<EachBlock> {
2239 let children = named_children_vec(block);
2240 let end_idx = children
2241 .iter()
2242 .rposition(|c| c.kind() == "block_end")
2243 .unwrap_or(children.len());
2244 let has_as_clause = cst_node_has_direct_token(block, "as");
2245
2246 let mut expression = block
2247 .child_by_field_name("expression")
2248 .map(|node| parse_modern_expression_field_or_empty(source, node))
2249 .unwrap_or_else(|| modern_empty_identifier_at_block_tag_end(block));
2250
2251 let (context, context_error) = block
2252 .child_by_field_name("binding")
2253 .map(|node| parse_modern_binding_field_with_error(source, node, true))
2254 .unwrap_or((None, None));
2255
2256 let mut index = block
2257 .child_by_field_name("index")
2258 .map(|node| text_for_node(source, node).trim().to_string())
2259 .filter(|text| !text.is_empty())
2260 .map(Arc::<str>::from);
2261
2262 let mut key = block
2263 .child_by_field_name("key")
2264 .map(|node| parse_modern_expression_field_or_empty(source, node));
2265
2266 let mut invalid_key_without_as = false;
2267 if !has_as_clause
2268 && context.is_none()
2269 && key.is_none()
2270 && let Some(expression_field) = block.child_by_field_name("expression")
2271 && let Some(recovered) = recover_each_header_without_as_key(source, expression_field)
2272 {
2273 expression = recovered.expression;
2274 index = recovered.index;
2275 key = Some(recovered.key);
2276 invalid_key_without_as = true;
2277 }
2278
2279 let body_start = body_start_index(block, &children, &["expression", "binding", "index", "key"]);
2280 let branch_indices: Vec<usize> = children
2281 .iter()
2282 .enumerate()
2283 .filter_map(|(idx, node)| (node.kind() == "else_clause").then_some(idx))
2284 .collect();
2285
2286 let body_end = branch_indices.first().copied().unwrap_or(end_idx);
2287 let body_nodes = parse_modern_nodes_slice(source, &children[body_start..body_end], false);
2288 let fallback = branch_indices.iter().find_map(|branch_index| {
2289 let branch = *children.get(*branch_index)?;
2290 if branch.kind() != "else_clause" {
2291 return None;
2292 }
2293 let clause_children = named_children_vec(branch);
2295 Some(Fragment {
2296 r#type: FragmentType::Fragment,
2297 nodes: parse_modern_nodes_slice(source, &clause_children, false).into_boxed_slice(),
2298 })
2299 });
2300
2301 Some(EachBlock {
2302 start: block.start_byte(),
2303 end: block.end_byte(),
2304 expression,
2305 body: Fragment {
2306 r#type: FragmentType::Fragment,
2307 nodes: body_nodes.into_boxed_slice(),
2308 },
2309 has_as_clause,
2310 invalid_key_without_as,
2311 context,
2312 context_error,
2313 index,
2314 key,
2315 fallback,
2316 })
2317}
2318
2319struct EachHeaderMissingAsRecovery {
2320 expression: Expression,
2321 index: Option<Arc<str>>,
2322 key: Expression,
2323}
2324
2325fn recover_each_header_without_as_key(
2326 source: &str,
2327 expression_field: TsNode<'_>,
2328) -> Option<EachHeaderMissingAsRecovery> {
2329 let raw = expression_field.utf8_text(source.as_bytes()).ok()?;
2330 let trimmed = raw.trim();
2331 if trimmed.is_empty() {
2332 return None;
2333 }
2334 let field_abs = expression_field.start_byte() + raw.find(trimmed).unwrap_or(0);
2335 let segments = split_top_level_commas(trimmed);
2336 if segments.len() < 2 {
2337 return None;
2338 }
2339
2340 let expression_segment = segments.first()?.0.trim();
2341 if expression_segment.is_empty() {
2342 return None;
2343 }
2344 let expression_abs = field_abs + trimmed.find(expression_segment).unwrap_or(0);
2345 let (expression_line, expression_col) = line_column_at_offset(source, expression_abs);
2346 let expression = parse_modern_expression_from_text(
2347 expression_segment,
2348 expression_abs,
2349 expression_line,
2350 expression_col,
2351 )?;
2352
2353 let tail_offset = segments.get(1)?.1;
2354 let tail = trimmed.get(tail_offset..)?.trim();
2355 let tail_abs = field_abs + tail_offset + trimmed.get(tail_offset..)?.find(tail).unwrap_or(0);
2356 let (binding_raw, key_raw, key_inner_offset) = split_trailing_parenthesized_group(tail)?;
2357
2358 let binding = binding_raw.trim();
2359 if binding.is_empty() || parse_identifier_name(binding).is_none() {
2360 return None;
2361 }
2362 let index = Some(Arc::<str>::from(binding));
2363
2364 let key_expression = key_raw.trim();
2365 if key_expression.is_empty() {
2366 return None;
2367 }
2368 let key_abs = tail_abs + key_inner_offset + key_raw.find(key_expression).unwrap_or(0);
2369 let (key_line, key_col) = line_column_at_offset(source, key_abs);
2370 let key = parse_modern_expression_from_text(key_expression, key_abs, key_line, key_col)?;
2371
2372 Some(EachHeaderMissingAsRecovery {
2373 expression,
2374 index,
2375 key,
2376 })
2377}
2378
2379fn split_trailing_parenthesized_group(text: &str) -> Option<(&str, &str, usize)> {
2380 let trimmed = text.trim_end();
2381 if !trimmed.ends_with(')') {
2382 return None;
2383 }
2384
2385 let mut depth = 0usize;
2386 for (idx, ch) in trimmed.char_indices().rev() {
2387 match ch {
2388 ')' => depth += 1,
2389 '(' => {
2390 depth = depth.saturating_sub(1);
2391 if depth == 0 {
2392 let before = &trimmed[..idx];
2393 let inner_start = idx + ch.len_utf8();
2394 let inner = &trimmed[inner_start..trimmed.len() - 1];
2395 return Some((before, inner, inner_start));
2396 }
2397 }
2398 _ => {}
2399 }
2400 }
2401
2402 None
2403}
2404
2405fn parse_modern_key_block(source: &str, block: TsNode<'_>) -> Option<KeyBlock> {
2406 let children = named_children_vec(block);
2407 let end_idx = children
2408 .iter()
2409 .rposition(|c| c.kind() == "block_end")
2410 .unwrap_or(children.len());
2411
2412 let expression = block
2413 .child_by_field_name("expression")
2414 .and_then(|node| parse_modern_expression_field(source, node))?;
2415 let body_start = body_start_index(block, &children, &["expression"]);
2416 let fragment = Fragment {
2417 r#type: FragmentType::Fragment,
2418 nodes: parse_modern_nodes_slice(source, &children[body_start..end_idx], false)
2419 .into_boxed_slice(),
2420 };
2421
2422 Some(KeyBlock {
2423 start: block.start_byte(),
2424 end: block.end_byte(),
2425 expression,
2426 fragment,
2427 })
2428}
2429
2430fn parse_modern_await_block(source: &str, block: TsNode<'_>) -> Option<AwaitBlock> {
2431 let children = named_children_vec(block);
2432 let end_idx = children
2433 .iter()
2434 .rposition(|c| c.kind() == "block_end")
2435 .unwrap_or(children.len());
2436
2437 let inline_kind = find_first_named_child(block, "shorthand_kind")
2439 .and_then(|node| node.utf8_text(source.as_bytes()).ok())
2440 .map(str::trim)
2441 .and_then(BlockBranchKind::parse_await_shorthand)
2442 .or_else(|| {
2443 if cst_node_has_direct_token(block, "then") {
2444 Some(BlockBranchKind::Then)
2445 } else if cst_node_has_direct_token(block, "catch") {
2446 Some(BlockBranchKind::Catch)
2447 } else {
2448 None
2449 }
2450 });
2451
2452 let inline_binding_field = block
2453 .child_by_field_name("binding")
2454 .and_then(|node| parse_modern_binding_field(source, node, true));
2455 let expression = block
2456 .child_by_field_name("expression")
2457 .map(|node| parse_modern_expression_field_or_empty(source, node))
2458 .unwrap_or_else(|| modern_empty_identifier_at_block_tag_end(block));
2459
2460 let branch_indices: Vec<usize> = children
2461 .iter()
2462 .enumerate()
2463 .filter_map(|(idx, node)| (node.kind() == "await_branch").then_some(idx))
2464 .collect();
2465 let first_branch_idx = branch_indices.first().copied().unwrap_or(end_idx);
2466
2467 let parse_await_children_field = |node: TsNode<'_>| -> Vec<crate::ast::modern::Node> {
2468 let child_nodes = named_children_vec(node);
2469 parse_modern_nodes_slice(source, &child_nodes, false)
2470 };
2471
2472 let pending = if inline_kind.is_some() {
2473 None
2474 } else {
2475 let mut pending_nodes = Vec::new();
2476
2477 if let Some(pending_node) = block
2478 .child_by_field_name("pending")
2479 .filter(|node| node.kind() == "await_pending")
2480 {
2481 pending_nodes.extend(parse_await_children_field(pending_node));
2482 }
2483
2484 let body_start = body_start_index(block, &children, &["expression", "binding", "pending"]);
2485 for node in &children[body_start..first_branch_idx] {
2486 if node.kind() == "await_pending" {
2487 continue;
2488 }
2489 let mut recovered = parse_modern_nodes_slice(source, std::slice::from_ref(node), false);
2490 if recovered.is_empty()
2491 && node.kind() == "ERROR"
2492 && let Some(text) = recover_await_error_pending_text(source, *node)
2493 {
2494 push_modern_text_node(&mut recovered, text);
2495 }
2496 pending_nodes.extend(recovered);
2497 }
2498
2499 (branch_indices.is_empty() || !pending_nodes.is_empty()).then_some(Fragment {
2500 r#type: FragmentType::Fragment,
2501 nodes: pending_nodes.into_boxed_slice(),
2502 })
2503 };
2504
2505 let inline_binding = inline_binding_field;
2506 let mut value = None;
2507 let mut error = None;
2508 let mut then_fragment = None;
2509 let mut catch_fragment = None;
2510
2511 match inline_kind {
2512 Some(BlockBranchKind::Then) => value = inline_binding,
2513 Some(BlockBranchKind::Catch) => error = inline_binding,
2514 _ => {}
2515 }
2516
2517 if let Some(inline_branch_kind) = inline_kind {
2518 let inline_nodes = find_first_named_child(block, "await_branch_children")
2519 .map(parse_await_children_field)
2520 .unwrap_or_default();
2521
2522 let fragment = Fragment {
2523 r#type: FragmentType::Fragment,
2524 nodes: inline_nodes.into_boxed_slice(),
2525 };
2526
2527 match inline_branch_kind {
2528 BlockBranchKind::Then => then_fragment = Some(fragment),
2529 BlockBranchKind::Catch => catch_fragment = Some(fragment),
2530 _ => {}
2531 }
2532 }
2533
2534 for branch_child_idx in branch_indices.iter().copied() {
2535 let branch_node = *children.get(branch_child_idx)?;
2536
2537 let kind = find_first_named_child(branch_node, "branch_kind")
2538 .and_then(|n| n.utf8_text(source.as_bytes()).ok())
2539 .and_then(|s| BlockBranchKind::parse_await_shorthand(s.trim()));
2540 let Some(kind) = kind else {
2541 continue;
2542 };
2543
2544 let binding_expr = branch_node
2545 .child_by_field_name("binding")
2546 .and_then(|node| parse_modern_binding_field(source, node, true));
2547
2548 let fragment_nodes = find_first_named_child(branch_node, "await_branch_children")
2549 .map(parse_await_children_field)
2550 .unwrap_or_default();
2551 let fragment = Fragment {
2552 r#type: FragmentType::Fragment,
2553 nodes: fragment_nodes.into_boxed_slice(),
2554 };
2555
2556 match kind {
2557 BlockBranchKind::Then => {
2558 if value.is_none() {
2559 value = binding_expr;
2560 }
2561 then_fragment = Some(fragment);
2562 }
2563 BlockBranchKind::Catch => {
2564 if error.is_none() {
2565 error = binding_expr;
2566 }
2567 catch_fragment = Some(fragment);
2568 }
2569 _ => {}
2570 }
2571 }
2572
2573 Some(AwaitBlock {
2574 start: block.start_byte(),
2575 end: block.end_byte(),
2576 expression,
2577 value,
2578 error,
2579 pending,
2580 then: then_fragment,
2581 catch: catch_fragment,
2582 })
2583}
2584
2585fn recover_await_error_pending_text(source: &str, error_node: TsNode<'_>) -> Option<Text> {
2586 let start = error_node.start_byte();
2587 let end = error_node.end_byte();
2588 if start >= end || end > source.len() {
2589 return None;
2590 }
2591
2592 let raw = &source[start..end];
2593 let close = raw.find('}')?;
2594 let tail = raw
2595 .get((close + 1)..)?
2596 .trim_start_matches(char::is_whitespace);
2597 if tail.is_empty() {
2598 return None;
2599 }
2600
2601 let tail_start = start + close + 1 + (raw[(close + 1)..].len() - tail.len());
2602 Some(Text {
2603 start: tail_start,
2604 end,
2605 raw: Arc::from(tail),
2606 data: Arc::from(decode_html_entities_cow(tail).into_owned()),
2607 })
2608}
2609
2610pub fn find_matching_brace_close(source: &str, open_index: usize, limit: usize) -> Option<usize> {
2611 let bytes = source.as_bytes();
2612 let mut index = open_index;
2613 let mut depth = 0usize;
2614 let mut in_single = false;
2615 let mut in_double = false;
2616 let mut in_template = false;
2617 let mut in_line_comment = false;
2618 let mut in_block_comment = false;
2619 let mut escaped = false;
2620
2621 while index < limit {
2622 let byte = *bytes.get(index)?;
2623 let ch = byte as char;
2624 let next = bytes.get(index + 1).copied().unwrap_or_default() as char;
2625
2626 if in_line_comment {
2627 if ch == '\n' || ch == '\r' {
2628 in_line_comment = false;
2629 }
2630 index += 1;
2631 continue;
2632 }
2633
2634 if in_block_comment {
2635 if ch == '*' && next == '/' {
2636 in_block_comment = false;
2637 index += 2;
2638 continue;
2639 }
2640 index += 1;
2641 continue;
2642 }
2643
2644 if escaped {
2645 escaped = false;
2646 index += 1;
2647 continue;
2648 }
2649
2650 if in_single {
2651 if ch == '\\' {
2652 escaped = true;
2653 } else if ch == '\'' {
2654 in_single = false;
2655 }
2656 index += 1;
2657 continue;
2658 }
2659
2660 if in_double {
2661 if ch == '\\' {
2662 escaped = true;
2663 } else if ch == '"' {
2664 in_double = false;
2665 }
2666 index += 1;
2667 continue;
2668 }
2669
2670 if in_template {
2671 if ch == '\\' {
2672 escaped = true;
2673 } else if ch == '`' {
2674 in_template = false;
2675 }
2676 index += 1;
2677 continue;
2678 }
2679
2680 if ch == '/' && next == '/' {
2681 in_line_comment = true;
2682 index += 2;
2683 continue;
2684 }
2685
2686 if ch == '/' && next == '*' {
2687 in_block_comment = true;
2688 index += 2;
2689 continue;
2690 }
2691
2692 match ch {
2693 '\'' => in_single = true,
2694 '"' => in_double = true,
2695 '`' => in_template = true,
2696 '{' => depth += 1,
2697 '}' => {
2698 depth = depth.saturating_sub(1);
2699 if depth == 0 {
2700 return Some(index);
2701 }
2702 }
2703 _ => {}
2704 }
2705
2706 index += 1;
2707 }
2708
2709 None
2710}
2711
2712pub(crate) fn parse_modern_expression_field(source: &str, node: TsNode<'_>) -> Option<Expression> {
2713 let raw = node.utf8_text(source.as_bytes()).ok()?;
2714 let trimmed = raw.trim();
2715 if trimmed.is_empty() {
2716 return None;
2717 }
2718
2719 let leading = raw.find(trimmed).unwrap_or(0);
2720 let abs = node.start_byte() + leading;
2721 let (line, column) = line_column_at_offset(source, abs);
2722 parse_modern_expression_from_text(trimmed, abs, line, column)
2723}
2724
2725fn parse_modern_expression_field_or_empty(source: &str, node: TsNode<'_>) -> Expression {
2726 parse_modern_expression_field(source, node)
2727 .unwrap_or_else(|| modern_empty_identifier_expression_for_field(source, node))
2728}
2729
2730fn modern_empty_identifier_expression_for_field(source: &str, node: TsNode<'_>) -> Expression {
2731 let raw = node.utf8_text(source.as_bytes()).ok().unwrap_or_default();
2732 let trimmed = raw.trim();
2733 if trimmed.is_empty() {
2734 let pos = if node.start_byte() == node.end_byte() {
2737 node.start_byte()
2738 } else {
2739 node.end_byte().saturating_sub(1)
2740 };
2741 return modern_empty_identifier_expression_span(pos, 0);
2742 }
2743
2744 let leading = raw.find(trimmed).unwrap_or(0);
2745 let start = node.start_byte() + leading;
2746 modern_empty_identifier_expression_span(start, trimmed.len())
2747}
2748
2749fn modern_empty_identifier_at_block_tag_end(node: TsNode<'_>) -> Expression {
2750 modern_empty_identifier_expression_span(node.end_byte().saturating_sub(1), 0)
2751}
2752
2753fn parse_modern_binding_field(
2754 source: &str,
2755 node: TsNode<'_>,
2756 with_character: bool,
2757) -> Option<Expression> {
2758 parse_modern_binding_field_with_error(source, node, with_character).0
2759}
2760
2761fn parse_modern_binding_field_with_error(
2762 source: &str,
2763 node: TsNode<'_>,
2764 with_character: bool,
2765) -> (Option<Expression>, Option<ParseError>) {
2766 let Ok(raw) = node.utf8_text(source.as_bytes()) else {
2767 return (None, None);
2768 };
2769 let trimmed = raw.trim();
2770 if trimmed.is_empty() {
2771 return (None, None);
2772 }
2773
2774 let leading = raw.find(trimmed).unwrap_or(0);
2775 let abs = node.start_byte() + leading;
2776 let (line, column) = line_column_at_offset(source, abs);
2777
2778 if let Some(word) = reserved_binding_word(trimmed) {
2779 return (
2780 None,
2781 Some(ParseError {
2782 kind: ParseErrorKind::UnexpectedReservedWord {
2783 word: Arc::from(word),
2784 },
2785 start: abs,
2786 end: abs,
2787 }),
2788 );
2789 }
2790
2791 if let Some(comma_pos) = find_rest_comma_in_text(trimmed) {
2793 return (
2794 None,
2795 Some(ParseError {
2796 kind: ParseErrorKind::JsParseError {
2797 message: Arc::from("Comma is not permitted after the rest element"),
2798 },
2799 start: abs + comma_pos,
2800 end: abs + comma_pos,
2801 }),
2802 );
2803 }
2804
2805 if let Some(mut expression) = parse_pattern_with_oxc(trimmed, abs, line, column) {
2806 if with_character {
2807 set_expression_character(source, &mut expression);
2808 }
2809 return (Some(expression), None);
2810 }
2811
2812 if let Some((start, message)) = reserved_binding_pattern_error(trimmed, abs) {
2813 return (
2814 None,
2815 Some(ParseError {
2816 kind: ParseErrorKind::JsParseError { message },
2817 start,
2818 end: start,
2819 }),
2820 );
2821 }
2822
2823 if let Some(expression) = parse_modern_expression_from_text(trimmed, abs, line, column)
2824 && let Some((start, message)) = invalid_binding_expression_error(&expression)
2825 {
2826 return (
2827 None,
2828 Some(ParseError {
2829 kind: ParseErrorKind::JsParseError { message },
2830 start,
2831 end: start,
2832 }),
2833 );
2834 }
2835
2836 let error =
2837 parse_pattern_error_from_text(trimmed, abs, line, column).map(|(start, message)| {
2838 ParseError {
2839 kind: ParseErrorKind::JsParseError { message },
2840 start,
2841 end: start,
2842 }
2843 });
2844 (None, error)
2845}
2846
2847fn is_js_reserved_word(text: &str) -> bool {
2848 matches!(
2849 text,
2850 "await"
2851 | "break"
2852 | "case"
2853 | "catch"
2854 | "class"
2855 | "const"
2856 | "continue"
2857 | "debugger"
2858 | "default"
2859 | "delete"
2860 | "do"
2861 | "else"
2862 | "enum"
2863 | "export"
2864 | "extends"
2865 | "false"
2866 | "finally"
2867 | "for"
2868 | "function"
2869 | "if"
2870 | "import"
2871 | "in"
2872 | "instanceof"
2873 | "new"
2874 | "null"
2875 | "return"
2876 | "super"
2877 | "switch"
2878 | "this"
2879 | "throw"
2880 | "true"
2881 | "try"
2882 | "typeof"
2883 | "var"
2884 | "void"
2885 | "while"
2886 | "with"
2887 | "yield"
2888 )
2889}
2890
2891fn reserved_binding_word(text: &str) -> Option<&str> {
2892 let word = leading_identifier_word(text)?;
2893 let tail = &text[word.len()..];
2894 let tail = tail.trim_matches(|ch: char| ch.is_whitespace() || ch == '}');
2895 (is_js_reserved_word(word) && tail.is_empty()).then_some(word)
2896}
2897
2898fn reserved_binding_pattern_error(text: &str, start: usize) -> Option<(usize, Arc<str>)> {
2899 let trimmed = text.trim();
2900 if trimmed.starts_with('[') {
2901 return reserved_array_binding_error(trimmed, start);
2902 }
2903 if trimmed.starts_with('{') {
2904 return reserved_object_binding_error(trimmed, start);
2905 }
2906 None
2907}
2908
2909fn reserved_array_binding_error(text: &str, start: usize) -> Option<(usize, Arc<str>)> {
2910 let close = text.rfind(']')?;
2911 let inner = &text[1..close];
2912 let leading = inner.find(|ch: char| !ch.is_whitespace())?;
2913 let word = leading_identifier_word(&inner[leading..])?;
2914 is_js_reserved_word(word).then_some((start + 1 + leading, Arc::from("Unexpected token")))
2915}
2916
2917fn reserved_object_binding_error(text: &str, start: usize) -> Option<(usize, Arc<str>)> {
2918 let close = text.rfind('}')?;
2919 let inner = &text[1..close];
2920 let leading = inner.find(|ch: char| !ch.is_whitespace())?;
2921 let rest = &inner[leading..];
2922 let word = leading_identifier_word(rest)?;
2923 if !is_js_reserved_word(word) {
2924 return None;
2925 }
2926 let tail = rest[word.len()..].trim_start();
2927 (tail.is_empty() || matches!(tail.chars().next(), Some(','))).then_some((
2928 start + 1 + leading,
2929 Arc::from(format!("Unexpected keyword '{word}'")),
2930 ))
2931}
2932
2933fn leading_identifier_word(text: &str) -> Option<&str> {
2934 let mut end = 0usize;
2935 for (idx, ch) in text.char_indices() {
2936 let ok = if idx == 0 {
2937 ch == '_' || ch == '$' || ch.is_ascii_alphabetic()
2938 } else {
2939 ch == '_' || ch == '$' || ch.is_ascii_alphanumeric()
2940 };
2941 if !ok {
2942 break;
2943 }
2944 end = idx + ch.len_utf8();
2945 }
2946 (end > 0).then_some(&text[..end])
2947}
2948
2949fn invalid_binding_expression_error(expression: &Expression) -> Option<(usize, Arc<str>)> {
2950 crate::parse::oxc_query::invalid_binding_expression_error(expression)
2951}
2952
2953fn parse_pattern_error_from_text(
2954 text: &str,
2955 start_byte: usize,
2956 line: usize,
2957 column: usize,
2958) -> Option<(usize, Arc<str>)> {
2959 let wrapped = format!("({text})=>{{}}");
2960 let base_column = column.saturating_sub(1);
2961 crate::parse::parse_modern_expression_error_detail_with_oxc(
2962 &wrapped,
2963 start_byte.saturating_sub(1),
2964 line,
2965 base_column,
2966 )
2967}
2968
2969fn parse_modern_snippet_block(source: &str, block: TsNode<'_>) -> Option<SnippetBlock> {
2970 let children = named_children_vec(block);
2971 let end_idx = children
2972 .iter()
2973 .rposition(|c| c.kind() == "block_end")
2974 .unwrap_or(children.len());
2975
2976 let name_node = block
2977 .child_by_field_name("name")
2978 .or_else(|| block.child_by_field_name("expression"));
2979 let type_params_node = block.child_by_field_name("type_parameters");
2980 let params_node = block.child_by_field_name("parameters");
2981 let expression = parse_snippet_name(source, block, name_node);
2982 let type_params = parse_snippet_type_params(source, type_params_node);
2983 let parameters = parse_snippet_params(source, params_node);
2984 let body_start = body_start_index(block, &children, &["name", "type_parameters", "parameters"]);
2985 let body_nodes = parse_modern_nodes_slice(source, &children[body_start..end_idx], false);
2986
2987 let has_lparen = (0..block.child_count())
2991 .filter_map(|i| block.child(i as u32))
2992 .any(|c| !c.is_named() && c.kind() == "(");
2993 let header_error = if block.has_error() && params_node.is_some() && has_lparen {
2994 let has_rparen = (0..block.child_count())
2995 .filter_map(|i| block.child(i as u32))
2996 .any(|c| !c.is_named() && c.kind() == ")");
2997 if has_rparen {
2998 let error_pos =
3000 snippet_header_error_pos(block, name_node, type_params_node, params_node);
3001 Some(SnippetHeaderError {
3002 kind: SnippetHeaderErrorKind::ExpectedRightBrace,
3003 start: error_pos,
3004 end: error_pos,
3005 })
3006 } else {
3007 let error_pos = children
3010 .get(end_idx)
3011 .map(|n| n.end_byte())
3012 .unwrap_or(block.end_byte().saturating_sub(1));
3013 Some(SnippetHeaderError {
3014 kind: SnippetHeaderErrorKind::ExpectedRightParen,
3015 start: error_pos,
3016 end: error_pos,
3017 })
3018 }
3019 } else if block.has_error() && params_node.is_none() {
3020 let missing_right_brace = name_node
3021 .and_then(|name| source.get(name.end_byte()..block.end_byte()))
3022 .is_some_and(|tail| {
3023 tail.find('(')
3024 .zip(tail.find(')'))
3025 .is_some_and(|(left, right)| left < right)
3026 });
3027 missing_right_brace.then_some(SnippetHeaderError {
3028 kind: SnippetHeaderErrorKind::ExpectedRightBrace,
3029 start: block.start_byte(),
3030 end: block.start_byte(),
3031 })
3032 } else {
3033 None
3034 };
3035
3036 Some(SnippetBlock {
3037 start: block.start_byte(),
3038 end: block.end_byte(),
3039 expression,
3040 type_params,
3041 parameters: parameters.into_boxed_slice(),
3042 body: Fragment {
3043 r#type: FragmentType::Fragment,
3044 nodes: body_nodes.into_boxed_slice(),
3045 },
3046 header_error,
3047 })
3048}
3049
3050fn recover_malformed_snippet_block(source: &str, error_node: TsNode<'_>) -> Option<SnippetBlock> {
3051 let raw = source
3055 .get(error_node.start_byte()..error_node.end_byte())
3056 .unwrap_or_default();
3057 if raw.starts_with("{:") {
3058 return None;
3059 }
3060 recover_snippet_block_missing_right_brace(source, error_node)
3061 .or_else(|| recover_snippet_block_missing_right_paren(source, error_node))
3062}
3063
3064fn recover_snippet_block_missing_right_brace(
3065 source: &str,
3066 error_node: TsNode<'_>,
3067) -> Option<SnippetBlock> {
3068 let start_node = find_snippet_start(error_node, source)?;
3069 let name_node = start_node
3070 .child_by_field_name("name")
3071 .or_else(|| start_node.child_by_field_name("expression"))
3072 .or_else(|| find_named_descendant(start_node, "snippet_name"));
3073 let type_params_node = start_node
3074 .child_by_field_name("type_parameters")
3075 .or_else(|| find_named_descendant(start_node, "snippet_type_parameters"));
3076 let params_node = start_node
3077 .child_by_field_name("parameters")
3078 .or_else(|| find_named_descendant(start_node, "snippet_parameters"));
3079 let has_lparen = (0..start_node.child_count())
3080 .filter_map(|i| start_node.child(i as u32))
3081 .any(|c| !c.is_named() && c.kind() == "(");
3082
3083 let header_error = if params_node.is_some() && has_lparen {
3084 let error_pos =
3085 snippet_header_error_pos(start_node, name_node, type_params_node, params_node);
3086 Some(SnippetHeaderError {
3087 kind: SnippetHeaderErrorKind::ExpectedRightBrace,
3088 start: error_pos,
3089 end: error_pos,
3090 })
3091 } else {
3092 None
3093 };
3094
3095 Some(SnippetBlock {
3096 start: error_node.start_byte(),
3097 end: error_node.end_byte(),
3098 expression: parse_snippet_name(source, start_node, name_node),
3099 type_params: parse_snippet_type_params(source, type_params_node),
3100 parameters: parse_snippet_params(source, params_node).into_boxed_slice(),
3101 body: snippet_recovery_body_fragment(source, error_node),
3102 header_error,
3103 })
3104}
3105
3106fn recover_snippet_block_missing_right_paren(
3107 source: &str,
3108 error_node: TsNode<'_>,
3109) -> Option<SnippetBlock> {
3110 find_named_descendant(error_node, "snippet_name")?;
3112
3113 let name_node = find_named_descendant(error_node, "snippet_name");
3114 let type_params_node = find_named_descendant(error_node, "snippet_type_parameters");
3115 let params_node = find_named_descendant(error_node, "snippet_parameters");
3116 let error_pos = error_node.end_byte().saturating_sub(1);
3117
3118 Some(SnippetBlock {
3119 start: error_node.start_byte(),
3120 end: error_node.end_byte(),
3121 expression: parse_snippet_name(source, error_node, name_node),
3122 type_params: parse_snippet_type_params(source, type_params_node),
3123 parameters: parse_snippet_params(source, params_node).into_boxed_slice(),
3124 body: empty_fragment(),
3125 header_error: Some(SnippetHeaderError {
3126 kind: SnippetHeaderErrorKind::ExpectedRightParen,
3127 start: error_pos,
3128 end: error_pos,
3129 }),
3130 })
3131}
3132
3133pub(crate) fn parse_snippet_type_params(
3134 source: &str,
3135 node: Option<TsNode<'_>>,
3136) -> Option<Arc<str>> {
3137 let raw = node?.utf8_text(source.as_bytes()).ok()?.trim();
3138 let inner = raw
3139 .strip_prefix('<')
3140 .and_then(|tail| tail.strip_suffix('>'))
3141 .unwrap_or(raw)
3142 .trim();
3143 (!inner.is_empty()).then(|| Arc::from(inner))
3144}
3145
3146pub(crate) fn parse_snippet_params(
3147 source: &str,
3148 params_node: Option<TsNode<'_>>,
3149) -> Vec<Expression> {
3150 let Some(params_node) = params_node else {
3151 return Vec::new();
3152 };
3153 let raw = text_for_node(source, params_node);
3154 let Some(parsed) = crate::js::ParsedJsParameters::parse(raw.as_ref()).ok().map(Arc::new) else {
3155 return named_children_vec(params_node)
3156 .into_iter()
3157 .filter(|node| node.kind() == "pattern")
3158 .filter_map(|node| parse_modern_binding_field(source, node, false))
3159 .collect::<Vec<_>>();
3160 };
3161
3162 let mut parameters = parsed
3163 .parameters()
3164 .items
3165 .iter()
3166 .enumerate()
3167 .map(|(index, parameter)| {
3168 let span = parameter.span();
3169 Expression::from_parameter_item(
3170 parsed.clone(),
3171 index,
3172 params_node.start_byte() + span.start as usize - 1,
3173 params_node.start_byte() + span.end as usize - 1,
3174 )
3175 })
3176 .collect::<Vec<_>>();
3177
3178 if let Some(rest) = parsed.rest_parameter() {
3179 let span = rest.span();
3180 parameters.push(Expression::from_rest_parameter(
3181 parsed,
3182 params_node.start_byte() + span.start as usize - 1,
3183 params_node.start_byte() + span.end as usize - 1,
3184 ));
3185 }
3186
3187 parameters
3188}
3189
3190pub(crate) fn parse_snippet_name(
3191 source: &str,
3192 owner: TsNode<'_>,
3193 name_node: Option<TsNode<'_>>,
3194) -> Expression {
3195 let mut expression = if let Some(name_node) = name_node {
3196 let raw = name_node
3197 .utf8_text(source.as_bytes())
3198 .ok()
3199 .unwrap_or_default();
3200 let trimmed = raw.trim();
3201 if trimmed.is_empty() {
3202 let start = if name_node.start_byte() == name_node.end_byte() {
3204 name_node.start_byte()
3205 } else {
3206 name_node.end_byte().saturating_sub(1)
3207 };
3208 let (line, column) = line_column_at_offset(source, start);
3209 modern_identifier_expression_with_loc(Arc::from(""), start, start, line, column)
3210 } else {
3211 parse_modern_expression_field_or_empty(source, name_node)
3212 }
3213 } else {
3214 let start = named_children_vec(owner)
3216 .first()
3217 .map(|n| n.start_byte().saturating_sub(1))
3218 .unwrap_or_else(|| owner.end_byte().saturating_sub(1));
3219 let (line, column) = line_column_at_offset(source, start);
3220 modern_identifier_expression_with_loc(Arc::from(""), start, start, line, column)
3221 };
3222 set_expression_character(source, &mut expression);
3223 expression
3224}
3225
3226fn empty_fragment() -> Fragment {
3227 Fragment {
3228 r#type: FragmentType::Fragment,
3229 nodes: Box::new([]),
3230 }
3231}
3232
3233fn snippet_recovery_body_fragment(source: &str, error_node: TsNode<'_>) -> Fragment {
3234 let Some(raw) = source.get(error_node.start_byte()..error_node.end_byte()) else {
3235 return empty_fragment();
3236 };
3237 let Some(header_close) = raw.find('}') else {
3238 return empty_fragment();
3239 };
3240 let Some(block_end_start) = raw.rfind("{/snippet}") else {
3241 return empty_fragment();
3242 };
3243 if header_close + 1 >= block_end_start {
3244 return empty_fragment();
3245 }
3246
3247 let body_start = error_node.start_byte() + header_close + 1;
3248 let body_end = error_node.start_byte() + block_end_start;
3249 let Some(body_raw) = source.get(body_start..body_end) else {
3250 return empty_fragment();
3251 };
3252 if body_raw.is_empty() {
3253 return empty_fragment();
3254 }
3255
3256 Fragment {
3257 r#type: FragmentType::Fragment,
3258 nodes: Box::new([Node::Text(Text {
3259 start: body_start,
3260 end: body_end,
3261 raw: Arc::from(body_raw),
3262 data: Arc::from(body_raw),
3263 })]),
3264 }
3265}
3266
3267fn snippet_header_error_pos(
3268 start_node: TsNode<'_>,
3269 name_node: Option<TsNode<'_>>,
3270 type_params_node: Option<TsNode<'_>>,
3271 params_node: Option<TsNode<'_>>,
3272) -> usize {
3273 if let Some(error) = find_direct_named_child(start_node, "ERROR")
3274 .or_else(|| find_named_descendant(start_node, "ERROR"))
3275 {
3276 return error.start_byte();
3277 }
3278
3279 params_node
3280 .or(type_params_node)
3281 .or(name_node)
3282 .map(|node| node.end_byte())
3283 .unwrap_or_else(|| start_node.end_byte().saturating_sub(1))
3284}
3285
3286fn find_snippet_start<'tree>(node: TsNode<'tree>, _source: &str) -> Option<TsNode<'tree>> {
3287 if node.kind() == "snippet_block" {
3288 return Some(node);
3289 }
3290 if find_direct_named_child(node, "snippet_name").is_some() {
3292 return Some(node);
3293 }
3294
3295 for index in 0..node.child_count() {
3296 let Some(child) = node.child(index as u32) else {
3297 continue;
3298 };
3299 if let Some(found) = find_snippet_start(child, _source) {
3300 return Some(found);
3301 }
3302 }
3303 None
3304}
3305
3306fn find_named_descendant<'tree>(node: TsNode<'tree>, kind: &str) -> Option<TsNode<'tree>> {
3307 if node.kind() == kind {
3308 return Some(node);
3309 }
3310
3311 for index in 0..node.child_count() {
3312 let Some(child) = node.child(index as u32) else {
3313 continue;
3314 };
3315 if let Some(found) = find_named_descendant(child, kind) {
3316 return Some(found);
3317 }
3318 }
3319 None
3320}
3321
3322pub(crate) fn split_top_level_commas(text: &str) -> Vec<(&str, usize)> {
3323 let mut segments = Vec::new();
3324 let mut start = 0usize;
3325 let mut depth_paren = 0usize;
3326 let mut depth_brace = 0usize;
3327 let mut depth_bracket = 0usize;
3328 let bytes = text.as_bytes();
3329
3330 for (idx, byte) in bytes.iter().enumerate() {
3331 match *byte {
3332 b'(' => depth_paren += 1,
3333 b')' => depth_paren = depth_paren.saturating_sub(1),
3334 b'{' => depth_brace += 1,
3335 b'}' => depth_brace = depth_brace.saturating_sub(1),
3336 b'[' => depth_bracket += 1,
3337 b']' => depth_bracket = depth_bracket.saturating_sub(1),
3338 b',' if depth_paren == 0 && depth_brace == 0 && depth_bracket == 0 => {
3339 segments.push((&text[start..idx], start));
3340 start = idx + 1;
3341 }
3342 _ => {}
3343 }
3344 }
3345
3346 if start <= text.len() {
3347 segments.push((&text[start..], start));
3348 }
3349
3350 segments
3351}
3352
3353pub(crate) fn parse_pattern_with_oxc(
3354 text: &str,
3355 abs_start: usize,
3356 line: usize,
3357 column: usize,
3358) -> Option<Expression> {
3359 let trimmed = text.trim();
3360 if trimmed.is_empty() {
3361 return None;
3362 }
3363
3364 let leading_ws = text.find(trimmed).unwrap_or(0);
3365 let start = abs_start + leading_ws;
3366 let parsed = Arc::new(crate::js::ParsedJsPattern::parse(trimmed).ok()?);
3367 let end = start + trimmed.len();
3368 let mut expression = Expression::from_pattern(parsed, start, end);
3369 expression.syntax.parens = leading_parens(trimmed, start, expression.start);
3370 let _ = (line, column);
3371 Some(expression)
3372}
3373
3374fn find_rest_comma_in_text(text: &str) -> Option<usize> {
3378 let bytes = text.as_bytes();
3379 let mut i = 0;
3380 let mut brace_depth: i32 = 0;
3381 let mut bracket_depth: i32 = 0;
3382
3383 while i < bytes.len() {
3384 match bytes[i] {
3385 b'{' => brace_depth += 1,
3386 b'}' => brace_depth -= 1,
3387 b'[' => bracket_depth += 1,
3388 b']' => bracket_depth -= 1,
3389 b'.' if i + 2 < bytes.len() && bytes[i + 1] == b'.' && bytes[i + 2] == b'.' => {
3390 let rest_start = i;
3392 i += 3;
3393 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
3395 i += 1;
3396 }
3397 let id_start = i;
3399 while i < bytes.len()
3400 && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_' || bytes[i] == b'$')
3401 {
3402 i += 1;
3403 }
3404 if i > id_start {
3405 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
3407 i += 1;
3408 }
3409 if i < bytes.len() && bytes[i] == b',' {
3410 if brace_depth > 0 || bracket_depth > 0 {
3412 return Some(i);
3413 }
3414 }
3415 }
3416 let _ = rest_start;
3417 continue;
3418 }
3419 _ => {}
3420 }
3421 i += 1;
3422 }
3423 None
3424}
3425
3426pub fn line_column_at_offset(source: &str, offset: usize) -> (usize, usize) {
3427 SourceText::new(SourceId::new(0), source, None).line_column_at_offset(offset)
3428}
3429
3430fn source_location_at_offset(source: &str, offset: usize) -> SourceLocation {
3431 SourceText::new(SourceId::new(0), source, None).location_at_offset(offset)
3432}
3433
3434fn set_expression_character(_source: &str, _expression: &mut Expression) {}
3435
3436fn parse_modern_element_node(
3437 source: &str,
3438 node: TsNode<'_>,
3439 in_shadowroot_template: bool,
3440 in_svelte_head: bool,
3441 loose: bool,
3442 hint: Option<&IncrementalHint<'_>>,
3443) -> Node {
3444 let mut tag_cursor = node.walk();
3445 let mut start_tag: Option<TsNode<'_>> = None;
3446 let mut end_tag: Option<TsNode<'_>> = None;
3447 let mut self_closing_tag: Option<TsNode<'_>> = None;
3448 let mut trailing_text: Option<TsNode<'_>> = None;
3449 for child in node.named_children(&mut tag_cursor) {
3450 match child.kind() {
3451 "start_tag" => start_tag = Some(child),
3452 "end_tag" => end_tag = Some(child),
3453 "self_closing_tag" => self_closing_tag = Some(child),
3454 "text" if trailing_text.is_none() => {
3455 trailing_text = Some(child);
3456 }
3457 _ => {}
3458 }
3459 }
3460
3461 if let (Some(start_tag_node), Some(text_node)) = (start_tag, trailing_text)
3462 && text_node.start_byte() != start_tag_node.end_byte()
3463 {
3464 trailing_text = None;
3465 }
3466
3467 if let Some(start_tag) = start_tag
3468 && end_tag.is_none()
3469 && self_closing_tag.is_none()
3470 && !text_for_node(source, start_tag).trim_end().ends_with('>')
3471 {
3472 return parse_modern_loose_start_tag_node(source, start_tag, trailing_text);
3473 }
3474
3475 let element =
3476 parse_modern_regular_element(source, node, in_shadowroot_template, in_svelte_head, loose, hint);
3477 classify_modern_element(element, in_shadowroot_template, in_svelte_head)
3478}
3479
3480fn extract_this_expression(attributes: Box<[Attribute]>) -> (Option<Expression>, Box<[Attribute]>) {
3483 let mut this_expr = None;
3484 let mut remaining = Vec::with_capacity(attributes.len());
3485
3486 for attr in Vec::from(attributes) {
3487 if this_expr.is_none()
3488 && let Attribute::Attribute(ref named) = attr
3489 && classify_attribute_name(named.name.as_ref()) == AttributeKind::This
3490 && let AttributeValueList::ExpressionTag(ref tag) = named.value
3491 {
3492 this_expr = Some(tag.expression.clone());
3493 continue;
3494 }
3495 if this_expr.is_none()
3496 && let Attribute::Attribute(ref named) = attr
3497 && classify_attribute_name(named.name.as_ref()) == AttributeKind::This
3498 && let AttributeValueList::Values(ref values) = named.value
3499 && values.len() == 1
3500 && let AttributeValue::Text(text) = &values[0]
3501 {
3502 this_expr = Some(modern_string_literal_expression(
3503 text.data.clone(),
3504 text.start,
3505 text.end,
3506 ));
3507 continue;
3508 }
3509 remaining.push(attr);
3510 }
3511
3512 (this_expr, remaining.into_boxed_slice())
3513}
3514
3515fn modern_string_literal_expression(value: Arc<str>, start: usize, end: usize) -> Expression {
3516 let raw = format!("'{}'", value.replace('\\', "\\\\").replace('\'', "\\'"));
3517 match crate::js::ParsedJsExpression::parse(raw, oxc_span::SourceType::ts().with_module(true)) {
3518 Ok(parsed) => Expression::from_expression(Arc::new(parsed), start, end),
3519 Err(_) => Expression::empty(start, end),
3520 }
3521}
3522
3523fn classify_modern_element(
3525 element: RegularElement,
3526 in_shadowroot_template: bool,
3527 in_svelte_head: bool,
3528) -> Node {
3529 match classify_element_name(element.name.as_ref()) {
3530 ElementKind::Slot if !in_shadowroot_template => Node::SlotElement(SlotElement {
3531 start: element.start,
3532 end: element.end,
3533 name: element.name,
3534 name_loc: element.name_loc,
3535 attributes: element.attributes,
3536 fragment: element.fragment,
3537 }),
3538 ElementKind::Svelte(kind) => classify_svelte_element(element, kind),
3539 _ if element.name.as_ref() == "title" && in_svelte_head => {
3540 Node::TitleElement(crate::ast::modern::TitleElement {
3541 start: element.start,
3542 end: element.end,
3543 name: element.name,
3544 name_loc: element.name_loc,
3545 attributes: element.attributes,
3546 fragment: element.fragment,
3547 })
3548 }
3549 _ if is_component_name(element.name.as_ref()) => Node::Component(Component {
3550 start: element.start,
3551 end: element.end,
3552 name: element.name,
3553 name_loc: element.name_loc,
3554 attributes: element.attributes,
3555 fragment: element.fragment,
3556 }),
3557 _ => Node::RegularElement(element),
3558 }
3559}
3560
3561fn classify_svelte_element(element: RegularElement, kind: SvelteElementKind) -> Node {
3562 match kind {
3563 SvelteElementKind::Head => Node::SvelteHead(crate::ast::modern::SvelteHead {
3564 start: element.start,
3565 end: element.end,
3566 name: element.name,
3567 name_loc: element.name_loc,
3568 attributes: element.attributes,
3569 fragment: element.fragment,
3570 }),
3571 SvelteElementKind::Body => Node::SvelteBody(crate::ast::modern::SvelteBody {
3572 start: element.start,
3573 end: element.end,
3574 name: element.name,
3575 name_loc: element.name_loc,
3576 attributes: element.attributes,
3577 fragment: element.fragment,
3578 }),
3579 SvelteElementKind::Window => Node::SvelteWindow(crate::ast::modern::SvelteWindow {
3580 start: element.start,
3581 end: element.end,
3582 name: element.name,
3583 name_loc: element.name_loc,
3584 attributes: element.attributes,
3585 fragment: element.fragment,
3586 }),
3587 SvelteElementKind::Document => Node::SvelteDocument(crate::ast::modern::SvelteDocument {
3588 start: element.start,
3589 end: element.end,
3590 name: element.name,
3591 name_loc: element.name_loc,
3592 attributes: element.attributes,
3593 fragment: element.fragment,
3594 }),
3595 SvelteElementKind::Component => {
3596 let (expression, attributes) = extract_this_expression(element.attributes);
3597 Node::SvelteComponent(crate::ast::modern::SvelteComponent {
3598 start: element.start,
3599 end: element.end,
3600 name: element.name,
3601 name_loc: element.name_loc,
3602 attributes,
3603 fragment: element.fragment,
3604 expression,
3605 })
3606 }
3607 SvelteElementKind::Element => {
3608 let (expression, attributes) = extract_this_expression(element.attributes);
3609 Node::SvelteElement(crate::ast::modern::SvelteElement {
3610 start: element.start,
3611 end: element.end,
3612 name: element.name,
3613 name_loc: element.name_loc,
3614 attributes,
3615 fragment: element.fragment,
3616 expression,
3617 })
3618 }
3619 SvelteElementKind::SelfTag => Node::SvelteSelf(crate::ast::modern::SvelteSelf {
3620 start: element.start,
3621 end: element.end,
3622 name: element.name,
3623 name_loc: element.name_loc,
3624 attributes: element.attributes,
3625 fragment: element.fragment,
3626 }),
3627 SvelteElementKind::Fragment => Node::SvelteFragment(crate::ast::modern::SvelteFragment {
3628 start: element.start,
3629 end: element.end,
3630 name: element.name,
3631 name_loc: element.name_loc,
3632 attributes: element.attributes,
3633 fragment: element.fragment,
3634 }),
3635 SvelteElementKind::Boundary => Node::SvelteBoundary(crate::ast::modern::SvelteBoundary {
3636 start: element.start,
3637 end: element.end,
3638 name: element.name,
3639 name_loc: element.name_loc,
3640 attributes: element.attributes,
3641 fragment: element.fragment,
3642 }),
3643 SvelteElementKind::Options | SvelteElementKind::Unknown => Node::RegularElement(element),
3645 }
3646}
3647
3648fn parse_modern_regular_element(
3649 source: &str,
3650 node: TsNode<'_>,
3651 in_shadowroot_template: bool,
3652 in_svelte_head: bool,
3653 loose: bool,
3654 hint: Option<&IncrementalHint<'_>>,
3655) -> RegularElement {
3656 let mut cursor = node.walk();
3657 let mut start_tag: Option<TsNode<'_>> = None;
3658 let mut end_tag: Option<TsNode<'_>> = None;
3659 let mut self_closing_tag: Option<TsNode<'_>> = None;
3660
3661 for child in node.named_children(&mut cursor) {
3662 match child.kind() {
3663 "start_tag" => start_tag = Some(child),
3664 "end_tag" => end_tag = Some(child),
3665 "self_closing_tag" => self_closing_tag = Some(child),
3666 _ => {}
3667 }
3668 }
3669
3670 let tag_node = start_tag.or(self_closing_tag);
3671 let tag_name = tag_node.and_then(|tag| find_first_named_child(tag, "tag_name"));
3672
3673 let name = tag_name
3674 .map(|tag_name| text_for_node(source, tag_name))
3675 .unwrap_or_else(|| Arc::from(""));
3676
3677 let name_loc = if let Some(tag_name) = tag_name {
3678 LegacyNameLocation {
3679 start: source_location_from_point(
3680 source,
3681 tag_name.start_position(),
3682 tag_name.start_byte(),
3683 ),
3684 end: source_location_from_point(source, tag_name.end_position(), tag_name.end_byte()),
3685 }
3686 } else {
3687 LegacyNameLocation {
3688 start: source_location_from_point(source, node.start_position(), node.start_byte()),
3689 end: source_location_from_point(source, node.start_position(), node.start_byte()),
3690 }
3691 };
3692
3693 let attributes = tag_node
3694 .map(|tag| parse_modern_attributes(source, tag, loose))
3695 .unwrap_or_default();
3696
3697 let is_shadowroot_template =
3698 matches!(classify_element_name(name.as_ref()), ElementKind::Template)
3699 && attributes.iter().any(|attr| {
3700 matches!(
3701 attr,
3702 Attribute::Attribute(NamedAttribute { name, .. })
3703 if name.as_ref() == "shadowrootmode"
3704 )
3705 });
3706
3707 let children_in_svelte_head = in_svelte_head
3708 || matches!(
3709 classify_element_name(name.as_ref()),
3710 ElementKind::Svelte(SvelteElementKind::Head)
3711 );
3712
3713 let mut fragment_nodes = Vec::new();
3714 let malformed_unclosed_start_tag = start_tag
3715 .map(|tag| {
3716 end_tag.is_none()
3717 && self_closing_tag.is_none()
3718 && !text_for_node(source, tag).trim_end().ends_with('>')
3719 })
3720 .unwrap_or(false);
3721 let mut old_node_cursor = 0usize;
3722 let mut inner_cursor = node.walk();
3723 for child in node.named_children(&mut inner_cursor) {
3724 if malformed_unclosed_start_tag
3725 && let Some(tag) = start_tag
3726 && child.start_byte() >= tag.end_byte()
3727 && child.kind() != "start_tag"
3728 {
3729 continue;
3730 }
3731
3732 if start_tag.is_some_and(|tag| tag.has_error())
3733 && end_tag.is_none()
3734 && self_closing_tag.is_none()
3735 && child.kind() == "text"
3736 && source
3737 .get(child.start_byte()..child.end_byte())
3738 .is_some_and(|raw| raw.contains("/>"))
3739 {
3740 continue;
3741 }
3742
3743 let child_start = child.start_byte();
3744 let child_end = child.end_byte();
3745
3746 if let Some(ref hint) = hint {
3748 if !any_range_overlaps(hint.changed_ranges, child_start, child_end) {
3749 if let Some(reused) = try_reuse_node(
3750 hint.old_source,
3751 source,
3752 hint.old_nodes,
3753 &mut old_node_cursor,
3754 child_start,
3755 child_end,
3756 ) {
3757 fragment_nodes.push(reused);
3758 continue;
3759 }
3760 }
3761 }
3762
3763 match child.kind() {
3764 "start_tag" if Some(child) != start_tag && Some(child) != self_closing_tag => {
3765 fragment_nodes.push(parse_modern_loose_start_tag_node(source, child, None));
3766 }
3767 "end_tag" | "self_closing_tag" => {}
3768 "text" | "entity" | "raw_text" => {
3769 push_modern_text_node(&mut fragment_nodes, parse_modern_text(source, child));
3770 }
3771 "comment" => fragment_nodes.push(Node::Comment(parse_modern_comment(source, child))),
3772 "expression" => {
3773 if let Some(tag) = parse_modern_expression_tag(source, child) {
3774 fragment_nodes.push(Node::ExpressionTag(tag));
3775 }
3776 }
3777 "element" => {
3778 let child_hint = hint.as_ref().and_then(|h| {
3779 make_child_hint(h, &mut old_node_cursor, child_start, child_end, "element")
3780 });
3781 fragment_nodes.push(parse_modern_element_node(
3782 source,
3783 child,
3784 in_shadowroot_template || is_shadowroot_template,
3785 children_in_svelte_head,
3786 loose,
3787 child_hint.as_ref(),
3788 ));
3789 }
3790 kind if is_typed_block_kind(kind) => {
3791 let child_hint = hint.as_ref().and_then(|h| {
3792 make_child_hint(h, &mut old_node_cursor, child_start, child_end, kind)
3793 });
3794 if let Some(block_node) = parse_modern_block(source, child, child_hint.as_ref()) {
3795 fragment_nodes.push(block_node);
3796 }
3797 }
3798 kind if is_typed_tag_kind(kind) => {
3799 if let Some(tag_node) = parse_modern_tag(source, child) {
3800 fragment_nodes.push(tag_node);
3801 }
3802 }
3803 "ERROR" => {
3804 fragment_nodes.extend(recover_modern_error_nodes(
3805 source,
3806 child,
3807 in_shadowroot_template || is_shadowroot_template,
3808 ));
3809 }
3810 _ => {}
3811 }
3812 }
3813
3814 RegularElement {
3815 start: node.start_byte(),
3816 end: node.end_byte(),
3817 name,
3818 name_loc,
3819 self_closing: self_closing_tag.is_some(),
3820 has_end_tag: end_tag.is_some(),
3821 attributes: attributes.into_boxed_slice(),
3822 fragment: Fragment {
3823 r#type: FragmentType::Fragment,
3824 nodes: fragment_nodes.into_boxed_slice(),
3825 },
3826 }
3827}
3828
3829fn parse_modern_alternate(
3830 source: &str,
3831 children: &[TsNode<'_>],
3832 branch_indices: &[usize],
3833 branch_index: usize,
3834 block_end_idx: usize,
3835) -> Option<Alternate> {
3836 let branch_child_idx = *branch_indices.get(branch_index)?;
3837 let branch = *children.get(branch_child_idx)?;
3838
3839 match branch.kind() {
3840 "else_if_clause" => {
3841 let test = branch
3842 .child_by_field_name("expression")
3843 .map(|node| parse_modern_expression_field_or_empty(source, node))
3844 .unwrap_or_else(|| modern_empty_identifier_at_block_tag_end(branch));
3845 let clause_children = named_children_vec(branch);
3847 let clause_body_start = body_start_index(branch, &clause_children, &["expression"]);
3848 let consequent = Fragment {
3849 r#type: FragmentType::Fragment,
3850 nodes: parse_modern_nodes_slice(
3851 source,
3852 &clause_children[clause_body_start..],
3853 false,
3854 )
3855 .into_boxed_slice(),
3856 };
3857
3858 let nested_alternate = if branch_index + 1 < branch_indices.len() {
3859 parse_modern_alternate(
3860 source,
3861 children,
3862 branch_indices,
3863 branch_index + 1,
3864 block_end_idx,
3865 )
3866 .map(Box::new)
3867 } else {
3868 None
3869 };
3870
3871 let nested_if = IfBlock {
3872 elseif: true,
3873 start: branch.start_byte(),
3874 end: children
3875 .get(block_end_idx)
3876 .map(|n| n.end_byte())
3877 .unwrap_or(branch.end_byte()),
3878 test,
3879 consequent,
3880 alternate: nested_alternate,
3881 };
3882
3883 Some(Alternate::Fragment(Fragment {
3884 r#type: FragmentType::Fragment,
3885 nodes: vec![Node::IfBlock(nested_if)].into_boxed_slice(),
3886 }))
3887 }
3888 "else_clause" => {
3889 let clause_children = named_children_vec(branch);
3891 Some(Alternate::Fragment(Fragment {
3892 r#type: FragmentType::Fragment,
3893 nodes: parse_modern_nodes_slice(source, &clause_children, false).into_boxed_slice(),
3894 }))
3895 }
3896 _ => {
3897 if branch_index + 1 < branch_indices.len() {
3898 parse_modern_alternate(
3899 source,
3900 children,
3901 branch_indices,
3902 branch_index + 1,
3903 block_end_idx,
3904 )
3905 } else {
3906 None
3907 }
3908 }
3909 }
3910}
3911
3912fn parse_modern_nodes_slice(
3913 source: &str,
3914 nodes: &[TsNode<'_>],
3915 in_shadowroot_template: bool,
3916) -> Vec<Node> {
3917 let mut out = Vec::new();
3918 let mut previous_end = None;
3919
3920 let mut index = 0usize;
3921 while index < nodes.len() {
3922 let node = nodes[index];
3923 if let Some(gap_start) = previous_end {
3924 push_modern_gap_text(source, &mut out, gap_start, node.start_byte());
3925 }
3926
3927 match node.kind() {
3928 "text" | "entity" => push_modern_text_node(&mut out, parse_modern_text(source, node)),
3929 "comment" => out.push(Node::Comment(parse_modern_comment(source, node))),
3930 "expression" => {
3931 if let Some(tag) = parse_modern_expression_tag(source, node) {
3932 out.push(Node::ExpressionTag(tag));
3933 }
3934 }
3935 "element" => out.push(parse_modern_element_node(
3936 source,
3937 node,
3938 in_shadowroot_template,
3939 false,
3940 false,
3941 None,
3942 )),
3943 "start_tag" => {
3944 if let Some(name) = start_end_tag_name(source, node)
3945 && let Some(close_index) =
3946 find_matching_loose_end_tag(source, nodes, index, name.as_ref())
3947 {
3948 let child_nodes = parse_modern_nodes_slice(
3949 source,
3950 &nodes[(index + 1)..close_index],
3951 in_shadowroot_template,
3952 );
3953 out.push(parse_modern_loose_start_tag_node_with_fragment(
3954 source,
3955 node,
3956 child_nodes,
3957 Some(nodes[close_index].end_byte()),
3958 ));
3959 index = close_index + 1;
3960 continue;
3961 }
3962
3963 let mut stop = nodes.len();
3964 for (lookahead, candidate) in nodes.iter().enumerate().skip(index + 1) {
3965 if is_loose_start_tag_boundary(*candidate) {
3966 stop = lookahead;
3967 break;
3968 }
3969 }
3970
3971 let child_nodes = parse_modern_nodes_slice(
3972 source,
3973 &nodes[(index + 1)..stop],
3974 in_shadowroot_template,
3975 );
3976 let end_override = (stop > index + 1).then(|| nodes[stop - 1].end_byte());
3977 out.push(parse_modern_loose_start_tag_node_with_fragment(
3978 source,
3979 node,
3980 child_nodes,
3981 end_override,
3982 ));
3983 index = stop;
3984 continue;
3985 }
3986 "self_closing_tag" => out.push(parse_modern_loose_start_tag_node(source, node, None)),
3987 kind if is_typed_block_kind(kind) => {
3988 if let Some(block_node) = parse_modern_block(source, node, None) {
3989 out.push(block_node);
3990 }
3991 }
3992 kind if is_typed_tag_kind(kind) => {
3993 if let Some(tag_node) = parse_modern_tag(source, node) {
3994 out.push(tag_node);
3995 }
3996 }
3997 "tag_name" => out.push(parse_modern_loose_tag_name_node(source, node)),
3998 "ERROR" => {
3999 out.extend(recover_modern_error_nodes(
4000 source,
4001 node,
4002 in_shadowroot_template,
4003 ));
4004 }
4005 _ => {}
4006 }
4007
4008 previous_end = Some(node.end_byte());
4009 index += 1;
4010 }
4011
4012 out
4013}
4014
4015fn push_modern_gap_text(source: &str, nodes: &mut Vec<Node>, start: usize, end: usize) {
4016 if start >= end {
4017 return;
4018 }
4019 let Some(raw) = source.get(start..end) else {
4020 return;
4021 };
4022 if raw.is_empty() {
4023 return;
4024 }
4025 push_modern_text_node(
4026 nodes,
4027 Text {
4028 start,
4029 end,
4030 raw: Arc::from(raw),
4031 data: Arc::from(raw),
4032 },
4033 );
4034}
4035
4036#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4037enum BlockKind {
4038 If,
4039 Each,
4040 Await,
4041 Key,
4042 Snippet,
4043}
4044
4045impl BlockKind {
4046 fn from_node_kind(kind: &str) -> Option<Self> {
4047 match kind {
4048 "if_block" => Some(Self::If),
4049 "each_block" => Some(Self::Each),
4050 "await_block" => Some(Self::Await),
4051 "key_block" => Some(Self::Key),
4052 "snippet_block" => Some(Self::Snippet),
4053 _ => None,
4054 }
4055 }
4056}
4057
4058impl std::str::FromStr for BlockKind {
4059 type Err = ();
4060
4061 fn from_str(raw: &str) -> Result<Self, Self::Err> {
4062 match raw {
4063 "if" => Ok(Self::If),
4064 "each" => Ok(Self::Each),
4065 "await" => Ok(Self::Await),
4066 "key" => Ok(Self::Key),
4067 "snippet" => Ok(Self::Snippet),
4068 _ => Err(()),
4069 }
4070 }
4071}
4072
4073#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4074enum BlockBranchKind {
4075 Else,
4076 ElseIf,
4077 Then,
4078 Catch,
4079}
4080
4081impl std::str::FromStr for BlockBranchKind {
4082 type Err = ();
4083
4084 fn from_str(raw: &str) -> Result<Self, Self::Err> {
4085 match raw {
4086 "else" => Ok(Self::Else),
4087 "else if" => Ok(Self::ElseIf),
4088 "then" => Ok(Self::Then),
4089 "catch" => Ok(Self::Catch),
4090 _ => Err(()),
4091 }
4092 }
4093}
4094
4095impl BlockBranchKind {
4096 fn parse_await_shorthand(raw: &str) -> Option<Self> {
4097 match raw {
4098 "then" => Some(Self::Then),
4099 "catch" => Some(Self::Catch),
4100 _ => None,
4101 }
4102 }
4103}
4104
4105impl BlockKind {
4106 fn accepts(self, branch: BlockBranchKind) -> bool {
4107 match self {
4108 Self::If => matches!(branch, BlockBranchKind::Else | BlockBranchKind::ElseIf),
4109 Self::Each => branch == BlockBranchKind::Else,
4110 Self::Await => matches!(branch, BlockBranchKind::Then | BlockBranchKind::Catch),
4111 Self::Key | Self::Snippet => false,
4112 }
4113 }
4114
4115 fn expected_branch_error(self) -> ParseErrorKind {
4116 match self {
4117 Self::Await => ParseErrorKind::ExpectedTokenAwaitBranch,
4118 Self::If | Self::Each | Self::Key | Self::Snippet => ParseErrorKind::ExpectedTokenElse,
4119 }
4120 }
4121}
4122
4123pub(crate) fn is_typed_block_kind(kind: &str) -> bool {
4124 matches!(
4125 kind,
4126 "if_block" | "each_block" | "await_block" | "key_block" | "snippet_block"
4127 )
4128}
4129
4130pub(crate) fn is_typed_tag_kind(kind: &str) -> bool {
4131 matches!(
4132 kind,
4133 "html_tag" | "debug_tag" | "const_tag" | "render_tag" | "attach_tag"
4134 )
4135}
4136
4137pub(crate) fn body_start_index(
4140 block: TsNode<'_>,
4141 children: &[TsNode<'_>],
4142 field_names: &[&str],
4143) -> usize {
4144 let mut max_idx = 0;
4145 for name in field_names {
4146 if let Some(field_node) = block.child_by_field_name(name)
4147 && let Some(idx) = children.iter().position(|c| c.id() == field_node.id())
4148 {
4149 max_idx = max_idx.max(idx + 1);
4150 }
4151 }
4152 max_idx
4153}
4154
4155fn cst_node_has_direct_token(node: TsNode<'_>, token: &str) -> bool {
4156 let mut cursor = node.walk();
4157 node.children(&mut cursor)
4158 .any(|child| !child.is_named() && child.kind() == token)
4159}
4160
4161pub fn parse_modern_expression_tag(source: &str, node: TsNode<'_>) -> Option<ExpressionTag> {
4162 let expression = parse_modern_expression(source, node)?;
4163
4164 Some(ExpressionTag {
4165 r#type: ExpressionTagType::ExpressionTag,
4166 start: node.start_byte(),
4167 end: node.end_byte(),
4168 expression,
4169 })
4170}
4171
4172pub(crate) fn parse_modern_expression_tag_loose(source: &str, node: TsNode<'_>) -> ExpressionTag {
4175 let expression = parse_modern_expression(source, node)
4176 .unwrap_or_else(|| loose_empty_expression_for_braces(source, node));
4177
4178 ExpressionTag {
4179 r#type: ExpressionTagType::ExpressionTag,
4180 start: node.start_byte(),
4181 end: node.end_byte(),
4182 expression,
4183 }
4184}
4185
4186fn loose_empty_expression_for_braces(source: &str, node: TsNode<'_>) -> Expression {
4188 let raw = node.utf8_text(source.as_bytes()).ok().unwrap_or_default();
4189 let inner_start = node.start_byte().saturating_add(1);
4190 let inner_end = if raw.ends_with('}') {
4191 node.end_byte().saturating_sub(1)
4192 } else {
4193 node.end_byte()
4194 };
4195 modern_empty_identifier_expression_span(inner_start, inner_end.saturating_sub(inner_start))
4196}
4197
4198fn loose_tag_name_range(
4199 source: &str,
4200 start: usize,
4201 fallback_end: usize,
4202) -> Option<(Arc<str>, usize)> {
4203 let raw = source.get(start..)?;
4204 let len = raw
4205 .chars()
4206 .take_while(|ch| !ch.is_whitespace() && *ch != '>' && *ch != '/')
4207 .map(char::len_utf8)
4208 .sum::<usize>();
4209
4210 if len == 0 {
4211 let fallback = source.get(start..fallback_end).unwrap_or_default();
4212 if fallback.is_empty() {
4213 return None;
4214 }
4215 return Some((Arc::from(fallback), fallback_end));
4216 }
4217
4218 let end = start + len;
4219 let text = source.get(start..end).unwrap_or_default();
4220 Some((Arc::from(text), end))
4221}
4222
4223fn loose_tag_name_and_loc(
4224 source: &str,
4225 container: TsNode<'_>,
4226 name_node: Option<TsNode<'_>>,
4227) -> (Arc<str>, LegacyNameLocation) {
4228 let name_start = name_node.map(|node| node.start_byte()).unwrap_or_else(|| {
4229 container
4230 .start_byte()
4231 .saturating_add(1)
4232 .min(container.end_byte())
4233 });
4234 let fallback_end = name_node.map(|node| node.end_byte()).unwrap_or(name_start);
4235
4236 if let Some((name, name_end)) = loose_tag_name_range(source, name_start, fallback_end) {
4237 return (
4238 name,
4239 LegacyNameLocation {
4240 start: source_location_from_point(
4241 source,
4242 name_node
4243 .map(|node| node.start_position())
4244 .unwrap_or_else(|| container.start_position()),
4245 name_start,
4246 ),
4247 end: source_location_at_offset(source, name_end),
4248 },
4249 );
4250 }
4251
4252 (
4253 Arc::from(""),
4254 LegacyNameLocation {
4255 start: source_location_from_point(
4256 source,
4257 container.start_position(),
4258 container.start_byte(),
4259 ),
4260 end: source_location_from_point(
4261 source,
4262 container.start_position(),
4263 container.start_byte(),
4264 ),
4265 },
4266 )
4267}
4268
4269fn parse_modern_loose_tag_name_node(source: &str, node: TsNode<'_>) -> Node {
4270 let (name, name_loc) = loose_tag_name_and_loc(source, node, Some(node));
4271 let start = if node.start_byte() > 0
4272 && source.as_bytes().get(node.start_byte().saturating_sub(1)) == Some(&b'<')
4273 {
4274 node.start_byte() - 1
4275 } else {
4276 node.start_byte()
4277 };
4278 let end = name_loc.end.character;
4279
4280 let fragment = Fragment {
4281 r#type: FragmentType::Fragment,
4282 nodes: Box::new([]),
4283 };
4284
4285 let element = RegularElement {
4286 start,
4287 end,
4288 name,
4289 name_loc,
4290 self_closing: node.kind() == "self_closing_tag",
4291 has_end_tag: false,
4292 attributes: Box::new([]),
4293 fragment,
4294 };
4295 classify_modern_element(element, false, false)
4296}
4297
4298fn parse_modern_loose_start_tag_node(
4299 source: &str,
4300 node: TsNode<'_>,
4301 trailing_text: Option<TsNode<'_>>,
4302) -> Node {
4303 let end_override = trailing_text.map(|text| text.end_byte());
4304 let fragment_nodes = trailing_text
4305 .map(|text| vec![Node::Text(parse_modern_text(source, text))])
4306 .unwrap_or_default();
4307 parse_modern_loose_start_tag_node_with_fragment(source, node, fragment_nodes, end_override)
4308}
4309
4310fn parse_modern_loose_start_tag_node_with_fragment(
4311 source: &str,
4312 node: TsNode<'_>,
4313 fragment_nodes: Vec<Node>,
4314 end_override: Option<usize>,
4315) -> Node {
4316 let name_node = find_first_named_child(node, "tag_name");
4317 let (name, name_loc) = loose_tag_name_and_loc(source, node, name_node);
4318
4319 let end = end_override.unwrap_or_else(|| node.end_byte());
4320
4321 let attributes = parse_modern_attributes(source, node, false);
4322 let fragment = Fragment {
4323 r#type: FragmentType::Fragment,
4324 nodes: fragment_nodes.into_boxed_slice(),
4325 };
4326
4327 let element = RegularElement {
4328 start: node.start_byte(),
4329 end,
4330 name,
4331 name_loc,
4332 self_closing: node.kind() == "self_closing_tag",
4333 has_end_tag: false,
4334 attributes: attributes.into_boxed_slice(),
4335 fragment,
4336 };
4337 classify_modern_element(element, false, false)
4338}
4339
4340fn is_loose_start_tag_boundary(node: TsNode<'_>) -> bool {
4341 matches!(
4342 node.kind(),
4343 "start_tag"
4344 | "self_closing_tag"
4345 | "end_tag"
4346 | "block_end"
4347 | "else_if_clause"
4348 | "else_clause"
4349 | "await_branch"
4350 ) || is_typed_block_kind(node.kind())
4351}
4352
4353fn start_end_tag_name(source: &str, node: TsNode<'_>) -> Option<Arc<str>> {
4354 find_first_named_child(node, "tag_name").map(|name| text_for_node(source, name))
4355}
4356
4357fn find_matching_loose_end_tag(
4358 source: &str,
4359 nodes: &[TsNode<'_>],
4360 start_index: usize,
4361 target_name: &str,
4362) -> Option<usize> {
4363 let mut depth = 0usize;
4364
4365 for (index, node) in nodes.iter().enumerate().skip(start_index + 1) {
4366 match node.kind() {
4367 "start_tag" => {
4368 if let Some(name) = start_end_tag_name(source, *node)
4369 && name.as_ref() == target_name
4370 {
4371 depth += 1;
4372 }
4373 }
4374 "end_tag" => {
4375 if let Some(name) = start_end_tag_name(source, *node)
4376 && name.as_ref() == target_name
4377 {
4378 if depth == 0 {
4379 return Some(index);
4380 }
4381 depth = depth.saturating_sub(1);
4382 }
4383 }
4384 _ => {}
4385 }
4386 }
4387
4388 None
4389}
4390
4391pub(crate) fn parse_modern_expression(source: &str, node: TsNode<'_>) -> Option<Expression> {
4392 let (raw, start) = expression_node_text(source, node)?;
4393 let (line, column) = line_column_at_offset(source, start);
4394 parse_modern_expression_from_text(raw, start, line, column)
4395}
4396
4397fn parse_modern_expression_error(source: &str, node: TsNode<'_>) -> Option<(usize, Arc<str>)> {
4398 let raw = node.utf8_text(source.as_bytes()).ok()?;
4399 if raw.starts_with("{:") {
4400 return None;
4401 }
4402
4403 let (raw, start) = expression_node_text(source, node)?;
4404 let (line, column) = line_column_at_offset(source, start);
4405 parse_modern_expression_error_from_text(raw, start, line, column)
4406}
4407
4408fn expression_node_text<'a>(source: &'a str, node: TsNode<'_>) -> Option<(&'a str, usize)> {
4409 if node.kind() == "expression" {
4410 if let Some(content) = node.child_by_field_name("content") {
4411 let raw = content.utf8_text(source.as_bytes()).ok()?;
4412 return Some((raw, content.start_byte()));
4413 }
4414 let raw = node.utf8_text(source.as_bytes()).ok()?;
4415 if raw.len() >= 2 && raw.starts_with('{') && raw.ends_with('}') {
4416 return Some((&raw[1..raw.len().saturating_sub(1)], node.start_byte() + 1));
4417 }
4418 }
4419
4420 Some((node.utf8_text(source.as_bytes()).ok()?, node.start_byte()))
4421}
4422
4423pub fn modern_empty_identifier_expression(node: TsNode<'_>) -> Expression {
4424 let start = node.start_byte().saturating_add(1).min(node.end_byte());
4425 modern_empty_identifier_expression_span(start, 0)
4426}
4427
4428fn modern_empty_identifier_expression_span(start: usize, len: usize) -> Expression {
4429 let end = start.saturating_add(len);
4430 Expression::empty(start, end)
4431}
4432
4433fn modern_identifier_expression_with_loc(
4434 name: Arc<str>,
4435 start: usize,
4436 end: usize,
4437 line: usize,
4438 column: usize,
4439) -> Expression {
4440 let _ = (name, line, column);
4441 Expression::empty(start, end)
4442}
4443
4444pub fn parse_modern_expression_from_text(
4445 text: &str,
4446 start_byte: usize,
4447 line: usize,
4448 column: usize,
4449) -> Option<Expression> {
4450 let trimmed = text.trim();
4451 if trimmed.is_empty() {
4452 return None;
4453 }
4454
4455 let leading_ws = text.find(trimmed).unwrap_or(0);
4456 let start = start_byte + leading_ws;
4457 let (start_line, start_col) = offset_to_line_column(text, leading_ws, line, column);
4458 let mut raw =
4459 crate::parse::parse_modern_expression_with_oxc(trimmed, start, start_line, start_col)?;
4460 raw.syntax.parens = leading_parens(trimmed, start, raw.start);
4461 attach_leading_comments_to_expression(&mut raw, trimmed, start);
4462 Some(raw)
4463}
4464
4465fn parse_modern_expression_error_from_text(
4466 text: &str,
4467 start_byte: usize,
4468 line: usize,
4469 column: usize,
4470) -> Option<(usize, Arc<str>)> {
4471 let trimmed = text.trim();
4472 if trimmed.is_empty() {
4473 return None;
4474 }
4475
4476 let leading_ws = text.find(trimmed).unwrap_or(0);
4477 let start = start_byte + leading_ws;
4478 let (start_line, start_col) = offset_to_line_column(text, leading_ws, line, column);
4479 let message = crate::parse::parse_modern_expression_error_with_oxc(
4480 trimmed, start, start_line, start_col,
4481 )?;
4482 Some((start, message))
4483}
4484
4485fn leading_parens(text: &str, start: usize, node_start: usize) -> u16 {
4486 let prefix_len = node_start.saturating_sub(start).min(text.len());
4487 let bytes = text.as_bytes();
4488 let mut i = 0usize;
4489 let mut parens = 0u16;
4490
4491 while i < prefix_len {
4492 match bytes[i] {
4493 b'(' => {
4494 parens = parens.saturating_add(1);
4495 i += 1;
4496 }
4497 b'/' if i + 1 < prefix_len && bytes[i + 1] == b'/' => {
4498 i += 2;
4499 while i < prefix_len && bytes[i] != b'\n' {
4500 i += 1;
4501 }
4502 }
4503 b'/' if i + 1 < prefix_len && bytes[i + 1] == b'*' => {
4504 i += 2;
4505 while i + 1 < prefix_len && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
4506 i += 1;
4507 }
4508 i = (i + 2).min(prefix_len);
4509 }
4510 b' ' | b'\t' | b'\r' | b'\n' => {
4511 i += 1;
4512 }
4513 _ => {
4514 i += 1;
4515 }
4516 }
4517 }
4518
4519 parens
4520}
4521
4522pub fn attach_leading_comments_to_expression(
4523 _expression: &mut Expression,
4524 _source: &str,
4525 _global_start: usize,
4526) {
4527}
4528
4529pub fn attach_trailing_comments_to_expression(
4530 _expression: &mut Expression,
4531 _source: &str,
4532 _global_start: usize,
4533) {
4534}
4535
4536pub fn modern_node_span(node: &Node) -> (usize, usize) {
4537 (node.start(), node.end())
4538}
4539
4540pub fn expression_identifier_name(expression: &Expression) -> Option<Arc<str>> {
4541 crate::parse::oxc_query::expression_identifier_name(expression)
4542}
4543
4544pub fn expression_literal_string(expression: &Expression) -> Option<Arc<str>> {
4545 crate::parse::oxc_query::expression_literal_string(expression)
4546}
4547
4548pub fn expression_literal_bool(expression: &Expression) -> Option<bool> {
4549 crate::parse::oxc_query::expression_literal_bool(expression)
4550}
4551
4552fn offset_to_line_column(
4553 text: &str,
4554 offset: usize,
4555 base_line: usize,
4556 base_column: usize,
4557) -> (usize, usize) {
4558 let mut line = base_line;
4559 let mut column = base_column;
4560 let bytes = text.as_bytes();
4561 let limit = offset.min(bytes.len());
4562
4563 for byte in bytes.iter().take(limit) {
4564 if *byte == b'\n' {
4565 line += 1;
4566 column = 0;
4567 } else {
4568 column += 1;
4569 }
4570 }
4571
4572 (line, column)
4573}
4574
4575pub fn legacy_expression_from_modern_expression(
4576 expression: Expression,
4577 include_character: bool,
4578) -> Option<LegacyExpression> {
4579 super::legacy::legacy_expression_from_modern(expression, include_character)
4580}
4581
4582pub fn named_children_vec(node: TsNode<'_>) -> Vec<TsNode<'_>> {
4583 let mut cursor = node.walk();
4584 node.named_children(&mut cursor).collect()
4585}