Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #18483 from tareknaser/syntax_factory_introduce_named_generic
Migrate `introduce_named_generic` Assist to Use `SyntaxFactory`
David Barsky 2024-12-06
parent 34665c7 · parent 8954a7f · commit 2fd0654
-rw-r--r--crates/ide-assists/src/handlers/introduce_named_generic.rs49
-rw-r--r--crates/syntax/src/ast/syntax_factory/constructors.rs28
-rw-r--r--crates/syntax/src/syntax_editor.rs1
-rw-r--r--crates/syntax/src/syntax_editor/edits.rs72
4 files changed, 123 insertions, 27 deletions
diff --git a/crates/ide-assists/src/handlers/introduce_named_generic.rs b/crates/ide-assists/src/handlers/introduce_named_generic.rs
index bf6ac1719f..8c276415bb 100644
--- a/crates/ide-assists/src/handlers/introduce_named_generic.rs
+++ b/crates/ide-assists/src/handlers/introduce_named_generic.rs
@@ -1,9 +1,6 @@
use ide_db::syntax_helpers::suggest_name;
use itertools::Itertools;
-use syntax::{
- ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasGenericParams, HasName},
- ted,
-};
+use syntax::ast::{self, syntax_factory::SyntaxFactory, AstNode, HasGenericParams, HasName};
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -25,42 +22,42 @@ pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_>
let type_bound_list = impl_trait_type.type_bound_list()?;
+ let make = SyntaxFactory::new();
let target = fn_.syntax().text_range();
acc.add(
AssistId("introduce_named_generic", AssistKind::RefactorRewrite),
"Replace impl trait with generic",
target,
- |edit| {
- let impl_trait_type = edit.make_mut(impl_trait_type);
- let fn_ = edit.make_mut(fn_);
- let fn_generic_param_list = fn_.get_or_create_generic_param_list();
-
- let existing_names = fn_generic_param_list
- .generic_params()
- .flat_map(|param| match param {
- ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()),
- p => Some(p.to_string()),
- })
- .collect_vec();
+ |builder| {
+ let mut editor = builder.make_editor(fn_.syntax());
+
+ let existing_names = match fn_.generic_param_list() {
+ Some(generic_param_list) => generic_param_list
+ .generic_params()
+ .flat_map(|param| match param {
+ ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()),
+ p => Some(p.to_string()),
+ })
+ .collect_vec(),
+ None => Vec::new(),
+ };
let type_param_name = suggest_name::NameGenerator::new_with_names(
existing_names.iter().map(|s| s.as_str()),
)
.for_impl_trait_as_generic(&impl_trait_type);
- let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list))
- .clone_for_update();
- let new_ty = make::ty(&type_param_name).clone_for_update();
+ let type_param = make.type_param(make.name(&type_param_name), Some(type_bound_list));
+ let new_ty = make.ty(&type_param_name);
- ted::replace(impl_trait_type.syntax(), new_ty.syntax());
- fn_generic_param_list.add_generic_param(type_param.into());
+ editor.replace(impl_trait_type.syntax(), new_ty.syntax());
+ editor.add_generic_param(&fn_, type_param.clone().into());
if let Some(cap) = ctx.config.snippet_cap {
- if let Some(generic_param) =
- fn_.generic_param_list().and_then(|it| it.generic_params().last())
- {
- edit.add_tabstop_before(cap, generic_param);
- }
+ editor.add_annotation(type_param.syntax(), builder.make_tabstop_before(cap));
}
+
+ editor.add_mappings(make.finish_with_mappings());
+ builder.add_file_edits(ctx.file_id(), editor);
},
)
}
diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs
index 9f88add0f7..35c467a1e8 100644
--- a/crates/syntax/src/ast/syntax_factory/constructors.rs
+++ b/crates/syntax/src/ast/syntax_factory/constructors.rs
@@ -2,7 +2,7 @@
use itertools::Itertools;
use crate::{
- ast::{self, make, HasName},
+ ast::{self, make, HasName, HasTypeBounds},
syntax_editor::SyntaxMappingBuilder,
AstNode,
};
@@ -14,6 +14,32 @@ impl SyntaxFactory {
make::name(name).clone_for_update()
}
+ pub fn ty(&self, text: &str) -> ast::Type {
+ make::ty(text).clone_for_update()
+ }
+
+ pub fn type_param(
+ &self,
+ name: ast::Name,
+ bounds: Option<ast::TypeBoundList>,
+ ) -> ast::TypeParam {
+ let ast = make::type_param(name.clone(), bounds.clone()).clone_for_update();
+
+ if let Some(mut mapping) = self.mappings() {
+ let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
+ builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone());
+ if let Some(input) = bounds {
+ builder.map_node(
+ input.syntax().clone(),
+ ast.type_bound_list().unwrap().syntax().clone(),
+ );
+ }
+ builder.finish(&mut mapping);
+ }
+
+ ast
+ }
+
pub fn ident_pat(&self, ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat {
let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update();
diff --git a/crates/syntax/src/syntax_editor.rs b/crates/syntax/src/syntax_editor.rs
index 714f5a9911..7e5d0f27e0 100644
--- a/crates/syntax/src/syntax_editor.rs
+++ b/crates/syntax/src/syntax_editor.rs
@@ -16,6 +16,7 @@ use rustc_hash::FxHashMap;
use crate::{SyntaxElement, SyntaxNode, SyntaxToken};
mod edit_algo;
+mod edits;
mod mapping;
pub use mapping::{SyntaxMapping, SyntaxMappingBuilder};
diff --git a/crates/syntax/src/syntax_editor/edits.rs b/crates/syntax/src/syntax_editor/edits.rs
new file mode 100644
index 0000000000..73196f5cb1
--- /dev/null
+++ b/crates/syntax/src/syntax_editor/edits.rs
@@ -0,0 +1,72 @@
+//! Structural editing for ast using `SyntaxEditor`
+
+use crate::{
+ ast::make, ast::AstNode, ast::Fn, ast::GenericParam, ast::HasGenericParams, ast::HasName,
+ syntax_editor::Position, syntax_editor::SyntaxEditor, SyntaxKind,
+};
+
+impl SyntaxEditor {
+ /// Adds a new generic param to the function using `SyntaxEditor`
+ pub fn add_generic_param(&mut self, function: &Fn, new_param: GenericParam) {
+ match function.generic_param_list() {
+ Some(generic_param_list) => match generic_param_list.generic_params().last() {
+ Some(last_param) => {
+ // There exists a generic param list and it's not empty
+ let position = generic_param_list.r_angle_token().map_or_else(
+ || Position::last_child_of(function.syntax()),
+ Position::before,
+ );
+
+ if last_param
+ .syntax()
+ .next_sibling_or_token()
+ .map_or(false, |it| it.kind() == SyntaxKind::COMMA)
+ {
+ self.insert(
+ Position::after(last_param.syntax()),
+ new_param.syntax().clone(),
+ );
+ self.insert(
+ Position::after(last_param.syntax()),
+ make::token(SyntaxKind::WHITESPACE),
+ );
+ self.insert(
+ Position::after(last_param.syntax()),
+ make::token(SyntaxKind::COMMA),
+ );
+ } else {
+ let elements = vec![
+ make::token(SyntaxKind::COMMA).into(),
+ make::token(SyntaxKind::WHITESPACE).into(),
+ new_param.syntax().clone().into(),
+ ];
+ self.insert_all(position, elements);
+ }
+ }
+ None => {
+ // There exists a generic param list but it's empty
+ let position = Position::after(generic_param_list.l_angle_token().unwrap());
+ self.insert(position, new_param.syntax());
+ }
+ },
+ None => {
+ // There was no generic param list
+ let position = if let Some(name) = function.name() {
+ Position::after(name.syntax)
+ } else if let Some(fn_token) = function.fn_token() {
+ Position::after(fn_token)
+ } else if let Some(param_list) = function.param_list() {
+ Position::before(param_list.syntax)
+ } else {
+ Position::last_child_of(function.syntax())
+ };
+ let elements = vec![
+ make::token(SyntaxKind::L_ANGLE).into(),
+ new_param.syntax().clone().into(),
+ make::token(SyntaxKind::R_ANGLE).into(),
+ ];
+ self.insert_all(position, elements);
+ }
+ }
+ }
+}