Improve error message about wrong symbol in Ninja identifier

Otherwise, it is quite hard to debug problems.

Closes #10755.

PiperOrigin-RevId: 294455487
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/file/ByteBufferFragment.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/file/ByteBufferFragment.java
index 4996841..4e10509 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/file/ByteBufferFragment.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/file/ByteBufferFragment.java
@@ -91,6 +91,21 @@
     return bytes;
   }
 
+  /**
+   * Helper method for forming error messages with text fragment around a place with a problem.
+   *
+   * @param index position of the problematic symbol.
+   * @return a fragment if text around the problematic place, that can be retrieved from this buffer
+   */
+  public String getFragmentAround(int index) {
+    if (index < 0 || index >= length()) {
+      throw new IndexOutOfBoundsException(
+          String.format("Index out of bounds: %d (%d, %d).", index, 0, length()));
+    }
+    byte[] bytes = getBytes(Math.max(0, index - 200), Math.min(length(), index + 200));
+    return new String(bytes, StandardCharsets.ISO_8859_1);
+  }
+
   private void getBytes(byte[] dst, int offset) {
     if (dst.length - offset < length()) {
       throw new IndexOutOfBoundsException(
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/lexer/NinjaLexerStep.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/lexer/NinjaLexerStep.java
index 6635378..650d0a4 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/lexer/NinjaLexerStep.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/ninja/lexer/NinjaLexerStep.java
@@ -47,7 +47,7 @@
   private static final ImmutableSortedSet<Byte> IDENTIFIER_SYMBOLS =
       createByteSet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-");
   private static final ImmutableSortedSet<Byte> TEXT_STOPPERS = createByteSet("\n\r \t#$:\u0000");
-  private static final ImmutableSortedSet<Byte> PATH_STOPPERS = createByteSet("\n\r \t#$:|\u0000");
+  private static final ImmutableSortedSet<Byte> PATH_STOPPERS = createByteSet("\n\r \t$:|\u0000");
 
   private static ImmutableSortedSet<Byte> createByteSet(String variants) {
     ImmutableSortedSet.Builder<Byte> builder = ImmutableSortedSet.naturalOrder();
@@ -221,7 +221,11 @@
   public void tryReadIdentifier() {
     end = readIdentifier(position, true);
     if (position >= end) {
-      error = "Symbol is not allowed in the identifier.";
+      error =
+          String.format(
+              "Symbol '%s' is not allowed in the identifier,"
+                  + " the text fragment with the symbol:\n%s\n",
+              fragment.subFragment(position, position + 1), fragment.getFragmentAround(position));
       end = position + 1;
     }
   }
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaLexerStepTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaLexerStepTest.java
index d343232..9d88fe0 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaLexerStepTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaLexerStepTest.java
@@ -130,12 +130,18 @@
     doTest("abc_d-18", NinjaLexerStep::tryReadIdentifier);
     doTest("abc_d-18.ccc", NinjaLexerStep::tryReadIdentifier);
     doTest("abc_d-18.ccc=", NinjaLexerStep::tryReadIdentifier, "abc_d-18.ccc", true);
+    // Have a longer text to demonstrate the error output.
     doTestError(
-        "^abc",
+        "^abc Bazel only rebuilds what is necessary. "
+            + "With advanced local and distributed caching, optimized dependency analysis "
+            + "and parallel execution, you get fast and incremental builds.",
         NinjaLexerStep::tryReadIdentifier,
         "^",
         true,
-        "Symbol is not allowed in the identifier.");
+        "Symbol '^' is not allowed in the identifier, the text fragment with the symbol:\n"
+            + "^abc Bazel only rebuilds what is necessary. With advanced local and distributed"
+            + " caching, optimized dependency analysis and parallel execution,"
+            + " you get fast and incremental builds.\n");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaLexerTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaLexerTest.java
index 94787c3..7ea35cd 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaLexerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/ninja/NinjaLexerTest.java
@@ -79,7 +79,10 @@
 
   @Test
   public void testDisallowedSymbols() {
-    assertError(createLexer("^"), "Symbol is not allowed in the identifier.", "^");
+    assertError(
+        createLexer("^"),
+        "Symbol '^' is not allowed in the identifier, the text fragment with the symbol:\n^\n",
+        "^");
   }
 
   @Test
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 6b6ccfb..5ecdde7 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
@@ -57,7 +57,9 @@
     doTestVariableParsingException(" ", "Expected identifier, but got indent");
     doTestVariableParsingException("a", "Expected = after 'a'");
     doTestVariableParsingException(
-        "^a=", "Expected identifier, but got error: 'Symbol is not allowed in the identifier.'");
+        "^a=",
+        "Expected identifier, but got error: 'Symbol '^' is not allowed in the identifier, "
+            + "the text fragment with the symbol:\n^a=\n'");
   }
 
   private static void doTestVariableParsingException(String text, String message) {
@@ -183,7 +185,8 @@
         "rule testRule\ncommand =", "Expected indent, but got identifier");
     doTestNinjaRuleParsingException(
         "rule testRule\n ^custom = a",
-        "Expected identifier, but got error: 'Symbol is not allowed in the identifier.'");
+        "Expected identifier, but got error: 'Symbol '^' is not allowed in the identifier, "
+            + "the text fragment with the symbol:\nrule testRule\n ^custom = a\n'");
     doTestNinjaRuleParsingException("rule testRule\n custom = a", "Unexpected variable 'custom'");
   }