Repository: ruby-syntax-tree/syntax_tree (Ruby)

Issue:
Code causes syntax tree to crash
Looking at this issue https://github.com/ruby-syntax-tree/syntax_tree/issues/129 and it seems to work correctly from the CLI

example code: 
```
LongRecordName.transaction do
  LongRecordName
    .joins(:other_record)
    # rubocop:disable Rails/SkipsModelValidations

    .update_all(foo: 'bar')
end
```
CLI:
```
Error: Cannot find expected end
> 1 | LongRecordName.transaction do
    |                            ^
  2 |   LongRecordName
  3 |     .joins(:other_record)
  4 |     # rubocop:disable Rails/SkipsModelValidations
The listed files did not match the expected format.
```

However in vscode it crashes synax-tree and there is nothing indicating that something is wrong.  Is this the correct behavior?

VSCODE:
```
/Users/nix/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/syntax_tree-3.6.0/lib/syntax_tree/parser.rb:251:in `find_token': Cannot find expected end (SyntaxTree::Parser::ParseError)
	from /Users/nix/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/syntax_tree-3.6.0/lib/syntax_tree/parser.rb:1242:in `on_do_block'
	from /Users/nix/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/syntax_tree-3.6.0/lib/syntax_tree.rb:47:in `parse'
	from /Users/nix/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/syntax_tree-3.6.0/lib/syntax_tree.rb:47:in `parse'
	from /Users/nix/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/syntax_tree-3.6.0/lib/syntax_tree.rb:54:in `format'
	from /Users/nix/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/syntax_tree-3.6.0/lib/syntax_tree/language_server.rb:90:in `format'
	from /Users/nix/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/syntax_tree-3.6.0/lib/syntax_tree/language_server.rb:59:in `run'
	from /Users/nix/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/syntax_tree-3.6.0/lib/syntax_tree/cli.rb:410:in `run'
	from /Users/nix/.asdf/installs/ruby/3.1.2/lib/ruby/gems/3.1.0/gems/syntax_tree-3.6.0/exe/stree:9:in `<top (required)>'
	from /Users/nix/.asdf/installs/ruby/3.1.2/bin/stree:25:in `load'
	from /Users/nix/.asdf/installs/ruby/3.1.2/bin/stree:25:in `<main>'
[Info  - 5:29:20 PM] Connection to server got closed. Server will restart.
```


Discussion:
Yeah we could definitely improve the user experience here. I think honestly if it fails to parse it should just not try to format.

Want to take a crack at improving this behavior? It would involve fixing it here: https://github.com/ruby-syntax-tree/syntax_tree/blob/695918e84d5bdc912492db9013110c841da31109/lib/syntax_tree/language_server.rb#L58-L59.
It seemed like vscode-prettier handled this sort of stuff well.  SyntaxTree crashes on any syntax error with no UI warning.  Even simple things like this crash it instead of fixing.  Is this the intended behavior? 

`has_many :steps through: :user_topics`
No, the intended behavior is to not have it crash when there's a syntax error. Would you be able to attempt a PR? It would involve handling the parser error on that line I linked.
I guess i'm missing this answer.  Is the purpose of syntax tree to show the error too in vscode, or to try and fix it first?  I would assume on syntax error it would try to format and fix?  Sorry for all the questions.  
Syntax Tree can't recover from syntax errors because the underlying CRuby parser isn't error tolerant. It has no knowledge of how to fix those things.

In VSCode I haven't made any effort to show the error as well. At the moment that's beyond the scope of this plugin. I'd be happy to support it if you want to put up a PR, but my efforts are elsewhere at the moment.



Patch:
diff --git a/lib/syntax_tree/language_server.rb b/lib/syntax_tree/language_server.rb
index 16e94534..d2714b5c 100644
--- a/lib/syntax_tree/language_server.rb
+++ b/lib/syntax_tree/language_server.rb
@@ -56,10 +56,10 @@ def run
           store.delete(uri)
         in { method: "textDocument/formatting", id:, params: { textDocument: { uri: } } }
           contents = store[uri]
-          write(id: id, result: contents ? [format(store[uri], uri.split(".").last)] : nil)
+          write(id: id, result: contents ? format(contents, uri.split(".").last) : nil)
         in { method: "textDocument/inlayHint", id:, params: { textDocument: { uri: } } }
           contents = store[uri]
-          write(id: id, result: contents ? inlay_hints(store[uri]) : nil)
+          write(id: id, result: contents ? inlay_hints(contents) : nil)
         in { method: "syntaxTree/visualizing", id:, params: { textDocument: { uri: } } }
           write(id: id, result: PP.pp(SyntaxTree.parse(store[uri]), +""))
         in { method: %r{\$/.+} }
@@ -89,19 +89,25 @@ def capabilities
     def format(source, extension)
       text = SyntaxTree::HANDLERS[".#{extension}"].format(source, print_width)
 
-      {
-        range: {
-          start: {
-            line: 0,
-            character: 0
+      [
+        {
+          range: {
+            start: {
+              line: 0,
+              character: 0
+            },
+            end: {
+              line: source.lines.size + 1,
+              character: 0
+            }
           },
-          end: {
-            line: source.lines.size + 1,
-            character: 0
-          }
-        },
-        newText: text
-      }
+          newText: text
+        }
+      ]
+    rescue Parser::ParseError
+      # If there is a parse error, then we're not going to return any formatting
+      # changes for this source.
+      nil
     end
 
     def inlay_hints(source)


Test patch:
diff --git a/test/language_server_test.rb b/test/language_server_test.rb
index 31062e87..466bf737 100644
--- a/test/language_server_test.rb
+++ b/test/language_server_test.rb
@@ -120,6 +120,24 @@ def test_formatting
       end
     end
 
+    def test_formatting_failure
+      messages = [
+        Initialize.new(1),
+        TextDocumentDidOpen.new("file:///path/to/file.rb", "<>"),
+        TextDocumentFormatting.new(2, "file:///path/to/file.rb"),
+        Shutdown.new(3)
+      ]
+
+      case run_server(messages)
+      in [
+           { id: 1, result: { capabilities: Hash } },
+           { id: 2, result: },
+           { id: 3, result: {} }
+         ]
+        assert_nil(result)
+      end
+    end
+
     def test_formatting_print_width
       contents = "#{"a" * 40} + #{"b" * 40}\n"
       messages = [