aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r--crates/ra_ide_api/src/change.rs5
-rw-r--r--crates/ra_ide_api/src/completion/complete_dot.rs4
-rw-r--r--crates/ra_ide_api/src/completion/complete_struct_literal.rs4
-rw-r--r--crates/ra_ide_api/src/goto_definition.rs17
-rw-r--r--crates/ra_ide_api/src/hover.rs43
-rw-r--r--crates/ra_ide_api/src/lib.rs7
-rw-r--r--crates/ra_ide_api/src/syntax_tree.rs87
-rw-r--r--crates/ra_ide_api/tests/test/main.rs254
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
145fn rust_code_markup<CODE: AsRef<str>>(val: CODE) -> String {
146 rust_code_markup_with_doc::<_, &str>(val, None)
147}
148
149fn rust_code_markup_with_doc<CODE, DOC>(val: CODE, doc: Option<DOC>) -> String
150where
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`.
147fn doc_text_for(db: &RootDatabase, nav: NavigationTarget) -> Option<String> { 163fn 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;
32mod impls; 32mod impls;
33mod assists; 33mod assists;
34mod diagnostics; 34mod diagnostics;
35mod syntax_tree;
35 36
36#[cfg(test)] 37#[cfg(test)]
37mod marks; 38mod marks;
38 39
39use std::sync::Arc; 40use std::sync::Arc;
40 41
41use ra_syntax::{SourceFile, TreeArc, TextRange, TextUnit, AstNode}; 42use ra_syntax::{SourceFile, TreeArc, TextRange, TextUnit};
42use ra_text_edit::TextEdit; 43use ra_text_edit::TextEdit;
43use ra_db::{ 44use 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 @@
1use ra_db::SourceDatabase;
2use crate::db::RootDatabase;
3use ra_syntax::{
4 SourceFile, SyntaxNode, TextRange, AstNode,
5 algo::{self, visit::{visitor, Visitor}}, ast::{self, AstToken}
6};
7
8pub use ra_db::FileId;
9
10pub(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
31fn 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
41fn 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 @@
1use insta::assert_debug_snapshot_matches; 1use insta::assert_debug_snapshot_matches;
2use ra_ide_api::{ 2use 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]
143fn 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#"
151SOURCE_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#"
170fn 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#"
183SOURCE_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]
219fn 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#"
226FN_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#"
256EXPR_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]
277fn test_syntax_tree_inside_string() {
278 let (analysis, range) = single_file_with_range(
279 r#"fn test() {
280 assert!("
281<|>fn foo() {
282}<|>
283fn 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#"
293SOURCE_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}<|>
317fn 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#"
327SOURCE_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<|>#"
349fn foo() {
350}
351fn 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#"
360SOURCE_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}