aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax/src/validation.rs
blob: 009f5052f92c72675501eadbefce5679d025bc92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
use crate::{
    algo::visit::{visitor_ctx, VisitorCtx},
    ast::{self, AstNode},
    File,
    string_lexing::{self, CharComponentKind},
    yellow::{
        SyntaxError,
        SyntaxErrorKind::*,
    },
};

pub(crate) fn validate(file: &File) -> Vec<SyntaxError> {
    let mut errors = Vec::new();
    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,
    }
}