1use std::{error::Error, path::Path};
2
3use ra_ap_syntax::{
4 AstNode, Edition, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
5 ast::{
6 self, HasLoopBody, HasName, HasVisibility,
7 edit::{AstNodeEdit, IndentLevel},
8 make,
9 },
10 hacks,
11 syntax_editor::{Position, SyntaxEditor},
12};
13
14pub trait Host: AstNode + HasName {
15 const KIND: &'static str;
16}
17
18impl Host for ast::Fn {
19 const KIND: &'static str = "function";
20}
21
22impl Host for ast::Struct {
23 const KIND: &'static str = "struct";
24}
25
26impl Host for ast::Enum {
27 const KIND: &'static str = "enum";
28}
29
30impl Host for ast::RecordField {
31 const KIND: &'static str = "field";
32}
33
34impl Host for ast::Variant {
35 const KIND: &'static str = "variant";
36}
37
38fn open(source: &str) -> Result<(SyntaxEditor, SyntaxNode), Box<dyn Error>> {
39 let file = parse_file(source)?;
40 Ok(SyntaxEditor::new(file.syntax().clone()))
41}
42
43fn commit(source: &mut String, editor: SyntaxEditor) -> Result<(), Box<dyn Error>> {
44 let text = editor.finish().new_root().to_string();
45 parse_file(&text)?;
46 *source = text;
47 Ok(())
48}
49
50fn named<N: Host>(root: &SyntaxNode, name: &str) -> Result<N, Box<dyn Error>> {
51 let (scope, name) = match name.split_once("::") {
52 Some((parent, child)) => {
53 let adt = root
54 .descendants()
55 .filter_map(ast::Adt::cast)
56 .find(|item| item.name().is_some_and(|it| it.text() == parent))
57 .ok_or_else(|| format!("could not find item `{parent}`"))?;
58 (adt.syntax().clone(), child)
59 }
60 None => (root.clone(), name),
61 };
62 scope
63 .descendants()
64 .filter_map(N::cast)
65 .find(|item| item.name().is_some_and(|it| it.text() == name))
66 .ok_or_else(|| format!("could not find {} `{name}`", N::KIND).into())
67}
68
69pub fn one<T>(
70 mut candidates: impl Iterator<Item = T>,
71 description: &str,
72) -> Result<T, Box<dyn Error>> {
73 let first = candidates
74 .next()
75 .ok_or_else(|| format!("no {description}"))?;
76 if candidates.next().is_some() {
77 return Err(format!("more than one {description}").into());
78 }
79 Ok(first)
80}
81
82pub fn calls(scope: &impl AstNode, name: &str) -> impl Iterator<Item = ast::MethodCallExpr> {
83 scope
84 .syntax()
85 .descendants()
86 .filter_map(ast::MethodCallExpr::cast)
87 .filter(|call| call.name_ref().is_some_and(|it| it.text() == name))
88 .collect::<Vec<_>>()
89 .into_iter()
90}
91
92pub fn arms(scope: &impl AstNode, variant: &str) -> impl Iterator<Item = ast::MatchArm> {
93 scope
94 .syntax()
95 .descendants()
96 .filter_map(ast::MatchArm::cast)
97 .filter(|arm| {
98 arm.pat().is_some_and(|pat| {
99 pat.syntax()
100 .descendants()
101 .filter_map(ast::Path::cast)
102 .any(|path| path.syntax().text() == variant)
103 })
104 })
105 .collect::<Vec<_>>()
106 .into_iter()
107}
108
109pub fn for_loops(scope: &impl AstNode) -> impl Iterator<Item = ast::ForExpr> {
110 scope
111 .syntax()
112 .descendants()
113 .filter_map(ast::ForExpr::cast)
114 .collect::<Vec<_>>()
115 .into_iter()
116}
117
118pub fn ifs_referencing(scope: &impl AstNode, field: &str) -> impl Iterator<Item = ast::IfExpr> {
119 ifs_where(scope, |condition| {
120 condition
121 .syntax()
122 .descendants()
123 .filter_map(ast::FieldExpr::cast)
124 .any(|expr| expr.name_ref().is_some_and(|it| it.text() == field))
125 })
126 .into_iter()
127}
128
129pub fn ifs_calling(scope: &impl AstNode, method: &str) -> impl Iterator<Item = ast::IfExpr> {
130 ifs_where(scope, |condition| {
131 condition
132 .syntax()
133 .descendants()
134 .filter_map(ast::MethodCallExpr::cast)
135 .any(|call| call.name_ref().is_some_and(|it| it.text() == method))
136 })
137 .into_iter()
138}
139
140fn ifs_where(scope: &impl AstNode, matches: impl Fn(&ast::Expr) -> bool) -> Vec<ast::IfExpr> {
141 scope
142 .syntax()
143 .descendants()
144 .filter_map(ast::IfExpr::cast)
145 .filter(|if_expr| {
146 if_expr
147 .condition()
148 .is_some_and(|condition| matches(&condition))
149 })
150 .collect()
151}
152
153pub fn rename<N: Host>(
154 source: &mut String,
155 name: &str,
156 replacement: &str,
157) -> Result<(), Box<dyn Error>> {
158 let (editor, root) = open(source)?;
159 let item: N = named(&root, name)?;
160 let name = item
161 .name()
162 .ok_or_else(|| format!("{} has no name", N::KIND))?;
163 editor.replace(name.syntax(), make::name(replacement).syntax().clone());
164 commit(source, editor)
165}
166
167pub trait VisibilityHost: Host + HasVisibility {
168 fn visibility_slot(&self) -> Result<SyntaxElement, Box<dyn Error>>;
169}
170
171impl VisibilityHost for ast::Fn {
172 fn visibility_slot(&self) -> Result<SyntaxElement, Box<dyn Error>> {
173 Ok(self.fn_token().ok_or("function has no fn token")?.into())
174 }
175}
176
177impl VisibilityHost for ast::Enum {
178 fn visibility_slot(&self) -> Result<SyntaxElement, Box<dyn Error>> {
179 self.syntax()
180 .first_child_or_token()
181 .ok_or_else(|| "enum has no content".into())
182 }
183}
184
185impl VisibilityHost for ast::RecordField {
186 fn visibility_slot(&self) -> Result<SyntaxElement, Box<dyn Error>> {
187 Ok(self
188 .name()
189 .ok_or("field has no name")?
190 .syntax()
191 .clone()
192 .into())
193 }
194}
195
196pub fn set_visibility<N: VisibilityHost>(
197 source: &mut String,
198 name: &str,
199 visibility: &str,
200) -> Result<(), Box<dyn Error>> {
201 let (editor, root) = open(source)?;
202 let item: N = named(&root, name)?;
203 if let Some(existing) = item.visibility() {
204 editor.replace(
205 existing.syntax(),
206 visibility_node(visibility)?.syntax().clone(),
207 );
208 } else {
209 editor.insert_all(
210 Position::before(item.visibility_slot()?),
211 vec![
212 visibility_node(visibility)?.syntax().clone().into(),
213 make::tokens::single_space().into(),
214 ],
215 );
216 }
217 commit(source, editor)
218}
219
220pub fn add_attr<N: Host>(
221 source: &mut String,
222 name: &str,
223 attribute: &str,
224) -> Result<(), Box<dyn Error>> {
225 let (editor, root) = open(source)?;
226 let item: N = named(&root, name)?;
227 let indent = indent_before(&item.syntax().clone().into());
228 let file = parse_file(&format!("{attribute}\nfn w() {{}}"))?;
229 let attr = one(
230 file.syntax().descendants().filter_map(ast::Attr::cast),
231 "attribute in wrapper",
232 )?;
233 editor.insert_all(
234 Position::first_child_of(item.syntax()),
235 vec![
236 attr.syntax().clone().into(),
237 make::tokens::whitespace(&format!("\n{indent}")).into(),
238 ],
239 );
240 commit(source, editor)
241}
242
243pub struct Field<'a> {
244 pub vis: Option<&'a str>,
245 pub name: &'a str,
246 pub ty: &'a str,
247}
248
249pub struct Variant<'a> {
250 pub name: &'a str,
251 pub tuple_fields: &'a [&'a str],
252}
253
254pub trait ListHost: Host {
255 type Item<'a>;
256 fn append_items(
257 &self,
258 editor: &SyntaxEditor,
259 items: &[Self::Item<'_>],
260 ) -> Result<(), Box<dyn Error>>;
261}
262
263impl ListHost for ast::Struct {
264 type Item<'a> = Field<'a>;
265
266 fn append_items(
267 &self,
268 editor: &SyntaxEditor,
269 items: &[Field<'_>],
270 ) -> Result<(), Box<dyn Error>> {
271 let Some(ast::FieldList::RecordFieldList(fields)) = self.field_list() else {
272 return Err("struct has no record field list".into());
273 };
274 let close = fields
275 .r_curly_token()
276 .ok_or("record struct has no closing brace")?;
277 let indent = IndentLevel::from_node(fields.syntax()) + 1;
278 let mut elements = Vec::new();
279 for item in items {
280 elements.extend([
281 make::tokens::whitespace(&indent.to_string()).into(),
282 make::record_field(
283 item.vis.map(visibility_node).transpose()?,
284 make::name(item.name),
285 make::ty(item.ty),
286 )
287 .syntax()
288 .clone()
289 .into(),
290 make::token(SyntaxKind::COMMA).into(),
291 make::tokens::single_newline().into(),
292 ]);
293 }
294 editor.insert_all(Position::before(close), elements);
295 Ok(())
296 }
297}
298
299impl ListHost for ast::Enum {
300 type Item<'a> = Variant<'a>;
301
302 fn append_items(
303 &self,
304 editor: &SyntaxEditor,
305 items: &[Variant<'_>],
306 ) -> Result<(), Box<dyn Error>> {
307 let list = self.variant_list().ok_or("enum has no variant list")?;
308 for item in items {
309 let fields = (!item.tuple_fields.is_empty()).then(|| {
310 make::tuple_field_list(
311 item.tuple_fields
312 .iter()
313 .map(|ty| make::tuple_field(None, make::ty(ty))),
314 )
315 .into()
316 });
317 list.add_variant(
318 editor,
319 &make::variant(None, make::name(item.name), fields, None),
320 );
321 }
322 Ok(())
323 }
324}
325
326impl ListHost for ast::Fn {
327 type Item<'a> = Param<'a>;
328
329 fn append_items(
330 &self,
331 editor: &SyntaxEditor,
332 items: &[Param<'_>],
333 ) -> Result<(), Box<dyn Error>> {
334 let list = self.param_list().ok_or("function has no parameter list")?;
335 let close = list
336 .r_paren_token()
337 .ok_or("parameter list has no closing paren")?;
338 let mut elements = Vec::new();
339 for item in items {
340 elements.extend([
341 make::token(SyntaxKind::COMMA).into(),
342 make::tokens::single_space().into(),
343 make::param(
344 make::ident_pat(false, false, make::name(item.name)).into(),
345 make::ty(item.ty),
346 )
347 .syntax()
348 .clone()
349 .into(),
350 ]);
351 }
352 editor.insert_all(Position::before(close), elements);
353 Ok(())
354 }
355}
356
357pub fn append<N: ListHost>(
358 source: &mut String,
359 name: &str,
360 items: &[N::Item<'_>],
361) -> Result<(), Box<dyn Error>> {
362 let (editor, root) = open(source)?;
363 let item: N = named(&root, name)?;
364 item.append_items(&editor, items)?;
365 commit(source, editor)
366}
367
368fn record_exprs_in(function: &ast::Fn, path_tail: &str) -> Vec<ast::RecordExpr> {
369 function
370 .syntax()
371 .descendants()
372 .filter_map(ast::RecordExpr::cast)
373 .filter(|record| {
374 record
375 .path()
376 .is_some_and(|path| path.syntax().text().to_string().ends_with(path_tail))
377 })
378 .collect()
379}
380
381pub struct FieldInit<'a> {
382 pub name: &'a str,
383 pub value: Option<&'a str>,
384}
385
386pub fn append_record_fields(
387 source: &mut String,
388 function: &str,
389 path_tail: &str,
390 fields: &[FieldInit<'_>],
391) -> Result<(), Box<dyn Error>> {
392 let (editor, root) = open(source)?;
393 let function: ast::Fn = named(&root, function)?;
394 for record in record_exprs_in(&function, path_tail) {
395 let Some(field_list) = record.record_expr_field_list() else {
396 continue;
397 };
398 let fields = fields
399 .iter()
400 .map(|field| {
401 Ok(make::record_expr_field(
402 make::name_ref(field.name),
403 field.value.map(expr_node).transpose()?,
404 ))
405 })
406 .collect::<Result<Vec<_>, Box<dyn Error>>>()?;
407 field_list.add_fields(&editor, fields);
408 return commit(source, editor);
409 }
410 Err(format!("function has no `{path_tail}` record expression").into())
411}
412
413pub fn set_record_field(
414 source: &mut String,
415 function: &str,
416 path_tail: &str,
417 field: &str,
418 value: &str,
419) -> Result<(), Box<dyn Error>> {
420 let (editor, root) = open(source)?;
421 let function: ast::Fn = named(&root, function)?;
422 for record in record_exprs_in(&function, path_tail) {
423 let Some(field_list) = record.record_expr_field_list() else {
424 continue;
425 };
426 for record_field in field_list.fields() {
427 let Some(name) = record_field.name_ref() else {
428 continue;
429 };
430 if name.text() != field {
431 continue;
432 }
433 let Some(expr) = record_field.expr() else {
434 return Err(format!("record field `{field}` has no expression").into());
435 };
436 editor.replace(expr.syntax(), expr_node(value)?.syntax().clone());
437 return commit(source, editor);
438 }
439 }
440 Err(format!("function has no `{path_tail}.{field}` field").into())
441}
442
443pub fn add_rest_pattern(
444 source: &mut String,
445 function: &str,
446 path_tail: &str,
447) -> Result<(), Box<dyn Error>> {
448 let (editor, root) = open(source)?;
449 let function: ast::Fn = named(&root, function)?;
450 for record in function
451 .syntax()
452 .descendants()
453 .filter_map(ast::RecordPat::cast)
454 {
455 let Some(path) = record.path() else {
456 continue;
457 };
458 if !path.syntax().text().to_string().ends_with(path_tail) {
459 continue;
460 }
461 let Some(fields) = record.record_pat_field_list() else {
462 continue;
463 };
464 if fields.rest_pat().is_some() {
465 return Ok(());
466 }
467 let last = fields
468 .fields()
469 .last()
470 .ok_or("record pattern has no fields")?;
471 editor.insert_all(
472 Position::after(last.syntax()),
473 vec![
474 make::token(SyntaxKind::COMMA).into(),
475 make::tokens::single_space().into(),
476 make::rest_pat().syntax().clone().into(),
477 ],
478 );
479 return commit(source, editor);
480 }
481 Err(format!("function has no `{path_tail}` record pattern").into())
482}
483
484pub fn rename_path_root(
485 source: &mut String,
486 function: &str,
487 root_name: &str,
488 replacement: &str,
489) -> Result<usize, Box<dyn Error>> {
490 let (editor, root) = open(source)?;
491 let function: ast::Fn = named(&root, function)?;
492 let mut count = 0;
493 for node in function.syntax().descendants() {
494 let Some(segment) = ast::PathSegment::cast(node) else {
495 continue;
496 };
497 let Some(name) = segment.name_ref() else {
498 continue;
499 };
500 if name.text() != root_name {
501 continue;
502 }
503 let Some(path) = segment.syntax().parent().and_then(ast::Path::cast) else {
504 continue;
505 };
506 if path.qualifier().is_some() {
507 continue;
508 }
509 editor.replace(name.syntax(), make::name_ref(replacement).syntax().clone());
510 count += 1;
511 }
512 if count > 0 {
513 commit(source, editor)?;
514 }
515 Ok(count)
516}
517
518pub fn add_use(
519 source: &mut String,
520 visibility: Option<&str>,
521 path: &str,
522) -> Result<(), Box<dyn Error>> {
523 let item = make::use_(
524 std::iter::empty(),
525 visibility.map(visibility_node).transpose()?,
526 make::use_tree(make::path_from_text(path), None, None, false),
527 );
528 let statement = item.to_string();
529 if source.contains(&statement) {
530 return Err(format!("source already contains `{statement}`").into());
531 }
532
533 let (editor, root) = open(source)?;
534 let anchor = root
535 .children()
536 .find(|node| ast::Use::can_cast(node.kind()))
537 .or_else(|| {
538 root.children()
539 .find(|node| ast::Item::can_cast(node.kind()))
540 })
541 .ok_or("source has no items")?;
542 editor.insert_all(
543 Position::before(&anchor),
544 vec![
545 item.syntax().clone().into(),
546 make::tokens::single_newline().into(),
547 ],
548 );
549 commit(source, editor)
550}
551
552pub fn retarget_use(source: &mut String, name: &str, path: &str) -> Result<(), Box<dyn Error>> {
553 let (editor, root) = open(source)?;
554 let matches = root
555 .descendants()
556 .filter_map(ast::UseTree::cast)
557 .filter_map(|tree| {
558 let path = tree.path()?;
559 let segment = path.segment()?;
560 let name_ref = segment.name_ref()?;
561 (name_ref.text() == name && tree.rename().is_none()).then_some(tree)
562 })
563 .collect::<Vec<_>>();
564 match matches.as_slice() {
565 [tree] => {
566 if tree
567 .syntax()
568 .ancestors()
569 .find_map(ast::UseTreeList::cast)
570 .is_some()
571 {
572 for element in use_tree_removal(tree) {
573 editor.delete(element);
574 }
575 commit(source, editor)?;
576 add_use(source, None, path)
577 } else {
578 let replacement = make::use_tree(make::path_from_text(path), None, None, false);
579 editor.replace(tree.syntax(), replacement.syntax().clone());
580 commit(source, editor)
581 }
582 }
583 [] => Err(format!("could not find use tree `{name}`").into()),
584 _ => Err(format!("found multiple use trees `{name}`").into()),
585 }
586}
587
588fn use_tree_removal(tree: &ast::UseTree) -> Vec<SyntaxElement> {
589 let mut elements = vec![SyntaxElement::from(tree.syntax().clone())];
590
591 let mut after = Vec::new();
592 let mut cursor = tree.syntax().next_sibling_or_token();
593 while let Some(element) = cursor {
594 cursor = element.next_sibling_or_token();
595 match element.kind() {
596 SyntaxKind::WHITESPACE => after.push(element),
597 SyntaxKind::COMMA => {
598 after.push(element);
599 while let Some(trailing) = cursor.clone() {
600 if trailing.kind() != SyntaxKind::WHITESPACE {
601 break;
602 }
603 cursor = trailing.next_sibling_or_token();
604 after.push(trailing);
605 }
606 elements.extend(after);
607 return elements;
608 }
609 _ => break,
610 }
611 }
612
613 let mut before = Vec::new();
614 let mut cursor = tree.syntax().prev_sibling_or_token();
615 while let Some(element) = cursor {
616 cursor = element.prev_sibling_or_token();
617 match element.kind() {
618 SyntaxKind::WHITESPACE => before.push(element),
619 SyntaxKind::COMMA => {
620 before.push(element);
621 elements.extend(before);
622 return elements;
623 }
624 _ => break,
625 }
626 }
627
628 elements
629}
630
631pub fn mount_module(source: &mut String, visibility: Option<&str>, name: &str, path: &Path) {
632 let visibility = visibility.map_or(String::new(), |visibility| format!("{visibility} "));
633 source.insert_str(
634 0,
635 &format!(
636 "#[path = {:?}]\n{visibility}mod {name};\n\n",
637 path.to_string_lossy().into_owned()
638 ),
639 );
640}
641
642pub struct Param<'a> {
643 pub name: &'a str,
644 pub ty: &'a str,
645}
646
647pub struct Method<'a> {
648 pub name: &'a str,
649 pub receiver: Option<&'a str>,
650 pub params: &'a [Param<'a>],
651 pub args: &'a [&'a str],
652 pub return_ty: Option<&'a str>,
653}
654
655pub struct Selection {
656 kind: SelectionKind,
657}
658
659enum SelectionKind {
660 Statement { statement: SyntaxNode },
661 LoopBody { list: ast::StmtList },
662 ThroughTail { from: SyntaxNode, tail: SyntaxNode },
663 ParamsTail,
664}
665
666pub fn stmt(node: &impl AstNode) -> Result<Selection, Box<dyn Error>> {
667 let statement = node
668 .syntax()
669 .ancestors()
670 .find_map(ast::Stmt::cast)
671 .ok_or("node is not part of a statement")?;
672 Ok(Selection {
673 kind: SelectionKind::Statement {
674 statement: statement.syntax().clone(),
675 },
676 })
677}
678
679pub fn for_body(loop_expr: &ast::ForExpr) -> Result<Selection, Box<dyn Error>> {
680 let list = loop_expr
681 .loop_body()
682 .and_then(|body| body.stmt_list())
683 .ok_or("for loop has no statement list")?;
684 Ok(Selection {
685 kind: SelectionKind::LoopBody { list },
686 })
687}
688
689pub fn through_tail(from: &impl AstNode, function: &ast::Fn) -> Result<Selection, Box<dyn Error>> {
690 let tail = function
691 .body()
692 .and_then(|body| body.stmt_list())
693 .and_then(|list| list.tail_expr())
694 .ok_or("function has no tail expression")?;
695 Ok(Selection {
696 kind: SelectionKind::ThroughTail {
697 from: from.syntax().clone(),
698 tail: tail.syntax().clone(),
699 },
700 })
701}
702
703pub fn params_tail() -> Selection {
704 Selection {
705 kind: SelectionKind::ParamsTail,
706 }
707}
708
709pub fn extract(
710 source: &mut String,
711 function: &str,
712 select: impl FnOnce(&ast::Fn) -> Result<Selection, Box<dyn Error>>,
713 method: Method<'_>,
714) -> Result<(), Box<dyn Error>> {
715 let (editor, root) = open(source)?;
716 let function_node: ast::Fn = named(&root, function)?;
717 let function_level = IndentLevel::from_node(function_node.syntax());
718 let selection = select(&function_node)?;
719 let call = make::expr_method_call(
720 make::ext::expr_self(),
721 make::name_ref(method.name),
722 make::arg_list(
723 method
724 .args
725 .iter()
726 .map(|arg| expr_node(arg))
727 .collect::<Result<Vec<_>, _>>()?,
728 ),
729 );
730 let mut body = match selection.kind {
731 SelectionKind::Statement { statement } => {
732 let region = vec![SyntaxElement::from(statement.clone())];
733 editor.replace(&statement, make::expr_stmt(call.into()).syntax().clone());
734 region
735 }
736 SelectionKind::LoopBody { list } => {
737 let open_brace = list
738 .l_curly_token()
739 .ok_or("for loop has no opening brace")?;
740 let close_brace = list
741 .r_curly_token()
742 .ok_or("for loop has no closing brace")?;
743 let first_statement = list.statements().next().ok_or("for loop has empty body")?;
744 let inner = elements_between(list.syntax(), &open_brace, &close_brace)?;
745 let call_indent = indent_before(&first_statement.syntax().clone().into());
746 let close_indent = indent_before(&close_brace.into());
747 replace_elements(
748 &editor,
749 inner.clone(),
750 vec![
751 make::tokens::whitespace(&format!("\n{call_indent}")).into(),
752 make::expr_stmt(call.into()).syntax().clone().into(),
753 make::tokens::whitespace(&format!("\n{close_indent}")).into(),
754 ],
755 )?;
756 inner
757 }
758 SelectionKind::ThroughTail { from, tail } => {
759 let list = tail
760 .parent()
761 .ok_or_else(|| format!("function `{function}` has no statement list"))?;
762 let start = child_of(&list, &from)?;
763 let range = element_range(&list, &start.into(), &tail.into())?;
764 replace_elements(&editor, range.clone(), vec![call.syntax().clone().into()])?;
765 range
766 }
767 SelectionKind::ParamsTail => {
768 let list = function_node
769 .body()
770 .and_then(|body| body.stmt_list())
771 .ok_or_else(|| format!("function `{function}` has no statement list"))?;
772 let mut anchor: Option<SyntaxNode> = None;
773 for param in method.params {
774 let mut definition = None;
775 for statement in list.statements() {
776 if let ast::Stmt::LetStmt(let_statement) = &statement
777 && pat_is_ident(let_statement.pat(), param.name)
778 {
779 definition = Some(statement.syntax().clone());
780 }
781 }
782 let definition = definition.ok_or_else(|| {
783 format!(
784 "function `{function}` does not define parameter `{}`",
785 param.name
786 )
787 })?;
788 if anchor.as_ref().is_none_or(|current| {
789 current.text_range().end() < definition.text_range().end()
790 }) {
791 anchor = Some(definition);
792 }
793 }
794 let anchor = anchor.ok_or_else(|| {
795 format!("extracted method for `{function}` declares no parameters")
796 })?;
797 let first_statement = list
798 .statements()
799 .find(|statement| {
800 statement.syntax().text_range().start() > anchor.text_range().end()
801 })
802 .ok_or_else(|| {
803 format!("function `{function}` has no statements after its parameters")
804 })?;
805 let close_brace = list
806 .r_curly_token()
807 .ok_or_else(|| format!("function `{function}` has no closing brace"))?;
808 let elements = list.syntax().children_with_tokens().collect::<Vec<_>>();
809 let start = elements
810 .iter()
811 .position(|element| element.as_node() == Some(&anchor))
812 .ok_or("anchor is not a direct child")?;
813 let end = elements
814 .iter()
815 .position(|element| element.as_token() == Some(&close_brace))
816 .ok_or("closing brace is not a direct child")?;
817 let range = elements[start + 1..end].to_vec();
818 let call_indent = indent_before(&first_statement.syntax().clone().into());
819 replace_elements(
820 &editor,
821 range.clone(),
822 vec![
823 make::tokens::whitespace(&format!("\n{call_indent}")).into(),
824 make::expr_stmt(call.into()).syntax().clone().into(),
825 make::tokens::whitespace(&format!("\n{function_level}")).into(),
826 ],
827 )?;
828 range
829 }
830 };
831 while body
832 .first()
833 .is_some_and(|element| element.kind() == SyntaxKind::WHITESPACE)
834 {
835 body.remove(0);
836 }
837 while body
838 .last()
839 .is_some_and(|element| element.kind() == SyntaxKind::WHITESPACE)
840 {
841 body.pop();
842 }
843 let first = body.first().ok_or("extracted selection is empty")?;
844 let body_level = IndentLevel::from_element(first);
845
846 let receiver = method
847 .receiver
848 .map(|receiver| match receiver {
849 "&self" => Ok(make::self_param()),
850 "&mut self" => Ok(make::mut_self_param()),
851 _ => Err(format!("unsupported receiver `{receiver}`")),
852 })
853 .transpose()?;
854 let params = make::param_list(
855 receiver,
856 method.params.iter().map(|param| {
857 make::param(
858 make::ident_pat(false, false, make::name(param.name)).into(),
859 make::ty(param.ty),
860 )
861 }),
862 );
863 let method_node = make::fn_(
864 std::iter::empty(),
865 None,
866 make::name(method.name),
867 None,
868 None,
869 params,
870 make::block_expr(std::iter::empty(), None),
871 method.return_ty.map(|ty| make::ret_type(make::ty(ty))),
872 false,
873 false,
874 false,
875 false,
876 );
877 let (body_editor, method_node) = SyntaxEditor::with_ast_node(&method_node);
878 let list = method_node
879 .body()
880 .and_then(|body| body.stmt_list())
881 .ok_or("extracted method has no statement list")?;
882 let open_brace = list
883 .l_curly_token()
884 .ok_or("extracted method has no opening brace")?;
885 let close_brace = list
886 .r_curly_token()
887 .ok_or("extracted method has no closing brace")?;
888 for element in list.syntax().children_with_tokens() {
889 if element.as_token() != Some(&open_brace) && element.as_token() != Some(&close_brace) {
890 body_editor.delete(element);
891 }
892 }
893 let mut content = Vec::with_capacity(body.len() + 2);
894 content.push(make::tokens::whitespace(&format!("\n{body_level}")).into());
895 content.extend(body);
896 content.push(make::tokens::whitespace(&format!("\n{}", IndentLevel(body_level.0 - 1))).into());
897 body_editor.insert_all(Position::after(open_brace), content);
898 let method_node = ast::Fn::cast(body_editor.finish().new_root().clone())
899 .ok_or("extracted method is not a function")?;
900 let target_level = function_level + 1;
901 let method_node = if target_level.0 > body_level.0 {
902 method_node.indent(IndentLevel(target_level.0 - body_level.0))
903 } else if body_level.0 > target_level.0 {
904 method_node.dedent(IndentLevel(body_level.0 - target_level.0))
905 } else {
906 method_node
907 };
908 editor.insert_all(
909 Position::after(function_node.syntax()),
910 vec![
911 make::tokens::whitespace(&format!("\n\n{function_level}")).into(),
912 method_node.syntax().clone().into(),
913 ],
914 );
915 commit(source, editor)
916}
917
918pub fn redirect_call(
919 source: &mut String,
920 function: &str,
921 from: &str,
922 to: &str,
923) -> Result<(), Box<dyn Error>> {
924 let (editor, root) = open(source)?;
925 let function_node: ast::Fn = named(&root, function)?;
926 let body_stmt_list = function_node
927 .body()
928 .and_then(|body| body.stmt_list())
929 .ok_or_else(|| format!("function `{function}` has no statement list"))?
930 .syntax()
931 .clone();
932 let call = one(
933 calls(&function_node, from).filter(|call| {
934 call.syntax()
935 .ancestors()
936 .filter_map(ast::StmtList::cast)
937 .next()
938 .is_some_and(|list| *list.syntax() == body_stmt_list)
939 }),
940 &format!("top-level `{from}` call in `{function}`"),
941 )?;
942 let name = call
943 .name_ref()
944 .ok_or_else(|| format!("`{from}` call has no method name"))?;
945 editor.replace(name.syntax(), make::name_ref(to).syntax().clone());
946 commit(source, editor)
947}
948
949fn visibility_node(visibility: &str) -> Result<ast::Visibility, Box<dyn Error>> {
950 match visibility {
951 "pub" => Ok(make::visibility_pub()),
952 "pub(crate)" => Ok(make::visibility_pub_crate()),
953 _ => Err(format!("unsupported visibility `{visibility}`").into()),
954 }
955}
956
957fn expr_node(expr: &str) -> Result<ast::Expr, Box<dyn Error>> {
958 hacks::parse_expr_from_str(expr, Edition::CURRENT)
959 .ok_or_else(|| format!("could not parse expression `{expr}`").into())
960}
961
962fn parse_file(source: &str) -> Result<SourceFile, Box<dyn Error>> {
963 let parsed = SourceFile::parse(source, Edition::CURRENT);
964 let errors = parsed.errors();
965 if !errors.is_empty() {
966 return Err(format!("could not parse Rust source: {errors:?}").into());
967 }
968 Ok(parsed.tree())
969}
970
971fn pat_is_ident(pattern: Option<ast::Pat>, name: &str) -> bool {
972 matches!(pattern, Some(ast::Pat::IdentPat(pattern)) if pattern.name().is_some_and(|it| it.text() == name))
973}
974
975fn child_of(list: &SyntaxNode, node: &SyntaxNode) -> Result<SyntaxNode, Box<dyn Error>> {
976 node.ancestors()
977 .find(|candidate| candidate.parent().as_ref() == Some(list))
978 .ok_or_else(|| "node is not part of the statement list".into())
979}
980
981fn element_range(
982 parent: &SyntaxNode,
983 first: &SyntaxElement,
984 last: &SyntaxElement,
985) -> Result<Vec<SyntaxElement>, Box<dyn Error>> {
986 let elements = parent.children_with_tokens().collect::<Vec<_>>();
987 let start = elements
988 .iter()
989 .position(|element| element == first)
990 .ok_or("range start is not a direct child")?;
991 let end = elements
992 .iter()
993 .position(|element| element == last)
994 .ok_or("range end is not a direct child")?;
995 if end < start {
996 return Err("range end precedes its start".into());
997 }
998 Ok(elements[start..=end].to_vec())
999}
1000
1001fn elements_between(
1002 parent: &SyntaxNode,
1003 open: &SyntaxToken,
1004 close: &SyntaxToken,
1005) -> Result<Vec<SyntaxElement>, Box<dyn Error>> {
1006 let elements = parent.children_with_tokens().collect::<Vec<_>>();
1007 let start = elements
1008 .iter()
1009 .position(|element| element.as_token() == Some(open))
1010 .ok_or("opening token is not a direct child")?;
1011 let end = elements
1012 .iter()
1013 .position(|element| element.as_token() == Some(close))
1014 .ok_or("closing token is not a direct child")?;
1015 if end <= start + 1 {
1016 return Err("delimited range is empty".into());
1017 }
1018 Ok(elements[start + 1..end].to_vec())
1019}
1020
1021fn replace_elements(
1022 editor: &SyntaxEditor,
1023 old: Vec<SyntaxElement>,
1024 new: Vec<SyntaxElement>,
1025) -> Result<(), Box<dyn Error>> {
1026 let first = old.first().ok_or("nothing to replace")?.clone();
1027 let last = old.last().ok_or("nothing to replace")?.clone();
1028 editor.replace_all(first..=last, new);
1029 Ok(())
1030}
1031
1032fn indent_before(element: &SyntaxElement) -> String {
1033 let whitespace = match element.prev_sibling_or_token() {
1034 Some(previous) if previous.kind() == SyntaxKind::WHITESPACE => previous,
1035 _ => return String::new(),
1036 };
1037 let text = whitespace.to_string();
1038 text.rsplit('\n').next().unwrap_or_default().to_owned()
1039}
1040
1041#[cfg(test)]
1042mod tests {
1043 use super::*;
1044
1045 #[test]
1046 fn injects_use_before_existing_imports() {
1047 let mut source = String::from("#![allow(clippy::all)]\n\nuse std::path::Path;\n");
1048
1049 add_use(&mut source, None, "crate::patched::run_flycheck").unwrap();
1050
1051 assert_eq!(
1052 source,
1053 "#![allow(clippy::all)]\n\nuse crate::patched::run_flycheck;\nuse std::path::Path;\n"
1054 );
1055 }
1056}