diff options
Diffstat (limited to 'crates/ra_syntax/src/validation.rs')
-rw-r--r-- | crates/ra_syntax/src/validation.rs | 119 |
1 files changed, 71 insertions, 48 deletions
diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs index f0b3dec63..e075cd801 100644 --- a/crates/ra_syntax/src/validation.rs +++ b/crates/ra_syntax/src/validation.rs | |||
@@ -96,7 +96,7 @@ pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> { | |||
96 | ast::RecordField(it) => validate_numeric_name(it.name_ref(), &mut errors), | 96 | ast::RecordField(it) => validate_numeric_name(it.name_ref(), &mut errors), |
97 | ast::Visibility(it) => validate_visibility(it, &mut errors), | 97 | ast::Visibility(it) => validate_visibility(it, &mut errors), |
98 | ast::RangeExpr(it) => validate_range_expr(it, &mut errors), | 98 | ast::RangeExpr(it) => validate_range_expr(it, &mut errors), |
99 | ast::PathSegment(it) => validate_crate_keyword_in_path_segment(it, &mut errors), | 99 | ast::PathSegment(it) => validate_path_keywords(it, &mut errors), |
100 | _ => (), | 100 | _ => (), |
101 | } | 101 | } |
102 | } | 102 | } |
@@ -224,59 +224,82 @@ fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec<SyntaxError>) { | |||
224 | } | 224 | } |
225 | } | 225 | } |
226 | 226 | ||
227 | fn validate_crate_keyword_in_path_segment( | 227 | fn validate_path_keywords(segment: ast::PathSegment, errors: &mut Vec<SyntaxError>) { |
228 | segment: ast::PathSegment, | 228 | 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 | 229 | ||
233 | let crate_token = match segment.crate_token() { | 230 | let path = segment.parent_path(); |
234 | None => return, | 231 | let is_path_start = segment.coloncolon_token().is_none() && path.qualifier().is_none(); |
235 | Some(it) => it, | 232 | |
236 | }; | 233 | if let Some(token) = segment.self_token() { |
234 | if !is_path_start { | ||
235 | errors.push(SyntaxError::new( | ||
236 | "The `self` keyword is only allowed as the first segment of a path", | ||
237 | token.text_range(), | ||
238 | )); | ||
239 | } | ||
240 | } else if let Some(token) = segment.crate_token() { | ||
241 | if !is_path_start || use_prefix(path).is_some() { | ||
242 | errors.push(SyntaxError::new( | ||
243 | "The `crate` keyword is only allowed as the first segment of a path", | ||
244 | token.text_range(), | ||
245 | )); | ||
246 | } | ||
247 | } else if let Some(token) = segment.super_token() { | ||
248 | if !all_supers(&path) { | ||
249 | errors.push(SyntaxError::new( | ||
250 | "The `super` keyword may only be preceded by other `super`s", | ||
251 | token.text_range(), | ||
252 | )); | ||
253 | return; | ||
254 | } | ||
237 | 255 | ||
238 | // Disallow both ::crate and foo::crate | 256 | let mut curr_path = path; |
239 | let mut path = segment.parent_path(); | 257 | while let Some(prefix) = use_prefix(curr_path) { |
240 | if segment.coloncolon_token().is_some() || path.qualifier().is_some() { | 258 | if !all_supers(&prefix) { |
241 | errors.push(SyntaxError::new(ERR_MSG, crate_token.text_range())); | 259 | errors.push(SyntaxError::new( |
242 | return; | 260 | "The `super` keyword may only be preceded by other `super`s", |
261 | token.text_range(), | ||
262 | )); | ||
263 | return; | ||
264 | } | ||
265 | curr_path = prefix; | ||
266 | } | ||
243 | } | 267 | } |
244 | 268 | ||
245 | // For expressions and types, validation is complete, but we still have | 269 | fn use_prefix(mut path: ast::Path) -> Option<ast::Path> { |
246 | // to handle invalid UseItems like this: | 270 | for node in path.syntax().ancestors().skip(1) { |
247 | // | 271 | match_ast! { |
248 | // use foo:{crate::bar::baz}; | 272 | match node { |
249 | // | 273 | ast::UseTree(it) => if let Some(tree_path) = it.path() { |
250 | // To handle this we must inspect the parent `UseItem`s and `UseTree`s | 274 | // 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 | 275 | // allow our path but disallow anything else |
252 | // `Path`s are left-associative: | 276 | if tree_path != path { |
253 | // | 277 | return Some(tree_path); |
254 | // ((crate)::bar)::baz) | 278 | } |
255 | // ^ current value of path | 279 | }, |
256 | // | 280 | ast::UseTreeList(_it) => continue, |
257 | // So we need to climb to the top | 281 | ast::Path(parent) => path = parent, |
258 | while let Some(parent) = path.parent_path() { | 282 | _ => return None, |
259 | path = parent; | 283 | } |
284 | }; | ||
285 | } | ||
286 | return None; | ||
260 | } | 287 | } |
261 | 288 | ||
262 | // Now that we've found the whole path we need to see if there's a prefix | 289 | fn all_supers(path: &ast::Path) -> bool { |
263 | // somewhere in the UseTree hierarchy. This check is arbitrarily deep | 290 | let segment = match path.segment() { |
264 | // because rust allows arbitrary nesting like so: | 291 | Some(it) => it, |
265 | // | 292 | 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 | }; | 293 | }; |
294 | |||
295 | if segment.kind() != Some(PathSegmentKind::SuperKw) { | ||
296 | return false; | ||
297 | } | ||
298 | |||
299 | if let Some(ref subpath) = path.qualifier() { | ||
300 | return all_supers(subpath); | ||
301 | } | ||
302 | |||
303 | return true; | ||
281 | } | 304 | } |
282 | } | 305 | } |