Repository: rust-lang/rust-analyzer
Pull request #12481 (2022-06-06 11:32:35)

feat: add label to loop
closes: #12392 

I changed `'loop` to `'l`, as `cargo check` says `'loop` is an invalid label name.


Patch:
diff --git a/crates/ide-assists/src/handlers/add_label_to_loop.rs b/crates/ide-assists/src/handlers/add_label_to_loop.rs
new file mode 100644
index 0000000000..a14cc45989
--- /dev/null
+++ b/crates/ide-assists/src/handlers/add_label_to_loop.rs
@@ -0,0 +1,164 @@
+use ide_db::syntax_helpers::node_ext::for_each_break_and_continue_expr;
+use syntax::{
+    ast::{self, AstNode, HasLoopBody},
+    T,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: add_label_to_loop
+//
+// Adds a label to a loop.
+//
+// ```
+// fn main() {
+//     loop$0 {
+//         break;
+//         continue;
+//     }
+// }
+// ```
+// ->
+// ```
+// fn main() {
+//     'l: loop {
+//         break 'l;
+//         continue 'l;
+//     }
+// }
+// ```
+pub(crate) fn add_label_to_loop(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    let loop_kw = ctx.find_token_syntax_at_offset(T![loop])?;
+    let loop_expr = loop_kw.parent().and_then(ast::LoopExpr::cast)?;
+    if loop_expr.label().is_some() {
+        return None;
+    }
+
+    acc.add(
+        AssistId("add_label_to_loop", AssistKind::Generate),
+        "Add Label",
+        loop_expr.syntax().text_range(),
+        |builder| {
+            builder.insert(loop_kw.text_range().start(), "'l: ");
+
+            let loop_body = loop_expr.loop_body().and_then(|it| it.stmt_list());
+            for_each_break_and_continue_expr(
+                loop_expr.label(),
+                loop_body,
+                &mut |expr| match expr {
+                    ast::Expr::BreakExpr(break_expr) => {
+                        if let Some(break_token) = break_expr.break_token() {
+                            builder.insert(break_token.text_range().end(), " 'l")
+                        }
+                    }
+                    ast::Expr::ContinueExpr(continue_expr) => {
+                        if let Some(continue_token) = continue_expr.continue_token() {
+                            builder.insert(continue_token.text_range().end(), " 'l")
+                        }
+                    }
+                    _ => {}
+                },
+            );
+        },
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn add_label() {
+        check_assist(
+            add_label_to_loop,
+            r#"
+fn main() {
+    loop$0 {
+        break;
+        continue;
+    }
+}"#,
+            r#"
+fn main() {
+    'l: loop {
+        break 'l;
+        continue 'l;
+    }
+}"#,
+        );
+    }
+
+    #[test]
+    fn add_label_to_outer_loop() {
+        check_assist(
+            add_label_to_loop,
+            r#"
+fn main() {
+    loop$0 {
+        break;
+        continue;
+        loop {
+            break;
+            continue;
+        }
+    }
+}"#,
+            r#"
+fn main() {
+    'l: loop {
+        break 'l;
+        continue 'l;
+        loop {
+            break;
+            continue;
+        }
+    }
+}"#,
+        );
+    }
+
+    #[test]
+    fn add_label_to_inner_loop() {
+        check_assist(
+            add_label_to_loop,
+            r#"
+fn main() {
+    loop {
+        break;
+        continue;
+        loop$0 {
+            break;
+            continue;
+        }
+    }
+}"#,
+            r#"
+fn main() {
+    loop {
+        break;
+        continue;
+        'l: loop {
+            break 'l;
+            continue 'l;
+        }
+    }
+}"#,
+        );
+    }
+
+    #[test]
+    fn do_not_add_label_if_exists() {
+        check_assist_not_applicable(
+            add_label_to_loop,
+            r#"
+fn main() {
+    'l: loop$0 {
+        break 'l;
+        continue 'l;
+    }
+}"#,
+        );
+    }
+}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index 94fbaff400..7b9134efb4 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -104,6 +104,7 @@ mod handlers {
     pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
 
     mod add_explicit_type;
+    mod add_label_to_loop;
     mod add_lifetime_to_type;
     mod add_missing_impl_members;
     mod add_turbo_fish;
@@ -193,6 +194,7 @@ mod handlers {
         &[
             // These are alphabetic for the foolish consistency
             add_explicit_type::add_explicit_type,
+            add_label_to_loop::add_label_to_loop,
             add_missing_match_arms::add_missing_match_arms,
             add_lifetime_to_type::add_lifetime_to_type,
             add_return_type::add_return_type,


Test patch:
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index 2d57101409..e8d48607be 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -102,6 +102,29 @@ impl Trait<u32> for () {
     )
 }
 
+#[test]
+fn doctest_add_label_to_loop() {
+    check_doc_test(
+        "add_label_to_loop",
+        r#####"
+fn main() {
+    loop$0 {
+        break;
+        continue;
+    }
+}
+"#####,
+        r#####"
+fn main() {
+    'l: loop {
+        break 'l;
+        continue 'l;
+    }
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_add_lifetime_to_type() {
     check_doc_test(