Shorten targets from //path/to/foo:foo to //path/to/foo
Improve formatting to remove redundancy that often results from
developers copy and pasting pedantic output from "gn check".
Bug: 40760278
Change-Id: I068e7121c67790e1347d498ef1aa487873e6ba73
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/19322
Commit-Queue: Takuto Ikuta <tikuta@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/command_format.cc b/src/gn/command_format.cc
index 1a6f9ec..c9b9233 100644
--- a/src/gn/command_format.cc
+++ b/src/gn/command_format.cc
@@ -163,6 +163,10 @@
// Whether there's a blank separator line at the current position.
bool HaveBlankLine();
+ // Shorten entries if possible. e.g. Shorten "//path/to/foo:foo" to
+ // "/path/to/foo". Applies to 'visibility', 'deps', or ends in 'deps'.
+ void ShortenIfApplicable(const BinaryOpNode* binop);
+
// Sort a list on the RHS if the LHS is one of the following:
// 'sources': sorted alphabetically.
// 'deps' or ends in 'deps': sorted such that relative targets are first,
@@ -396,6 +400,23 @@
return n > 2 && output_[n - 1] == '\n' && output_[n - 2] == '\n';
}
+void Printer::ShortenIfApplicable(const BinaryOpNode* binop) {
+ const IdentifierNode* ident = binop->left()->AsIdentifier();
+ if ((binop->op().value() == "=" || binop->op().value() == "+=" ||
+ binop->op().value() == "-=") &&
+ ident) {
+ const std::string_view lhs = ident->value().value();
+ if (lhs.ends_with("deps") || lhs == "visibility") {
+ TraverseBinaryOpNode(binop->right(), [](const ParseNode* node) {
+ const ListNode* list = node->AsList();
+ if (list) {
+ const_cast<ListNode*>(list)->ShortenTargets();
+ }
+ });
+ }
+ }
+}
+
void Printer::SortIfApplicable(const BinaryOpNode* binop) {
if (const Comments* comments = binop->comments()) {
const std::vector<Token>& before = comments->before();
@@ -757,6 +778,8 @@
} else if (const BinaryOpNode* binop = root->AsBinaryOp()) {
CHECK(precedence_.find(binop->op().value()) != precedence_.end());
+ // Shorten before sorting, since the shortening may affect the ordering.
+ ShortenIfApplicable(binop);
SortIfApplicable(binop);
Precedence prec = precedence_[binop->op().value()];
diff --git a/src/gn/format_test_data/063.golden b/src/gn/format_test_data/063.golden
index 8972886..73cfc04 100644
--- a/src/gn/format_test_data/063.golden
+++ b/src/gn/format_test_data/063.golden
@@ -5,13 +5,13 @@
":a",
":b",
"a",
- "a:a",
+ "a",
"a:b",
"a/a",
"a/b",
"b",
"//a",
- "//a:a",
+ "//a",
"//a:b",
"//a/a",
"//a/b",
@@ -25,10 +25,10 @@
public_deps += [
":a",
"a",
- "a:a",
+ "a",
"a/a",
"//a",
- "//a:a",
+ "//a",
"//a/a",
a,
]
diff --git a/src/gn/parse_tree.cc b/src/gn/parse_tree.cc
index feccdb2..b9f04f1 100644
--- a/src/gn/parse_tree.cc
+++ b/src/gn/parse_tree.cc
@@ -996,6 +996,14 @@
}
}
+void ListNode::ShortenTargets() {
+ for (auto& cur : contents_) {
+ if (cur->AsLiteral()) {
+ const_cast<LiteralNode*>(cur->AsLiteral())->ShortenTarget();
+ }
+ }
+}
+
void ListNode::SortAsStringsList() {
// Sorts alphabetically.
SortList([](const ParseNode* a, const ParseNode* b) {
@@ -1147,6 +1155,48 @@
value_.set_location(Location(old.file(), line_number, old.column_number()));
}
+void LiteralNode::ShortenTarget() {
+ std::string_view str = value_.value();
+ DepsCategory category = GetDepsCategory(str);
+ if (category != DEPS_CATEGORY_RELATIVE &&
+ category != DEPS_CATEGORY_ABSOLUTE) {
+ return;
+ }
+
+ str = str.substr(1, str.length() - 2); // Remove quotes.
+
+ // Slashes may not exist in relative paths.
+ size_t last_slash = str.rfind('/');
+ if (last_slash != std::string::npos) {
+ str = str.substr(last_slash + 1);
+ }
+
+ // Split on the last colon.
+ size_t last_separator = str.rfind(':');
+ if (last_separator == std::string::npos) {
+ return;
+ }
+
+ std::string_view lhs = str.substr(0, last_separator);
+ std::string_view rhs = str.substr(last_separator + 1);
+ if (lhs != rhs) {
+ return;
+ }
+
+ // Even if the two sides match, sanity check there are no other colons.
+ last_separator = lhs.rfind(':');
+ if (last_separator != std::string::npos) {
+ return;
+ }
+
+ // Accounts for ':' and '"'.
+ size_t new_length = value_.value().length() - lhs.length() - 2;
+ shortened_value_ = value_.value().substr(0, new_length);
+ // Then put the '"' back.
+ shortened_value_.push_back('"');
+ value_ = Token(value_.location(), value_.type(), shortened_value_);
+}
+
// UnaryOpNode ----------------------------------------------------------------
UnaryOpNode::UnaryOpNode() = default;
diff --git a/src/gn/parse_tree.h b/src/gn/parse_tree.h
index 2ed8c54..6ad2aec 100644
--- a/src/gn/parse_tree.h
+++ b/src/gn/parse_tree.h
@@ -8,6 +8,7 @@
#include <stddef.h>
#include <memory>
+#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -467,6 +468,7 @@
return contents_;
}
+ void ShortenTargets();
void SortAsStringsList();
void SortAsTargetsList();
@@ -516,11 +518,13 @@
void set_value(const Token& t) { value_ = t; }
void SetNewLocation(int line_number);
+ void ShortenTarget();
static constexpr const char* kDumpNodeName = "LITERAL";
private:
Token value_;
+ std::string shortened_value_;
LiteralNode(const LiteralNode&) = delete;
LiteralNode& operator=(const LiteralNode&) = delete;
diff --git a/src/gn/parse_tree_unittest.cc b/src/gn/parse_tree_unittest.cc
index 9b684f0..d72e579 100644
--- a/src/gn/parse_tree_unittest.cc
+++ b/src/gn/parse_tree_unittest.cc
@@ -6,9 +6,11 @@
#include <stdint.h>
+#include <algorithm>
#include <memory>
#include <utility>
+#include "base/containers/span.h"
#include "gn/input_file.h"
#include "gn/scope.h"
#include "gn/test_with_scope.h"
@@ -172,6 +174,62 @@
EXPECT_EQ(20, err.location().column_number());
}
+TEST(ParseTree, ShortenTargets) {
+ TestParseInput input(
+ "deps = [\n"
+ " \":abc\",\n"
+ " \"bcd/efg\",\n"
+ " \"cde/fgh:fgh\",\n"
+ " \"cde/fgh:fghz\",\n"
+ " \"//def/ghi\",\n"
+ " \"//efg/hij:hij\",\n"
+ " \"//efg/hij:hihihi\",\n"
+ "]\n");
+ EXPECT_FALSE(input.has_error());
+ ASSERT_TRUE(input.parsed()->AsBlock());
+ ASSERT_TRUE(input.parsed()->AsBlock()->statements()[0]->AsBinaryOp());
+ const BinaryOpNode* binop =
+ input.parsed()->AsBlock()->statements()[0]->AsBinaryOp();
+ ASSERT_TRUE(binop->right()->AsList());
+ auto* list = const_cast<ListNode*>(binop->right()->AsList());
+ const auto& contents = list->contents();
+ ASSERT_EQ(7u, contents.size());
+
+ auto all_elements_are_literal_nodes =
+ [](base::span<const std::unique_ptr<const ParseNode>> container) -> bool {
+ // TODO(thestig): Switch to std::ranges::all_of() when the CI/CQ bots all
+ // support it.
+ return std::all_of(container.begin(), container.end(),
+ [](const std::unique_ptr<const ParseNode>& element) {
+ return element->AsLiteral();
+ });
+ };
+
+ auto get_literal_value = [](const ParseNode& node) {
+ return node.AsLiteral()->value().value();
+ };
+
+ ASSERT_TRUE(all_elements_are_literal_nodes(contents));
+ EXPECT_EQ("\":abc\"", get_literal_value(*contents[0]));
+ EXPECT_EQ("\"bcd/efg\"", get_literal_value(*contents[1]));
+ EXPECT_EQ("\"cde/fgh:fgh\"", get_literal_value(*contents[2]));
+ EXPECT_EQ("\"cde/fgh:fghz\"", get_literal_value(*contents[3]));
+ EXPECT_EQ("\"//def/ghi\"", get_literal_value(*contents[4]));
+ EXPECT_EQ("\"//efg/hij:hij\"", get_literal_value(*contents[5]));
+ EXPECT_EQ("\"//efg/hij:hihihi\"", get_literal_value(*contents[6]));
+
+ list->ShortenTargets();
+
+ ASSERT_TRUE(all_elements_are_literal_nodes(contents));
+ EXPECT_EQ("\":abc\"", get_literal_value(*contents[0]));
+ EXPECT_EQ("\"bcd/efg\"", get_literal_value(*contents[1]));
+ EXPECT_EQ("\"cde/fgh\"", get_literal_value(*contents[2]));
+ EXPECT_EQ("\"cde/fgh:fghz\"", get_literal_value(*contents[3]));
+ EXPECT_EQ("\"//def/ghi\"", get_literal_value(*contents[4]));
+ EXPECT_EQ("\"//efg/hij\"", get_literal_value(*contents[5]));
+ EXPECT_EQ("\"//efg/hij:hihihi\"", get_literal_value(*contents[6]));
+}
+
TEST(ParseTree, SortRangeExtraction) {
TestWithScope setup;