Add --dump-json-tree option for format subcommand.

The option allows to output a token tree in the JSON format, which is
intended to be used by another program.

This feature will be firstly used for making a linter of .gn files
(crbug.com/912393).

Change-Id: I3b97cb6fbd3fb672c38ae57b63db8f1b9f983146
Reviewed-on: https://gn-review.googlesource.com/c/3860
Commit-Queue: Brett Wilson <brettw@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/tools/gn/command_format.cc b/tools/gn/command_format.cc
index b620013..5b9580b 100644
--- a/tools/gn/command_format.cc
+++ b/tools/gn/command_format.cc
@@ -10,6 +10,7 @@
 
 #include "base/command_line.h"
 #include "base/files/file_util.h"
+#include "base/json/json_writer.h"
 #include "base/macros.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
@@ -26,6 +27,8 @@
 
 const char kSwitchDryRun[] = "dry-run";
 const char kSwitchDumpTree[] = "dump-tree";
+const char kSwitchDumpTreeText[] = "text";
+const char kSwitchDumpTreeJSON[] = "json";
 const char kSwitchStdin[] = "stdin";
 
 const char kFormat[] = "format";
@@ -55,9 +58,9 @@
       - Exit code 1: general failure (parse error, etc.)
       - Exit code 2: successful format, but differs from on disk.
 
-  --dump-tree
-      For debugging, dumps the parse tree to stdout and does not update the
-      file or print formatted output.
+  --dump-tree[=( text | json )]
+      Dumps the parse tree to stdout and does not update the file or print
+      formatted output. If no format is specified, text format will be used.
 
   --stdin
       Read input from stdin and write to stdout rather than update a file
@@ -1059,12 +1062,19 @@
   return false;
 }
 
-void DoFormat(const ParseNode* root, bool dump_tree, std::string* output) {
-  if (dump_tree) {
+void DoFormat(const ParseNode* root, TreeDumpMode dump_tree,
+              std::string* output) {
+  if (dump_tree == TreeDumpMode::kPlainText) {
     std::ostringstream os;
-    root->Print(os, 0);
+    RenderToText(root->GetJSONNode(), 0, os);
     fprintf(stderr, "%s", os.str().c_str());
+  } else if (dump_tree == TreeDumpMode::kJSON) {
+    std::string os;
+    base::JSONWriter::WriteWithOptions(root->GetJSONNode(),
+        base::JSONWriter::OPTIONS_PRETTY_PRINT, &os);
+    fprintf(stderr, "%s", os.c_str());
   }
+
   Printer pr;
   pr.Block(root);
   *output = pr.String();
@@ -1091,7 +1101,7 @@
 
 bool FormatFileToString(Setup* setup,
                         const SourceFile& file,
-                        bool dump_tree,
+                        TreeDumpMode dump_tree,
                         std::string* output) {
   Err err;
   const ParseNode* parse_node =
@@ -1106,7 +1116,7 @@
 }
 
 bool FormatStringToString(const std::string& input,
-                          bool dump_tree,
+                          TreeDumpMode dump_tree,
                           std::string* output) {
   SourceFile source_file;
   InputFile file(source_file);
@@ -1133,8 +1143,23 @@
 int RunFormat(const std::vector<std::string>& args) {
   bool dry_run =
       base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDryRun);
-  bool dump_tree =
-      base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree);
+  TreeDumpMode dump_tree = TreeDumpMode::kInactive;
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree)) {
+    std::string tree_type = base::CommandLine::ForCurrentProcess()->
+        GetSwitchValueASCII(kSwitchDumpTree);
+    if (tree_type == kSwitchDumpTreeJSON) {
+      dump_tree = TreeDumpMode::kJSON;
+    } else if (tree_type.empty() || tree_type == kSwitchDumpTreeText) {
+      dump_tree = TreeDumpMode::kPlainText;
+    } else {
+      Err(Location(),
+          tree_type + " is an invalid value for --dump-tree. Specify "
+          "\"" + kSwitchDumpTreeText + "\" or \"" + kSwitchDumpTreeJSON +
+          "\".\n")
+          .PrintToStdout();
+      return 1;
+    }
+  }
   bool from_stdin =
       base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchStdin);
 
@@ -1180,7 +1205,7 @@
 
     std::string output_string;
     if (FormatFileToString(&setup, file, dump_tree, &output_string)) {
-      if (!dump_tree) {
+      if (dump_tree == TreeDumpMode::kInactive) {
         // Update the file in-place.
         base::FilePath to_write = setup.build_settings().GetFullPath(file);
         std::string original_contents;
diff --git a/tools/gn/command_format.h b/tools/gn/command_format.h
index 413937e..785b070 100644
--- a/tools/gn/command_format.h
+++ b/tools/gn/command_format.h
@@ -12,13 +12,25 @@
 
 namespace commands {
 
+enum class TreeDumpMode {
+  // Normal operation mode. Format the input file.
+  kInactive,
+
+  // Output the token tree with indented plain text. For debugging.
+  kPlainText,
+
+  // Output the token tree in JSON format. Used for exporting a tree to another
+  // program.
+  kJSON
+};
+
 bool FormatFileToString(Setup* setup,
                         const SourceFile& file,
-                        bool dump_tree,
+                        TreeDumpMode dump_tree,
                         std::string* output);
 
 bool FormatStringToString(const std::string& input,
-                          bool dump_tree,
+                          TreeDumpMode dump_tree,
                           std::string* output);
 
 }  // namespace commands
diff --git a/tools/gn/command_format_unittest.cc b/tools/gn/command_format_unittest.cc
index bad12ee..b00f6ac 100644
--- a/tools/gn/command_format_unittest.cc
+++ b/tools/gn/command_format_unittest.cc
@@ -23,8 +23,8 @@
         GetExePath().DirName().Append(FILE_PATH_LITERAL(".."));             \
     base::SetCurrentDirectory(src_dir);                                     \
     EXPECT_TRUE(commands::FormatFileToString(                               \
-        &setup, SourceFile("//tools/gn/format_test_data/" #n ".gn"), false, \
-        &out));                                                             \
+        &setup, SourceFile("//tools/gn/format_test_data/" #n ".gn"),        \
+        commands::TreeDumpMode::kInactive, &out));                          \
     ASSERT_TRUE(base::ReadFileToString(                                     \
         base::FilePath(FILE_PATH_LITERAL("tools/gn/format_test_data/")      \
                            FILE_PATH_LITERAL(#n)                            \
@@ -33,7 +33,8 @@
     EXPECT_EQ(expected, out);                                               \
     /* Make sure formatting the output doesn't cause further changes. */    \
     std::string out_again;                                                  \
-    EXPECT_TRUE(commands::FormatStringToString(out, false, &out_again));    \
+    EXPECT_TRUE(commands::FormatStringToString(out,                         \
+        commands::TreeDumpMode::kInactive, &out_again));                    \
     ASSERT_EQ(out, out_again);                                              \
   }
 
diff --git a/tools/gn/operators_unittest.cc b/tools/gn/operators_unittest.cc
index db90ac9..2569bc5 100644
--- a/tools/gn/operators_unittest.cc
+++ b/tools/gn/operators_unittest.cc
@@ -40,7 +40,9 @@
                           const std::string& help) const override {
     return Err(this, msg);
   }
-  void Print(std::ostream& out, int indent) const override {}
+  base::Value GetJSONNode() const override {
+    return base::Value();
+  }
 
  private:
   Value value_;
diff --git a/tools/gn/parse_tree.cc b/tools/gn/parse_tree.cc
index 6dde01d..821fb7e 100644
--- a/tools/gn/parse_tree.cc
+++ b/tools/gn/parse_tree.cc
@@ -10,6 +10,7 @@
 #include <string>
 #include <tuple>
 
+#include "base/json/string_escape.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "tools/gn/functions.h"
@@ -17,6 +18,14 @@
 #include "tools/gn/scope.h"
 #include "tools/gn/string_utils.h"
 
+// Dictionary keys used for JSON-formatted tree dump.
+const char kJsonNodeChild[] = "child";
+const char kJsonNodeType[] = "type";
+const char kJsonNodeValue[] = "value";
+const char kJsonBeforeComment[] = "before_comment";
+const char kJsonSuffixComment[] = "suffix_comment";
+const char kJsonAfterComment[] = "after_comment";
+
 namespace {
 
 enum DepsCategory {
@@ -53,10 +62,6 @@
                              : base::StringPiece());
 }
 
-std::string IndentFor(int value) {
-  return std::string(value, ' ');
-}
-
 bool IsSortRangeSeparator(const ParseNode* node, const ParseNode* prev) {
   // If it's a block comment, or has an attached comment with a blank line
   // before it, then we break the range at this point.
@@ -133,15 +138,42 @@
   return comments_.get();
 }
 
-void ParseNode::PrintComments(std::ostream& out, int indent) const {
+base::Value ParseNode::CreateJSONNode(const char* type) const {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetKey(kJsonNodeType, base::Value(type));
+  AddCommentsJSONNodes(&dict);
+  return dict;
+}
+
+base::Value ParseNode::CreateJSONNode(const char* type,
+    const base::StringPiece& value) const {
+  base::Value dict(base::Value::Type::DICTIONARY);
+  dict.SetKey(kJsonNodeType, base::Value(type));
+  dict.SetKey(kJsonNodeValue, base::Value(value));
+  AddCommentsJSONNodes(&dict);
+  return dict;
+}
+
+void ParseNode::AddCommentsJSONNodes(base::Value* out_value) const {
   if (comments_) {
-    std::string ind = IndentFor(indent + 1);
-    for (const auto& token : comments_->before())
-      out << ind << "+BEFORE_COMMENT(\"" << token.value() << "\")\n";
-    for (const auto& token : comments_->suffix())
-      out << ind << "+SUFFIX_COMMENT(\"" << token.value() << "\")\n";
-    for (const auto& token : comments_->after())
-      out << ind << "+AFTER_COMMENT(\"" << token.value() << "\")\n";
+    if (comments_->before().size()) {
+      base::Value comment_values(base::Value::Type::LIST);
+      for (const auto& token : comments_->before())
+        comment_values.GetList().push_back(base::Value(token.value()));
+      out_value->SetKey(kJsonBeforeComment, std::move(comment_values));
+    }
+    if (comments_->suffix().size()) {
+      base::Value comment_values(base::Value::Type::LIST);
+      for (const auto& token : comments_->suffix())
+        comment_values.GetList().push_back(base::Value(token.value()));
+      out_value->SetKey(kJsonSuffixComment, std::move(comment_values));
+    }
+    if (comments_->after().size()) {
+      base::Value comment_values(base::Value::Type::LIST);
+      for (const auto& token : comments_->after())
+        comment_values.GetList().push_back(base::Value(token.value()));
+      out_value->SetKey(kJsonAfterComment, std::move(comment_values));
+    }
   }
 }
 
@@ -178,14 +210,15 @@
   return Err(GetRange(), msg, help);
 }
 
-void AccessorNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "ACCESSOR\n";
-  PrintComments(out, indent);
-  out << IndentFor(indent + 1) << base_.value() << "\n";
+base::Value AccessorNode::GetJSONNode() const {
+  base::Value dict(CreateJSONNode("ACCESSOR", base_.value()));
+  base::Value child(base::Value::Type::LIST);
   if (index_)
-    index_->Print(out, indent + 1);
+    child.GetList().push_back(index_->GetJSONNode());
   else if (member_)
-    member_->Print(out, indent + 1);
+    child.GetList().push_back(member_->GetJSONNode());
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return dict;
 }
 
 Value AccessorNode::ExecuteArrayAccess(Scope* scope, Err* err) const {
@@ -311,11 +344,13 @@
   return Err(op_, msg, help);
 }
 
-void BinaryOpNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "BINARY(" << op_.value() << ")\n";
-  PrintComments(out, indent);
-  left_->Print(out, indent + 1);
-  right_->Print(out, indent + 1);
+base::Value BinaryOpNode::GetJSONNode() const {
+  base::Value dict(CreateJSONNode("BINARY", op_.value()));
+  base::Value child(base::Value::Type::LIST);
+  child.GetList().push_back(left_->GetJSONNode());
+  child.GetList().push_back(right_->GetJSONNode());
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return dict;
 }
 
 // BlockNode ------------------------------------------------------------------
@@ -388,13 +423,16 @@
   return Err(GetRange(), msg, help);
 }
 
-void BlockNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "BLOCK\n";
-  PrintComments(out, indent);
+base::Value BlockNode::GetJSONNode() const {
+  base::Value dict(CreateJSONNode("BLOCK"));
+  base::Value statements(base::Value::Type::LIST);
   for (const auto& statement : statements_)
-    statement->Print(out, indent + 1);
+    statements.GetList().push_back(statement->GetJSONNode());
   if (end_ && end_->comments())
-    end_->Print(out, indent + 1);
+    statements.GetList().push_back(end_->GetJSONNode());
+
+  dict.SetKey("child", std::move(statements));
+  return dict;
 }
 
 // ConditionNode --------------------------------------------------------------
@@ -441,13 +479,16 @@
   return Err(if_token_, msg, help);
 }
 
-void ConditionNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "CONDITION\n";
-  PrintComments(out, indent);
-  condition_->Print(out, indent + 1);
-  if_true_->Print(out, indent + 1);
-  if (if_false_)
-    if_false_->Print(out, indent + 1);
+base::Value ConditionNode::GetJSONNode() const {
+  base::Value dict = CreateJSONNode("CONDITION");
+  base::Value child(base::Value::Type::LIST);
+  child.GetList().push_back(condition_->GetJSONNode());
+  child.GetList().push_back(if_true_->GetJSONNode());
+  if (if_false_) {
+    child.GetList().push_back(if_false_->GetJSONNode());
+  }
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return std::move(dict);
 }
 
 // FunctionCallNode -----------------------------------------------------------
@@ -477,12 +518,15 @@
   return Err(function_, msg, help);
 }
 
-void FunctionCallNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "FUNCTION(" << function_.value() << ")\n";
-  PrintComments(out, indent);
-  args_->Print(out, indent + 1);
-  if (block_)
-    block_->Print(out, indent + 1);
+base::Value FunctionCallNode::GetJSONNode() const {
+  base::Value dict = CreateJSONNode("FUNCTION", function_.value());
+  base::Value child(base::Value::Type::LIST);
+  child.GetList().push_back(args_->GetJSONNode());
+  if (block_) {
+    child.GetList().push_back(block_->GetJSONNode());
+  }
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return dict;
 }
 
 void FunctionCallNode::SetNewLocation(int line_number) {
@@ -539,9 +583,8 @@
   return Err(value_, msg, help);
 }
 
-void IdentifierNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "IDENTIFIER(" << value_.value() << ")\n";
-  PrintComments(out, indent);
+base::Value IdentifierNode::GetJSONNode() const {
+  return CreateJSONNode("IDENTIFIER", value_.value());
 }
 
 void IdentifierNode::SetNewLocation(int line_number) {
@@ -589,14 +632,17 @@
   return Err(begin_token_, msg, help);
 }
 
-void ListNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "LIST" << (prefer_multiline_ ? " multiline" : "")
-      << "\n";
-  PrintComments(out, indent);
-  for (const auto& cur : contents_)
-    cur->Print(out, indent + 1);
-  if (end_ && end_->comments())
-    end_->Print(out, indent + 1);
+base::Value ListNode::GetJSONNode() const {
+  base::Value dict(CreateJSONNode("LIST"));
+  base::Value child(base::Value::Type::LIST);
+  for (const auto& cur : contents_) {
+    child.GetList().push_back(cur->GetJSONNode());
+  }
+  if (end_ && end_->comments()) {
+    child.GetList().push_back(end_->GetJSONNode());
+  }
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return dict;
 }
 
 template <typename Comparator>
@@ -794,9 +840,8 @@
   return Err(value_, msg, help);
 }
 
-void LiteralNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "LITERAL(" << value_.value() << ")\n";
-  PrintComments(out, indent);
+base::Value LiteralNode::GetJSONNode() const {
+  return CreateJSONNode("LITERAL", value_.value());
 }
 
 void LiteralNode::SetNewLocation(int line_number) {
@@ -831,10 +876,12 @@
   return Err(op_, msg, help);
 }
 
-void UnaryOpNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "UNARY(" << op_.value() << ")\n";
-  PrintComments(out, indent);
-  operand_->Print(out, indent + 1);
+base::Value UnaryOpNode::GetJSONNode() const {
+  base::Value dict = CreateJSONNode("UNARY", op_.value());
+  base::Value child(base::Value::Type::LIST);
+  child.GetList().push_back(operand_->GetJSONNode());
+  dict.SetKey(kJsonNodeChild, std::move(child));
+  return dict;
 }
 
 // BlockCommentNode ------------------------------------------------------------
@@ -860,9 +907,10 @@
   return Err(comment_, msg, help);
 }
 
-void BlockCommentNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "BLOCK_COMMENT(" << comment_.value() << ")\n";
-  PrintComments(out, indent);
+base::Value BlockCommentNode::GetJSONNode() const {
+  std::string escaped;
+  base::EscapeJSONString(comment_.value().as_string(), false, &escaped);
+  return CreateJSONNode("BLOCK_COMMENT", escaped);
 }
 
 // EndNode ---------------------------------------------------------------------
@@ -888,7 +936,6 @@
   return Err(value_, msg, help);
 }
 
-void EndNode::Print(std::ostream& out, int indent) const {
-  out << IndentFor(indent) << "END(" << value_.value() << ")\n";
-  PrintComments(out, indent);
+base::Value EndNode::GetJSONNode() const {
+  return CreateJSONNode("END", value_.value());
 }
diff --git a/tools/gn/parse_tree.h b/tools/gn/parse_tree.h
index 9927307..6c61b72 100644
--- a/tools/gn/parse_tree.h
+++ b/tools/gn/parse_tree.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "base/values.h"
 #include "tools/gn/err.h"
 #include "tools/gn/token.h"
 #include "tools/gn/value.h"
@@ -29,6 +30,14 @@
 class Scope;
 class UnaryOpNode;
 
+// Dictionary keys used for JSON-formatted tree dump.
+extern const char kJsonNodeChild[];
+extern const char kJsonNodeType[];
+extern const char kJsonNodeValue[];
+extern const char kJsonBeforeComment[];
+extern const char kJsonSuffixComment[];
+extern const char kJsonAfterComment[];
+
 class Comments {
  public:
   Comments();
@@ -92,15 +101,24 @@
       const std::string& msg,
       const std::string& help = std::string()) const = 0;
 
-  // Prints a representation of this node to the given string, indenting
-  // by the given number of spaces.
-  virtual void Print(std::ostream& out, int indent) const = 0;
+  // Generates a representation of this node in base::Value, to be used for
+  // exporting the tree as a JSON or formatted text with indents.
+  virtual base::Value GetJSONNode() const = 0;
 
   const Comments* comments() const { return comments_.get(); }
   Comments* comments_mutable();
-  void PrintComments(std::ostream& out, int indent) const;
+
+ protected:
+  // Helper functions for GetJSONNode. Creates and fills a Value object with
+  // given type (and value).
+  base::Value CreateJSONNode(const char* type) const;
+  base::Value CreateJSONNode(const char* type, const base::StringPiece& value)
+      const;
 
  private:
+  // Helper function for CreateJSONNode.
+  void AddCommentsJSONNodes(base::Value* out_value) const;
+
   std::unique_ptr<Comments> comments_;
 
   DISALLOW_COPY_AND_ASSIGN(ParseNode);
@@ -142,7 +160,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   // Base is the thing on the left of the [] or dot, currently always required
   // to be an identifier token.
@@ -196,7 +214,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   const Token& op() const { return op_; }
   void set_op(const Token& t) { op_ = t; }
@@ -241,7 +259,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   void set_begin_token(const Token& t) { begin_token_ = t; }
   void set_end(std::unique_ptr<EndNode> e) { end_ = std::move(e); }
@@ -282,7 +300,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   void set_if_token(const Token& token) { if_token_ = token; }
 
@@ -323,7 +341,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   const Token& function() const { return function_; }
   void set_function(Token t) { function_ = t; }
@@ -358,7 +376,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   const Token& value() const { return value_; }
   void set_value(const Token& t) { value_ = t; }
@@ -384,7 +402,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   void set_begin_token(const Token& t) { begin_token_ = t; }
   const Token& Begin() const { return begin_token_; }
@@ -446,7 +464,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   const Token& value() const { return value_; }
   void set_value(const Token& t) { value_ = t; }
@@ -472,7 +490,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   const Token& op() const { return op_; }
   void set_op(const Token& t) { op_ = t; }
@@ -507,7 +525,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   const Token& comment() const { return comment_; }
   void set_comment(const Token& t) { comment_ = t; }
@@ -535,7 +553,7 @@
   Err MakeErrorDescribing(
       const std::string& msg,
       const std::string& help = std::string()) const override;
-  void Print(std::ostream& out, int indent) const override;
+  base::Value GetJSONNode() const override;
 
   const Token& value() const { return value_; }
   void set_value(const Token& t) { value_ = t; }
diff --git a/tools/gn/parser.cc b/tools/gn/parser.cc
index f4f028f..e10ed41 100644
--- a/tools/gn/parser.cc
+++ b/tools/gn/parser.cc
@@ -889,3 +889,49 @@
       const_cast<ParseNode*>(*i)->comments_mutable()->ReverseSuffix();
   }
 }
+
+std::string IndentFor(int value) {
+  return std::string(value, ' ');
+}
+
+void RenderToText(const base::Value& node, int indent_level,
+    std::ostringstream& os) {
+  const base::Value* child = node.FindKey(std::string("child"));
+  std::string node_type(node.FindKey("type")->GetString());
+  if (node_type == "ACCESSOR") {
+    // AccessorNode is a bit special, in that it holds a Token, not a ParseNode
+    // for the base.
+    os << IndentFor(indent_level) << node_type << std::endl;
+    os << IndentFor(indent_level + 1) << node.FindKey("value")->GetString()
+        << std::endl;
+  } else {
+    os << IndentFor(indent_level) << node_type;
+    if (node.FindKey("value")) {
+      os << "(" << node.FindKey("value")->GetString() << ")";
+    }
+    os << std::endl;
+  }
+  if (node.FindKey(kJsonBeforeComment)) {
+    for (auto& v : node.FindKey(kJsonBeforeComment)->GetList()) {
+      os << IndentFor(indent_level + 1) <<
+          "+BEFORE_COMMENT(\"" << v.GetString() << "\")\n";
+    }
+  }
+  if (node.FindKey(kJsonSuffixComment)) {
+    for (auto& v : node.FindKey(kJsonSuffixComment)->GetList()) {
+      os << IndentFor(indent_level + 1) <<
+          "+SUFFIX_COMMENT(\"" << v.GetString() << "\")\n";
+    }
+  }
+  if (node.FindKey(kJsonAfterComment)) {
+    for (auto& v : node.FindKey(kJsonAfterComment)->GetList()) {
+      os << IndentFor(indent_level + 1) <<
+          "+AFTER_COMMENT(\"" << v.GetString() << "\")\n";
+    }
+  }
+  if (child) {
+    for (const base::Value& n : child->GetList()) {
+      RenderToText(n, indent_level + 1, os);
+    }
+  }
+}
diff --git a/tools/gn/parser.h b/tools/gn/parser.h
index c1930b9..5ecbd9d 100644
--- a/tools/gn/parser.h
+++ b/tools/gn/parser.h
@@ -147,4 +147,9 @@
   int precedence;
 };
 
+// Renders parse subtree as a formatted text, indenting by the given number of
+// spaces.
+void RenderToText(const base::Value& node, int indent_level,
+    std::ostringstream& os);
+
 #endif  // TOOLS_GN_PARSER_H_
diff --git a/tools/gn/parser_unittest.cc b/tools/gn/parser_unittest.cc
index e2bbf39..d24067c 100644
--- a/tools/gn/parser_unittest.cc
+++ b/tools/gn/parser_unittest.cc
@@ -32,7 +32,7 @@
   ASSERT_TRUE(result);
 
   std::ostringstream collector;
-  result->Print(collector, 0);
+  RenderToText(result->GetJSONNode(), 0, collector);
 
   EXPECT_EQ(expected, collector.str());
 }
@@ -48,7 +48,7 @@
   ASSERT_TRUE(result);
 
   std::ostringstream collector;
-  result->Print(collector, 0);
+  RenderToText(result->GetJSONNode(), 0, collector);
 
   EXPECT_EQ(expected, collector.str());
 }
diff --git a/tools/gn/setup.cc b/tools/gn/setup.cc
index 6720f65..b9debab 100644
--- a/tools/gn/setup.cc
+++ b/tools/gn/setup.cc
@@ -527,7 +527,7 @@
   base::CreateDirectory(build_arg_file.DirName());
 
   std::string contents = args_input_file_->contents();
-  commands::FormatStringToString(contents, false, &contents);
+  commands::FormatStringToString(contents, commands::TreeDumpMode::kInactive, &contents);
 #if defined(OS_WIN)
   // Use Windows lineendings for this file since it will often open in
   // Notepad which can't handle Unix ones.