Skip to main content

analyzed_bridge/
edit.rs

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}