Sort "deps" and "public_deps" when running the "gn format" command.

The "deps" and "public_deps" are sorted using the following rules:
  1. relative targets (":a", "../b") sort before absolute targets ("//c"),
  2. each group (relative and absolute) is sorted alphabetically.

BUG=554928

Review URL: https://codereview.chromium.org/1442023002

Cr-Original-Commit-Position: refs/heads/master@{#360042}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 354261e7dd491e88fde3b3bd3fa5ef6f1c629f00
diff --git a/tools/gn/command_format.cc b/tools/gn/command_format.cc
index 5eff601..6c199d3 100644
--- a/tools/gn/command_format.cc
+++ b/tools/gn/command_format.cc
@@ -128,8 +128,11 @@
   // Flag assignments to sources, deps, etc. to make their RHSs multiline.
   void AnnotatePreferredMultilineAssignment(const BinaryOpNode* binop);
 
-  // Alphabetically a list on the RHS if the LHS is 'sources'.
-  void SortIfSources(const BinaryOpNode* binop);
+  // Sort a list on the RHS if the LHS is 'sources', 'deps' or 'public_deps'.
+  // The 'sources' are sorted alphabetically while the 'deps' and 'public_deps'
+  // are sorted putting first the relative targets and then the global ones
+  // (both sorted alphabetically).
+  void SortIfSourcesOrDeps(const BinaryOpNode* binop);
 
   // Heuristics to decide if there should be a blank line added between two
   // items. For various "small" items, it doesn't look nice if there's too much
@@ -307,20 +310,20 @@
   }
 }
 
-void Printer::SortIfSources(const BinaryOpNode* binop) {
+void Printer::SortIfSourcesOrDeps(const BinaryOpNode* binop) {
   const IdentifierNode* ident = binop->left()->AsIdentifier();
   const ListNode* list = binop->right()->AsList();
-  // TODO(scottmg): Sort more than 'sources'?
   if ((binop->op().value() == "=" || binop->op().value() == "+=" ||
        binop->op().value() == "-=") &&
       ident && list) {
     const base::StringPiece lhs = ident->value().value();
     if (lhs == "sources")
       const_cast<ListNode*>(list)->SortAsStringsList();
+    else if (lhs == "deps" || lhs == "public_deps")
+      const_cast<ListNode*>(list)->SortAsDepsList();
   }
 }
 
-
 bool Printer::ShouldAddBlankLineInBetween(const ParseNode* a,
                                           const ParseNode* b) {
   LocationRange a_range = a->GetRange();
@@ -458,7 +461,7 @@
     CHECK(precedence_.find(binop->op().value()) != precedence_.end());
     AnnotatePreferredMultilineAssignment(binop);
 
-    SortIfSources(binop);
+    SortIfSourcesOrDeps(binop);
 
     Precedence prec = precedence_[binop->op().value()];
 
diff --git a/tools/gn/command_format_unittest.cc b/tools/gn/command_format_unittest.cc
index e0f6816..8e8c256 100644
--- a/tools/gn/command_format_unittest.cc
+++ b/tools/gn/command_format_unittest.cc
@@ -100,3 +100,6 @@
 FORMAT_TEST(060)
 FORMAT_TEST(061)
 FORMAT_TEST(062)
+FORMAT_TEST(063)
+FORMAT_TEST(064)
+FORMAT_TEST(065)
diff --git a/tools/gn/format_test_data/063.gn b/tools/gn/format_test_data/063.gn
new file mode 100644
index 0000000..a933f4b
--- /dev/null
+++ b/tools/gn/format_test_data/063.gn
@@ -0,0 +1,18 @@
+source_set("test") {
+  deps = [
+    "//c",
+    "//a",
+    ":b",
+    "../d",
+  ]
+
+  public_deps = []
+  if (condition) {
+    public_deps += [
+      "//y",
+      "//w",
+      ":x",
+      "../z",
+    ]
+  }
+}
diff --git a/tools/gn/format_test_data/063.golden b/tools/gn/format_test_data/063.golden
new file mode 100644
index 0000000..acf2843
--- /dev/null
+++ b/tools/gn/format_test_data/063.golden
@@ -0,0 +1,18 @@
+source_set("test") {
+  deps = [
+    ":b",
+    "../d",
+    "//a",
+    "//c",
+  ]
+
+  public_deps = []
+  if (condition) {
+    public_deps += [
+      ":x",
+      "../z",
+      "//w",
+      "//y",
+    ]
+  }
+}
diff --git a/tools/gn/format_test_data/064.gn b/tools/gn/format_test_data/064.gn
new file mode 100644
index 0000000..c186725
--- /dev/null
+++ b/tools/gn/format_test_data/064.gn
@@ -0,0 +1,3 @@
+source_set("test") {
+  deps = [ rebase_path(sdk_dep, ".", mojo_root) ]
+}
diff --git a/tools/gn/format_test_data/064.golden b/tools/gn/format_test_data/064.golden
new file mode 100644
index 0000000..3ff56f6
--- /dev/null
+++ b/tools/gn/format_test_data/064.golden
@@ -0,0 +1,5 @@
+source_set("test") {
+  deps = [
+    rebase_path(sdk_dep, ".", mojo_root),
+  ]
+}
diff --git a/tools/gn/format_test_data/065.gn b/tools/gn/format_test_data/065.gn
new file mode 100644
index 0000000..a448909
--- /dev/null
+++ b/tools/gn/format_test_data/065.gn
@@ -0,0 +1,4 @@
+source_set("test") {
+  some_target_name = ":some_target"
+  deps = [ some_target_name, "//last_target", ":another_target" ]
+}
diff --git a/tools/gn/format_test_data/065.golden b/tools/gn/format_test_data/065.golden
new file mode 100644
index 0000000..5df85fd
--- /dev/null
+++ b/tools/gn/format_test_data/065.golden
@@ -0,0 +1,8 @@
+source_set("test") {
+  some_target_name = ":some_target"
+  deps = [
+    ":another_target",
+    "//last_target",
+    some_target_name,
+  ]
+}
diff --git a/tools/gn/parse_tree.cc b/tools/gn/parse_tree.cc
index 5e5b9ea..d22adca 100644
--- a/tools/gn/parse_tree.cc
+++ b/tools/gn/parse_tree.cc
@@ -5,6 +5,7 @@
 #include "tools/gn/parse_tree.h"
 
 #include <string>
+#include <tuple>
 
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
@@ -15,6 +16,20 @@
 
 namespace {
 
+std::tuple<base::StringPiece, base::StringPiece> SplitAtFirst(
+    base::StringPiece str,
+    char c) {
+  if (!str.starts_with("\"") || !str.ends_with("\""))
+    return std::make_tuple(str, base::StringPiece());
+
+  str = str.substr(1, str.length() - 2);
+  size_t index_of_first = str.find(c);
+  return std::make_tuple(str.substr(0, index_of_first),
+                         index_of_first != base::StringPiece::npos
+                             ? str.substr(index_of_first + 1)
+                             : base::StringPiece());
+}
+
 std::string IndentFor(int value) {
   return std::string(value, ' ');
 }
@@ -502,10 +517,21 @@
     end_->Print(out, indent + 1);
 }
 
-void ListNode::SortAsStringsList() {
-  // Sorts alphabetically. Partitions first on BlockCommentNodes and sorts each
-  // partition separately.
+template <typename Comparator>
+void ListNode::SortList(Comparator comparator) {
+  // Partitions first on BlockCommentNodes and sorts each partition separately.
   for (auto sr : GetSortRanges()) {
+    bool skip = false;
+    for (size_t i = sr.begin; i != sr.end; ++i) {
+      // Bails out if any of the nodes are unsupported.
+      const ParseNode* node = contents_[i];
+      if (!node->AsLiteral() && !node->AsIdentifier() && !node->AsAccessor()) {
+        skip = true;
+        continue;
+      }
+    }
+    if (skip)
+      continue;
     // Save the original line number so that we can re-assign ranges. We assume
     // they're contiguous lines because GetSortRanges() does so above. We need
     // to re-assign these line numbers primiarily because `gn format` uses them
@@ -514,11 +540,7 @@
     int start_line = contents_[sr.begin]->GetRange().begin().line_number();
     const ParseNode* original_first = contents_[sr.begin];
     std::sort(contents_.begin() + sr.begin, contents_.begin() + sr.end,
-              [](const ParseNode* a, const ParseNode* b) {
-                base::StringPiece astr = GetStringRepresentation(a);
-                base::StringPiece bstr = GetStringRepresentation(b);
-                return astr < bstr;
-              });
+              comparator);
     // If the beginning of the range had before comments, and the first node
     // moved during the sort, then move its comments to the new head of the
     // range.
@@ -553,6 +575,25 @@
   }
 }
 
+void ListNode::SortAsStringsList() {
+  // Sorts alphabetically.
+  SortList([](const ParseNode* a, const ParseNode* b) {
+    base::StringPiece astr = GetStringRepresentation(a);
+    base::StringPiece bstr = GetStringRepresentation(b);
+    return astr < bstr;
+  });
+}
+
+void ListNode::SortAsDepsList() {
+  // Sorts first relative targets, then absolute, each group is sorted
+  // alphabetically.
+  SortList([](const ParseNode* a, const ParseNode* b) {
+    base::StringPiece astr = GetStringRepresentation(a);
+    base::StringPiece bstr = GetStringRepresentation(b);
+    return SplitAtFirst(astr, ':') < SplitAtFirst(bstr, ':');
+  });
+}
+
 // Breaks the ParseNodes of |contents| up by ranges that should be separately
 // sorted. In particular, we break at a block comment, or an item that has an
 // attached "before" comment and is separated by a blank line from the item
diff --git a/tools/gn/parse_tree.h b/tools/gn/parse_tree.h
index b134d4a..36c511a 100644
--- a/tools/gn/parse_tree.h
+++ b/tools/gn/parse_tree.h
@@ -372,6 +372,7 @@
   const std::vector<const ParseNode*>& contents() const { return contents_; }
 
   void SortAsStringsList();
+  void SortAsDepsList();
 
   // During formatting, do we want this list to always be multliline? This is
   // used to make assignments to deps, sources, etc. always be multiline lists,
@@ -390,6 +391,9 @@
   std::vector<SortRange> GetSortRanges() const;
 
  private:
+  template <typename Comparator>
+  void SortList(Comparator comparator);
+
   // Tokens corresponding to the [ and ]. The end token is stored in inside an
   // custom parse node so that it can have comments hung off of it.
   Token begin_token_;