diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs index 3186eaf1a86..f92181b867c 100644 --- a/crates/ra_editor/src/lib.rs +++ b/crates/ra_editor/src/lib.rs @@ -31,6 +31,7 @@ use ra_syntax::{ algo::find_leaf_at_offset, ast::{self, AstNode, NameOwner}, File, + Location, SyntaxKind::{self, *}, SyntaxNodeRef, TextRange, TextUnit, }; @@ -100,11 +101,18 @@ pub fn highlight(file: &File) -> Vec<HighlightedRange> { } pub fn diagnostics(file: &File) -> Vec<Diagnostic> { + fn location_to_range(location: Location) -> TextRange { + match location { + Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), + Location::Range(range) => range, + } + } + file.errors() .into_iter() .map(|err| Diagnostic { - range: TextRange::offset_len(err.offset, 1.into()), - msg: "Syntax Error: ".to_string() + &err.msg, + range: location_to_range(err.location()), + msg: format!("Syntax Error: {}", err), }) .collect() } diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index acc2d960333..1230028257b 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs @@ -54,7 +54,7 @@ pub use crate::{ rowan::{SmolStr, TextRange, TextUnit}, syntax_kinds::SyntaxKind, yellow::{ - Direction, OwnedRoot, RefRoot, SyntaxError, SyntaxNode, SyntaxNodeRef, TreeRoot, WalkEvent, + Direction, OwnedRoot, RefRoot, SyntaxError, SyntaxNode, SyntaxNodeRef, TreeRoot, WalkEvent, Location, }, }; diff --git a/crates/ra_syntax/src/parser_impl/event.rs b/crates/ra_syntax/src/parser_impl/event.rs index 79fa21389d1..bf9c1cef095 100644 --- a/crates/ra_syntax/src/parser_impl/event.rs +++ b/crates/ra_syntax/src/parser_impl/event.rs @@ -13,6 +13,11 @@ use crate::{ SmolStr, SyntaxKind::{self, *}, TextRange, TextUnit, + yellow::syntax_error::{ + ParseError, + SyntaxError, + SyntaxErrorKind, + }, }; use std::mem; @@ -75,7 +80,7 @@ pub(crate) enum Event { }, Error { - msg: String, + msg: ParseError, }, } @@ -157,7 +162,10 @@ impl<'a, S: Sink> EventProcessor<'a, S> { .sum::<TextUnit>(); self.leaf(kind, len, n_raw_tokens); } - Event::Error { msg } => self.sink.error(msg, self.text_pos), + Event::Error { msg } => self.sink.error(SyntaxError::new( + SyntaxErrorKind::ParseError(msg), + self.text_pos, + )), } } self.sink diff --git a/crates/ra_syntax/src/parser_impl/mod.rs b/crates/ra_syntax/src/parser_impl/mod.rs index 2b026d61e4c..cb6e370ac51 100644 --- a/crates/ra_syntax/src/parser_impl/mod.rs +++ b/crates/ra_syntax/src/parser_impl/mod.rs @@ -10,7 +10,11 @@ use crate::{ event::{Event, EventProcessor}, input::{InputPosition, ParserInput}, }, - SmolStr, TextUnit, + SmolStr, + yellow::syntax_error::{ + ParseError, + SyntaxError, + }, }; use crate::SyntaxKind::{self, EOF, TOMBSTONE}; @@ -21,7 +25,7 @@ pub(crate) trait Sink { fn leaf(&mut self, kind: SyntaxKind, text: SmolStr); fn start_internal(&mut self, kind: SyntaxKind); fn finish_internal(&mut self); - fn error(&mut self, message: String, offset: TextUnit); + fn error(&mut self, error: SyntaxError); fn finish(self) -> Self::Tree; } @@ -144,7 +148,9 @@ impl<'t> ParserImpl<'t> { } pub(super) fn error(&mut self, msg: String) { - self.event(Event::Error { msg }) + self.event(Event::Error { + msg: ParseError(msg), + }) } pub(super) fn complete(&mut self, pos: u32, kind: SyntaxKind) { diff --git a/crates/ra_syntax/src/reparsing.rs b/crates/ra_syntax/src/reparsing.rs index b3b51b3e486..3c4ea5c22b7 100644 --- a/crates/ra_syntax/src/reparsing.rs +++ b/crates/ra_syntax/src/reparsing.rs @@ -165,20 +165,14 @@ fn merge_errors( ) -> Vec<SyntaxError> { let mut res = Vec::new(); for e in old_errors { - if e.offset <= old_node.range().start() { + if e.offset() <= old_node.range().start() { res.push(e) - } else if e.offset >= old_node.range().end() { - res.push(SyntaxError { - msg: e.msg, - offset: e.offset + TextUnit::of_str(&edit.insert) - edit.delete.len(), - }) + } else if e.offset() >= old_node.range().end() { + res.push(e.add_offset(TextUnit::of_str(&edit.insert) - edit.delete.len())); } } for e in new_errors { - res.push(SyntaxError { - msg: e.msg, - offset: e.offset + old_node.range().start(), - }) + res.push(e.add_offset(old_node.range().start())); } res } diff --git a/crates/ra_syntax/src/string_lexing/mod.rs b/crates/ra_syntax/src/string_lexing/mod.rs index 6b52c62c381..f0812ff289d 100644 --- a/crates/ra_syntax/src/string_lexing/mod.rs +++ b/crates/ra_syntax/src/string_lexing/mod.rs @@ -100,10 +100,6 @@ impl<'a> Parser<'a> { // Char parsing methods fn parse_unicode_escape(&mut self, start: TextUnit) -> CharComponent { - // Note: validation of UnicodeEscape will be done elsewhere: - // * Only hex digits or underscores allowed - // * Max 6 chars - // * Within allowed range (must be at most 10FFFF) match self.peek() { Some('{') => { self.advance(); @@ -127,9 +123,6 @@ impl<'a> Parser<'a> { } fn parse_ascii_code_escape(&mut self, start: TextUnit) -> CharComponent { - // Note: validation of AsciiCodeEscape will be done elsewhere: - // * First digit is octal - // * Second digit is hex let code_start = self.get_pos(); while let Some(next) = self.peek() { if next == '\'' || (self.get_pos() - code_start == 2.into()) { @@ -144,9 +137,6 @@ impl<'a> Parser<'a> { } fn parse_escape(&mut self, start: TextUnit) -> CharComponent { - // Note: validation of AsciiEscape will be done elsewhere: - // * The escape sequence is non-empty - // * The escape sequence is valid if self.peek().is_none() { return CharComponent::new(TextRange::from_to(start, start), AsciiEscape); } diff --git a/crates/ra_syntax/src/utils.rs b/crates/ra_syntax/src/utils.rs index 00f00139a0d..288d7edd4e8 100644 --- a/crates/ra_syntax/src/utils.rs +++ b/crates/ra_syntax/src/utils.rs @@ -4,7 +4,7 @@ use std::fmt::Write; /// Parse a file and create a string representation of the resulting parse tree. pub fn dump_tree(syntax: SyntaxNodeRef) -> String { let mut errors: Vec<_> = syntax.root_data().to_vec(); - errors.sort_by_key(|e| e.offset); + errors.sort_by_key(|e| e.offset()); let mut err_pos = 0; let mut level = 0; let mut buf = String::new(); @@ -23,9 +23,9 @@ pub fn dump_tree(syntax: SyntaxNodeRef) -> String { writeln!(buf, "{:?}", node).unwrap(); if node.first_child().is_none() { let off = node.range().end(); - while err_pos < errors.len() && errors[err_pos].offset <= off { + while err_pos < errors.len() && errors[err_pos].offset() <= off { indent!(); - writeln!(buf, "err: `{}`", errors[err_pos].msg).unwrap(); + writeln!(buf, "err: `{}`", errors[err_pos]).unwrap(); err_pos += 1; } } @@ -37,7 +37,7 @@ pub fn dump_tree(syntax: SyntaxNodeRef) -> String { assert_eq!(level, 0); for err in errors[err_pos..].iter() { - writeln!(buf, "err: `{}`", err.msg).unwrap(); + writeln!(buf, "err: `{}`", err).unwrap(); } buf diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs index 03d98eff490..009f5052f92 100644 --- a/crates/ra_syntax/src/validation.rs +++ b/crates/ra_syntax/src/validation.rs @@ -1,40 +1,78 @@ use crate::{ + algo::visit::{visitor_ctx, VisitorCtx}, ast::{self, AstNode}, File, - string_lexing, + string_lexing::{self, CharComponentKind}, yellow::{ SyntaxError, + SyntaxErrorKind::*, }, }; pub(crate) fn validate(file: &File) -> Vec<SyntaxError> { let mut errors = Vec::new(); - for d in file.root.borrowed().descendants() { - if let Some(c) = ast::Char::cast(d) { - let components = &mut string_lexing::parse_char_literal(c.text()); - let len = components.count(); - - if !components.has_closing_quote { - errors.push(SyntaxError { - msg: "Unclosed char literal".to_string(), - offset: d.range().start(), - }); - } - - if len == 0 { - errors.push(SyntaxError { - msg: "Empty char literal".to_string(), - offset: d.range().start(), - }); - } - - if len > 1 { - errors.push(SyntaxError { - msg: "Character literal should be only one character long".to_string(), - offset: d.range().start(), - }); - } - } + for node in file.root.borrowed().descendants() { + let _ = visitor_ctx(&mut errors) + .visit::<ast::Char, _>(validate_char) + .accept(node); } errors } + +fn validate_char(node: ast::Char, errors: &mut Vec<SyntaxError>) { + let mut components = string_lexing::parse_char_literal(node.text()); + let mut len = 0; + for component in &mut components { + len += 1; + + // Validate escapes + let text = &node.text()[component.range]; + let range = component.range + node.syntax().range().start(); + use self::CharComponentKind::*; + match component.kind { + AsciiEscape => { + if text.len() == 1 { + // Escape sequence consists only of leading `\` + errors.push(SyntaxError::new(EmptyAsciiEscape, range)); + } else { + let escape_code = text.chars().skip(1).next().unwrap(); + if !is_ascii_escape(escape_code) { + errors.push(SyntaxError::new(InvalidAsciiEscape, range)); + } + } + } + AsciiCodeEscape => { + // TODO: + // * First digit is octal + // * Second digit is hex + } + UnicodeEscape => { + // TODO: + // * Only hex digits or underscores allowed + // * Max 6 chars + // * Within allowed range (must be at most 10FFFF) + } + // Code points are always valid + CodePoint => (), + } + } + + if !components.has_closing_quote { + errors.push(SyntaxError::new(UnclosedChar, node.syntax().range())); + } + + if len == 0 { + errors.push(SyntaxError::new(EmptyChar, node.syntax().range())); + } + + if len > 1 { + errors.push(SyntaxError::new(LongChar, node.syntax().range())); + } +} + +fn is_ascii_escape(code: char) -> bool { + match code { + '\'' | '"' | 'n' | 'r' | 't' | '0' => true, + _ => false, + } +} diff --git a/crates/ra_syntax/src/yellow/builder.rs b/crates/ra_syntax/src/yellow/builder.rs index d6405340993..9fcebfb93b9 100644 --- a/crates/ra_syntax/src/yellow/builder.rs +++ b/crates/ra_syntax/src/yellow/builder.rs @@ -1,7 +1,7 @@ use crate::{ parser_impl::Sink, yellow::{GreenNode, RaTypes, SyntaxError}, - SmolStr, SyntaxKind, TextUnit, + SmolStr, SyntaxKind, }; use rowan::GreenNodeBuilder; @@ -34,11 +34,7 @@ impl Sink for GreenBuilder { self.inner.finish_internal(); } - fn error(&mut self, message: String, offset: TextUnit) { - let error = SyntaxError { - msg: message, - offset, - }; + fn error(&mut self, error: SyntaxError) { self.errors.push(error) } diff --git a/crates/ra_syntax/src/yellow/mod.rs b/crates/ra_syntax/src/yellow/mod.rs index 65091721423..6da9486489d 100644 --- a/crates/ra_syntax/src/yellow/mod.rs +++ b/crates/ra_syntax/src/yellow/mod.rs @@ -1,8 +1,9 @@ mod builder; +pub mod syntax_error; mod syntax_text; use self::syntax_text::SyntaxText; -use crate::{SmolStr, SyntaxKind, TextRange, TextUnit}; +use crate::{SmolStr, SyntaxKind, TextRange}; use rowan::Types; use std::{ fmt, @@ -10,6 +11,7 @@ use std::{ }; pub(crate) use self::builder::GreenBuilder; +pub use self::syntax_error::{SyntaxError, SyntaxErrorKind, Location}; pub use rowan::{TreeRoot, WalkEvent}; #[derive(Debug, Clone, Copy)] @@ -24,12 +26,6 @@ pub type RefRoot<'a> = ::rowan::RefRoot<'a, RaTypes>; pub type GreenNode = ::rowan::GreenNode<RaTypes>; -#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct SyntaxError { - pub msg: String, - pub offset: TextUnit, -} - #[derive(Clone, Copy)] pub struct SyntaxNode<R: TreeRoot<RaTypes> = OwnedRoot>(::rowan::SyntaxNode<RaTypes, R>); pub type SyntaxNodeRef<'a> = SyntaxNode<RefRoot<'a>>; diff --git a/crates/ra_syntax/src/yellow/syntax_error.rs b/crates/ra_syntax/src/yellow/syntax_error.rs new file mode 100644 index 00000000000..f3df6bc1531 --- /dev/null +++ b/crates/ra_syntax/src/yellow/syntax_error.rs @@ -0,0 +1,89 @@ +use std::fmt; + +use crate::{TextRange, TextUnit}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SyntaxError { + kind: SyntaxErrorKind, + location: Location, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Location { + Offset(TextUnit), + Range(TextRange), +} + +impl Into<Location> for TextUnit { + fn into(self) -> Location { + Location::Offset(self) + } +} + +impl Into<Location> for TextRange { + fn into(self) -> Location { + Location::Range(self) + } +} + +impl SyntaxError { + pub fn new<L: Into<Location>>(kind: SyntaxErrorKind, loc: L) -> SyntaxError { + SyntaxError { + kind, + location: loc.into(), + } + } + + pub fn location(&self) -> Location { + self.location.clone() + } + + pub fn offset(&self) -> TextUnit { + match self.location { + Location::Offset(offset) => offset, + Location::Range(range) => range.start(), + } + } + + pub fn add_offset(mut self, plus_offset: TextUnit) -> SyntaxError { + self.location = match self.location { + Location::Range(range) => Location::Range(range + plus_offset), + Location::Offset(offset) => Location::Offset(offset + plus_offset), + }; + + self + } +} + +impl fmt::Display for SyntaxError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.kind.fmt(f) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum SyntaxErrorKind { + ParseError(ParseError), + EmptyChar, + UnclosedChar, + LongChar, + EmptyAsciiEscape, + InvalidAsciiEscape, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ParseError(pub String); + +impl fmt::Display for SyntaxErrorKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::SyntaxErrorKind::*; + match self { + EmptyAsciiEscape => write!(f, "Empty escape sequence"), + InvalidAsciiEscape => write!(f, "Invalid escape sequence"), + EmptyChar => write!(f, "Empty char literal"), + UnclosedChar => write!(f, "Unclosed char literal"), + LongChar => write!(f, "Char literal should be one character long"), + ParseError(msg) => write!(f, "{}", msg.0), + } + } +}