Repository: prettier/plugin-ruby (javascript)

Issue:
Syntax error created with `for _ in _ do` loops inside of a block paramter
## Metadata

- Operating system: MacOS 11.6
- Ruby version: 2.6.8p205
- Node version: 14.17.3
- `@prettier/plugin-ruby` or `prettier` gem version: latest
- Options:
  - `rubyArrayLiteral` - `true`
  - `rubyHashLabel` - `true`
  - `rubyModifier` - `true`
  - `rubyNetcatCommand` - `null`
  - `rubySingleQuote` - `true`
  - `rubyToProc` - `false`
  - `trailingComma` - `"none"`

## Input

```ruby
A = [1,2,3]

def helper(&block); end

helper do
  # comment
  for val in A do
    puts val
  end
end

```

## Current output

```ruby
A = [1, 2, 3]

def helper(&block); end

helper# comment
 do
  for val in A
    puts val
  end
end
```

## Expected output

Same as input

------------------------------

Error does not occur if the optional `do` is removed from the `for` loop line or if there is no comment.


Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
 
 ## [Unreleased]
 
+### Changed
+
+- [#958](https://github.com/prettier/plugin-ruby/issues/958) - mharris-figma, kddnewton - Handle optional `do` keyword in `for` loop expressions.
+
 ## [1.6.1] - 2021-06-30
 
 ### Changed
diff --git a/src/ruby/parser.rb b/src/ruby/parser.rb
--- a/src/ruby/parser.rb
+++ b/src/ruby/parser.rb
@@ -1273,15 +1273,22 @@ def on_fndptn(const, presplat, args, postsplat)
   # loop. It accepts as arguments an ident which is the iterating variable,
   # an enumerable for that which is being enumerated, and a stmts event that
   # represents the statements inside the for loop.
-  def on_for(ident, enumerable, stmts)
+  def on_for(ident, enum, stmts)
     beging = find_scanner_event(:@kw, 'for')
     ending = find_scanner_event(:@kw, 'end')
 
-    stmts.bind(enumerable[:ec], ending[:sc])
+    # Consume the do keyword if it exists so that it doesn't get confused for
+    # some other block
+    do_event = find_scanner_event(:@kw, 'do', consume: false)
+    if do_event && do_event[:sc] > enum[:ec] && do_event[:ec] < ending[:sc]
+      scanner_events.delete(do_event)
+    end
+
+    stmts.bind((do_event || enum)[:ec], ending[:sc])
 
     {
       type: :for,
-      body: [ident, enumerable, stmts],
+      body: [ident, enum, stmts],
       sl: beging[:sl],
       sc: beging[:sc],
       el: ending[:el],


Test patch:
diff --git a/test/js/ruby/nodes/blocks.test.ts b/test/js/ruby/nodes/blocks.test.ts
--- a/test/js/ruby/nodes/blocks.test.ts
+++ b/test/js/ruby/nodes/blocks.test.ts
@@ -82,6 +82,28 @@ describe("blocks", () => {
 
       return expect(content).toMatchFormat();
     });
+
+    test("optional do keyword", () => {
+      const content = ruby(`
+        a do
+          # comment
+          for b in c do
+            puts b
+          end
+        end
+      `);
+
+      const expected = ruby(`
+        a do
+          # comment
+          for b in c
+            puts b
+          end
+        end
+      `);
+
+      return expect(content).toChangeFormat(expected);
+    });
   });
 
   // from ruby test/ruby/test_call.rb