aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax/src/validation.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_syntax/src/validation.rs')
-rw-r--r--crates/ra_syntax/src/validation.rs84
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 @@
1use crate::{ 1use 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
10pub(crate) fn validate(file: &File) -> Vec<SyntaxError> { 12pub(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 { 22fn 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
73fn is_ascii_escape(code: char) -> bool {
74 match code {
75 '\'' | '"' | 'n' | 'r' | 't' | '0' => true,
76 _ => false,
77 }
40} 78}