1use lru::LruCache;
19use std::num::NonZeroUsize;
20use std::path::{Path, PathBuf};
21use std::sync::{Mutex, MutexGuard};
22use tree_sitter::{InputEdit, Point, Tree};
23
24pub struct InputEditCalculator;
45
46impl InputEditCalculator {
47 #[must_use]
58 pub fn calculate(old_content: &[u8], new_content: &[u8]) -> InputEdit {
59 if old_content.is_empty() && new_content.is_empty() {
61 return InputEdit {
62 start_byte: 0,
63 old_end_byte: 0,
64 new_end_byte: 0,
65 start_position: Point { row: 0, column: 0 },
66 old_end_position: Point { row: 0, column: 0 },
67 new_end_position: Point { row: 0, column: 0 },
68 };
69 }
70
71 if old_content.is_empty() {
72 let new_end_position = Self::byte_to_point(new_content, new_content.len());
74 return InputEdit {
75 start_byte: 0,
76 old_end_byte: 0,
77 new_end_byte: new_content.len(),
78 start_position: Point { row: 0, column: 0 },
79 old_end_position: Point { row: 0, column: 0 },
80 new_end_position,
81 };
82 }
83
84 if new_content.is_empty() {
85 let old_end_position = Self::byte_to_point(old_content, old_content.len());
87 return InputEdit {
88 start_byte: 0,
89 old_end_byte: old_content.len(),
90 new_end_byte: 0,
91 start_position: Point { row: 0, column: 0 },
92 old_end_position,
93 new_end_position: Point { row: 0, column: 0 },
94 };
95 }
96
97 let prefix_len = Self::find_common_prefix(old_content, new_content);
99
100 let old_remaining = &old_content[prefix_len..];
102 let new_remaining = &new_content[prefix_len..];
103 let suffix_len = Self::find_common_suffix(old_remaining, new_remaining);
104
105 let start_byte = prefix_len;
107 let old_end_byte = prefix_len + old_remaining.len() - suffix_len;
108 let new_end_byte = prefix_len + new_remaining.len() - suffix_len;
109
110 let start_position = Self::byte_to_point(old_content, start_byte);
112 let old_end_position = Self::byte_to_point(old_content, old_end_byte);
113 let new_end_position = Self::byte_to_point(new_content, new_end_byte);
114
115 InputEdit {
116 start_byte,
117 old_end_byte,
118 new_end_byte,
119 start_position,
120 old_end_position,
121 new_end_position,
122 }
123 }
124
125 fn find_common_prefix(a: &[u8], b: &[u8]) -> usize {
127 a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count()
128 }
129
130 fn find_common_suffix(a: &[u8], b: &[u8]) -> usize {
132 a.iter()
133 .rev()
134 .zip(b.iter().rev())
135 .take_while(|(x, y)| x == y)
136 .count()
137 }
138
139 fn byte_to_point(content: &[u8], offset: usize) -> Point {
144 let mut row = 0;
145 let mut column = 0;
146
147 for (i, &byte) in content.iter().enumerate() {
148 if i >= offset {
149 break;
150 }
151 if byte == b'\n' {
152 row += 1;
153 column = 0;
154 } else {
155 column += 1;
156 }
157 }
158
159 Point { row, column }
160 }
161}
162
163#[derive(Debug)]
168struct CachedTree {
169 tree: Tree,
171 hash: u64,
173}
174
175impl CachedTree {
176 fn new(tree: Tree, hash: u64) -> Self {
177 Self { tree, hash }
178 }
179}
180
181pub struct TreeCache {
209 cache: Mutex<LruCache<PathBuf, CachedTree>>,
210}
211
212impl TreeCache {
213 pub const DEFAULT_CAPACITY: usize = 100;
215
216 #[must_use]
226 pub fn new(capacity: usize) -> Self {
227 let capacity = NonZeroUsize::new(capacity).expect("capacity must be > 0");
228 Self {
229 cache: Mutex::new(LruCache::new(capacity)),
230 }
231 }
232
233 fn lock_cache(&self) -> MutexGuard<'_, LruCache<PathBuf, CachedTree>> {
234 self.cache
235 .lock()
236 .unwrap_or_else(std::sync::PoisonError::into_inner)
237 }
238
239 #[must_use]
241 pub fn with_default_capacity() -> Self {
242 Self::new(Self::DEFAULT_CAPACITY)
243 }
244
245 pub fn insert(&self, path: &Path, tree: Tree, hash: u64) {
255 let mut cache = self.lock_cache();
256 cache.put(path.to_path_buf(), CachedTree::new(tree, hash));
257 }
258
259 pub fn get(&self, path: &Path) -> Option<(Tree, u64)> {
274 let mut cache = self.lock_cache();
275 cache
276 .get(path)
277 .map(|cached| (cached.tree.clone(), cached.hash))
278 }
279
280 pub fn remove(&self, path: &Path) -> bool {
290 let mut cache = self.lock_cache();
291 cache.pop(path).is_some()
292 }
293
294 pub fn clear(&self) {
296 let mut cache = self.lock_cache();
297 cache.clear();
298 }
299
300 pub fn len(&self) -> usize {
302 let cache = self.lock_cache();
303 cache.len()
304 }
305
306 pub fn is_empty(&self) -> bool {
308 self.len() == 0
309 }
310}
311
312pub struct IncrementalParser {
354 cache: TreeCache,
355}
356
357impl IncrementalParser {
358 #[must_use]
368 pub fn new(capacity: usize) -> Self {
369 Self {
370 cache: TreeCache::new(capacity),
371 }
372 }
373
374 #[must_use]
376 pub fn with_default_capacity() -> Self {
377 Self {
378 cache: TreeCache::with_default_capacity(),
379 }
380 }
381
382 pub fn parse<P>(
408 &self,
409 plugin: &P,
410 path: &Path,
411 new_content: &[u8],
412 old_content: Option<&[u8]>,
413 ) -> Result<Tree, crate::plugin::error::ParseError>
414 where
415 P: crate::plugin::LanguagePlugin + ?Sized,
416 {
417 let new_hash = Self::hash_content(new_content);
419
420 if let (Some(old_content), Some((old_tree, old_hash))) = (old_content, self.cache.get(path))
422 {
423 if new_hash == old_hash {
425 return Ok(old_tree);
427 }
428 match Self::parse_incremental(plugin, &old_tree, old_content, new_content) {
429 Ok(tree) => {
430 self.cache.insert(path, tree.clone(), new_hash);
432 return Ok(tree);
433 }
434 Err(_) => {
435 log::debug!(
437 "Incremental parse failed for {}, falling back to full parse",
438 path.display()
439 );
440 }
441 }
442 }
443
444 let tree = plugin.parse_ast(new_content)?;
446 self.cache.insert(path, tree.clone(), new_hash);
447 Ok(tree)
448 }
449
450 fn parse_incremental<P>(
470 plugin: &P,
471 old_tree: &Tree,
472 old_content: &[u8],
473 new_content: &[u8],
474 ) -> Result<Tree, crate::plugin::error::ParseError>
475 where
476 P: crate::plugin::LanguagePlugin + ?Sized,
477 {
478 let edit = InputEditCalculator::calculate(old_content, new_content);
480
481 let mut edited_tree = old_tree.clone();
483 edited_tree.edit(&edit);
484
485 let mut parser = tree_sitter::Parser::new();
488 parser
489 .set_language(&plugin.language())
490 .map_err(|e| crate::plugin::error::ParseError::LanguageSetFailed(e.to_string()))?;
491
492 parser
493 .parse(new_content, Some(&edited_tree))
494 .ok_or(crate::plugin::error::ParseError::TreeSitterFailed)
495 }
496
497 fn hash_content(content: &[u8]) -> u64 {
499 use std::hash::{Hash, Hasher};
500 let mut hasher = std::collections::hash_map::DefaultHasher::new();
501 content.hash(&mut hasher);
502 hasher.finish()
503 }
504
505 pub fn clear_cache(&self) {
507 self.cache.clear();
508 }
509
510 pub fn cache_len(&self) -> usize {
512 self.cache.len()
513 }
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519
520 #[test]
521 fn test_calculate_single_line_edit() {
522 let old = b"fn foo() {}";
523 let new = b"fn bar() {}";
524 let edit = InputEditCalculator::calculate(old, new);
525
526 assert_eq!(edit.start_byte, 3); assert_eq!(edit.old_end_byte, 6); assert_eq!(edit.new_end_byte, 6); assert_eq!(edit.start_position, Point { row: 0, column: 3 });
530 assert_eq!(edit.old_end_position, Point { row: 0, column: 6 });
531 assert_eq!(edit.new_end_position, Point { row: 0, column: 6 });
532 }
533
534 #[test]
535 fn test_calculate_multiline_insert() {
536 let old = b"line1\nline3\n";
537 let new = b"line1\nline2\nline3\n";
538 let edit = InputEditCalculator::calculate(old, new);
539
540 assert_eq!(edit.start_byte, 10); assert_eq!(edit.old_end_byte, 10); assert_eq!(edit.new_end_byte, 16); assert_eq!(edit.start_position, Point { row: 1, column: 4 });
546 assert_eq!(edit.old_end_position, Point { row: 1, column: 4 });
547 assert_eq!(edit.new_end_position, Point { row: 2, column: 4 });
548 }
549
550 #[test]
551 fn test_calculate_multiline_delete() {
552 let old = b"line1\nline2\nline3\n";
553 let new = b"line1\nline3\n";
554 let edit = InputEditCalculator::calculate(old, new);
555
556 assert_eq!(edit.start_byte, 10); assert_eq!(edit.old_end_byte, 16); assert_eq!(edit.new_end_byte, 10); assert_eq!(edit.start_position, Point { row: 1, column: 4 });
562 assert_eq!(edit.old_end_position, Point { row: 2, column: 4 });
563 assert_eq!(edit.new_end_position, Point { row: 1, column: 4 });
564 }
565
566 #[test]
567 fn test_calculate_empty_to_content() {
568 let old = b"";
569 let new = b"hello\nworld\n";
570 let edit = InputEditCalculator::calculate(old, new);
571
572 assert_eq!(edit.start_byte, 0);
573 assert_eq!(edit.old_end_byte, 0);
574 assert_eq!(edit.new_end_byte, 12);
575 assert_eq!(edit.start_position, Point { row: 0, column: 0 });
576 assert_eq!(edit.old_end_position, Point { row: 0, column: 0 });
577 assert_eq!(edit.new_end_position, Point { row: 2, column: 0 });
578 }
579
580 #[test]
581 fn test_calculate_content_to_empty() {
582 let old = b"hello\nworld\n";
583 let new = b"";
584 let edit = InputEditCalculator::calculate(old, new);
585
586 assert_eq!(edit.start_byte, 0);
587 assert_eq!(edit.old_end_byte, 12);
588 assert_eq!(edit.new_end_byte, 0);
589 assert_eq!(edit.start_position, Point { row: 0, column: 0 });
590 assert_eq!(edit.old_end_position, Point { row: 2, column: 0 });
591 assert_eq!(edit.new_end_position, Point { row: 0, column: 0 });
592 }
593
594 #[test]
595 fn test_calculate_no_change() {
596 let old = b"unchanged";
597 let new = b"unchanged";
598 let edit = InputEditCalculator::calculate(old, new);
599
600 assert_eq!(edit.start_byte, 9); assert_eq!(edit.old_end_byte, 9);
602 assert_eq!(edit.new_end_byte, 9);
603 assert_eq!(edit.start_position, Point { row: 0, column: 9 });
604 assert_eq!(edit.old_end_position, Point { row: 0, column: 9 });
605 assert_eq!(edit.new_end_position, Point { row: 0, column: 9 });
606 }
607
608 #[test]
609 fn test_byte_to_point_multiline() {
610 let content = b"line1\nline2\nline3\n";
611
612 assert_eq!(
613 InputEditCalculator::byte_to_point(content, 0),
614 Point { row: 0, column: 0 }
615 );
616 assert_eq!(
617 InputEditCalculator::byte_to_point(content, 5),
618 Point { row: 0, column: 5 }
619 );
620 assert_eq!(
621 InputEditCalculator::byte_to_point(content, 6),
622 Point { row: 1, column: 0 }
623 );
624 assert_eq!(
625 InputEditCalculator::byte_to_point(content, 12),
626 Point { row: 2, column: 0 }
627 );
628 }
629
630 #[test]
631 fn test_find_common_prefix() {
632 assert_eq!(InputEditCalculator::find_common_prefix(b"abc", b"abx"), 2);
633 assert_eq!(InputEditCalculator::find_common_prefix(b"abc", b"xyz"), 0);
634 assert_eq!(InputEditCalculator::find_common_prefix(b"abc", b"abc"), 3);
635 assert_eq!(InputEditCalculator::find_common_prefix(b"", b"abc"), 0);
636 }
637
638 #[test]
639 fn test_find_common_suffix() {
640 assert_eq!(InputEditCalculator::find_common_suffix(b"abc", b"xbc"), 2);
641 assert_eq!(InputEditCalculator::find_common_suffix(b"abc", b"xyz"), 0);
642 assert_eq!(InputEditCalculator::find_common_suffix(b"abc", b"abc"), 3);
643 assert_eq!(InputEditCalculator::find_common_suffix(b"", b"abc"), 0);
644 }
645
646 fn create_dummy_tree() -> Tree {
649 use tree_sitter::Parser;
650 let mut parser = Parser::new();
651 parser
652 .set_language(&tree_sitter_rust::LANGUAGE.into())
653 .expect("set language");
654 parser.parse("fn main() {}", None).expect("parse")
655 }
656
657 #[test]
658 fn test_tree_cache_insert_and_get() {
659 let cache = TreeCache::with_default_capacity();
660 let path = PathBuf::from("/test/file.rs");
661 let tree = create_dummy_tree();
662 let hash = 0x1234_5678_90ab_cdef;
663
664 cache.insert(&path, tree, hash);
666 assert_eq!(cache.len(), 1);
667 assert!(!cache.is_empty());
668
669 let result = cache.get(&path);
671 assert!(result.is_some());
672 let (cached_tree, cached_hash) = result.unwrap();
673 assert_eq!(cached_hash, hash);
674 assert!(!cached_tree.root_node().kind().is_empty());
676 }
677
678 #[test]
679 fn test_tree_cache_miss() {
680 let cache = TreeCache::with_default_capacity();
681 let path = PathBuf::from("/test/file.rs");
682
683 assert!(cache.get(&path).is_none());
685 assert!(cache.is_empty());
686 }
687
688 #[test]
689 fn test_tree_cache_remove() {
690 let cache = TreeCache::with_default_capacity();
691 let path = PathBuf::from("/test/file.rs");
692 let tree = create_dummy_tree();
693
694 cache.insert(&path, tree, 0x123);
695 assert_eq!(cache.len(), 1);
696
697 assert!(cache.remove(&path));
699 assert_eq!(cache.len(), 0);
700 assert!(cache.get(&path).is_none());
701
702 assert!(!cache.remove(&path));
704 }
705
706 #[test]
707 fn test_tree_cache_clear() {
708 let cache = TreeCache::with_default_capacity();
709 let path1 = PathBuf::from("/test/file1.rs");
710 let path2 = PathBuf::from("/test/file2.rs");
711
712 cache.insert(&path1, create_dummy_tree(), 0x123);
713 cache.insert(&path2, create_dummy_tree(), 0x456);
714 assert_eq!(cache.len(), 2);
715
716 cache.clear();
717 assert_eq!(cache.len(), 0);
718 assert!(cache.is_empty());
719 assert!(cache.get(&path1).is_none());
720 assert!(cache.get(&path2).is_none());
721 }
722
723 #[test]
724 fn test_tree_cache_lru_eviction() {
725 let cache = TreeCache::new(2); let path1 = PathBuf::from("/test/file1.rs");
727 let path2 = PathBuf::from("/test/file2.rs");
728 let path3 = PathBuf::from("/test/file3.rs");
729
730 cache.insert(&path1, create_dummy_tree(), 0x111);
732 cache.insert(&path2, create_dummy_tree(), 0x222);
733 assert_eq!(cache.len(), 2);
734
735 cache.insert(&path3, create_dummy_tree(), 0x333);
737 assert_eq!(cache.len(), 2);
738 assert!(cache.get(&path1).is_none()); assert!(cache.get(&path2).is_some()); assert!(cache.get(&path3).is_some()); }
742
743 #[test]
744 fn test_tree_cache_lru_access_updates() {
745 let cache = TreeCache::new(2); let path1 = PathBuf::from("/test/file1.rs");
747 let path2 = PathBuf::from("/test/file2.rs");
748 let path3 = PathBuf::from("/test/file3.rs");
749
750 cache.insert(&path1, create_dummy_tree(), 0x111);
752 cache.insert(&path2, create_dummy_tree(), 0x222);
753
754 assert!(cache.get(&path1).is_some());
756
757 cache.insert(&path3, create_dummy_tree(), 0x333);
759 assert_eq!(cache.len(), 2);
760 assert!(cache.get(&path1).is_some()); assert!(cache.get(&path2).is_none()); assert!(cache.get(&path3).is_some()); }
764
765 #[test]
766 fn test_tree_cache_thread_safety() {
767 use std::sync::Arc;
768 use std::thread;
769
770 let cache = Arc::new(TreeCache::with_default_capacity());
771 let mut handles = vec![];
772
773 for i in 0_u64..10 {
775 let cache_clone = Arc::clone(&cache);
776 let handle = thread::spawn(move || {
777 let path = PathBuf::from(format!("/test/file{i}.rs"));
778 cache_clone.insert(&path, create_dummy_tree(), i);
779 });
780 handles.push(handle);
781 }
782
783 for handle in handles {
785 handle.join().unwrap();
786 }
787
788 assert_eq!(cache.len(), 10);
790 }
791
792 struct MockPlugin;
796
797 impl crate::plugin::LanguagePlugin for MockPlugin {
798 fn metadata(&self) -> crate::plugin::LanguageMetadata {
799 crate::plugin::LanguageMetadata {
800 id: "mock",
801 name: "Mock",
802 version: "1.0.0",
803 author: "test",
804 description: "Mock plugin for testing",
805 tree_sitter_version: "0.24",
806 }
807 }
808
809 fn extensions(&self) -> &'static [&'static str] {
810 &["mock"]
811 }
812
813 fn language(&self) -> tree_sitter::Language {
814 tree_sitter_rust::LANGUAGE.into()
815 }
816
817 fn parse_ast(&self, content: &[u8]) -> Result<Tree, crate::plugin::error::ParseError> {
818 use tree_sitter::Parser;
819 let mut parser = Parser::new();
820 parser
821 .set_language(&self.language())
822 .map_err(|e| crate::plugin::error::ParseError::LanguageSetFailed(e.to_string()))?;
823 parser
824 .parse(content, None)
825 .ok_or(crate::plugin::error::ParseError::TreeSitterFailed)
826 }
827
828 fn extract_scopes(
829 &self,
830 _tree: &Tree,
831 _content: &[u8],
832 _file_path: &Path,
833 ) -> Result<Vec<crate::ast::Scope>, crate::plugin::error::ScopeError> {
834 Ok(vec![])
835 }
836 }
837
838 #[test]
839 fn test_incremental_parser_cache_miss() {
840 let parser = IncrementalParser::with_default_capacity();
841 let plugin = MockPlugin;
842 let path = PathBuf::from("/test/file.rs");
843 let content = b"fn foo() {}";
844
845 let tree = parser.parse(&plugin, &path, content, None).unwrap();
847 assert!(!tree.root_node().kind().is_empty());
848 assert_eq!(parser.cache_len(), 1);
849 }
850
851 #[test]
852 fn test_incremental_parser_cache_hit_unchanged() {
853 let parser = IncrementalParser::with_default_capacity();
854 let plugin = MockPlugin;
855 let path = PathBuf::from("/test/file.rs");
856 let content = b"fn foo() {}";
857
858 let tree1 = parser.parse(&plugin, &path, content, None).unwrap();
860
861 let tree2 = parser
863 .parse(&plugin, &path, content, Some(content))
864 .unwrap();
865
866 assert_eq!(tree1.root_node().kind(), tree2.root_node().kind());
868 assert_eq!(parser.cache_len(), 1);
869 }
870
871 #[test]
872 fn test_incremental_parser_incremental_parse() {
873 let parser = IncrementalParser::with_default_capacity();
874 let plugin = MockPlugin;
875 let path = PathBuf::from("/test/file.rs");
876 let old_content = b"fn foo() {}";
877 let new_content = b"fn bar() {}";
878
879 let tree1 = parser.parse(&plugin, &path, old_content, None).unwrap();
881 assert_eq!(parser.cache_len(), 1);
882
883 let tree2 = parser
885 .parse(&plugin, &path, new_content, Some(old_content))
886 .unwrap();
887
888 assert!(!tree1.root_node().kind().is_empty());
890 assert!(!tree2.root_node().kind().is_empty());
891 assert_eq!(parser.cache_len(), 1);
893 }
894
895 #[test]
896 fn test_incremental_parser_multiline_edit() {
897 let parser = IncrementalParser::with_default_capacity();
898 let plugin = MockPlugin;
899 let path = PathBuf::from("/test/file.rs");
900
901 let old_content = b"fn foo() {\n println!(\"hello\");\n}";
902 let new_content = b"fn foo() {\n println!(\"world\");\n println!(\"test\");\n}";
903
904 parser.parse(&plugin, &path, old_content, None).unwrap();
906
907 let tree = parser
909 .parse(&plugin, &path, new_content, Some(old_content))
910 .unwrap();
911
912 assert!(!tree.root_node().kind().is_empty());
914 assert_eq!(parser.cache_len(), 1);
915 }
916
917 #[test]
918 fn test_incremental_parser_clear_cache() {
919 let parser = IncrementalParser::with_default_capacity();
920 let plugin = MockPlugin;
921 let path1 = PathBuf::from("/test/file1.rs");
922 let path2 = PathBuf::from("/test/file2.rs");
923
924 parser.parse(&plugin, &path1, b"fn foo() {}", None).unwrap();
925 parser.parse(&plugin, &path2, b"fn bar() {}", None).unwrap();
926 assert_eq!(parser.cache_len(), 2);
927
928 parser.clear_cache();
929 assert_eq!(parser.cache_len(), 0);
930 }
931
932 #[test]
933 fn test_incremental_parser_different_files() {
934 let parser = IncrementalParser::with_default_capacity();
935 let plugin = MockPlugin;
936 let path1 = PathBuf::from("/test/file1.rs");
937 let path2 = PathBuf::from("/test/file2.rs");
938
939 parser.parse(&plugin, &path1, b"fn foo() {}", None).unwrap();
941 parser.parse(&plugin, &path2, b"fn bar() {}", None).unwrap();
942
943 assert_eq!(parser.cache_len(), 2);
945
946 parser
948 .parse(&plugin, &path1, b"fn baz() {}", Some(b"fn foo() {}"))
949 .unwrap();
950
951 assert_eq!(parser.cache_len(), 2);
953 }
954
955 #[test]
958 fn test_acceptance_incremental_equals_full_parse() {
959 let parser = IncrementalParser::with_default_capacity();
960 let plugin = MockPlugin;
961 let path = PathBuf::from("/test/file.rs");
962
963 let original_content = b"fn foo() { let x = 1; }";
964 let modified_content = b"fn foo() { let x = 2; let y = 3; }";
965
966 let tree1 = parser
968 .parse(&plugin, &path, original_content, None)
969 .unwrap();
970
971 let tree2_incremental = parser
973 .parse(&plugin, &path, modified_content, Some(original_content))
974 .unwrap();
975
976 parser.clear_cache(); let tree2_full = parser
979 .parse(&plugin, &path, modified_content, None)
980 .unwrap();
981
982 assert_eq!(
984 tree2_incremental.root_node().to_sexp(),
985 tree2_full.root_node().to_sexp(),
986 "Incremental parsing MUST produce identical AST to full parsing"
987 );
988
989 assert!(!tree1.root_node().kind().is_empty());
991 assert!(!tree2_incremental.root_node().kind().is_empty());
992 assert!(!tree2_full.root_node().kind().is_empty());
993 }
994
995 #[test]
998 fn test_acceptance_fallback_on_incremental_failure() {
999 let parser = IncrementalParser::with_default_capacity();
1000 let plugin = MockPlugin;
1001 let path = PathBuf::from("/test/file.rs");
1002
1003 let original_content = b"fn foo() {}";
1004 let modified_content = b"fn bar() {}";
1005
1006 let tree1 = parser
1008 .parse(&plugin, &path, original_content, None)
1009 .unwrap();
1010 assert!(!tree1.root_node().kind().is_empty());
1011
1012 let corrupted_old_content = b"this is not the old content";
1015
1016 let result = parser.parse(
1017 &plugin,
1018 &path,
1019 modified_content,
1020 Some(corrupted_old_content),
1021 );
1022
1023 assert!(result.is_ok(), "Should fall back to full parse on mismatch");
1025 let tree2 = result.unwrap();
1026 assert!(!tree2.root_node().kind().is_empty());
1027
1028 assert_eq!(parser.cache_len(), 1);
1030 }
1031}