Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-assists/src/handlers/add_attribute.rs196
-rw-r--r--crates/ide-assists/src/handlers/generate_derive.rs132
-rw-r--r--crates/ide-assists/src/lib.rs6
-rw-r--r--crates/ide-assists/src/tests.rs1
-rw-r--r--crates/ide-assists/src/tests/generated.rs40
-rw-r--r--crates/ide-assists/src/tests/sourcegen.rs9
6 files changed, 158 insertions, 226 deletions
diff --git a/crates/ide-assists/src/handlers/add_attribute.rs b/crates/ide-assists/src/handlers/add_attribute.rs
deleted file mode 100644
index dcb52b151d..0000000000
--- a/crates/ide-assists/src/handlers/add_attribute.rs
+++ /dev/null
@@ -1,196 +0,0 @@
-use ide_db::assists::{AssistId, AssistKind, GroupLabel};
-use syntax::{
- ast::{self, HasAttrs},
- match_ast, AstNode, SyntaxKind, TextSize,
-};
-
-use crate::assist_context::{AssistContext, Assists};
-
-// Assist: add_attribute
-//
-// Adds commonly used attributes to items.
-//
-// ```
-// struct Point {
-// x: u32,
-// y: u32,$0
-// }
-// ```
-// ->add_derive
-// ```
-// #[derive($0)]
-// struct Point {
-// x: u32,
-// y: u32,
-// }
-// ```
-pub(crate) fn add_attribute(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
- let cap = ctx.config.snippet_cap?;
-
- let (attr_owner, attrs) = ctx
- .find_node_at_offset::<ast::AnyHasAttrs>()?
- .syntax()
- .ancestors()
- .filter_map(ast::AnyHasAttrs::cast)
- .find_map(|attr_owner| {
- let node = attr_owner.syntax();
- match_ast! {
- match node {
- ast::Adt(_) => Some((attr_owner, ADT_ATTRS)),
- ast::Fn(_) => Some((attr_owner, FN_ATTRS)),
- _ => None,
- }
- }
- })?;
-
- let offset = attr_insertion_offset(&attr_owner)?;
-
- for tpl in attrs {
- let existing_offset = attr_owner.attrs().find_map(|attr| {
- if attr.simple_name()? == tpl.name {
- match attr.token_tree() {
- Some(tt) => {
- // Attribute like `#[derive(...)]`, position cursor right before the `)`
- return Some(tt.syntax().text_range().end() - TextSize::of(')'));
- }
- None => {
- // `#[key = value]`
- let tok = attr.syntax().last_token()?;
- if tok.kind() == SyntaxKind::R_BRACK {
- return Some(tok.text_range().end() - TextSize::of(']'));
- }
- }
- }
- }
- None
- });
- acc.add_group(
- &GroupLabel("Add attribute".into()),
- AssistId(tpl.id, AssistKind::Generate),
- format!("Add `#[{}]`", tpl.name),
- attr_owner.syntax().text_range(),
- |b| match existing_offset {
- Some(offset) => {
- b.insert_snippet(cap, offset, "$0");
- }
- None => {
- b.insert_snippet(cap, offset, format!("#[{}]\n", tpl.snippet));
- }
- },
- );
- }
-
- Some(())
-}
-
-fn attr_insertion_offset(nominal: &ast::AnyHasAttrs) -> Option<TextSize> {
- let non_ws_child = nominal
- .syntax()
- .children_with_tokens()
- .find(|it| it.kind() != SyntaxKind::COMMENT && it.kind() != SyntaxKind::WHITESPACE)?;
- Some(non_ws_child.text_range().start())
-}
-
-static ADT_ATTRS: &[AttrTemplate] = &[
- AttrTemplate { id: "add_derive", name: "derive", snippet: "derive($0)" },
- AttrTemplate { id: "add_must_use", name: "must_use", snippet: "must_use$0" },
-];
-
-static FN_ATTRS: &[AttrTemplate] = &[
- AttrTemplate { id: "add_inline", name: "inline", snippet: "inline$0" },
- AttrTemplate { id: "add_must_use", name: "must_use", snippet: "must_use$0" },
-];
-
-struct AttrTemplate {
- /// Assist ID.
- id: &'static str,
- /// User-facing attribute name.
- name: &'static str,
- /// Snippet to insert.
- snippet: &'static str,
-}
-
-#[cfg(test)]
-mod tests {
- use crate::tests::{check_assist_by_label, check_assist_target};
-
- use super::add_attribute;
-
- fn check_derive(ra_fixture_before: &str, ra_fixture_after: &str) {
- check_assist_by_label(
- add_attribute,
- ra_fixture_before,
- ra_fixture_after,
- "Add `#[derive]`",
- );
- }
-
- fn check_must_use(ra_fixture_before: &str, ra_fixture_after: &str) {
- check_assist_by_label(
- add_attribute,
- ra_fixture_before,
- ra_fixture_after,
- "Add `#[must_use]`",
- );
- }
-
- #[test]
- fn add_derive_new() {
- check_derive("struct Foo { a: i32, $0}", "#[derive($0)]\nstruct Foo { a: i32, }");
- check_derive("struct Foo { $0 a: i32, }", "#[derive($0)]\nstruct Foo { a: i32, }");
- }
-
- #[test]
- fn add_derive_existing() {
- check_derive(
- "#[derive(Clone)]\nstruct Foo { a: i32$0, }",
- "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
- );
- }
-
- #[test]
- fn add_derive_new_with_doc_comment() {
- check_derive(
- "
-/// `Foo` is a pretty important struct.
-/// It does stuff.
-struct Foo { a: i32$0, }
- ",
- "
-/// `Foo` is a pretty important struct.
-/// It does stuff.
-#[derive($0)]
-struct Foo { a: i32, }
- ",
- );
- }
-
- #[test]
- fn add_derive_target() {
- check_assist_target(
- add_attribute,
- r#"
-struct SomeThingIrrelevant;
-/// `Foo` is a pretty important struct.
-/// It does stuff.
-struct Foo { a: i32$0, }
-struct EvenMoreIrrelevant;
- "#,
- "/// `Foo` is a pretty important struct.
-/// It does stuff.
-struct Foo { a: i32, }",
- );
- }
-
- #[test]
- fn insert_must_use() {
- check_must_use("struct S$0;", "#[must_use$0]\nstruct S;");
- check_must_use("$0fn f() {}", "#[must_use$0]\nfn f() {}");
-
- check_must_use(r#"#[must_use = "bla"] struct S$0;"#, r#"#[must_use = "bla"$0] struct S;"#);
- check_must_use(r#"#[must_use = ] struct S$0;"#, r#"#[must_use = $0] struct S;"#);
-
- check_must_use(r#"#[must_use = "bla"] $0fn f() {}"#, r#"#[must_use = "bla"$0] fn f() {}"#);
- check_must_use(r#"#[must_use = ] $0fn f() {}"#, r#"#[must_use = $0] fn f() {}"#);
- }
-}
diff --git a/crates/ide-assists/src/handlers/generate_derive.rs b/crates/ide-assists/src/handlers/generate_derive.rs
new file mode 100644
index 0000000000..28483f795e
--- /dev/null
+++ b/crates/ide-assists/src/handlers/generate_derive.rs
@@ -0,0 +1,132 @@
+use syntax::{
+ ast::{self, AstNode, HasAttrs},
+ SyntaxKind::{COMMENT, WHITESPACE},
+ TextSize,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: generate_derive
+//
+// Adds a new `#[derive()]` clause to a struct or enum.
+//
+// ```
+// struct Point {
+// x: u32,
+// y: u32,$0
+// }
+// ```
+// ->
+// ```
+// #[derive($0)]
+// struct Point {
+// x: u32,
+// y: u32,
+// }
+// ```
+pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+ let cap = ctx.config.snippet_cap?;
+ let nominal = ctx.find_node_at_offset::<ast::Adt>()?;
+ let node_start = derive_insertion_offset(&nominal)?;
+ let target = nominal.syntax().text_range();
+ acc.add(
+ AssistId("generate_derive", AssistKind::Generate),
+ "Add `#[derive]`",
+ target,
+ |builder| {
+ let derive_attr = nominal
+ .attrs()
+ .filter_map(|x| x.as_simple_call())
+ .filter(|(name, _arg)| name == "derive")
+ .map(|(_name, arg)| arg)
+ .next();
+ match derive_attr {
+ None => {
+ builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
+ }
+ Some(tt) => {
+ // Just move the cursor.
+ builder.insert_snippet(
+ cap,
+ tt.syntax().text_range().end() - TextSize::of(')'),
+ "$0",
+ )
+ }
+ };
+ },
+ )
+}
+
+// Insert `derive` after doc comments.
+fn derive_insertion_offset(nominal: &ast::Adt) -> Option<TextSize> {
+ let non_ws_child = nominal
+ .syntax()
+ .children_with_tokens()
+ .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
+ Some(non_ws_child.text_range().start())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_target};
+
+ use super::*;
+
+ #[test]
+ fn add_derive_new() {
+ check_assist(
+ generate_derive,
+ "struct Foo { a: i32, $0}",
+ "#[derive($0)]\nstruct Foo { a: i32, }",
+ );
+ check_assist(
+ generate_derive,
+ "struct Foo { $0 a: i32, }",
+ "#[derive($0)]\nstruct Foo { a: i32, }",
+ );
+ }
+
+ #[test]
+ fn add_derive_existing() {
+ check_assist(
+ generate_derive,
+ "#[derive(Clone)]\nstruct Foo { a: i32$0, }",
+ "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
+ );
+ }
+
+ #[test]
+ fn add_derive_new_with_doc_comment() {
+ check_assist(
+ generate_derive,
+ "
+/// `Foo` is a pretty important struct.
+/// It does stuff.
+struct Foo { a: i32$0, }
+ ",
+ "
+/// `Foo` is a pretty important struct.
+/// It does stuff.
+#[derive($0)]
+struct Foo { a: i32, }
+ ",
+ );
+ }
+
+ #[test]
+ fn add_derive_target() {
+ check_assist_target(
+ generate_derive,
+ "
+struct SomeThingIrrelevant;
+/// `Foo` is a pretty important struct.
+/// It does stuff.
+struct Foo { a: i32$0, }
+struct EvenMoreIrrelevant;
+ ",
+ "/// `Foo` is a pretty important struct.
+/// It does stuff.
+struct Foo { a: i32, }",
+ );
+ }
+}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index f78dbf2ab8..b8607579a3 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -106,8 +106,6 @@ mod handlers {
mod add_explicit_type;
mod add_lifetime_to_type;
mod add_missing_impl_members;
- mod add_missing_match_arms;
- mod add_attribute;
mod add_turbo_fish;
mod apply_demorgan;
mod auto_import;
@@ -128,6 +126,7 @@ mod handlers {
mod extract_struct_from_enum_variant;
mod extract_type_alias;
mod extract_variable;
+ mod add_missing_match_arms;
mod fix_visibility;
mod flip_binexpr;
mod flip_comma;
@@ -136,6 +135,7 @@ mod handlers {
mod generate_default_from_enum_variant;
mod generate_default_from_new;
mod generate_deref;
+ mod generate_derive;
mod generate_documentation_template;
mod generate_enum_is_method;
mod generate_enum_projection_method;
@@ -191,7 +191,6 @@ mod handlers {
pub(crate) fn all() -> &'static [Handler] {
&[
// These are alphabetic for the foolish consistency
- add_attribute::add_attribute,
add_explicit_type::add_explicit_type,
add_missing_match_arms::add_missing_match_arms,
add_lifetime_to_type::add_lifetime_to_type,
@@ -222,6 +221,7 @@ mod handlers {
generate_constant::generate_constant,
generate_default_from_enum_variant::generate_default_from_enum_variant,
generate_default_from_new::generate_default_from_new,
+ generate_derive::generate_derive,
generate_documentation_template::generate_documentation_template,
generate_documentation_template::generate_doc_example,
generate_enum_is_method::generate_enum_is_method,
diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs
index ad2066a2ac..a8d5f85ba6 100644
--- a/crates/ide-assists/src/tests.rs
+++ b/crates/ide-assists/src/tests.rs
@@ -251,7 +251,6 @@ pub fn test_some_range(a: int) -> bool {
Extract into variable
Extract into function
Replace if let with match
- Add attribute
"#]]
.assert_eq(&expected);
}
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index 69d754d4f7..c9b0b1ce82 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -3,26 +3,6 @@
use super::check_doc_test;
#[test]
-fn doctest_add_attribute() {
- check_doc_test(
- "add_derive",
- r#####"
-struct Point {
- x: u32,
- y: u32,$0
-}
-"#####,
- r#####"
-#[derive($0)]
-struct Point {
- x: u32,
- y: u32,
-}
-"#####,
- )
-}
-
-#[test]
fn doctest_add_explicit_type() {
check_doc_test(
"add_explicit_type",
@@ -863,6 +843,26 @@ impl core::ops::Deref for B {
}
#[test]
+fn doctest_generate_derive() {
+ check_doc_test(
+ "generate_derive",
+ r#####"
+struct Point {
+ x: u32,
+ y: u32,$0
+}
+"#####,
+ r#####"
+#[derive($0)]
+struct Point {
+ x: u32,
+ y: u32,
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_generate_doc_example() {
check_doc_test(
"generate_doc_example",
diff --git a/crates/ide-assists/src/tests/sourcegen.rs b/crates/ide-assists/src/tests/sourcegen.rs
index 577bc0894e..d45e54186b 100644
--- a/crates/ide-assists/src/tests/sourcegen.rs
+++ b/crates/ide-assists/src/tests/sourcegen.rs
@@ -31,7 +31,7 @@ r#####"
}}
"######,
&test_id,
- &section.assist_id,
+ &assist.id,
reveal_hash_comments(&section.before),
reveal_hash_comments(&section.after)
);
@@ -61,7 +61,6 @@ r#####"
}
#[derive(Debug)]
struct Section {
- assist_id: String,
doc: String,
before: String,
after: String,
@@ -112,13 +111,11 @@ impl Assist {
let before = take_until(lines.by_ref(), "```");
- let arrow = lines.next().unwrap();
- assert!(arrow.starts_with("->"));
- let id = if arrow[2..].is_empty() { &assist.id } else { &arrow[2..] };
+ assert_eq!(lines.next().unwrap().as_str(), "->");
assert_eq!(lines.next().unwrap().as_str(), "```");
let after = take_until(lines.by_ref(), "```");
- assist.sections.push(Section { assist_id: id.to_string(), doc, before, after });
+ assist.sections.push(Section { doc, before, after });
}
acc.push(assist)