Repository: rust-analyzer/rust-analyzer
Pull request #7777 (2021-02-24 21:52:23)

Implement line<->block comment assist
Fixes: https://github.com/rust-analyzer/rust-analyzer/issues/6515


Patch:
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index c94fb580a2..c7609e90d9 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -189,7 +189,6 @@ pub mod known {
         // Components of known path (function name)
         filter_map,
         next,
-        iter_mut,
         // Builtin macros
         file,
         column,
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 5d0449e567..55f95ebae8 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -1224,29 +1224,4 @@ fn test() {}
             "#]],
         );
     }
-
-    #[test]
-    fn test_const_in_pattern() {
-        check(
-            r#"
-const A$0: i32 = 42;
-
-fn main() {
-    match A {
-        A => (),
-        _ => (),
-    }
-    if let A = A {}
-}
-"#,
-            expect![[r#"
-                A Const FileId(0) 0..18 6..7
-
-                FileId(0) 42..43
-                FileId(0) 54..55
-                FileId(0) 97..98
-                FileId(0) 101..102
-            "#]],
-        );
-    }
 }
diff --git a/crates/ide_assists/src/handlers/apply_demorgan.rs b/crates/ide_assists/src/handlers/apply_demorgan.rs
index 6997ea048e..ed4d114559 100644
--- a/crates/ide_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ide_assists/src/handlers/apply_demorgan.rs
@@ -7,17 +7,18 @@ use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKin
 // Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
 // This transforms expressions of the form `!l || !r` into `!(l && r)`.
 // This also works with `&&`. This assist can only be applied with the cursor
-// on either `||` or `&&`.
+// on either `||` or `&&`, with both operands being a negation of some kind.
+// This means something of the form `!x` or `x != y`.
 //
 // ```
 // fn main() {
-//     if x != 4 ||$0 y < 3.14 {}
+//     if x != 4 ||$0 !y {}
 // }
 // ```
 // ->
 // ```
 // fn main() {
-//     if !(x == 4 && !(y < 3.14)) {}
+//     if !(x == 4 && y) {}
 // }
 // ```
 pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -32,11 +33,11 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
 
     let lhs = expr.lhs()?;
     let lhs_range = lhs.syntax().text_range();
-    let not_lhs = invert_boolean_expression(&ctx.sema, lhs);
+    let not_lhs = invert_boolean_expression(lhs);
 
     let rhs = expr.rhs()?;
     let rhs_range = rhs.syntax().text_range();
-    let not_rhs = invert_boolean_expression(&ctx.sema, rhs);
+    let not_rhs = invert_boolean_expression(rhs);
 
     acc.add(
         AssistId("apply_demorgan", AssistKind::RefactorRewrite),
@@ -61,77 +62,10 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
 
 #[cfg(test)]
 mod tests {
-    use ide_db::helpers::FamousDefs;
-
     use super::*;
 
     use crate::tests::{check_assist, check_assist_not_applicable};
 
-    const ORDABLE_FIXTURE: &'static str = r"
-//- /lib.rs deps:core crate:ordable
-struct NonOrderable;
-struct Orderable;
-impl core::cmp::Ord for Orderable {}
-";
-
-    fn check(ra_fixture_before: &str, ra_fixture_after: &str) {
-        let before = &format!(
-            "//- /main.rs crate:main deps:core,ordable\n{}\n{}{}",
-            ra_fixture_before,
-            FamousDefs::FIXTURE,
-            ORDABLE_FIXTURE
-        );
-        check_assist(apply_demorgan, before, &format!("{}\n", ra_fixture_after));
-    }
-
-    #[test]
-    fn demorgan_handles_leq() {
-        check(
-            r"use ordable::Orderable;
-fn f() {
-    Orderable < Orderable &&$0 Orderable <= Orderable
-}",
-            r"use ordable::Orderable;
-fn f() {
-    !(Orderable >= Orderable || Orderable > Orderable)
-}",
-        );
-        check(
-            r"use ordable::NonOrderable;
-fn f() {
-    NonOrderable < NonOrderable &&$0 NonOrderable <= NonOrderable
-}",
-            r"use ordable::NonOrderable;
-fn f() {
-    !(!(NonOrderable < NonOrderable) || !(NonOrderable <= NonOrderable))
-}",
-        );
-    }
-
-    #[test]
-    fn demorgan_handles_geq() {
-        check(
-            r"use ordable::Orderable;
-fn f() {
-    Orderable > Orderable &&$0 Orderable >= Orderable
-}",
-            r"use ordable::Orderable;
-fn f() {
-    !(Orderable <= Orderable || Orderable < Orderable)
-}",
-        );
-        check(
-            r"use ordable::NonOrderable;
-fn f() {
-    Orderable > Orderable &&$0 Orderable >= Orderable
-}",
-            r"use ordable::NonOrderable;
-fn f() {
-    !(!(Orderable > Orderable) || !(Orderable >= Orderable))
-}",
-        );
-    }
-
     #[test]
     fn demorgan_turns_and_into_or() {
         check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }")
diff --git a/crates/ide_assists/src/handlers/convert_comment_block.rs b/crates/ide_assists/src/handlers/convert_comment_block.rs
new file mode 100644
index 0000000000..cdc45fc42c
--- /dev/null
+++ b/crates/ide_assists/src/handlers/convert_comment_block.rs
@@ -0,0 +1,419 @@
+use itertools::Itertools;
+use std::convert::identity;
+use syntax::{
+    ast::{
+        self,
+        edit::IndentLevel,
+        Comment, CommentKind,
+        CommentPlacement::{Inner, Outer},
+        CommentShape::{self, Block, Line},
+        Whitespace,
+    },
+    AstToken, Direction, SyntaxElement, TextRange,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+/// Assist: line_to_block
+///
+/// Converts comments between block and single-line form
+///
+/// ```
+///    // Multi-line
+///    // comment
+/// ```
+/// ->
+/// ```
+///   /**
+///   Multi-line
+///   comment
+///   */
+/// ```
+pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    if let Some(comment) = ctx.find_token_at_offset::<ast::Comment>() {
+        // Only allow comments which are alone on their line
+        if let Some(prev) = comment.syntax().prev_token() {
+            if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() {
+                return None;
+            }
+        }
+
+        return match comment.kind().shape {
+            ast::CommentShape::Block => block_to_line(acc, comment),
+            ast::CommentShape::Line => line_to_block(acc, comment),
+        };
+    }
+
+    return None;
+}
+
+fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
+    let target = comment.syntax().text_range();
+
+    acc.add(
+        AssistId("block_to_line", AssistKind::RefactorRewrite),
+        "Replace block comment with line comments",
+        target,
+        |edit| {
+            let indentation = IndentLevel::from_token(comment.syntax());
+            let line_prefix =
+                comment_kind_prefix(CommentKind { shape: CommentShape::Line, ..comment.kind() });
+
+            let text = comment.text();
+            let text = &text[comment.prefix().len()..(text.len() - "*/".len())].trim();
+
+            let lines = text.lines().peekable();
+
+            let indent_spaces = indentation.to_string();
+            let output = lines
+                .map(|l| l.trim_start_matches(&indent_spaces))
+                .map(|l| {
+                    // Don't introduce trailing whitespace
+                    if l.is_empty() {
+                        line_prefix.to_string()
+                    } else {
+                        format!("{} {}", line_prefix, l.trim_start_matches(&indent_spaces))
+                    }
+                })
+                .join(&format!("\n{}", indent_spaces));
+
+            edit.replace(target, output)
+        },
+    )
+}
+
+fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
+    // Find all the comments we'll be collapsing into a block
+    let comments = relevant_line_comments(&comment);
+
+    // Establish the target of our edit based on the comments we found
+    let target = TextRange::new(
+        comments[0].syntax().text_range().start(),
+        comments.last().unwrap().syntax().text_range().end(),
+    );
+
+    acc.add(
+        AssistId("line_to_block", AssistKind::RefactorRewrite),
+        "Replace line comments with a single block comment",
+        target,
+        |edit| {
+            // We pick a single indentation level for the whole block comment based on the
+            // comment where the assist was invoked. This will be prepended to the
+            // contents of each line comment when they're put into the block comment.
+            let indentation = IndentLevel::from_token(&comment.syntax());
+
+            let block_comment_body =
+                comments.into_iter().map(|c| line_comment_text(indentation, c)).join("\n");
+
+            let block_prefix =
+                comment_kind_prefix(CommentKind { shape: CommentShape::Block, ..comment.kind() });
+
+            let output =
+                format!("{}\n{}\n{}*/", block_prefix, block_comment_body, indentation.to_string());
+
+            edit.replace(target, output)
+        },
+    )
+}
+
+/// The line -> block assist can  be invoked from anywhere within a sequence of line comments.
+/// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will
+/// be joined.
+fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
+    // The prefix identifies the kind of comment we're dealing with
+    let prefix = comment.prefix();
+    let same_prefix = |c: &ast::Comment| c.prefix() == prefix;
+
+    // These tokens are allowed to exist between comments
+    let skippable = |not: &SyntaxElement| {
+        not.clone()
+            .into_token()
+            .and_then(Whitespace::cast)
+            .map(|w| !w.spans_multiple_lines())
+            .unwrap_or(false)
+    };
+
+    // Find all preceding comments (in reverse order) that have the same prefix
+    let prev_comments = comment
+        .syntax()
+        .siblings_with_tokens(Direction::Prev)
+        .filter(|s| !skippable(s))
+        .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
+        .take_while(|opt_com| opt_com.is_some())
+        .filter_map(identity)
+        .skip(1); // skip the first element so we don't duplicate it in next_comments
+
+    let next_comments = comment
+        .syntax()
+        .siblings_with_tokens(Direction::Next)
+        .filter(|s| !skippable(s))
+        .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
+        .take_while(|opt_com| opt_com.is_some())
+        .filter_map(identity);
+
+    let mut comments: Vec<_> = prev_comments.collect();
+    comments.reverse();
+    comments.extend(next_comments);
+    comments
+}
+
+// Line comments usually begin with a single space character following the prefix as seen here:
+//^
+// But comments can also include indented text:
+//    > Hello there
+//
+// We handle this by stripping *AT MOST* one space character from the start of the line
+// This has its own problems because it can cause alignment issues:
+//
+//              /*
+// a      ----> a
+//b       ----> b
+//              */
+//
+// But since such comments aren't idiomatic we're okay with this.
+fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String {
+    let contents_without_prefix = comm.text().strip_prefix(comm.prefix()).unwrap();
+    let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix);
+
+    // Don't add the indentation if the line is empty
+    if contents.is_empty() {
+        contents.to_owned()
+    } else {
+        indentation.to_string() + &contents
+    }
+}
+
+fn comment_kind_prefix(ck: ast::CommentKind) -> &'static str {
+    match (ck.shape, ck.doc) {
+        (Line, Some(Inner)) => "//!",
+        (Line, Some(Outer)) => "///",
+        (Line, None) => "//",
+        (Block, Some(Inner)) => "/*!",
+        (Block, Some(Outer)) => "/**",
+        (Block, None) => "/*",
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn single_line_to_block() {
+        check_assist(
+            convert_comment_block,
+            r#"
+// line$0 comment
+fn main() {
+    foo();
+}
+"#,
+            r#"
+/*
+line comment
+*/
+fn main() {
+    foo();
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn single_line_to_block_indented() {
+        check_assist(
+            convert_comment_block,
+            r#"
+fn main() {
+    // line$0 comment
+    foo();
+}
+"#,
+            r#"
+fn main() {
+    /*
+    line comment
+    */
+    foo();
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn multiline_to_block() {
+        check_assist(
+            convert_comment_block,
+            r#"
+fn main() {
+    // above
+    // line$0 comment
+    //
+    // below
+    foo();
+}
+"#,
+            r#"
+fn main() {
+    /*
+    above
+    line comment
+
+    below
+    */
+    foo();
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn end_of_line_to_block() {
+        check_assist_not_applicable(
+            convert_comment_block,
+            r#"
+fn main() {
+    foo(); // end-of-line$0 comment
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn single_line_different_kinds() {
+        check_assist(
+            convert_comment_block,
+            r#"
+fn main() {
+    /// different prefix
+    // line$0 comment
+    // below
+    foo();
+}
+"#,
+            r#"
+fn main() {
+    /// different prefix
+    /*
+    line comment
+    below
+    */
+    foo();
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn single_line_separate_chunks() {
+        check_assist(
+            convert_comment_block,
+            r#"
+fn main() {
+    // different chunk
+
+    // line$0 comment
+    // below
+    foo();
+}
+"#,
+            r#"
+fn main() {
+    // different chunk
+
+    /*
+    line comment
+    below
+    */
+    foo();
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn doc_block_comment_to_lines() {
+        check_assist(
+            convert_comment_block,
+            r#"
+/**
+ hi$0 there
+*/
+"#,
+            r#"
+/// hi there
+"#,
+        );
+    }
+
+    #[test]
+    fn block_comment_to_lines() {
+        check_assist(
+            convert_comment_block,
+            r#"
+/*
+ hi$0 there
+*/
+"#,
+            r#"
+// hi there
+"#,
+        );
+    }
+
+    #[test]
+    fn inner_doc_block_to_lines() {
+        check_assist(
+            convert_comment_block,
+            r#"
+/*!
+ hi$0 there
+*/
+"#,
+            r#"
+//! hi there
+"#,
+        );
+    }
+
+    #[test]
+    fn block_to_lines_indent() {
+        check_assist(
+            convert_comment_block,
+            r#"
+fn main() {
+    /*!
+    hi$0 there
+
+    ```
+      code_sample
+    ```
+    */
+}
+"#,
+            r#"
+fn main() {
+    //! hi there
+    //!
+    //! ```
+    //!   code_sample
+    //! ```
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn end_of_line_block_to_line() {
+        check_assist_not_applicable(
+            convert_comment_block,
+            r#"
+fn main() {
+    foo(); /* end-of-line$0 comment */
+}
+"#,
+        );
+    }
+}
diff --git a/crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs b/crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs
deleted file mode 100644
index 9fddf889cf..0000000000
--- a/crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs
+++ /dev/null
@@ -1,301 +0,0 @@
-use ast::LoopBodyOwner;
-use hir::known;
-use ide_db::helpers::FamousDefs;
-use stdx::format_to;
-use syntax::{ast, AstNode};
-
-use crate::{AssistContext, AssistId, AssistKind, Assists};
-
-// Assist: convert_for_to_iter_for_each
-//
-// Converts a for loop into a for_each loop on the Iterator.
-//
-// ```
-// fn main() {
-//     let x = vec![1, 2, 3];
-//     for $0v in x {
-//         let y = v * 2;
-//     }
-// }
-// ```
-// ->
-// ```
-// fn main() {
-//     let x = vec![1, 2, 3];
-//     x.into_iter().for_each(|v| {
-//         let y = v * 2;
-//     });
-// }
-// ```
-pub(crate) fn convert_for_to_iter_for_each(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let for_loop = ctx.find_node_at_offset::<ast::ForExpr>()?;
-    let iterable = for_loop.iterable()?;
-    let pat = for_loop.pat()?;
-    let body = for_loop.loop_body()?;
-
-    acc.add(
-        AssistId("convert_for_to_iter_for_each", AssistKind::RefactorRewrite),
-        "Convert a for loop into an Iterator::for_each",
-        for_loop.syntax().text_range(),
-        |builder| {
-            let mut buf = String::new();
-
-            if let Some((expr_behind_ref, method)) =
-                is_ref_and_impls_iter_method(&ctx.sema, &iterable)
-            {
-                // We have either "for x in &col" and col implements a method called iter
-                //             or "for x in &mut col" and col implements a method called iter_mut
-                format_to!(buf, "{}.{}()", expr_behind_ref, method);
-            } else if impls_core_iter(&ctx.sema, &iterable) {
-                format_to!(buf, "{}", iterable);
-            } else {
-                if let ast::Expr::RefExpr(_) = iterable {
-                    format_to!(buf, "({}).into_iter()", iterable);
-                } else {
-                    format_to!(buf, "{}.into_iter()", iterable);
-                }
-            }
-
-            format_to!(buf, ".for_each(|{}| {});", pat, body);
-
-            builder.replace(for_loop.syntax().text_range(), buf)
-        },
-    )
-}
-
-/// If iterable is a reference where the expression behind the reference implements a method
-/// returning an Iterator called iter or iter_mut (depending on the type of reference) then return
-/// the expression behind the reference and the method name
-fn is_ref_and_impls_iter_method(
-    sema: &hir::Semantics<ide_db::RootDatabase>,
-    iterable: &ast::Expr,
-) -> Option<(ast::Expr, hir::Name)> {
-    let ref_expr = match iterable {
-        ast::Expr::RefExpr(r) => r,
-        _ => return None,
-    };
-    let wanted_method = if ref_expr.mut_token().is_some() { known::iter_mut } else { known::iter };
-    let expr_behind_ref = ref_expr.expr()?;
-    let typ = sema.type_of_expr(&expr_behind_ref)?;
-    let scope = sema.scope(iterable.syntax());
-    let krate = scope.module()?.krate();
-    let traits_in_scope = scope.traits_in_scope();
-    let iter_trait = FamousDefs(sema, Some(krate)).core_iter_Iterator()?;
-    let has_wanted_method = typ.iterate_method_candidates(
-        sema.db,
-        krate,
-        &traits_in_scope,
-        Some(&wanted_method),
-        |_, func| {
-            if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) {
-                return Some(());
-            }
-            None
-        },
-    );
-    has_wanted_method.and(Some((expr_behind_ref, wanted_method)))
-}
-
-/// Whether iterable implements core::Iterator
-fn impls_core_iter(sema: &hir::Semantics<ide_db::RootDatabase>, iterable: &ast::Expr) -> bool {
-    let it_typ = if let Some(i) = sema.type_of_expr(iterable) {
-        i
-    } else {
-        return false;
-    };
-    let module = if let Some(m) = sema.scope(iterable.syntax()).module() {
-        m
-    } else {
-        return false;
-    };
-    let krate = module.krate();
-    if let Some(iter_trait) = FamousDefs(sema, Some(krate)).core_iter_Iterator() {
-        return it_typ.impls_trait(sema.db, iter_trait, &[]);
-    }
-    false
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::tests::{check_assist, check_assist_not_applicable};
-
-    use super::*;
-
-    const EMPTY_ITER_FIXTURE: &'static str = r"
-//- /lib.rs deps:core crate:empty_iter
-pub struct EmptyIter;
-impl Iterator for EmptyIter {
-    type Item = usize;
-    fn next(&mut self) -> Option<Self::Item> { None }
-}
-
-pub struct Empty;
-impl Empty {
-    pub fn iter(&self) -> EmptyIter { EmptyIter }
-    pub fn iter_mut(&self) -> EmptyIter { EmptyIter }
-}
-
-pub struct NoIterMethod;
-";
-
-    fn check_assist_with_fixtures(before: &str, after: &str) {
-        let before = &format!(
-            "//- /main.rs crate:main deps:core,empty_iter{}{}{}",
-            before,
-            FamousDefs::FIXTURE,
-            EMPTY_ITER_FIXTURE
-        );
-        check_assist(convert_for_to_iter_for_each, before, after);
-    }
-
-    #[test]
-    fn test_not_for() {
-        check_assist_not_applicable(
-            convert_for_to_iter_for_each,
-            r"
-let mut x = vec![1, 2, 3];
-x.iter_mut().$0for_each(|v| *v *= 2);
-        ",
-        )
-    }
-
-    #[test]
-    fn test_simple_for() {
-        check_assist(
-            convert_for_to_iter_for_each,
-            r"
-fn main() {
-    let x = vec![1, 2, 3];
-    for $0v in x {
-        v *= 2;
-    }
-}",
-            r"
-fn main() {
-    let x = vec![1, 2, 3];
-    x.into_iter().for_each(|v| {
-        v *= 2;
-    });
-}",
-        )
-    }
-
-    #[test]
-    fn test_for_borrowed() {
-        check_assist_with_fixtures(
-            r"
-use empty_iter::*;
-fn main() {
-    let x = Empty;
-    for $0v in &x {
-        let a = v * 2;
-    }
-}
-",
-            r"
-use empty_iter::*;
-fn main() {
-    let x = Empty;
-    x.iter().for_each(|v| {
-        let a = v * 2;
-    });
-}
-",
-        )
-    }
-
-    #[test]
-    fn test_for_borrowed_no_iter_method() {
-        check_assist_with_fixtures(
-            r"
-use empty_iter::*;
-fn main() {
-    let x = NoIterMethod;
-    for $0v in &x {
-        let a = v * 2;
-    }
-}
-",
-            r"
-use empty_iter::*;
-fn main() {
-    let x = NoIterMethod;
-    (&x).into_iter().for_each(|v| {
-        let a = v * 2;
-    });
-}
-",
-        )
-    }
-
-    #[test]
-    fn test_for_borrowed_mut() {
-        check_assist_with_fixtures(
-            r"
-use empty_iter::*;
-fn main() {
-    let x = Empty;
-    for $0v in &mut x {
-        let a = v * 2;
-    }
-}
-",
-            r"
-use empty_iter::*;
-fn main() {
-    let x = Empty;
-    x.iter_mut().for_each(|v| {
-        let a = v * 2;
-    });
-}
-",
-        )
-    }
-
-    #[test]
-    fn test_for_borrowed_mut_behind_var() {
-        check_assist(
-            convert_for_to_iter_for_each,
-            r"
-fn main() {
-    let x = vec![1, 2, 3];
-    let y = &mut x;
-    for $0v in y {
-        *v *= 2;
-    }
-}",
-            r"
-fn main() {
-    let x = vec![1, 2, 3];
-    let y = &mut x;
-    y.into_iter().for_each(|v| {
-        *v *= 2;
-    });
-}",
-        )
-    }
-
-    #[test]
-    fn test_already_impls_iterator() {
-        check_assist_with_fixtures(
-            r#"
-use empty_iter::*;
-fn main() {
-    let x = Empty;
-    for$0 a in x.iter().take(1) {
-        println!("{}", a);
-    }
-}
-"#,
-            r#"
-use empty_iter::*;
-fn main() {
-    let x = Empty;
-    x.iter().take(1).for_each(|a| {
-        println!("{}", a);
-    });
-}
-"#,
-        );
-    }
-}
diff --git a/crates/ide_assists/src/handlers/early_return.rs b/crates/ide_assists/src/handlers/early_return.rs
index 9e0918477b..6b87c3c057 100644
--- a/crates/ide_assists/src/handlers/early_return.rs
+++ b/crates/ide_assists/src/handlers/early_return.rs
@@ -111,7 +111,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
                     let new_expr = {
                         let then_branch =
                             make::block_expr(once(make::expr_stmt(early_expression).into()), None);
-                        let cond = invert_boolean_expression(&ctx.sema, cond_expr);
+                        let cond = invert_boolean_expression(cond_expr);
                         make::expr_if(make::condition(cond, None), then_branch, None)
                             .indent(if_indent_level)
                     };
diff --git a/crates/ide_assists/src/handlers/invert_if.rs b/crates/ide_assists/src/handlers/invert_if.rs
index b131dc2058..5b69dafd4b 100644
--- a/crates/ide_assists/src/handlers/invert_if.rs
+++ b/crates/ide_assists/src/handlers/invert_if.rs
@@ -50,7 +50,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     };
 
     acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
-        let flip_cond = invert_boolean_expression(&ctx.sema, cond.clone());
+        let flip_cond = invert_boolean_expression(cond.clone());
         edit.replace_ast(cond, flip_cond);
 
         let else_node = else_block.syntax();
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index f4c7e6fbf7..b9799b9d5a 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -114,8 +114,8 @@ mod handlers {
     mod apply_demorgan;
     mod auto_import;
     mod change_visibility;
-    mod convert_for_to_iter_for_each;
     mod convert_integer_literal;
+    mod convert_comment_block;
     mod early_return;
     mod expand_glob_import;
     mod extract_function;
@@ -176,8 +176,8 @@ mod handlers {
             apply_demorgan::apply_demorgan,
             auto_import::auto_import,
             change_visibility::change_visibility,
-            convert_for_to_iter_for_each::convert_for_to_iter_for_each,
             convert_integer_literal::convert_integer_literal,
+            convert_comment_block::convert_comment_block,
             early_return::convert_to_guarded_return,
             expand_glob_import::expand_glob_import,
             move_module_to_file::move_module_to_file,
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index 276792bc1a..0074da7410 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -3,11 +3,8 @@
 use std::ops;
 
 use ast::TypeBoundsOwner;
-use hir::{Adt, HasSource, Semantics};
-use ide_db::{
-    helpers::{FamousDefs, SnippetCap},
-    RootDatabase,
-};
+use hir::{Adt, HasSource};
+use ide_db::{helpers::SnippetCap, RootDatabase};
 use itertools::Itertools;
 use stdx::format_to;
 use syntax::{
@@ -208,36 +205,23 @@ pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
         .unwrap_or_else(|| node.text_range().start())
 }
 
-pub(crate) fn invert_boolean_expression(
-    sema: &Semantics<RootDatabase>,
-    expr: ast::Expr,
-) -> ast::Expr {
-    if let Some(expr) = invert_special_case(sema, &expr) {
+pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
+    if let Some(expr) = invert_special_case(&expr) {
         return expr;
     }
     make::expr_prefix(T![!], expr)
 }
 
-fn invert_special_case(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ast::Expr> {
+fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
     match expr {
         ast::Expr::BinExpr(bin) => match bin.op_kind()? {
             ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
             ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()),
-            // Swap `<` with `>=`, `<=` with `>`, ... if operands `impl Ord`
-            ast::BinOp::LesserTest if bin_impls_ord(sema, bin) => {
-                bin.replace_op(T![>=]).map(|it| it.into())
-            }
-            ast::BinOp::LesserEqualTest if bin_impls_ord(sema, bin) => {
-                bin.replace_op(T![>]).map(|it| it.into())
-            }
-            ast::BinOp::GreaterTest if bin_impls_ord(sema, bin) => {
-                bin.replace_op(T![<=]).map(|it| it.into())
-            }
-            ast::BinOp::GreaterEqualTest if bin_impls_ord(sema, bin) => {
-                bin.replace_op(T![<]).map(|it| it.into())
+            // Parenthesize composite boolean expressions before prefixing `!`
+            ast::BinOp::BooleanAnd | ast::BinOp::BooleanOr => {
+                Some(make::expr_prefix(T![!], make::expr_paren(expr.clone())))
             }
-            // Parenthesize other expressions before prefixing `!`
-            _ => Some(make::expr_prefix(T![!], make::expr_paren(expr.clone()))),
+            _ => None,
         },
         ast::Expr::MethodCallExpr(mce) => {
             let receiver = mce.receiver()?;
@@ -266,22 +250,6 @@ fn invert_special_case(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Opti
     }
 }
 
-fn bin_impls_ord(sema: &Semantics<RootDatabase>, bin: &ast::BinExpr) -> bool {
-    match (
-        bin.lhs().and_then(|lhs| sema.type_of_expr(&lhs)),
-        bin.rhs().and_then(|rhs| sema.type_of_expr(&rhs)),
-    ) {
-        (Some(lhs_ty), Some(rhs_ty)) if lhs_ty == rhs_ty => {
-            let krate = sema.scope(bin.syntax()).module().map(|it| it.krate());
-            let ord_trait = FamousDefs(sema, krate).core_cmp_Ord();
-            ord_trait.map_or(false, |ord_trait| {
-                lhs_ty.autoderef(sema.db).any(|ty| ty.impls_trait(sema.db, ord_trait, &[]))
-            })
-        }
-        _ => false,
-    }
-}
-
 pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
     [Direction::Next, Direction::Prev].iter().copied()
 }
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index f9de8ce0e6..bc7aee1103 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -45,10 +45,6 @@ impl FamousDefs<'_, '_> {
         self.find_crate("core")
     }
 
-    pub fn core_cmp_Ord(&self) -> Option<Trait> {
-        self.find_trait("core:cmp:Ord")
-    }
-
     pub fn core_convert_From(&self) -> Option<Trait> {
         self.find_trait("core:convert:From")
     }
diff --git a/crates/ide_db/src/helpers/famous_defs_fixture.rs b/crates/ide_db/src/helpers/famous_defs_fixture.rs
index bb4e9666b1..5e88de64db 100644
--- a/crates/ide_db/src/helpers/famous_defs_fixture.rs
+++ b/crates/ide_db/src/helpers/famous_defs_fixture.rs
@@ -1,15 +1,5 @@
 //- /libcore.rs crate:core
 //! Signatures of traits, types and functions from the core lib for use in tests.
-pub mod cmp {
-
-    pub trait Ord {
-        fn cmp(&self, other: &Self) -> Ordering;
-        fn max(self, other: Self) -> Self;
-        fn min(self, other: Self) -> Self;
-        fn clamp(self, min: Self, max: Self) -> Self;
-    }
-}
-
 pub mod convert {
     pub trait From<T> {
         fn from(t: T) -> Self;
@@ -119,7 +109,6 @@ pub mod option {
 
 pub mod prelude {
     pub use crate::{
-        cmp::Ord,
         convert::From,
         default::Default,
         iter::{IntoIterator, Iterator},
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index ddcfbd3f3f..ba8bea0020 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -416,11 +416,10 @@ impl<'a> FindUsages<'a> {
         sink: &mut dyn FnMut(FileId, FileReference) -> bool,
     ) -> bool {
         match NameClass::classify(self.sema, name) {
-            Some(NameClass::PatFieldShorthand { local_def: _, field_ref })
-                if matches!(
-                    self.def, Definition::Field(_) if &field_ref == self.def
-                ) =>
-            {
+            Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => {
+                if !matches!(self.def, Definition::Field(_) if &field_ref == self.def) {
+                    return false;
+                }
                 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
                 let reference = FileReference {
                     range,
@@ -430,12 +429,6 @@ impl<'a> FindUsages<'a> {
                 };
                 sink(file_id, reference)
             }
-            Some(NameClass::ConstReference(def)) if *self.def == def => {
-                let FileRange { file_id, range } = self.sema.original_range(name.syntax());
-                let reference =
-                    FileReference { range, name: ast::NameLike::Name(name.clone()), access: None };
-                sink(file_id, reference)
-            }
             _ => false, // not a usage
         }
     }
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 824ebf41cf..0b3b76d4a7 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -595,11 +595,14 @@ impl ops::Add<u8> for IndentLevel {
 
 impl IndentLevel {
     pub fn from_node(node: &SyntaxNode) -> IndentLevel {
-        let first_token = match node.first_token() {
-            Some(it) => it,
+        match node.first_token() {
+            Some(it) => Self::from_token(&it),
             None => return IndentLevel(0),
-        };
-        for ws in prev_tokens(first_token).filter_map(ast::Whitespace::cast) {
+        }
+    }
+
+    pub fn from_token(token: &SyntaxToken) -> IndentLevel {
+        for ws in prev_tokens(token.clone()).filter_map(ast::Whitespace::cast) {
             let text = ws.syntax().text();
             if let Some(pos) = text.rfind('\n') {
                 let level = text[pos + 1..].chars().count() / 4;
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index b6c5de6588..5eee335458 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -527,11 +527,8 @@ pub mod tokens {
 
     use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken};
 
-    pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| {
-        SourceFile::parse(
-            "const C: <()>::Item = (1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p)\n;\n\n",
-        )
-    });
+    pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> =
+        Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2, !true, *p)\n;\n\n"));
 
     pub fn single_space() -> SyntaxToken {
         SOURCE_FILE
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 044e3e5e84..977eb81819 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -85,8 +85,9 @@ pub enum CommentPlacement {
 }
 
 impl CommentKind {
-    const BY_PREFIX: [(&'static str, CommentKind); 8] = [
+    const BY_PREFIX: [(&'static str, CommentKind); 9] = [
         ("/**/", CommentKind { shape: CommentShape::Block, doc: None }),
+        ("/***", CommentKind { shape: CommentShape::Block, doc: None }),
         ("////", CommentKind { shape: CommentShape::Line, doc: None }),
         ("///", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Outer) }),
         ("//!", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Inner) }),
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 9f28237ff3..a2c7f56b35 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -398,7 +398,7 @@ In general `rust-analyzer` is configured via LSP messages, which means that it's
 
 Some clients, such as <<vs-code,VS Code>> or <<coc-rust-analyzer,COC plugin in Vim>> provide `rust-analyzer` specific configuration UIs. Others may require you to know a bit more about the interaction with `rust-analyzer`.
 
-For the later category, it might help to know that the initial configuration is specified as a value of the `initializationOptions` field of the https://microsoft.github.io/language-server-protocol/specifications/specification-current/#initialize[`InitializeParams` message, in the LSP protocol].
+For the later category, it might help to know that the initial configuration is specified as a value of the `intializationOptions` field of the https://microsoft.github.io/language-server-protocol/specifications/specification-current/#initialize[`InitializeParams` message, in the LSP protocol].
 The spec says that the field type is `any?`, but `rust-analyzer` is looking for a JSON object that is constructed using settings from the list below.
 Name of the setting, ignoring the `rust-analyzer.` prefix, is used as a path, and value of the setting becomes the JSON property value.
 


Test patch:
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index d428758222..0516deaffd 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -147,12 +147,12 @@ fn doctest_apply_demorgan() {
         "apply_demorgan",
         r#####"
 fn main() {
-    if x != 4 ||$0 y < 3.14 {}
+    if x != 4 ||$0 !y {}
 }
 "#####,
         r#####"
 fn main() {
-    if !(x == 4 && !(y < 3.14)) {}
+    if !(x == 4 && y) {}
 }
 "#####,
     )
@@ -192,29 +192,6 @@ pub(crate) fn frobnicate() {}
     )
 }
 
-#[test]
-fn doctest_convert_for_to_iter_for_each() {
-    check_doc_test(
-        "convert_for_to_iter_for_each",
-        r#####"
-fn main() {
-    let x = vec![1, 2, 3];
-    for $0v in x {
-        let y = v * 2;
-    }
-}
-"#####,
-        r#####"
-fn main() {
-    let x = vec![1, 2, 3];
-    x.into_iter().for_each(|v| {
-        let y = v * 2;
-    });
-}
-"#####,
-    )
-}
-
 #[test]
 fn doctest_convert_integer_literal() {
     check_doc_test(