Repository: Shopify/ruby-lsp (Ruby)

Issue:
On type formatting incorrectly adds `end` after hitting Enter key at the end of method's arguments when it has a trailing comma
### Description

### Environment

* Ruby LSP VS Code extension version: v0.7.12
* VS Code version: Version: 1.91.1 (Universal)
* Ruby version: ruby 3.1.6p260 (2024-05-29 revision a777087be6) [arm64-darwin24]
* Ruby version manager version: asdf v0.12.0
* Project setup: workspace

### Reproduction steps

1. Open VSCode with the Ruby LSP extension enabled.
2. Open a blank Ruby file.
3. Write the following:

```rb
link_to "Something",
  some_path,
  class: "some_class",
```

4. Hit enter after the last comma:

Expected:

```rb
link_to "Something",
  some_path,
  class: "some_class",
  # cursor here
```

Actual:

```rb
link_to "Something",
  some_path,
  class: "some_class",
    # cursor here
  end
```

This also happens when doing it on a single line:

1. Write the following:

```rb
link_to "Something", some_path, class: "some_class",
```

2. Hit enter after the last comma.

Expected:

```rb
link_to "Something", some_path, class: "some_class",
# cursor here
``` 

Actual:

```rb
link_to "Something", some_path, class: "some_class",
  # cursor here
end
```


Discussion:
Thank you for the bug report! The attached PR should fix this.



Patch:
diff --git a/lib/ruby_lsp/requests/on_type_formatting.rb b/lib/ruby_lsp/requests/on_type_formatting.rb
index 0831388f82..0ae5793201 100644
--- a/lib/ruby_lsp/requests/on_type_formatting.rb
+++ b/lib/ruby_lsp/requests/on_type_formatting.rb
@@ -32,8 +32,9 @@ def provider
 
       END_REGEXES = T.let(
         [
-          /\b(if|unless|for|while|class|module|until|def|case)\b.*/,
-          /.*\s\bdo\b/,
+          /\b(if|unless|for|while|until)\b($|\s|\()/,
+          /\b(class|module|def|case)\b($|\s)/,
+          /.*\s\bdo\b($|\s)/,
         ],
         T::Array[Regexp],
       )


Test patch:
diff --git a/test/requests/on_type_formatting_test.rb b/test/requests/on_type_formatting_test.rb
index 137e3d06a5..2c23e5c6ae 100644
--- a/test/requests/on_type_formatting_test.rb
+++ b/test/requests/on_type_formatting_test.rb
@@ -687,4 +687,62 @@ def test_includes_snippets_on_vscode_insiders
     ]
     assert_equal(expected_edits.to_json, T.must(edits).to_json)
   end
+
+  def test_does_not_confuse_class_parameter_with_keyword
+    document = RubyLsp::RubyDocument.new(source: +"", version: 1, uri: URI("file:///fake.rb"))
+
+    document.push_edits(
+      [{
+        range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
+        text: "link_to :something,\n  class: 'foo',\n  ",
+      }],
+      version: 2,
+    )
+    document.parse
+
+    edits = RubyLsp::Requests::OnTypeFormatting.new(
+      document,
+      { line: 2, character: 4 },
+      "\n",
+      "Visual Studio Code - Insiders",
+    ).perform
+
+    assert_empty(edits)
+  end
+
+  def test_allows_end_completion_when_parenthesis_are_present
+    document = RubyLsp::RubyDocument.new(source: +"", version: 1, uri: URI("file:///fake.rb"))
+
+    document.push_edits(
+      [{
+        range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
+        text: "if(\n  ",
+      }],
+      version: 2,
+    )
+    document.parse
+
+    edits = RubyLsp::Requests::OnTypeFormatting.new(
+      document,
+      { line: 1, character: 2 },
+      "\n",
+      "Visual Studio Code - Insiders",
+    ).perform
+
+    expected_edits = [
+      {
+        range: { start: { line: 1, character: 2 }, end: { line: 1, character: 2 } },
+        newText: "\n",
+      },
+      {
+        range: { start: { line: 1, character: 2 }, end: { line: 1, character: 2 } },
+        newText: "end",
+      },
+      {
+        range: { start: { line: 1, character: 2 }, end: { line: 1, character: 2 } },
+        newText: "$0",
+      },
+    ]
+    assert_equal(expected_edits.to_json, T.must(edits).to_json)
+  end
 end