tree_sitter_cli/
query.rs

1use std::{
2    fs,
3    io::{self, Write},
4    ops::Range,
5    path::Path,
6    time::Instant,
7};
8
9use anstyle::AnsiColor;
10use anyhow::{Context, Result};
11use streaming_iterator::StreamingIterator;
12use tree_sitter::{Language, Parser, Point, Query, QueryCursor};
13
14use crate::{
15    query_testing::{self, to_utf8_point},
16    test::paint,
17};
18
19#[allow(clippy::too_many_arguments)]
20pub fn query_file_at_path(
21    language: &Language,
22    path: &Path,
23    name: &str,
24    query_path: &Path,
25    ordered_captures: bool,
26    byte_range: Option<Range<usize>>,
27    point_range: Option<Range<Point>>,
28    should_test: bool,
29    quiet: bool,
30    print_time: bool,
31    stdin: bool,
32) -> Result<()> {
33    let stdout = io::stdout();
34    let mut stdout = stdout.lock();
35
36    let query_source = fs::read_to_string(query_path)
37        .with_context(|| format!("Error reading query file {query_path:?}"))?;
38    let query = Query::new(language, &query_source).with_context(|| "Query compilation failed")?;
39
40    let mut query_cursor = QueryCursor::new();
41    if let Some(range) = byte_range {
42        query_cursor.set_byte_range(range);
43    }
44    if let Some(range) = point_range {
45        query_cursor.set_point_range(range);
46    }
47
48    let mut parser = Parser::new();
49    parser.set_language(language)?;
50
51    let mut results = Vec::new();
52
53    if !should_test && !stdin {
54        writeln!(&mut stdout, "{name}")?;
55    }
56
57    let source_code =
58        fs::read(path).with_context(|| format!("Error reading source file {path:?}"))?;
59    let tree = parser.parse(&source_code, None).unwrap();
60
61    let start = Instant::now();
62    if ordered_captures {
63        let mut captures = query_cursor.captures(&query, tree.root_node(), source_code.as_slice());
64        while let Some((mat, capture_index)) = captures.next() {
65            let capture = mat.captures[*capture_index];
66            let capture_name = &query.capture_names()[capture.index as usize];
67            if !quiet && !should_test {
68                writeln!(
69                        &mut stdout,
70                        "    pattern: {:>2}, capture: {} - {capture_name}, start: {}, end: {}, text: `{}`",
71                        mat.pattern_index,
72                        capture.index,
73                        capture.node.start_position(),
74                        capture.node.end_position(),
75                        capture.node.utf8_text(&source_code).unwrap_or("")
76                    )?;
77            }
78            results.push(query_testing::CaptureInfo {
79                name: (*capture_name).to_string(),
80                start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
81                end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
82            });
83        }
84    } else {
85        let mut matches = query_cursor.matches(&query, tree.root_node(), source_code.as_slice());
86        while let Some(m) = matches.next() {
87            if !quiet && !should_test {
88                writeln!(&mut stdout, "  pattern: {}", m.pattern_index)?;
89            }
90            for capture in m.captures {
91                let start = capture.node.start_position();
92                let end = capture.node.end_position();
93                let capture_name = &query.capture_names()[capture.index as usize];
94                if !quiet && !should_test {
95                    if end.row == start.row {
96                        writeln!(
97                                &mut stdout,
98                                "    capture: {} - {capture_name}, start: {start}, end: {end}, text: `{}`",
99                                capture.index,
100                                capture.node.utf8_text(&source_code).unwrap_or("")
101                            )?;
102                    } else {
103                        writeln!(
104                            &mut stdout,
105                            "    capture: {capture_name}, start: {start}, end: {end}",
106                        )?;
107                    }
108                }
109                results.push(query_testing::CaptureInfo {
110                    name: (*capture_name).to_string(),
111                    start: to_utf8_point(capture.node.start_position(), source_code.as_slice()),
112                    end: to_utf8_point(capture.node.end_position(), source_code.as_slice()),
113                });
114            }
115        }
116    }
117    if query_cursor.did_exceed_match_limit() {
118        writeln!(
119            &mut stdout,
120            "  WARNING: Query exceeded maximum number of in-progress captures!"
121        )?;
122    }
123    if should_test {
124        let path_name = if stdin {
125            "stdin"
126        } else {
127            Path::new(&path).file_name().unwrap().to_str().unwrap()
128        };
129        match query_testing::assert_expected_captures(&results, path, &mut parser, language) {
130            Ok(assertion_count) => {
131                println!(
132                    "  ✓ {} ({} assertions)",
133                    paint(Some(AnsiColor::Green), path_name),
134                    assertion_count
135                );
136            }
137            Err(e) => {
138                println!("  ✗ {}", paint(Some(AnsiColor::Red), path_name));
139                return Err(e);
140            }
141        }
142    }
143    if print_time {
144        writeln!(&mut stdout, "{:?}", start.elapsed())?;
145    }
146
147    Ok(())
148}