Add a check for missing braces

PiperOrigin-RevId: 368113953
diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/MissingBraces.java b/core/src/main/java/com/google/errorprone/bugpatterns/MissingBraces.java
new file mode 100644
index 0000000..55ae172
--- /dev/null
+++ b/core/src/main/java/com/google/errorprone/bugpatterns/MissingBraces.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2021 The Error Prone Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.errorprone.bugpatterns;
+
+import static com.google.errorprone.matchers.Description.NO_MATCH;
+
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.BugPattern.LinkType;
+import com.google.errorprone.BugPattern.SeverityLevel;
+import com.google.errorprone.BugPattern.StandardTags;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker.DoWhileLoopTreeMatcher;
+import com.google.errorprone.bugpatterns.BugChecker.EnhancedForLoopTreeMatcher;
+import com.google.errorprone.bugpatterns.BugChecker.ForLoopTreeMatcher;
+import com.google.errorprone.bugpatterns.BugChecker.IfTreeMatcher;
+import com.google.errorprone.bugpatterns.BugChecker.WhileLoopTreeMatcher;
+import com.google.errorprone.fixes.SuggestedFix;
+import com.google.errorprone.matchers.Description;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.DoWhileLoopTree;
+import com.sun.source.tree.EnhancedForLoopTree;
+import com.sun.source.tree.ForLoopTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.WhileLoopTree;
+
+/** A {@link BugChecker}; see the associated {@link BugPattern} annotation for details. */
+@BugPattern(
+    name = "MissingBraces",
+    summary =
+        "The Google Java Style Guide requires braces to be used with if, else, for, do and while"
+            + " statements, even when the body is empty or contains only a single statement.",
+    severity = SeverityLevel.SUGGESTION,
+    tags = StandardTags.STYLE,
+    linkType = LinkType.CUSTOM,
+    link = "https://google.github.io/styleguide/javaguide.html#s4.1.1-braces-always-used"
+    )
+public class MissingBraces extends BugChecker
+    implements IfTreeMatcher,
+        ForLoopTreeMatcher,
+        DoWhileLoopTreeMatcher,
+        WhileLoopTreeMatcher,
+        EnhancedForLoopTreeMatcher {
+
+  @Override
+  public Description matchIf(IfTree tree, VisitorState state) {
+    check(tree.getThenStatement(), state);
+    if (tree.getElseStatement() != null
+        && !tree.getElseStatement().getKind().equals(Tree.Kind.IF)) {
+      check(tree.getElseStatement(), state);
+    }
+    return NO_MATCH;
+  }
+
+  @Override
+  public Description matchDoWhileLoop(DoWhileLoopTree tree, VisitorState state) {
+    check(tree.getStatement(), state);
+    return NO_MATCH;
+  }
+
+  @Override
+  public Description matchForLoop(ForLoopTree tree, VisitorState state) {
+    check(tree.getStatement(), state);
+    return NO_MATCH;
+  }
+
+  @Override
+  public Description matchEnhancedForLoop(EnhancedForLoopTree tree, VisitorState state) {
+    check(tree.getStatement(), state);
+    return NO_MATCH;
+  }
+
+  @Override
+  public Description matchWhileLoop(WhileLoopTree tree, VisitorState state) {
+    check(tree.getStatement(), state);
+    return NO_MATCH;
+  }
+
+  void check(StatementTree tree, VisitorState state) {
+    if (tree != null && !(tree instanceof BlockTree)) {
+      state.reportMatch(
+          describeMatch(
+              tree, SuggestedFix.builder().prefixWith(tree, "{").postfixWith(tree, "}").build()));
+    }
+  }
+}
diff --git a/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java b/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java
index 50e8fa6..177024d 100644
--- a/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java
+++ b/core/src/main/java/com/google/errorprone/scanner/BuiltInCheckerSuppliers.java
@@ -197,6 +197,7 @@
 import com.google.errorprone.bugpatterns.MathRoundIntLong;
 import com.google.errorprone.bugpatterns.MemberName;
 import com.google.errorprone.bugpatterns.MethodCanBeStatic;
+import com.google.errorprone.bugpatterns.MissingBraces;
 import com.google.errorprone.bugpatterns.MissingCasesInEnumSwitch;
 import com.google.errorprone.bugpatterns.MissingDefault;
 import com.google.errorprone.bugpatterns.MissingFail;
@@ -994,6 +995,7 @@
           LongLiteralLowerCaseSuffix.class,
           MemberName.class,
           MethodCanBeStatic.class,
+          MissingBraces.class,
           MissingDefault.class,
           MixedArrayDimensions.class,
           MoreThanOneQualifier.class,
diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/MissingBracesTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/MissingBracesTest.java
new file mode 100644
index 0000000..961e30a
--- /dev/null
+++ b/core/src/test/java/com/google/errorprone/bugpatterns/MissingBracesTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2021 The Error Prone Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.errorprone.bugpatterns;
+
+import com.google.errorprone.BugCheckerRefactoringTestHelper;
+import com.google.errorprone.CompilationTestHelper;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** {@link MissingBraces}Test */
+@RunWith(JUnit4.class)
+public class MissingBracesTest {
+  @Test
+  public void positive() {
+    BugCheckerRefactoringTestHelper.newInstance(MissingBraces.class, getClass())
+        .addInputLines(
+            "Test.java",
+            "import java.util.List;",
+            "class Test {",
+            "  void f(boolean x, List<Integer> is) {",
+            "    if (x) throw new AssertionError();",
+            "    else x = !x;",
+            "    while (x) g();",
+            "    do g(); while (x);",
+            "    for ( ; x; ) g();",
+            "    for (int i : is) g();",
+            "  }",
+            "  void g() {}",
+            "}")
+        .addOutputLines(
+            "Test.java",
+            "import java.util.List;",
+            "class Test {",
+            "  void f(boolean x, List<Integer> is) {",
+            "    if (x) { throw new AssertionError(); }",
+            "    else { x = !x; }",
+            "    while (x) { g(); }",
+            "    do { g(); } while (x);",
+            "    for ( ; x; ) { g(); }",
+            "    for (int i : is) { g(); }",
+            "  }",
+            "  void g() {}",
+            "}")
+        .doTest();
+  }
+
+  @Test
+  public void negative() {
+    CompilationTestHelper.newInstance(MissingBraces.class, getClass())
+        .addSourceLines(
+            "Test.java",
+            "class Test {",
+            "  void f(boolean x) {",
+            "    if (x) { g(); }",
+            "    else { g(); }",
+            "    while (x) { g(); }",
+            "    do { g(); } while (x);",
+            "    for (;;) { g(); }",
+            "  }",
+            "  void g() {}",
+            "}")
+        .doTest();
+  }
+}