Index: tools/gn/parse_tree.cc |
diff --git a/tools/gn/parse_tree.cc b/tools/gn/parse_tree.cc |
index 74f493b601741f7a36e970674118b79635a4ca55..503cb98e8f1e157b4e03468cb951bbb89135af97 100644 |
--- a/tools/gn/parse_tree.cc |
+++ b/tools/gn/parse_tree.cc |
@@ -19,6 +19,27 @@ 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. |
+ return node->AsBlockComment() != nullptr || |
+ (prev && node->comments() && !node->comments()->before().empty() && |
+ (node->GetRange().begin().line_number() > |
+ prev->GetRange().end().line_number() + |
+ static_cast<int>(node->comments()->before().size() + 1))); |
+} |
+ |
+base::StringPiece GetStringRepresentation(const ParseNode* node) { |
+ DCHECK(node->AsLiteral() || node->AsIdentifier() || node->AsAccessor()); |
+ if (node->AsLiteral()) |
+ return node->AsLiteral()->value().value(); |
+ else if (node->AsIdentifier()) |
+ return node->AsIdentifier()->value().value(); |
+ else if (node->AsAccessor()) |
+ return node->AsAccessor()->base().value(); |
+ return base::StringPiece(); |
+} |
+ |
} // namespace |
Comments::Comments() { |
@@ -194,6 +215,12 @@ Value AccessorNode::ExecuteScopeAccess(Scope* scope, Err* err) const { |
return *result; |
} |
+void AccessorNode::SetNewLocation(int line_number) { |
+ Location old = base_.location(); |
+ base_.set_location( |
+ Location(old.file(), line_number, old.char_offset(), old.byte())); |
+} |
+ |
// BinaryOpNode --------------------------------------------------------------- |
BinaryOpNode::BinaryOpNode() { |
@@ -436,6 +463,12 @@ void IdentifierNode::Print(std::ostream& out, int indent) const { |
PrintComments(out, indent); |
} |
+void IdentifierNode::SetNewLocation(int line_number) { |
+ Location old = value_.location(); |
+ value_.set_location( |
+ Location(old.file(), line_number, old.char_offset(), old.byte())); |
+} |
+ |
// ListNode ------------------------------------------------------------------- |
ListNode::ListNode() : prefer_multiline_(false) { |
@@ -490,6 +523,116 @@ void ListNode::Print(std::ostream& out, int indent) const { |
end_->Print(out, indent + 1); |
} |
+void ListNode::SortAsStringsList() { |
+ // Sorts alphabetically. Partitions first on BlockCommentNodes and sorts each |
+ // partition separately. |
+ for (auto sr : GetSortRanges()) { |
+ // 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 |
+ // to determine whether two nodes were initially separated by a blank line |
+ // or not. |
+ 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; |
+ }); |
+ // 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. |
+ if (original_first->comments() && contents_[sr.begin] != original_first) { |
+ for (const auto& hc : original_first->comments()->before()) { |
+ const_cast<ParseNode*>(contents_[sr.begin]) |
+ ->comments_mutable() |
+ ->append_before(hc); |
+ } |
+ const_cast<ParseNode*>(original_first) |
+ ->comments_mutable() |
+ ->clear_before(); |
+ } |
+ const ParseNode* prev = nullptr; |
+ for (size_t i = sr.begin; i != sr.end; ++i) { |
+ const ParseNode* node = contents_[i]; |
+ DCHECK(node->AsLiteral() || node->AsIdentifier() || node->AsAccessor()); |
+ int line_number = |
+ prev ? prev->GetRange().end().line_number() + 1 : start_line; |
+ if (node->AsLiteral()) { |
+ const_cast<LiteralNode*>(node->AsLiteral()) |
+ ->SetNewLocation(line_number); |
+ } else if (node->AsIdentifier()) { |
+ const_cast<IdentifierNode*>(node->AsIdentifier()) |
+ ->SetNewLocation(line_number); |
+ } else if (node->AsAccessor()) { |
+ const_cast<AccessorNode*>(node->AsAccessor()) |
+ ->SetNewLocation(line_number); |
+ } |
+ prev = node; |
+ } |
+ } |
+} |
+ |
+// 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 |
+// before it. The assumption is that both of these indicate a separate 'section' |
+// of a sources block across which items should not be inter-sorted. |
+std::vector<ListNode::SortRange> ListNode::GetSortRanges() const { |
+ std::vector<SortRange> ranges; |
+ const ParseNode* prev = nullptr; |
+ size_t begin = 0; |
+ for (size_t i = begin; i < contents_.size(); prev = contents_[i++]) { |
+ if (IsSortRangeSeparator(contents_[i], prev)) { |
+ if (i > begin) { |
+ ranges.push_back(SortRange(begin, i)); |
+ // If |i| is an item with an attached comment, then we start the next |
+ // range at that point, because we want to include it in the sort. |
+ // Otherwise, it's a block comment which we skip over entirely because |
+ // we don't want to move or include it in the sort. The two cases are: |
+ // |
+ // sources = [ |
+ // "a", |
+ // "b", |
+ // |
+ // # |
+ // # This is a block comment. |
+ // # |
+ // |
+ // "c", |
+ // "d", |
+ // ] |
+ // |
+ // which contains 5 elements, and for which the ranges would be { [0, |
+ // 2), [3, 5) } (notably excluding 2, the block comment), and: |
+ // |
+ // sources = [ |
+ // "a", |
+ // "b", |
+ // |
+ // # This is a header comment. |
+ // "c", |
+ // "d", |
+ // ] |
+ // |
+ // which contains 4 elements, index 2 containing an attached 'before' |
+ // comments, and the ranges should be { [0, 2), [2, 4) }. |
+ if (!contents_[i]->AsBlockComment()) |
+ begin = i; |
+ else |
+ begin = i + 1; |
+ } else { |
+ // If it was a one item range, just skip over it. |
+ begin = i + 1; |
+ } |
+ } |
+ } |
+ if (begin != contents_.size()) |
+ ranges.push_back(SortRange(begin, contents_.size())); |
+ return ranges; |
+} |
+ |
// LiteralNode ----------------------------------------------------------------- |
LiteralNode::LiteralNode() { |
@@ -544,6 +687,12 @@ void LiteralNode::Print(std::ostream& out, int indent) const { |
PrintComments(out, indent); |
} |
+void LiteralNode::SetNewLocation(int line_number) { |
+ Location old = value_.location(); |
+ value_.set_location( |
+ Location(old.file(), line_number, old.char_offset(), old.byte())); |
+} |
+ |
// UnaryOpNode ---------------------------------------------------------------- |
UnaryOpNode::UnaryOpNode() { |
@@ -608,7 +757,6 @@ void BlockCommentNode::Print(std::ostream& out, int indent) const { |
PrintComments(out, indent); |
} |
- |
// EndNode --------------------------------------------------------------------- |
EndNode::EndNode(const Token& token) : value_(token) { |