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.rs503
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs201
-rw-r--r--crates/syntax/src/ast/make.rs122
-rw-r--r--crates/syntax/src/ast/token_ext.rs81
4 files changed, 357 insertions, 550 deletions
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 8c60927e4..61952377f 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -2,20 +2,16 @@
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::{self, make, AstNode},
14 self, 14 ted, AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxKind,
15 make::{self, tokens},
16 AstNode, GenericParamsOwner, NameOwner, TypeBoundsOwner,
17 },
18 AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind,
19 SyntaxKind::{ATTR, COMMENT, WHITESPACE}, 15 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
20 SyntaxNode, SyntaxToken, T, 16 SyntaxNode, SyntaxToken, T,
21}; 17};
@@ -29,250 +25,6 @@ impl ast::BinExpr {
29 } 25 }
30} 26}
31 27
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
64where
65 N: AstNode + Clone,
66{
67 let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
68 Some(it) => it,
69 None => return node,
70 };
71 let sibling = match l_curly.next_sibling_or_token() {
72 Some(it) => it,
73 None => return node,
74 };
75 let existing_ws = match sibling.as_token() {
76 None => None,
77 Some(tok) if tok.kind() != WHITESPACE => None,
78 Some(ws) => {
79 if ws.text().contains('\n') {
80 return node;
81 }
82 Some(ws.clone())
83 }
84 };
85
86 let indent = leading_indent(node.syntax()).unwrap_or_default();
87 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
88 let to_insert = iter::once(ws.ws().into());
89 match existing_ws {
90 None => node.insert_children(InsertPosition::After(l_curly), to_insert),
91 Some(ws) => node.replace_children(single_node(ws), to_insert),
92 }
93}
94
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 {
171 #[must_use]
172 pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList {
173 self.insert_field(InsertPosition::Last, field)
174 }
175
176 #[must_use]
177 pub fn insert_field(
178 &self,
179 position: InsertPosition<&'_ ast::RecordExprField>,
180 field: &ast::RecordExprField,
181 ) -> ast::RecordExprFieldList {
182 let is_multiline = self.syntax().text().contains_char('\n');
183 let ws;
184 let space = if is_multiline {
185 ws = tokens::WsBuilder::new(&format!(
186 "\n{} ",
187 leading_indent(self.syntax()).unwrap_or_default()
188 ));
189 ws.ws()
190 } else {
191 tokens::single_space()
192 };
193
194 let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
195 to_insert.push(space.into());
196 to_insert.push(field.syntax().clone().into());
197 to_insert.push(make::token(T![,]).into());
198
199 macro_rules! after_l_curly {
200 () => {{
201 let anchor = match self.l_curly_token() {
202 Some(it) => it.into(),
203 None => return self.clone(),
204 };
205 InsertPosition::After(anchor)
206 }};
207 }
208
209 macro_rules! after_field {
210 ($anchor:expr) => {
211 if let Some(comma) = $anchor
212 .syntax()
213 .siblings_with_tokens(Direction::Next)
214 .find(|it| it.kind() == T![,])
215 {
216 InsertPosition::After(comma)
217 } else {
218 to_insert.insert(0, make::token(T![,]).into());
219 InsertPosition::After($anchor.syntax().clone().into())
220 }
221 };
222 }
223
224 let position = match position {
225 InsertPosition::First => after_l_curly!(),
226 InsertPosition::Last => {
227 if !is_multiline {
228 // don't insert comma before curly
229 to_insert.pop();
230 }
231 match self.fields().last() {
232 Some(it) => after_field!(it),
233 None => after_l_curly!(),
234 }
235 }
236 InsertPosition::Before(anchor) => {
237 InsertPosition::Before(anchor.syntax().clone().into())
238 }
239 InsertPosition::After(anchor) => after_field!(anchor),
240 };
241
242 self.insert_children(position, to_insert)
243 }
244}
245
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 { 28impl ast::Path {
277 #[must_use] 29 #[must_use]
278 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { 30 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path {
@@ -313,33 +65,7 @@ impl ast::PathSegment {
313 } 65 }
314} 66}
315 67
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 { 68impl 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. 69 /// 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] 70 #[must_use]
345 pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree { 71 pub fn split_prefix(&self, prefix: &ast::Path) -> ast::UseTree {
@@ -376,134 +102,6 @@ impl ast::UseTree {
376 } 102 }
377} 103}
378 104
379impl ast::MatchArmList {
380 #[must_use]
381 pub fn append_arms(&self, items: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
382 let mut res = self.clone();
383 res = res.strip_if_only_whitespace();
384 if !res.syntax().text().contains_char('\n') {
385 res = make_multiline(res);
386 }
387 items.into_iter().for_each(|it| res = res.append_arm(it));
388 res
389 }
390
391 fn strip_if_only_whitespace(&self) -> ast::MatchArmList {
392 let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']);
393 iter.next(); // Eat the curly
394 let mut inner = iter.take_while(|it| it.kind() != T!['}']);
395 if !inner.clone().all(|it| it.kind() == WHITESPACE) {
396 return self.clone();
397 }
398 let start = match inner.next() {
399 Some(s) => s,
400 None => return self.clone(),
401 };
402 let end = match inner.last() {
403 Some(s) => s,
404 None => start.clone(),
405 };
406 self.replace_children(start..=end, &mut iter::empty())
407 }
408
409 #[must_use]
410 pub fn remove_placeholder(&self) -> ast::MatchArmList {
411 let placeholder =
412 self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
413 if let Some(placeholder) = placeholder {
414 self.remove_arm(&placeholder)
415 } else {
416 self.clone()
417 }
418 }
419
420 #[must_use]
421 fn remove_arm(&self, arm: &ast::MatchArm) -> ast::MatchArmList {
422 let start = arm.syntax().clone();
423 let end = if let Some(comma) = start
424 .siblings_with_tokens(Direction::Next)
425 .skip(1)
426 .find(|it| !it.kind().is_trivia())
427 .filter(|it| it.kind() == T![,])
428 {
429 comma
430 } else {
431 start.clone().into()
432 };
433 self.replace_children(start.into()..=end, None)
434 }
435
436 #[must_use]
437 pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList {
438 let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) {
439 Some(t) => t,
440 None => return self.clone(),
441 };
442 let position = InsertPosition::Before(r_curly);
443 let arm_ws = tokens::WsBuilder::new(" ");
444 let match_indent = &leading_indent(self.syntax()).unwrap_or_default();
445 let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent));
446 let to_insert: ArrayVec<SyntaxElement, 3> =
447 [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into();
448 self.insert_children(position, to_insert)
449 }
450}
451
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] 105#[must_use]
508pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 106pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
509 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() 107 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
@@ -554,6 +152,12 @@ impl ops::Add<u8> for IndentLevel {
554} 152}
555 153
556impl IndentLevel { 154impl IndentLevel {
155 pub fn single() -> IndentLevel {
156 IndentLevel(0)
157 }
158 pub fn is_zero(&self) -> bool {
159 self.0 == 0
160 }
557 pub fn from_element(element: &SyntaxElement) -> IndentLevel { 161 pub fn from_element(element: &SyntaxElement) -> IndentLevel {
558 match element { 162 match element {
559 rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it), 163 rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),
@@ -588,54 +192,40 @@ impl IndentLevel {
588 /// ``` 192 /// ```
589 /// if you indent the block, the `{` token would stay put. 193 /// if you indent the block, the `{` token would stay put.
590 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { 194 fn increase_indent(self, node: SyntaxNode) -> SyntaxNode {
591 let mut rewriter = SyntaxRewriter::default(); 195 let res = node.clone_subtree().clone_for_update();
592 node.descendants_with_tokens() 196 let tokens = res.preorder_with_tokens().filter_map(|event| match event {
593 .filter_map(|el| el.into_token()) 197 rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
594 .filter_map(ast::Whitespace::cast) 198 _ => None,
595 .filter(|ws| { 199 });
596 let text = ws.syntax().text(); 200 for token in tokens {
597 text.contains('\n') 201 if let Some(ws) = ast::Whitespace::cast(token) {
598 }) 202 if ws.text().contains('\n') {
599 .for_each(|ws| { 203 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self));
600 let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,)); 204 ted::replace(ws.syntax(), &new_ws)
601 rewriter.replace(ws.syntax(), &new_ws) 205 }
602 }); 206 }
603 rewriter.rewrite(&node) 207 }
208 res.clone_subtree()
604 } 209 }
605 210
606 fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode { 211 fn decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
607 let mut rewriter = SyntaxRewriter::default(); 212 let res = node.clone_subtree().clone_for_update();
608 node.descendants_with_tokens() 213 let tokens = res.preorder_with_tokens().filter_map(|event| match event {
609 .filter_map(|el| el.into_token()) 214 rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
610 .filter_map(ast::Whitespace::cast) 215 _ => None,
611 .filter(|ws| { 216 });
612 let text = ws.syntax().text(); 217 for token in tokens {
613 text.contains('\n') 218 if let Some(ws) = ast::Whitespace::cast(token) {
614 }) 219 if ws.text().contains('\n') {
615 .for_each(|ws| { 220 let new_ws = make::tokens::whitespace(
616 let new_ws = make::tokens::whitespace( 221 &ws.syntax().text().replace(&format!("\n{}", self), "\n"),
617 &ws.syntax().text().replace(&format!("\n{}", self), "\n"), 222 );
618 ); 223 ted::replace(ws.syntax(), &new_ws)
619 rewriter.replace(ws.syntax(), &new_ws) 224 }
620 });
621 rewriter.rewrite(&node)
622 }
623}
624
625// FIXME: replace usages with IndentLevel above
626fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
627 for token in prev_tokens(node.first_token()?) {
628 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
629 let ws_text = ws.text();
630 if let Some(pos) = ws_text.rfind('\n') {
631 return Some(ws_text[pos + 1..].into());
632 } 225 }
633 } 226 }
634 if token.text().contains('\n') { 227 res.clone_subtree()
635 break;
636 }
637 } 228 }
638 None
639} 229}
640 230
641fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { 231fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
@@ -662,23 +252,6 @@ pub trait AstNodeEdit: AstNode + Clone + Sized {
662 let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert); 252 let new_syntax = algo::replace_children(self.syntax(), to_replace, to_insert);
663 Self::cast(new_syntax).unwrap() 253 Self::cast(new_syntax).unwrap()
664 } 254 }
665
666 #[must_use]
667 fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self {
668 self.replace_descendants(iter::once((old, new)))
669 }
670
671 #[must_use]
672 fn replace_descendants<D: AstNode>(
673 &self,
674 replacement_map: impl IntoIterator<Item = (D, D)>,
675 ) -> Self {
676 let mut rewriter = SyntaxRewriter::default();
677 for (from, to) in replacement_map {
678 rewriter.replace(from.syntax(), to.syntax())
679 }
680 rewriter.rewrite_ast(self)
681 }
682 fn indent_level(&self) -> IndentLevel { 255 fn indent_level(&self) -> IndentLevel {
683 IndentLevel::from_node(self.syntax()) 256 IndentLevel::from_node(self.syntax())
684 } 257 }
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 04f97f368..2676ed8c9 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -2,13 +2,18 @@
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, SyntaxNode,
12}; 17};
13 18
14use super::NameOwner; 19use super::NameOwner;
@@ -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())
@@ -195,18 +200,13 @@ impl ast::GenericParamList {
195 pub fn add_generic_param(&self, generic_param: ast::GenericParam) { 200 pub fn add_generic_param(&self, generic_param: ast::GenericParam) {
196 match self.generic_params().last() { 201 match self.generic_params().last() {
197 Some(last_param) => { 202 Some(last_param) => {
198 let mut elems = Vec::new(); 203 let position = Position::after(last_param.syntax());
199 if !last_param 204 let elements = vec![
200 .syntax() 205 make::token(T![,]).into(),
201 .siblings_with_tokens(Direction::Next) 206 make::tokens::single_space().into(),
202 .any(|it| it.kind() == T![,]) 207 generic_param.syntax().clone().into(),
203 { 208 ];
204 elems.push(make::token(T![,]).into()); 209 ted::insert_all(position, elements);
205 elems.push(make::tokens::single_space().into());
206 };
207 elems.push(generic_param.syntax().clone().into());
208 let after_last_param = Position::after(last_param.syntax());
209 ted::insert_all(after_last_param, elems);
210 } 210 }
211 None => { 211 None => {
212 let after_l_angle = Position::after(self.l_angle_token().unwrap()); 212 let after_l_angle = Position::after(self.l_angle_token().unwrap());
@@ -277,6 +277,167 @@ impl ast::Use {
277 } 277 }
278} 278}
279 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 normalize_ws_between_braces(self.syntax());
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
314impl ast::Fn {
315 pub fn get_or_create_body(&self) -> ast::BlockExpr {
316 if self.body().is_none() {
317 let body = make::ext::empty_block_expr().clone_for_update();
318 match self.semicolon_token() {
319 Some(semi) => {
320 ted::replace(semi, body.syntax());
321 ted::insert(Position::before(body.syntax), make::tokens::single_space());
322 }
323 None => ted::append_child(self.syntax(), body.syntax()),
324 }
325 }
326 self.body().unwrap()
327 }
328}
329
330impl ast::MatchArm {
331 pub fn remove(&self) {
332 if let Some(sibling) = self.syntax().prev_sibling_or_token() {
333 if sibling.kind() == SyntaxKind::WHITESPACE {
334 ted::remove(sibling);
335 }
336 }
337 if let Some(sibling) = self.syntax().next_sibling_or_token() {
338 if sibling.kind() == T![,] {
339 ted::remove(sibling);
340 }
341 }
342 ted::remove(self.syntax());
343 }
344}
345
346impl ast::MatchArmList {
347 pub fn add_arm(&self, arm: ast::MatchArm) {
348 normalize_ws_between_braces(self.syntax());
349 let position = match self.arms().last() {
350 Some(last_arm) => {
351 let curly = last_arm
352 .syntax()
353 .siblings_with_tokens(Direction::Next)
354 .find(|it| it.kind() == T![,]);
355 Position::after(curly.unwrap_or_else(|| last_arm.syntax().clone().into()))
356 }
357 None => match self.l_curly_token() {
358 Some(it) => Position::after(it),
359 None => Position::last_child_of(self.syntax()),
360 },
361 };
362 let indent = IndentLevel::from_node(self.syntax()) + 1;
363 let elements = vec![
364 make::tokens::whitespace(&format!("\n{}", indent)).into(),
365 arm.syntax().clone().into(),
366 ];
367 ted::insert_all(position, elements);
368 }
369}
370
371impl ast::RecordExprFieldList {
372 pub fn add_field(&self, field: ast::RecordExprField) {
373 let is_multiline = self.syntax().text().contains_char('\n');
374 let whitespace = if is_multiline {
375 let indent = IndentLevel::from_node(self.syntax()) + 1;
376 make::tokens::whitespace(&format!("\n{}", indent))
377 } else {
378 make::tokens::single_space()
379 };
380
381 if is_multiline {
382 normalize_ws_between_braces(self.syntax());
383 }
384
385 let position = match self.fields().last() {
386 Some(last_field) => {
387 let comma = match last_field
388 .syntax()
389 .siblings_with_tokens(Direction::Next)
390 .filter_map(|it| it.into_token())
391 .find(|it| it.kind() == T![,])
392 {
393 Some(it) => it,
394 None => {
395 let comma = ast::make::token(T![,]);
396 ted::insert(Position::after(last_field.syntax()), &comma);
397 comma
398 }
399 };
400 Position::after(comma)
401 }
402 None => match self.l_curly_token() {
403 Some(it) => Position::after(it),
404 None => Position::last_child_of(self.syntax()),
405 },
406 };
407
408 ted::insert_all(position, vec![whitespace.into(), field.syntax().clone().into()]);
409 if is_multiline {
410 ted::insert(Position::after(field.syntax()), ast::make::token(T![,]));
411 }
412 }
413}
414
415fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> {
416 let l = node
417 .children_with_tokens()
418 .filter_map(|it| it.into_token())
419 .find(|it| it.kind() == T!['{'])?;
420 let r = node
421 .children_with_tokens()
422 .filter_map(|it| it.into_token())
423 .find(|it| it.kind() == T!['}'])?;
424
425 let indent = IndentLevel::from_node(node);
426
427 match l.next_sibling_or_token() {
428 Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => {
429 if ws.next_sibling_or_token()?.into_token()? == r {
430 ted::replace(ws, make::tokens::whitespace(&format!("\n{}", indent)));
431 }
432 }
433 Some(ws) if ws.kind() == T!['}'] => {
434 ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{}", indent)));
435 }
436 _ => (),
437 }
438 Some(())
439}
440
280#[cfg(test)] 441#[cfg(test)]
281mod tests { 442mod tests {
282 use std::fmt; 443 use std::fmt;
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index f8b508a90..d13926ded 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -3,25 +3,55 @@
3//! 3//!
4//! Note that all functions here intended to be stupid constructors, which just 4//! Note that all functions here intended to be stupid constructors, which just
5//! assemble a finish node from immediate children. If you want to do something 5//! assemble a finish node from immediate children. If you want to do something
6//! smarter than that, it probably doesn't belong in this module. 6//! smarter than that, it belongs to the `ext` submodule.
7//! 7//!
8//! Keep in mind that `from_text` functions should be kept private. The public 8//! Keep in mind that `from_text` functions should be kept private. The public
9//! API should require to assemble every node piecewise. The trick of 9//! API should require to assemble every node piecewise. The trick of
10//! `parse(format!())` we use internally is an implementation detail -- long 10//! `parse(format!())` we use internally is an implementation detail -- long
11//! term, it will be replaced with direct tree manipulation. 11//! term, it will be replaced with direct tree manipulation.
12use itertools::Itertools; 12use itertools::Itertools;
13use stdx::format_to; 13use stdx::{format_to, never};
14 14
15use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, SyntaxToken}; 15use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxToken};
16
17/// While the parent module defines basic atomic "constructors", the `ext`
18/// module defines shortcuts for common things.
19///
20/// It's named `ext` rather than `shortcuts` just to keep it short.
21pub mod ext {
22 use super::*;
23
24 pub fn ident_path(ident: &str) -> ast::Path {
25 path_unqualified(path_segment(name_ref(ident)))
26 }
27
28 pub fn expr_unreachable() -> ast::Expr {
29 expr_from_text("unreachable!()")
30 }
31 pub fn expr_todo() -> ast::Expr {
32 expr_from_text("todo!()")
33 }
34 pub fn empty_block_expr() -> ast::BlockExpr {
35 block_expr(None, None)
36 }
37
38 pub fn ty_bool() -> ast::Type {
39 ty_path(ident_path("bool"))
40 }
41 pub fn ty_option(t: ast::Type) -> ast::Type {
42 ty_from_text(&format!("Option<{}>", t))
43 }
44 pub fn ty_result(t: ast::Type, e: ast::Type) -> ast::Type {
45 ty_from_text(&format!("Result<{}, {}>", t, e))
46 }
47}
16 48
17pub fn name(text: &str) -> ast::Name { 49pub fn name(text: &str) -> ast::Name {
18 ast_from_text(&format!("mod {}{};", raw_ident_esc(text), text)) 50 ast_from_text(&format!("mod {}{};", raw_ident_esc(text), text))
19} 51}
20
21pub fn name_ref(text: &str) -> ast::NameRef { 52pub fn name_ref(text: &str) -> ast::NameRef {
22 ast_from_text(&format!("fn f() {{ {}{}; }}", raw_ident_esc(text), text)) 53 ast_from_text(&format!("fn f() {{ {}{}; }}", raw_ident_esc(text), text))
23} 54}
24
25fn raw_ident_esc(ident: &str) -> &'static str { 55fn raw_ident_esc(ident: &str) -> &'static str {
26 let is_keyword = parser::SyntaxKind::from_keyword(ident).is_some(); 56 let is_keyword = parser::SyntaxKind::from_keyword(ident).is_some();
27 if is_keyword && !matches!(ident, "self" | "crate" | "super" | "Self") { 57 if is_keyword && !matches!(ident, "self" | "crate" | "super" | "Self") {
@@ -31,13 +61,23 @@ fn raw_ident_esc(ident: &str) -> &'static str {
31 } 61 }
32} 62}
33 63
64pub fn lifetime(text: &str) -> ast::Lifetime {
65 let mut text = text;
66 let tmp;
67 if never!(!text.starts_with('\'')) {
68 tmp = format!("'{}", text);
69 text = &tmp;
70 }
71 ast_from_text(&format!("fn f<{}>() {{ }}", text))
72}
73
34// FIXME: replace stringly-typed constructor with a family of typed ctors, a-la 74// FIXME: replace stringly-typed constructor with a family of typed ctors, a-la
35// `expr_xxx`. 75// `expr_xxx`.
36pub fn ty(text: &str) -> ast::Type { 76pub fn ty(text: &str) -> ast::Type {
37 ast_from_text(&format!("fn f() -> {} {{}}", text)) 77 ty_from_text(text)
38} 78}
39pub fn ty_unit() -> ast::Type { 79pub fn ty_unit() -> ast::Type {
40 ty("()") 80 ty_from_text("()")
41} 81}
42pub fn ty_tuple(types: impl IntoIterator<Item = ast::Type>) -> ast::Type { 82pub fn ty_tuple(types: impl IntoIterator<Item = ast::Type>) -> ast::Type {
43 let mut count: usize = 0; 83 let mut count: usize = 0;
@@ -46,19 +86,20 @@ pub fn ty_tuple(types: impl IntoIterator<Item = ast::Type>) -> ast::Type {
46 contents.push(','); 86 contents.push(',');
47 } 87 }
48 88
49 ty(&format!("({})", contents)) 89 ty_from_text(&format!("({})", contents))
50}
51// FIXME: handle path to type
52pub fn ty_generic(name: ast::NameRef, types: impl IntoIterator<Item = ast::Type>) -> ast::Type {
53 let contents = types.into_iter().join(", ");
54 ty(&format!("{}<{}>", name, contents))
55} 90}
56pub fn ty_ref(target: ast::Type, exclusive: bool) -> ast::Type { 91pub fn ty_ref(target: ast::Type, exclusive: bool) -> ast::Type {
57 ty(&if exclusive { format!("&mut {}", target) } else { format!("&{}", target) }) 92 ty_from_text(&if exclusive { format!("&mut {}", target) } else { format!("&{}", target) })
93}
94pub fn ty_path(path: ast::Path) -> ast::Type {
95 ty_from_text(&path.to_string())
96}
97fn ty_from_text(text: &str) -> ast::Type {
98 ast_from_text(&format!("type _T = {};", text))
58} 99}
59 100
60pub fn assoc_item_list() -> ast::AssocItemList { 101pub fn assoc_item_list() -> ast::AssocItemList {
61 ast_from_text("impl C for D {};") 102 ast_from_text("impl C for D {}")
62} 103}
63 104
64pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl { 105pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl {
@@ -88,7 +129,7 @@ pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
88pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { 129pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
89 ast_from_text(&format!("{}::{}", qual, segment)) 130 ast_from_text(&format!("{}::{}", qual, segment))
90} 131}
91 132// FIXME: path concatenation operation doesn't make sense as AST op.
92pub fn path_concat(first: ast::Path, second: ast::Path) -> ast::Path { 133pub fn path_concat(first: ast::Path, second: ast::Path) -> ast::Path {
93 ast_from_text(&format!("{}::{}", first, second)) 134 ast_from_text(&format!("{}::{}", first, second))
94} 135}
@@ -104,15 +145,14 @@ pub fn path_from_segments(
104 format!("use {};", segments) 145 format!("use {};", segments)
105 }) 146 })
106} 147}
107 148// FIXME: should not be pub
108pub fn path_from_text(text: &str) -> ast::Path { 149pub fn path_from_text(text: &str) -> ast::Path {
109 ast_from_text(&format!("fn main() {{ let test = {}; }}", text)) 150 ast_from_text(&format!("fn main() {{ let test = {}; }}", text))
110} 151}
111 152
112pub fn glob_use_tree() -> ast::UseTree { 153pub fn use_tree_glob() -> ast::UseTree {
113 ast_from_text("use *;") 154 ast_from_text("use *;")
114} 155}
115
116pub fn use_tree( 156pub fn use_tree(
117 path: ast::Path, 157 path: ast::Path,
118 use_tree_list: Option<ast::UseTreeList>, 158 use_tree_list: Option<ast::UseTreeList>,
@@ -207,15 +247,6 @@ pub fn expr_literal(text: &str) -> ast::Literal {
207pub fn expr_empty_block() -> ast::Expr { 247pub fn expr_empty_block() -> ast::Expr {
208 expr_from_text("{}") 248 expr_from_text("{}")
209} 249}
210pub fn expr_unimplemented() -> ast::Expr {
211 expr_from_text("unimplemented!()")
212}
213pub fn expr_unreachable() -> ast::Expr {
214 expr_from_text("unreachable!()")
215}
216pub fn expr_todo() -> ast::Expr {
217 expr_from_text("todo!()")
218}
219pub fn expr_path(path: ast::Path) -> ast::Expr { 250pub fn expr_path(path: ast::Path) -> ast::Expr {
220 expr_from_text(&path.to_string()) 251 expr_from_text(&path.to_string())
221} 252}
@@ -275,6 +306,9 @@ pub fn expr_tuple(elements: impl IntoIterator<Item = ast::Expr>) -> ast::Expr {
275 let expr = elements.into_iter().format(", "); 306 let expr = elements.into_iter().format(", ");
276 expr_from_text(&format!("({})", expr)) 307 expr_from_text(&format!("({})", expr))
277} 308}
309pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr {
310 expr_from_text(&format!("{} = {}", lhs, rhs))
311}
278fn expr_from_text(text: &str) -> ast::Expr { 312fn expr_from_text(text: &str) -> ast::Expr {
279 ast_from_text(&format!("const C: () = {};", text)) 313 ast_from_text(&format!("const C: () = {};", text))
280} 314}
@@ -441,17 +475,6 @@ pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt {
441 ast_from_text(&format!("fn f() {{ {}{} (); }}", expr, semi)) 475 ast_from_text(&format!("fn f() {{ {}{} (); }}", expr, semi))
442} 476}
443 477
444pub fn token(kind: SyntaxKind) -> SyntaxToken {
445 tokens::SOURCE_FILE
446 .tree()
447 .syntax()
448 .clone_for_update()
449 .descendants_with_tokens()
450 .filter_map(|it| it.into_token())
451 .find(|it| it.kind() == kind)
452 .unwrap_or_else(|| panic!("unhandled token: {:?}", kind))
453}
454
455pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param { 478pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param {
456 ast_from_text(&format!("fn f({}: {}) {{ }}", pat, ty)) 479 ast_from_text(&format!("fn f({}: {}) {{ }}", pat, ty))
457} 480}
@@ -473,7 +496,7 @@ pub fn param_list(
473 ast_from_text(&list) 496 ast_from_text(&list)
474} 497}
475 498
476pub fn generic_param(name: String, ty: Option<ast::TypeBoundList>) -> ast::GenericParam { 499pub fn type_param(name: ast::Name, ty: Option<ast::TypeBoundList>) -> ast::TypeParam {
477 let bound = match ty { 500 let bound = match ty {
478 Some(it) => format!(": {}", it), 501 Some(it) => format!(": {}", it),
479 None => String::new(), 502 None => String::new(),
@@ -481,6 +504,10 @@ pub fn generic_param(name: String, ty: Option<ast::TypeBoundList>) -> ast::Gener
481 ast_from_text(&format!("fn f<{}{}>() {{ }}", name, bound)) 504 ast_from_text(&format!("fn f<{}{}>() {{ }}", name, bound))
482} 505}
483 506
507pub fn lifetime_param(lifetime: ast::Lifetime) -> ast::LifetimeParam {
508 ast_from_text(&format!("fn f<{}>() {{ }}", lifetime))
509}
510
484pub fn generic_param_list( 511pub fn generic_param_list(
485 pats: impl IntoIterator<Item = ast::GenericParam>, 512 pats: impl IntoIterator<Item = ast::GenericParam>,
486) -> ast::GenericParamList { 513) -> ast::GenericParamList {
@@ -574,15 +601,20 @@ fn ast_from_text<N: AstNode>(text: &str) -> N {
574 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)
575 } 602 }
576 }; 603 };
577 let node = node.syntax().clone(); 604 let node = node.clone_subtree();
578 let node = unroot(node);
579 let node = N::cast(node).unwrap();
580 assert_eq!(node.syntax().text_range().start(), 0.into()); 605 assert_eq!(node.syntax().text_range().start(), 0.into());
581 node 606 node
582} 607}
583 608
584fn unroot(n: SyntaxNode) -> SyntaxNode { 609pub fn token(kind: SyntaxKind) -> SyntaxToken {
585 SyntaxNode::new_root(n.green().into()) 610 tokens::SOURCE_FILE
611 .tree()
612 .syntax()
613 .clone_for_update()
614 .descendants_with_tokens()
615 .filter_map(|it| it.into_token())
616 .find(|it| it.kind() == kind)
617 .unwrap_or_else(|| panic!("unhandled token: {:?}", kind))
586} 618}
587 619
588pub mod tokens { 620pub mod tokens {
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 29d25a58a..4b1e1ccee 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -143,6 +143,30 @@ impl QuoteOffsets {
143 } 143 }
144} 144}
145 145
146pub trait IsString: AstToken {
147 fn quote_offsets(&self) -> Option<QuoteOffsets> {
148 let text = self.text();
149 let offsets = QuoteOffsets::new(text)?;
150 let o = self.syntax().text_range().start();
151 let offsets = QuoteOffsets {
152 quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o),
153 contents: offsets.contents + o,
154 };
155 Some(offsets)
156 }
157 fn text_range_between_quotes(&self) -> Option<TextRange> {
158 self.quote_offsets().map(|it| it.contents)
159 }
160 fn open_quote_text_range(&self) -> Option<TextRange> {
161 self.quote_offsets().map(|it| it.quotes.0)
162 }
163 fn close_quote_text_range(&self) -> Option<TextRange> {
164 self.quote_offsets().map(|it| it.quotes.1)
165 }
166}
167
168impl IsString for ast::String {}
169
146impl ast::String { 170impl ast::String {
147 pub fn is_raw(&self) -> bool { 171 pub fn is_raw(&self) -> bool {
148 self.text().starts_with('r') 172 self.text().starts_with('r')
@@ -187,32 +211,49 @@ impl ast::String {
187 (false, false) => Some(Cow::Owned(buf)), 211 (false, false) => Some(Cow::Owned(buf)),
188 } 212 }
189 } 213 }
190
191 pub fn quote_offsets(&self) -> Option<QuoteOffsets> {
192 let text = self.text();
193 let offsets = QuoteOffsets::new(text)?;
194 let o = self.syntax().text_range().start();
195 let offsets = QuoteOffsets {
196 quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o),
197 contents: offsets.contents + o,
198 };
199 Some(offsets)
200 }
201 pub fn text_range_between_quotes(&self) -> Option<TextRange> {
202 self.quote_offsets().map(|it| it.contents)
203 }
204 pub fn open_quote_text_range(&self) -> Option<TextRange> {
205 self.quote_offsets().map(|it| it.quotes.0)
206 }
207 pub fn close_quote_text_range(&self) -> Option<TextRange> {
208 self.quote_offsets().map(|it| it.quotes.1)
209 }
210} 214}
211 215
216impl IsString for ast::ByteString {}
217
212impl ast::ByteString { 218impl ast::ByteString {
213 pub fn is_raw(&self) -> bool { 219 pub fn is_raw(&self) -> bool {
214 self.text().starts_with("br") 220 self.text().starts_with("br")
215 } 221 }
222
223 pub fn value(&self) -> Option<Cow<'_, [u8]>> {
224 if self.is_raw() {
225 let text = self.text();
226 let text =
227 &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
228 return Some(Cow::Borrowed(text.as_bytes()));
229 }
230
231 let text = self.text();
232 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
233
234 let mut buf: Vec<u8> = Vec::new();
235 let mut text_iter = text.chars();
236 let mut has_error = false;
237 unescape_literal(text, Mode::ByteStr, &mut |char_range, unescaped_char| match (
238 unescaped_char,
239 buf.capacity() == 0,
240 ) {
241 (Ok(c), false) => buf.push(c as u8),
242 (Ok(c), true) if char_range.len() == 1 && Some(c) == text_iter.next() => (),
243 (Ok(c), true) => {
244 buf.reserve_exact(text.len());
245 buf.extend_from_slice(&text[..char_range.start].as_bytes());
246 buf.push(c as u8);
247 }
248 (Err(_), _) => has_error = true,
249 });
250
251 match (has_error, buf.capacity() == 0) {
252 (true, _) => None,
253 (false, true) => Some(Cow::Borrowed(text.as_bytes())),
254 (false, false) => Some(Cow::Owned(buf)),
255 }
256 }
216} 257}
217 258
218#[derive(Debug)] 259#[derive(Debug)]