<NME> ElmSmartEnterProcessorTest.kt
<BEF> package org.elm.ide.typing

import com.intellij.openapi.actionSystem.IdeActions
import org.elm.lang.ElmTestBase
import org.intellij.lang.annotations.Language

class ElmSmartEnterProcessorTest : ElmTestBase() {
    fun `test fix missing case branches`() = doTest("""
type Foo = Bar | Baz | Qux

foo : Foo -> ()
foo : Foo -> ()
foo it =
    case it of{-caret-}
""", """
type Foo = Bar | Baz | Qux

foo : Foo -> ()
foo it =
    case it of
            {-caret-}

        Baz ->
            

        Qux ->

""")


    private fun doTest(@Language("Elm") before: String, @Language("Elm") after: String) =
            checkByText(before, after) {
                myFixture.performEditorAction(IdeActions.ACTION_EDITOR_COMPLETE_STATEMENT)
            }
}
""", """
type Foo = Bar | Baz | Qux

foo : Foo -> ()
foo it =
  case it of
    Bar ->
      {-caret-}

    Baz ->
      --EOL

    Qux ->

""".replace("--EOL", "")) {
        myFixture.file.indentStyle.INDENT_SIZE = 2
        myFixture.performEditorAction(IdeActions.ACTION_EDITOR_COMPLETE_STATEMENT)
    }

    private fun doTest(@Language("Elm") before: String, @Language("Elm") after: String) {
        // We use the --EOL marker to avoid editors trimming trailing whitespace, which is
        // significant for this test.
        checkByText(before, after.replace("--EOL", "")) {
            myFixture.performEditorAction(IdeActions.ACTION_EDITOR_COMPLETE_STATEMENT)
        }
    }
}

<MSG> Support more expression types in Smart Enter Processor (#205)

Add "smart-enter" support for

- Individual case branches
- if/then expressions
- let/in expressions
- value declarations (top level and inside let/in expressions)

The value declaration support is a bit hacky due to the way our grammar is structured. The `ValueDeclaration` rule pins on `=`, so a top-level identifier just parses as an error. We can't just pin on the id, since that breaks type annotations. We can change move `TypeAnnotation` to be higher priority than `ValueDeclaration`, but that still causes some partial programs to parse weirdly. Since there's no `VIRTUAL_OPEN_DECL` token, we don't know whether an id is on a line by itself, so sometimes a `ValueDeclaration` would start on the same line as other code.

There's probably a solution that involves moving the annotation and declaration into the same rule, but I'm not going to work on that for now.
<DFF> @@ -5,7 +5,7 @@ import org.elm.lang.ElmTestBase
 import org.intellij.lang.annotations.Language
 
 class ElmSmartEnterProcessorTest : ElmTestBase() {
-    fun `test fix missing case branches`() = doTest("""
+    fun `test case expression`() = doTest("""
 type Foo = Bar | Baz | Qux
 
 foo : Foo -> ()
@@ -21,15 +21,214 @@ foo it =
             {-caret-}
 
         Baz ->
-            
+            --EOL
 
         Qux ->
 
 """)
 
+    fun `test single case branch `() = doTest("""
+type Foo = Bar | Baz | Qux
+
+foo : Foo -> ()
+foo it =
+    case it of
+        Bar{-caret-}
+""", """
+type Foo = Bar | Baz | Qux
+
+foo : Foo -> ()
+foo it =
+    case it of
+        Bar ->
+            {-caret-}
+""")
+
+    fun `test let with no body`() = doTest("""
+foo =
+    let{-caret-}
+""", """
+foo =
+    let
+        {-caret-}
+    in
+        --EOL
+""")
+
+    fun `test nested let with no body and no parent in-keyword`() = doTest("""
+foo =
+    let
+        bar =
+            let{-caret-}
+""", """
+foo =
+    let
+        bar =
+            let
+                {-caret-}
+            in
+                --EOL
+""")
+
+    fun `test if with no then`() = doTest("""
+foo =
+    if True{-caret-}
+""", """
+foo =
+    if True then
+        {-caret-}
+    else
+        --EOL
+""")
+
+    fun `test if with no body`() = doTest("""
+foo =
+    if True then{-caret-}
+""", """
+foo =
+    if True then
+        {-caret-}
+    else
+        --EOL
+""")
+
+    fun `test if with no body, caret before then`() = doTest("""
+foo =
+    if True{-caret-} then
+""", """
+foo =
+    if True then
+        {-caret-}
+    else
+        --EOL
+""")
+
+    fun `test if with no else`() = doTest("""
+foo =
+    if True then
+        1{-caret-}
+""", """
+foo =
+    if True then
+        1
+    else
+        {-caret-}
+""")
+
+    fun `test chained if with no then`() = doTest("""
+foo =
+    if True then
+        1
+    else if True{-caret-}
+""", """
+foo =
+    if True then
+        1
+    else if True then
+        {-caret-}
+    else
+        --EOL
+""")
+
+    fun `test chained if with no else`() = doTest("""
+foo =
+    if True then
+        1
+    else if True then
+        2{-caret-}
+""", """
+foo =
+    if True then
+        1
+    else if True then
+        2
+    else
+        {-caret-}
+""")
+
+    // Need module statement to prevent `foo` from being parsed inside an error element
+    fun `test function declaration with no params`() = doTest("""
+module Main exposing (..)
+foo{-caret-}
+""", """
+module Main exposing (..)
+foo =
+    {-caret-}
+""")
+
+    fun `test function declaration with params`() = doTest("""
+module Main exposing (..)
+foo a b{-caret-}
+""", """
+module Main exposing (..)
+foo a b =
+    {-caret-}
+""")
+
+    fun `test nested function declaration with no params`() = doTest("""
+foo =
+    let
+        bar{-caret-}
+    in
+        ()
+""", """
+foo =
+    let
+        bar =
+            {-caret-}
+    in
+        ()
+""")
+
+    fun `test nested function declaration with params`() = doTest("""
+foo =
+    let
+        bar a b{-caret-}
+    in
+        ()
+""", """
+foo =
+    let
+        bar a b =
+            {-caret-}
+    in
+        ()
+""")
+
+    fun `test nested tuple destructuring with no params`() = doTest("""
+foo =
+    let
+        (a, b){-caret-}
+    in
+        ()
+""", """
+foo =
+    let
+        (a, b) =
+            {-caret-}
+    in
+        ()
+""")
+
+    fun `test nested record destructuring with no params`() = doTest("""
+foo =
+    let
+        {a, b}{-caret-}
+    in
+        ()
+""", """
+foo =
+    let
+        {a, b} =
+            {-caret-}
+    in
+        ()
+""")
 
     private fun doTest(@Language("Elm") before: String, @Language("Elm") after: String) =
-            checkByText(before, after) {
+            // We use the --EOL marker to avoid editors trimming trailing whitespace, which is
+            // significant for this test.
+            checkByText(before, after.replace("--EOL", "")) {
                 myFixture.performEditorAction(IdeActions.ACTION_EDITOR_COMPLETE_STATEMENT)
             }
 }

Support more expression types in Smart Enter Processor (#205)