use std::{env, fmt::Write, ops::ControlFlow, sync::LazyLock};

use indoc::indoc;
use rand::{SeedableRng, prelude::StdRng};
use streaming_iterator::StreamingIterator;
use tree_sitter::{
    CaptureQuantifier, InputEdit, Language, Node, Parser, Point, Query, QueryCursor,
    QueryCursorOptions, QueryCursorState, QueryError, QueryErrorKind, QueryPredicate,
    QueryPredicateArg, QueryProperty, Range,
};
use tree_sitter_generate::load_grammar_file;
use unindent::Unindent;

use super::helpers::{
    allocations,
    fixtures::{get_language, get_test_language},
    query_helpers::{Match, Pattern, assert_query_matches},
};
use crate::tests::{
    ITERATION_COUNT, generate_parser,
    helpers::{
        fixtures::get_test_fixture_language,
        query_helpers::{collect_captures, collect_matches},
    },
};

static EXAMPLE_FILTER: LazyLock<Option<String>> =
    LazyLock::new(|| env::var("TREE_SITTER_TEST_EXAMPLE_FILTER").ok());

#[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:(parenthesized_expression (identifier)))"
            )
            .is_ok()
        );

        // Mismatched parens
        assert_eq!(
            Query::new(&language, "(if_statement").unwrap_err().message,
            [
                "(if_statement", //
                "             ^",
            ]
            .join("\n")
        );
        assert_eq!(
            Query::new(&language, "; comment 1\n; comment 2\n  (if_statement))")
                .unwrap_err()
                .message,
            [
                "  (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)")
                .unwrap_err()
                .message,
            [
                "(if_statement identifier)", //
                "              ^",
            ]
            .join("\n")
        );
        assert_eq!(
            Query::new(&language, "(if_statement condition:)")
                .unwrap_err()
                .message,
            [
                "(if_statement condition:)", //
                "                        ^",
            ]
            .join("\n")
        );

        // Return an error at the beginning of an unterminated string.
        assert_eq!(
            Query::new(&language, r#"(identifier) "h "#)
                .unwrap_err()
                .message,
            [
                r#"(identifier) "h "#, //
                r"             ^",
            ]
            .join("\n")
        );

        // Empty tree pattern
        assert_eq!(
            Query::new(&language, r"((identifier) ()")
                .unwrap_err()
                .message,
            [
                "((identifier) ()", //
                "               ^",
            ]
            .join("\n")
        );

        // Empty alternation
        assert_eq!(
            Query::new(&language, r"((identifier) [])")
                .unwrap_err()
                .message,
            [
                "((identifier) [])", //
                "               ^",
            ]
            .join("\n")
        );

        // Unclosed sibling expression with predicate
        assert_eq!(
            Query::new(&language, r"((identifier) (#a?)")
                .unwrap_err()
                .message,
            [
                "((identifier) (#a?)", //
                "                   ^",
            ]
            .join("\n")
        );

        // Predicate not ending in `?` or `!`
        assert_eq!(
            Query::new(&language, r"((identifier) (#a))")
                .unwrap_err()
                .message,
            [
                "((identifier) (#a))", //
                "                 ^",
            ]
            .join("\n")
        );

        // Unclosed predicate
        assert_eq!(
            Query::new(&language, r"((identifier) @x (#eq? @x a")
                .unwrap_err()
                .message,
            [
                r"((identifier) @x (#eq? @x a",
                r"                           ^",
            ]
            .join("\n")
        );

        // Need at least one child node for a child anchor
        assert_eq!(
            Query::new(&language, r"(statement_block .)")
                .unwrap_err()
                .message,
            [
                //
                r"(statement_block .)",
                r"                  ^"
            ]
            .join("\n")
        );

        // Need a field name after a negated field operator
        assert_eq!(
            Query::new(&language, r"(statement_block ! (if_statement))")
                .unwrap_err()
                .message,
            [
                r"(statement_block ! (if_statement))",
                r"                   ^"
            ]
            .join("\n")
        );

        // Unclosed alternation within a tree
        // tree-sitter/tree-sitter/issues/968
        assert_eq!(
            Query::new(&get_language("c"), r#"(parameter_list [ ")" @foo)"#)
                .unwrap_err()
                .message,
            [
                r#"(parameter_list [ ")" @foo)"#,
                r"                          ^"
            ]
            .join("\n")
        );

        // Unclosed tree within an alternation
        // tree-sitter/tree-sitter/issues/1436
        assert_eq!(
            Query::new(
                &get_language("python"),
                r"[(unary_operator (_) @operand) (not_operator (_) @operand]"
            )
            .unwrap_err()
            .message,
            [
                r"[(unary_operator (_) @operand) (not_operator (_) @operand]",
                r"                                                         ^"
            ]
            .join("\n")
        );

        // MISSING keyword with full pattern
        assert_eq!(
            Query::new(
                &get_language("c"),
                r"(MISSING (function_declarator (identifier))) "
            )
            .unwrap_err()
            .message,
            [
                r"(MISSING (function_declarator (identifier))) ",
                r"         ^",
            ]
            .join("\n")
        );

        // MISSING keyword with multiple identifiers
        assert_eq!(
            Query::new(
                &get_language("c"),
                r"(MISSING function_declarator function_declarator) "
            )
            .unwrap_err()
            .message,
            [
                r"(MISSING function_declarator function_declarator) ",
                r"                             ^",
            ]
            .join("\n")
        );
        assert_eq!(
            Query::new(&language, "(statement / export_statement)").unwrap_err(),
            QueryError {
                row: 0,
                offset: 11,
                column: 11,
                kind: QueryErrorKind::Syntax,
                message: [
                    "(statement / export_statement)", //
                    "           ^"
                ]
                .join("\n")
            }
        );
    });
}

#[test]
fn test_query_errors_on_anchor_at_group_edge() {
    allocations::record(|| {
        let language = get_language("javascript");

        // Anchors between siblings, or at the first/last position of a *node*
        // pattern, are valid.
        assert!(Query::new(&language, "((_) . (_))").is_ok());
        assert!(Query::new(&language, "(program (_) (_) .)").is_ok());
        assert!(Query::new(&language, "(program (_)* @x . (_))").is_ok());

        // A `.` at the edge of a *group* is rejected. A group is not a node, so it
        // has no last child to anchor, and there is no sibling within the group to
        // anchor to.
        assert_eq!(
            Query::new(&language, "((_) .)").unwrap_err(),
            QueryError {
                row: 0,
                offset: 5,
                column: 5,
                kind: QueryErrorKind::Syntax,
                message: [
                    "((_) .)", //
                    "     ^"
                ]
                .join("\n")
            }
        );
        assert_eq!(
            Query::new(&language, "(program ((_)+ .)? (_))").unwrap_err(),
            QueryError {
                row: 0,
                offset: 15,
                column: 15,
                kind: QueryErrorKind::Syntax,
                message: [
                    "(program ((_)+ .)? (_))", //
                    "               ^"
                ]
                .join("\n")
            }
        );
    });
}

#[test]
fn test_query_errors_on_invalid_symbols() {
    allocations::record(|| {
        let language = get_language("javascript");

        assert_eq!(
            Query::new(&language, "\">>>>\"").unwrap_err(),
            QueryError {
                row: 0,
                offset: 1,
                column: 1,
                kind: QueryErrorKind::NodeType,
                message: "\">>>>\"".to_string()
            }
        );
        assert_eq!(
            Query::new(&language, "\"te\\\"st\"").unwrap_err(),
            QueryError {
                row: 0,
                offset: 1,
                column: 1,
                kind: QueryErrorKind::NodeType,
                message: "\"te\\\"st\"".to_string()
            }
        );
        assert_eq!(
            Query::new(&language, "\"\\\\\" @cap").unwrap_err(),
            QueryError {
                row: 0,
                offset: 1,
                column: 1,
                kind: QueryErrorKind::NodeType,
                message: "\"\\\\\"".to_string()
            }
        );
        assert_eq!(
            Query::new(&language, "(clas)").unwrap_err(),
            QueryError {
                row: 0,
                offset: 1,
                column: 1,
                kind: QueryErrorKind::NodeType,
                message: "\"clas\"".to_string()
            }
        );
        assert_eq!(
            Query::new(&language, "(if_statement (arrayyyyy))").unwrap_err(),
            QueryError {
                row: 0,
                offset: 15,
                column: 15,
                kind: QueryErrorKind::NodeType,
                message: "\"arrayyyyy\"".to_string()
            },
        );
        assert_eq!(
            Query::new(&language, "(if_statement condition: (non_existent3))").unwrap_err(),
            QueryError {
                row: 0,
                offset: 26,
                column: 26,
                kind: QueryErrorKind::NodeType,
                message: "\"non_existent3\"".to_string()
            },
        );
        assert_eq!(
            Query::new(&language, "(if_statement condit: (identifier))").unwrap_err(),
            QueryError {
                row: 0,
                offset: 14,
                column: 14,
                kind: QueryErrorKind::Field,
                message: "\"condit\"".to_string()
            },
        );
        assert_eq!(
            Query::new(&language, "(if_statement conditioning: (identifier))").unwrap_err(),
            QueryError {
                row: 0,
                offset: 14,
                column: 14,
                kind: QueryErrorKind::Field,
                message: "\"conditioning\"".to_string()
            }
        );
        assert_eq!(
            Query::new(&language, "(if_statement !alternativ)").unwrap_err(),
            QueryError {
                row: 0,
                offset: 15,
                column: 15,
                kind: QueryErrorKind::Field,
                message: "\"alternativ\"".to_string()
            }
        );
        assert_eq!(
            Query::new(&language, "(if_statement !alternatives)").unwrap_err(),
            QueryError {
                row: 0,
                offset: 15,
                column: 15,
                kind: QueryErrorKind::Field,
                message: "\"alternatives\"".to_string()
            }
        );
        assert_eq!(
            Query::new(&language, "fakefield: (identifier)").unwrap_err(),
            QueryError {
                row: 0,
                offset: 0,
                column: 0,
                kind: QueryErrorKind::Field,
                message: "\"fakefield\"".to_string()
            }
        );
        assert_eq!(
            Query::new(&language, "(MISS)").unwrap_err(),
            QueryError {
                row: 0,
                offset: 1,
                column: 1,
                kind: QueryErrorKind::NodeType,
                message: "\"MISS\"".to_string(),
            }
        );
    });
}

#[test]
fn test_query_errors_on_invalid_predicates() {
    allocations::record(|| {
        let language = get_language("javascript");

        assert_eq!(
            Query::new(&language, "((identifier) @id (@id))").unwrap_err(),
            QueryError {
                kind: QueryErrorKind::Syntax,
                row: 0,
                column: 19,
                offset: 19,
                message: [
                    "((identifier) @id (@id))", //
                    "                   ^"
                ]
                .join("\n")
            }
        );
        assert_eq!(
            Query::new(&language, "((identifier) @id (#eq? @id))").unwrap_err(),
            QueryError {
                kind: QueryErrorKind::Predicate,
                row: 0,
                column: 0,
                offset: 0,
                message: "Wrong number of arguments to #eq? predicate. Expected 2, got 1."
                    .to_string()
            }
        );
        assert_eq!(
            Query::new(&language, "((identifier) @id (#eq? @id @ok))").unwrap_err(),
            QueryError {
                kind: QueryErrorKind::Capture,
                row: 0,
                column: 29,
                offset: 29,
                message: "\"ok\"".to_string(),
            }
        );
    });
}

#[test]
fn test_query_errors_on_impossible_patterns() {
    let js_lang = get_language("javascript");
    let rb_lang = get_language("ruby");

    allocations::record(|| {
        assert_eq!(
            Query::new(
                &js_lang,
                "(binary_expression left: (expression (identifier)) left: (expression (identifier)))"
            ),
            Err(QueryError {
                kind: QueryErrorKind::Structure,
                row: 0,
                offset: 37,
                column: 37,
                message: [
                    "(binary_expression left: (expression (identifier)) left: (expression (identifier)))",
                    "                                     ^",
                ]
                .join("\n"),
            })
        );

        Query::new(
            &js_lang,
            "(function_declaration name: (identifier) (statement_block))",
        )
        .unwrap();
        assert_eq!(
            Query::new(&js_lang, "(function_declaration name: (statement_block))"),
            Err(QueryError {
                kind: QueryErrorKind::Structure,
                row: 0,
                offset: 22,
                column: 22,
                message: [
                    "(function_declaration name: (statement_block))",
                    "                      ^",
                ]
                .join("\n")
            })
        );

        Query::new(&rb_lang, "(call receiver:(call))").unwrap();
        assert_eq!(
            Query::new(&rb_lang, "(call receiver:(binary))"),
            Err(QueryError {
                kind: QueryErrorKind::Structure,
                row: 0,
                offset: 6,
                column: 6,
                message: [
                    "(call receiver:(binary))", //
                    "      ^",
                ]
                .join("\n")
            })
        );

        Query::new(
            &js_lang,
            "[
                (function_expression (identifier))
                (function_declaration (identifier))
                (generator_function_declaration (identifier))
            ]",
        )
        .unwrap();
        assert_eq!(
            Query::new(
                &js_lang,
                "[
                    (function_expression (identifier))
                    (function_declaration (object))
                    (generator_function_declaration (identifier))
                ]",
            ),
            Err(QueryError {
                kind: QueryErrorKind::Structure,
                row: 2,
                offset: 99,
                column: 42,
                message: [
                    "                    (function_declaration (object))", //
                    "                                          ^",
                ]
                .join("\n")
            })
        );

        assert_eq!(
            Query::new(&js_lang, "(identifier (identifier))",),
            Err(QueryError {
                kind: QueryErrorKind::Structure,
                row: 0,
                offset: 12,
                column: 12,
                message: [
                    "(identifier (identifier))", //
                    "            ^",
                ]
                .join("\n")
            })
        );
        assert_eq!(
            Query::new(&js_lang, "(true (true))",),
            Err(QueryError {
                kind: QueryErrorKind::Structure,
                row: 0,
                offset: 6,
                column: 6,
                message: [
                    "(true (true))", //
                    "      ^",
                ]
                .join("\n")
            })
        );

        Query::new(
            &js_lang,
            "(if_statement
                condition: (parenthesized_expression (expression) @cond))",
        )
        .unwrap();

        assert_eq!(
            Query::new(&js_lang, "(if_statement condition: (expression))"),
            Err(QueryError {
                kind: QueryErrorKind::Structure,
                row: 0,
                offset: 14,
                column: 14,
                message: [
                    "(if_statement condition: (expression))", //
                    "              ^",
                ]
                .join("\n")
            })
        );
        assert_eq!(
            Query::new(&js_lang, "(identifier/identifier)").unwrap_err(),
            QueryError {
                row: 0,
                offset: 0,
                column: 0,
                kind: QueryErrorKind::Structure,
                message: [
                    "(identifier/identifier)", //
                    "^"
                ]
                .join("\n")
            }
        );

        if js_lang.abi_version() >= 15 {
            assert_eq!(
                Query::new(&js_lang, "(statement/identifier)").unwrap_err(),
                QueryError {
                    row: 0,
                    offset: 0,
                    column: 0,
                    kind: QueryErrorKind::Structure,
                    message: [
                        "(statement/identifier)", //
                        "^"
                    ]
                    .join("\n")
                }
            );
            assert_eq!(
                Query::new(&js_lang, "(statement/pattern)").unwrap_err(),
                QueryError {
                    row: 0,
                    offset: 0,
                    column: 0,
                    kind: QueryErrorKind::Structure,
                    message: [
                        "(statement/pattern)", //
                        "^"
                    ]
                    .join("\n")
                }
            );
        }
    });
}

#[test]
fn test_query_verifies_possible_patterns_with_aliased_parent_nodes() {
    allocations::record(|| {
        let language = get_language("ruby");

        Query::new(&language, "(destructured_parameter (identifier))").unwrap();

        assert_eq!(
            Query::new(&language, "(destructured_parameter (string))",),
            Err(QueryError {
                kind: QueryErrorKind::Structure,
                row: 0,
                offset: 24,
                column: 24,
                message: [
                    "(destructured_parameter (string))", //
                    "                        ^",
                ]
                .join("\n")
            })
        );
    });
}

#[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();

        assert_query_matches(
            &language,
            &query,
            "function one() { two(); function three() {} }",
            &[
                (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();

        assert_query_matches(
            &language,
            &query,
            "
            class Person {
                // the constructor
                constructor(name) { this.name = name; }

                // the getter
                getFullName() { return this.name; }
            }
            ",
            &[
                (
                    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();

        assert_query_matches(
            &language,
            &query,
            "
            function f1() {
                f2(f3());
            }
            ",
            &[
                (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_expression))

              (pair
                key: (property_identifier) @method-def
                value: (arrow_function))
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            a = {
                b: () => { return c; },
                d: function() { return d; }
            };
            ",
            &[
                (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();

        assert_query_matches(
            &language,
            &query,
            "
            [[a]];
            [[c, d], [e, f, g, h]];
            [[h], [i]];
            ",
            &[
                (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_results() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(&language, "(array (identifier) @element)").unwrap();

        assert_query_matches(
            &language,
            &query,
            &"[hello];\n".repeat(50),
            &vec![(0, vec![("element", "hello")]); 50],
        );
    });
}

#[test]
fn test_query_matches_with_many_overlapping_results() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            (call_expression
                function: (member_expression
                    property: (property_identifier) @method))
            (call_expression
                function: (identifier) @function)
            ((identifier) @constant
             (#match? @constant "[A-Z\\d_]+"))
            "#,
        )
        .unwrap();

        let count = 1024;

        // Deeply nested chained function calls:
        // a
        //    .foo(bar(BAZ))
        //    .foo(bar(BAZ))
        //    .foo(bar(BAZ))
        //    ...
        let source = format!("a{}", "\n  .foo(bar(BAZ))".repeat(count));

        assert_query_matches(
            &language,
            &query,
            &source,
            &[
                (0, vec![("method", "foo")]),
                (1, vec![("function", "bar")]),
                (2, vec![("constant", "BAZ")]),
            ]
            .iter()
            .cloned()
            .cycle()
            .take(3 * count)
            .collect::<Vec<_>>(),
        );
    });
}

#[test]
fn test_query_matches_capturing_error_nodes() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "
            (ERROR (identifier) @the-error-identifier) @the-error
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "function a(b,, c, d :e:) {}",
            &[(0, vec![("the-error", ":e:"), ("the-error-identifier", "e")])],
        );
    });
}

#[test]
fn test_query_matches_capturing_missing_nodes() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            (MISSING
              ; Comments should be valid
            ) @missing
            (MISSING
              ; Comments should be valid
              ";"
              ; Comments should be valid
              ) @missing-semicolon
            "#,
        )
        .unwrap();

        // Missing anonymous nodes
        assert_query_matches(
            &language,
            &query,
            "
            x = function(a) { b; } function(c) { d; }
            //                    ^ MISSING semicolon here
            ",
            &[
                (0, vec![("missing", "")]),
                (1, vec![("missing-semicolon", "")]),
            ],
        );

        let language = get_language("c");
        let query = Query::new(
            &language,
            "(MISSING field_identifier) @missing-field-ident
            (MISSING identifier) @missing-ident
            (MISSING) @missing-anything",
        )
        .unwrap();

        // Missing named nodes
        assert_query_matches(
            &language,
            &query,
            "
            int main() {
              if (a.) {
              //    ^ MISSING field_identifier here
                b();
                c();

                if (*) d();
                //   ^ MISSING identifier here
              }
            }
            ",
            &[
                (0, vec![("missing-field-ident", "")]),
                (2, vec![("missing-anything", "")]),
                (1, vec![("missing-ident", "")]),
                (2, vec![("missing-anything", "")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_extra_children() {
    allocations::record(|| {
        let language = get_language("ruby");
        let query = Query::new(
            &language,
            "
            (program(comment) @top_level_comment)
            (argument_list (heredoc_body) @heredoc_in_args)
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            # top-level
            puts(
                # not-top-level
                <<-IN_ARGS, bar.baz
                HELLO
                IN_ARGS
            )

            puts <<-NOT_IN_ARGS
            NO
            NOT_IN_ARGS
            ",
            &[
                (0, vec![("top_level_comment", "# top-level")]),
                (
                    1,
                    vec![(
                        "heredoc_in_args",
                        "\n                HELLO\n                IN_ARGS",
                    )],
                ),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_named_wildcard() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "
            (return_statement (_) @the-return-value)
            (binary_expression operator: _ @the-operator)
            ",
        )
        .unwrap();

        let source = "return a + b - c;";

        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(), source.as_bytes());

        assert_eq!(
            collect_matches(matches, &query, source),
            &[
                (0, vec![("the-return-value", "a + b - c")]),
                (1, vec![("the-operator", "+")]),
                (1, vec![("the-operator", "-")]),
            ]
        );
    });
}

#[test]
fn test_query_matches_with_wildcard_at_the_root() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "
            (_
                (comment) @doc
                .
                (function_declaration
                    name: (identifier) @name))
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "/* one */ var x; /* two */ function y() {} /* three */ class Z {}",
            &[(0, vec![("doc", "/* two */"), ("name", "y")])],
        );

        let query = Query::new(
            &language,
            "
                (_ (string) @a)
                (_ (number) @b)
                (_ (true) @c)
                (_ (false) @d)
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "['hi', x(true), {y: false}]",
            &[
                (0, vec![("a", "'hi'")]),
                (2, vec![("c", "true")]),
                (3, vec![("d", "false")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_wildcard_within_wildcard() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "
            (_ (_) @child) @parent
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "/* a */ b; c;",
            &[
                (0, vec![("parent", "/* a */ b; c;"), ("child", "/* a */")]),
                (0, vec![("parent", "/* a */ b; c;"), ("child", "b;")]),
                (0, vec![("parent", "b;"), ("child", "b")]),
                (0, vec![("parent", "/* a */ b; c;"), ("child", "c;")]),
                (0, vec![("parent", "c;"), ("child", "c")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_immediate_siblings() {
    allocations::record(|| {
        let language = get_language("python");

        // The immediate child operator '.' can be used in three similar ways:
        // 1. Before the first child node in a pattern, it means that there cannot be any named
        //    siblings before that child node.
        // 2. After the last child node in a pattern, it means that there cannot be any named
        //    sibling after that child node.
        // 3. Between two child nodes in a pattern, it specifies that there cannot be any named
        //    siblings between those two child nodes.
        let query = Query::new(
            &language,
            "
            (dotted_name
                (identifier) @parent
                .
                (identifier) @child)
            (dotted_name
                (identifier) @last-child
                .)
            (list
                .
                (_) @first-element)
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "import a.b.c.d; return [w, [1, y], z]",
            &[
                (0, vec![("parent", "a"), ("child", "b")]),
                (0, vec![("parent", "b"), ("child", "c")]),
                (0, vec![("parent", "c"), ("child", "d")]),
                (1, vec![("last-child", "d")]),
                (2, vec![("first-element", "w")]),
                (2, vec![("first-element", "1")]),
            ],
        );

        let query = Query::new(
            &language,
            "
            (block . (_) @first-stmt)
            (block (_) @stmt)
            (block (_) @last-stmt .)
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            if a:
                b()
                c()
                if d(): e(); f()
                g()
            ",
            &[
                (0, vec![("first-stmt", "b()")]),
                (1, vec![("stmt", "b()")]),
                (1, vec![("stmt", "c()")]),
                (1, vec![("stmt", "if d(): e(); f()")]),
                (0, vec![("first-stmt", "e()")]),
                (1, vec![("stmt", "e()")]),
                (1, vec![("stmt", "f()")]),
                (2, vec![("last-stmt", "f()")]),
                (1, vec![("stmt", "g()")]),
                (2, vec![("last-stmt", "g()")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_anchor_after_zero_quantifier() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "(program (comment)* @doc . (function_declaration name: (identifier) @name))",
        )
        .unwrap();

        // No comments and the function is not the first child. An anchor after a
        // zero-matched quantifier is vacuous, so the function still matches.
        assert_query_matches(
            &language,
            &query,
            "
class X {}
function foo() {}
",
            &[(0, vec![("name", "foo")])],
        );

        // With at least one comment the anchor applies, so the comments must
        // immediately precede the function.
        assert_query_matches(
            &language,
            &query,
            "
// c
function foo() {}
",
            &[(0, vec![("doc", "// c"), ("name", "foo")])],
        );
    });
}

#[test]
fn test_query_matches_with_anchor_after_nested_zero_quantifier() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            (_
              (field_definition
                property: (_) @name
                value: (_)? @value
              ) @field
              .
              ";" @semicolon
            )
            "#,
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "class Foo { bar; baz = 0; }",
            &[
                (
                    0,
                    vec![("field", "bar"), ("name", "bar"), ("semicolon", ";")],
                ),
                (
                    0,
                    vec![
                        ("field", "baz = 0"),
                        ("name", "baz"),
                        ("value", "0"),
                        ("semicolon", ";"),
                    ],
                ),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_last_child_anchor_after_optional() {
    allocations::record(|| {
        let language = get_language("c");
        let query = Query::new(
            &language,
            "(preproc_if (preproc_def)+ @def . (preproc_else)? @else .)",
        )
        .unwrap();

        // The optional `(preproc_else)?` is absent, so the trailing anchor's
        // last-child requirement transfers to the last `preproc_def`. A trailing
        // comment means the def is not the last child, so nothing matches.
        assert_query_matches(
            &language,
            &query,
            "
#if X
#define A
// c
#endif
",
            &[],
        );

        // With the def as the last child, the (else-less) match is allowed.
        assert_query_matches(
            &language,
            &query,
            "
#if X
#define A
#endif
",
            &[(0, vec![("def", "#define A\n")])],
        );
    });
}

#[test]
fn test_query_matches_with_anchors_on_both_sides_of_zero_quantifier() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "(program (lexical_declaration) @a . (comment)* . (function_declaration) @b)",
        )
        .unwrap();

        // Anchors on both sides of a zero-matched quantifier collapse into a single
        // adjacency constraint: with no comments, the declaration must be immediately
        // followed by the function.
        assert_query_matches(
            &language,
            &query,
            "
const a = 1;
const b = 2;
function foo() {}
",
            &[(0, vec![("a", "const b = 2;"), ("b", "function foo() {}")])],
        );

        // With a comment present the quantifier is non-zero, so the anchors apply
        // normally: the comment must sit immediately between the declaration and the
        // function.
        assert_query_matches(
            &language,
            &query,
            "
const b = 2;
// c
function foo() {}
",
            &[(0, vec![("a", "const b = 2;"), ("b", "function foo() {}")])],
        );
    });
}

#[test]
fn test_query_matches_with_leading_anchor_before_zero_quantifier() {
    allocations::record(|| {
        let language = get_language("c");
        let query = Query::new(
            &language,
            "(translation_unit . (comment)* (function_definition) @f)",
        )
        .unwrap();

        // The leading `.` anchors the comment run to the parent's first child. When the
        // run matches zero comments, that first-child requirement transfers to the
        // function, so it matches only when it is itself the first child.
        assert_query_matches(
            &language,
            &query,
            "
int main() {}
",
            &[(0, vec![("f", "int main() {}")])],
        );

        // The function is the second child, so with no leading comments it must not match.
        assert_query_matches(
            &language,
            &query,
            "
int a;
int main() {}
",
            &[],
        );

        // With a leading comment the run starts at the first child and the function follows.
        assert_query_matches(
            &language,
            &query,
            "
// c
int main() {}
",
            &[(0, vec![("f", "int main() {}")])],
        );
    });
}

#[test]
fn test_query_matches_with_last_named_child() {
    allocations::record(|| {
        let language = get_language("c");
        let query = Query::new(
            &language,
            "(compound_statement
                (_)
                (_)
                (expression_statement
                    (identifier) @last_id) .)",
        )
        .unwrap();
        assert_query_matches(
            &language,
            &query,
            "
            void one() { a; b; c; }
            void two() { d; e; }
            void three() { f; g; h; i; }
            ",
            &[(0, vec![("last_id", "c")]), (0, vec![("last_id", "i")])],
        );
    });
}

#[test]
fn test_query_matches_with_negated_fields() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "
            (import_specifier
                !alias
                name: (identifier) @import_name)

            (export_specifier
                !alias
                name: (identifier) @export_name)

            (export_statement
                !decorator
                !source
                (_) @exported)

            ; This negated field list is an extension of a previous
            ; negated field list. The order of the children and negated
            ; fields doesn't matter.
            (export_statement
                !decorator
                !source
                (_) @exported_expr
                !declaration)

            ; This negated field list is a prefix of a previous
            ; negated field list.
            (export_statement
                !decorator
                (_) @export_child .)
            ",
        )
        .unwrap();
        assert_query_matches(
            &language,
            &query,
            "
            import {a as b, c} from 'p1';
            export {g, h as i} from 'p2';

            @foo
            export default 1;

            export var j = 1;

            export default k;
            ",
            &[
                (0, vec![("import_name", "c")]),
                (1, vec![("export_name", "g")]),
                (4, vec![("export_child", "'p2'")]),
                (2, vec![("exported", "var j = 1;")]),
                (4, vec![("export_child", "var j = 1;")]),
                (2, vec![("exported", "k")]),
                (3, vec![("exported_expr", "k")]),
                (4, vec![("export_child", "k")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_field_at_root() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(&language, "name: (identifier) @name").unwrap();
        assert_query_matches(
            &language,
            &query,
            "
            a();
            function b() {}
            class c extends d {}
            ",
            &[(0, vec![("name", "b")]), (0, vec![("name", "c")])],
        );
    });
}

#[test]
fn test_query_matches_with_repeated_leaf_nodes() {
    allocations::record(|| {
        let language = get_language("javascript");

        let query = Query::new(
            &language,
            "
            (
                (comment)+ @doc
                .
                (class_declaration
                    name: (identifier) @name)
            )

            (
                (comment)+ @doc
                .
                (function_declaration
                    name: (identifier) @name)
            )
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            // one
            // two
            a();

            // three
            {
                // four
                // five
                // six
                class B {}

                // seven
                c();

                // eight
                function d() {}
            }
            ",
            &[
                (
                    0,
                    vec![
                        ("doc", "// four"),
                        ("doc", "// five"),
                        ("doc", "// six"),
                        ("name", "B"),
                    ],
                ),
                (1, vec![("doc", "// eight"), ("name", "d")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_optional_capture_before_uncaptured_required_sibling() {
    allocations::record(|| {
        let language = get_language("rust");
        let query = Query::new(&language, "(block (line_comment)? @doc (line_comment))").unwrap();

        // The optional `(line_comment)? @doc` before an *uncaptured* required
        // `(line_comment)` yields two candidate completions at the block:
        //     - one where the optional captured `// a` (the required node is then `// b`),
        //     - one where the optional matched zero (the required node absorbs `// a`,
        //     leaving `@doc` unbound).
        // The zero-match completion's captures are a strict subset of the other's, so the
        // longest-match rule must drop it (there is exactly one match). This regressed when
        // the dedup pass gained an early-break. Without the accompanying capture-position
        // sort, the break skips the subset "loser" and it leaks as an extra empty match.
        assert_query_matches(
            &language,
            &query,
            "
            fn f() {
                // a
                // b
            }
            ",
            &[(0, vec![("doc", "// a")])],
        );
    });
}

#[test]
fn test_query_matches_with_optional_nodes_inside_of_repetitions() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(&language, r#"(array (","? (number) @num)+)"#).unwrap();

        assert_query_matches(
            &language,
            &query,
            r"
            var a = [1, 2, 3, 4]
            ",
            &[(
                0,
                vec![("num", "1"), ("num", "2"), ("num", "3"), ("num", "4")],
            )],
        );
    });
}

#[test]
fn test_query_matches_with_top_level_repetitions() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r"
            (comment)+ @doc
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            r"
            // a
            // b
            // c

            d()

            // e
            ",
            &[
                (0, vec![("doc", "// a"), ("doc", "// b"), ("doc", "// c")]),
                (0, vec![("doc", "// e")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_non_terminal_repetitions_within_root() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(&language, "(_ (expression_statement (identifier) @id)+)").unwrap();

        assert_query_matches(
            &language,
            &query,
            r"
            function f() {
                d;
                e;
                f;
                g;
            }
            a;
            b;
            c;
            ",
            &[
                (0, vec![("id", "d"), ("id", "e"), ("id", "f"), ("id", "g")]),
                (0, vec![("id", "a"), ("id", "b"), ("id", "c")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_nested_repetitions() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            (variable_declaration
                (","? (variable_declarator name: (identifier) @x))+)+
            "#,
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            r"
            var a = b, c, d
            var e, f

            // more
            var g
            ",
            &[
                (
                    0,
                    vec![("x", "a"), ("x", "c"), ("x", "d"), ("x", "e"), ("x", "f")],
                ),
                (0, vec![("x", "g")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_multiple_repetition_patterns_that_intersect_other_pattern() {
    allocations::record(|| {
        let language = get_language("javascript");

        // When this query sees a comment, it must keep track of several potential
        // matches: up to two for each pattern that begins with a comment.
        let query = Query::new(
            &language,
            r"
            (call_expression
                function: (member_expression
                    property: (property_identifier) @name)) @ref.method

            ((comment)* @doc (function_declaration))
            ((comment)* @doc (generator_function_declaration))
            ((comment)* @doc (class_declaration))
            ((comment)* @doc (lexical_declaration))
            ((comment)* @doc (variable_declaration))
            ((comment)* @doc (method_definition))

            (comment) @comment
            ",
        )
        .unwrap();

        // Here, a series of comments occurs in the middle of a match of the first
        // pattern. To avoid exceeding the storage limits and discarding that outer
        // match, the comment-related matches need to be managed efficiently.
        let source = format!(
            "theObject\n{}\n.theMethod()",
            "  // the comment\n".repeat(64)
        );

        assert_query_matches(
            &language,
            &query,
            &source,
            &vec![(7, vec![("comment", "// the comment")]); 64]
                .into_iter()
                .chain(vec![(
                    0,
                    vec![("ref.method", source.as_str()), ("name", "theMethod")],
                )])
                .collect::<Vec<_>>(),
        );
    });
}

#[test]
fn test_query_matches_with_trailing_repetitions_of_last_child() {
    allocations::record(|| {
        let language = get_language("javascript");

        let query = Query::new(
            &language,
            "
            (unary_expression (primary_expression)+ @operand)
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            a = typeof (!b && ~c);
            ",
            &[
                (0, vec![("operand", "b")]),
                (0, vec![("operand", "c")]),
                (0, vec![("operand", "(!b && ~c)")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_leading_zero_or_more_repeated_leaf_nodes() {
    allocations::record(|| {
        let language = get_language("javascript");

        let query = Query::new(
            &language,
            "
            (
                (comment)* @doc
                .
                (function_declaration
                    name: (identifier) @name)
            )
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            function a() {
                // one
                var b;

                function c() {}

                // two
                // three
                var d;

                // four
                // five
                function e() {

                }
            }

            // six
            ",
            &[
                (0, vec![("name", "a")]),
                (0, vec![("name", "c")]),
                (
                    0,
                    vec![("doc", "// four"), ("doc", "// five"), ("name", "e")],
                ),
            ],
        );
    });
}

#[test]
fn test_matches_with_anchor_sibling_inside_parent() {
    allocations::record(|| {
        let language = get_language("rust");

        let query = Query::new(
            &language,
            "
            (source_file
                (line_comment)
                .
                (function_item
                    name: (identifier) @name)
            )",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            // A
            fn a() {}

            // B
            fn b() {}
            ",
            &[(0, vec![("name", "a")]), (0, vec![("name", "b")])],
        );
    });
}

#[test]
fn test_matches_with_anchor_sibling_with_quantifier_inside_parent() {
    allocations::record(|| {
        let language = get_language("rust");

        let query = Query::new(
            &language,
            "
            (source_file
                (line_comment)+
                .
                (function_item
                    name: (identifier) @name)
            )",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            // A
            fn a() {}

            // B
            fn b() {}
            ",
            &[(0, vec![("name", "a")]), (0, vec![("name", "b")])],
        );
    });
}

#[test]
fn test_matches_with_anchor_sibling_with_quantifier_captured_inside_parent() {
    allocations::record(|| {
        let language = get_language("rust");

        let query = Query::new(
            &language,
            "
            (source_file
                (line_comment)+ @doc
                .
                (function_item
                    name: (identifier) @name)
            )",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            // A
            fn a() {}

            // B
            fn b() {}
            ",
            &[
                (0, vec![("doc", "// A"), ("name", "a")]),
                (0, vec![("doc", "// B"), ("name", "b")]),
            ],
        );
    });
}

#[test]
fn test_matches_anchored_quantified_sibling_inside_parent() {
    allocations::record(|| {
        let language = get_language("c");
        let query = Query::new(
            &language,
            "(translation_unit (comment)* @comment . (declaration) @decl)",
        )
        .unwrap();
        assert_query_matches(
            &language,
            &query,
            "
void foo() {}

// this one has
// two comments
extern int baz;

// this one has a comment
extern int bar;
",
            &[
                (
                    0,
                    vec![
                        ("comment", "// this one has"),
                        ("comment", "// two comments"),
                        ("decl", "extern int baz;"),
                    ],
                ),
                (
                    0,
                    vec![
                        ("comment", "// this one has a comment"),
                        ("decl", "extern int bar;"),
                    ],
                ),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_trailing_optional_nodes() {
    allocations::record(|| {
        let language = get_language("javascript");

        let query = Query::new(
            &language,
            "
            (class_declaration
                name: (identifier) @class
                (class_heritage
                  (identifier) @superclass)?)
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "class A {}",
            &[(0, vec![("class", "A")])],
        );

        assert_query_matches(
            &language,
            &query,
            "
            class A {}
            class B extends C {}
            class D extends (E.F) {}
            ",
            &[
                (0, vec![("class", "A")]),
                (0, vec![("class", "B"), ("superclass", "C")]),
                (0, vec![("class", "D")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_nested_optional_nodes() {
    allocations::record(|| {
        let language = get_language("javascript");

        // A function call, optionally containing a function call, which optionally contains a
        // number
        let query = Query::new(
            &language,
            "
            (call_expression
                function: (identifier) @outer-fn
                arguments: (arguments
                    (call_expression
                        function: (identifier) @inner-fn
                        arguments: (arguments
                            (number)? @num))?))
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            r"
            a(b, c(), d(null, 1, 2))
            e()
            f(g())
            ",
            &[
                (0, vec![("outer-fn", "a"), ("inner-fn", "c")]),
                (0, vec![("outer-fn", "c")]),
                (0, vec![("outer-fn", "a"), ("inner-fn", "d"), ("num", "1")]),
                (0, vec![("outer-fn", "a"), ("inner-fn", "d"), ("num", "2")]),
                (0, vec![("outer-fn", "d")]),
                (0, vec![("outer-fn", "e")]),
                (0, vec![("outer-fn", "f"), ("inner-fn", "g")]),
                (0, vec![("outer-fn", "g")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_repeated_internal_nodes() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "
            (_
                (method_definition
                    (decorator (identifier) @deco)+
                    name: (property_identifier) @name))
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            class A {
                @c
                @d
                e() {}
            }
            ",
            &[(0, vec![("deco", "c"), ("deco", "d"), ("name", "e")])],
        );
    });
}

#[test]
fn test_query_matches_with_simple_alternatives() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "
            (pair
                key: [(property_identifier) (string)] @key
                value: [(function_expression) @val1 (arrow_function) @val2])
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            a = {
                b: c,
                'd': e => f,
                g: {
                    h: function i() {},
                    'x': null,
                    j: _ => k
                },
                'l': function m() {},
            };
            ",
            &[
                (0, vec![("key", "'d'"), ("val2", "e => f")]),
                (0, vec![("key", "h"), ("val1", "function i() {}")]),
                (0, vec![("key", "j"), ("val2", "_ => k")]),
                (0, vec![("key", "'l'"), ("val1", "function m() {}")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_alternatives_in_repetitions() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            (array
                [(identifier) (string)] @el
                .
                (
                    ","
                    .
                    [(identifier) (string)] @el
                )*)
            "#,
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            a = [b, 'c', d, 1, e, 'f', 'g', h];
            ",
            &[
                (0, vec![("el", "b"), ("el", "'c'"), ("el", "d")]),
                (
                    0,
                    vec![("el", "e"), ("el", "'f'"), ("el", "'g'"), ("el", "h")],
                ),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_alternatives_at_root() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            [
                "if"
                "else"
                "function"
                "throw"
                "return"
            ] @keyword
            "#,
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            function a(b, c, d) {
                if (b) {
                    return c;
                } else {
                    throw d;
                }
            }
            ",
            &[
                (0, vec![("keyword", "function")]),
                (0, vec![("keyword", "if")]),
                (0, vec![("keyword", "return")]),
                (0, vec![("keyword", "else")]),
                (0, vec![("keyword", "throw")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_alternatives_under_fields() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r"
            (assignment_expression
                left: [
                    (identifier) @variable
                    (member_expression property: (property_identifier) @variable)
                ])
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            a = b;
            b = c.d;
            e.f = g;
            h.i = j.k;
            ",
            &[
                (0, vec![("variable", "a")]),
                (0, vec![("variable", "b")]),
                (0, vec![("variable", "f")]),
                (0, vec![("variable", "i")]),
            ],
        );
    });
}

#[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();

        assert_query_matches(
            &language,
            &query,
            "
            <div>
                <script>hi</script>
                <style>hi</style>
            </div>
            ",
            &[
                (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_different_tokens_with_the_same_string_value() {
    allocations::record(|| {
        // In Rust, there are two '<' tokens: one for the binary operator,
        // and one with higher precedence for generics.
        let language = get_language("rust");
        let query = Query::new(
            &language,
            r#"
                "<" @less
                ">" @greater
                "#,
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "const A: B<C> = d < e || f > g;",
            &[
                (0, vec![("less", "<")]),
                (1, vec![("greater", ">")]),
                (0, vec![("less", "<")]),
                (1, vec![("greater", ">")]),
            ],
        );
    });
}

#[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();
        cursor.set_match_limit(32);
        let matches = cursor.matches(&query, tree.root_node(), source.as_bytes());

        // For this pathological query, some match permutations will be dropped.
        // Just check that a subset of the results are returned, and no crash or
        // leak occurs.
        assert_eq!(
            collect_matches(matches, &query, source.as_str())[0],
            (0, vec![("pre", "hello"), ("post", "hello")]),
        );
        assert!(cursor.did_exceed_match_limit());
    });
}

#[test]
fn test_query_sibling_patterns_dont_match_children_of_an_error() {
    allocations::record(|| {
        let language = get_language("rust");
        let query = Query::new(
            &language,
            r#"
            ("{" @open "}" @close)

            [
              (line_comment)
              (block_comment)
            ] @comment

            ("<" @first "<" @second)
            "#,
        )
        .unwrap();

        // Most of the document will fail to parse, resulting in a
        // large number of tokens that are *direct* children of an
        // ERROR node.
        //
        // These children should still match, unless they are part
        // of a "non-rooted" pattern, in which there are multiple
        // top-level sibling nodes. Those patterns should not match
        // directly inside of an error node, because the contents of
        // an error node are not syntactically well-structured, so we
        // would get many spurious matches.
        let source = "
            fn a() {}

            <<<<<<<<<< add pub b fn () {}
            // comment 1
            pub fn b() {
            /* comment 2 */
            ==========
            pub fn c() {
            // comment 3
            >>>>>>>>>> add pub c fn () {}
            }
        ";

        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(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, source),
            &[
                (0, vec![("open", "{"), ("close", "}")]),
                (1, vec![("comment", "// comment 1")]),
                (1, vec![("comment", "/* comment 2 */")]),
                (1, vec![("comment", "// comment 3")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_alternatives_and_too_many_permutations_to_track() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "
            (
                (comment) @doc
                ; not immediate
                (class_declaration) @class
            )

            (call_expression
                function: [
                    (identifier) @function
                    (member_expression property: (property_identifier) @method)
                ])
            ",
        )
        .unwrap();

        let source = "/* hi */ a.b(); ".repeat(50);

        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(&source, None).unwrap();
        let mut cursor = QueryCursor::new();
        cursor.set_match_limit(32);
        let matches = cursor.matches(&query, tree.root_node(), source.as_bytes());

        assert_eq!(
            collect_matches(matches, &query, source.as_str()),
            vec![(1, vec![("method", "b")]); 50],
        );
        assert!(cursor.did_exceed_match_limit());
    });
}

#[test]
fn test_repetitions_before_with_alternatives() {
    allocations::record(|| {
        let language = get_language("rust");
        let query = Query::new(
            &language,
            r"
            (
                (line_comment)* @comment
                .
                [
                    (struct_item name: (_) @name)
                    (function_item name: (_) @name)
                    (enum_item name: (_) @name)
                    (impl_item type: (_) @name)
                ]
            )
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            r"
            // a
            // b
            fn c() {}

            // d
            // e
            impl F {}
            ",
            &[
                (
                    0,
                    vec![("comment", "// a"), ("comment", "// b"), ("name", "c")],
                ),
                (
                    0,
                    vec![("comment", "// d"), ("comment", "// e"), ("name", "F")],
                ),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_anonymous_tokens() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            ";" @punctuation
            "&&" @operator
            "\"" @quote
            "#,
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            r#"foo(a && "b");"#,
            &[
                (1, vec![("operator", "&&")]),
                (2, vec![("quote", "\"")]),
                (2, vec![("quote", "\"")]),
                (0, vec![("punctuation", ";")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_supertypes() {
    allocations::record(|| {
        let language = get_language("python");
        let query = Query::new(
            &language,
            r"
            (argument_list (expression) @arg)

            (keyword_argument
                value: (expression) @kw_arg)

            (assignment
              left: (identifier) @var_def)

            (primary_expression/identifier) @var_ref
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
                a = b.c(
                    [d],
                    # a comment
                    e=f
                )
            ",
            &[
                (2, vec![("var_def", "a")]),
                (3, vec![("var_ref", "b")]),
                (0, vec![("arg", "[d]")]),
                (3, vec![("var_ref", "d")]),
                (1, vec![("kw_arg", "f")]),
                (3, vec![("var_ref", "f")]),
            ],
        );
    });
}

#[test]
#[expect(clippy::reversed_empty_ranges, reason = "testing empty range behavior")]
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(0..8)
                .matches(&query, tree.root_node(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, source),
            &[
                (0, vec![("element", "a")]),
                (0, vec![("element", "b")]),
                (0, vec![("element", "c")]),
            ]
        );

        let matches =
            cursor
                .set_byte_range(5..15)
                .matches(&query, tree.root_node(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, source),
            &[
                (0, vec![("element", "c")]),
                (0, vec![("element", "d")]),
                (0, vec![("element", "e")]),
            ]
        );

        // An end byte of zero indicates there is no end
        let matches =
            cursor
                .set_byte_range(12..0)
                .matches(&query, tree.root_node(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, source),
            &[
                (0, vec![("element", "e")]),
                (0, vec![("element", "f")]),
                (0, vec![("element", "g")]),
            ]
        );
    });
}

#[test]
fn test_query_matches_within_point_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, h,
              i, j,
              k, l,
            ]
        "
        .unindent();

        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_point_range(Point::new(1, 0)..Point::new(2, 3))
            .matches(&query, tree.root_node(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, &source),
            &[
                (0, vec![("element", "a")]),
                (0, vec![("element", "b")]),
                (0, vec![("element", "c")]),
            ]
        );

        let matches = cursor
            .set_point_range(Point::new(2, 0)..Point::new(3, 3))
            .matches(&query, tree.root_node(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, &source),
            &[
                (0, vec![("element", "c")]),
                (0, vec![("element", "d")]),
                (0, vec![("element", "e")]),
            ]
        );

        // Zero end point is treated like no end point.
        let matches = cursor
            .set_point_range(Point::new(4, 1)..Point::new(0, 0))
            .matches(&query, tree.root_node(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, &source),
            &[
                (0, vec![("element", "g")]),
                (0, vec![("element", "h")]),
                (0, vec![("element", "i")]),
                (0, vec![("element", "j")]),
                (0, vec![("element", "k")]),
                (0, vec![("element", "l")]),
            ]
        );
    });
}

#[test]
fn test_query_captures_within_byte_range() {
    allocations::record(|| {
        let language = get_language("c");
        let query = Query::new(
            &language,
            "
            (call_expression
                function: (identifier) @function
                arguments: (argument_list (string_literal) @string.arg))

            (string_literal) @string
           ",
        )
        .unwrap();

        let source = r#"DEFUN ("safe-length", Fsafe_length, Ssafe_length, 1, 1, 0)"#;

        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
                .set_byte_range(3..27)
                .captures(&query, tree.root_node(), source.as_bytes());

        assert_eq!(
            collect_captures(captures, &query, source),
            &[
                ("function", "DEFUN"),
                ("string.arg", "\"safe-length\""),
                ("string", "\"safe-length\""),
            ]
        );
    });
}

#[test]
fn test_query_cursor_next_capture_with_byte_range() {
    allocations::record(|| {
        let language = get_language("python");
        let query = Query::new(
            &language,
            "(function_definition name: (identifier) @function)
             (attribute attribute: (identifier) @property)
             ((identifier) @variable)",
        )
        .unwrap();

        let source = "def func():\n  foo.bar.baz()\n";
        //            ^            ^    ^          ^
        // byte_pos   0           12    17        27
        // point_pos (0,0)      (1,0)  (1,5)    (1,15)

        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
                .set_byte_range(12..17)
                .captures(&query, tree.root_node(), source.as_bytes());

        assert_eq!(
            collect_captures(captures, &query, source),
            &[("variable", "foo"),]
        );
    });
}

#[test]
fn test_query_cursor_next_capture_with_point_range() {
    allocations::record(|| {
        let language = get_language("python");
        let query = Query::new(
            &language,
            "(function_definition name: (identifier) @function)
             (attribute attribute: (identifier) @property)
             ((identifier) @variable)",
        )
        .unwrap();

        let source = "def func():\n  foo.bar.baz()\n";
        //            ^            ^    ^          ^
        // byte_pos   0           12    17        27
        // point_pos (0,0)      (1,0)  (1,5)    (1,15)

        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
            .set_point_range(Point::new(1, 0)..Point::new(1, 5))
            .captures(&query, tree.root_node(), source.as_bytes());

        assert_eq!(
            collect_captures(captures, &query, source),
            &[("variable", "foo"),]
        );
    });
}

#[test]
fn test_query_matches_with_unrooted_patterns_intersecting_byte_range() {
    allocations::record(|| {
        let language = get_language("rust");
        let query = Query::new(
            &language,
            r#"
            ("{" @left "}" @right)
            ("<" @left ">" @right)
            "#,
        )
        .unwrap();

        let source = "mod a { fn a<B: C, D: E>(f: B) { g(f) } }";

        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(source, None).unwrap();
        let mut cursor = QueryCursor::new();

        // within the type parameter list
        let offset = source.find("D: E>").unwrap();
        let matches = cursor.set_byte_range(offset..offset).matches(
            &query,
            tree.root_node(),
            source.as_bytes(),
        );
        assert_eq!(
            collect_matches(matches, &query, source),
            &[
                (1, vec![("left", "<"), ("right", ">")]),
                (0, vec![("left", "{"), ("right", "}")]),
            ]
        );

        // from within the type parameter list to within the function body
        let start_offset = source.find("D: E>").unwrap();
        let end_offset = source.find("g(f)").unwrap();
        let matches = cursor.set_byte_range(start_offset..end_offset).matches(
            &query,
            tree.root_node(),
            source.as_bytes(),
        );
        assert_eq!(
            collect_matches(matches, &query, source),
            &[
                (1, vec![("left", "<"), ("right", ">")]),
                (0, vec![("left", "{"), ("right", "}")]),
                (0, vec![("left", "{"), ("right", "}")]),
            ]
        );
    });
}

#[test]
fn test_query_matches_with_wildcard_at_root_intersecting_byte_range() {
    allocations::record(|| {
        let language = get_language("python");
        let query = Query::new(
            &language,
            "
            [
                (_ body: (block))
                (_ consequence: (block))
            ] @indent
            ",
        )
        .unwrap();

        let source = "
            class A:
                def b():
                    if c:
                        d
                    else:
                        e
        "
        .trim();

        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(source, None).unwrap();
        let mut cursor = QueryCursor::new();

        // After the first line of the class definition
        let offset = source.find("A:").unwrap() + 2;
        let mut matches = Vec::new();
        let mut match_iter = cursor.set_byte_range(offset..offset).matches(
            &query,
            tree.root_node(),
            source.as_bytes(),
        );

        while let Some(mat) = match_iter.next() {
            if let Some(capture) = mat.captures.first() {
                matches.push(capture.node.kind());
            }
        }
        assert_eq!(matches, &["class_definition"]);

        // After the first line of the function definition
        let offset = source.find("b():").unwrap() + 4;
        let mut matches = Vec::new();
        let mut match_iter = cursor.set_byte_range(offset..offset).matches(
            &query,
            tree.root_node(),
            source.as_bytes(),
        );

        while let Some(mat) = match_iter.next() {
            if let Some(capture) = mat.captures.first() {
                matches.push(capture.node.kind());
            }
        }
        assert_eq!(matches, &["class_definition", "function_definition"]);

        // After the first line of the if statement
        let offset = source.find("c:").unwrap() + 2;
        let mut matches = Vec::new();
        let mut match_iter = cursor.set_byte_range(offset..offset).matches(
            &query,
            tree.root_node(),
            source.as_bytes(),
        );

        while let Some(mat) = match_iter.next() {
            if let Some(capture) = mat.captures.first() {
                matches.push(capture.node.kind());
            }
        }
        assert_eq!(
            matches,
            &["class_definition", "function_definition", "if_statement"]
        );
    });
}

#[test]
fn test_query_captures_within_byte_range_assigned_after_iterating() {
    allocations::record(|| {
        let language = get_language("rust");
        let query = Query::new(
            &language,
            r#"
            (function_item
                name: (identifier) @fn_name)

            (mod_item
                name: (identifier) @mod_name
                body: (declaration_list
                    "{" @lbrace
                    "}" @rbrace))

            ; functions that return Result<()>
            ((function_item
                return_type: (generic_type
                    type: (type_identifier) @result
                    type_arguments: (type_arguments
                        (unit_type)))
                body: _ @fallible_fn_body)
             (#eq? @result "Result"))
            "#,
        )
        .unwrap();
        let source = "
        mod m1 {
            mod m2 {
                fn f1() -> Option<()> { Some(()) }
            }
            fn f2() -> Result<()> { Ok(()) }
            fn f3() {}
        }
        ";

        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(), source.as_bytes());

        // Retrieve some captures
        let mut results = Vec::new();
        let mut first_five = captures.by_ref().take(5);
        while let Some((mat, capture_ix)) = first_five.next() {
            let capture = mat.captures[*capture_ix];
            results.push((
                query.capture_names()[capture.index as usize],
                &source[capture.node.byte_range()],
            ));
        }
        assert_eq!(
            results,
            vec![
                ("mod_name", "m1"),
                ("lbrace", "{"),
                ("mod_name", "m2"),
                ("lbrace", "{"),
                ("fn_name", "f1"),
            ]
        );

        // Advance to a range that only partially intersects some matches.
        // Captures from these matches are reported, but only those that
        // intersect the range.
        results.clear();
        captures.set_byte_range(source.find("Ok").unwrap()..source.len());
        while let Some((mat, capture_ix)) = captures.next() {
            let capture = mat.captures[*capture_ix];
            results.push((
                query.capture_names()[capture.index as usize],
                &source[capture.node.byte_range()],
            ));
        }
        assert_eq!(
            results,
            vec![
                ("fallible_fn_body", "{ Ok(()) }"),
                ("fn_name", "f3"),
                ("rbrace", "}")
            ]
        );
    });
}

#[test]
fn test_query_matches_within_range_of_long_repetition() {
    allocations::record(|| {
        let language = get_language("rust");
        let query = Query::new(
            &language,
            "
            (function_item name: (identifier) @fn-name)
            ",
        )
        .unwrap();

        let source = "
            fn zero() {}
            fn one() {}
            fn two() {}
            fn three() {}
            fn four() {}
            fn five() {}
            fn six() {}
            fn seven() {}
            fn eight() {}
            fn nine() {}
            fn ten() {}
            fn eleven() {}
            fn twelve() {}
        "
        .unindent();

        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
            .set_point_range(Point::new(8, 0)..Point::new(20, 0))
            .matches(&query, tree.root_node(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, &source),
            &[
                (0, vec![("fn-name", "eight")]),
                (0, vec![("fn-name", "nine")]),
                (0, vec![("fn-name", "ten")]),
                (0, vec![("fn-name", "eleven")]),
                (0, vec![("fn-name", "twelve")]),
            ]
        );
    });
}

#[test]
fn test_query_matches_contained_within_range() {
    allocations::record(|| {
        let language = get_language("json");
        let query = Query::new(
            &language,
            r#"
            ("[" @l_bracket "]" @r_bracket)
            ("{" @l_brace "}" @r_brace)
            "#,
        )
        .unwrap();

        let source = r#"
            [
                {"key1": "value1"},
                {"key2": "value2"},
                {"key3": "value3"},
                {"key4": "value4"},
                {"key5": "value5"},
                {"key6": "value6"},
                {"key7": "value7"},
                {"key8": "value8"},
                {"key9": "value9"},
                {"key10": "value10"},
                {"key11": "value11"},
                {"key12": "value12"},
            ]
        "#
        .unindent();

        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(&source, None).unwrap();

        let expected_matches = [
            (1, vec![("l_brace", "{"), ("r_brace", "}")]),
            (1, vec![("l_brace", "{"), ("r_brace", "}")]),
        ];
        {
            let mut cursor = QueryCursor::new();
            let matches = cursor
                .set_containing_point_range(Point::new(5, 0)..Point::new(7, 0))
                .matches(&query, tree.root_node(), source.as_bytes());
            assert_eq!(collect_matches(matches, &query, &source), &expected_matches);
        }
        {
            let mut cursor = QueryCursor::new();
            let matches = cursor.set_containing_byte_range(78..120).matches(
                &query,
                tree.root_node(),
                source.as_bytes(),
            );
            assert_eq!(collect_matches(matches, &query, &source), &expected_matches);
        }
    });
}

#[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(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query1, source),
            &[(0, vec![("id1", "a")]),]
        );

        let matches = cursor.matches(&query3, tree.root_node(), source.as_bytes());
        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(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query2, source),
            &[(0, vec![("id1", "a")]), (1, vec![("id2", "b")]),]
        );
    });
}

#[test]
fn test_query_matches_with_multiple_captures_on_a_node() {
    allocations::record(|| {
        let language = get_language("javascript");
        let mut query = Query::new(
            &language,
            "(function_declaration
                (identifier) @name1 @name2 @name3
                (statement_block) @body1 @body2)",
        )
        .unwrap();

        let source = "function foo() { return 1; }";
        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(&query, tree.root_node(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, source),
            &[(
                0,
                vec![
                    ("name1", "foo"),
                    ("name2", "foo"),
                    ("name3", "foo"),
                    ("body1", "{ return 1; }"),
                    ("body2", "{ return 1; }"),
                ]
            ),]
        );

        // disabling captures still works when there are multiple captures on a
        // single node.
        query.disable_capture("name2");
        let matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, source),
            &[(
                0,
                vec![
                    ("name1", "foo"),
                    ("name3", "foo"),
                    ("body1", "{ return 1; }"),
                    ("body2", "{ return 1; }"),
                ]
            ),]
        );
    });
}

#[test]
fn test_query_matches_with_captured_wildcard_at_root() {
    allocations::record(|| {
        let language = get_language("python");
        let query = Query::new(
            &language,
            "
            ; captured wildcard at the root
            (_ [
                (except_clause (block) @block)
                (finally_clause (block) @block)
            ]) @stmt

            [
                (while_statement (block) @block)
                (if_statement (block) @block)

                ; captured wildcard at the root within an alternation
                (_ [
                    (else_clause (block) @block)
                    (elif_clause (block) @block)
                ])

                (try_statement (block) @block)
                (for_statement (block) @block)
            ] @stmt
            ",
        )
        .unwrap();

        let source = "
        for i in j:
            while True:
                if a:
                    print b
                elif c:
                    print d
                else:
                    try:
                        print f
                    except:
                        print g
                    finally:
                        print h
            else:
                print i
        "
        .trim();

        let mut parser = Parser::new();
        let mut cursor = QueryCursor::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(source, None).unwrap();

        let mut match_capture_names_and_rows = Vec::new();
        let mut match_iter = cursor.matches(&query, tree.root_node(), source.as_bytes());

        while let Some(m) = match_iter.next() {
            let captures = m
                .captures
                .iter()
                .map(|c| {
                    (
                        query.capture_names()[c.index as usize],
                        c.node.kind(),
                        c.node.start_position().row,
                    )
                })
                .collect::<Vec<_>>();
            match_capture_names_and_rows.push(captures);
        }

        assert_eq!(
            match_capture_names_and_rows,
            &[
                vec![("stmt", "for_statement", 0), ("block", "block", 1)],
                vec![("stmt", "while_statement", 1), ("block", "block", 2)],
                vec![("stmt", "if_statement", 2), ("block", "block", 3)],
                vec![("stmt", "if_statement", 2), ("block", "block", 5)],
                vec![("stmt", "if_statement", 2), ("block", "block", 7)],
                vec![("stmt", "try_statement", 7), ("block", "block", 8)],
                vec![("stmt", "try_statement", 7), ("block", "block", 10)],
                vec![("stmt", "try_statement", 7), ("block", "block", 12)],
                vec![("stmt", "while_statement", 1), ("block", "block", 14)],
            ]
        );
    });
}

#[test]
fn test_query_matches_with_no_captures() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r"
            (identifier)
            (string) @s
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            a = 'hi';
            b = 'bye';
            ",
            &[
                (0, vec![]),
                (1, vec![("s", "'hi'")]),
                (0, vec![]),
                (1, vec![("s", "'bye'")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_repeated_fields() {
    allocations::record(|| {
        let language = get_language("c");
        let query = Query::new(
            &language,
            "(field_declaration declarator: (field_identifier) @field)",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            struct S {
                int a, b, c;
            };
            ",
            &[
                (0, vec![("field", "a")]),
                (0, vec![("field", "b")]),
                (0, vec![("field", "c")]),
            ],
        );
    });
}

#[test]
fn test_query_matches_with_deeply_nested_patterns_with_fields() {
    allocations::record(|| {
        let language = get_language("python");
        let query = Query::new(
            &language,
            "
            (call
                function: (_) @func
                arguments: (_) @args)
            (call
                function: (attribute
                    object: (_) @receiver
                    attribute: (identifier) @method)
                arguments: (argument_list))

            ; These don't match anything, but they require additional
            ; states to keep track of their captures.
            (call
                function: (_) @fn
                arguments: (argument_list
                    (keyword_argument
                        name: (identifier) @name
                        value: (_) @val) @arg) @args) @call
            (call
                function: (identifier) @fn
                (#eq? @fn \"super\")) @super_call
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            a(1).b(2).c(3).d(4).e(5).f(6).g(7).h(8)
            ",
            &[
                (0, vec![("func", "a"), ("args", "(1)")]),
                (0, vec![("func", "a(1).b"), ("args", "(2)")]),
                (1, vec![("receiver", "a(1)"), ("method", "b")]),
                (0, vec![("func", "a(1).b(2).c"), ("args", "(3)")]),
                (1, vec![("receiver", "a(1).b(2)"), ("method", "c")]),
                (0, vec![("func", "a(1).b(2).c(3).d"), ("args", "(4)")]),
                (1, vec![("receiver", "a(1).b(2).c(3)"), ("method", "d")]),
                (0, vec![("func", "a(1).b(2).c(3).d(4).e"), ("args", "(5)")]),
                (
                    1,
                    vec![("receiver", "a(1).b(2).c(3).d(4)"), ("method", "e")],
                ),
                (
                    0,
                    vec![("func", "a(1).b(2).c(3).d(4).e(5).f"), ("args", "(6)")],
                ),
                (
                    1,
                    vec![("receiver", "a(1).b(2).c(3).d(4).e(5)"), ("method", "f")],
                ),
                (
                    0,
                    vec![("func", "a(1).b(2).c(3).d(4).e(5).f(6).g"), ("args", "(7)")],
                ),
                (
                    1,
                    vec![
                        ("receiver", "a(1).b(2).c(3).d(4).e(5).f(6)"),
                        ("method", "g"),
                    ],
                ),
                (
                    0,
                    vec![
                        ("func", "a(1).b(2).c(3).d(4).e(5).f(6).g(7).h"),
                        ("args", "(8)"),
                    ],
                ),
                (
                    1,
                    vec![
                        ("receiver", "a(1).b(2).c(3).d(4).e(5).f(6).g(7)"),
                        ("method", "h"),
                    ],
                ),
            ],
        );
    });
}

#[test]
fn test_query_alternation_with_inner_quantifier() {
    let language = get_language("c");
    let source_code = "#include <foo>
#include <bar>
#include <baz>

// comment";
    let matches = &[
        (
            0,
            vec![
                ("capture", "#include <foo>\n"),
                ("capture", "#include <bar>\n"),
                ("capture", "#include <baz>\n"),
            ],
        ),
        (0, vec![("capture", "// comment")]),
    ];

    let query = "[
       (preproc_include)+
       (comment)
    ] @capture";
    let query = Query::new(&language, query).unwrap();
    assert_query_matches(&language, &query, source_code, matches);

    let query = "[
       (comment)
       (preproc_include)+
    ] @capture";
    let query = Query::new(&language, query).unwrap();
    assert_query_matches(&language, &query, source_code, matches);
}

#[test]
fn test_query_alternation_with_outer_quantifier() {
    let language = get_language("c");
    let source_code = "#include <foo>
#include <bar>
#include <baz>

// comment";
    let matches = &[(
        0,
        vec![
            ("capture", "#include <foo>\n"),
            ("capture", "#include <bar>\n"),
            ("capture", "#include <baz>\n"),
            ("capture", "// comment"),
        ],
    )];

    let query = "[
        (preproc_include)
        (comment)
    ]+ @capture";
    let query = Query::new(&language, query).unwrap();
    assert_query_matches(&language, &query, source_code, matches);

    let query = "([
        (preproc_include)
        (comment)
    ] (_)?)+ @capture";
    let query = Query::new(&language, query).unwrap();
    assert_query_matches(&language, &query, source_code, matches);
}

#[test]
fn test_query_matches_with_alternations_and_predicates() {
    allocations::record(|| {
        let language = get_language("java");
        let query = Query::new(
            &language,
            "
            (block
                [
                    (local_variable_declaration
                        (variable_declarator
                            (identifier) @def.a
                            (string_literal) @lit.a
                        )
                    )
                    (local_variable_declaration
                        (variable_declarator
                            (identifier) @def.b
                            (null_literal) @lit.b
                        )
                    )
                ]
                (expression_statement
                    (method_invocation [
                        (argument_list
                            (identifier) @ref.a
                            (string_literal)
                        )
                        (argument_list
                            (null_literal)
                            (identifier) @ref.b
                        )
                    ])
                )
                (#eq? @def.a @ref.a )
                (#eq? @def.b @ref.b )
            )
            ",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            r#"
            void test() {
                int a = "foo";
                f(null, b);
            }
            "#,
            &[],
        );
    });
}

#[test]
fn test_query_matches_with_indefinite_step_containing_no_captures() {
    allocations::record(|| {
        // This pattern depends on the field declarations within the
        // struct's body, but doesn't capture anything within the body.
        // It demonstrates that internally, state-splitting needs to occur
        // for each field declaration within the body, in order to avoid
        // prematurely failing if the first field does not match.
        //
        // https://github.com/tree-sitter/tree-sitter/issues/937
        let language = get_language("c");
        let query = Query::new(
            &language,
            "(struct_specifier
                name: (type_identifier) @name
                body: (field_declaration_list
                    (field_declaration
                        type: (union_specifier))))",
        )
        .unwrap();

        assert_query_matches(
            &language,
            &query,
            "
            struct LacksUnionField {
                int a;
                struct {
                    B c;
                } d;
                G *h;
            };

            struct HasUnionField {
                int a;
                struct {
                    B c;
                } d;
                union {
                    bool e;
                    float f;
                } g;
                G *h;
            };
            ",
            &[(0, vec![("name", "HasUnionField")])],
        );
    });
}

#[test]
fn test_query_captures_basic() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            (pair
              key: _ @method.def
              (function_expression
                name: (identifier) @method.alias))

            (variable_declarator
              name: _ @function.def
              value: (function_expression
                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(), source.as_bytes());

        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(), source.as_bytes());
        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.builtin
              (#any-of? @variable.builtin
                        "arguments"
                        "module"
                        "console"
                        "window"
                        "document"))

            ((identifier) @variable
             (#not-match? @variable "^(lambda|load)$"))
            "#,
        )
        .unwrap();

        let source = "
          toad
          load
          panda
          lambda
          const ab = require('./ab');
          new Cd(EF);
          document;
          module;
          console;
        ";

        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(), source.as_bytes());
        assert_eq!(
            collect_captures(captures, &query, source),
            &[
                ("variable", "toad"),
                ("variable", "panda"),
                ("variable", "ab"),
                ("function.builtin", "require"),
                ("variable", "require"),
                ("constructor", "Cd"),
                ("variable", "Cd"),
                ("constant", "EF"),
                ("constructor", "EF"),
                ("variable", "EF"),
                ("variable.builtin", "document"),
                ("variable", "document"),
                ("variable.builtin", "module"),
                ("variable", "module"),
                ("variable.builtin", "console"),
                ("variable", "console"),
            ],
        );
    });
}

#[test]
fn test_query_captures_with_predicates() {
    allocations::record(|| {
        let language = get_language("javascript");

        let query = Query::new(
            &language,
            r"
            ((call_expression (identifier) @foo)
             (#set! name something)
             (#set! cool)
             (#something! @foo omg))

            ((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.general_predicates(0),
            &[QueryPredicate {
                operator: "something!".to_string().into_boxed_str(),
                args: vec![
                    QueryPredicateArg::Capture(0),
                    QueryPredicateArg::String("omg".to_string().into_boxed_str()),
                ]
                .into_boxed_slice(),
            },]
        );
        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),
            ]
        );

        let source = "const a = window.b";
        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(source, None).unwrap();

        let query = Query::new(
            &language,
            r#"((identifier) @variable.builtin
                (#match? @variable.builtin "^(arguments|module|console|window|document)$")
                (#is-not? local))
            "#,
        )
        .unwrap();

        let mut cursor = QueryCursor::new();
        let matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
        let matches = collect_matches(matches, &query, source);

        assert_eq!(matches, &[(0, vec![("variable.builtin", "window")])]);
    });
}

#[test]
fn test_query_captures_with_quoted_predicate_args() {
    allocations::record(|| {
        let language = get_language("javascript");

        // Double-quoted strings can contain:
        // * special escape sequences like \n and \r
        // * escaped double quotes with \*
        // * literal backslashes with \\
        let query = Query::new(
            &language,
            r#"
            ((call_expression (identifier) @foo)
             (#set! one "\"something\ngreat\""))

            ((identifier)
             (#set! two "\\s(\r?\n)*$"))

            ((function_declaration)
             (#set! three "\"something\ngreat\""))
            "#,
        )
        .unwrap();

        assert_eq!(
            query.property_settings(0),
            &[QueryProperty::new(
                "one",
                Some("\"something\ngreat\""),
                None
            )]
        );
        assert_eq!(
            query.property_settings(1),
            &[QueryProperty::new("two", Some("\\s(\r?\n)*$"), None)]
        );
        assert_eq!(
            query.property_settings(2),
            &[QueryProperty::new(
                "three",
                Some("\"something\ngreat\""),
                None
            )]
        );
    });
}

#[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_expression))

            (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(), source.as_bytes());
        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{i}: $ => null,").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(), source.as_bytes());
        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 occurrences of the pattern.
        let count = 50;
        let mut source = "a ? {".to_owned();
        for i in 0..count {
            writeln!(&mut source, "  x: y{i} ? y{i}.z : null,").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(), source.as_bytes());
        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_with_too_many_nested_results() {
    allocations::record(|| {
        let language = get_language("javascript");

        // Search for method calls in general, and also method calls with a template string
        // in place of an argument list (aka "tagged template strings") in particular.
        //
        // This second pattern, which looks for the tagged template strings, is expensive to
        // use with the `captures()` method, because:
        // 1. When calling `captures`, all of the captures must be returned in order of their
        //    appearance.
        // 2. This pattern captures the root `call_expression`.
        // 3. This pattern's result also depends on the final child (the template string).
        // 4. In between the `call_expression` and the possible `template_string`, there can be an
        //    arbitrarily deep subtree.
        //
        // This means that, if any patterns match *after* the initial `call_expression` is
        // captured, but before the final `template_string` is found, those matches must
        // be buffered, in order to prevent captures from being returned out-of-order.
        let query = Query::new(
            &language,
            r"
            ;; easy 👇
            (call_expression
              function: (member_expression
                property: (property_identifier) @method-name))

            ;; hard 👇
            (call_expression
              function: (member_expression
                property: (property_identifier) @template-tag)
              arguments: (template_string)) @template-call
            ",
        )
        .unwrap();

        // There are a *lot* of matches in between the beginning of the outer `call_expression`
        // (the call to `a(...).f`), which starts at the beginning of the file, and the final
        // template string, which occurs at the end of the file. The query algorithm imposes a
        // limit on the total number of matches which can be buffered at a time. But we don't
        // want to neglect the inner matches just because of the expensive outer match, so we
        // abandon the outer match (which would have captured `f` as a `template-tag`).
        let source = "
        a(b => {
            b.c0().d0 `😄`;
            b.c1().d1 `😄`;
            b.c2().d2 `😄`;
            b.c3().d3 `😄`;
            b.c4().d4 `😄`;
            b.c5().d5 `😄`;
            b.c6().d6 `😄`;
            b.c7().d7 `😄`;
            b.c8().d8 `😄`;
            b.c9().d9 `😄`;
        }).e().f ``;
        "
        .trim();

        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(source, None).unwrap();
        let mut cursor = QueryCursor::new();
        cursor.set_match_limit(32);
        let captures = cursor.captures(&query, tree.root_node(), source.as_bytes());
        let captures = collect_captures(captures, &query, source);

        assert_eq!(
            &captures[0..4],
            &[
                ("template-call", "b.c0().d0 `😄`"),
                ("method-name", "c0"),
                ("method-name", "d0"),
                ("template-tag", "d0"),
            ]
        );
        assert_eq!(
            &captures[36..40],
            &[
                ("template-call", "b.c9().d9 `😄`"),
                ("method-name", "c9"),
                ("method-name", "d9"),
                ("template-tag", "d9"),
            ]
        );
        assert_eq!(
            &captures[40..],
            &[("method-name", "e"), ("method-name", "f"),]
        );
    });
}

#[test]
fn test_query_captures_with_definite_pattern_containing_many_nested_matches() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            (array
              "[" @l-bracket
              "]" @r-bracket)

            "." @dot
            "#,
        )
        .unwrap();

        // The '[' node must be returned before all of the '.' nodes,
        // even though its pattern does not finish until the ']' node
        // at the end of the document. But because the '[' is definite,
        // it can be returned before the pattern finishes matching.
        let source = "
        [
            a.b.c.d.e.f.g.h.i,
            a.b.c.d.e.f.g.h.i,
            a.b.c.d.e.f.g.h.i,
            a.b.c.d.e.f.g.h.i,
            a.b.c.d.e.f.g.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 captures = cursor.captures(&query, tree.root_node(), source.as_bytes());
        assert_eq!(
            collect_captures(captures, &query, source),
            std::iter::once(&("l-bracket", "["))
                .chain([("dot", "."); 40].iter())
                .chain(std::iter::once(&("r-bracket", "]")))
                .copied()
                .collect::<Vec<_>>(),
        );
    });
}

#[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(), source.as_bytes());
        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();

        let mut captures = cursor.captures(&query, tree.root_node(), source.as_bytes());
        while let Some((m, i)) = captures.next() {
            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_with_matches_removed_before_they_finish() {
    allocations::record(|| {
        let language = get_language("javascript");
        // When Tree-sitter detects that a pattern is guaranteed to match,
        // it will start to eagerly return the captures that it has found,
        // even though it hasn't matched the entire pattern yet. A
        // namespace_import node always has "*", "as" and then an identifier
        // for children, so captures will be emitted eagerly for this pattern.
        let query = Query::new(
            &language,
            r#"
            (namespace_import
              "*" @star
              "as" @as
              (identifier) @identifier)
            "#,
        )
        .unwrap();

        let source = "
          import * as name from 'module-name';
        ";

        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();
        let mut captures = cursor.captures(&query, tree.root_node(), source.as_bytes());
        while let Some((m, i)) = captures.next() {
            let capture = m.captures[*i];
            let text = capture.node.utf8_text(source.as_bytes()).unwrap();
            if text == "as" {
                m.remove();
                continue;
            }
            captured_strings.push(text);
        }

        // .remove() removes the match before it is finished. The identifier
        // "name" is part of this match, so we expect that removing the "as"
        // capture from the match should prevent "name" from matching:
        assert_eq!(captured_strings, &["*",]);
    });
}

#[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(), source.as_bytes());

        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(), source.as_bytes());
        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_text_callback_returns_chunks() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            ((identifier) @leading_upper
             (#match? @leading_upper "^[A-Z][A-Z_]*[a-z]"))
            ((identifier) @all_upper
             (#match? @all_upper "^[A-Z][A-Z_]*$"))
            ((identifier) @all_lower
             (#match? @all_lower "^[a-z][a-z_]*$"))
            "#,
        )
        .unwrap();

        let source = "SOMETHING[a] = transform(AnotherThing[b].property[c], PARAMETER);";

        // Store the source code in chunks of 3 bytes, and expose it via
        // an iterator API.
        let source_chunks = source.as_bytes().chunks(3).collect::<Vec<_>>();
        let chunks_in_range = |range: std::ops::Range<usize>| {
            let mut offset = 0;
            source_chunks.iter().filter_map(move |chunk| {
                let end_offset = offset + chunk.len();
                if offset < range.end && range.start < end_offset {
                    let end_in_chunk = (range.end - offset).min(chunk.len());
                    let start_in_chunk = range.start.max(offset) - offset;
                    offset = end_offset;
                    Some(&chunk[start_in_chunk..end_in_chunk])
                } else {
                    offset = end_offset;
                    None
                }
            })
        };
        assert_eq!(
            chunks_in_range(0..9)
                .map(|c| std::str::from_utf8(c).unwrap())
                .collect::<String>(),
            "SOMETHING",
        );
        assert_eq!(
            chunks_in_range(15..24)
                .map(|c| std::str::from_utf8(c).unwrap())
                .collect::<String>(),
            "transform",
        );

        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(), |node: Node| {
            chunks_in_range(node.byte_range())
        });

        assert_eq!(
            collect_captures(captures, &query, source),
            &[
                ("all_upper", "SOMETHING"),
                ("all_lower", "a"),
                ("all_lower", "transform"),
                ("leading_upper", "AnotherThing"),
                ("all_lower", "b"),
                ("all_lower", "c"),
                ("all_upper", "PARAMETER"),
            ]
        );
    });
}

#[test]
fn test_query_start_end_byte_for_pattern() {
    let language = get_language("javascript");

    let patterns_1 = indoc! {r#"
        "+" @operator
        "-" @operator
        "*" @operator
        "=" @operator
        "=>" @operator
    "#};

    let patterns_2 = indoc! {"
        (identifier) @a
        (string) @b
    "};

    let patterns_3 = indoc! {"
        ((identifier) @b (#match? @b i))
        (function_declaration name: (identifier) @c)
        (method_definition name: (property_identifier) @d)
    "};

    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.end_byte_for_pattern(0), "\"+\" @operator\n".len());
    assert_eq!(query.start_byte_for_pattern(5), patterns_1.len());
    assert_eq!(
        query.end_byte_for_pattern(5),
        patterns_1.len() + "(identifier) @a\n".len()
    );
    assert_eq!(
        query.start_byte_for_pattern(7),
        patterns_1.len() + patterns_2.len()
    );
    assert_eq!(
        query.end_byte_for_pattern(7),
        patterns_1.len() + patterns_2.len() + "((identifier) @b (#match? @b i))\n".len()
    );
}

#[test]
fn test_query_capture_names() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            r#"
            (if_statement
              condition: (parenthesized_expression (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", "right-operand", "body", "loop-condition"]
        );
    });
}

#[test]
fn test_query_lifetime_is_separate_from_nodes_lifetime() {
    allocations::record(|| {
        let query = r"(call_expression) @call";
        let source = "a(1); b(2);";

        let language = get_language("javascript");
        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(source, None).unwrap();

        fn take_first_node_from_captures<'tree>(
            source: &str,
            query: &str,
            node: Node<'tree>,
        ) -> Node<'tree> {
            // Following 2 lines are redundant but needed to demonstrate
            // more understandable compiler error message
            let language = get_language("javascript");
            let query = Query::new(&language, query).unwrap();
            let mut cursor = QueryCursor::new();

            cursor
                .matches(&query, node, source.as_bytes())
                .next()
                .unwrap()
                .captures[0]
                .node
        }

        let node = take_first_node_from_captures(source, query, tree.root_node());
        assert_eq!(node.kind(), "call_expression");

        fn take_first_node_from_matches<'tree>(
            source: &str,
            query: &str,
            node: Node<'tree>,
        ) -> Node<'tree> {
            let language = get_language("javascript");
            let query = Query::new(&language, query).unwrap();
            let mut cursor = QueryCursor::new();

            cursor
                .captures(&query, node, source.as_bytes())
                .next()
                .unwrap()
                .0
                .captures[0]
                .node
        }

        let node = take_first_node_from_matches(source, query, tree.root_node());
        assert_eq!(node.kind(), "call_expression");
    });
}

#[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(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, source),
            &[(0, vec![("fn-name", "one")]),],
        );
    });
}

#[test]
fn test_query_disable_pattern() {
    allocations::record(|| {
        let language = get_language("javascript");
        let mut query = Query::new(
            &language,
            "
                (function_declaration
                    name: (identifier) @name)
                (function_declaration
                    body: (statement_block) @body)
                (class_declaration
                    name: (identifier) @name)
                (class_declaration
                    body: (class_body) @body)
            ",
        )
        .unwrap();

        // disable the patterns that match names
        query.disable_pattern(0);
        query.disable_pattern(2);

        let source = "class A { constructor() {} } function b() { return 1; }";
        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(), source.as_bytes());
        assert_eq!(
            collect_matches(matches, &query, source),
            &[
                (3, vec![("body", "{ constructor() {} }")]),
                (1, vec![("body", "{ return 1; }")]),
            ],
        );
    });
}

#[test]
fn test_query_deep_clone() {
    allocations::record(|| {
        let language = get_language("javascript");
        let query = Query::new(
            &language,
            "
                (function_declaration
                    name: (identifier) @name)
                (function_declaration
                    body: (statement_block) @body)
            ",
        )
        .unwrap();

        let mut clone = query.deep_clone();
        clone.disable_pattern(1);

        let source = "function foo() { return 1; }";
        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(source, None).unwrap();
        let mut cursor = QueryCursor::new();

        // The clone with pattern 1 disabled only produces the @name match.
        let clone_matches = collect_matches(
            cursor.matches(&clone, tree.root_node(), source.as_bytes()),
            &clone,
            source,
        );
        assert_eq!(clone_matches, &[(0, vec![("name", "foo")])]);

        // The original is unaffected and still produces both @name and @body.
        let original_matches = collect_matches(
            cursor.matches(&query, tree.root_node(), source.as_bytes()),
            &query,
            source,
        );
        assert_eq!(
            original_matches,
            &[
                (0, vec![("name", "foo")]),
                (1, vec![("body", "{ return 1; }")]),
            ]
        );
    });
}

#[test]
fn test_query_alternative_predicate_prefix() {
    allocations::record(|| {
        let language = get_language("c");
        let query = Query::new(
            &language,
            r#"
            ((call_expression
              function: (identifier) @keyword
              arguments: (argument_list
                          (string_literal) @function))
             (.eq? @keyword "DEFUN"))
        "#,
        )
        .unwrap();
        let source = r#"
            DEFUN ("identity", Fidentity, Sidentity, 1, 1, 0,
                   doc: /* Return the argument unchanged.  */
                   attributes: const)
              (Lisp_Object arg)
            {
              return arg;
            }
        "#;
        assert_query_matches(
            &language,
            &query,
            source,
            &[(0, vec![("keyword", "DEFUN"), ("function", "\"identity\"")])],
        );
    });
}

#[test]
fn test_query_random() {
    use pretty_assertions::assert_eq;

    allocations::record(|| {
        let language = get_language("rust");
        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let mut cursor = QueryCursor::new();
        cursor.set_match_limit(64);

        let pattern_tree = parser
            .parse(include_str!("helpers/query_helpers.rs"), None)
            .unwrap();
        let test_tree = parser
            .parse(include_str!("helpers/query_helpers.rs"), None)
            .unwrap();

        let start_seed = 0;
        let end_seed = start_seed + *ITERATION_COUNT;

        for seed in start_seed..(start_seed + end_seed) {
            let seed = seed as u64;
            let mut rand = StdRng::seed_from_u64(seed);
            let (pattern_ast, _) = Pattern::random_pattern_in_tree(&pattern_tree, &mut rand);
            let pattern = pattern_ast.to_string();
            let expected_matches = pattern_ast.matches_in_tree(&test_tree);

            let query = Query::new(&language, &pattern).unwrap_or_else(|e| {
                panic!("failed to build query for pattern {pattern}. seed: {seed}\n{e}")
            });
            let mut actual_matches = Vec::new();
            let mut match_iter = cursor.matches(
                &query,
                test_tree.root_node(),
                include_bytes!("parser_test.rs").as_ref(),
            );

            while let Some(mat) = match_iter.next() {
                let transformed_match = Match {
                    last_node: None,
                    captures: mat
                        .captures
                        .iter()
                        .map(|c| (query.capture_names()[c.index as usize], c.node))
                        .collect::<Vec<_>>(),
                };
                actual_matches.push(transformed_match);
            }

            // actual_matches.sort_unstable();
            actual_matches.dedup();

            if !cursor.did_exceed_match_limit() {
                assert_eq!(
                    actual_matches, expected_matches,
                    "seed: {}, pattern:\n{}",
                    seed, pattern
                );
            }
        }
    });
}

#[test]
fn test_query_is_pattern_guaranteed_at_step() {
    struct Row {
        language: Language,
        description: &'static str,
        pattern: &'static str,
        results_by_substring: &'static [(&'static str, bool)],
    }

    let rows = &[
        Row {
            description: "no guaranteed steps",
            language: get_language("python"),
            pattern: r"(expression_statement (string))",
            results_by_substring: &[("expression_statement", false), ("string", false)],
        },
        Row {
            description: "all guaranteed steps",
            language: get_language("javascript"),
            pattern: r#"(object "{" "}")"#,
            results_by_substring: &[("object", false), ("{", true), ("}", true)],
        },
        Row {
            description: "a fallible step that is optional",
            language: get_language("javascript"),
            pattern: r#"(object "{" (identifier)? @foo "}")"#,
            results_by_substring: &[
                ("object", false),
                ("{", true),
                ("(identifier)?", false),
                ("}", true),
            ],
        },
        Row {
            description: "multiple fallible steps that are optional",
            language: get_language("javascript"),
            pattern: r#"(object "{" (identifier)? @id1 ("," (identifier) @id2)? "}")"#,
            results_by_substring: &[
                ("object", false),
                ("{", true),
                ("(identifier)? @id1", false),
                ("\",\"", false),
                ("}", true),
            ],
        },
        Row {
            description: "guaranteed step after fallibe step",
            language: get_language("javascript"),
            pattern: r#"(pair (property_identifier) ":")"#,
            results_by_substring: &[("pair", false), ("property_identifier", false), (":", true)],
        },
        Row {
            description: "fallible step in between two guaranteed steps",
            language: get_language("javascript"),
            pattern: r#"(ternary_expression
                condition: (_)
                "?"
                consequence: (call_expression)
                ":"
                alternative: (_))"#,
            results_by_substring: &[
                ("condition:", false),
                ("\"?\"", false),
                ("consequence:", false),
                ("\":\"", true),
                ("alternative:", true),
            ],
        },
        Row {
            description: "one guaranteed step after a repetition",
            language: get_language("javascript"),
            pattern: r#"(object "{" (_) "}")"#,
            results_by_substring: &[("object", false), ("{", false), ("(_)", false), ("}", true)],
        },
        Row {
            description: "guaranteed steps after multiple repetitions",
            language: get_language("json"),
            pattern: r#"(object "{" (pair) "," (pair) "," (_) "}")"#,
            results_by_substring: &[
                ("object", false),
                ("{", false),
                ("(pair) \",\" (pair)", false),
                ("(pair) \",\" (_)", false),
                ("\",\" (_)", false),
                ("(_)", true),
                ("}", true),
            ],
        },
        Row {
            description: "a guaranteed step with a field",
            language: get_language("javascript"),
            pattern: r"(binary_expression left: (expression) right: (_))",
            results_by_substring: &[
                ("binary_expression", false),
                ("(expression)", false),
                ("(_)", true),
            ],
        },
        Row {
            description: "multiple guaranteed steps with fields",
            language: get_language("javascript"),
            pattern: r"(function_declaration name: (identifier) body: (statement_block))",
            results_by_substring: &[
                ("function_declaration", false),
                ("identifier", true),
                ("statement_block", true),
            ],
        },
        Row {
            description: "nesting, one guaranteed step",
            language: get_language("javascript"),
            pattern: r#"
                (function_declaration
                    name: (identifier)
                    body: (statement_block "{" (expression_statement) "}"))"#,
            results_by_substring: &[
                ("function_declaration", false),
                ("identifier", false),
                ("statement_block", false),
                ("{", false),
                ("expression_statement", false),
                ("}", true),
            ],
        },
        Row {
            description: "a guaranteed step after some deeply nested hidden nodes",
            language: get_language("ruby"),
            pattern: r#"
            (singleton_class
                value: (constant)
                "end")
            "#,
            results_by_substring: &[
                ("singleton_class", false),
                ("constant", false),
                ("end", true),
            ],
        },
        Row {
            description: "nesting, no guaranteed steps",
            language: get_language("javascript"),
            pattern: r"
            (call_expression
                function: (member_expression
                  property: (property_identifier) @template-tag)
                arguments: (template_string)) @template-call
            ",
            results_by_substring: &[("property_identifier", false), ("template_string", false)],
        },
        Row {
            description: "a guaranteed step after a nested node",
            language: get_language("javascript"),
            pattern: r#"
            (subscript_expression
                object: (member_expression
                    object: (identifier) @obj
                    property: (property_identifier) @prop)
                "[")
            "#,
            results_by_substring: &[
                ("identifier", false),
                ("property_identifier", false),
                ("[", true),
            ],
        },
        Row {
            description: "a step that is fallible due to a predicate",
            language: get_language("javascript"),
            pattern: r#"
            (subscript_expression
                object: (member_expression
                    object: (identifier) @obj
                    property: (property_identifier) @prop)
                "["
                (#match? @prop "foo"))
            "#,
            results_by_substring: &[
                ("identifier", false),
                ("property_identifier", false),
                ("[", true),
            ],
        },
        Row {
            description: "alternation where one branch has guaranteed steps",
            language: get_language("javascript"),
            pattern: r"
            [
                (unary_expression (identifier))
                (call_expression
                  function: (_)
                  arguments: (_))
                (binary_expression right: (call_expression))
            ]
            ",
            results_by_substring: &[
                ("identifier", false),
                ("right:", false),
                ("function:", true),
                ("arguments:", true),
            ],
        },
        Row {
            description: "guaranteed step at the end of an aliased parent node",
            language: get_language("ruby"),
            pattern: r#"
            (method_parameters "(" (identifier) @id")")
            "#,
            results_by_substring: &[("\"(\"", false), ("(identifier)", false), ("\")\"", true)],
        },
        Row {
            description: "long, but not too long to analyze",
            language: get_language("javascript"),
            pattern: r#"
            (object "{" (pair) (pair) (pair) (pair) "}")
            "#,
            results_by_substring: &[
                ("\"{\"", false),
                ("(pair)", false),
                ("(pair) \"}\"", false),
                ("\"}\"", true),
            ],
        },
        Row {
            description: "too long to analyze",
            language: get_language("javascript"),
            pattern: r#"
            (object "{" (pair) (pair) (pair) (pair) (pair) (pair) (pair) (pair) (pair) (pair) (pair) (pair) "}")
            "#,
            results_by_substring: &[
                ("\"{\"", false),
                ("(pair)", false),
                ("(pair) \"}\"", false),
                ("\"}\"", false),
            ],
        },
        Row {
            description: "hidden nodes that have several fields",
            language: get_language("java"),
            pattern: r"
            (method_declaration name: (identifier))
            ",
            results_by_substring: &[("name:", true)],
        },
        Row {
            description: "top-level non-terminal extra nodes",
            language: get_language("ruby"),
            pattern: r"
            (heredoc_body
                (interpolation)
                (heredoc_end) @end)
            ",
            results_by_substring: &[
                ("(heredoc_body", false),
                ("(interpolation)", false),
                ("(heredoc_end)", true),
            ],
        },
        // TODO: figure out why line comments, an extra, are no longer allowed *anywhere*
        // likely culprits are the fact that it's no longer a token itself or that it uses an
        // external token
        // Row {
        //     description: "multiple extra nodes",
        //     language: get_language("rust"),
        //     pattern: r"
        //     (call_expression
        //         (line_comment) @a
        //         (line_comment) @b
        //         (arguments))
        //     ",
        //     results_by_substring: &[
        //         ("(line_comment) @a", false),
        //         ("(line_comment) @b", false),
        //         ("(arguments)", true),
        //     ],
        // },
    ];

    allocations::record(|| {
        eprintln!();

        for row in rows {
            if let Some(filter) = EXAMPLE_FILTER.as_ref()
                && !row.description.contains(filter.as_str())
            {
                continue;
            }
            eprintln!("  query example: {:?}", row.description);
            let query = Query::new(&row.language, row.pattern).unwrap();
            for (substring, is_definite) in row.results_by_substring {
                let offset = row.pattern.find(substring).unwrap();
                assert_eq!(
                    query.is_pattern_guaranteed_at_step(offset),
                    *is_definite,
                    "Description: {}, Pattern: {:?}, substring: {:?}, expected is_definite to be {}",
                    row.description,
                    row.pattern
                        .split_ascii_whitespace()
                        .collect::<Vec<_>>()
                        .join(" "),
                    substring,
                    is_definite,
                );
            }
        }
    });
}

#[test]
fn test_query_is_pattern_rooted() {
    struct Row {
        description: &'static str,
        pattern: &'static str,
        is_rooted: bool,
    }

    let rows = [
        Row {
            description: "simple token",
            pattern: r"(identifier)",
            is_rooted: true,
        },
        Row {
            description: "simple non-terminal",
            pattern: r"(function_definition name: (identifier))",
            is_rooted: true,
        },
        Row {
            description: "alternative of many tokens",
            pattern: r#"["if" "def" (identifier) (comment)]"#,
            is_rooted: true,
        },
        Row {
            description: "alternative of many non-terminals",
            pattern: r"[
                (function_definition name: (identifier))
                (class_definition name: (identifier))
                (block)
            ]",
            is_rooted: true,
        },
        Row {
            description: "two siblings",
            pattern: r#"("{" "}")"#,
            is_rooted: false,
        },
        Row {
            description: "top-level repetition",
            pattern: r"(comment)*",
            is_rooted: false,
        },
        Row {
            description: "alternative where one option has two siblings",
            pattern: r#"[
                (block)
                (class_definition)
                ("(" ")")
                (function_definition)
            ]"#,
            is_rooted: false,
        },
        Row {
            description: "alternative where one option has a top-level repetition",
            pattern: r"[
                (block)
                (class_definition)
                (comment)*
                (function_definition)
            ]",
            is_rooted: false,
        },
    ];

    allocations::record(|| {
        eprintln!();

        let language = get_language("python");
        for row in &rows {
            if let Some(filter) = EXAMPLE_FILTER.as_ref()
                && !row.description.contains(filter.as_str())
            {
                continue;
            }
            eprintln!("  query example: {:?}", row.description);
            let query = Query::new(&language, row.pattern).unwrap();
            assert_eq!(
                query.is_pattern_rooted(0),
                row.is_rooted,
                "Description: {}, Pattern: {:?}",
                row.description,
                row.pattern
                    .split_ascii_whitespace()
                    .collect::<Vec<_>>()
                    .join(" "),
            );
        }
    });
}

#[test]
fn test_query_is_pattern_non_local() {
    struct Row {
        description: &'static str,
        pattern: &'static str,
        language: Language,
        is_non_local: bool,
    }

    let rows = [
        Row {
            description: "simple token",
            pattern: r"(identifier)",
            language: get_language("python"),
            is_non_local: false,
        },
        Row {
            description: "siblings that can occur in an argument list",
            pattern: r"((identifier) (identifier))",
            language: get_language("python"),
            is_non_local: true,
        },
        Row {
            description: "siblings that can occur in a statement block",
            pattern: r"((return_statement) (return_statement))",
            language: get_language("python"),
            is_non_local: true,
        },
        Row {
            description: "siblings that can occur in a source file",
            pattern: r"((function_definition) (class_definition))",
            language: get_language("python"),
            is_non_local: true,
        },
        Row {
            description: "siblings that can't occur in any repetition",
            pattern: r#"("{" "}")"#,
            language: get_language("python"),
            is_non_local: false,
        },
        Row {
            description: "siblings that can't occur in any repetition, wildcard root",
            pattern: r#"(_ "{" "}") @foo"#,
            language: get_language("javascript"),
            is_non_local: false,
        },
        Row {
            description: "siblings that can occur in a class body, wildcard root",
            pattern: r"(_ (method_definition) (method_definition)) @foo",
            language: get_language("javascript"),
            is_non_local: true,
        },
        Row {
            description: "top-level repetitions that can occur in a class body",
            pattern: r"(method_definition)+ @foo",
            language: get_language("javascript"),
            is_non_local: true,
        },
        Row {
            description: "top-level repetitions that can occur in a statement block",
            pattern: r"(return_statement)+ @foo",
            language: get_language("javascript"),
            is_non_local: true,
        },
        Row {
            description: "rooted pattern that can occur in a statement block",
            pattern: r"(return_statement) @foo",
            language: get_language("javascript"),
            is_non_local: false,
        },
    ];

    allocations::record(|| {
        eprintln!();

        for row in &rows {
            if let Some(filter) = EXAMPLE_FILTER.as_ref()
                && !row.description.contains(filter.as_str())
            {
                continue;
            }
            eprintln!("  query example: {:?}", row.description);
            let query = Query::new(&row.language, row.pattern).unwrap();
            assert_eq!(
                query.is_pattern_non_local(0),
                row.is_non_local,
                "Description: {}, Pattern: {:?}",
                row.description,
                row.pattern
                    .split_ascii_whitespace()
                    .collect::<Vec<_>>()
                    .join(" "),
            );
        }
    });
}

#[test]
fn test_capture_quantifiers() {
    struct Row {
        description: &'static str,
        language: Language,
        pattern: &'static str,
        capture_quantifiers: &'static [(usize, &'static str, CaptureQuantifier)],
    }

    let rows = &[
        // Simple quantifiers
        Row {
            description: "Top level capture",
            language: get_language("python"),
            pattern: r"
                (module) @mod
            ",
            capture_quantifiers: &[(0, "mod", CaptureQuantifier::One)],
        },
        Row {
            description: "Nested list capture capture",
            language: get_language("javascript"),
            pattern: r"
                (array (_)* @elems) @array
            ",
            capture_quantifiers: &[
                (0, "array", CaptureQuantifier::One),
                (0, "elems", CaptureQuantifier::ZeroOrMore),
            ],
        },
        Row {
            description: "Nested non-empty list capture capture",
            language: get_language("javascript"),
            pattern: r"
                (array (_)+ @elems) @array
            ",
            capture_quantifiers: &[
                (0, "array", CaptureQuantifier::One),
                (0, "elems", CaptureQuantifier::OneOrMore),
            ],
        },
        // Nested quantifiers
        Row {
            description: "capture nested in optional pattern",
            language: get_language("javascript"),
            pattern: r"
                (array (call_expression (arguments (_) @arg))? @call) @array
            ",
            capture_quantifiers: &[
                (0, "array", CaptureQuantifier::One),
                (0, "call", CaptureQuantifier::ZeroOrOne),
                (0, "arg", CaptureQuantifier::ZeroOrOne),
            ],
        },
        Row {
            description: "optional capture nested in non-empty list pattern",
            language: get_language("javascript"),
            pattern: r"
                (array (call_expression (arguments (_)? @arg))+ @call) @array
            ",
            capture_quantifiers: &[
                (0, "array", CaptureQuantifier::One),
                (0, "call", CaptureQuantifier::OneOrMore),
                (0, "arg", CaptureQuantifier::ZeroOrMore),
            ],
        },
        Row {
            description: "non-empty list capture nested in optional pattern",
            language: get_language("javascript"),
            pattern: r"
                (array (call_expression (arguments (_)+ @args))? @call) @array
            ",
            capture_quantifiers: &[
                (0, "array", CaptureQuantifier::One),
                (0, "call", CaptureQuantifier::ZeroOrOne),
                (0, "args", CaptureQuantifier::ZeroOrMore),
            ],
        },
        // Quantifiers in alternations
        Row {
            description: "capture is the same in all alternatives",
            language: get_language("javascript"),
            pattern: r"[
                (function_declaration name:(identifier) @name)
                (call_expression function:(identifier) @name)
            ]",
            capture_quantifiers: &[(0, "name", CaptureQuantifier::One)],
        },
        Row {
            description: "capture appears in some alternatives",
            language: get_language("javascript"),
            pattern: r"[
                (function_declaration name:(identifier) @name)
                (function_expression)
            ] @fun",
            capture_quantifiers: &[
                (0, "fun", CaptureQuantifier::One),
                (0, "name", CaptureQuantifier::ZeroOrOne),
            ],
        },
        Row {
            description: "capture has different quantifiers in alternatives",
            language: get_language("javascript"),
            pattern: r"[
                (call_expression arguments: (arguments (_)+ @args))
                (new_expression  arguments: (arguments (_)? @args))
            ] @call",
            capture_quantifiers: &[
                (0, "call", CaptureQuantifier::One),
                (0, "args", CaptureQuantifier::ZeroOrMore),
            ],
        },
        // Quantifiers in siblings
        Row {
            description: "siblings have different captures with different quantifiers",
            language: get_language("javascript"),
            pattern: r"
                (call_expression (arguments (identifier)? @self (_)* @args)) @call
            ",
            capture_quantifiers: &[
                (0, "call", CaptureQuantifier::One),
                (0, "self", CaptureQuantifier::ZeroOrOne),
                (0, "args", CaptureQuantifier::ZeroOrMore),
            ],
        },
        Row {
            description: "siblings have same capture with different quantifiers",
            language: get_language("javascript"),
            pattern: r"
                (call_expression (arguments (identifier) @args (_)* @args)) @call
            ",
            capture_quantifiers: &[
                (0, "call", CaptureQuantifier::One),
                (0, "args", CaptureQuantifier::OneOrMore),
            ],
        },
        // Combined scenarios
        Row {
            description: "combined nesting, alternatives, and siblings",
            language: get_language("javascript"),
            pattern: r"
                (array
                    (call_expression
                        (arguments [
                            (identifier) @self
                            (_)+ @args
                        ])
                    )+ @call
                ) @array
            ",
            capture_quantifiers: &[
                (0, "array", CaptureQuantifier::One),
                (0, "call", CaptureQuantifier::OneOrMore),
                (0, "self", CaptureQuantifier::ZeroOrMore),
                (0, "args", CaptureQuantifier::ZeroOrMore),
            ],
        },
        // Multiple patterns
        Row {
            description: "multiple patterns",
            language: get_language("javascript"),
            pattern: r"
                (function_declaration name: (identifier) @x)
                (statement_identifier) @y
                (property_identifier)+ @z
                (array (identifier)* @x)
            ",
            capture_quantifiers: &[
                // x
                (0, "x", CaptureQuantifier::One),
                (1, "x", CaptureQuantifier::Zero),
                (2, "x", CaptureQuantifier::Zero),
                (3, "x", CaptureQuantifier::ZeroOrMore),
                // y
                (0, "y", CaptureQuantifier::Zero),
                (1, "y", CaptureQuantifier::One),
                (2, "y", CaptureQuantifier::Zero),
                (3, "y", CaptureQuantifier::Zero),
                // z
                (0, "z", CaptureQuantifier::Zero),
                (1, "z", CaptureQuantifier::Zero),
                (2, "z", CaptureQuantifier::OneOrMore),
                (3, "z", CaptureQuantifier::Zero),
            ],
        },
        Row {
            description: "multiple alternatives",
            language: get_language("javascript"),
            pattern: r"
            [
                (array (identifier) @x)
                (function_declaration name: (identifier)+ @x)
            ]
            [
                (array (identifier) @x)
                (function_declaration name: (identifier)+ @x)
            ]
            ",
            capture_quantifiers: &[
                (0, "x", CaptureQuantifier::OneOrMore),
                (1, "x", CaptureQuantifier::OneOrMore),
            ],
        },
    ];

    allocations::record(|| {
        eprintln!();

        for row in rows {
            if let Some(filter) = EXAMPLE_FILTER.as_ref()
                && !row.description.contains(filter.as_str())
            {
                continue;
            }
            eprintln!("  query example: {:?}", row.description);
            let query = Query::new(&row.language, row.pattern).unwrap();
            for (pattern, capture, expected_quantifier) in row.capture_quantifiers {
                let index = query.capture_index_for_name(capture).unwrap();
                let actual_quantifier = query.capture_quantifiers(*pattern)[index as usize];
                assert_eq!(
                    actual_quantifier,
                    *expected_quantifier,
                    "Description: {}, Pattern: {:?}, expected quantifier of @{} to be {:?} instead of {:?}",
                    row.description,
                    row.pattern
                        .split_ascii_whitespace()
                        .collect::<Vec<_>>()
                        .join(" "),
                    capture,
                    *expected_quantifier,
                    actual_quantifier,
                );
            }
        }
    });
}

#[test]
fn test_query_quantified_captures() {
    struct Row {
        description: &'static str,
        language: Language,
        code: &'static str,
        pattern: &'static str,
        captures: &'static [(&'static str, &'static str)],
    }

    // #[rustfmt::skip]
    let rows = &[
        Row {
            description: "doc comments where all must match the prefix",
            language: get_language("c"),
            code: indoc! {"
            /// foo
            /// bar
            /// baz

            void main() {}

            /// qux
            /// quux
            // quuz
        "},
            pattern: r#"
                ((comment)+ @comment.documentation
                  (#match? @comment.documentation "^///"))
            "#,
            captures: &[
                ("comment.documentation", "/// foo"),
                ("comment.documentation", "/// bar"),
                ("comment.documentation", "/// baz"),
            ],
        },
        Row {
            description: "doc comments where one must match the prefix",
            language: get_language("c"),
            code: indoc! {"
            /// foo
            /// bar
            /// baz

            void main() {}

            /// qux
            /// quux
            // quuz
        "},
            pattern: r#"
                ((comment)+ @comment.documentation
                  (#any-match? @comment.documentation "^///"))
            "#,
            captures: &[
                ("comment.documentation", "/// foo"),
                ("comment.documentation", "/// bar"),
                ("comment.documentation", "/// baz"),
                ("comment.documentation", "/// qux"),
                ("comment.documentation", "/// quux"),
                ("comment.documentation", "// quuz"),
            ],
        },
        Row {
            description: "multiple quantifiers should not hang query parsing",
            language: get_language("c"),
            code: indoc! {"
            // foo
            // bar
            // baz
        "},
            pattern: r"
                ((comment) ?+ @comment)
            ",
            // This should be identical to the `*` quantifier.
            captures: &[
                ("comment", "// foo"),
                ("comment", "// foo"),
                ("comment", "// foo"),
                ("comment", "// bar"),
                ("comment", "// baz"),
            ],
        },
    ];

    allocations::record(|| {
        for row in rows {
            eprintln!("  quantified query example: {:?}", row.description);

            let mut parser = Parser::new();
            parser.set_language(&row.language).unwrap();
            let tree = parser.parse(row.code, None).unwrap();

            let query = Query::new(&row.language, row.pattern).unwrap();

            let mut cursor = QueryCursor::new();
            let matches = cursor.captures(&query, tree.root_node(), row.code.as_bytes());

            assert_eq!(collect_captures(matches, &query, row.code), row.captures);
        }
    });
}

#[test]
fn test_query_max_start_depth() {
    struct Row {
        description: &'static str,
        pattern: &'static str,
        depth: u32,
        matches: &'static [(usize, &'static [(&'static str, &'static str)])],
    }

    let source = indoc! {"
        if (a1 && a2) {
            if (b1 && b2) { }
            if (c) { }
        }
        if (d) {
            if (e1 && e2) { }
            if (f) { }
        }
    "};

    #[rustfmt::skip]
    let rows = &[
        Row {
            description: "depth 0: match translation unit",
            depth: 0,
            pattern: r"
                (translation_unit) @capture
            ",
            matches: &[
                (0, &[("capture", "if (a1 && a2) {\n    if (b1 && b2) { }\n    if (c) { }\n}\nif (d) {\n    if (e1 && e2) { }\n    if (f) { }\n}\n")]),
            ]
        },
        Row {
            description: "depth 0: match none",
            depth: 0,
            pattern: r"
                (if_statement) @capture
            ",
            matches: &[]
        },
        Row {
            description: "depth 1: match 2 if statements at the top level",
            depth: 1,
            pattern: r"
                (if_statement) @capture
            ",
            matches : &[
                (0, &[("capture", "if (a1 && a2) {\n    if (b1 && b2) { }\n    if (c) { }\n}")]),
                (0, &[("capture", "if (d) {\n    if (e1 && e2) { }\n    if (f) { }\n}")]),
            ]
        },
        Row {
            description: "depth 1 with deep pattern: match the only the first if statement",
            depth: 1,
            pattern: r"
                (if_statement
                    condition: (parenthesized_expression
                        (binary_expression)
                    )
                ) @capture
            ",
            matches: &[
                (0, &[("capture", "if (a1 && a2) {\n    if (b1 && b2) { }\n    if (c) { }\n}")]),
            ]
        },
        Row {
            description: "depth 3 with deep pattern: match all if statements with a binexpr condition",
            depth: 3,
            pattern: r"
                (if_statement
                    condition: (parenthesized_expression
                        (binary_expression)
                    )
                ) @capture
            ",
            matches: &[
                (0, &[("capture", "if (a1 && a2) {\n    if (b1 && b2) { }\n    if (c) { }\n}")]),
                (0, &[("capture", "if (b1 && b2) { }")]),
                (0, &[("capture", "if (e1 && e2) { }")]),
            ]
        },
    ];

    allocations::record(|| {
        let language = get_language("c");
        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(source, None).unwrap();
        let mut cursor = QueryCursor::new();

        for row in rows {
            eprintln!("  query example: {:?}", row.description);

            let query = Query::new(&language, row.pattern).unwrap();
            cursor.set_max_start_depth(Some(row.depth));

            let matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
            let expected = row
                .matches
                .iter()
                .map(|x| (x.0, x.1.to_vec()))
                .collect::<Vec<_>>();

            assert_eq!(collect_matches(matches, &query, source), expected);
        }
    });
}

#[test]
fn test_query_error_does_not_oob() {
    let language = get_language("javascript");

    assert_eq!(
        Query::new(&language, "(clas").unwrap_err(),
        QueryError {
            row: 0,
            offset: 1,
            column: 1,
            kind: QueryErrorKind::NodeType,
            message: "\"clas\"".to_string()
        }
    );
}

#[test]
fn test_consecutive_zero_or_modifiers() {
    let language = get_language("javascript");
    let mut parser = Parser::new();
    parser.set_language(&language).unwrap();

    let zero_source = "";
    let three_source = "/**/ /**/ /**/";

    let zero_tree = parser.parse(zero_source, None).unwrap();
    let three_tree = parser.parse(three_source, None).unwrap();

    let tests = [
        "(comment)*** @capture",
        "(comment)??? @capture",
        "(comment)*?* @capture",
        "(comment)?*? @capture",
    ];

    for test in tests {
        let query = Query::new(&language, test).unwrap();

        let mut cursor = QueryCursor::new();
        let mut matches = cursor.matches(&query, zero_tree.root_node(), zero_source.as_bytes());
        assert!(matches.next().is_some());

        let mut cursor = QueryCursor::new();
        let mut matches = cursor.matches(&query, three_tree.root_node(), three_source.as_bytes());

        let mut len_3 = false;
        let mut len_1 = false;

        while let Some(m) = matches.next() {
            if m.captures.len() == 3 {
                len_3 = true;
            }
            if m.captures.len() == 1 {
                len_1 = true;
            }
        }

        assert_eq!(len_3, test.contains('*'));
        assert_eq!(len_1, test.contains("???"));
    }
}

#[test]
fn test_query_max_start_depth_more() {
    struct Row {
        depth: u32,
        matches: &'static [(usize, &'static [(&'static str, &'static str)])],
    }

    let source = indoc! {"
        {
            { }
            {
                { }
            }
        }
    "};

    #[rustfmt::skip]
    let rows = &[
        Row {
            depth: 0,
            matches: &[
                (0, &[("capture", "{\n    { }\n    {\n        { }\n    }\n}")])
            ]
        },
        Row {
            depth: 1,
            matches: &[
                (0, &[("capture", "{\n    { }\n    {\n        { }\n    }\n}")]),
                (0, &[("capture", "{ }")]),
                (0, &[("capture", "{\n        { }\n    }")])
            ]
        },
        Row {
            depth: 2,
            matches: &[
                (0, &[("capture", "{\n    { }\n    {\n        { }\n    }\n}")]),
                (0, &[("capture", "{ }")]),
                (0, &[("capture", "{\n        { }\n    }")]),
                (0, &[("capture", "{ }")]),
            ]
        },
    ];

    allocations::record(|| {
        let language = get_language("c");
        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();
        let tree = parser.parse(source, None).unwrap();
        let mut cursor = QueryCursor::new();
        let query = Query::new(&language, "(compound_statement) @capture").unwrap();

        let mut matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
        let node = matches.next().unwrap().captures[0].node;
        assert_eq!(node.kind(), "compound_statement");

        for row in rows {
            eprintln!("  depth: {}", row.depth);

            cursor.set_max_start_depth(Some(row.depth));

            let matches = cursor.matches(&query, node, source.as_bytes());
            let expected = row
                .matches
                .iter()
                .map(|x| (x.0, x.1.to_vec()))
                .collect::<Vec<_>>();

            assert_eq!(collect_matches(matches, &query, source), expected);
        }
    });
}

#[test]
fn test_grammar_with_aliased_literal_query() {
    // module.exports = grammar({
    //   name: 'test',
    //
    //   rules: {
    //     source: $ => repeat(choice($.compound_statement, $.expansion)),
    //
    //     compound_statement: $ => seq(alias(token(prec(-1, '}')), '}')),
    //
    //     expansion: $ => seq('}'),
    //   },
    // });
    let (parser_name, parser_code) = generate_parser(
        r#"
        {
            "name": "test",
            "rules": {
                "source": {
                    "type": "REPEAT",
                    "content": {
                        "type": "CHOICE",
                        "members": [
                            {
                                "type": "SYMBOL",
                                "name": "compound_statement"
                            },
                            {
                                "type": "SYMBOL",
                                "name": "expansion"
                            }
                        ]
                    }
                },
                "compound_statement": {
                    "type": "SEQ",
                    "members": [
                        {
                            "type": "ALIAS",
                            "content": {
                                "type": "TOKEN",
                                "content": {
                                    "type": "PREC",
                                    "value": -1,
                                    "content": {
                                        "type": "STRING",
                                        "value": "}"
                                    }
                                }
                            },
                            "named": false,
                            "value": "}"
                        }
                    ]
                },
                "expansion": {
                    "type": "SEQ",
                    "members": [
                        {
                            "type": "STRING",
                            "value": "}"
                        }
                    ]
                }
            }
        }
        "#,
    )
    .unwrap();

    let language = get_test_language(&parser_name, &parser_code, None);

    let query = Query::new(
        &language,
        r#"
        (compound_statement "}" @bracket1)
        (expansion "}" @bracket2)
        "#,
    );

    assert!(query.is_ok());
}

#[test]
fn test_query_with_first_child_in_group_is_anchor() {
    let language = get_language("c");
    let source_code = r"void fun(int a, char b, int c) { };";
    let query = r#"
            (parameter_list
              .
              ((parameter_declaration) @constant
                (#match? @constant "^int")))"#;
    let query = Query::new(&language, query).unwrap();
    assert_query_matches(
        &language,
        &query,
        source_code,
        &[(0, vec![("constant", "int a")])],
    );
}

// This test needs be executed with UBSAN enabled to check for regressions:
// ```
// UBSAN_OPTIONS="halt_on_error=1" \
// CFLAGS="-fsanitize=undefined"   \
// RUSTFLAGS="-lubsan"             \
// cargo test --target $(rustc -vV | sed -nr 's/^host: //p') -- --test-threads 1
// ```
#[test]
fn test_query_compiler_oob_access() {
    let language = get_language("java");
    // UBSAN should not report any OOB access
    assert!(Query::new(&language, "(package_declaration _ (_) @name _)").is_ok());
}

#[test]
fn test_query_wildcard_with_immediate_first_child() {
    let language = get_language("javascript");
    let query = Query::new(&language, "(_ . (identifier) @firstChild)").unwrap();
    let source = "function name(one, two, three) { }";

    assert_query_matches(
        &language,
        &query,
        source,
        &[
            (0, vec![("firstChild", "name")]),
            (0, vec![("firstChild", "one")]),
        ],
    );
}

#[test]
fn test_query_on_empty_source_code() {
    let language = get_language("javascript");
    let source_code = "";
    let query = "(program) @program";
    let query = Query::new(&language, query).unwrap();
    assert_query_matches(
        &language,
        &query,
        source_code,
        &[(0, vec![("program", "")])],
    );
}

#[test]
fn test_query_execution_with_timeout() {
    let language = get_language("javascript");
    let mut parser = Parser::new();
    parser.set_language(&language).unwrap();

    let source_code = "function foo() { while (true) { } }\n".repeat(1000);
    let tree = parser.parse(&source_code, None).unwrap();

    let query = Query::new(&language, "(function_declaration) @function").unwrap();
    let start_time = std::time::Instant::now();
    let mut progress_callback = |_: &QueryCursorState| {
        if start_time.elapsed().as_micros() > 1000 {
            ControlFlow::Break(())
        } else {
            ControlFlow::Continue(())
        }
    };
    let mut cursor = QueryCursor::new();
    let matches = cursor
        .matches_with_options(
            &query,
            tree.root_node(),
            source_code.as_bytes(),
            QueryCursorOptions::new().progress_callback(&mut progress_callback),
        )
        .count();
    assert!(matches < 1000);

    let matches = cursor
        .matches(&query, tree.root_node(), source_code.as_bytes())
        .count();
    assert_eq!(matches, 1000);
}

#[test]
fn test_query_progress_callback_lives_as_long_as_matches() {
    let language = get_language("javascript");
    let mut parser = Parser::new();
    parser.set_language(&language).unwrap();

    let source_code = "function foo() {}\n".repeat(1000);
    let tree = parser.parse(&source_code, None).unwrap();
    let query = Query::new(&language, "(function_declaration) @function").unwrap();

    let mut cursor = QueryCursor::new();
    let mut callback_was_called = false;
    let mut progress_callback = |_: &QueryCursorState| {
        callback_was_called = true;
        ControlFlow::Continue(())
    };
    let matches = cursor.matches_with_options(
        &query,
        tree.root_node(),
        source_code.as_bytes(),
        QueryCursorOptions::new().progress_callback(&mut progress_callback),
    );

    assert_eq!(matches.count(), 1000);
    assert!(callback_was_called);
}

#[test]
fn test_query_execution_with_points_causing_underflow() {
    let language = get_language("rust");
    let mut parser = Parser::new();
    parser.set_language(&language).unwrap();

    #[expect(
        clippy::literal_string_with_formatting_args,
        reason = "raw string contains format syntax as literal code"
    )]
    let code = r#"fn main() {
    println!("{:?}", foo());
}"#;
    parser
        .set_included_ranges(&[Range {
            start_byte: 24,
            end_byte: 39,
            start_point: Point::new(0, 0), // 5, 12
            end_point: Point::new(0, 0),   // 5, 27
        }])
        .unwrap();

    let query = Query::new(&language, "(call_expression) @cap").unwrap();
    let mut cursor = QueryCursor::new();

    let mut tree = parser.parse(code, None).unwrap();

    let matches = {
        let root_node = tree.root_node();
        let matches = cursor.matches(&query, root_node, code.as_bytes());
        collect_matches(matches, &query, code)
            .into_iter()
            .map(|(i, m)| {
                (
                    i,
                    m.into_iter()
                        .map(|(k, v)| (k.to_string(), v.to_string()))
                        .collect::<Vec<_>>(),
                )
            })
            .collect::<Vec<_>>()
    };

    tree.edit(&InputEdit {
        start_byte: 40,
        old_end_byte: 40,
        new_end_byte: 41,
        start_position: Point::new(1, 28),
        old_end_position: Point::new(1, 28),
        new_end_position: Point::new(2, 0),
    });

    let tree2 = parser.parse(code, Some(&tree)).unwrap();

    let matches2 = {
        let root_node = tree2.root_node();
        let matches = cursor.matches(&query, root_node, code.as_bytes());
        collect_matches(matches, &query, code)
            .into_iter()
            .map(|(i, m)| {
                (
                    i,
                    m.into_iter()
                        .map(|(k, v)| (k.to_string(), v.to_string()))
                        .collect::<Vec<_>>(),
                )
            })
            .collect::<Vec<_>>()
    };

    assert_eq!(matches, matches2);
}

#[test]
fn test_wildcard_behavior_before_anchor() {
    let language = get_language("python");
    let mut parser = Parser::new();
    parser.set_language(&language).unwrap();

    let source = "
        (a, b)
        (c, d,)
    ";

    //  In this query, we're targeting any *named* node immediately before a closing parenthesis.
    let query = Query::new(&language, r#"(tuple (_) @last . ")" .) @match"#).unwrap();
    assert_query_matches(
        &language,
        &query,
        source,
        &[
            (0, vec![("match", "(a, b)"), ("last", "b")]),
            (0, vec![("match", "(c, d,)"), ("last", "d")]),
        ],
    );

    // In this query, we're targeting *any* node immediately before a closing
    // parenthesis.
    let query = Query::new(&language, r#"(tuple _ @last . ")" .) @match"#).unwrap();
    assert_query_matches(
        &language,
        &query,
        source,
        &[
            (0, vec![("match", "(a, b)"), ("last", "b")]),
            (0, vec![("match", "(c, d,)"), ("last", ",")]),
        ],
    );
}

#[test]
fn test_pattern_alternatives_follow_last_child_constraint() {
    let language = get_language("rust");
    let mut parser = Parser::new();
    parser.set_language(&language).unwrap();

    let code = "
fn f() {
    if a {} // <- should NOT match
    if b {}
}";

    let tree = parser.parse(code, None).unwrap();
    let mut cursor = QueryCursor::new();

    let query = Query::new(
        &language,
        "(block
        [
            (type_cast_expression)
            (expression_statement)
        ] @last
        .
        )",
    )
    .unwrap();

    let matches = {
        let root_node = tree.root_node();
        let matches = cursor.matches(&query, root_node, code.as_bytes());
        collect_matches(matches, &query, code)
            .into_iter()
            .map(|(i, m)| {
                (
                    i,
                    m.into_iter()
                        .map(|(k, v)| (k.to_string(), v.to_string()))
                        .collect::<Vec<_>>(),
                )
            })
            .collect::<Vec<_>>()
    };

    let flipped_query = Query::new(
        &language,
        "(block
        [
            (expression_statement)
            (type_cast_expression)
        ] @last
        .
        )",
    )
    .unwrap();

    let flipped_matches = {
        let root_node = tree.root_node();
        let matches = cursor.matches(&flipped_query, root_node, code.as_bytes());
        collect_matches(matches, &flipped_query, code)
            .into_iter()
            .map(|(i, m)| {
                (
                    i,
                    m.into_iter()
                        .map(|(k, v)| (k.to_string(), v.to_string()))
                        .collect::<Vec<_>>(),
                )
            })
            .collect::<Vec<_>>()
    };

    assert_eq!(
        matches,
        vec![(0, vec![(String::from("last"), String::from("if b {}"))])]
    );
    assert_eq!(matches, flipped_matches);
}

#[test]
fn test_wildcard_parent_allows_fallible_child_patterns() {
    let language = get_language("javascript");
    let mut parser = Parser::new();
    parser.set_language(&language).unwrap();

    let source_code = r#"
function foo() {
    "bar"
}
    "#;

    let query = Query::new(
        &language,
        "(function_declaration
          (_
            (expression_statement)
          )
        ) @part",
    )
    .unwrap();

    assert_query_matches(
        &language,
        &query,
        source_code,
        &[(0, vec![("part", "function foo() {\n    \"bar\"\n}")])],
    );
}

#[test]
fn test_unfinished_captures_are_not_definite_with_pending_anchors() {
    let language = get_language("javascript");
    let mut parser = Parser::new();
    parser.set_language(&language).unwrap();

    let source_code = "
const foo = [
  1, 2, 3
]
";

    let tree = parser.parse(source_code, None).unwrap();
    let query = Query::new(&language, r#"(array (_) @foo . "]")"#).unwrap();
    let mut matches_cursor = QueryCursor::new();
    let mut captures_cursor = QueryCursor::new();

    let captures = captures_cursor.captures(&query, tree.root_node(), source_code.as_bytes());
    let captures = collect_captures(captures, &query, source_code);

    let matches = matches_cursor.matches(&query, tree.root_node(), source_code.as_bytes());
    let matches = collect_matches(matches, &query, source_code);

    assert_eq!(captures, vec![("foo", "3")]);
    assert_eq!(matches.len(), 1);
    assert_eq!(matches[0].1, captures);
}

#[test]
fn test_query_with_predicate_causing_oob_access() {
    let language = get_language("rust");

    let query = "(call_expression
     function: (scoped_identifier
       path: (scoped_identifier (identifier) @_regex (#any-of? @_regex \"Regex\" \"RegexBuilder\") .))
     (#set! injection.language \"regex\"))";
    Query::new(&language, query).unwrap();
}

#[test]
fn test_query_with_anonymous_error_node() {
    let language = get_test_fixture_language("anonymous_error");
    let mut parser = Parser::new();
    parser.set_language(&language).unwrap();

    let source = "ERROR";

    let tree = parser.parse(source, None).unwrap();
    let query = Query::new(
        &language,
        r#"
          "ERROR" @error
          (document "ERROR" @error)
        "#,
    )
    .unwrap();
    let mut cursor = QueryCursor::new();
    let matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
    let matches = collect_matches(matches, &query, source);

    assert_eq!(
        matches,
        vec![(1, vec![("error", "ERROR")]), (0, vec![("error", "ERROR")])]
    );
}

#[test]
fn test_query_allows_error_nodes_with_children() {
    allocations::record(|| {
        let language = get_language("cpp");

        let code = "SomeStruct foo{.bar{}};";

        let mut parser = Parser::new();
        parser.set_language(&language).unwrap();

        let tree = parser.parse(code, None).unwrap();
        let root = tree.root_node();

        let query = Query::new(&language, "(initializer_list (ERROR) @error)").unwrap();
        let mut cursor = QueryCursor::new();

        let matches = cursor.matches(&query, root, code.as_bytes());
        let matches = collect_matches(matches, &query, code);
        assert_eq!(matches, &[(0, vec![("error", ".bar")])]);
    });
}

#[test]
fn test_query_assertion_on_unreachable_node_with_child() {
    // The `await_binding` rule is unreachable because it has a lower precedence than
    // `identifier`, so we'll always reduce to an expression of type `identifier`
    // instead whenever we see the token `await` followed by an identifier.
    //
    // A query that tries to capture the `await` token in the `await_binding` rule
    // should not cause an assertion failure during query analysis.
    let grammar = r#"
export default grammar({
  name: "query_assertion_crash",

  rules: {
    source_file: $ => repeat($.expression),

    expression: $ => choice(
      $.await_binding,
      $.await_expr,
      $.equal_expr,
      prec(3, $.identifier),
    ),

    await_binding: $ => prec(1, seq('await', $.identifier, '=', $.expression)),

    await_expr: $ => prec(1, seq('await', $.expression)),

    equal_expr: $ => prec.right(2, seq($.expression, '=', $.expression)),

    identifier: _ => /[a-z]+/,
  }
});
    "#;

    let file = tempfile::NamedTempFile::with_suffix(".js").unwrap();
    std::fs::write(file.path(), grammar).unwrap();

    let grammar_json = load_grammar_file(file.path(), None).unwrap();

    let (parser_name, parser_code) = generate_parser(&grammar_json).unwrap();

    let language = get_test_language(&parser_name, &parser_code, None);

    let query_result = Query::new(&language, r#"(await_binding "await")"#);

    assert!(query_result.is_err());
    assert_eq!(
        query_result.unwrap_err(),
        QueryError {
            kind: QueryErrorKind::Structure,
            row: 0,
            offset: 0,
            column: 0,
            message: ["(await_binding \"await\")", "^"].join("\n"),
        }
    );
}

#[test]
fn test_query_supertype_with_anonymous_node() {
    let grammar = r#"
export default grammar({
  name: "supertype_anonymous_test",

  extras: $ => [/\s/, $.comment],

  supertypes: $ => [$.expression],

  word: $ => $.identifier,

  rules: {
    source_file: $ => repeat($.expression),

    expression: $ => choice(
      $.function_call,
      '()' // an empty tuple, which should be queryable with the supertype syntax
    ),

    function_call: $ => seq($.identifier, '()'),

    identifier: _ => /[a-zA-Z_][a-zA-Z0-9_]*/,

    comment: _ => token(seq('//', /.*/)),
  }
});
    "#;

    let file = tempfile::NamedTempFile::with_suffix(".js").unwrap();
    std::fs::write(file.path(), grammar).unwrap();

    let grammar_json = load_grammar_file(file.path(), None).unwrap();

    let (parser_name, parser_code) = generate_parser(&grammar_json).unwrap();

    let language = get_test_language(&parser_name, &parser_code, None);

    let query_result = Query::new(&language, r#"(expression/"()") @tuple"#);

    assert!(query_result.is_ok());

    let query = query_result.unwrap();

    let source = "foo()\n()";

    assert_query_matches(&language, &query, source, &[(0, vec![("tuple", "()")])]);
}

#[test]
fn test_last_child_anchor_looks_past_hidden_repeat() {
    let language = get_test_fixture_language("last_child_anchor_past_hidden_repeat");

    let source = "T a.b.c\nL a.b.c\nN a!.b!.c\n";

    let query = Query::new(
        &language,
        "
        (trailing_sep (name) @last .)
        (leading_sep (name) @last .)
        (trailing_named (name) @last .)
        ",
    )
    .unwrap();

    assert_query_matches(
        &language,
        &query,
        source,
        &[
            (0, vec![("last", "c")]),
            (1, vec![("last", "c")]),
            (2, vec![("last", "c")]),
        ],
    );

    let query = Query::new(
        &language,
        "
        (trailing_sep . (name) @first)
        (trailing_sep (name) @a . (name) @b)
        ",
    )
    .unwrap();

    assert_query_matches(
        &language,
        &query,
        source,
        &[
            (0, vec![("first", "a")]),
            (1, vec![("a", "a"), ("b", "b")]),
            (1, vec![("a", "b"), ("b", "c")]),
        ],
    );
}

#[test]
fn test_last_child_anchor_looks_past_hidden_node() {
    allocations::record(|| {
        let language = get_language("c");

        let query = Query::new(&language, "(translation_unit (_) @last .)").unwrap();

        let source = "enum E { A };\nint x;\nint y;\n";

        assert_query_matches(&language, &query, source, &[(0, vec![("last", "int y;")])]);
    });
}
