Repository: intellij-rust/intellij-rust
Issue: Join lines action doesn't work for doc comments with 2020.1

<!--
Hello and thank you for the issue!
If you would like to report a bug, we have added some points below that you can fill out.
Feel free to remove all the irrelevant text to request a new feature.
-->

## Environment

* **IntelliJ Rust plugin version:** 0.2.114.SNAPSHOT-201-dev
* **Rust toolchain version:** 1.40.0 (73528e339 2019-12-16) x86_64-apple-darwin
* **IDE name and version:** CLion 2020.1 EAP (CL-201.3803.17)
* **Operating system:** macOS 10.13.6

## Problem description
Join lines action (`Ctrl+Shift+J`) incorrectly joins doc comment lines with 201 platform.

For the following code 
```rust
//! line 1<caret>
//! line 2

fn main() {}
```

it produces
```rust
//! line 1 //! line 2

fn main() {}
```

instead of
```rust
//! line 1 line 2

fn main() {}
```

The relevant tests were temporarily [disabled](https://github.com/intellij-rust/intellij-rust/blob/c113c6b26334873bf11cf8c03ce3817b6b075d1c/src/test/kotlin/org/rust/ide/actions/RsJoinLinesHandlerTest.kt#L94-L116).

Looks like [RsJoinLinesHandler](https://github.com/intellij-rust/intellij-rust/blob/e494c90ae5b0e2f407d79b5ae4262bf27c62ef37/src/main/kotlin/org/rust/ide/actions/RsJoinLinesHandler.kt) is not called by platform to make proper code transformation


Changed files:
src/main/kotlin/org/rust/ide/actions/RsJoinRawLinesHandler.kt, src/test/kotlin/org/rust/ide/actions/RsJoinLinesHandlerTest.kt, src/main/kotlin/org/rust/ide/actions/RsJoinLinesHandler.kt

diff --git a/src/main/kotlin/org/rust/ide/actions/RsJoinLinesHandler.kt b/src/main/kotlin/org/rust/ide/actions/RsJoinLinesHandler.kt
index ee445cbe0..b3827bfa0 100644
--- a/src/main/kotlin/org/rust/ide/actions/RsJoinLinesHandler.kt
+++ b/src/main/kotlin/org/rust/ide/actions/RsJoinLinesHandler.kt
@@ -13,8 +13,6 @@ import com.intellij.psi.PsiFile
 import com.intellij.util.text.CharSequenceSubSequence
 import org.rust.ide.formatter.impl.CommaList
 import org.rust.ide.typing.endsWithUnescapedBackslash
-import org.rust.lang.core.parser.RustParserDefinition.Companion.INNER_EOL_DOC_COMMENT
-import org.rust.lang.core.parser.RustParserDefinition.Companion.OUTER_EOL_DOC_COMMENT
 import org.rust.lang.core.psi.RS_STRING_LITERALS
 import org.rust.lang.core.psi.RsElementTypes.COMMA
 import org.rust.lang.core.psi.RsFile
@@ -40,9 +38,6 @@ class RsJoinLinesHandler : JoinLinesHandlerDelegate {
             in RS_STRING_LITERALS ->
                 joinStringLiteral(document, offsetNear, end)
 
-            INNER_EOL_DOC_COMMENT, OUTER_EOL_DOC_COMMENT ->
-                joinLineDocComment(document, offsetNear, end)
-
             else -> CANNOT_JOIN
         }
     }
@@ -61,15 +56,6 @@ class RsJoinLinesHandler : JoinLinesHandlerDelegate {
         return CANNOT_JOIN
     }
 
-    // Normally this is handled by `CodeDocumentationAwareCommenter`, but Rust have different styles
-    // of documentation comments, so we handle this manually.
-    private fun joinLineDocComment(document: Document, offsetNear: Int, end: Int): Int {
-        val prefix = document.charsSequence.subSequence(end, end + 3).toString()
-        if (prefix != "///" && prefix != "//!") return CANNOT_JOIN
-        document.deleteString(offsetNear + 1, end + prefix.length)
-        return offsetNear + 1
-    }
-
     private fun joinStringLiteral(document: Document, offsetNear: Int, end: Int): Int {
         val text = document.charsSequence
 
diff --git a/src/main/kotlin/org/rust/ide/actions/RsJoinRawLinesHandler.kt b/src/main/kotlin/org/rust/ide/actions/RsJoinRawLinesHandler.kt
index df0bad9af..5a5d29854 100644
--- a/src/main/kotlin/org/rust/ide/actions/RsJoinRawLinesHandler.kt
+++ b/src/main/kotlin/org/rust/ide/actions/RsJoinRawLinesHandler.kt
@@ -9,6 +9,7 @@ import com.intellij.codeInsight.editorActions.JoinLinesHandlerDelegate.CANNOT_JO
 import com.intellij.codeInsight.editorActions.JoinRawLinesHandlerDelegate
 import com.intellij.openapi.editor.Document
 import com.intellij.psi.PsiFile
+import org.rust.lang.core.parser.RustParserDefinition
 import org.rust.lang.core.psi.*
 import org.rust.lang.core.psi.RsElementTypes.COMMA
 import org.rust.lang.core.psi.RsElementTypes.LBRACE
@@ -23,10 +24,32 @@ class RsJoinRawLinesHandler : JoinRawLinesHandlerDelegate {
         if (file !is RsFile) return CANNOT_JOIN
         if (start == 0) return CANNOT_JOIN
 
-        return tryJoinSingleExpressionBlock(file, start)
+        val tryJoinSingleExpressionBlock = tryJoinSingleExpressionBlock(file, start)
+        if (tryJoinSingleExpressionBlock != CANNOT_JOIN) return tryJoinSingleExpressionBlock
+
+        val leftPsi = file.findElementAt(start) ?: return CANNOT_JOIN
+        val rightPsi = file.findElementAt(end) ?: return CANNOT_JOIN
+
+        if (leftPsi != rightPsi) return CANNOT_JOIN
+
+        return when (leftPsi.elementType) {
+            RustParserDefinition.INNER_EOL_DOC_COMMENT, RustParserDefinition.OUTER_EOL_DOC_COMMENT ->
+                joinLineDocComment(document, start, end)
+
+            else -> CANNOT_JOIN
+        }
+    }
+
+    // Normally this is handled by `CodeDocumentationAwareCommenter`, but Rust have different styles
+    // of documentation comments, so we handle this manually.
+    private fun joinLineDocComment(document: Document, start: Int, end: Int): Int {
+        val prefix = document.charsSequence.subSequence(end, end + 3).toString()
+        if (prefix != "///" && prefix != "//!") return CANNOT_JOIN
+        document.deleteString(start, end + prefix.length)
+        return start
     }
 
-    fun tryJoinSingleExpressionBlock(file: RsFile, start: Int): Int {
+    private fun tryJoinSingleExpressionBlock(file: RsFile, start: Int): Int {
         val lbrace = file.findElementAt(start - 1)!!
         if (lbrace.elementType != LBRACE) return CANNOT_JOIN
 
diff --git a/src/test/kotlin/org/rust/ide/actions/RsJoinLinesHandlerTest.kt b/src/test/kotlin/org/rust/ide/actions/RsJoinLinesHandlerTest.kt
index 292a14e45..1f56d40c4 100644
--- a/src/test/kotlin/org/rust/ide/actions/RsJoinLinesHandlerTest.kt
+++ b/src/test/kotlin/org/rust/ide/actions/RsJoinLinesHandlerTest.kt
@@ -5,11 +5,8 @@
 
 package org.rust.ide.actions
 
-import com.intellij.openapi.application.ApplicationInfo
-import com.intellij.openapi.util.BuildNumber
-
 class RsJoinLinesHandlerTest : RsJoinLinesHandlerTestBase() {
-    fun `test empty file`() = doTestRaw ("/*caret*/", "/*caret*/")
+    fun `test empty file`() = doTestRaw("/*caret*/", "/*caret*/")
 
     fun `test blank file1`() = doTestRaw("/*caret*/\\n\\n", "/*caret*/\\n")
     fun `test blank file2`() = doTestRaw("\\n/*caret*/\\n", "\\n/*caret*/")
@@ -91,29 +88,21 @@ class RsJoinLinesHandlerTest : RsJoinLinesHandlerTestBase() {
         }
     """)
 
-    fun `test outer doc comment`() {
-        // fixme on 2020.1
-        if (ApplicationInfo.getInstance().build > BuildNumber.fromString("193")!!) return
-        doTest("""
-            /// Hello<caret>
-            /// Docs
-            fn foo() {}
-        """, """
-            /// Hello<caret> Docs
-            fn foo() {}
-        """)
-    }
+    fun `test outer doc comment`() = doTest("""
+        /// Hello<caret>
+        /// Docs
+        fn foo() {}
+    """, """
+        /// Hello<caret> Docs
+        fn foo() {}
+    """)
 
-    fun `test inner doc comment`() {
-        // fixme on 2020.1
-        if (ApplicationInfo.getInstance().build > BuildNumber.fromString("193")!!) return
-        doTest("""
-            //! Hello<caret>
-            //! Docs
-        """, """
-            //! Hello<caret> Docs
-        """)
-    }
+    fun `test inner doc comment`() = doTest("""
+        //! Hello<caret>
+        //! Docs
+    """, """
+        //! Hello<caret> Docs
+    """)
 
     fun `test outer doc comment not comment`() = doTest("""
         /// Hello<caret>
@@ -131,7 +120,7 @@ class RsJoinLinesHandlerTest : RsJoinLinesHandlerTestBase() {
                     bar: 42,
                 };</selection>
             }
-        ""","""
+        """, """
             struct S { foo: i32, bar: i32 }
             fn main() {
                 let _ = S { foo: 42, bar: 42 };
@@ -145,7 +134,7 @@ class RsJoinLinesHandlerTest : RsJoinLinesHandlerTestBase() {
                 foo: 42,
             };
         }
-    ""","""
+    """, """
         struct S { foo: i32 }
         fn main() {
             let _ = S {/*caret*/ foo: 42,
@@ -159,7 +148,7 @@ class RsJoinLinesHandlerTest : RsJoinLinesHandlerTestBase() {
             let _ = S { foo: 42,/*caret*/
              };
         }
-    ""","""
+    """, """
         struct S { foo: i32 }
         fn main() {
             let _ = S { foo: 42/*caret*/ };
@@ -174,7 +163,7 @@ class RsJoinLinesHandlerTest : RsJoinLinesHandlerTestBase() {
                 bar: /*caret*/42,
              };
         }
-    ""","""
+    """, """
         struct S { foo: i32, bar: i32 }
         fn main() {
             let _ = S {
@@ -186,21 +175,21 @@ class RsJoinLinesHandlerTest : RsJoinLinesHandlerTestBase() {
     fun `test remove comma struct definition`() = doTest("""
         /*caret*/struct S { foo: i32,
         }
-    ""","""
+    """, """
         struct S { foo: i32/*caret*/ }
     """)
 
     fun `test remove comma tuple struct definition`() = doTest("""
         /*caret*/struct S(i32,
         );
-    ""","""
+    """, """
         struct S(i32/*caret*/);
     """)
 
     fun `test remove comma function parameter`() = doTest("""
         /*caret*/fn foo (foo: i32,
         ) {}
-    ""","""
+    """, """
         fn foo (foo: i32/*caret*/) {}
     """)
 
@@ -209,7 +198,7 @@ class RsJoinLinesHandlerTest : RsJoinLinesHandlerTestBase() {
             foo(1,/*caret*/
             )
         }
-    ""","""
+    """, """
         fn main() {
             foo(1/*caret*/)
         }