#[cfg(feature = "term-colors")]
use colored::Colorize;
use std::ops::Range;
use std::path::Path;
use tree_sitter::Node;
use tree_sitter::Tree;
#[derive(Debug)]
pub enum ParseError<'tree> {
Missing(Node<'tree>),
Unexpected(Node<'tree>),
}
impl<'tree> ParseError<'tree> {
pub fn first(tree: &Tree) -> Option<ParseError> {
let mut errors = Vec::new();
find_errors(tree, &mut errors, true);
errors.into_iter().next()
}
pub fn into_first(tree: Tree) -> TreeWithParseErrorOption {
TreeWithParseErrorOption::into_first(tree)
}
pub fn all(tree: &'tree Tree) -> Vec<ParseError<'tree>> {
let mut errors = Vec::new();
find_errors(tree, &mut errors, false);
errors
}
pub fn into_all(tree: Tree) -> TreeWithParseErrorVec {
TreeWithParseErrorVec::into_all(tree)
}
}
impl<'tree> ParseError<'tree> {
pub fn node(&self) -> &Node<'tree> {
match self {
Self::Missing(node) => node,
Self::Unexpected(node) => node,
}
}
pub fn display(
&'tree self,
path: &'tree Path,
source: &'tree str,
) -> impl std::fmt::Display + 'tree {
ParseErrorDisplay {
error: self,
path,
source,
}
}
pub fn display_pretty(
&'tree self,
path: &'tree Path,
source: &'tree str,
) -> impl std::fmt::Display + 'tree {
ParseErrorDisplayPretty {
error: self,
path,
source,
}
}
}
struct ParseErrorDisplay<'tree> {
error: &'tree ParseError<'tree>,
path: &'tree Path,
source: &'tree str,
}
impl std::fmt::Display for ParseErrorDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let node = self.error.node();
write!(
f,
"{}:{}:{}: ",
self.path.display(),
node.start_position().row + 1,
node.start_position().column + 1
)?;
let node = match self.error {
ParseError::Missing(node) => {
write!(f, "missing syntax")?;
node
}
ParseError::Unexpected(node) => {
write!(f, "unexpected syntax")?;
node
}
};
if node.byte_range().is_empty() {
writeln!(f, "")?;
} else {
let end_byte = self.source[node.byte_range()]
.chars()
.take_while(|c| *c != '\n')
.map(|c| c.len_utf8())
.sum();
let text = &self.source[node.start_byte()..end_byte];
write!(f, ": {}", text)?;
}
Ok(())
}
}
struct ParseErrorDisplayPretty<'tree> {
error: &'tree ParseError<'tree>,
path: &'tree Path,
source: &'tree str,
}
impl std::fmt::Display for ParseErrorDisplayPretty<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let node = match self.error {
ParseError::Missing(node) => {
writeln!(f, "missing syntax")?;
node
}
ParseError::Unexpected(node) => {
writeln!(f, "unexpected syntax")?;
node
}
};
if node.byte_range().is_empty() {
writeln!(f, "")?;
} else {
let start_column = node.start_position().column;
let end_column = node.start_position().column
+ self.source[node.byte_range()]
.chars()
.take_while(|c| *c != '\n')
.count();
write!(
f,
"{}",
Excerpt::from_source(
self.path,
self.source,
node.start_position().row,
start_column..end_column,
0,
),
)?;
}
Ok(())
}
}
fn find_errors<'tree>(tree: &'tree Tree, errors: &mut Vec<ParseError<'tree>>, first_only: bool) {
if !tree.root_node().has_error() {
return;
}
let mut cursor = tree.walk();
let mut did_visit_children = false;
loop {
let node = cursor.node();
if node.is_error() {
errors.push(ParseError::Unexpected(node));
if first_only {
break;
}
did_visit_children = true;
} else if node.is_missing() {
errors.push(ParseError::Missing(node));
if first_only {
break;
}
did_visit_children = true;
}
if did_visit_children {
if cursor.goto_next_sibling() {
did_visit_children = false;
} else if cursor.goto_parent() {
did_visit_children = true;
} else {
break;
}
} else {
if cursor.goto_first_child() {
did_visit_children = false;
} else {
did_visit_children = true;
}
}
}
cursor.reset(tree.root_node());
}
pub struct TreeWithParseError {
tree: Tree,
error: ParseError<'static>,
}
impl TreeWithParseError {
pub fn tree(&self) -> &Tree {
&self.tree
}
pub fn into_tree(self) -> Tree {
self.tree
}
pub fn error(&self) -> &ParseError {
&self.error
}
}
impl std::fmt::Debug for TreeWithParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self.error)
}
}
unsafe impl Send for TreeWithParseError {}
unsafe impl Sync for TreeWithParseError {}
pub struct TreeWithParseErrorOption {
tree: Tree,
error: Option<ParseError<'static>>,
}
impl TreeWithParseErrorOption {
fn into_first(tree: Tree) -> TreeWithParseErrorOption {
let mut errors = Vec::new();
find_errors(&tree, &mut errors, true);
Self {
error: unsafe { std::mem::transmute(errors.into_iter().next()) },
tree: tree,
}
}
}
impl TreeWithParseErrorOption {
pub fn tree(&self) -> &Tree {
&self.tree
}
pub fn into_tree(self) -> Tree {
self.tree
}
pub fn error(&self) -> &Option<ParseError> {
&self.error
}
pub fn into_option(self) -> Option<TreeWithParseError> {
match self.error {
None => None,
Some(error) => Some(TreeWithParseError {
tree: self.tree,
error,
}),
}
}
}
impl std::fmt::Debug for TreeWithParseErrorOption {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self.error)
}
}
unsafe impl Send for TreeWithParseErrorOption {}
unsafe impl Sync for TreeWithParseErrorOption {}
pub struct TreeWithParseErrorVec {
tree: Tree,
errors: Vec<ParseError<'static>>,
}
impl TreeWithParseErrorVec {
fn into_all(tree: Tree) -> TreeWithParseErrorVec {
let mut errors = Vec::new();
find_errors(&tree, &mut errors, false);
TreeWithParseErrorVec {
errors: unsafe { std::mem::transmute(errors) },
tree: tree,
}
}
}
impl TreeWithParseErrorVec {
pub fn tree(&self) -> &Tree {
&self.tree
}
pub fn into_tree(self) -> Tree {
self.tree
}
pub fn errors(&self) -> &Vec<ParseError> {
&self.errors
}
}
impl std::fmt::Debug for TreeWithParseErrorVec {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self.errors)
}
}
unsafe impl Send for TreeWithParseErrorVec {}
unsafe impl Sync for TreeWithParseErrorVec {}
pub struct Excerpt<'a> {
path: &'a Path,
source: Option<&'a str>,
row: usize,
columns: Range<usize>,
indent: usize,
}
impl<'a> Excerpt<'a> {
pub fn from_source(
path: &'a Path,
source: &'a str,
row: usize,
mut columns: Range<usize>,
indent: usize,
) -> Excerpt<'a> {
let source = source.lines().nth(row);
columns.end = std::cmp::min(columns.end, source.map(|s| s.len()).unwrap_or_default());
Excerpt {
path,
source,
row,
columns,
indent,
}
}
fn gutter_width(&self) -> usize {
((self.row + 1) as f64).log10() as usize + 1
}
}
impl<'a> std::fmt::Display for Excerpt<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(
f,
"{}{}:{}:{}:",
" ".repeat(self.indent),
header_style(&self.path.to_string_lossy()),
header_style(&format!("{}", self.row + 1)),
header_style(&format!("{}", self.columns.start + 1)),
)?;
if let Some(source) = self.source {
writeln!(
f,
"{}{}{}{}",
" ".repeat(self.indent),
prefix_style(&format!("{}", self.row + 1)),
prefix_style(" | "),
source,
)?;
writeln!(
f,
"{}{}{}{}{}",
" ".repeat(self.indent),
" ".repeat(self.gutter_width()),
prefix_style(" | "),
" ".repeat(self.columns.start),
underline_style(&"^".repeat(self.columns.len())),
)?;
} else {
writeln!(f, "{}{}", " ".repeat(self.indent), "<missing source>",)?;
}
Ok(())
}
}
#[cfg(feature = "term-colors")]
fn header_style(str: &str) -> impl std::fmt::Display {
str.bold()
}
#[cfg(not(feature = "term-colors"))]
fn header_style<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
str
}
#[cfg(feature = "term-colors")]
fn prefix_style(str: &str) -> impl std::fmt::Display {
str.dimmed()
}
#[cfg(not(feature = "term-colors"))]
fn prefix_style<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
str
}
#[cfg(feature = "term-colors")]
fn underline_style(str: &str) -> impl std::fmt::Display {
str.green()
}
#[cfg(not(feature = "term-colors"))]
fn underline_style<'a>(str: &'a str) -> impl std::fmt::Display + 'a {
str
}