Skip to main content

big_code_analysis/output/
dump.rs

1// Per-language metric and AST modules deliberately consume the macro-
2// generated tree-sitter token enums via `use crate::*` and `use Foo::*`
3// inside match expressions — explicit imports would list dozens of
4// variants per arm and obscure the per-language token sets that are the
5// point of these files. Allowed at the module level rather than per
6// function so the per-language impl blocks stay readable.
7#![allow(clippy::enum_glob_use, clippy::ref_option, clippy::wildcard_imports)]
8
9use std::io::Write;
10
11use termcolor::{Color, ColorChoice, StandardStream, StandardStreamLock};
12
13use crate::node::Node;
14use crate::tools::{color, intense_color};
15
16use crate::traits::*;
17
18/// Dumps the `AST` of a code.
19///
20/// Returns a [`Result`] value, when an error occurs.
21///
22/// # Errors
23///
24/// Propagates any [`std::io::Error`] produced by the color-aware
25/// writer that backs `stdout` (broken pipe, write failure, …).
26///
27/// # Examples
28///
29/// ```
30/// use big_code_analysis::{dump_node, tree_sitter, LANG, Node};
31///
32/// let source = b"int a = 42;";
33/// let mut parser = tree_sitter::Parser::new();
34/// parser
35///     .set_language(
36///         &LANG::Cpp
37///             .get_tree_sitter_language()
38///             .expect("cpp feature enabled"),
39///     )
40///     .expect("cpp grammar pinned to a compatible version");
41/// let tree = parser.parse(source, None).expect("parser has a language set");
42/// let root = Node(tree.root_node());
43///
44/// // Dump the AST from the first line of code in a file to the last one
45/// dump_node(source, &root, -1, None, None).unwrap();
46/// ```
47///
48/// [`Result`]: #variant.Result
49pub fn dump_node(
50    code: &[u8],
51    node: &Node,
52    depth: i32,
53    line_start: Option<usize>,
54    line_end: Option<usize>,
55) -> std::io::Result<()> {
56    let stdout = StandardStream::stdout(ColorChoice::Always);
57    let mut stdout = stdout.lock();
58    let ret = dump_tree_helper(
59        code,
60        node,
61        "",
62        true,
63        &mut stdout,
64        depth,
65        &line_start,
66        &line_end,
67    );
68
69    color(&mut stdout, Color::White)?;
70
71    ret
72}
73
74#[allow(clippy::too_many_arguments)]
75fn dump_tree_helper(
76    code: &[u8],
77    node: &Node,
78    prefix: &str,
79    last: bool,
80    stdout: &mut StandardStreamLock,
81    depth: i32,
82    line_start: &Option<usize>,
83    line_end: &Option<usize>,
84) -> std::io::Result<()> {
85    if depth == 0 {
86        return Ok(());
87    }
88
89    let (pref_child, pref) = if node.parent().is_none() {
90        ("", "")
91    } else if last {
92        ("   ", "╰─ ")
93    } else {
94        ("│  ", "├─ ")
95    };
96
97    let node_row = node.start_row() + 1;
98    let mut display = true;
99    if let Some(line_start) = line_start {
100        display = node_row >= *line_start;
101    }
102    if let Some(line_end) = line_end {
103        display = display && node_row <= *line_end;
104    }
105
106    if display {
107        color(stdout, Color::Blue)?;
108        write!(stdout, "{prefix}{pref}")?;
109
110        intense_color(stdout, Color::Yellow)?;
111        write!(stdout, "{{{}:{}}} ", node.kind(), node.kind_id())?;
112
113        color(stdout, Color::White)?;
114        write!(stdout, "from ")?;
115
116        color(stdout, Color::Green)?;
117        let (pos_row, pos_column) = node.start_position();
118        write!(stdout, "({}, {}) ", pos_row + 1, pos_column + 1)?;
119
120        color(stdout, Color::White)?;
121        write!(stdout, "to ")?;
122
123        color(stdout, Color::Green)?;
124        let (pos_row, pos_column) = node.end_position();
125        write!(stdout, "({}, {}) ", pos_row + 1, pos_column + 1)?;
126
127        if node.start_row() == node.end_row() {
128            color(stdout, Color::White)?;
129            write!(stdout, ": ")?;
130
131            intense_color(stdout, Color::Red)?;
132            let code = &code[node.start_byte()..node.end_byte()];
133            if let Ok(code) = str::from_utf8(code) {
134                write!(stdout, "{code} ")?;
135            } else {
136                stdout.write_all(code)?;
137            }
138        }
139
140        writeln!(stdout)?;
141    }
142
143    let count = node.child_count();
144    if count != 0 {
145        let prefix = format!("{prefix}{pref_child}");
146        let mut i = count;
147        let mut cursor = node.cursor();
148        cursor.goto_first_child();
149
150        loop {
151            i -= 1;
152            dump_tree_helper(
153                code,
154                &cursor.node(),
155                &prefix,
156                i == 0,
157                stdout,
158                depth - 1,
159                line_start,
160                line_end,
161            )?;
162            if !cursor.goto_next_sibling() {
163                break;
164            }
165        }
166    }
167
168    Ok(())
169}
170
171/// Configuration options for dumping the `AST` of a code.
172#[derive(Debug)]
173pub struct DumpCfg {
174    /// The first line of code to dump
175    ///
176    /// If `None`, the code is dumped from the first line of code
177    /// in a file
178    pub line_start: Option<usize>,
179    /// The last line of code to dump
180    ///
181    /// If `None`, the code is dumped until the last line of code
182    /// in a file
183    pub line_end: Option<usize>,
184}
185
186/// Type tag identifying the AST-dump action; carries no data.
187pub struct Dump {
188    _guard: (),
189}
190
191impl Callback for Dump {
192    type Res = std::io::Result<()>;
193    type Cfg = DumpCfg;
194
195    fn call<T: ParserTrait>(cfg: Self::Cfg, parser: &T) -> Self::Res {
196        dump_node(
197            parser.get_code(),
198            &parser.get_root(),
199            -1,
200            cfg.line_start,
201            cfg.line_end,
202        )
203    }
204}
205
206#[cfg(test)]
207#[allow(
208    clippy::float_cmp,
209    clippy::cast_precision_loss,
210    clippy::cast_possible_truncation,
211    clippy::cast_sign_loss,
212    clippy::similar_names,
213    clippy::doc_markdown,
214    clippy::needless_raw_string_hashes,
215    clippy::too_many_lines
216)]
217mod tests {
218    use std::path::PathBuf;
219
220    use crate::{CppParser, ParserTrait};
221
222    use super::*;
223
224    #[test]
225    fn dump_node_non_utf8_source_does_not_panic() {
226        // Regression: `stdout.write_all(code).unwrap()` panicked when the raw-bytes
227        // fallback branch was taken for non-UTF-8 source content.
228        let code = b"char c = '\xff';";
229        let path = PathBuf::from("test.c");
230        let parser = CppParser::new(code.to_vec(), &path, None);
231        let root = parser.get_root();
232        assert!(dump_node(code, &root, -1, None, None).is_ok());
233    }
234}