fork of https://github.com/tree-sitter/tree-sitter-graph
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> { 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 white_bold(&self.path.to_string_lossy()), 400 white_bold(&format!("{}", self.row + 1)), 401 white_bold(&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 blue(&format!("{}", self.row + 1)), 410 blue(" | "), 411 source, 412 )?; 413 // second line: caret 414 writeln!( 415 f, 416 "{}{}{}{}{}", 417 " ".repeat(self.indent), 418 " ".repeat(self.gutter_width()), 419 blue(" | "), 420 " ".repeat(self.columns.start), 421 green_bold(&"^".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 blue(str: &str) -> impl std::fmt::Display { 434 str.blue() 435} 436#[cfg(not(feature = "term-colors"))] 437fn blue<'a>(str: &'a str) -> impl std::fmt::Display + 'a { 438 str 439} 440 441#[cfg(feature = "term-colors")] 442fn green_bold(str: &str) -> impl std::fmt::Display { 443 str.green().bold() 444} 445#[cfg(not(feature = "term-colors"))] 446fn green_bold<'a>(str: &'a str) -> impl std::fmt::Display + 'a { 447 str 448} 449 450#[cfg(feature = "term-colors")] 451fn white_bold(str: &str) -> impl std::fmt::Display { 452 str.white().bold() 453} 454#[cfg(not(feature = "term-colors"))] 455fn white_bold<'a>(str: &'a str) -> impl std::fmt::Display + 'a { 456 str 457}