use super::helpers::allocations; use super::helpers::fixtures::get_language; use std::fmt::Write; use tree_sitter::{ Node, Parser, Query, QueryCapture, QueryCursor, QueryError, QueryMatch, QueryProperty, }; #[test] fn test_query_errors_on_invalid_syntax() { allocations::record(|| { let language = get_language("javascript"); assert!(Query::new(language, "(if_statement)").is_ok()); assert!(Query::new(language, "(if_statement condition:(identifier))").is_ok()); // Mismatched parens assert_eq!( Query::new(language, "(if_statement"), Err(QueryError::Syntax( [ "(if_statement", // " ^", ] .join("\n") )) ); assert_eq!( Query::new(language, "; comment 1\n; comment 2\n (if_statement))"), Err(QueryError::Syntax( [ " (if_statement))", // " ^", ] .join("\n") )) ); // Return an error at the *beginning* of a bare identifier not followed a colon. // If there's a colon but no pattern, return an error at the end of the colon. assert_eq!( Query::new(language, "(if_statement identifier)"), Err(QueryError::Syntax( [ "(if_statement identifier)", // " ^", ] .join("\n") )) ); assert_eq!( Query::new(language, "(if_statement condition:)"), Err(QueryError::Syntax( [ "(if_statement condition:)", // " ^", ] .join("\n") )) ); // Return an error at the beginning of an unterminated string. assert_eq!( Query::new(language, r#"(identifier) "h "#), Err(QueryError::Syntax( [ r#"(identifier) "h "#, // r#" ^"#, ] .join("\n") )) ); assert_eq!( Query::new(language, r#"((identifier) ()"#), Err(QueryError::Syntax( [ "((identifier) ()", // " ^", ] .join("\n") )) ); assert_eq!( Query::new(language, r#"((identifier) @x (eq? @x a"#), Err(QueryError::Syntax( [ r#"((identifier) @x (eq? @x a"#, r#" ^"#, ] .join("\n") )) ); }); } #[test] fn test_query_errors_on_invalid_symbols() { allocations::record(|| { let language = get_language("javascript"); assert_eq!( Query::new(language, "(clas)"), Err(QueryError::NodeType("clas".to_string())) ); assert_eq!( Query::new(language, "(if_statement (arrayyyyy))"), Err(QueryError::NodeType("arrayyyyy".to_string())) ); assert_eq!( Query::new(language, "(if_statement condition: (non_existent3))"), Err(QueryError::NodeType("non_existent3".to_string())) ); assert_eq!( Query::new(language, "(if_statement condit: (identifier))"), Err(QueryError::Field("condit".to_string())) ); assert_eq!( Query::new(language, "(if_statement conditioning: (identifier))"), Err(QueryError::Field("conditioning".to_string())) ); }); } #[test] fn test_query_errors_on_invalid_conditions() { allocations::record(|| { let language = get_language("javascript"); assert_eq!( Query::new(language, "((identifier) @id (@id))"), Err(QueryError::Predicate( "Expected predicate to start with a function name. Got @id.".to_string() )) ); assert_eq!( Query::new(language, "((identifier) @id (eq? @id))"), Err(QueryError::Predicate( "Wrong number of arguments to eq? predicate. Expected 2, got 1.".to_string() )) ); assert_eq!( Query::new(language, "((identifier) @id (eq? @id @ok))"), Err(QueryError::Capture("ok".to_string())) ); }); } #[test] fn test_query_matches_with_simple_pattern() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, "(function_declaration name: (identifier) @fn-name)", ) .unwrap(); let source = "function one() { two(); function three() {} }"; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query, source), &[ (0, vec![("fn-name", "one")]), (0, vec![("fn-name", "three")]) ], ); }); } #[test] fn test_query_matches_with_multiple_on_same_root() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, "(class_declaration name: (identifier) @the-class-name (class_body (method_definition name: (property_identifier) @the-method-name)))", ) .unwrap(); let source = " class Person { // the constructor constructor(name) { this.name = name; } // the getter getFullName() { return this.name; } } "; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query, source), &[ ( 0, vec![ ("the-class-name", "Person"), ("the-method-name", "constructor") ] ), ( 0, vec![ ("the-class-name", "Person"), ("the-method-name", "getFullName") ] ), ], ); }); } #[test] fn test_query_matches_with_multiple_patterns_different_roots() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, " (function_declaration name:(identifier) @fn-def) (call_expression function:(identifier) @fn-ref) ", ) .unwrap(); let source = " function f1() { f2(f3()); } "; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query, source), &[ (0, vec![("fn-def", "f1")]), (1, vec![("fn-ref", "f2")]), (1, vec![("fn-ref", "f3")]), ], ); }); } #[test] fn test_query_matches_with_multiple_patterns_same_root() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, " (pair key: (property_identifier) @method-def value: (function)) (pair key: (property_identifier) @method-def value: (arrow_function)) ", ) .unwrap(); let source = " a = { b: () => { return c; }, d: function() { return d; } }; "; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query, source), &[ (1, vec![("method-def", "b")]), (0, vec![("method-def", "d")]), ], ); }); } #[test] fn test_query_matches_with_nesting_and_no_fields() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, " (array (array (identifier) @x1 (identifier) @x2)) ", ) .unwrap(); let source = " [[a]]; [[c, d], [e, f, g, h]]; [[h], [i]]; "; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query, source), &[ (0, vec![("x1", "c"), ("x2", "d")]), (0, vec![("x1", "e"), ("x2", "f")]), (0, vec![("x1", "e"), ("x2", "g")]), (0, vec![("x1", "f"), ("x2", "g")]), (0, vec![("x1", "e"), ("x2", "h")]), (0, vec![("x1", "f"), ("x2", "h")]), (0, vec![("x1", "g"), ("x2", "h")]), ], ); }); } #[test] fn test_query_matches_with_many() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new(language, "(array (identifier) @element)").unwrap(); let source = "[hello];\n".repeat(50); let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(&source)); assert_eq!( collect_matches(matches, &query, source.as_str()), vec![(0, vec![("element", "hello")]); 50], ); }); } #[test] fn test_query_matches_in_language_with_simple_aliases() { allocations::record(|| { let language = get_language("html"); // HTML uses different tokens to track start tags names, end // tag names, script tag names, and style tag names. All of // these tokens are aliased to `tag_name`. let query = Query::new(language, "(tag_name) @tag").unwrap(); let source = "
"; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(&source)); assert_eq!( collect_matches(matches, &query, source), &[ (0, vec![("tag", "div")]), (0, vec![("tag", "script")]), (0, vec![("tag", "script")]), (0, vec![("tag", "style")]), (0, vec![("tag", "style")]), (0, vec![("tag", "div")]), ], ); }); } #[test] fn test_query_matches_with_too_many_permutations_to_track() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, " (array (identifier) @pre (identifier) @post) ", ) .unwrap(); let mut source = "hello, ".repeat(50); source.insert(0, '['); source.push_str("];"); let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(&source)); // For this pathological query, some match permutations will be dropped. // Just check that a subset of the results are returned, and crash or // leak occurs. assert_eq!( collect_matches(matches, &query, source.as_str())[0], (0, vec![("pre", "hello"), ("post", "hello")]), ); }); } #[test] fn test_query_matches_with_anonymous_tokens() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, r#" ";" @punctuation "&&" @operator "#, ) .unwrap(); let source = "foo(a && b);"; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query, source), &[ (1, vec![("operator", "&&")]), (0, vec![("punctuation", ";")]), ] ); }); } #[test] fn test_query_matches_within_byte_range() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new(language, "(identifier) @element").unwrap(); let source = "[a, b, c, d, e, f, g]"; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor .set_byte_range(5, 15) .matches(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query, source), &[ (0, vec![("element", "c")]), (0, vec![("element", "d")]), (0, vec![("element", "e")]), ] ); }); } #[test] fn test_query_matches_different_queries_same_cursor() { allocations::record(|| { let language = get_language("javascript"); let query1 = Query::new( language, " (array (identifier) @id1) ", ) .unwrap(); let query2 = Query::new( language, " (array (identifier) @id1) (pair (identifier) @id2) ", ) .unwrap(); let query3 = Query::new( language, " (array (identifier) @id1) (pair (identifier) @id2) (parenthesized_expression (identifier) @id3) ", ) .unwrap(); let source = "[a, {b: b}, (c)];"; let mut parser = Parser::new(); let mut cursor = QueryCursor::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let matches = cursor.matches(&query1, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query1, source), &[(0, vec![("id1", "a")]),] ); let matches = cursor.matches(&query3, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query3, source), &[ (0, vec![("id1", "a")]), (1, vec![("id2", "b")]), (2, vec![("id3", "c")]), ] ); let matches = cursor.matches(&query2, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query2, source), &[(0, vec![("id1", "a")]), (1, vec![("id2", "b")]),] ); }); } #[test] fn test_query_captures() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, r#" (pair key: * @method.def (function name: (identifier) @method.alias)) (variable_declarator name: * @function.def value: (function name: (identifier) @function.alias)) ":" @delimiter "=" @operator "#, ) .unwrap(); let source = " a({ bc: function de() { const fg = function hi() {} }, jk: function lm() { const no = function pq() {} }, }); "; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query, source), &[ (2, vec![("delimiter", ":")]), (0, vec![("method.def", "bc"), ("method.alias", "de")]), (3, vec![("operator", "=")]), (1, vec![("function.def", "fg"), ("function.alias", "hi")]), (2, vec![("delimiter", ":")]), (0, vec![("method.def", "jk"), ("method.alias", "lm")]), (3, vec![("operator", "=")]), (1, vec![("function.def", "no"), ("function.alias", "pq")]), ], ); let captures = cursor.captures(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_captures(captures, &query, source), &[ ("method.def", "bc"), ("delimiter", ":"), ("method.alias", "de"), ("function.def", "fg"), ("operator", "="), ("function.alias", "hi"), ("method.def", "jk"), ("delimiter", ":"), ("method.alias", "lm"), ("function.def", "no"), ("operator", "="), ("function.alias", "pq"), ] ); }); } #[test] fn test_query_captures_with_text_conditions() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, r#" ((identifier) @constant (match? @constant "^[A-Z]{2,}$")) ((identifier) @constructor (match? @constructor "^[A-Z]")) ((identifier) @function.builtin (eq? @function.builtin "require")) (identifier) @variable "#, ) .unwrap(); let source = " const ab = require('./ab'); new Cd(EF); "; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let captures = cursor.captures(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_captures(captures, &query, source), &[ ("variable", "ab"), ("function.builtin", "require"), ("variable", "require"), ("constructor", "Cd"), ("variable", "Cd"), ("constant", "EF"), ("constructor", "EF"), ("variable", "EF"), ], ); }); } #[test] fn test_query_captures_with_set_properties() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, r#" ((call_expression (identifier) @foo) (set! name something) (set! cool)) ((property_identifier) @bar (is? cool) (is-not? name something))"#, ) .unwrap(); assert_eq!( query.property_settings(0), &[ QueryProperty::new("name", Some("something"), None), QueryProperty::new("cool", None, None), ] ); assert_eq!(query.property_settings(1), &[]); assert_eq!(query.property_predicates(0), &[]); assert_eq!( query.property_predicates(1), &[ (QueryProperty::new("cool", None, None), true), (QueryProperty::new("name", Some("something"), None), false), ] ); }); } #[test] fn test_query_captures_with_duplicates() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, r#" (variable_declarator name: (identifier) @function value: (function)) (identifier) @variable "#, ) .unwrap(); let source = " var x = function() {}; "; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let captures = cursor.captures(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_captures(captures, &query, source), &[("function", "x"), ("variable", "x"),], ); }); } #[test] fn test_query_captures_with_many_nested_results_without_fields() { allocations::record(|| { let language = get_language("javascript"); // Search for key-value pairs whose values are anonymous functions. let query = Query::new( language, r#" (pair key: * @method-def (arrow_function)) ":" @colon "," @comma "#, ) .unwrap(); // The `pair` node for key `y` does not match any pattern, but inside of // its value, it contains many other `pair` nodes that do match the pattern. // The match for the *outer* pair should be terminated *before* descending into // the object value, so that we can avoid needing to buffer all of the inner // matches. let method_count = 50; let mut source = "x = { y: {\n".to_owned(); for i in 0..method_count { writeln!(&mut source, " method{}: $ => null,", i).unwrap(); } source.push_str("}};\n"); let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let captures = cursor.captures(&query, tree.root_node(), to_callback(&source)); let captures = collect_captures(captures, &query, &source); assert_eq!( &captures[0..13], &[ ("colon", ":"), ("method-def", "method0"), ("colon", ":"), ("comma", ","), ("method-def", "method1"), ("colon", ":"), ("comma", ","), ("method-def", "method2"), ("colon", ":"), ("comma", ","), ("method-def", "method3"), ("colon", ":"), ("comma", ","), ] ); // Ensure that we don't drop matches because of needing to buffer too many. assert_eq!(captures.len(), 1 + 3 * method_count); }); } #[test] fn test_query_captures_with_many_nested_results_with_fields() { allocations::record(|| { let language = get_language("javascript"); // Search expressions like `a ? a.b : null` let query = Query::new( language, r#" ((ternary_expression condition: (identifier) @left consequence: (member_expression object: (identifier) @right) alternative: (null)) (eq? @left @right)) "#, ) .unwrap(); // The outer expression does not match the pattern, but the consequence of the ternary // is an object that *does* contain many occurences of the pattern. let count = 50; let mut source = "a ? {".to_owned(); for i in 0..count { writeln!(&mut source, " x: y{} ? y{}.z : null,", i, i).unwrap(); } source.push_str("} : null;\n"); let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let captures = cursor.captures(&query, tree.root_node(), to_callback(&source)); let captures = collect_captures(captures, &query, &source); assert_eq!( &captures[0..20], &[ ("left", "y0"), ("right", "y0"), ("left", "y1"), ("right", "y1"), ("left", "y2"), ("right", "y2"), ("left", "y3"), ("right", "y3"), ("left", "y4"), ("right", "y4"), ("left", "y5"), ("right", "y5"), ("left", "y6"), ("right", "y6"), ("left", "y7"), ("right", "y7"), ("left", "y8"), ("right", "y8"), ("left", "y9"), ("right", "y9"), ] ); // Ensure that we don't drop matches because of needing to buffer too many. assert_eq!(captures.len(), 2 * count); }); } #[test] fn test_query_captures_ordered_by_both_start_and_end_positions() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, r#" (call_expression) @call (member_expression) @member (identifier) @variable "#, ) .unwrap(); let source = " a.b(c.d().e).f; "; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let captures = cursor.captures(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_captures(captures, &query, source), &[ ("member", "a.b(c.d().e).f"), ("call", "a.b(c.d().e)"), ("member", "a.b"), ("variable", "a"), ("member", "c.d().e"), ("call", "c.d()"), ("member", "c.d"), ("variable", "c"), ], ); }); } #[test] fn test_query_captures_with_matches_removed() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, r#" (binary_expression left: (identifier) @left operator: * @op right: (identifier) @right) "#, ) .unwrap(); let source = " a === b && c > d && e < f; "; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let mut captured_strings = Vec::new(); for (m, i) in cursor.captures(&query, tree.root_node(), to_callback(source)) { let capture = m.captures[i]; let text = capture.node.utf8_text(source.as_bytes()).unwrap(); if text == "a" { m.remove(); continue; } captured_strings.push(text); } assert_eq!(captured_strings, &["c", ">", "d", "e", "<", "f",]); }); } #[test] fn test_query_captures_and_matches_iterators_are_fused() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, r#" (comment) @comment "#, ) .unwrap(); let source = " // one // two // three /* unfinished "; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(&source, None).unwrap(); let mut cursor = QueryCursor::new(); let mut captures = cursor.captures(&query, tree.root_node(), to_callback(source)); assert_eq!(captures.next().unwrap().0.captures[0].index, 0); assert_eq!(captures.next().unwrap().0.captures[0].index, 0); assert_eq!(captures.next().unwrap().0.captures[0].index, 0); assert!(captures.next().is_none()); assert!(captures.next().is_none()); assert!(captures.next().is_none()); drop(captures); let mut matches = cursor.matches(&query, tree.root_node(), to_callback(source)); assert_eq!(matches.next().unwrap().captures[0].index, 0); assert_eq!(matches.next().unwrap().captures[0].index, 0); assert_eq!(matches.next().unwrap().captures[0].index, 0); assert!(matches.next().is_none()); assert!(matches.next().is_none()); assert!(matches.next().is_none()); }); } #[test] fn test_query_start_byte_for_pattern() { let language = get_language("javascript"); let patterns_1 = r#" "+" @operator "-" @operator "*" @operator "=" @operator "=>" @operator "# .trim_start(); let patterns_2 = " (identifier) @a (string) @b " .trim_start(); let patterns_3 = " ((identifier) @b (match? @b i)) (function_declaration name: (identifier) @c) (method_definition name: (identifier) @d) " .trim_start(); let mut source = String::new(); source += patterns_1; source += patterns_2; source += patterns_3; let query = Query::new(language, &source).unwrap(); assert_eq!(query.start_byte_for_pattern(0), 0); assert_eq!(query.start_byte_for_pattern(5), patterns_1.len()); assert_eq!( query.start_byte_for_pattern(7), patterns_1.len() + patterns_2.len() ); } #[test] fn test_query_capture_names() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, r#" (if_statement condition: (binary_expression left: * @left-operand operator: "||" right: * @right-operand) consequence: (statement_block) @body) (while_statement condition:* @loop-condition) "#, ) .unwrap(); assert_eq!( query.capture_names(), &[ "left-operand".to_string(), "right-operand".to_string(), "body".to_string(), "loop-condition".to_string(), ] ); }); } #[test] fn test_query_with_no_patterns() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new(language, "").unwrap(); assert!(query.capture_names().is_empty()); assert_eq!(query.pattern_count(), 0); }); } #[test] fn test_query_comments() { allocations::record(|| { let language = get_language("javascript"); let query = Query::new( language, " ; this is my first comment ; i have two comments here (function_declaration ; there is also a comment here ; and here name: (identifier) @fn-name)", ) .unwrap(); let source = "function one() { }"; let mut parser = Parser::new(); parser.set_language(language).unwrap(); let tree = parser.parse(source, None).unwrap(); let mut cursor = QueryCursor::new(); let matches = cursor.matches(&query, tree.root_node(), to_callback(source)); assert_eq!( collect_matches(matches, &query, source), &[(0, vec![("fn-name", "one")]),], ); }); } fn collect_matches<'a>( matches: impl Iterator>, query: &'a Query, source: &'a str, ) -> Vec<(usize, Vec<(&'a str, &'a str)>)> { matches .map(|m| { ( m.pattern_index, format_captures(m.captures.iter().cloned(), query, source), ) }) .collect() } fn collect_captures<'a>( captures: impl Iterator, usize)>, query: &'a Query, source: &'a str, ) -> Vec<(&'a str, &'a str)> { format_captures(captures.map(|(m, i)| m.captures[i]), query, source) } fn format_captures<'a>( captures: impl Iterator>, query: &'a Query, source: &'a str, ) -> Vec<(&'a str, &'a str)> { captures .map(|capture| { ( query.capture_names()[capture.index as usize].as_str(), capture.node.utf8_text(source.as_bytes()).unwrap(), ) }) .collect() } fn to_callback<'a>(source: &'a str) -> impl Fn(Node) -> &'a [u8] { move |n| &source.as_bytes()[n.byte_range()] }