aboutsummaryrefslogtreecommitdiff
path: root/crates/syntax/src/ast
diff options
context:
space:
mode:
Diffstat (limited to 'crates/syntax/src/ast')
-rw-r--r--crates/syntax/src/ast/edit.rs296
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs88
-rw-r--r--crates/syntax/src/ast/make.rs12
3 files changed, 124 insertions, 272 deletions
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index cbc75f922..4b5f5c571 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -2,20 +2,20 @@
2//! immutable, all function here return a fresh copy of the tree, instead of 2//! immutable, all function here return a fresh copy of the tree, instead of
3//! doing an in-place modification. 3//! doing an in-place modification.
4use std::{ 4use std::{
5 array, fmt, iter, 5 fmt, iter,
6 ops::{self, RangeInclusive}, 6 ops::{self, RangeInclusive},
7}; 7};
8 8
9use arrayvec::ArrayVec; 9use arrayvec::ArrayVec;
10 10
11use crate::{ 11use crate::{
12 algo::{self, SyntaxRewriter}, 12 algo,
13 ast::{ 13 ast::{
14 self, 14 self,
15 make::{self, tokens}, 15 make::{self, tokens},
16 AstNode, GenericParamsOwner, NameOwner, TypeBoundsOwner, 16 AstNode,
17 }, 17 },
18 AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind, 18 ted, AstToken, Direction, InsertPosition, NodeOrToken, SmolStr, SyntaxElement, SyntaxKind,
19 SyntaxKind::{ATTR, COMMENT, WHITESPACE}, 19 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
20 SyntaxNode, SyntaxToken, T, 20 SyntaxNode, SyntaxToken, T,
21}; 21};
@@ -29,37 +29,6 @@ impl ast::BinExpr {
29 } 29 }
30} 30}
31 31
32impl ast::Fn {
33 #[must_use]
34 pub fn with_body(&self, body: ast::BlockExpr) -> ast::Fn {
35 let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
36 let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.body() {
37 old_body.syntax().clone().into()
38 } else if let Some(semi) = self.semicolon_token() {
39 to_insert.push(make::tokens::single_space().into());
40 semi.into()
41 } else {
42 to_insert.push(make::tokens::single_space().into());
43 to_insert.push(body.syntax().clone().into());
44 return self.insert_children(InsertPosition::Last, to_insert);
45 };
46 to_insert.push(body.syntax().clone().into());
47 self.replace_children(single_node(old_body_or_semi), to_insert)
48 }
49
50 #[must_use]
51 pub fn with_generic_param_list(&self, generic_args: ast::GenericParamList) -> ast::Fn {
52 if let Some(old) = self.generic_param_list() {
53 return self.replace_descendant(old, generic_args);
54 }
55
56 let anchor = self.name().expect("The function must have a name").syntax().clone();
57
58 let to_insert = [generic_args.syntax().clone().into()];
59 self.insert_children(InsertPosition::After(anchor.into()), array::IntoIter::new(to_insert))
60 }
61}
62
63fn make_multiline<N>(node: N) -> N 32fn make_multiline<N>(node: N) -> N
64where 33where
65 N: AstNode + Clone, 34 N: AstNode + Clone,
@@ -92,81 +61,6 @@ where
92 } 61 }
93} 62}
94 63
95impl ast::Impl {
96 #[must_use]
97 pub fn with_assoc_item_list(&self, items: ast::AssocItemList) -> ast::Impl {
98 let mut to_insert: ArrayVec<SyntaxElement, 2> = ArrayVec::new();
99 if let Some(old_items) = self.assoc_item_list() {
100 let to_replace: SyntaxElement = old_items.syntax().clone().into();
101 to_insert.push(items.syntax().clone().into());
102 self.replace_children(single_node(to_replace), to_insert)
103 } else {
104 to_insert.push(make::tokens::single_space().into());
105 to_insert.push(items.syntax().clone().into());
106 self.insert_children(InsertPosition::Last, to_insert)
107 }
108 }
109}
110
111impl ast::AssocItemList {
112 #[must_use]
113 pub fn append_items(
114 &self,
115 items: impl IntoIterator<Item = ast::AssocItem>,
116 ) -> ast::AssocItemList {
117 let mut res = self.clone();
118 if !self.syntax().text().contains_char('\n') {
119 res = make_multiline(res);
120 }
121 items.into_iter().for_each(|it| res = res.append_item(it));
122 res.fixup_trailing_whitespace().unwrap_or(res)
123 }
124
125 #[must_use]
126 pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList {
127 let (indent, position, whitespace) = match self.assoc_items().last() {
128 Some(it) => (
129 leading_indent(it.syntax()).unwrap_or_default().to_string(),
130 InsertPosition::After(it.syntax().clone().into()),
131 "\n\n",
132 ),
133 None => match self.l_curly_token() {
134 Some(it) => (
135 " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
136 InsertPosition::After(it.into()),
137 "\n",
138 ),
139 None => return self.clone(),
140 },
141 };
142 let ws = tokens::WsBuilder::new(&format!("{}{}", whitespace, indent));
143 let to_insert: ArrayVec<SyntaxElement, 2> =
144 [ws.ws().into(), item.syntax().clone().into()].into();
145 self.insert_children(position, to_insert)
146 }
147
148 /// Remove extra whitespace between last item and closing curly brace.
149 fn fixup_trailing_whitespace(&self) -> Option<ast::AssocItemList> {
150 let first_token_after_items =
151 self.assoc_items().last()?.syntax().next_sibling_or_token()?;
152 let last_token_before_curly = self.r_curly_token()?.prev_sibling_or_token()?;
153 if last_token_before_curly != first_token_after_items {
154 // there is something more between last item and
155 // right curly than just whitespace - bail out
156 return None;
157 }
158 let whitespace =
159 last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?;
160 let text = whitespace.syntax().text();
161 let newline = text.rfind('\n')?;
162 let keep = tokens::WsBuilder::new(&text[newline..]);
163 Some(self.replace_children(
164 first_token_after_items..=last_token_before_curly,
165 std::iter::once(keep.ws().into()),
166 ))
167 }
168}
169
170impl ast::RecordExprFieldList { 64impl ast::RecordExprFieldList {
171 #[must_use] 65 #[must_use]
172 pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList { 66 pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList {
@@ -243,36 +137,6 @@ impl ast::RecordExprFieldList {
243 } 137 }
244} 138}
245 139
246impl ast::TypeAlias {
247 #[must_use]
248 pub fn remove_bounds(&self) -> ast::TypeAlias {
249 let colon = match self.colon_token() {
250 Some(it) => it,
251 None => return self.clone(),
252 };
253 let end = match self.type_bound_list() {
254 Some(it) => it.syntax().clone().into(),
255 None => colon.clone().into(),
256 };
257 self.replace_children(colon.into()..=end, iter::empty())
258 }
259}
260
261impl ast::TypeParam {
262 #[must_use]
263 pub fn remove_bounds(&self) -> ast::TypeParam {
264 let colon = match self.colon_token() {
265 Some(it) => it,
266 None => return self.clone(),
267 };
268 let end = match self.type_bound_list() {
269 Some(it) => it.syntax().clone().into(),
270 None => colon.clone().into(),
271 };
272 self.replace_children(colon.into()..=end, iter::empty())
273 }
274}
275
276impl ast::Path { 140impl ast::Path {
277 #[must_use] 141 #[must_use]
278 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { 142 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path {
@@ -313,33 +177,7 @@ impl ast::PathSegment {
313 } 177 }
314} 178}
315 179
316impl ast::Use {
317 #[must_use]
318 pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::Use {
319 if let Some(old) = self.use_tree() {
320 return self.replace_descendant(old, use_tree);
321 }
322 self.clone()
323 }
324}
325
326impl ast::UseTree { 180impl ast::UseTree {
327 #[must_use]
328 pub fn with_path(&self, path: ast::Path) -> ast::UseTree {
329 if let Some(old) = self.path() {
330 return self.replace_descendant(old, path);
331 }
332 self.clone()
333 }
334
335 #[must_use]
336 pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree {
337 if let Some(old) = self.use_tree_list() {
338 return self.replace_descendant(old, use_tree_list);
339 }
340 self.clone()
341 }
342
343 /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items. 181 /// Splits off the given prefix, making it the path component of the use tree, appending the rest of the path to all UseTreeList items.
344 #[must_use] 182 #[must_use]
345 pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree { 183 pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree {
@@ -449,61 +287,6 @@ impl ast::MatchArmList {
449 } 287 }
450} 288}
451 289
452impl ast::GenericParamList {
453 #[must_use]
454 pub fn append_params(
455 &self,
456 params: impl IntoIterator<Item = ast::GenericParam>,
457 ) -> ast::GenericParamList {
458 let mut res = self.clone();
459 params.into_iter().for_each(|it| res = res.append_param(it));
460 res
461 }
462
463 #[must_use]
464 pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList {
465 let space = tokens::single_space();
466
467 let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
468 if self.generic_params().next().is_some() {
469 to_insert.push(space.into());
470 }
471 to_insert.push(item.syntax().clone().into());
472
473 macro_rules! after_l_angle {
474 () => {{
475 let anchor = match self.l_angle_token() {
476 Some(it) => it.into(),
477 None => return self.clone(),
478 };
479 InsertPosition::After(anchor)
480 }};
481 }
482
483 macro_rules! after_field {
484 ($anchor:expr) => {
485 if let Some(comma) = $anchor
486 .syntax()
487 .siblings_with_tokens(Direction::Next)
488 .find(|it| it.kind() == T![,])
489 {
490 InsertPosition::After(comma)
491 } else {
492 to_insert.insert(0, make::token(T![,]).into());
493 InsertPosition::After($anchor.syntax().clone().into())
494 }
495 };
496 }
497
498 let position = match self.generic_params().last() {
499 Some(it) => after_field!(it),
500 None => after_l_angle!(),
501 };
502
503 self.insert_children(position, to_insert)
504 }
505}
506
507#[must_use] 290#[must_use]
508pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 291pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
509 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() 292 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
@@ -554,6 +337,12 @@ impl ops::Add<u8> for IndentLevel {
554} 337}
555 338
556impl IndentLevel { 339impl IndentLevel {
340 pub fn single() -> IndentLevel {
341 IndentLevel(0)
342 }
343 pub fn is_zero(&self) -> bool {
344 self.0 == 0
345 }
557 pub fn from_element(element: &SyntaxElement) -> IndentLevel { 346 pub fn from_element(element: &SyntaxElement) -> IndentLevel {
558 match element { 347 match element {
559 rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it), 348 rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),
@@ -588,37 +377,39 @@ impl IndentLevel {
588 /// ``` 377 /// ```
589 /// if you indent the block, the `{` token would stay put. 378 /// if you indent the block, the `{` token would stay put.
590 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { 379 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode {
591 let mut rewriter = SyntaxRewriter::default(); 380 let res = node.clone_subtree().clone_for_update();
592 node.descendants_with_tokens() 381 let tokens = res.preorder_with_tokens().filter_map(|event| match event {
593 .filter_map(|el| el.into_token()) 382 rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
594 .filter_map(ast::Whitespace::cast) 383 _ => None,
595 .filter(|ws| { 384 });
596 let text = ws.syntax().text(); 385 for token in tokens {
597 text.contains('\n') 386 if let Some(ws) = ast::Whitespace::cast(token) {
598 }) 387 if ws.text().contains('\n') {
599 .for_each(|ws| { 388 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self));
600 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,)); 389 ted::replace(ws.syntax(), &new_ws)
601 rewriter.replace(ws.syntax(), &new_ws) 390 }
602 }); 391 }
603 rewriter.rewrite(&node) 392 }
393 res.clone_subtree()
604 } 394 }
605 395
606 fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode { 396 fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
607 let mut rewriter = SyntaxRewriter::default(); 397 let res = node.clone_subtree().clone_for_update();
608 node.descendants_with_tokens() 398 let tokens = res.preorder_with_tokens().filter_map(|event| match event {
609 .filter_map(|el| el.into_token()) 399 rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
610 .filter_map(ast::Whitespace::cast) 400 _ => None,
611 .filter(|ws| { 401 });
612 let text = ws.syntax().text(); 402 for token in tokens {
613 text.contains('\n') 403 if let Some(ws) = ast::Whitespace::cast(token) {
614 }) 404 if ws.text().contains('\n') {
615 .for_each(|ws| { 405 let new_ws = make::tokens::whitespace(
616 let new_ws = make::tokens::whitespace( 406 &ws.syntax().text().replace(&format!("\n{}", self), "\n"),
617 &ws.syntax().text().replace(&format!("\n{}", self), "\n"), 407 );
618 ); 408 ted::replace(ws.syntax(), &new_ws)
619 rewriter.replace(ws.syntax(), &new_ws) 409 }
620 }); 410 }
621 rewriter.rewrite(&node) 411 }
412 res.clone_subtree()
622 } 413 }
623} 414}
624 415
@@ -662,13 +453,6 @@ pub trait AstNodeEdit: AstNode + Clone + Sized {
662 let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert); 453 let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert);
663 Self::cast(new_syntax).unwrap() 454 Self::cast(new_syntax).unwrap()
664 } 455 }
665
666 #[must_use]
667 fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self {
668 let mut rewriter = SyntaxRewriter::default();
669 rewriter.replace(old.syntax(), new.syntax());
670 rewriter.rewrite_ast(self)
671 }
672 fn indent_level(&self) -> IndentLevel { 456 fn indent_level(&self) -> IndentLevel {
673 IndentLevel::from_node(self.syntax()) 457 IndentLevel::from_node(self.syntax())
674 } 458 }
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 168355555..ca777d057 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -2,11 +2,16 @@
2 2
3use std::iter::empty; 3use std::iter::empty;
4 4
5use parser::T; 5use parser::{SyntaxKind, T};
6use rowan::SyntaxElement;
6 7
7use crate::{ 8use crate::{
8 algo::neighbor, 9 algo::neighbor,
9 ast::{self, edit::AstNodeEdit, make, GenericParamsOwner, WhereClause}, 10 ast::{
11 self,
12 edit::{AstNodeEdit, IndentLevel},
13 make, GenericParamsOwner,
14 },
10 ted::{self, Position}, 15 ted::{self, Position},
11 AstNode, AstToken, Direction, 16 AstNode, AstToken, Direction,
12}; 17};
@@ -37,7 +42,7 @@ impl GenericParamsOwnerEdit for ast::Fn {
37 } 42 }
38 } 43 }
39 44
40 fn get_or_create_where_clause(&self) -> WhereClause { 45 fn get_or_create_where_clause(&self) -> ast::WhereClause {
41 if self.where_clause().is_none() { 46 if self.where_clause().is_none() {
42 let position = if let Some(ty) = self.ret_type() { 47 let position = if let Some(ty) = self.ret_type() {
43 Position::after(ty.syntax()) 48 Position::after(ty.syntax())
@@ -67,7 +72,7 @@ impl GenericParamsOwnerEdit for ast::Impl {
67 } 72 }
68 } 73 }
69 74
70 fn get_or_create_where_clause(&self) -> WhereClause { 75 fn get_or_create_where_clause(&self) -> ast::WhereClause {
71 if self.where_clause().is_none() { 76 if self.where_clause().is_none() {
72 let position = if let Some(items) = self.assoc_item_list() { 77 let position = if let Some(items) = self.assoc_item_list() {
73 Position::before(items.syntax()) 78 Position::before(items.syntax())
@@ -97,7 +102,7 @@ impl GenericParamsOwnerEdit for ast::Trait {
97 } 102 }
98 } 103 }
99 104
100 fn get_or_create_where_clause(&self) -> WhereClause { 105 fn get_or_create_where_clause(&self) -> ast::WhereClause {
101 if self.where_clause().is_none() { 106 if self.where_clause().is_none() {
102 let position = if let Some(items) = self.assoc_item_list() { 107 let position = if let Some(items) = self.assoc_item_list() {
103 Position::before(items.syntax()) 108 Position::before(items.syntax())
@@ -127,7 +132,7 @@ impl GenericParamsOwnerEdit for ast::Struct {
127 } 132 }
128 } 133 }
129 134
130 fn get_or_create_where_clause(&self) -> WhereClause { 135 fn get_or_create_where_clause(&self) -> ast::WhereClause {
131 if self.where_clause().is_none() { 136 if self.where_clause().is_none() {
132 let tfl = self.field_list().and_then(|fl| match fl { 137 let tfl = self.field_list().and_then(|fl| match fl {
133 ast::FieldList::RecordFieldList(_) => None, 138 ast::FieldList::RecordFieldList(_) => None,
@@ -165,7 +170,7 @@ impl GenericParamsOwnerEdit for ast::Enum {
165 } 170 }
166 } 171 }
167 172
168 fn get_or_create_where_clause(&self) -> WhereClause { 173 fn get_or_create_where_clause(&self) -> ast::WhereClause {
169 if self.where_clause().is_none() { 174 if self.where_clause().is_none() {
170 let position = if let Some(gpl) = self.generic_param_list() { 175 let position = if let Some(gpl) = self.generic_param_list() {
171 Position::after(gpl.syntax()) 176 Position::after(gpl.syntax())
@@ -272,6 +277,75 @@ impl ast::Use {
272 } 277 }
273} 278}
274 279
280impl ast::Impl {
281 pub fn get_or_create_assoc_item_list(&self) -> ast::AssocItemList {
282 if self.assoc_item_list().is_none() {
283 let assoc_item_list = make::assoc_item_list().clone_for_update();
284 ted::append_child(self.syntax(), assoc_item_list.syntax());
285 }
286 self.assoc_item_list().unwrap()
287 }
288}
289
290impl ast::AssocItemList {
291 pub fn add_item(&self, item: ast::AssocItem) {
292 let (indent, position, whitespace) = match self.assoc_items().last() {
293 Some(last_item) => (
294 IndentLevel::from_node(last_item.syntax()),
295 Position::after(last_item.syntax()),
296 "\n\n",
297 ),
298 None => match self.l_curly_token() {
299 Some(l_curly) => {
300 self.normalize_ws_between_braces();
301 (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n")
302 }
303 None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"),
304 },
305 };
306 let elements: Vec<SyntaxElement<_>> = vec![
307 make::tokens::whitespace(&format!("{}{}", whitespace, indent)).into(),
308 item.syntax().clone().into(),
309 ];
310 ted::insert_all(position, elements);
311 }
312
313 fn normalize_ws_between_braces(&self) -> Option<()> {
314 let l = self.l_curly_token()?;
315 let r = self.r_curly_token()?;
316 let indent = IndentLevel::from_node(self.syntax());
317
318 match l.next_sibling_or_token() {
319 Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => {
320 if ws.next_sibling_or_token()?.into_token()? == r {
321 ted::replace(ws, make::tokens::whitespace(&format!("\n{}", indent)));
322 }
323 }
324 Some(ws) if ws.kind() == T!['}'] => {
325 ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{}", indent)));
326 }
327 _ => (),
328 }
329 Some(())
330 }
331}
332
333impl ast::Fn {
334 pub fn get_or_create_body(&self) -> ast::BlockExpr {
335 if self.body().is_none() {
336 let body = make::ext::empty_block_expr().clone_for_update();
337 match self.semicolon_token() {
338 Some(semi) => {
339 ted::replace(semi, body.syntax());
340 ted::insert(Position::before(body.syntax), make::tokens::single_space());
341 }
342 None => ted::append_child(self.syntax(), body.syntax()),
343 }
344 }
345 self.body().unwrap()
346 }
347}
348
275#[cfg(test)] 349#[cfg(test)]
276mod tests { 350mod tests {
277 use std::fmt; 351 use std::fmt;
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 1998ad1f6..d13926ded 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -12,7 +12,7 @@
12use itertools::Itertools; 12use itertools::Itertools;
13use stdx::{format_to, never}; 13use stdx::{format_to, never};
14 14
15use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, SyntaxToken}; 15use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxToken};
16 16
17/// While the parent module defines basic atomic "constructors", the `ext` 17/// While the parent module defines basic atomic "constructors", the `ext`
18/// module defines shortcuts for common things. 18/// module defines shortcuts for common things.
@@ -99,7 +99,7 @@ fn ty_from_text(text: &str) -> ast::Type {
99} 99}
100 100
101pub fn assoc_item_list() -> ast::AssocItemList { 101pub fn assoc_item_list() -> ast::AssocItemList {
102 ast_from_text("impl C for D {};") 102 ast_from_text("impl C for D {}")
103} 103}
104 104
105pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl { 105pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl {
@@ -601,17 +601,11 @@ fn ast_from_text<N: AstNode>(text: &str) -> N {
601 panic!("Failed to make ast node `{}` from text {}", std::any::type_name::<N>(), text) 601 panic!("Failed to make ast node `{}` from text {}", std::any::type_name::<N>(), text)
602 } 602 }
603 }; 603 };
604 let node = node.syntax().clone(); 604 let node = node.clone_subtree();
605 let node = unroot(node);
606 let node = N::cast(node).unwrap();
607 assert_eq!(node.syntax().text_range().start(), 0.into()); 605 assert_eq!(node.syntax().text_range().start(), 0.into());
608 node 606 node
609} 607}
610 608
611fn unroot(n: SyntaxNode) -> SyntaxNode {
612 SyntaxNode::new_root(n.green().into())
613}
614
615pub fn token(kind: SyntaxKind) -> SyntaxToken { 609pub fn token(kind: SyntaxKind) -> SyntaxToken {
616 tokens::SOURCE_FILE 610 tokens::SOURCE_FILE
617 .tree() 611 .tree()