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.rs141
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
3mod block; 3mod block;
4 4
5use std::convert::TryFrom;
6
7use rustc_lexer::unescape;
8
9use crate::{ 5use 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};
10use rustc_lexer::unescape::{
11 self, unescape_byte, unescape_byte_literal, unescape_char, unescape_literal, Mode,
12};
13use std::convert::TryFrom;
14 14
15fn rustc_unescape_error_to_string(err: unescape::EscapeError) -> &'static str { 15fn 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
82pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> { 82pub(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
227fn validate_crate_keyword_in_path_segment( 225fn 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}