Allow to not specify the value of variable (in expression "var_name =")

Closes #10753.

PiperOrigin-RevId: 294429500
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/parser/NinjaParserStep.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/parser/NinjaParserStep.java
index 67408d3..c625dc5 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/parser/NinjaParserStep.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/parser/NinjaParserStep.java
@@ -17,7 +17,6 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Ascii;
 import com.google.common.base.Preconditions;
-import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -49,17 +48,17 @@
     String name = asString(parseExpected(NinjaToken.IDENTIFIER));
     parseExpected(NinjaToken.EQUALS);
 
-    NinjaVariableValue value = parseVariableValue(name);
+    NinjaVariableValue value = parseVariableValue();
     return Pair.of(name, value);
   }
 
   @VisibleForTesting
-  public NinjaVariableValue parseVariableValue(String name) throws GenericParsingException {
-    return parseVariableValueImpl(() -> String.format("Variable '%s' has no value.", name));
+  public NinjaVariableValue parseVariableValue() throws GenericParsingException {
+    return Preconditions.checkNotNull(parseVariableValueImpl(true));
   }
 
-  private NinjaVariableValue parseVariableValueImpl(Supplier<String> messageForNoValue)
-      throws GenericParsingException {
+  @Nullable
+  private NinjaVariableValue parseVariableValueImpl(boolean noValueAsEmpty) {
     NinjaVariableValue.Builder varBuilder = NinjaVariableValue.builder();
     int previous = -1;
     while (lexer.hasNextToken()) {
@@ -88,7 +87,12 @@
     }
     if (previous == -1) {
       // We read no value.
-      throw new GenericParsingException(messageForNoValue.get());
+      if (noValueAsEmpty) {
+        // Use empty string for value if specified by caller.
+        return NinjaVariableValue.createPlainText("");
+      }
+      // Otherwise, return null to indicate there was no value.
+      return null;
     }
     return varBuilder.build();
   }
@@ -154,9 +158,11 @@
   private NinjaVariableValue parseIncludeOrSubNinja(NinjaToken token)
       throws GenericParsingException {
     parseExpected(token);
-    NinjaVariableValue value =
-        parseVariableValueImpl(
-            () -> String.format("%s statement has no path.", Ascii.toLowerCase(token.name())));
+    NinjaVariableValue value = parseVariableValueImpl(false);
+    if (value == null) {
+      throw new GenericParsingException(
+          String.format("%s statement has no path.", Ascii.toLowerCase(token.name())));
+    }
     if (lexer.hasNextToken()) {
       parseExpected(NinjaToken.NEWLINE);
       lexer.undo();
@@ -178,7 +184,7 @@
       parseExpected(NinjaToken.INDENT);
       String variableName = asString(parseExpected(NinjaToken.IDENTIFIER));
       parseExpected(NinjaToken.EQUALS);
-      NinjaVariableValue value = parseVariableValue(variableName);
+      NinjaVariableValue value = parseVariableValue();
 
       NinjaRuleVariable ninjaRuleVariable = NinjaRuleVariable.nullOrValue(variableName);
       if (ninjaRuleVariable == null) {
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaParserStepTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaParserStepTest.java
index e752ef9..6b6ccfb 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaParserStepTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaParserStepTest.java
@@ -56,7 +56,6 @@
   public void testVariableParsingException() {
     doTestVariableParsingException(" ", "Expected identifier, but got indent");
     doTestVariableParsingException("a", "Expected = after 'a'");
-    doTestVariableParsingException("a=", "Variable 'a' has no value.");
     doTestVariableParsingException(
         "^a=", "Expected identifier, but got error: 'Symbol is not allowed in the identifier.'");
   }
@@ -68,7 +67,7 @@
   }
 
   @Test
-  public void testNoValue() {
+  public void testNoValue() throws Exception {
     doTestNoValue("a=");
     doTestNoValue("a=\u000018");
     doTestNoValue("a  =    ");
@@ -161,12 +160,26 @@
   }
 
   @Test
+  public void testVariableWithoutValue() throws Exception {
+    NinjaParserStep parser =
+        createParser(
+            "rule testRule  \n"
+                + " command = executable --flag $TARGET $out && $POST_BUILD\n"
+                + " description =\n");
+    NinjaRule ninjaRule = parser.parseNinjaRule();
+    ImmutableSortedMap<NinjaRuleVariable, NinjaVariableValue> variables = ninjaRule.getVariables();
+    assertThat(variables.keySet())
+        .containsExactly(
+            NinjaRuleVariable.NAME, NinjaRuleVariable.COMMAND, NinjaRuleVariable.DESCRIPTION);
+    assertThat(variables.get(NinjaRuleVariable.NAME).getRawText()).isEqualTo("testRule");
+    assertThat(variables.get(NinjaRuleVariable.DESCRIPTION).getRawText()).isEmpty();
+  }
+
+  @Test
   public void testNinjaRuleParsingException() {
     doTestNinjaRuleParsingException(
         "rule testRule extra-word\n", "Expected newline, but got identifier");
     doTestNinjaRuleParsingException(
-        "rule testRule\n command =", "Variable 'command' has no value.");
-    doTestNinjaRuleParsingException(
         "rule testRule\ncommand =", "Expected indent, but got identifier");
     doTestNinjaRuleParsingException(
         "rule testRule\n ^custom = a",
@@ -300,11 +313,11 @@
     assertThat(expander.getRequestedVariables()).isEmpty();
   }
 
-  private static void doTestNoValue(String text) {
+  private static void doTestNoValue(String text) throws Exception {
     NinjaParserStep parser = createParser(text);
-    GenericParsingException exception =
-        assertThrows(GenericParsingException.class, parser::parseVariable);
-    assertThat(exception).hasMessageThat().isEqualTo("Variable 'a' has no value.");
+    NinjaVariableValue value = parser.parseVariable().getSecond();
+    assertThat(value).isNotNull();
+    assertThat(value.getRawText()).isEmpty();
   }
 
   private static void doTestWithVariablesInValue(
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaScopeTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaScopeTest.java
index 1033620..15d10af 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaScopeTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaScopeTest.java
@@ -307,6 +307,6 @@
   private static NinjaVariableValue parseValue(String text) throws Exception {
     ByteBuffer bb = ByteBuffer.wrap(text.getBytes(StandardCharsets.ISO_8859_1));
     NinjaLexer lexer = new NinjaLexer(new ByteBufferFragment(bb, 0, bb.limit()));
-    return new NinjaParserStep(lexer).parseVariableValue("test");
+    return new NinjaParserStep(lexer).parseVariableValue();
   }
 }