diff options
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r-- | crates/ra_ide_api/src/change.rs | 5 | ||||
-rw-r--r-- | crates/ra_ide_api/src/completion/complete_dot.rs | 4 | ||||
-rw-r--r-- | crates/ra_ide_api/src/completion/complete_struct_literal.rs | 4 | ||||
-rw-r--r-- | crates/ra_ide_api/src/goto_definition.rs | 17 | ||||
-rw-r--r-- | crates/ra_ide_api/src/hover.rs | 43 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 7 | ||||
-rw-r--r-- | crates/ra_ide_api/src/syntax_tree.rs | 87 | ||||
-rw-r--r-- | crates/ra_ide_api/tests/test/main.rs | 254 |
8 files changed, 389 insertions, 32 deletions
diff --git a/crates/ra_ide_api/src/change.rs b/crates/ra_ide_api/src/change.rs index 3f041f9c3..0c90ed5b5 100644 --- a/crates/ra_ide_api/src/change.rs +++ b/crates/ra_ide_api/src/change.rs | |||
@@ -223,8 +223,7 @@ impl RootDatabase { | |||
223 | self.query(hir::db::FileItemsQuery).sweep(sweep); | 223 | self.query(hir::db::FileItemsQuery).sweep(sweep); |
224 | self.query(hir::db::FileItemQuery).sweep(sweep); | 224 | self.query(hir::db::FileItemQuery).sweep(sweep); |
225 | 225 | ||
226 | self.query(hir::db::LowerModuleQuery).sweep(sweep); | 226 | self.query(hir::db::LowerModuleWithSourceMapQuery).sweep(sweep); |
227 | self.query(hir::db::LowerModuleSourceMapQuery).sweep(sweep); | 227 | self.query(hir::db::BodyWithSourceMapQuery).sweep(sweep); |
228 | self.query(hir::db::BodySyntaxMappingQuery).sweep(sweep); | ||
229 | } | 228 | } |
230 | } | 229 | } |
diff --git a/crates/ra_ide_api/src/completion/complete_dot.rs b/crates/ra_ide_api/src/completion/complete_dot.rs index d5ad2e79f..94c66be31 100644 --- a/crates/ra_ide_api/src/completion/complete_dot.rs +++ b/crates/ra_ide_api/src/completion/complete_dot.rs | |||
@@ -9,8 +9,8 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | |||
9 | _ => return, | 9 | _ => return, |
10 | }; | 10 | }; |
11 | let infer_result = function.infer(ctx.db); | 11 | let infer_result = function.infer(ctx.db); |
12 | let syntax_mapping = function.body_syntax_mapping(ctx.db); | 12 | let source_map = function.body_source_map(ctx.db); |
13 | let expr = match syntax_mapping.node_expr(receiver) { | 13 | let expr = match source_map.node_expr(receiver) { |
14 | Some(expr) => expr, | 14 | Some(expr) => expr, |
15 | None => return, | 15 | None => return, |
16 | }; | 16 | }; |
diff --git a/crates/ra_ide_api/src/completion/complete_struct_literal.rs b/crates/ra_ide_api/src/completion/complete_struct_literal.rs index afb092f59..6bef9624e 100644 --- a/crates/ra_ide_api/src/completion/complete_struct_literal.rs +++ b/crates/ra_ide_api/src/completion/complete_struct_literal.rs | |||
@@ -9,8 +9,8 @@ pub(super) fn complete_struct_literal(acc: &mut Completions, ctx: &CompletionCon | |||
9 | _ => return, | 9 | _ => return, |
10 | }; | 10 | }; |
11 | let infer_result = function.infer(ctx.db); | 11 | let infer_result = function.infer(ctx.db); |
12 | let syntax_mapping = function.body_syntax_mapping(ctx.db); | 12 | let source_map = function.body_source_map(ctx.db); |
13 | let expr = match syntax_mapping.node_expr(struct_lit.into()) { | 13 | let expr = match source_map.node_expr(struct_lit.into()) { |
14 | Some(expr) => expr, | 14 | Some(expr) => expr, |
15 | None => return, | 15 | None => return, |
16 | }; | 16 | }; |
diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs index 6fa430754..9ec179593 100644 --- a/crates/ra_ide_api/src/goto_definition.rs +++ b/crates/ra_ide_api/src/goto_definition.rs | |||
@@ -54,10 +54,10 @@ pub(crate) fn reference_definition( | |||
54 | if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) { | 54 | if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) { |
55 | tested_by!(goto_definition_works_for_methods); | 55 | tested_by!(goto_definition_works_for_methods); |
56 | let infer_result = function.infer(db); | 56 | let infer_result = function.infer(db); |
57 | let syntax_mapping = function.body_syntax_mapping(db); | 57 | let source_map = function.body_source_map(db); |
58 | let expr = ast::Expr::cast(method_call.syntax()).unwrap(); | 58 | let expr = ast::Expr::cast(method_call.syntax()).unwrap(); |
59 | if let Some(func) = | 59 | if let Some(func) = |
60 | syntax_mapping.node_expr(expr).and_then(|it| infer_result.method_resolution(it)) | 60 | source_map.node_expr(expr).and_then(|it| infer_result.method_resolution(it)) |
61 | { | 61 | { |
62 | return Exact(NavigationTarget::from_function(db, func)); | 62 | return Exact(NavigationTarget::from_function(db, func)); |
63 | }; | 63 | }; |
@@ -66,10 +66,10 @@ pub(crate) fn reference_definition( | |||
66 | if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) { | 66 | if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) { |
67 | tested_by!(goto_definition_works_for_fields); | 67 | tested_by!(goto_definition_works_for_fields); |
68 | let infer_result = function.infer(db); | 68 | let infer_result = function.infer(db); |
69 | let syntax_mapping = function.body_syntax_mapping(db); | 69 | let source_map = function.body_source_map(db); |
70 | let expr = ast::Expr::cast(field_expr.syntax()).unwrap(); | 70 | let expr = ast::Expr::cast(field_expr.syntax()).unwrap(); |
71 | if let Some(field) = | 71 | if let Some(field) = |
72 | syntax_mapping.node_expr(expr).and_then(|it| infer_result.field_resolution(it)) | 72 | source_map.node_expr(expr).and_then(|it| infer_result.field_resolution(it)) |
73 | { | 73 | { |
74 | return Exact(NavigationTarget::from_field(db, field)); | 74 | return Exact(NavigationTarget::from_field(db, field)); |
75 | }; | 75 | }; |
@@ -80,11 +80,11 @@ pub(crate) fn reference_definition( | |||
80 | tested_by!(goto_definition_works_for_named_fields); | 80 | tested_by!(goto_definition_works_for_named_fields); |
81 | 81 | ||
82 | let infer_result = function.infer(db); | 82 | let infer_result = function.infer(db); |
83 | let syntax_mapping = function.body_syntax_mapping(db); | 83 | let source_map = function.body_source_map(db); |
84 | 84 | ||
85 | let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast); | 85 | let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast); |
86 | 86 | ||
87 | if let Some(expr) = struct_lit.and_then(|lit| syntax_mapping.node_expr(lit.into())) { | 87 | if let Some(expr) = struct_lit.and_then(|lit| source_map.node_expr(lit.into())) { |
88 | let ty = infer_result[expr].clone(); | 88 | let ty = infer_result[expr].clone(); |
89 | if let hir::Ty::Adt { def_id, .. } = ty { | 89 | if let hir::Ty::Adt { def_id, .. } = ty { |
90 | if let hir::AdtDef::Struct(s) = def_id { | 90 | if let hir::AdtDef::Struct(s) = def_id { |
@@ -109,9 +109,8 @@ pub(crate) fn reference_definition( | |||
109 | Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)), | 109 | Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)), |
110 | Some(Resolution::LocalBinding(pat)) => { | 110 | Some(Resolution::LocalBinding(pat)) => { |
111 | let body = resolver.body().expect("no body for local binding"); | 111 | let body = resolver.body().expect("no body for local binding"); |
112 | let syntax_mapping = body.syntax_mapping(db); | 112 | let source_map = body.owner().body_source_map(db); |
113 | let ptr = | 113 | let ptr = source_map.pat_syntax(pat).expect("pattern not found in syntax mapping"); |
114 | syntax_mapping.pat_syntax(pat).expect("pattern not found in syntax mapping"); | ||
115 | let name = | 114 | let name = |
116 | path.as_ident().cloned().expect("local binding from a multi-segment path"); | 115 | path.as_ident().cloned().expect("local binding from a multi-segment path"); |
117 | let nav = NavigationTarget::from_scope_entry(file_id, name, ptr); | 116 | let nav = NavigationTarget::from_scope_entry(file_id, name, ptr); |
diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index 1a1853df3..8ec60090d 100644 --- a/crates/ra_ide_api/src/hover.rs +++ b/crates/ra_ide_api/src/hover.rs | |||
@@ -107,7 +107,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
107 | leaf.ancestors().find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some()) | 107 | leaf.ancestors().find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some()) |
108 | })?; | 108 | })?; |
109 | let frange = FileRange { file_id: position.file_id, range: node.range() }; | 109 | let frange = FileRange { file_id: position.file_id, range: node.range() }; |
110 | res.extend(type_of(db, frange).map(Into::into)); | 110 | res.extend(type_of(db, frange).map(rust_code_markup)); |
111 | range = Some(node.range()); | 111 | range = Some(node.range()); |
112 | } | 112 | } |
113 | 113 | ||
@@ -132,22 +132,37 @@ pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { | |||
132 | let parent_fn = node.ancestors().find_map(ast::FnDef::cast)?; | 132 | let parent_fn = node.ancestors().find_map(ast::FnDef::cast)?; |
133 | let function = hir::source_binder::function_from_source(db, frange.file_id, parent_fn)?; | 133 | let function = hir::source_binder::function_from_source(db, frange.file_id, parent_fn)?; |
134 | let infer = function.infer(db); | 134 | let infer = function.infer(db); |
135 | let syntax_mapping = function.body_syntax_mapping(db); | 135 | let source_map = function.body_source_map(db); |
136 | if let Some(expr) = ast::Expr::cast(node).and_then(|e| syntax_mapping.node_expr(e)) { | 136 | if let Some(expr) = ast::Expr::cast(node).and_then(|e| source_map.node_expr(e)) { |
137 | Some(infer[expr].to_string()) | 137 | Some(infer[expr].to_string()) |
138 | } else if let Some(pat) = ast::Pat::cast(node).and_then(|p| syntax_mapping.node_pat(p)) { | 138 | } else if let Some(pat) = ast::Pat::cast(node).and_then(|p| source_map.node_pat(p)) { |
139 | Some(infer[pat].to_string()) | 139 | Some(infer[pat].to_string()) |
140 | } else { | 140 | } else { |
141 | None | 141 | None |
142 | } | 142 | } |
143 | } | 143 | } |
144 | 144 | ||
145 | fn rust_code_markup<CODE: AsRef<str>>(val: CODE) -> String { | ||
146 | rust_code_markup_with_doc::<_, &str>(val, None) | ||
147 | } | ||
148 | |||
149 | fn rust_code_markup_with_doc<CODE, DOC>(val: CODE, doc: Option<DOC>) -> String | ||
150 | where | ||
151 | CODE: AsRef<str>, | ||
152 | DOC: AsRef<str>, | ||
153 | { | ||
154 | if let Some(doc) = doc { | ||
155 | format!("```rust\n{}\n```\n\n{}", val.as_ref(), doc.as_ref()) | ||
156 | } else { | ||
157 | format!("```rust\n{}\n```", val.as_ref()) | ||
158 | } | ||
159 | } | ||
160 | |||
145 | // FIXME: this should not really use navigation target. Rather, approximately | 161 | // FIXME: this should not really use navigation target. Rather, approximately |
146 | // resolved symbol should return a `DefId`. | 162 | // resolved symbol should return a `DefId`. |
147 | fn doc_text_for(db: &RootDatabase, nav: NavigationTarget) -> Option<String> { | 163 | fn doc_text_for(db: &RootDatabase, nav: NavigationTarget) -> Option<String> { |
148 | match (nav.description(db), nav.docs(db)) { | 164 | match (nav.description(db), nav.docs(db)) { |
149 | (Some(desc), Some(docs)) => Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs), | 165 | (Some(desc), docs) => Some(rust_code_markup_with_doc(desc, docs)), |
150 | (Some(desc), None) => Some("```rust\n".to_string() + &*desc + "\n```"), | ||
151 | (None, Some(docs)) => Some(docs), | 166 | (None, Some(docs)) => Some(docs), |
152 | _ => None, | 167 | _ => None, |
153 | } | 168 | } |
@@ -238,6 +253,10 @@ mod tests { | |||
238 | s.trim_start_matches("```rust\n").trim_end_matches("\n```") | 253 | s.trim_start_matches("```rust\n").trim_end_matches("\n```") |
239 | } | 254 | } |
240 | 255 | ||
256 | fn trim_markup_opt(s: Option<&str>) -> Option<&str> { | ||
257 | s.map(trim_markup) | ||
258 | } | ||
259 | |||
241 | fn check_hover_result(fixture: &str, expected: &[&str]) { | 260 | fn check_hover_result(fixture: &str, expected: &[&str]) { |
242 | let (analysis, position) = analysis_and_position(fixture); | 261 | let (analysis, position) = analysis_and_position(fixture); |
243 | let hover = analysis.hover(position).unwrap().unwrap(); | 262 | let hover = analysis.hover(position).unwrap().unwrap(); |
@@ -264,7 +283,7 @@ mod tests { | |||
264 | ); | 283 | ); |
265 | let hover = analysis.hover(position).unwrap().unwrap(); | 284 | let hover = analysis.hover(position).unwrap().unwrap(); |
266 | assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); | 285 | assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); |
267 | assert_eq!(hover.info.first(), Some("u32")); | 286 | assert_eq!(trim_markup_opt(hover.info.first()), Some("u32")); |
268 | } | 287 | } |
269 | 288 | ||
270 | #[test] | 289 | #[test] |
@@ -410,21 +429,21 @@ mod tests { | |||
410 | ); | 429 | ); |
411 | let hover = analysis.hover(position).unwrap().unwrap(); | 430 | let hover = analysis.hover(position).unwrap().unwrap(); |
412 | // not the nicest way to show it currently | 431 | // not the nicest way to show it currently |
413 | assert_eq!(hover.info.first(), Some("Some<i32>(T) -> Option<T>")); | 432 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Some<i32>(T) -> Option<T>")); |
414 | } | 433 | } |
415 | 434 | ||
416 | #[test] | 435 | #[test] |
417 | fn hover_for_local_variable() { | 436 | fn hover_for_local_variable() { |
418 | let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }"); | 437 | let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }"); |
419 | let hover = analysis.hover(position).unwrap().unwrap(); | 438 | let hover = analysis.hover(position).unwrap().unwrap(); |
420 | assert_eq!(hover.info.first(), Some("i32")); | 439 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); |
421 | } | 440 | } |
422 | 441 | ||
423 | #[test] | 442 | #[test] |
424 | fn hover_for_local_variable_pat() { | 443 | fn hover_for_local_variable_pat() { |
425 | let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}"); | 444 | let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}"); |
426 | let hover = analysis.hover(position).unwrap().unwrap(); | 445 | let hover = analysis.hover(position).unwrap().unwrap(); |
427 | assert_eq!(hover.info.first(), Some("i32")); | 446 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); |
428 | } | 447 | } |
429 | 448 | ||
430 | #[test] | 449 | #[test] |
@@ -455,7 +474,7 @@ mod tests { | |||
455 | ); | 474 | ); |
456 | 475 | ||
457 | let type_name = analysis.type_of(range).unwrap().unwrap(); | 476 | let type_name = analysis.type_of(range).unwrap().unwrap(); |
458 | assert_eq!("[unknown]", &type_name); | 477 | assert_eq!("{unknown}", &type_name); |
459 | } | 478 | } |
460 | 479 | ||
461 | #[test] | 480 | #[test] |
@@ -491,6 +510,6 @@ mod tests { | |||
491 | ", | 510 | ", |
492 | ); | 511 | ); |
493 | let hover = analysis.hover(position).unwrap().unwrap(); | 512 | let hover = analysis.hover(position).unwrap().unwrap(); |
494 | assert_eq!(hover.info.first(), Some("Thing")); | 513 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); |
495 | } | 514 | } |
496 | } | 515 | } |
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 6546d0644..b8a4adbce 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs | |||
@@ -32,13 +32,14 @@ mod references; | |||
32 | mod impls; | 32 | mod impls; |
33 | mod assists; | 33 | mod assists; |
34 | mod diagnostics; | 34 | mod diagnostics; |
35 | mod syntax_tree; | ||
35 | 36 | ||
36 | #[cfg(test)] | 37 | #[cfg(test)] |
37 | mod marks; | 38 | mod marks; |
38 | 39 | ||
39 | use std::sync::Arc; | 40 | use std::sync::Arc; |
40 | 41 | ||
41 | use ra_syntax::{SourceFile, TreeArc, TextRange, TextUnit, AstNode}; | 42 | use ra_syntax::{SourceFile, TreeArc, TextRange, TextUnit}; |
42 | use ra_text_edit::TextEdit; | 43 | use ra_text_edit::TextEdit; |
43 | use ra_db::{ | 44 | use ra_db::{ |
44 | SourceDatabase, CheckCanceled, | 45 | SourceDatabase, CheckCanceled, |
@@ -245,8 +246,8 @@ impl Analysis { | |||
245 | 246 | ||
246 | /// Returns a syntax tree represented as `String`, for debug purposes. | 247 | /// Returns a syntax tree represented as `String`, for debug purposes. |
247 | // FIXME: use a better name here. | 248 | // FIXME: use a better name here. |
248 | pub fn syntax_tree(&self, file_id: FileId) -> String { | 249 | pub fn syntax_tree(&self, file_id: FileId, text_range: Option<TextRange>) -> String { |
249 | self.db.parse(file_id).syntax().debug_dump() | 250 | syntax_tree::syntax_tree(&self.db, file_id, text_range) |
250 | } | 251 | } |
251 | 252 | ||
252 | /// Returns an edit to remove all newlines in the range, cleaning up minor | 253 | /// Returns an edit to remove all newlines in the range, cleaning up minor |
diff --git a/crates/ra_ide_api/src/syntax_tree.rs b/crates/ra_ide_api/src/syntax_tree.rs new file mode 100644 index 000000000..bbe9222b4 --- /dev/null +++ b/crates/ra_ide_api/src/syntax_tree.rs | |||
@@ -0,0 +1,87 @@ | |||
1 | use ra_db::SourceDatabase; | ||
2 | use crate::db::RootDatabase; | ||
3 | use ra_syntax::{ | ||
4 | SourceFile, SyntaxNode, TextRange, AstNode, | ||
5 | algo::{self, visit::{visitor, Visitor}}, ast::{self, AstToken} | ||
6 | }; | ||
7 | |||
8 | pub use ra_db::FileId; | ||
9 | |||
10 | pub(crate) fn syntax_tree( | ||
11 | db: &RootDatabase, | ||
12 | file_id: FileId, | ||
13 | text_range: Option<TextRange>, | ||
14 | ) -> String { | ||
15 | if let Some(text_range) = text_range { | ||
16 | let file = db.parse(file_id); | ||
17 | let node = algo::find_covering_node(file.syntax(), text_range); | ||
18 | |||
19 | if let Some(tree) = syntax_tree_for_string(node, text_range) { | ||
20 | return tree; | ||
21 | } | ||
22 | |||
23 | node.debug_dump() | ||
24 | } else { | ||
25 | db.parse(file_id).syntax().debug_dump() | ||
26 | } | ||
27 | } | ||
28 | |||
29 | /// Attempts parsing the selected contents of a string literal | ||
30 | /// as rust syntax and returns its syntax tree | ||
31 | fn syntax_tree_for_string(node: &SyntaxNode, text_range: TextRange) -> Option<String> { | ||
32 | // When the range is inside a string | ||
33 | // we'll attempt parsing it as rust syntax | ||
34 | // to provide the syntax tree of the contents of the string | ||
35 | visitor() | ||
36 | .visit(|node: &ast::String| syntax_tree_for_token(node, text_range)) | ||
37 | .visit(|node: &ast::RawString| syntax_tree_for_token(node, text_range)) | ||
38 | .accept(node)? | ||
39 | } | ||
40 | |||
41 | fn syntax_tree_for_token<T: AstToken>(node: &T, text_range: TextRange) -> Option<String> { | ||
42 | // Range of the full node | ||
43 | let node_range = node.syntax().range(); | ||
44 | let text = node.text().to_string(); | ||
45 | |||
46 | // We start at some point inside the node | ||
47 | // Either we have selected the whole string | ||
48 | // or our selection is inside it | ||
49 | let start = text_range.start() - node_range.start(); | ||
50 | |||
51 | // how many characters we have selected | ||
52 | let len = text_range.len().to_usize(); | ||
53 | |||
54 | let node_len = node_range.len().to_usize(); | ||
55 | |||
56 | let start = start.to_usize(); | ||
57 | |||
58 | // We want to cap our length | ||
59 | let len = len.min(node_len); | ||
60 | |||
61 | // Ensure our slice is inside the actual string | ||
62 | let end = if start + len < text.len() { start + len } else { text.len() - start }; | ||
63 | |||
64 | let text = &text[start..end]; | ||
65 | |||
66 | // Remove possible extra string quotes from the start | ||
67 | // and the end of the string | ||
68 | let text = text | ||
69 | .trim_start_matches('r') | ||
70 | .trim_start_matches('#') | ||
71 | .trim_start_matches('"') | ||
72 | .trim_end_matches('#') | ||
73 | .trim_end_matches('"') | ||
74 | .trim() | ||
75 | // Remove custom markers | ||
76 | .replace("<|>", ""); | ||
77 | |||
78 | let parsed = SourceFile::parse(&text); | ||
79 | |||
80 | // If the "file" parsed without errors, | ||
81 | // return its syntax | ||
82 | if parsed.errors().is_empty() { | ||
83 | return Some(parsed.syntax().debug_dump()); | ||
84 | } | ||
85 | |||
86 | None | ||
87 | } | ||
diff --git a/crates/ra_ide_api/tests/test/main.rs b/crates/ra_ide_api/tests/test/main.rs index ff1a0e46b..0f0766f62 100644 --- a/crates/ra_ide_api/tests/test/main.rs +++ b/crates/ra_ide_api/tests/test/main.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use insta::assert_debug_snapshot_matches; | 1 | use insta::assert_debug_snapshot_matches; |
2 | use ra_ide_api::{ | 2 | use ra_ide_api::{ |
3 | mock_analysis::{single_file, single_file_with_position, MockAnalysis}, | 3 | mock_analysis::{single_file, single_file_with_position, single_file_with_range, MockAnalysis}, |
4 | AnalysisChange, CrateGraph, Edition::Edition2018, Query, NavigationTarget, | 4 | AnalysisChange, CrateGraph, Edition::Edition2018, Query, NavigationTarget, |
5 | ReferenceSearchResult, | 5 | ReferenceSearchResult, |
6 | }; | 6 | }; |
@@ -138,3 +138,255 @@ mod foo { | |||
138 | assert_eq!(s.name(), "FooInner"); | 138 | assert_eq!(s.name(), "FooInner"); |
139 | assert_eq!(s.container_name(), Some(&SmolStr::new("foo"))); | 139 | assert_eq!(s.container_name(), Some(&SmolStr::new("foo"))); |
140 | } | 140 | } |
141 | |||
142 | #[test] | ||
143 | fn test_syntax_tree_without_range() { | ||
144 | // Basic syntax | ||
145 | let (analysis, file_id) = single_file(r#"fn foo() {}"#); | ||
146 | let syn = analysis.syntax_tree(file_id, None); | ||
147 | |||
148 | assert_eq!( | ||
149 | syn.trim(), | ||
150 | r#" | ||
151 | SOURCE_FILE@[0; 11) | ||
152 | FN_DEF@[0; 11) | ||
153 | FN_KW@[0; 2) | ||
154 | WHITESPACE@[2; 3) | ||
155 | NAME@[3; 6) | ||
156 | IDENT@[3; 6) "foo" | ||
157 | PARAM_LIST@[6; 8) | ||
158 | L_PAREN@[6; 7) | ||
159 | R_PAREN@[7; 8) | ||
160 | WHITESPACE@[8; 9) | ||
161 | BLOCK@[9; 11) | ||
162 | L_CURLY@[9; 10) | ||
163 | R_CURLY@[10; 11) | ||
164 | "# | ||
165 | .trim() | ||
166 | ); | ||
167 | |||
168 | let (analysis, file_id) = single_file( | ||
169 | r#" | ||
170 | fn test() { | ||
171 | assert!(" | ||
172 | fn foo() { | ||
173 | } | ||
174 | ", ""); | ||
175 | }"# | ||
176 | .trim(), | ||
177 | ); | ||
178 | let syn = analysis.syntax_tree(file_id, None); | ||
179 | |||
180 | assert_eq!( | ||
181 | syn.trim(), | ||
182 | r#" | ||
183 | SOURCE_FILE@[0; 60) | ||
184 | FN_DEF@[0; 60) | ||
185 | FN_KW@[0; 2) | ||
186 | WHITESPACE@[2; 3) | ||
187 | NAME@[3; 7) | ||
188 | IDENT@[3; 7) "test" | ||
189 | PARAM_LIST@[7; 9) | ||
190 | L_PAREN@[7; 8) | ||
191 | R_PAREN@[8; 9) | ||
192 | WHITESPACE@[9; 10) | ||
193 | BLOCK@[10; 60) | ||
194 | L_CURLY@[10; 11) | ||
195 | WHITESPACE@[11; 16) | ||
196 | EXPR_STMT@[16; 58) | ||
197 | MACRO_CALL@[16; 57) | ||
198 | PATH@[16; 22) | ||
199 | PATH_SEGMENT@[16; 22) | ||
200 | NAME_REF@[16; 22) | ||
201 | IDENT@[16; 22) "assert" | ||
202 | EXCL@[22; 23) | ||
203 | TOKEN_TREE@[23; 57) | ||
204 | L_PAREN@[23; 24) | ||
205 | STRING@[24; 52) | ||
206 | COMMA@[52; 53) | ||
207 | WHITESPACE@[53; 54) | ||
208 | STRING@[54; 56) | ||
209 | R_PAREN@[56; 57) | ||
210 | SEMI@[57; 58) | ||
211 | WHITESPACE@[58; 59) | ||
212 | R_CURLY@[59; 60) | ||
213 | "# | ||
214 | .trim() | ||
215 | ); | ||
216 | } | ||
217 | |||
218 | #[test] | ||
219 | fn test_syntax_tree_with_range() { | ||
220 | let (analysis, range) = single_file_with_range(r#"<|>fn foo() {}<|>"#.trim()); | ||
221 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)); | ||
222 | |||
223 | assert_eq!( | ||
224 | syn.trim(), | ||
225 | r#" | ||
226 | FN_DEF@[0; 11) | ||
227 | FN_KW@[0; 2) | ||
228 | WHITESPACE@[2; 3) | ||
229 | NAME@[3; 6) | ||
230 | IDENT@[3; 6) "foo" | ||
231 | PARAM_LIST@[6; 8) | ||
232 | L_PAREN@[6; 7) | ||
233 | R_PAREN@[7; 8) | ||
234 | WHITESPACE@[8; 9) | ||
235 | BLOCK@[9; 11) | ||
236 | L_CURLY@[9; 10) | ||
237 | R_CURLY@[10; 11) | ||
238 | "# | ||
239 | .trim() | ||
240 | ); | ||
241 | |||
242 | let (analysis, range) = single_file_with_range( | ||
243 | r#"fn test() { | ||
244 | <|>assert!(" | ||
245 | fn foo() { | ||
246 | } | ||
247 | ", "");<|> | ||
248 | }"# | ||
249 | .trim(), | ||
250 | ); | ||
251 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)); | ||
252 | |||
253 | assert_eq!( | ||
254 | syn.trim(), | ||
255 | r#" | ||
256 | EXPR_STMT@[16; 58) | ||
257 | MACRO_CALL@[16; 57) | ||
258 | PATH@[16; 22) | ||
259 | PATH_SEGMENT@[16; 22) | ||
260 | NAME_REF@[16; 22) | ||
261 | IDENT@[16; 22) "assert" | ||
262 | EXCL@[22; 23) | ||
263 | TOKEN_TREE@[23; 57) | ||
264 | L_PAREN@[23; 24) | ||
265 | STRING@[24; 52) | ||
266 | COMMA@[52; 53) | ||
267 | WHITESPACE@[53; 54) | ||
268 | STRING@[54; 56) | ||
269 | R_PAREN@[56; 57) | ||
270 | SEMI@[57; 58) | ||
271 | "# | ||
272 | .trim() | ||
273 | ); | ||
274 | } | ||
275 | |||
276 | #[test] | ||
277 | fn test_syntax_tree_inside_string() { | ||
278 | let (analysis, range) = single_file_with_range( | ||
279 | r#"fn test() { | ||
280 | assert!(" | ||
281 | <|>fn foo() { | ||
282 | }<|> | ||
283 | fn bar() { | ||
284 | } | ||
285 | ", ""); | ||
286 | }"# | ||
287 | .trim(), | ||
288 | ); | ||
289 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)); | ||
290 | assert_eq!( | ||
291 | syn.trim(), | ||
292 | r#" | ||
293 | SOURCE_FILE@[0; 12) | ||
294 | FN_DEF@[0; 12) | ||
295 | FN_KW@[0; 2) | ||
296 | WHITESPACE@[2; 3) | ||
297 | NAME@[3; 6) | ||
298 | IDENT@[3; 6) "foo" | ||
299 | PARAM_LIST@[6; 8) | ||
300 | L_PAREN@[6; 7) | ||
301 | R_PAREN@[7; 8) | ||
302 | WHITESPACE@[8; 9) | ||
303 | BLOCK@[9; 12) | ||
304 | L_CURLY@[9; 10) | ||
305 | WHITESPACE@[10; 11) | ||
306 | R_CURLY@[11; 12) | ||
307 | "# | ||
308 | .trim() | ||
309 | ); | ||
310 | |||
311 | // With a raw string | ||
312 | let (analysis, range) = single_file_with_range( | ||
313 | r###"fn test() { | ||
314 | assert!(r#" | ||
315 | <|>fn foo() { | ||
316 | }<|> | ||
317 | fn bar() { | ||
318 | } | ||
319 | "#, ""); | ||
320 | }"### | ||
321 | .trim(), | ||
322 | ); | ||
323 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)); | ||
324 | assert_eq!( | ||
325 | syn.trim(), | ||
326 | r#" | ||
327 | SOURCE_FILE@[0; 12) | ||
328 | FN_DEF@[0; 12) | ||
329 | FN_KW@[0; 2) | ||
330 | WHITESPACE@[2; 3) | ||
331 | NAME@[3; 6) | ||
332 | IDENT@[3; 6) "foo" | ||
333 | PARAM_LIST@[6; 8) | ||
334 | L_PAREN@[6; 7) | ||
335 | R_PAREN@[7; 8) | ||
336 | WHITESPACE@[8; 9) | ||
337 | BLOCK@[9; 12) | ||
338 | L_CURLY@[9; 10) | ||
339 | WHITESPACE@[10; 11) | ||
340 | R_CURLY@[11; 12) | ||
341 | "# | ||
342 | .trim() | ||
343 | ); | ||
344 | |||
345 | // With a raw string | ||
346 | let (analysis, range) = single_file_with_range( | ||
347 | r###"fn test() { | ||
348 | assert!(r<|>#" | ||
349 | fn foo() { | ||
350 | } | ||
351 | fn bar() { | ||
352 | }"<|>#, ""); | ||
353 | }"### | ||
354 | .trim(), | ||
355 | ); | ||
356 | let syn = analysis.syntax_tree(range.file_id, Some(range.range)); | ||
357 | assert_eq!( | ||
358 | syn.trim(), | ||
359 | r#" | ||
360 | SOURCE_FILE@[0; 25) | ||
361 | FN_DEF@[0; 12) | ||
362 | FN_KW@[0; 2) | ||
363 | WHITESPACE@[2; 3) | ||
364 | NAME@[3; 6) | ||
365 | IDENT@[3; 6) "foo" | ||
366 | PARAM_LIST@[6; 8) | ||
367 | L_PAREN@[6; 7) | ||
368 | R_PAREN@[7; 8) | ||
369 | WHITESPACE@[8; 9) | ||
370 | BLOCK@[9; 12) | ||
371 | L_CURLY@[9; 10) | ||
372 | WHITESPACE@[10; 11) | ||
373 | R_CURLY@[11; 12) | ||
374 | WHITESPACE@[12; 13) | ||
375 | FN_DEF@[13; 25) | ||
376 | FN_KW@[13; 15) | ||
377 | WHITESPACE@[15; 16) | ||
378 | NAME@[16; 19) | ||
379 | IDENT@[16; 19) "bar" | ||
380 | PARAM_LIST@[19; 21) | ||
381 | L_PAREN@[19; 20) | ||
382 | R_PAREN@[20; 21) | ||
383 | WHITESPACE@[21; 22) | ||
384 | BLOCK@[22; 25) | ||
385 | L_CURLY@[22; 23) | ||
386 | WHITESPACE@[23; 24) | ||
387 | R_CURLY@[24; 25) | ||
388 | |||
389 | "# | ||
390 | .trim() | ||
391 | ); | ||
392 | } | ||