Add support escape JSON string to stream for export compile commands

gn gen --export-compile-commands does not produce valid JSON
The resulting file has "command" entries which are invalid JSON:
they are strings containing unescaped double quotation marks,
like "command": "... /showIncludes "..." "..." "..."".

Added new function EscapeJSONStringToStream that escape strings suitable
for JSON to generate compile_commands.json with valid JSON. This function
works the same as EscapeString but escape JSON string and writes the
results to the given stream, saving a copy.

Made EscapeJSONString return void and deleting the GetQuotedJSONString in
string_escape.*.

Added unit-test for EscapeJSONStringToStream function to escape_unittest.cc

Change-Id: I39c313314a186cb1de0280494cba3b546e5d1a1d
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/8520
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/src/base/json/string_escape.cc b/src/base/json/string_escape.cc
index ea674f5..51a818c 100644
--- a/src/base/json/string_escape.cc
+++ b/src/base/json/string_escape.cc
@@ -116,30 +116,16 @@
 
 }  // namespace
 
-bool EscapeJSONString(std::string_view str,
+void EscapeJSONString(std::string_view str,
                       bool put_in_quotes,
                       std::string* dest) {
-  return EscapeJSONStringImpl(str, put_in_quotes, dest);
+  EscapeJSONStringImpl(str, put_in_quotes, dest);
 }
 
-bool EscapeJSONString(std::u16string_view str,
+void EscapeJSONString(std::u16string_view str,
                       bool put_in_quotes,
                       std::string* dest) {
-  return EscapeJSONStringImpl(str, put_in_quotes, dest);
-}
-
-std::string GetQuotedJSONString(std::string_view str) {
-  std::string dest;
-  bool ok = EscapeJSONStringImpl(str, true, &dest);
-  DCHECK(ok);
-  return dest;
-}
-
-std::string GetQuotedJSONString(std::u16string_view str) {
-  std::string dest;
-  bool ok = EscapeJSONStringImpl(str, true, &dest);
-  DCHECK(ok);
-  return dest;
+  EscapeJSONStringImpl(str, put_in_quotes, dest);
 }
 
 std::string EscapeBytesAsInvalidJSONString(std::string_view str,
diff --git a/src/base/json/string_escape.h b/src/base/json/string_escape.h
index ada5179..14eecdd 100644
--- a/src/base/json/string_escape.h
+++ b/src/base/json/string_escape.h
@@ -24,22 +24,17 @@
 //
 // If |put_in_quotes| is true, then a leading and trailing double-quote mark
 // will be appended to |dest| as well.
-bool EscapeJSONString(std::string_view str,
+void EscapeJSONString(std::string_view str,
                       bool put_in_quotes,
                       std::string* dest);
 
 // Performs a similar function to the UTF-8 std::string_view version above,
 // converting UTF-16 code units to UTF-8 code units and escaping non-printing
 // control characters. On return, |dest| will contain a valid UTF-8 JSON string.
-bool EscapeJSONString(std::u16string_view str,
+void EscapeJSONString(std::u16string_view str,
                       bool put_in_quotes,
                       std::string* dest);
 
-// Helper functions that wrap the above two functions but return the value
-// instead of appending. |put_in_quotes| is always true.
-std::string GetQuotedJSONString(std::string_view str);
-std::string GetQuotedJSONString(std::u16string_view str);
-
 // Given an arbitrary byte string |str|, this will escape all non-ASCII bytes
 // as \uXXXX escape sequences. This function is *NOT* meant to be used with
 // Unicode strings and does not validate |str| as one.
diff --git a/src/gn/compile_commands_writer.cc b/src/gn/compile_commands_writer.cc
index 8d6c7fb..d0367fc 100644
--- a/src/gn/compile_commands_writer.cc
+++ b/src/gn/compile_commands_writer.cc
@@ -159,7 +159,7 @@
   for (const auto& range : tool->command().ranges()) {
     // TODO: this is emitting a bonus space prior to each substitution.
     if (range.type == &SubstitutionLiteral) {
-      EscapeStringToStream(out, range.literal, no_quoting);
+      EscapeJSONStringToStream(out, range.literal, no_quoting);
     } else if (range.type == &SubstitutionOutput) {
       path_output.WriteFiles(out, tool_outputs);
     } else if (range.type == &CSubstitutionDefines) {
diff --git a/src/gn/escape.cc b/src/gn/escape.cc
index 98f77d3..1874905 100644
--- a/src/gn/escape.cc
+++ b/src/gn/escape.cc
@@ -9,6 +9,7 @@
 #include <memory>
 
 #include "base/compiler_specific.h"
+#include "base/json/string_escape.h"
 #include "base/logging.h"
 #include "util/build_config.h"
 
@@ -266,3 +267,13 @@
   StackOrHeapBuffer dest(str.size() * kMaxEscapedCharsPerChar);
   out.write(dest, EscapeStringToString(str, options, dest, nullptr));
 }
+
+void EscapeJSONStringToStream(std::ostream& out,
+                              const std::string_view& str,
+                              const EscapeOptions& options) {
+  std::string dest;
+  bool needed_quoting = !options.inhibit_quoting;
+  base::EscapeJSONString(str, needed_quoting, &dest);
+
+  EscapeStringToStream(out, dest, options);
+}
diff --git a/src/gn/escape.h b/src/gn/escape.h
index 28f31bf..78e6544 100644
--- a/src/gn/escape.h
+++ b/src/gn/escape.h
@@ -76,4 +76,10 @@
                           const std::string_view& str,
                           const EscapeOptions& options);
 
+// Same as EscapeString but escape JSON string and writes the results to the
+// given stream, saving a copy.
+void EscapeJSONStringToStream(std::ostream& out,
+                              const std::string_view& str,
+                              const EscapeOptions& options);
+
 #endif  // TOOLS_GN_ESCAPE_H_
diff --git a/src/gn/escape_unittest.cc b/src/gn/escape_unittest.cc
index ca42ac9..afc2bc4 100644
--- a/src/gn/escape_unittest.cc
+++ b/src/gn/escape_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "gn/escape.h"
+#include "gn/string_output_buffer.h"
 #include "util/test/test.h"
 
 TEST(Escape, Ninja) {
@@ -78,3 +79,25 @@
   EXPECT_EQ("-VERSION=\"libsrtp2\\ 2.1.0-pre\"",
             EscapeString("-VERSION=\"libsrtp2 2.1.0-pre\"", opts, nullptr));
 }
+
+TEST(EscapeJSONString, NinjaPreformatted) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND;
+  opts.inhibit_quoting = true;
+
+  StringOutputBuffer buffer;
+  std::ostream out(&buffer);
+
+  EscapeJSONStringToStream(out, "foo\\\" bar", opts);
+  EXPECT_EQ("foo\\\\\\\" bar", buffer.str());
+
+  StringOutputBuffer buffer1;
+  std::ostream out1(&buffer1);
+  EscapeJSONStringToStream(out1, "foo bar\\\\", opts);
+  EXPECT_EQ("foo bar\\\\\\\\", buffer1.str());
+
+  StringOutputBuffer buffer2;
+  std::ostream out2(&buffer2);
+  EscapeJSONStringToStream(out2, "a: \"$\\b", opts);
+  EXPECT_EQ("a: \\\"$$\\\\b", buffer2.str());
+}