From 9111e317196d72be408644eec117bb574b31511d Mon Sep 17 00:00:00 2001 From: cui fliter Date: Fri, 4 Sep 2026 11:25:21 +0800 Subject: [PATCH] fix(cli)!: fix query test capture range matching across lines (#5917) Signed-off-by: cuishuang BREAKING CHANGE: changes the signature of `assert_expected_captures`, which is exposed publicly when tree-sitter-cli is consumed as a library. (cherry picked from commit ee0c20b1f5fa26d0c567d5f438b0ac46644b530b) --- crates/cli/src/query.rs | 4 +++- crates/cli/src/query_testing.rs | 42 ++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/crates/cli/src/query.rs b/crates/cli/src/query.rs index fa14ebe16..5f1fddd04 100644 --- a/crates/cli/src/query.rs +++ b/crates/cli/src/query.rs @@ -142,7 +142,9 @@ pub fn query_file_at_path( }; // Invariant: `test_summary` will always be `Some` when `should_test` is true let test_summary = test_summary.unwrap(); - match query_testing::assert_expected_captures(&results, path, &mut parser, language) { + let assertions = + query_testing::parse_position_comments(&mut parser, language, source_code.as_slice())?; + match query_testing::assert_expected_captures(&results, &assertions) { Ok(assertion_count) => { test_summary.query_results.add_case(TestResult { name: path_name.to_string(), diff --git a/crates/cli/src/query_testing.rs b/crates/cli/src/query_testing.rs index 569584a2f..8c41c8eb5 100644 --- a/crates/cli/src/query_testing.rs +++ b/crates/cli/src/query_testing.rs @@ -1,4 +1,4 @@ -use std::{fs, path::Path, sync::LazyLock}; +use std::sync::LazyLock; use anyhow::{Result, anyhow}; use bstr::{BStr, ByteSlice}; @@ -219,19 +219,14 @@ pub fn parse_position_comments( Ok(result) } -pub fn assert_expected_captures( - infos: &[CaptureInfo], - path: &Path, - parser: &mut Parser, - language: &Language, -) -> Result { - let contents = fs::read_to_string(path)?; - let pairs = parse_position_comments(parser, language, contents.as_bytes())?; - for assertion in &pairs { +pub fn assert_expected_captures(infos: &[CaptureInfo], assertions: &[Assertion]) -> Result { + for assertion in assertions { if let Some(found) = &infos.iter().find(|p| { - assertion.position >= p.start - && (assertion.position.row < p.end.row - || assertion.position.column + assertion.length - 1 < p.end.column) + let assertion_end = Utf8Point::new( + assertion.position.row, + assertion.position.column + assertion.length - 1, + ); + assertion.position >= p.start && assertion_end < p.end }) { if assertion.expected_capture_name != found.name && found.name != "name" { return Err(anyhow!( @@ -250,5 +245,24 @@ pub fn assert_expected_captures( )); } } - Ok(pairs.len()) + Ok(assertions.len()) +} + +#[cfg(test)] +mod tests { + use super::{Assertion, CaptureInfo, Utf8Point, assert_expected_captures}; + + #[test] + fn test_assertion_after_multiline_capture_does_not_match() { + let captures = [CaptureInfo { + name: "foo".to_string(), + start: Utf8Point::new(0, 0), + end: Utf8Point::new(1, 1), + }]; + let assertions = [Assertion::new(2, 0, 1, false, "foo".to_string())]; + + let result = assert_expected_captures(&captures, &assertions); + + assert!(result.is_err()); + } }