Support unnamed variables

Fixes https://github.com/google/google-java-format/issues/978

PiperOrigin-RevId: 589179786
diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java
index 5a75afa..ea967b3 100644
--- a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java
+++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java
@@ -3563,7 +3563,7 @@
           if (receiverExpression.isPresent()) {
             scan(receiverExpression.get(), null);
           } else {
-            visit(name);
+            variableName(name);
           }
           builder.op(op);
         }
@@ -3606,6 +3606,10 @@
     return baseDims;
   }
 
+  protected void variableName(Name name) {
+    visit(name);
+  }
+
   private void maybeAddDims(Deque<List<? extends AnnotationTree>> annotations) {
     maybeAddDims(new ArrayDeque<>(), annotations);
   }
@@ -3696,7 +3700,7 @@
       builder.breakOp(" ");
       builder.open(ZERO);
       maybeAddDims(dims);
-      visit(fragment.getName());
+      variableName(fragment.getName());
       maybeAddDims(dims);
       ExpressionTree initializer = fragment.getInitializer();
       if (initializer != null) {
diff --git a/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java
index ad936ca..6abb93b 100644
--- a/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java
+++ b/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java
@@ -23,6 +23,7 @@
 import com.sun.source.tree.ExpressionTree;
 import com.sun.source.tree.PatternCaseLabelTree;
 import com.sun.source.tree.PatternTree;
+import javax.lang.model.element.Name;
 
 /**
  * Extends {@link Java17InputAstVisitor} with support for AST nodes that were added or modified in
@@ -76,4 +77,13 @@
     token(")");
     return null;
   }
+
+  @Override
+  protected void variableName(Name name) {
+    if (name.isEmpty()) {
+      token("_");
+    } else {
+      visit(name);
+    }
+  }
 }
diff --git a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java
index 67f0eec..ffcfd21 100644
--- a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java
+++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java
@@ -53,7 +53,13 @@
           .putAll(16, "I588")
           .putAll(17, "I683", "I684", "I696")
           .putAll(
-              21, "SwitchGuardClause", "SwitchRecord", "SwitchDouble", "SwitchUnderscore", "I880")
+              21,
+              "SwitchGuardClause",
+              "SwitchRecord",
+              "SwitchDouble",
+              "SwitchUnderscore",
+              "I880",
+              "Unnamed")
           .build();
 
   @Parameters(name = "{index}: {0}")
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.input
new file mode 100644
index 0000000..6c5efd1
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.input
@@ -0,0 +1,44 @@
+class Unnamed {
+  {
+    int acc = 0;
+    for (Order _ : orders) {
+      if (acc < LIMIT) { 
+        acc++;
+      }
+    }
+
+    for (int i = 0, _ = sideEffect(); i < 10; i++) { }
+
+    Queue<Integer> q = null;
+    while (q.size() >= 3) {
+      var x = q.remove();
+      var y = q.remove();
+      var _ = q.remove();
+      new Point(x, y);
+    }
+
+    while (q.size() >= 3) {
+      var x = q.remove();
+      var _ = q.remove();
+      var _ = q.remove(); 
+      new Point(x, 0) ;
+    }
+
+    String s = null;
+    try { 
+      int i = Integer.parseInt(s);
+    } catch (NumberFormatException _) { 
+      System.out.println("Bad number: " + s);
+    }
+
+    try { doSomething(); } 
+    catch (Exception _) { doSomething(); } 
+    catch (Throwable _) { doSomething(); }
+
+    try (var _ = ScopedContext.acquire()) {
+      doSomething();
+    }
+
+    stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA"));
+  }
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.output
new file mode 100644
index 0000000..84d8f87
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.output
@@ -0,0 +1,48 @@
+class Unnamed {
+  {
+    int acc = 0;
+    for (Order _ : orders) {
+      if (acc < LIMIT) {
+        acc++;
+      }
+    }
+
+    for (int i = 0, _ = sideEffect(); i < 10; i++) {}
+
+    Queue<Integer> q = null;
+    while (q.size() >= 3) {
+      var x = q.remove();
+      var y = q.remove();
+      var _ = q.remove();
+      new Point(x, y);
+    }
+
+    while (q.size() >= 3) {
+      var x = q.remove();
+      var _ = q.remove();
+      var _ = q.remove();
+      new Point(x, 0);
+    }
+
+    String s = null;
+    try {
+      int i = Integer.parseInt(s);
+    } catch (NumberFormatException _) {
+      System.out.println("Bad number: " + s);
+    }
+
+    try {
+      doSomething();
+    } catch (Exception _) {
+      doSomething();
+    } catch (Throwable _) {
+      doSomething();
+    }
+
+    try (var _ = ScopedContext.acquire()) {
+      doSomething();
+    }
+
+    stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA"));
+  }
+}