diff options
Diffstat (limited to 'crates/ra_syntax/src/validation.rs')
-rw-r--r-- | crates/ra_syntax/src/validation.rs | 141 |
1 files changed, 81 insertions, 60 deletions
diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs index f0b3dec63..fdec48fb0 100644 --- a/crates/ra_syntax/src/validation.rs +++ b/crates/ra_syntax/src/validation.rs | |||
@@ -2,15 +2,15 @@ | |||
2 | 2 | ||
3 | mod block; | 3 | mod block; |
4 | 4 | ||
5 | use std::convert::TryFrom; | ||
6 | |||
7 | use rustc_lexer::unescape; | ||
8 | |||
9 | use crate::{ | 5 | use crate::{ |
10 | ast, match_ast, AstNode, SyntaxError, | 6 | ast, match_ast, AstNode, SyntaxError, |
11 | SyntaxKind::{BYTE, BYTE_STRING, CHAR, CONST_DEF, FN_DEF, INT_NUMBER, STRING, TYPE_ALIAS_DEF}, | 7 | SyntaxKind::{BYTE, BYTE_STRING, CHAR, CONST_DEF, FN_DEF, INT_NUMBER, STRING, TYPE_ALIAS_DEF}, |
12 | SyntaxNode, SyntaxToken, TextSize, T, | 8 | SyntaxNode, SyntaxToken, TextSize, T, |
13 | }; | 9 | }; |
10 | use rustc_lexer::unescape::{ | ||
11 | self, unescape_byte, unescape_byte_literal, unescape_char, unescape_literal, Mode, | ||
12 | }; | ||
13 | use std::convert::TryFrom; | ||
14 | 14 | ||
15 | fn rustc_unescape_error_to_string(err: unescape::EscapeError) -> &'static str { | 15 | fn rustc_unescape_error_to_string(err: unescape::EscapeError) -> &'static str { |
16 | use unescape::EscapeError as EE; | 16 | use unescape::EscapeError as EE; |
@@ -54,7 +54,7 @@ fn rustc_unescape_error_to_string(err: unescape::EscapeError) -> &'static str { | |||
54 | "Unicode escape must not be empty" | 54 | "Unicode escape must not be empty" |
55 | } | 55 | } |
56 | EE::UnclosedUnicodeEscape => { | 56 | EE::UnclosedUnicodeEscape => { |
57 | "Missing '}' to terminate the unicode escape" | 57 | "Missing `}` to terminate the unicode escape" |
58 | } | 58 | } |
59 | EE::LeadingUnderscoreUnicodeEscape => { | 59 | EE::LeadingUnderscoreUnicodeEscape => { |
60 | "Unicode escape code must not begin with an underscore" | 60 | "Unicode escape code must not begin with an underscore" |
@@ -81,10 +81,8 @@ fn rustc_unescape_error_to_string(err: unescape::EscapeError) -> &'static str { | |||
81 | 81 | ||
82 | pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> { | 82 | pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> { |
83 | // FIXME: | 83 | // FIXME: |
84 | // * Add validation of character literal containing only a single char | 84 | // * Add unescape validation of raw string literals and raw byte string literals |
85 | // * Add validation of `crate` keyword not appearing in the middle of the symbol path | ||
86 | // * Add validation of doc comments are being attached to nodes | 85 | // * Add validation of doc comments are being attached to nodes |
87 | // * Remove validation of unterminated literals (it is already implemented in `tokenize()`) | ||
88 | 86 | ||
89 | let mut errors = Vec::new(); | 87 | let mut errors = Vec::new(); |
90 | for node in root.descendants() { | 88 | for node in root.descendants() { |
@@ -96,7 +94,7 @@ pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> { | |||
96 | ast::RecordField(it) => validate_numeric_name(it.name_ref(), &mut errors), | 94 | ast::RecordField(it) => validate_numeric_name(it.name_ref(), &mut errors), |
97 | ast::Visibility(it) => validate_visibility(it, &mut errors), | 95 | ast::Visibility(it) => validate_visibility(it, &mut errors), |
98 | ast::RangeExpr(it) => validate_range_expr(it, &mut errors), | 96 | ast::RangeExpr(it) => validate_range_expr(it, &mut errors), |
99 | ast::PathSegment(it) => validate_crate_keyword_in_path_segment(it, &mut errors), | 97 | ast::PathSegment(it) => validate_path_keywords(it, &mut errors), |
100 | _ => (), | 98 | _ => (), |
101 | } | 99 | } |
102 | } | 100 | } |
@@ -121,18 +119,18 @@ fn validate_literal(literal: ast::Literal, acc: &mut Vec<SyntaxError>) { | |||
121 | 119 | ||
122 | match token.kind() { | 120 | match token.kind() { |
123 | BYTE => { | 121 | BYTE => { |
124 | if let Some(Err(e)) = unquote(text, 2, '\'').map(unescape::unescape_byte) { | 122 | if let Some(Err(e)) = unquote(text, 2, '\'').map(unescape_byte) { |
125 | push_err(2, e); | 123 | push_err(2, e); |
126 | } | 124 | } |
127 | } | 125 | } |
128 | CHAR => { | 126 | CHAR => { |
129 | if let Some(Err(e)) = unquote(text, 1, '\'').map(unescape::unescape_char) { | 127 | if let Some(Err(e)) = unquote(text, 1, '\'').map(unescape_char) { |
130 | push_err(1, e); | 128 | push_err(1, e); |
131 | } | 129 | } |
132 | } | 130 | } |
133 | BYTE_STRING => { | 131 | BYTE_STRING => { |
134 | if let Some(without_quotes) = unquote(text, 2, '"') { | 132 | if let Some(without_quotes) = unquote(text, 2, '"') { |
135 | unescape::unescape_byte_str(without_quotes, &mut |range, char| { | 133 | unescape_byte_literal(without_quotes, Mode::ByteStr, &mut |range, char| { |
136 | if let Err(err) = char { | 134 | if let Err(err) = char { |
137 | push_err(2, (range.start, err)); | 135 | push_err(2, (range.start, err)); |
138 | } | 136 | } |
@@ -141,7 +139,7 @@ fn validate_literal(literal: ast::Literal, acc: &mut Vec<SyntaxError>) { | |||
141 | } | 139 | } |
142 | STRING => { | 140 | STRING => { |
143 | if let Some(without_quotes) = unquote(text, 1, '"') { | 141 | if let Some(without_quotes) = unquote(text, 1, '"') { |
144 | unescape::unescape_str(without_quotes, &mut |range, char| { | 142 | unescape_literal(without_quotes, Mode::Str, &mut |range, char| { |
145 | if let Err(err) = char { | 143 | if let Err(err) = char { |
146 | push_err(1, (range.start, err)); | 144 | push_err(1, (range.start, err)); |
147 | } | 145 | } |
@@ -224,59 +222,82 @@ fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec<SyntaxError>) { | |||
224 | } | 222 | } |
225 | } | 223 | } |
226 | 224 | ||
227 | fn validate_crate_keyword_in_path_segment( | 225 | fn validate_path_keywords(segment: ast::PathSegment, errors: &mut Vec<SyntaxError>) { |
228 | segment: ast::PathSegment, | 226 | use ast::PathSegmentKind; |
229 | errors: &mut Vec<SyntaxError>, | ||
230 | ) { | ||
231 | const ERR_MSG: &str = "The `crate` keyword is only allowed as the first segment of a path"; | ||
232 | 227 | ||
233 | let crate_token = match segment.crate_token() { | 228 | let path = segment.parent_path(); |
234 | None => return, | 229 | let is_path_start = segment.coloncolon_token().is_none() && path.qualifier().is_none(); |
235 | Some(it) => it, | ||
236 | }; | ||
237 | 230 | ||
238 | // Disallow both ::crate and foo::crate | 231 | if let Some(token) = segment.self_token() { |
239 | let mut path = segment.parent_path(); | 232 | if !is_path_start { |
240 | if segment.coloncolon_token().is_some() || path.qualifier().is_some() { | 233 | errors.push(SyntaxError::new( |
241 | errors.push(SyntaxError::new(ERR_MSG, crate_token.text_range())); | 234 | "The `self` keyword is only allowed as the first segment of a path", |
242 | return; | 235 | token.text_range(), |
236 | )); | ||
237 | } | ||
238 | } else if let Some(token) = segment.crate_token() { | ||
239 | if !is_path_start || use_prefix(path).is_some() { | ||
240 | errors.push(SyntaxError::new( | ||
241 | "The `crate` keyword is only allowed as the first segment of a path", | ||
242 | token.text_range(), | ||
243 | )); | ||
244 | } | ||
245 | } else if let Some(token) = segment.super_token() { | ||
246 | if !all_supers(&path) { | ||
247 | errors.push(SyntaxError::new( | ||
248 | "The `super` keyword may only be preceded by other `super`s", | ||
249 | token.text_range(), | ||
250 | )); | ||
251 | return; | ||
252 | } | ||
253 | |||
254 | let mut curr_path = path; | ||
255 | while let Some(prefix) = use_prefix(curr_path) { | ||
256 | if !all_supers(&prefix) { | ||
257 | errors.push(SyntaxError::new( | ||
258 | "The `super` keyword may only be preceded by other `super`s", | ||
259 | token.text_range(), | ||
260 | )); | ||
261 | return; | ||
262 | } | ||
263 | curr_path = prefix; | ||
264 | } | ||
243 | } | 265 | } |
244 | 266 | ||
245 | // For expressions and types, validation is complete, but we still have | 267 | fn use_prefix(mut path: ast::Path) -> Option<ast::Path> { |
246 | // to handle invalid UseItems like this: | 268 | for node in path.syntax().ancestors().skip(1) { |
247 | // | 269 | match_ast! { |
248 | // use foo:{crate::bar::baz}; | 270 | match node { |
249 | // | 271 | ast::UseTree(it) => if let Some(tree_path) = it.path() { |
250 | // To handle this we must inspect the parent `UseItem`s and `UseTree`s | 272 | // Even a top-level path exists within a `UseTree` so we must explicitly |
251 | // but right now we're looking deep inside the nested `Path` nodes because | 273 | // allow our path but disallow anything else |
252 | // `Path`s are left-associative: | 274 | if tree_path != path { |
253 | // | 275 | return Some(tree_path); |
254 | // ((crate)::bar)::baz) | 276 | } |
255 | // ^ current value of path | 277 | }, |
256 | // | 278 | ast::UseTreeList(_it) => continue, |
257 | // So we need to climb to the top | 279 | ast::Path(parent) => path = parent, |
258 | while let Some(parent) = path.parent_path() { | 280 | _ => return None, |
259 | path = parent; | 281 | } |
282 | }; | ||
283 | } | ||
284 | return None; | ||
260 | } | 285 | } |
261 | 286 | ||
262 | // Now that we've found the whole path we need to see if there's a prefix | 287 | fn all_supers(path: &ast::Path) -> bool { |
263 | // somewhere in the UseTree hierarchy. This check is arbitrarily deep | 288 | let segment = match path.segment() { |
264 | // because rust allows arbitrary nesting like so: | 289 | Some(it) => it, |
265 | // | 290 | None => return false, |
266 | // use {foo::{{{{crate::bar::baz}}}}}; | ||
267 | for node in path.syntax().ancestors().skip(1) { | ||
268 | match_ast! { | ||
269 | match node { | ||
270 | ast::UseTree(it) => if let Some(tree_path) = it.path() { | ||
271 | // Even a top-level path exists within a `UseTree` so we must explicitly | ||
272 | // allow our path but disallow anything else | ||
273 | if tree_path != path { | ||
274 | errors.push(SyntaxError::new(ERR_MSG, crate_token.text_range())); | ||
275 | } | ||
276 | }, | ||
277 | ast::UseTreeList(_it) => continue, | ||
278 | _ => return, | ||
279 | } | ||
280 | }; | 291 | }; |
292 | |||
293 | if segment.kind() != Some(PathSegmentKind::SuperKw) { | ||
294 | return false; | ||
295 | } | ||
296 | |||
297 | if let Some(ref subpath) = path.qualifier() { | ||
298 | return all_supers(subpath); | ||
299 | } | ||
300 | |||
301 | return true; | ||
281 | } | 302 | } |
282 | } | 303 | } |