tree_sitter_graph/
parse_error.rs

1// -*- coding: utf-8 -*-
2// ------------------------------------------------------------------------------------------------
3// Copyright © 2022, tree-sitter authors.
4// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6// ------------------------------------------------------------------------------------------------
7
8//! Data types and functions for finding and displaying tree-sitter parse errors.
9
10#[cfg(feature = "term-colors")]
11use colored::Colorize;
12use std::ops::Range;
13use std::path::Path;
14use tree_sitter::Node;
15use tree_sitter::Tree;
16
17/// Parse error for tree-sitter tree
18#[derive(Debug)]
19pub enum ParseError<'tree> {
20    /// Error representing missing syntax
21    Missing(Node<'tree>),
22    /// Error representing unexpected syntax
23    Unexpected(Node<'tree>),
24}
25
26impl<'tree> ParseError<'tree> {
27    /// Return the first parse error in the given tree, if it exists.
28    pub fn first(tree: &Tree) -> Option<ParseError> {
29        let mut errors = Vec::new();
30        find_errors(tree, &mut errors, true);
31        errors.into_iter().next()
32    }
33
34    /// Return the tree and the first parse error in the given tree, if it exists.
35    /// This returns a type, combining the tree and the error, that can be moved safely,
36    /// which is not possible with a seperate tree and error.
37    pub fn into_first(tree: Tree) -> TreeWithParseErrorOption {
38        TreeWithParseErrorOption::into_first(tree)
39    }
40
41    /// Return all parse errors in the given tree.
42    pub fn all(tree: &'tree Tree) -> Vec<ParseError<'tree>> {
43        let mut errors = Vec::new();
44        find_errors(tree, &mut errors, false);
45        errors
46    }
47
48    /// Return the tree and all parse errors in the given tree.
49    /// This returns a type, combining the tree and the errors, that can be moved safely,
50    /// which is not possible with a seperate tree and errors.
51    pub fn into_all(tree: Tree) -> TreeWithParseErrorVec {
52        TreeWithParseErrorVec::into_all(tree)
53    }
54}
55
56impl<'tree> ParseError<'tree> {
57    pub fn node(&self) -> &Node<'tree> {
58        match self {
59            Self::Missing(node) => node,
60            Self::Unexpected(node) => node,
61        }
62    }
63
64    pub fn display(
65        &'tree self,
66        path: &'tree Path,
67        source: &'tree str,
68    ) -> impl std::fmt::Display + 'tree {
69        ParseErrorDisplay {
70            error: self,
71            path,
72            source,
73        }
74    }
75
76    pub fn display_pretty(
77        &'tree self,
78        path: &'tree Path,
79        source: &'tree str,
80    ) -> impl std::fmt::Display + 'tree {
81        ParseErrorDisplayPretty {
82            error: self,
83            path,
84            source,
85        }
86    }
87}
88
89struct ParseErrorDisplay<'tree> {
90    error: &'tree ParseError<'tree>,
91    path: &'tree Path,
92    source: &'tree str,
93}
94
95impl std::fmt::Display for ParseErrorDisplay<'_> {
96    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
97        let node = self.error.node();
98        write!(
99            f,
100            "{}:{}:{}: ",
101            self.path.display(),
102            node.start_position().row + 1,
103            node.start_position().column + 1
104        )?;
105        let node = match self.error {
106            ParseError::Missing(node) => {
107                write!(f, "missing syntax")?;
108                node
109            }
110            ParseError::Unexpected(node) => {
111                write!(f, "unexpected syntax")?;
112                node
113            }
114        };
115        if node.byte_range().is_empty() {
116            writeln!(f, "")?;
117        } else {
118            let end_byte = self.source[node.byte_range()]
119                .chars()
120                .take_while(|c| *c != '\n')
121                .map(|c| c.len_utf8())
122                .sum();
123            let text = &self.source[node.start_byte()..end_byte];
124            write!(f, ": {}", text)?;
125        }
126        Ok(())
127    }
128}
129
130struct ParseErrorDisplayPretty<'tree> {
131    error: &'tree ParseError<'tree>,
132    path: &'tree Path,
133    source: &'tree str,
134}
135
136impl std::fmt::Display for ParseErrorDisplayPretty<'_> {
137    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
138        let node = match self.error {
139            ParseError::Missing(node) => {
140                writeln!(f, "missing syntax")?;
141                node
142            }
143            ParseError::Unexpected(node) => {
144                writeln!(f, "unexpected syntax")?;
145                node
146            }
147        };
148        if node.byte_range().is_empty() {
149            writeln!(f, "")?;
150        } else {
151            let start_column = node.start_position().column;
152            let end_column = node.start_position().column
153                + self.source[node.byte_range()]
154                    .chars()
155                    .take_while(|c| *c != '\n')
156                    .count();
157            write!(
158                f,
159                "{}",
160                Excerpt::from_source(
161                    self.path,
162                    self.source,
163                    node.start_position().row,
164                    start_column..end_column,
165                    0,
166                ),
167            )?;
168        }
169        Ok(())
170    }
171}
172
173/// Find errors in the given tree and add those to the given errors vector
174fn find_errors<'tree>(tree: &'tree Tree, errors: &mut Vec<ParseError<'tree>>, first_only: bool) {
175    // do not walk the tree unless there actually are errors
176    if !tree.root_node().has_error() {
177        return;
178    }
179
180    let mut cursor = tree.walk();
181    let mut did_visit_children = false;
182    loop {
183        let node = cursor.node();
184        if node.is_error() {
185            errors.push(ParseError::Unexpected(node));
186            if first_only {
187                break;
188            }
189            did_visit_children = true;
190        } else if node.is_missing() {
191            errors.push(ParseError::Missing(node));
192            if first_only {
193                break;
194            }
195            did_visit_children = true;
196        }
197        if did_visit_children {
198            if cursor.goto_next_sibling() {
199                did_visit_children = false;
200            } else if cursor.goto_parent() {
201                did_visit_children = true;
202            } else {
203                break;
204            }
205        } else {
206            if cursor.goto_first_child() {
207                did_visit_children = false;
208            } else {
209                did_visit_children = true;
210            }
211        }
212    }
213    cursor.reset(tree.root_node());
214}
215
216// ------------------------------------------------------------------------------------------------
217// Types to package a tree and parse errors for that tree
218//
219// Parse errors contain `Node` values, that are parametrized by the lifetime `tree of the tree they
220// are part of. It is normally not possible to combine a value and references to that value in a single
221// data type. However, in the case of tree-sitter trees and nodes, the nodes do not point to memory in the
222// tree value, but both point to heap allocated memory. Therefore, moving the tree does not invalidate the
223// node. We use this fact to implement the TreeWithParseError* types.
224//
225// To be able to use these types in errors, we implement Send and Sync. These traits are implemented for
226// Tree, but not for Node. However, since the TreeWithParseError* types contain the tree as well as the nodes,
227// it is okay to implement Send and Sync.
228
229/// A type containing a tree and a parse error
230pub struct TreeWithParseError {
231    tree: Tree,
232    // the 'static lifetime is okay because we own `tree`
233    error: ParseError<'static>,
234}
235
236impl TreeWithParseError {
237    pub fn tree(&self) -> &Tree {
238        &self.tree
239    }
240
241    pub fn into_tree(self) -> Tree {
242        self.tree
243    }
244
245    pub fn error(&self) -> &ParseError {
246        &self.error
247    }
248}
249
250impl std::fmt::Debug for TreeWithParseError {
251    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
252        write!(f, "{:?}", self.error)
253    }
254}
255
256// Send and Sync must be implemented for ParseError -> Node -> ffi::TSTree
257// This is okay because Send and Sync _are_ implemented for Tree, which also holds ffi::TSTree
258unsafe impl Send for TreeWithParseError {}
259unsafe impl Sync for TreeWithParseError {}
260
261/// A type containing a tree and an optional parse error
262pub struct TreeWithParseErrorOption {
263    tree: Tree,
264    // the 'static lifetime is okay because we own `tree`
265    error: Option<ParseError<'static>>,
266}
267
268impl TreeWithParseErrorOption {
269    fn into_first(tree: Tree) -> TreeWithParseErrorOption {
270        let mut errors = Vec::new();
271        find_errors(&tree, &mut errors, true);
272        Self {
273            error: unsafe { std::mem::transmute(errors.into_iter().next()) },
274            tree: tree,
275        }
276    }
277}
278
279impl TreeWithParseErrorOption {
280    pub fn tree(&self) -> &Tree {
281        &self.tree
282    }
283
284    pub fn into_tree(self) -> Tree {
285        self.tree
286    }
287
288    pub fn error(&self) -> &Option<ParseError> {
289        &self.error
290    }
291
292    pub fn into_option(self) -> Option<TreeWithParseError> {
293        match self.error {
294            None => None,
295            Some(error) => Some(TreeWithParseError {
296                tree: self.tree,
297                error,
298            }),
299        }
300    }
301}
302
303impl std::fmt::Debug for TreeWithParseErrorOption {
304    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
305        write!(f, "{:?}", self.error)
306    }
307}
308
309// Send and Sync must be implemented for ParseError -> Node -> ffi::TSTree
310// This is okay because Send and Sync _are_ implemented for Tree, which also holds ffi::TSTree
311unsafe impl Send for TreeWithParseErrorOption {}
312unsafe impl Sync for TreeWithParseErrorOption {}
313
314/// A type containing a tree and parse errors
315pub struct TreeWithParseErrorVec {
316    tree: Tree,
317    // the 'static lifetime is okay because we own `tree`
318    errors: Vec<ParseError<'static>>,
319}
320
321impl TreeWithParseErrorVec {
322    fn into_all(tree: Tree) -> TreeWithParseErrorVec {
323        let mut errors = Vec::new();
324        find_errors(&tree, &mut errors, false);
325        TreeWithParseErrorVec {
326            errors: unsafe { std::mem::transmute(errors) },
327            tree: tree,
328        }
329    }
330}
331
332impl TreeWithParseErrorVec {
333    pub fn tree(&self) -> &Tree {
334        &self.tree
335    }
336
337    pub fn into_tree(self) -> Tree {
338        self.tree
339    }
340
341    pub fn errors(&self) -> &Vec<ParseError> {
342        &self.errors
343    }
344}
345
346impl std::fmt::Debug for TreeWithParseErrorVec {
347    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
348        write!(f, "{:?}", self.errors)
349    }
350}
351
352// Send and Sync must be implemented for ParseError -> Node -> ffi::TSTree
353// This is okay because Send and Sync _are_ implemented for Tree, which also holds ffi::TSTree
354unsafe impl Send for TreeWithParseErrorVec {}
355unsafe impl Sync for TreeWithParseErrorVec {}
356
357//-----------------------------------------------------------------------------
358
359/// Excerpts of source from either the target language file or the tsg rules file.
360pub struct Excerpt<'a> {
361    path: &'a Path,
362    source: Option<&'a str>,
363    row: usize,
364    columns: Range<usize>,
365    indent: usize,
366}
367
368impl<'a> Excerpt<'a> {
369    pub fn from_source(
370        path: &'a Path,
371        source: &'a str,
372        row: usize,
373        mut columns: Range<usize>,
374        indent: usize,
375    ) -> Excerpt<'a> {
376        let source = source.lines().nth(row);
377        columns.end = std::cmp::min(columns.end, source.map(|s| s.len()).unwrap_or_default());
378        Excerpt {
379            path,
380            source,
381            row,
382            columns,
383            indent,
384        }
385    }
386
387    fn gutter_width(&self) -> usize {
388        ((self.row + 1) as f64).log10() as usize + 1
389    }
390}
391
392impl<'a> std::fmt::Display for Excerpt<'a> {
393    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
394        // path and line/col
395        writeln!(
396            f,
397            "{}{}:{}:{}:",
398            " ".repeat(self.indent),
399            header_style(&self.path.to_string_lossy()),
400            header_style(&format!("{}", self.row + 1)),
401            header_style(&format!("{}", self.columns.start + 1)),
402        )?;
403        if let Some(source) = self.source {
404            // first line: line number & source
405            writeln!(
406                f,
407                "{}{}{}{}",
408                " ".repeat(self.indent),
409                prefix_style(&format!("{}", self.row + 1)),
410                prefix_style(" | "),
411                source,
412            )?;
413            // second line: caret
414            writeln!(
415                f,
416                "{}{}{}{}{}",
417                " ".repeat(self.indent),
418                " ".repeat(self.gutter_width()),
419                prefix_style(" | "),
420                " ".repeat(self.columns.start),
421                underline_style(&"^".repeat(self.columns.len())),
422            )?;
423        } else {
424            writeln!(f, "{}{}", " ".repeat(self.indent), "<missing source>",)?;
425        }
426        Ok(())
427    }
428}
429
430// coloring functions
431
432#[cfg(feature = "term-colors")]
433fn header_style(str: &str) -> impl std::fmt::Display {
434    str.bold()
435}
436#[cfg(not(feature = "term-colors"))]
437fn header_style<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
438    str
439}
440
441#[cfg(feature = "term-colors")]
442fn prefix_style(str: &str) -> impl std::fmt::Display {
443    str.dimmed()
444}
445#[cfg(not(feature = "term-colors"))]
446fn prefix_style<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
447    str
448}
449
450#[cfg(feature = "term-colors")]
451fn underline_style(str: &str) -> impl std::fmt::Display {
452    str.green()
453}
454#[cfg(not(feature = "term-colors"))]
455fn underline_style<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
456    str
457}