diff options
author | Lukas Wirth <[email protected]> | 2021-04-20 01:05:22 +0100 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2021-04-20 01:09:12 +0100 |
commit | fa20a5064be85349d2d05abcd66f5662d3aecb0c (patch) | |
tree | 578e0f5f9208d607969b084921e174a1cebe1f44 /crates/ide_db/src | |
parent | e8744ed9bb3e139e5d427db1f4f219f1fdeee13e (diff) |
Remove SyntaxRewriter usage in insert_use in favor of ted
Diffstat (limited to 'crates/ide_db/src')
-rw-r--r-- | crates/ide_db/src/helpers/insert_use.rs | 262 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use/tests.rs | 19 |
2 files changed, 103 insertions, 178 deletions
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs index be3a22725..498d76f72 100644 --- a/crates/ide_db/src/helpers/insert_use.rs +++ b/crates/ide_db/src/helpers/insert_use.rs | |||
@@ -4,13 +4,9 @@ use std::{cmp::Ordering, iter::successors}; | |||
4 | use hir::Semantics; | 4 | use hir::Semantics; |
5 | use itertools::{EitherOrBoth, Itertools}; | 5 | use itertools::{EitherOrBoth, Itertools}; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | algo::SyntaxRewriter, | 7 | algo, |
8 | ast::{ | 8 | ast::{self, edit::AstNodeEdit, make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner}, |
9 | self, | 9 | ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken, |
10 | edit::{AstNodeEdit, IndentLevel}, | ||
11 | make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner, | ||
12 | }, | ||
13 | AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken, | ||
14 | }; | 10 | }; |
15 | 11 | ||
16 | use crate::RootDatabase; | 12 | use crate::RootDatabase; |
@@ -56,127 +52,32 @@ impl ImportScope { | |||
56 | } | 52 | } |
57 | } | 53 | } |
58 | 54 | ||
59 | fn indent_level(&self) -> IndentLevel { | 55 | pub fn clone_for_update(&self) -> Self { |
60 | match self { | 56 | match self { |
61 | ImportScope::File(file) => file.indent_level(), | 57 | ImportScope::File(file) => ImportScope::File(file.clone_for_update()), |
62 | ImportScope::Module(item_list) => item_list.indent_level() + 1, | 58 | ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()), |
63 | } | 59 | } |
64 | } | 60 | } |
65 | |||
66 | fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) { | ||
67 | match self { | ||
68 | ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice), | ||
69 | // don't insert the imports before the item list's opening curly brace | ||
70 | ImportScope::Module(item_list) => item_list | ||
71 | .l_curly_token() | ||
72 | .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around)) | ||
73 | .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)), | ||
74 | } | ||
75 | } | ||
76 | |||
77 | fn insert_pos_after_last_inner_element(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) { | ||
78 | self.as_syntax_node() | ||
79 | .children_with_tokens() | ||
80 | .filter(|child| match child { | ||
81 | NodeOrToken::Node(node) => is_inner_attribute(node.clone()), | ||
82 | NodeOrToken::Token(token) => is_inner_comment(token.clone()), | ||
83 | }) | ||
84 | .last() | ||
85 | .map(|last_inner_element| { | ||
86 | (InsertPosition::After(last_inner_element), AddBlankLine::BeforeTwice) | ||
87 | }) | ||
88 | .unwrap_or_else(|| self.first_insert_pos()) | ||
89 | } | ||
90 | } | ||
91 | |||
92 | fn is_inner_attribute(node: SyntaxNode) -> bool { | ||
93 | ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) | ||
94 | } | ||
95 | |||
96 | fn is_inner_comment(token: SyntaxToken) -> bool { | ||
97 | ast::Comment::cast(token).and_then(|comment| comment.kind().doc) | ||
98 | == Some(ast::CommentPlacement::Inner) | ||
99 | } | 61 | } |
100 | 62 | ||
101 | /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. | 63 | /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. |
102 | pub fn insert_use<'a>( | 64 | pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { |
103 | scope: &ImportScope, | ||
104 | path: ast::Path, | ||
105 | cfg: InsertUseConfig, | ||
106 | ) -> SyntaxRewriter<'a> { | ||
107 | let _p = profile::span("insert_use"); | 65 | let _p = profile::span("insert_use"); |
108 | let mut rewriter = SyntaxRewriter::default(); | 66 | let use_item = |
109 | let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false)); | 67 | make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update(); |
110 | // merge into existing imports if possible | 68 | // merge into existing imports if possible |
111 | if let Some(mb) = cfg.merge { | 69 | if let Some(mb) = cfg.merge { |
112 | for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { | 70 | for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { |
113 | if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { | 71 | if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { |
114 | rewriter.replace(existing_use.syntax(), merged.syntax()); | 72 | ted::replace(existing_use.syntax(), merged.syntax()); |
115 | return rewriter; | 73 | return; |
116 | } | 74 | } |
117 | } | 75 | } |
118 | } | 76 | } |
119 | 77 | ||
120 | // either we weren't allowed to merge or there is no import that fits the merge conditions | 78 | // either we weren't allowed to merge or there is no import that fits the merge conditions |
121 | // so look for the place we have to insert to | 79 | // so look for the place we have to insert to |
122 | let (insert_position, add_blank) = find_insert_position(scope, path, cfg.group); | 80 | insert_use_(scope, path, cfg.group, use_item); |
123 | |||
124 | let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize { | ||
125 | Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into()) | ||
126 | } else { | ||
127 | None | ||
128 | }; | ||
129 | |||
130 | let to_insert: Vec<SyntaxElement> = { | ||
131 | let mut buf = Vec::new(); | ||
132 | |||
133 | match add_blank { | ||
134 | AddBlankLine::Before | AddBlankLine::Around => { | ||
135 | buf.push(make::tokens::single_newline().into()) | ||
136 | } | ||
137 | AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()), | ||
138 | _ => (), | ||
139 | } | ||
140 | |||
141 | if add_blank.has_before() { | ||
142 | if let Some(indent) = indent.clone() { | ||
143 | cov_mark::hit!(insert_use_indent_before); | ||
144 | buf.push(indent); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | buf.push(use_item.syntax().clone().into()); | ||
149 | |||
150 | match add_blank { | ||
151 | AddBlankLine::After | AddBlankLine::Around => { | ||
152 | buf.push(make::tokens::single_newline().into()) | ||
153 | } | ||
154 | AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()), | ||
155 | _ => (), | ||
156 | } | ||
157 | |||
158 | // only add indentation *after* our stuff if there's another node directly after it | ||
159 | if add_blank.has_after() && matches!(insert_position, InsertPosition::Before(_)) { | ||
160 | if let Some(indent) = indent { | ||
161 | cov_mark::hit!(insert_use_indent_after); | ||
162 | buf.push(indent); | ||
163 | } | ||
164 | } else if add_blank.has_after() && matches!(insert_position, InsertPosition::After(_)) { | ||
165 | cov_mark::hit!(insert_use_no_indent_after); | ||
166 | } | ||
167 | |||
168 | buf | ||
169 | }; | ||
170 | |||
171 | match insert_position { | ||
172 | InsertPosition::First => { | ||
173 | rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert) | ||
174 | } | ||
175 | InsertPosition::Last => return rewriter, // actually unreachable | ||
176 | InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert), | ||
177 | InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert), | ||
178 | } | ||
179 | rewriter | ||
180 | } | 81 | } |
181 | 82 | ||
182 | fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { | 83 | fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { |
@@ -235,7 +136,7 @@ pub fn try_merge_trees( | |||
235 | } else { | 136 | } else { |
236 | (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix)) | 137 | (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix)) |
237 | }; | 138 | }; |
238 | recursive_merge(&lhs, &rhs, merge).map(|it| it.clone_for_update()) | 139 | recursive_merge(&lhs, &rhs, merge) |
239 | } | 140 | } |
240 | 141 | ||
241 | /// Recursively "zips" together lhs and rhs. | 142 | /// Recursively "zips" together lhs and rhs. |
@@ -334,7 +235,12 @@ fn recursive_merge( | |||
334 | } | 235 | } |
335 | } | 236 | } |
336 | } | 237 | } |
337 | Some(lhs.with_use_tree_list(make::use_tree_list(use_trees))) | 238 | |
239 | Some(if let Some(old) = lhs.use_tree_list() { | ||
240 | lhs.replace_descendant(old, make::use_tree_list(use_trees)).clone_for_update() | ||
241 | } else { | ||
242 | lhs.clone() | ||
243 | }) | ||
338 | } | 244 | } |
339 | 245 | ||
340 | /// Traverses both paths until they differ, returning the common prefix of both. | 246 | /// Traverses both paths until they differ, returning the common prefix of both. |
@@ -520,32 +426,15 @@ impl ImportGroup { | |||
520 | } | 426 | } |
521 | } | 427 | } |
522 | 428 | ||
523 | #[derive(PartialEq, Eq)] | 429 | fn insert_use_( |
524 | enum AddBlankLine { | ||
525 | Before, | ||
526 | BeforeTwice, | ||
527 | Around, | ||
528 | After, | ||
529 | AfterTwice, | ||
530 | } | ||
531 | |||
532 | impl AddBlankLine { | ||
533 | fn has_before(&self) -> bool { | ||
534 | matches!(self, AddBlankLine::Before | AddBlankLine::BeforeTwice | AddBlankLine::Around) | ||
535 | } | ||
536 | fn has_after(&self) -> bool { | ||
537 | matches!(self, AddBlankLine::After | AddBlankLine::AfterTwice | AddBlankLine::Around) | ||
538 | } | ||
539 | } | ||
540 | |||
541 | fn find_insert_position( | ||
542 | scope: &ImportScope, | 430 | scope: &ImportScope, |
543 | insert_path: ast::Path, | 431 | insert_path: ast::Path, |
544 | group_imports: bool, | 432 | group_imports: bool, |
545 | ) -> (InsertPosition<SyntaxElement>, AddBlankLine) { | 433 | use_item: ast::Use, |
434 | ) { | ||
435 | let scope_syntax = scope.as_syntax_node(); | ||
546 | let group = ImportGroup::new(&insert_path); | 436 | let group = ImportGroup::new(&insert_path); |
547 | let path_node_iter = scope | 437 | let path_node_iter = scope_syntax |
548 | .as_syntax_node() | ||
549 | .children() | 438 | .children() |
550 | .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) | 439 | .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) |
551 | .flat_map(|(use_, node)| { | 440 | .flat_map(|(use_, node)| { |
@@ -557,9 +446,12 @@ fn find_insert_position( | |||
557 | 446 | ||
558 | if !group_imports { | 447 | if !group_imports { |
559 | if let Some((_, _, node)) = path_node_iter.last() { | 448 | if let Some((_, _, node)) = path_node_iter.last() { |
560 | return (InsertPosition::After(node.into()), AddBlankLine::Before); | 449 | ted::insert(ted::Position::after(node), use_item.syntax()); |
450 | } else { | ||
451 | ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line()); | ||
452 | ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()); | ||
561 | } | 453 | } |
562 | return (InsertPosition::First, AddBlankLine::AfterTwice); | 454 | return; |
563 | } | 455 | } |
564 | 456 | ||
565 | // Iterator that discards anything thats not in the required grouping | 457 | // Iterator that discards anything thats not in the required grouping |
@@ -572,43 +464,83 @@ fn find_insert_position( | |||
572 | // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place | 464 | // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place |
573 | let mut last = None; | 465 | let mut last = None; |
574 | // find the element that would come directly after our new import | 466 | // find the element that would come directly after our new import |
575 | let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find( | 467 | let post_insert: Option<(_, _, SyntaxNode)> = group_iter |
576 | |&(ref path, has_tl, _)| { | 468 | .inspect(|(.., node)| last = Some(node.clone())) |
469 | .find(|&(ref path, has_tl, _)| { | ||
577 | use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater | 470 | use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater |
578 | }, | 471 | }); |
579 | ); | ||
580 | 472 | ||
581 | match post_insert { | 473 | if let Some((.., node)) = post_insert { |
582 | // insert our import before that element | 474 | // insert our import before that element |
583 | Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After), | 475 | return ted::insert(ted::Position::before(node), use_item.syntax()); |
476 | } | ||
477 | if let Some(node) = last { | ||
584 | // there is no element after our new import, so append it to the end of the group | 478 | // there is no element after our new import, so append it to the end of the group |
585 | None => match last { | 479 | return ted::insert(ted::Position::after(node), use_item.syntax()); |
586 | Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before), | 480 | } |
587 | // the group we were looking for actually doesnt exist, so insert | 481 | |
482 | // the group we were looking for actually doesn't exist, so insert | ||
483 | |||
484 | let mut last = None; | ||
485 | // find the group that comes after where we want to insert | ||
486 | let post_group = path_node_iter | ||
487 | .inspect(|(.., node)| last = Some(node.clone())) | ||
488 | .find(|(p, ..)| ImportGroup::new(p) > group); | ||
489 | if let Some((.., node)) = post_group { | ||
490 | ted::insert(ted::Position::before(&node), use_item.syntax()); | ||
491 | if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) { | ||
492 | ted::insert(ted::Position::after(node), make::tokens::single_newline()); | ||
493 | } | ||
494 | return; | ||
495 | } | ||
496 | // there is no such group, so append after the last one | ||
497 | if let Some(node) = last { | ||
498 | ted::insert(ted::Position::after(&node), use_item.syntax()); | ||
499 | ted::insert(ted::Position::after(node), make::tokens::single_newline()); | ||
500 | return; | ||
501 | } | ||
502 | // there are no imports in this file at all | ||
503 | if let Some(last_inner_element) = scope_syntax | ||
504 | .children_with_tokens() | ||
505 | .filter(|child| match child { | ||
506 | NodeOrToken::Node(node) => is_inner_attribute(node.clone()), | ||
507 | NodeOrToken::Token(token) => is_inner_comment(token.clone()), | ||
508 | }) | ||
509 | .last() | ||
510 | { | ||
511 | ted::insert(ted::Position::after(&last_inner_element), use_item.syntax()); | ||
512 | ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline()); | ||
513 | return; | ||
514 | } | ||
515 | match scope { | ||
516 | ImportScope::File(_) => { | ||
517 | ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line()); | ||
518 | ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()) | ||
519 | } | ||
520 | // don't insert the imports before the item list's opening curly brace | ||
521 | ImportScope::Module(item_list) => match item_list.l_curly_token() { | ||
522 | Some(b) => { | ||
523 | ted::insert(ted::Position::after(&b), make::tokens::single_newline()); | ||
524 | ted::insert(ted::Position::after(&b), use_item.syntax()); | ||
525 | } | ||
588 | None => { | 526 | None => { |
589 | // similar concept here to the `last` from above | 527 | ted::insert( |
590 | let mut last = None; | 528 | ted::Position::first_child_of(scope_syntax), |
591 | // find the group that comes after where we want to insert | 529 | make::tokens::blank_line(), |
592 | let post_group = path_node_iter | 530 | ); |
593 | .inspect(|(.., node)| last = Some(node.clone())) | 531 | ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()); |
594 | .find(|(p, ..)| ImportGroup::new(p) > group); | ||
595 | match post_group { | ||
596 | Some((.., node)) => { | ||
597 | (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice) | ||
598 | } | ||
599 | // there is no such group, so append after the last one | ||
600 | None => match last { | ||
601 | Some(node) => { | ||
602 | (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice) | ||
603 | } | ||
604 | // there are no imports in this file at all | ||
605 | None => scope.insert_pos_after_last_inner_element(), | ||
606 | }, | ||
607 | } | ||
608 | } | 532 | } |
609 | }, | 533 | }, |
610 | } | 534 | } |
611 | } | 535 | } |
612 | 536 | ||
537 | fn is_inner_attribute(node: SyntaxNode) -> bool { | ||
538 | ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) | ||
539 | } | ||
540 | |||
541 | fn is_inner_comment(token: SyntaxToken) -> bool { | ||
542 | ast::Comment::cast(token).and_then(|comment| comment.kind().doc) | ||
543 | == Some(ast::CommentPlacement::Inner) | ||
544 | } | ||
613 | #[cfg(test)] | 545 | #[cfg(test)] |
614 | mod tests; | 546 | mod tests; |
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs index 3d151e629..a3464d606 100644 --- a/crates/ide_db/src/helpers/insert_use/tests.rs +++ b/crates/ide_db/src/helpers/insert_use/tests.rs | |||
@@ -51,17 +51,16 @@ use std::bar::G;", | |||
51 | 51 | ||
52 | #[test] | 52 | #[test] |
53 | fn insert_start_indent() { | 53 | fn insert_start_indent() { |
54 | cov_mark::check!(insert_use_indent_after); | ||
55 | check_none( | 54 | check_none( |
56 | "std::bar::AA", | 55 | "std::bar::AA", |
57 | r" | 56 | r" |
58 | use std::bar::B; | 57 | use std::bar::B; |
59 | use std::bar::D;", | 58 | use std::bar::C;", |
60 | r" | 59 | r" |
61 | use std::bar::AA; | 60 | use std::bar::AA; |
62 | use std::bar::B; | 61 | use std::bar::B; |
63 | use std::bar::D;", | 62 | use std::bar::C;", |
64 | ) | 63 | ); |
65 | } | 64 | } |
66 | 65 | ||
67 | #[test] | 66 | #[test] |
@@ -120,7 +119,6 @@ use std::bar::ZZ;", | |||
120 | 119 | ||
121 | #[test] | 120 | #[test] |
122 | fn insert_end_indent() { | 121 | fn insert_end_indent() { |
123 | cov_mark::check!(insert_use_indent_before); | ||
124 | check_none( | 122 | check_none( |
125 | "std::bar::ZZ", | 123 | "std::bar::ZZ", |
126 | r" | 124 | r" |
@@ -255,7 +253,6 @@ fn insert_empty_file() { | |||
255 | 253 | ||
256 | #[test] | 254 | #[test] |
257 | fn insert_empty_module() { | 255 | fn insert_empty_module() { |
258 | cov_mark::check!(insert_use_no_indent_after); | ||
259 | check( | 256 | check( |
260 | "foo::bar", | 257 | "foo::bar", |
261 | "mod x {}", | 258 | "mod x {}", |
@@ -615,7 +612,7 @@ fn check( | |||
615 | if module { | 612 | if module { |
616 | syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone(); | 613 | syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone(); |
617 | } | 614 | } |
618 | let file = super::ImportScope::from(syntax).unwrap(); | 615 | let file = super::ImportScope::from(syntax.clone_for_update()).unwrap(); |
619 | let path = ast::SourceFile::parse(&format!("use {};", path)) | 616 | let path = ast::SourceFile::parse(&format!("use {};", path)) |
620 | .tree() | 617 | .tree() |
621 | .syntax() | 618 | .syntax() |
@@ -623,12 +620,8 @@ fn check( | |||
623 | .find_map(ast::Path::cast) | 620 | .find_map(ast::Path::cast) |
624 | .unwrap(); | 621 | .unwrap(); |
625 | 622 | ||
626 | let rewriter = insert_use( | 623 | insert_use(&file, path, InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group }); |
627 | &file, | 624 | let result = file.as_syntax_node().to_string(); |
628 | path, | ||
629 | InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group }, | ||
630 | ); | ||
631 | let result = rewriter.rewrite(file.as_syntax_node()).to_string(); | ||
632 | assert_eq_text!(ra_fixture_after, &result); | 625 | assert_eq_text!(ra_fixture_after, &result); |
633 | } | 626 | } |
634 | 627 | ||