diff options
Diffstat (limited to 'crates/ra_syntax/src/validation.rs')
-rw-r--r-- | crates/ra_syntax/src/validation.rs | 84 |
1 files changed, 61 insertions, 23 deletions
diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs index 03d98eff4..009f5052f 100644 --- a/crates/ra_syntax/src/validation.rs +++ b/crates/ra_syntax/src/validation.rs | |||
@@ -1,40 +1,78 @@ | |||
1 | use crate::{ | 1 | use crate::{ |
2 | algo::visit::{visitor_ctx, VisitorCtx}, | ||
2 | ast::{self, AstNode}, | 3 | ast::{self, AstNode}, |
3 | File, | 4 | File, |
4 | string_lexing, | 5 | string_lexing::{self, CharComponentKind}, |
5 | yellow::{ | 6 | yellow::{ |
6 | SyntaxError, | 7 | SyntaxError, |
8 | SyntaxErrorKind::*, | ||
7 | }, | 9 | }, |
8 | }; | 10 | }; |
9 | 11 | ||
10 | pub(crate) fn validate(file: &File) -> Vec<SyntaxError> { | 12 | pub(crate) fn validate(file: &File) -> Vec<SyntaxError> { |
11 | let mut errors = Vec::new(); | 13 | let mut errors = Vec::new(); |
12 | for d in file.root.borrowed().descendants() { | 14 | for node in file.root.borrowed().descendants() { |
13 | if let Some(c) = ast::Char::cast(d) { | 15 | let _ = visitor_ctx(&mut errors) |
14 | let components = &mut string_lexing::parse_char_literal(c.text()); | 16 | .visit::<ast::Char, _>(validate_char) |
15 | let len = components.count(); | 17 | .accept(node); |
18 | } | ||
19 | errors | ||
20 | } | ||
16 | 21 | ||
17 | if !components.has_closing_quote { | 22 | fn validate_char(node: ast::Char, errors: &mut Vec<SyntaxError>) { |
18 | errors.push(SyntaxError { | 23 | let mut components = string_lexing::parse_char_literal(node.text()); |
19 | msg: "Unclosed char literal".to_string(), | 24 | let mut len = 0; |
20 | offset: d.range().start(), | 25 | for component in &mut components { |
21 | }); | 26 | len += 1; |
22 | } | ||
23 | 27 | ||
24 | if len == 0 { | 28 | // Validate escapes |
25 | errors.push(SyntaxError { | 29 | let text = &node.text()[component.range]; |
26 | msg: "Empty char literal".to_string(), | 30 | let range = component.range + node.syntax().range().start(); |
27 | offset: d.range().start(), | 31 | use self::CharComponentKind::*; |
28 | }); | 32 | match component.kind { |
33 | AsciiEscape => { | ||
34 | if text.len() == 1 { | ||
35 | // Escape sequence consists only of leading `\` | ||
36 | errors.push(SyntaxError::new(EmptyAsciiEscape, range)); | ||
37 | } else { | ||
38 | let escape_code = text.chars().skip(1).next().unwrap(); | ||
39 | if !is_ascii_escape(escape_code) { | ||
40 | errors.push(SyntaxError::new(InvalidAsciiEscape, range)); | ||
41 | } | ||
42 | } | ||
29 | } | 43 | } |
30 | 44 | AsciiCodeEscape => { | |
31 | if len > 1 { | 45 | // TODO: |
32 | errors.push(SyntaxError { | 46 | // * First digit is octal |
33 | msg: "Character literal should be only one character long".to_string(), | 47 | // * Second digit is hex |
34 | offset: d.range().start(), | 48 | } |
35 | }); | 49 | UnicodeEscape => { |
50 | // TODO: | ||
51 | // * Only hex digits or underscores allowed | ||
52 | // * Max 6 chars | ||
53 | // * Within allowed range (must be at most 10FFFF) | ||
36 | } | 54 | } |
55 | // Code points are always valid | ||
56 | CodePoint => (), | ||
37 | } | 57 | } |
38 | } | 58 | } |
39 | errors | 59 | |
60 | if !components.has_closing_quote { | ||
61 | errors.push(SyntaxError::new(UnclosedChar, node.syntax().range())); | ||
62 | } | ||
63 | |||
64 | if len == 0 { | ||
65 | errors.push(SyntaxError::new(EmptyChar, node.syntax().range())); | ||
66 | } | ||
67 | |||
68 | if len > 1 { | ||
69 | errors.push(SyntaxError::new(LongChar, node.syntax().range())); | ||
70 | } | ||
71 | } | ||
72 | |||
73 | fn is_ascii_escape(code: char) -> bool { | ||
74 | match code { | ||
75 | '\'' | '"' | 'n' | 'r' | 't' | '0' => true, | ||
76 | _ => false, | ||
77 | } | ||
40 | } | 78 | } |