Merge pull request #20625 from A4-Tacks/multi-attr-to-cfg_attr

Add wrap multiple attr for wrap_unwrap_cfg_attr
This commit is contained in:
Chayim Refael Friedman
2026-03-20 11:14:03 +00:00
committed by GitHub
@@ -2,7 +2,7 @@
use itertools::Itertools;
use syntax::{
NodeOrToken, SyntaxToken, T, TextRange, algo,
ast::{self, AstNode, make, syntax_factory::SyntaxFactory},
ast::{self, AstNode, edit::AstNodeEdit, make, syntax_factory::SyntaxFactory},
};
use crate::{AssistContext, AssistId, Assists};
@@ -27,7 +27,7 @@
enum WrapUnwrapOption {
WrapDerive { derive: TextRange, attr: ast::Attr },
WrapAttr(ast::Attr),
WrapAttr(Vec<ast::Attr>),
}
/// Attempts to get the derive attribute from a derive attribute list
@@ -102,9 +102,9 @@ fn attempt_get_derive(attr: ast::Attr, ident: SyntaxToken) -> WrapUnwrapOption {
if ident.parent().and_then(ast::TokenTree::cast).is_none()
|| !attr.simple_name().map(|v| v.eq("derive")).unwrap_or_default()
{
WrapUnwrapOption::WrapAttr(attr)
WrapUnwrapOption::WrapAttr(vec![attr])
} else {
attempt_attr().unwrap_or(WrapUnwrapOption::WrapAttr(attr))
attempt_attr().unwrap_or_else(|| WrapUnwrapOption::WrapAttr(vec![attr]))
}
}
pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
@@ -118,13 +118,27 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -
Some(attempt_get_derive(attr, ident))
}
(Some(attr), _) => Some(WrapUnwrapOption::WrapAttr(attr)),
(Some(attr), _) => Some(WrapUnwrapOption::WrapAttr(vec![attr])),
_ => None,
}
} else {
let covering_element = ctx.covering_element();
match covering_element {
NodeOrToken::Node(node) => ast::Attr::cast(node).map(WrapUnwrapOption::WrapAttr),
NodeOrToken::Node(node) => {
if let Some(attr) = ast::Attr::cast(node.clone()) {
Some(WrapUnwrapOption::WrapAttr(vec![attr]))
} else {
let attrs = node
.children()
.filter(|it| it.text_range().intersect(ctx.selection_trimmed()).is_some())
.map(ast::Attr::cast)
.collect::<Option<Vec<_>>>()?;
if attrs.is_empty() {
return None;
}
Some(WrapUnwrapOption::WrapAttr(attrs))
}
}
NodeOrToken::Token(ident) if ident.kind() == syntax::T![ident] => {
let attr = ident.parent_ancestors().find_map(ast::Attr::cast)?;
Some(attempt_get_derive(attr, ident))
@@ -133,10 +147,12 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -
}
}?;
match option {
WrapUnwrapOption::WrapAttr(attr) if attr.simple_name().as_deref() == Some("cfg_attr") => {
unwrap_cfg_attr(acc, attr)
}
WrapUnwrapOption::WrapAttr(attr) => wrap_cfg_attr(acc, ctx, attr),
WrapUnwrapOption::WrapAttr(attrs) => match &attrs[..] {
[attr] if attr.simple_name().as_deref() == Some("cfg_attr") => {
unwrap_cfg_attr(acc, attrs.into_iter().next().unwrap())
}
_ => wrap_cfg_attrs(acc, ctx, attrs),
},
WrapUnwrapOption::WrapDerive { derive, attr } => wrap_derive(acc, ctx, attr, derive),
}
}
@@ -220,40 +236,51 @@ fn wrap_derive(
);
Some(())
}
fn wrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>, attr: ast::Attr) -> Option<()> {
let range = attr.syntax().text_range();
let path = attr.path()?;
fn wrap_cfg_attrs(acc: &mut Assists, ctx: &AssistContext<'_>, attrs: Vec<ast::Attr>) -> Option<()> {
let (first_attr, last_attr) = (attrs.first()?, attrs.last()?);
let range = first_attr.syntax().text_range().cover(last_attr.syntax().text_range());
let path_attrs =
attrs.iter().map(|attr| Some((attr.path()?, attr.clone()))).collect::<Option<Vec<_>>>()?;
let handle_source_change = |edit: &mut SourceChangeBuilder| {
let make = SyntaxFactory::with_mappings();
let mut editor = edit.make_editor(attr.syntax());
let mut raw_tokens =
vec![NodeOrToken::Token(make.token(T![,])), NodeOrToken::Token(make.whitespace(" "))];
path.syntax().descendants_with_tokens().for_each(|it| {
if let NodeOrToken::Token(token) = it {
raw_tokens.push(NodeOrToken::Token(token));
}
});
if let Some(meta) = attr.meta() {
if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) {
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
raw_tokens.push(NodeOrToken::Token(eq));
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
let mut editor = edit.make_editor(first_attr.syntax());
let mut raw_tokens = vec![];
for (path, attr) in path_attrs {
raw_tokens.extend([
NodeOrToken::Token(make.token(T![,])),
NodeOrToken::Token(make.whitespace(" ")),
]);
path.syntax().descendants_with_tokens().for_each(|it| {
if let NodeOrToken::Token(token) = it {
raw_tokens.push(NodeOrToken::Token(token));
}
});
if let Some(meta) = attr.meta() {
if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) {
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
raw_tokens.push(NodeOrToken::Token(eq));
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
expr.syntax().descendants_with_tokens().for_each(|it| {
if let NodeOrToken::Token(token) = it {
raw_tokens.push(NodeOrToken::Token(token));
}
});
} else if let Some(tt) = meta.token_tree() {
raw_tokens.extend(tt.token_trees_and_tokens());
expr.syntax().descendants_with_tokens().for_each(|it| {
if let NodeOrToken::Token(token) = it {
raw_tokens.push(NodeOrToken::Token(token));
}
});
} else if let Some(tt) = meta.token_tree() {
raw_tokens.extend(tt.token_trees_and_tokens());
}
}
}
let meta =
make.meta_token_tree(make.ident_path("cfg_attr"), make.token_tree(T!['('], raw_tokens));
let cfg_attr =
if attr.excl_token().is_some() { make.attr_inner(meta) } else { make.attr_outer(meta) };
let cfg_attr = if first_attr.excl_token().is_some() {
make.attr_inner(meta)
} else {
make.attr_outer(meta)
};
editor.replace(attr.syntax(), cfg_attr.syntax());
let syntax_range = first_attr.syntax().clone().into()..=last_attr.syntax().clone().into();
editor.replace_all(syntax_range, vec![cfg_attr.syntax().clone().into()]);
if let Some(snippet_cap) = ctx.config.snippet_cap
&& let Some(first_meta) =
@@ -332,7 +359,8 @@ fn unwrap_cfg_attr(acc: &mut Assists, attr: ast::Attr) -> Option<()> {
return None;
}
let handle_source_change = |f: &mut SourceChangeBuilder| {
let inner_attrs = inner_attrs.iter().map(|it| it.to_string()).join("\n");
let inner_attrs =
inner_attrs.iter().map(|it| it.to_string()).join(&format!("\n{}", attr.indent_level()));
f.replace(range, inner_attrs);
};
acc.add(
@@ -414,6 +442,42 @@ pub struct Test {
}
"#,
);
check_assist(
wrap_unwrap_cfg_attr,
r#"
pub struct Test {
#[other_attr]
$0#[foo]
#[bar]$0
#[other_attr]
test: u32,
}
"#,
r#"
pub struct Test {
#[other_attr]
#[cfg_attr($0, foo, bar)]
#[other_attr]
test: u32,
}
"#,
);
check_assist(
wrap_unwrap_cfg_attr,
r#"
pub struct Test {
#[cfg_attr(debug_assertions$0, foo, bar)]
test: u32,
}
"#,
r#"
pub struct Test {
#[foo]
#[bar]
test: u32,
}
"#,
);
}
#[test]
fn to_from_eq_attr() {