diff --git a/crates/ide-assists/src/handlers/generate_doc_cfg.rs b/crates/ide-assists/src/handlers/generate_doc_cfg.rs new file mode 100644 index 000000000000..297de8c7dc8a --- /dev/null +++ b/crates/ide-assists/src/handlers/generate_doc_cfg.rs @@ -0,0 +1,131 @@ +use ide_db::assists::GroupLabel; +use syntax::{ + NodeOrToken::{Node, Token}, + T, + ast::{self, AstNode, edit_in_place::Indent, make}, + syntax_editor::Position, +}; + +use crate::{AssistContext, AssistId, Assists}; + +// Assist: generate_doc_cfg +// +// Generate a doc-cfg attribute from cfg attribute. +// +// ``` +// #[$0cfg(unix)] +// pub struct Foo; +// ``` +// -> +// ``` +// #[cfg(unix)] +// #[cfg_attr(docsrs, doc(cfg(unix)))] +// pub struct Foo; +// ``` +pub(crate) fn generate_doc_cfg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let name = ctx.find_node_at_offset::()?; + let attr = ast::Attr::cast(name.syntax().parent()?.parent()?)?; + let indent = attr.indent_level(); + + if attr.simple_name()? != "cfg" { + return None; + } + + let target = attr.syntax().text_range(); + let group = GroupLabel("Generate doc-cfg".to_owned()); + + for on in ["docsrs", "doc"] { + let doc_cfg = generate_doc_cfg_attribute(&attr, on)?; + acc.add_group( + &group, + AssistId::generate("generate_doc_cfg"), + format!("Generate `{on}(docsrs, doc(cfg(...)))` from cfg"), + target, + |builder| { + let mut edit = builder.make_editor(attr.syntax()); + + edit.insert_all( + Position::after(attr.syntax()), + vec![ + make::tokens::whitespace(&format!("\n{indent}")).into(), + doc_cfg.syntax().clone().into(), + ], + ); + + builder.add_file_edits(ctx.vfs_file_id(), edit); + }, + ); + } + Some(()) +} + +fn generate_doc_cfg_attribute(attr: &ast::Attr, on: &str) -> Option { + let cfg = attr.token_tree()?; + let is_inner_attr = attr.excl_token().is_some(); + + let cfg_attr = make::meta_token_tree( + ident_path("cfg_attr"), + make::token_tree( + T!['('], + [ + Token(make::tokens::ident(on)), + Token(make::token(T![,])), + Token(make::tokens::single_space()), + Token(make::tokens::ident("doc")), + Node(make::token_tree(T!['('], [Token(make::tokens::ident("cfg")), Node(cfg)])), + ], + ), + ); + + if is_inner_attr { + Some(make::attr_inner(cfg_attr).clone_for_update()) + } else { + Some(make::attr_outer(cfg_attr).clone_for_update()) + } +} + +fn ident_path(s: &str) -> ast::Path { + make::path_from_segments([make::path_segment(make::name_ref(s))], false) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::check_assist; + + #[test] + fn test_generate_doc_cfg() { + check_assist( + generate_doc_cfg, + r#" + #[$0cfg(unix)] + pub struct Foo; + "#, + r#" + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + pub struct Foo; + "#, + ); + } + + #[test] + fn test_generate_doc_cfg_in_mod() { + check_assist( + generate_doc_cfg, + r#" + mod foo { + #[$0cfg(unix)] + pub struct Foo; + } + "#, + r#" + mod foo { + #[cfg(unix)] + #[cfg_attr(docsrs, doc(cfg(unix)))] + pub struct Foo; + } + "#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index cde0d875e0d6..eef91960932b 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -160,6 +160,7 @@ mod handlers { mod generate_delegate_trait; mod generate_deref; mod generate_derive; + mod generate_doc_cfg; mod generate_documentation_template; mod generate_enum_is_method; mod generate_enum_projection_method; @@ -293,6 +294,7 @@ mod handlers { generate_derive::generate_derive, generate_documentation_template::generate_doc_example, generate_documentation_template::generate_documentation_template, + generate_doc_cfg::generate_doc_cfg, generate_enum_is_method::generate_enum_is_method, generate_enum_projection_method::generate_enum_as_method, generate_enum_projection_method::generate_enum_try_into_method, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index fc1c6928ff31..6d3cf4d12851 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1540,6 +1540,22 @@ struct Point { ) } +#[test] +fn doctest_generate_doc_cfg() { + check_doc_test( + "generate_doc_cfg", + r#####" +#[$0cfg(unix)] +pub struct Foo; +"#####, + r#####" +#[cfg(unix)] +#[cfg_attr(docsrs, doc(cfg(unix)))] +pub struct Foo; +"#####, + ) +} + #[test] fn doctest_generate_doc_example() { check_doc_test(