Sort import() lines by group
Test: Test cases 071.gn 072.gn 073.gn added
Change-Id: I70417520e4534f65bd827e8cfe56d97b3d9f68ca
Reviewed-on: https://gn-review.googlesource.com/c/2980
Reviewed-by: Brett Wilson <brettw@google.com>
Commit-Queue: Scott Graham <scottmg@chromium.org>
diff --git a/tools/gn/command_format.cc b/tools/gn/command_format.cc
index 3b7a54e..d656dd8 100644
--- a/tools/gn/command_format.cc
+++ b/tools/gn/command_format.cc
@@ -111,7 +111,6 @@
// Format a list of values using the given style.
enum SequenceStyle {
kSequenceStyleList,
- kSequenceStyleBlock,
kSequenceStyleBracedBlock,
};
@@ -148,6 +147,11 @@
// (both sorted alphabetically).
void SortIfSourcesOrDeps(const BinaryOpNode* binop);
+ // Sort contiguous import() function calls in the given ordered list of
+ // statements (the body of a block or scope).
+ template <class PARSENODE>
+ void SortImports(std::vector<std::unique_ptr<PARSENODE>>& statements);
+
// 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
// vertical whitespace added.
@@ -343,6 +347,90 @@
}
}
+template <class PARSENODE>
+void Printer::SortImports(std::vector<std::unique_ptr<PARSENODE>>& statements) {
+ // Build a set of ranges by indices of FunctionCallNode's that are imports.
+
+ std::vector<std::vector<size_t>> import_statements;
+
+ auto is_import = [](const PARSENODE* p) {
+ const FunctionCallNode* func_call = p->AsFunctionCall();
+ return func_call && func_call->function().value() == "import";
+ };
+
+ std::vector<size_t> current_group;
+ for (size_t i = 0; i < statements.size(); ++i) {
+ if (is_import(statements[i].get())) {
+ if (i > 0 && (!is_import(statements[i - 1].get()) ||
+ ShouldAddBlankLineInBetween(statements[i - 1].get(),
+ statements[i].get()))) {
+ if (!current_group.empty()) {
+ import_statements.push_back(current_group);
+ current_group.clear();
+ }
+ }
+ current_group.push_back(i);
+ }
+ }
+
+ if (!current_group.empty())
+ import_statements.push_back(current_group);
+
+ struct CompareByImportFile {
+ bool operator()(const std::unique_ptr<PARSENODE>& a,
+ const std::unique_ptr<PARSENODE>& b) const {
+ const auto& a_args = a->AsFunctionCall()->args()->contents();
+ const auto& b_args = b->AsFunctionCall()->args()->contents();
+ base::StringPiece a_name;
+ base::StringPiece b_name;
+ if (!a_args.empty())
+ a_name = a_args[0]->AsLiteral()->value().value();
+ if (!b_args.empty())
+ b_name = b_args[0]->AsLiteral()->value().value();
+
+ auto is_absolute = [](base::StringPiece import) {
+ return import.size() >= 3 && import[0] == '"' && import[1] == '/' &&
+ import[2] == '/';
+ };
+ int a_is_rel = !is_absolute(a_name);
+ int b_is_rel = !is_absolute(b_name);
+
+ return std::tie(a_is_rel, a_name) < std::tie(b_is_rel, b_name);
+ }
+ };
+
+ int line_after_previous = -1;
+
+ for (const auto& group : import_statements) {
+ size_t begin = group[0];
+ size_t end = group.back() + 1;
+
+ // Save the original line number so that ranges can be re-assigned. They're
+ // contiguous because of the partitioning code above. Later formatting
+ // relies on correct line number to know whether to insert blank lines,
+ // which is why these need to be fixed up. Additionally, to handle multiple
+ // imports on one line, they're assigned sequential line numbers, and
+ // subsequent blocks will be gapped from them.
+ int start_line =
+ std::max(statements[begin]->GetRange().begin().line_number(),
+ line_after_previous + 1);
+
+ std::sort(statements.begin() + begin, statements.begin() + end,
+ CompareByImportFile());
+
+ const PARSENODE* prev = nullptr;
+ for (size_t i = begin; i < end; ++i) {
+ const PARSENODE* node = statements[i].get();
+ int line_number =
+ prev ? prev->GetRange().end().line_number() + 1 : start_line;
+ const_cast<FunctionCallNode*>(node->AsFunctionCall())
+ ->SetNewLocation(line_number);
+ prev = node;
+ line_after_previous = line_number + 1;
+ }
+ }
+}
+
bool Printer::ShouldAddBlankLineInBetween(const ParseNode* a,
const ParseNode* b) {
LocationRange a_range = a->GetRange();
@@ -382,6 +470,9 @@
}
}
+ SortImports(const_cast<std::vector<std::unique_ptr<ParseNode>>&>(
+ block->statements()));
+
size_t i = 0;
for (const auto& stmt : block->statements()) {
Expr(stmt.get(), kPrecedenceLowest, std::string());
@@ -692,8 +783,10 @@
else if (style == kSequenceStyleBracedBlock)
Print("{");
- if (style == kSequenceStyleBlock || style == kSequenceStyleBracedBlock)
+ if (style == kSequenceStyleBracedBlock) {
force_multiline = true;
+ SortImports(const_cast<std::vector<std::unique_ptr<PARSENODE>>&>(list));
+ }
force_multiline |= ListWillBeMultiline(list, end);
diff --git a/tools/gn/command_format_unittest.cc b/tools/gn/command_format_unittest.cc
index cd69e5d..db63a8c 100644
--- a/tools/gn/command_format_unittest.cc
+++ b/tools/gn/command_format_unittest.cc
@@ -106,3 +106,6 @@
FORMAT_TEST(068)
FORMAT_TEST(069)
FORMAT_TEST(070)
+FORMAT_TEST(071)
+FORMAT_TEST(072)
+FORMAT_TEST(073)
diff --git a/tools/gn/format_test_data/028.golden b/tools/gn/format_test_data/028.golden
index a1d54c5..9506896 100644
--- a/tools/gn/format_test_data/028.golden
+++ b/tools/gn/format_test_data/028.golden
@@ -1,6 +1,6 @@
# Don't separate these.
-import("wee.gni")
import("waa.gni")
+import("wee.gni")
import("woo.gni")
diff --git a/tools/gn/format_test_data/071.gn b/tools/gn/format_test_data/071.gn
new file mode 100644
index 0000000..665d099
--- /dev/null
+++ b/tools/gn/format_test_data/071.gn
@@ -0,0 +1,26 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("z.gni")
+import("x.gni")
+import("y.gni")
+
+import("b.gni")
+import("a.gni")
+
+
+
+import("m.gni")
+import("d1.gni")
+# Comment here
+import("c1.gni")
+
+import("../something/relative.gni")
+import("//build/stuff.gni")
+import("nopath.gni")
+import("//abc/things.gni")
+
+import("")
+import()
+import("a")
diff --git a/tools/gn/format_test_data/071.golden b/tools/gn/format_test_data/071.golden
new file mode 100644
index 0000000..6d8f98f
--- /dev/null
+++ b/tools/gn/format_test_data/071.golden
@@ -0,0 +1,25 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("x.gni")
+import("y.gni")
+import("z.gni")
+
+import("a.gni")
+import("b.gni")
+
+import("d1.gni")
+import("m.gni")
+
+# Comment here
+import("c1.gni")
+
+import("//abc/things.gni")
+import("//build/stuff.gni")
+import("../something/relative.gni")
+import("nopath.gni")
+
+import()
+import("")
+import("a")
diff --git a/tools/gn/format_test_data/072.gn b/tools/gn/format_test_data/072.gn
new file mode 100644
index 0000000..10b5a81
--- /dev/null
+++ b/tools/gn/format_test_data/072.gn
@@ -0,0 +1,8 @@
+import("b") import("c") import("a") import("d")
+
+import("z") declare_args() {} import("y") import("x") import("w")
+
+import("3") import("2") import("1")
+
+import("x") import("y")
+import("z") import("w")
diff --git a/tools/gn/format_test_data/072.golden b/tools/gn/format_test_data/072.golden
new file mode 100644
index 0000000..b4e5a4b
--- /dev/null
+++ b/tools/gn/format_test_data/072.golden
@@ -0,0 +1,21 @@
+import("a")
+import("b")
+import("c")
+import("d")
+
+import("z")
+declare_args() {
+}
+
+import("w")
+import("x")
+import("y")
+
+import("1")
+import("2")
+import("3")
+
+import("w")
+import("x")
+import("y")
+import("z")
diff --git a/tools/gn/format_test_data/073.gn b/tools/gn/format_test_data/073.gn
new file mode 100644
index 0000000..e3a6561
--- /dev/null
+++ b/tools/gn/format_test_data/073.gn
@@ -0,0 +1,23 @@
+import("//root/a")
+import("//root/c")
+import("//root/b")
+
+if (stuff) {
+import("3") import("2") import("1")
+ if (things) {
+ import("x")
+ import("z") import("y") import("w") import("q")
+
+ import("f") import("e") if (other) {import("z") import("a")} import("d")
+
+ template("wee") {
+ import("6")
+ import("7")
+ import("5")
+ }
+ }
+} else {
+ import("i")
+ import("h")
+ import("g")
+}
diff --git a/tools/gn/format_test_data/073.golden b/tools/gn/format_test_data/073.golden
new file mode 100644
index 0000000..e32977b
--- /dev/null
+++ b/tools/gn/format_test_data/073.golden
@@ -0,0 +1,34 @@
+import("//root/a")
+import("//root/b")
+import("//root/c")
+
+if (stuff) {
+ import("1")
+ import("2")
+ import("3")
+ if (things) {
+ import("q")
+ import("w")
+ import("x")
+ import("y")
+ import("z")
+
+ import("e")
+ import("f")
+ if (other) {
+ import("a")
+ import("z")
+ }
+
+ import("d")
+ template("wee") {
+ import("5")
+ import("6")
+ import("7")
+ }
+ }
+} else {
+ import("g")
+ import("h")
+ import("i")
+}
diff --git a/tools/gn/parse_tree.cc b/tools/gn/parse_tree.cc
index e2201d4..6dde01d 100644
--- a/tools/gn/parse_tree.cc
+++ b/tools/gn/parse_tree.cc
@@ -485,6 +485,21 @@
block_->Print(out, indent + 1);
}
+void FunctionCallNode::SetNewLocation(int line_number) {
+ Location func_old_loc = function_.location();
+ Location func_new_loc =
+ Location(func_old_loc.file(), line_number, func_old_loc.column_number(),
+ func_old_loc.byte());
+ function_.set_location(func_new_loc);
+
+ Location args_old_loc = args_->Begin().location();
+ Location args_new_loc =
+ Location(args_old_loc.file(), line_number, args_old_loc.column_number(),
+ args_old_loc.byte());
+ const_cast<Token&>(args_->Begin()).set_location(args_new_loc);
+ const_cast<Token&>(args_->End()->value()).set_location(args_new_loc);
+}
+
// IdentifierNode --------------------------------------------------------------
IdentifierNode::IdentifierNode() = default;
diff --git a/tools/gn/parse_tree.h b/tools/gn/parse_tree.h
index 415041e..9927307 100644
--- a/tools/gn/parse_tree.h
+++ b/tools/gn/parse_tree.h
@@ -334,6 +334,8 @@
const BlockNode* block() const { return block_.get(); }
void set_block(std::unique_ptr<BlockNode> b) { block_ = std::move(b); }
+ void SetNewLocation(int line_number);
+
private:
Token function_;
std::unique_ptr<ListNode> args_;
@@ -385,6 +387,7 @@
void Print(std::ostream& out, int indent) const override;
void set_begin_token(const Token& t) { begin_token_ = t; }
+ const Token& Begin() const { return begin_token_; }
void set_end(std::unique_ptr<EndNode> e) { end_ = std::move(e); }
const EndNode* End() const { return end_.get(); }