blob: e94fb4fbc6d0e78f3364a246948a6f5c0b9a8f56 [file] [log] [blame]
// Copyright 2014 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.
#include "gn/command_format.h"
#include <stddef.h>
#include <sstream>
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/macros.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "gn/commands.h"
#include "gn/filesystem_utils.h"
#include "gn/input_file.h"
#include "gn/parser.h"
#include "gn/scheduler.h"
#include "gn/setup.h"
#include "gn/source_file.h"
#include "gn/string_utils.h"
#include "gn/switches.h"
#include "gn/tokenizer.h"
#include "util/build_config.h"
#if defined(OS_WIN)
#include <fcntl.h>
#include <io.h>
#endif
namespace commands {
const char kSwitchDryRun[] = "dry-run";
const char kSwitchDumpTree[] = "dump-tree";
const char kSwitchReadTree[] = "read-tree";
const char kSwitchStdin[] = "stdin";
const char kSwitchTreeTypeJSON[] = "json";
const char kSwitchTreeTypeText[] = "text";
const char kFormat[] = "format";
const char kFormat_HelpShort[] = "format: Format .gn files.";
const char kFormat_Help[] =
R"(gn format [--dump-tree] (--stdin | <list of build_files...>)
Formats .gn file to a standard format.
The contents of some lists ('sources', 'deps', etc.) will be sorted to a
canonical order. To suppress this, you can add a comment of the form "#
NOSORT" immediately preceding the assignment. e.g.
# NOSORT
sources = [
"z.cc",
"a.cc",
]
Arguments
--dry-run
Prints the list of files that would be reformatted but does not write
anything to disk. This is useful for presubmit/lint-type checks.
- Exit code 0: successful format, matches on disk.
- Exit code 1: general failure (parse error, etc.)
- Exit code 2: successful format, but differs from on disk.
--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
in-place.
--read-tree=json
Reads an AST from stdin in the format output by --dump-tree=json and
uses that as the parse tree. (The only read-tree format currently
supported is json.) The given .gn file will be overwritten. This can be
used to programmatically transform .gn files.
Examples
gn format //some/BUILD.gn //some/other/BUILD.gn //and/another/BUILD.gn
gn format some\\BUILD.gn
gn format /abspath/some/BUILD.gn
gn format --stdin
gn format --read-tree=json //rewritten/BUILD.gn
)";
namespace {
const int kIndentSize = 2;
const int kMaximumWidth = 80;
const int kPenaltyLineBreak = 500;
const int kPenaltyHorizontalSeparation = 100;
const int kPenaltyExcess = 10000;
const int kPenaltyBrokenLineOnOneLiner = 5000;
enum Precedence {
kPrecedenceLowest,
kPrecedenceAssign,
kPrecedenceOr,
kPrecedenceAnd,
kPrecedenceCompare,
kPrecedenceAdd,
kPrecedenceUnary,
kPrecedenceSuffix,
};
int CountLines(const std::string& str) {
return static_cast<int>(base::SplitStringPiece(str, "\n",
base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL)
.size());
}
class Printer {
public:
Printer();
~Printer();
void Block(const ParseNode* file);
std::string String() const { return output_; }
private:
// Format a list of values using the given style.
enum SequenceStyle {
kSequenceStyleList,
kSequenceStyleBracedBlock,
kSequenceStyleBracedBlockAlreadyOpen,
};
struct Metrics {
Metrics() : first_length(-1), longest_length(-1), multiline(false) {}
int first_length;
int longest_length;
bool multiline;
};
// Add to output.
void Print(std::string_view str);
// Add the current margin (as spaces) to the output.
void PrintMargin();
void TrimAndPrintToken(const Token& token);
void PrintTrailingCommentsWrapped(const std::vector<Token>& comments);
void FlushComments();
void PrintSuffixComments(const ParseNode* node);
// End the current line, flushing end of line comments.
void Newline();
// Remove trailing spaces from the current line.
void Trim();
// Whether there's a blank separator line at the current position.
bool HaveBlankLine();
// 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,
// followed by global targets, each internally sorted alphabetically.
// 'visibility': same as 'deps'.
void SortIfApplicable(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.
bool ShouldAddBlankLineInBetween(const ParseNode* a, const ParseNode* b);
// Get the 0-based x position on the current line.
int CurrentColumn() const;
// Get the current line in the output;
int CurrentLine() const;
// Adds an opening ( if prec is less than the outers (to maintain evalution
// order for a subexpression). If an opening paren is emitted, *parenthesized
// will be set so it can be closed at the end of the expression.
void AddParen(int prec, int outer_prec, bool* parenthesized);
// Print the expression given by |root| to the output buffer and appends
// |suffix| to that output. Returns a penalty that represents the cost of
// adding that output to the buffer (where higher is worse). The value of
// outer_prec gives the precedence of the operator outside this Expr. If that
// operator binds tighter than root's, Expr() must introduce parentheses.
int Expr(const ParseNode* root, int outer_prec, const std::string& suffix);
// Generic penalties for exceeding maximum width, adding more lines, etc.
int AssessPenalty(const std::string& output);
// Tests if any lines exceed the maximum width.
bool ExceedsMaximumWidth(const std::string& output);
// Format a list of values using the given style.
// |end| holds any trailing comments to be printed just before the closing
// bracket.
template <class PARSENODE> // Just for const covariance.
void Sequence(SequenceStyle style,
const std::vector<std::unique_ptr<PARSENODE>>& list,
const ParseNode* end,
bool force_multiline);
// Returns the penalty.
int FunctionCall(const FunctionCallNode* func_call,
const std::string& suffix);
// Create a clone of this Printer in a similar state (other than the output,
// but including margins, etc.) to be used for dry run measurements.
void InitializeSub(Printer* sub);
template <class PARSENODE>
bool ListWillBeMultiline(const std::vector<std::unique_ptr<PARSENODE>>& list,
const ParseNode* end);
std::string output_; // Output buffer.
std::vector<Token> comments_; // Pending end-of-line comments.
int margin() const { return stack_.back().margin; }
int penalty_depth_;
int GetPenaltyForLineBreak() const {
return penalty_depth_ * kPenaltyLineBreak;
}
struct IndentState {
IndentState()
: margin(0),
continuation_requires_indent(false),
parent_is_boolean_or(false) {}
IndentState(int margin,
bool continuation_requires_indent,
bool parent_is_boolean_or)
: margin(margin),
continuation_requires_indent(continuation_requires_indent),
parent_is_boolean_or(parent_is_boolean_or) {}
// The left margin (number of spaces).
int margin;
bool continuation_requires_indent;
bool parent_is_boolean_or;
};
// Stack used to track
std::vector<IndentState> stack_;
// Gives the precedence for operators in a BinaryOpNode.
std::map<std::string_view, Precedence> precedence_;
DISALLOW_COPY_AND_ASSIGN(Printer);
};
Printer::Printer() : penalty_depth_(0) {
output_.reserve(100 << 10);
precedence_["="] = kPrecedenceAssign;
precedence_["+="] = kPrecedenceAssign;
precedence_["-="] = kPrecedenceAssign;
precedence_["||"] = kPrecedenceOr;
precedence_["&&"] = kPrecedenceAnd;
precedence_["<"] = kPrecedenceCompare;
precedence_[">"] = kPrecedenceCompare;
precedence_["=="] = kPrecedenceCompare;
precedence_["!="] = kPrecedenceCompare;
precedence_["<="] = kPrecedenceCompare;
precedence_[">="] = kPrecedenceCompare;
precedence_["+"] = kPrecedenceAdd;
precedence_["-"] = kPrecedenceAdd;
precedence_["!"] = kPrecedenceUnary;
stack_.push_back(IndentState());
}
Printer::~Printer() = default;
void Printer::Print(std::string_view str) {
output_.append(str);
}
void Printer::PrintMargin() {
output_ += std::string(margin(), ' ');
}
void Printer::TrimAndPrintToken(const Token& token) {
std::string trimmed;
TrimWhitespaceASCII(std::string(token.value()), base::TRIM_ALL, &trimmed);
Print(trimmed);
}
// Assumes that the margin is set to the indent level where the comments should
// be aligned. This doesn't de-wrap, it only wraps. So if a suffix comment
// causes the line to exceed 80 col it will be wrapped, but the subsequent line
// would fit on the then-broken line it will not be merged with it. This is
// partly because it's difficult to implement at this level, but also because
// it can break hand-authored line breaks where they're starting a new paragraph
// or statement.
void Printer::PrintTrailingCommentsWrapped(const std::vector<Token>& comments) {
bool have_empty_line = true;
auto start_next_line = [this, &have_empty_line]() {
Trim();
Print("\n");
PrintMargin();
have_empty_line = true;
};
for (const auto& c : comments) {
if (!have_empty_line) {
start_next_line();
}
std::string trimmed;
TrimWhitespaceASCII(std::string(c.value()), base::TRIM_ALL, &trimmed);
if (margin() + trimmed.size() <= kMaximumWidth) {
Print(trimmed);
have_empty_line = false;
} else {
bool continuation = false;
std::vector<std::string> split_on_spaces = base::SplitString(
c.value(), " ", base::WhitespaceHandling::TRIM_WHITESPACE,
base::SplitResult::SPLIT_WANT_NONEMPTY);
for (size_t j = 0; j < split_on_spaces.size(); ++j) {
if (have_empty_line && continuation) {
Print("# ");
}
Print(split_on_spaces[j]);
Print(" ");
if (split_on_spaces[j] != "#") {
have_empty_line = false;
}
if (!have_empty_line &&
(j < split_on_spaces.size() - 1 &&
CurrentColumn() + split_on_spaces[j + 1].size() > kMaximumWidth)) {
start_next_line();
continuation = true;
}
}
}
}
}
// Used during penalty evaluation, similar to Newline().
void Printer::PrintSuffixComments(const ParseNode* node) {
if (node->comments() && !node->comments()->suffix().empty()) {
Print(" ");
stack_.push_back(IndentState(CurrentColumn(), false, false));
PrintTrailingCommentsWrapped(node->comments()->suffix());
stack_.pop_back();
}
}
void Printer::FlushComments() {
if (!comments_.empty()) {
Print(" ");
// Save the margin, and temporarily set it to where the first comment
// starts so that multiple suffix comments are vertically aligned.
stack_.push_back(IndentState(CurrentColumn(), false, false));
PrintTrailingCommentsWrapped(comments_);
stack_.pop_back();
comments_.clear();
}
}
void Printer::Newline() {
FlushComments();
Trim();
Print("\n");
PrintMargin();
}
void Printer::Trim() {
size_t n = output_.size();
while (n > 0 && output_[n - 1] == ' ')
--n;
output_.resize(n);
}
bool Printer::HaveBlankLine() {
size_t n = output_.size();
while (n > 0 && output_[n - 1] == ' ')
--n;
return n > 2 && output_[n - 1] == '\n' && output_[n - 2] == '\n';
}
void Printer::SortIfApplicable(const BinaryOpNode* binop) {
if (const Comments* comments = binop->comments()) {
const std::vector<Token>& before = comments->before();
if (!before.empty() && (before.front().value() == "# NOSORT" ||
before.back().value() == "# NOSORT")) {
// Allow disabling of sort for specific actions that might be
// order-sensitive.
return;
}
}
const IdentifierNode* ident = binop->left()->AsIdentifier();
const ListNode* list = binop->right()->AsList();
if ((binop->op().value() == "=" || binop->op().value() == "+=" ||
binop->op().value() == "-=") &&
ident && list) {
const std::string_view lhs = ident->value().value();
if (base::EndsWith(lhs, "sources", base::CompareCase::SENSITIVE) ||
lhs == "public")
const_cast<ListNode*>(list)->SortAsStringsList();
else if (base::EndsWith(lhs, "deps", base::CompareCase::SENSITIVE) ||
lhs == "visibility")
const_cast<ListNode*>(list)->SortAsTargetsList();
}
}
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();
std::string_view a_name;
std::string_view b_name;
// Non-literal imports are treated as empty names, and order is
// maintained. Arbitrarily complex expressions in import() are
// rare, and it probably doesn't make sense to sort non-string
// literals anyway, see format_test_data/083.gn.
if (!a_args.empty() && a_args[0]->AsLiteral())
a_name = a_args[0]->AsLiteral()->value().value();
if (!b_args.empty() && b_args[0]->AsLiteral())
b_name = b_args[0]->AsLiteral()->value().value();
auto is_absolute = [](std::string_view 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;
if (node->comments() && !node->comments()->before().empty())
line_number++;
const_cast<FunctionCallNode*>(node->AsFunctionCall())
->SetNewLocation(line_number);
prev = node;
line_after_previous = line_number + 1;
}
}
}
namespace {
int SuffixCommentTreeWalk(const ParseNode* node) {
// Check all the children for suffix comments. This is conceptually simple,
// but ugly as there's not a generic parse tree walker. This walker goes
// lowest child first so that if it's valid that's returned.
if (!node)
return -1;
#define RETURN_IF_SET(x) \
if (int result = (x); result >= 0) \
return result;
if (const AccessorNode* accessor = node->AsAccessor()) {
RETURN_IF_SET(SuffixCommentTreeWalk(accessor->subscript()));
RETURN_IF_SET(SuffixCommentTreeWalk(accessor->member()));
} else if (const BinaryOpNode* binop = node->AsBinaryOp()) {
RETURN_IF_SET(SuffixCommentTreeWalk(binop->right()));
} else if (const BlockNode* block = node->AsBlock()) {
RETURN_IF_SET(SuffixCommentTreeWalk(block->End()));
} else if (const ConditionNode* condition = node->AsCondition()) {
RETURN_IF_SET(SuffixCommentTreeWalk(condition->if_false()));
RETURN_IF_SET(SuffixCommentTreeWalk(condition->if_true()));
RETURN_IF_SET(SuffixCommentTreeWalk(condition->condition()));
} else if (const FunctionCallNode* func_call = node->AsFunctionCall()) {
RETURN_IF_SET(SuffixCommentTreeWalk(func_call->block()));
RETURN_IF_SET(SuffixCommentTreeWalk(func_call->args()));
} else if (node->AsIdentifier()) {
// Nothing.
} else if (const ListNode* list = node->AsList()) {
RETURN_IF_SET(SuffixCommentTreeWalk(list->End()));
} else if (node->AsLiteral()) {
// Nothing.
} else if (const UnaryOpNode* unaryop = node->AsUnaryOp()) {
RETURN_IF_SET(SuffixCommentTreeWalk(unaryop->operand()));
} else if (node->AsBlockComment()) {
// Nothing.
} else if (node->AsEnd()) {
// Nothing.
} else {
CHECK(false) << "Unhandled case in SuffixCommentTreeWalk.";
}
#undef RETURN_IF_SET
// Check this node if there are no child comments.
if (node->comments() && !node->comments()->suffix().empty()) {
return node->comments()->suffix().back().location().line_number();
}
return -1;
};
// If there are suffix comments on the first node or its children, they might
// carry down multiple lines. Otherwise, use the node's normal end range. This
// function is needed because the parse tree doesn't include comments in the
// location ranges, and it's not a straightforword change to add them. So this
// is effectively finding the "real" range for |root| including suffix comments.
// Note that it's not enough to simply look at |root|'s suffix comments because
// in the case of:
//
// a =
// b + c # something
// # or other
// x = y
//
// the comments are attached to a BinOp+ which is a child of BinOp=, not
// directly to the BinOp= which will be what's being used to determine if there
// should be a blank line inserted before the |x| line.
int FindLowestSuffixComment(const ParseNode* root) {
LocationRange range = root->GetRange();
int end = range.end().line_number();
int result = SuffixCommentTreeWalk(root);
return (result == -1 || result < end) ? end : result;
}
} // namespace
bool Printer::ShouldAddBlankLineInBetween(const ParseNode* a,
const ParseNode* b) {
LocationRange b_range = b->GetRange();
int a_end = FindLowestSuffixComment(a);
// If they're already separated by 1 or more lines, then we want to keep a
// blank line.
return (b_range.begin().line_number() > a_end + 1) ||
// Always put a blank line before a block comment.
b->AsBlockComment();
}
int Printer::CurrentColumn() const {
int n = 0;
while (n < static_cast<int>(output_.size()) &&
output_[output_.size() - 1 - n] != '\n') {
++n;
}
return n;
}
int Printer::CurrentLine() const {
int count = 1;
for (const char* p = output_.c_str(); (p = strchr(p, '\n')) != nullptr;) {
++count;
++p;
}
return count;
}
void Printer::Block(const ParseNode* root) {
const BlockNode* block = root->AsBlock();
if (block->comments()) {
for (const auto& c : block->comments()->before()) {
TrimAndPrintToken(c);
Newline();
}
}
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());
Newline();
if (stmt->comments()) {
// Why are before() not printed here too? before() are handled inside
// Expr(), as are suffix() which are queued to the next Newline().
// However, because it's a general expression handler, it doesn't insert
// the newline itself, which only happens between block statements. So,
// the after are handled explicitly here.
for (const auto& c : stmt->comments()->after()) {
TrimAndPrintToken(c);
Newline();
}
}
if (i < block->statements().size() - 1 &&
(ShouldAddBlankLineInBetween(block->statements()[i].get(),
block->statements()[i + 1].get()))) {
Newline();
}
++i;
}
if (block->comments()) {
if (!block->statements().empty() &&
block->statements().back()->AsBlockComment()) {
// If the block ends in a comment, and there's a comment following it,
// then the two comments were originally separate, so keep them that way.
Newline();
}
for (const auto& c : block->comments()->after()) {
TrimAndPrintToken(c);
Newline();
}
}
}
int Printer::AssessPenalty(const std::string& output) {
int penalty = 0;
std::vector<std::string> lines = base::SplitString(
output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
penalty += static_cast<int>(lines.size() - 1) * GetPenaltyForLineBreak();
for (const auto& line : lines) {
if (line.size() > kMaximumWidth)
penalty += static_cast<int>(line.size() - kMaximumWidth) * kPenaltyExcess;
}
return penalty;
}
bool Printer::ExceedsMaximumWidth(const std::string& output) {
for (const auto& line : base::SplitString(output, "\n", base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL)) {
std::string_view trimmed =
TrimString(line, " ", base::TrimPositions::TRIM_TRAILING);
if (trimmed.size() > kMaximumWidth) {
return true;
}
}
return false;
}
void Printer::AddParen(int prec, int outer_prec, bool* parenthesized) {
if (prec < outer_prec) {
Print("(");
*parenthesized = true;
}
}
int Printer::Expr(const ParseNode* root,
int outer_prec,
const std::string& suffix) {
std::string at_end = suffix;
int penalty = 0;
penalty_depth_++;
if (root->comments()) {
if (!root->comments()->before().empty()) {
Trim();
// If there's already other text on the line, start a new line.
if (CurrentColumn() > 0)
Print("\n");
// We're printing a line comment, so we need to be at the current margin.
PrintMargin();
for (const auto& c : root->comments()->before()) {
TrimAndPrintToken(c);
Newline();
}
}
}
bool parenthesized = false;
if (const AccessorNode* accessor = root->AsAccessor()) {
AddParen(kPrecedenceSuffix, outer_prec, &parenthesized);
Print(accessor->base().value());
if (accessor->member()) {
Print(".");
Expr(accessor->member(), kPrecedenceLowest, std::string());
} else {
CHECK(accessor->subscript());
Print("[");
Expr(accessor->subscript(), kPrecedenceLowest, "]");
}
} else if (const BinaryOpNode* binop = root->AsBinaryOp()) {
CHECK(precedence_.find(binop->op().value()) != precedence_.end());
SortIfApplicable(binop);
Precedence prec = precedence_[binop->op().value()];
// Since binary operators format left-to-right, it is ok for the left side
// use the same operator without parentheses, so the left uses prec. For the
// same reason, the right side cannot reuse the same operator, or else "x +
// (y + z)" would format as "x + y + z" which means "(x + y) + z". So, treat
// the right expression as appearing one precedence level higher.
// However, because the source parens are not in the parse tree, as a
// special case for && and || we insert strictly-redundant-but-helpful-for-
// human-readers parentheses.
int prec_left = prec;
int prec_right = prec + 1;
if (binop->op().value() == "&&" && stack_.back().parent_is_boolean_or) {
Print("(");
parenthesized = true;
} else {
AddParen(prec_left, outer_prec, &parenthesized);
}
if (parenthesized)
at_end = ")" + at_end;
int start_line = CurrentLine();
int start_column = CurrentColumn();
bool is_assignment = binop->op().value() == "=" ||
binop->op().value() == "+=" ||
binop->op().value() == "-=";
int indent_column = start_column;
if (is_assignment) {
// Default to a double-indent for wrapped assignments.
indent_column = margin() + kIndentSize * 2;
// A special case for the long lists and scope assignments that are
// common in .gn files, don't indent them + 4, even though they're just
// continuations when they're simple lists like "x = [ a, b, c, ... ]" or
// scopes like "x = { a = 1 b = 2 }". Put back to "normal" indenting.
if (const ListNode* right_as_list = binop->right()->AsList()) {
if (ListWillBeMultiline(right_as_list->contents(),
right_as_list->End()))
indent_column = start_column;
} else {
if (binop->right()->AsBlock())
indent_column = start_column;
}
}
if (stack_.back().continuation_requires_indent)
indent_column += kIndentSize * 2;
stack_.push_back(IndentState(indent_column,
stack_.back().continuation_requires_indent,
binop->op().value() == "||"));
Printer sub_left;
InitializeSub(&sub_left);
sub_left.Expr(binop->left(), prec_left,
std::string(" ") + std::string(binop->op().value()));
bool left_is_multiline = CountLines(sub_left.String()) > 1;
// Avoid walking the whole left redundantly times (see timing of Format.046)
// so pull the output and comments from subprinter.
Print(sub_left.String().substr(start_column));
std::copy(sub_left.comments_.begin(), sub_left.comments_.end(),
std::back_inserter(comments_));
// Single line.
Printer sub1;
InitializeSub(&sub1);
sub1.Print(" ");
int penalty_current_line = sub1.Expr(binop->right(), prec_right, at_end);
sub1.PrintSuffixComments(root);
sub1.FlushComments();
penalty_current_line += AssessPenalty(sub1.String());
if (!is_assignment && left_is_multiline) {
// In e.g. xxx + yyy, if xxx is already multiline, then we want a penalty
// for trying to continue as if this were one line.
penalty_current_line +=
(CountLines(sub1.String()) - 1) * kPenaltyBrokenLineOnOneLiner;
}
// Break after operator.
Printer sub2;
InitializeSub(&sub2);
sub2.Newline();
int penalty_next_line = sub2.Expr(binop->right(), prec_right, at_end);
sub2.PrintSuffixComments(root);
sub2.FlushComments();
penalty_next_line += AssessPenalty(sub2.String());
// Force a list on the RHS that would normally be a single line into
// multiline.
bool tried_rhs_multiline = false;
Printer sub3;
InitializeSub(&sub3);
int penalty_multiline_rhs_list = std::numeric_limits<int>::max();
const ListNode* rhs_list = binop->right()->AsList();
if (is_assignment && rhs_list &&
!ListWillBeMultiline(rhs_list->contents(), rhs_list->End())) {
sub3.Print(" ");
sub3.stack_.push_back(IndentState(start_column, false, false));
sub3.Sequence(kSequenceStyleList, rhs_list->contents(), rhs_list->End(),
true);
sub3.PrintSuffixComments(root);
sub3.FlushComments();
sub3.stack_.pop_back();
penalty_multiline_rhs_list = AssessPenalty(sub3.String());
tried_rhs_multiline = true;
}
// If in all cases it was forced past 80col, then we don't break to avoid
// breaking after '=' in the case of:
// variable = "... very long string ..."
// as breaking and indenting doesn't make things much more readable, even
// though there's fewer characters past the maximum width.
bool exceeds_maximum_all_ways =
ExceedsMaximumWidth(sub1.String()) &&
ExceedsMaximumWidth(sub2.String()) &&
(!tried_rhs_multiline || ExceedsMaximumWidth(sub3.String()));
if (penalty_current_line < penalty_next_line || exceeds_maximum_all_ways) {
Print(" ");
Expr(binop->right(), prec_right, at_end);
at_end = "";
} else if (tried_rhs_multiline &&
penalty_multiline_rhs_list < penalty_next_line) {
// Force a multiline list on the right.
Print(" ");
stack_.push_back(IndentState(start_column, false, false));
Sequence(kSequenceStyleList, rhs_list->contents(), rhs_list->End(), true);
stack_.pop_back();
} else {
// Otherwise, put first argument and op, and indent next.
Newline();
penalty += std::abs(CurrentColumn() - start_column) *
kPenaltyHorizontalSeparation;
Expr(binop->right(), prec_right, at_end);
at_end = "";
}
stack_.pop_back();
penalty += (CurrentLine() - start_line) * GetPenaltyForLineBreak();
} else if (const BlockNode* block = root->AsBlock()) {
Sequence(kSequenceStyleBracedBlock, block->statements(), block->End(),
false);
} else if (const ConditionNode* condition = root->AsCondition()) {
Print("if (");
CHECK(at_end.empty());
Expr(condition->condition(), kPrecedenceLowest, ") {");
Sequence(kSequenceStyleBracedBlockAlreadyOpen,
condition->if_true()->statements(), condition->if_true()->End(),
false);
if (condition->if_false()) {
Print(" else ");
// If it's a block it's a bare 'else', otherwise it's an 'else if'. See
// ConditionNode::Execute.
bool is_else_if = condition->if_false()->AsBlock() == nullptr;
if (is_else_if) {
Expr(condition->if_false(), kPrecedenceLowest, std::string());
} else {
Sequence(kSequenceStyleBracedBlock,
condition->if_false()->AsBlock()->statements(),
condition->if_false()->AsBlock()->End(), false);
}
}
} else if (const FunctionCallNode* func_call = root->AsFunctionCall()) {
penalty += FunctionCall(func_call, at_end);
at_end = "";
} else if (const IdentifierNode* identifier = root->AsIdentifier()) {
Print(identifier->value().value());
} else if (const ListNode* list = root->AsList()) {
Sequence(kSequenceStyleList, list->contents(), list->End(),
/*force_multiline=*/false);
} else if (const LiteralNode* literal = root->AsLiteral()) {
Print(literal->value().value());
} else if (const UnaryOpNode* unaryop = root->AsUnaryOp()) {
Print(unaryop->op().value());
Expr(unaryop->operand(), kPrecedenceUnary, std::string());
} else if (const BlockCommentNode* block_comment = root->AsBlockComment()) {
Print(block_comment->comment().value());
} else if (const EndNode* end = root->AsEnd()) {
Print(end->value().value());
} else {
CHECK(false) << "Unhandled case in Expr.";
}
// Defer any end of line comment until we reach the newline.
if (root->comments() && !root->comments()->suffix().empty()) {
std::copy(root->comments()->suffix().begin(),
root->comments()->suffix().end(), std::back_inserter(comments_));
}
Print(at_end);
penalty_depth_--;
return penalty;
}
template <class PARSENODE>
void Printer::Sequence(SequenceStyle style,
const std::vector<std::unique_ptr<PARSENODE>>& list,
const ParseNode* end,
bool force_multiline) {
if (style == kSequenceStyleList) {
Print("[");
} else if (style == kSequenceStyleBracedBlock) {
Print("{");
} else if (style == kSequenceStyleBracedBlockAlreadyOpen) {
style = kSequenceStyleBracedBlock;
}
if (style == kSequenceStyleBracedBlock) {
force_multiline = true;
SortImports(const_cast<std::vector<std::unique_ptr<PARSENODE>>&>(list));
}
force_multiline |= ListWillBeMultiline(list, end);
if (list.size() == 0 && !force_multiline) {
// No elements, and not forcing newlines, print nothing.
} else if (list.size() == 1 && !force_multiline) {
Print(" ");
Expr(list[0].get(), kPrecedenceLowest, std::string());
CHECK(!list[0]->comments() || list[0]->comments()->after().empty());
Print(" ");
} else {
stack_.push_back(IndentState(margin() + kIndentSize,
style == kSequenceStyleList, false));
size_t i = 0;
for (const auto& x : list) {
Newline();
// If:
// - we're going to output some comments, and;
// - we haven't just started this multiline list, and;
// - there isn't already a blank line here;
// Then: insert one.
if (i != 0 && x->comments() && !x->comments()->before().empty() &&
!HaveBlankLine()) {
Newline();
}
bool body_of_list = i < list.size() - 1 || style == kSequenceStyleList;
bool want_comma =
body_of_list && (style == kSequenceStyleList && !x->AsBlockComment());
Expr(x.get(), kPrecedenceLowest, want_comma ? "," : std::string());
CHECK(!x->comments() || x->comments()->after().empty());
if (body_of_list) {
if (i < list.size() - 1 &&
ShouldAddBlankLineInBetween(list[i].get(), list[i + 1].get()))
Newline();
}
++i;
}
// Trailing comments.
if (end->comments() && !end->comments()->before().empty()) {
if (list.size() >= 2)
Newline();
for (const auto& c : end->comments()->before()) {
Newline();
TrimAndPrintToken(c);
}
}
stack_.pop_back();
Newline();
}
// Defer any end of line comment until we reach the newline.
if (end->comments() && !end->comments()->suffix().empty()) {
std::copy(end->comments()->suffix().begin(),
end->comments()->suffix().end(), std::back_inserter(comments_));
}
if (style == kSequenceStyleList)
Print("]");
else if (style == kSequenceStyleBracedBlock)
Print("}");
}
int Printer::FunctionCall(const FunctionCallNode* func_call,
const std::string& suffix) {
int start_line = CurrentLine();
int start_column = CurrentColumn();
Print(func_call->function().value());
Print("(");
bool have_block = func_call->block() != nullptr;
bool force_multiline = false;
const auto& list = func_call->args()->contents();
const ParseNode* end = func_call->args()->End();
if (end->comments() && !end->comments()->before().empty())
force_multiline = true;
// If there's before line comments, make sure we have a place to put them.
for (const auto& i : list) {
if (i->comments() && !i->comments()->before().empty())
force_multiline = true;
}
// Calculate the penalties for 3 possible layouts:
// 1. all on same line;
// 2. starting on same line, broken at each comma but paren aligned;
// 3. broken to next line + 4, broken at each comma.
std::string terminator = ")";
if (have_block)
terminator += " {";
terminator += suffix;
// Special case to make function calls of one arg taking a long list of
// boolean operators not indent.
bool continuation_requires_indent =
list.size() != 1 || !list[0]->AsBinaryOp();
// 1: Same line.
Printer sub1;
InitializeSub(&sub1);
sub1.stack_.push_back(
IndentState(CurrentColumn(), continuation_requires_indent, false));
int penalty_one_line = 0;
for (size_t i = 0; i < list.size(); ++i) {
penalty_one_line += sub1.Expr(list[i].get(), kPrecedenceLowest,
i < list.size() - 1 ? ", " : std::string());
}
sub1.Print(terminator);
penalty_one_line += AssessPenalty(sub1.String());
// This extra penalty prevents a short second argument from being squeezed in
// after a first argument that went multiline (and instead preferring a
// variant below).
penalty_one_line +=
(CountLines(sub1.String()) - 1) * kPenaltyBrokenLineOnOneLiner;
// 2: Starting on same line, broken at commas.
Printer sub2;
InitializeSub(&sub2);
sub2.stack_.push_back(
IndentState(CurrentColumn(), continuation_requires_indent, false));
int penalty_multiline_start_same_line = 0;
for (size_t i = 0; i < list.size(); ++i) {
penalty_multiline_start_same_line +=
sub2.Expr(list[i].get(), kPrecedenceLowest,
i < list.size() - 1 ? "," : std::string());
if (i < list.size() - 1) {
sub2.Newline();
}
}
sub2.Print(terminator);
penalty_multiline_start_same_line += AssessPenalty(sub2.String());
// 3: Starting on next line, broken at commas.
Printer sub3;
InitializeSub(&sub3);
sub3.stack_.push_back(IndentState(margin() + kIndentSize * 2,
continuation_requires_indent, false));
sub3.Newline();
int penalty_multiline_start_next_line = 0;
for (size_t i = 0; i < list.size(); ++i) {
if (i == 0) {
penalty_multiline_start_next_line +=
std::abs(sub3.CurrentColumn() - start_column) *
kPenaltyHorizontalSeparation;
}
penalty_multiline_start_next_line +=
sub3.Expr(list[i].get(), kPrecedenceLowest,
i < list.size() - 1 ? "," : std::string());
if (i < list.size() - 1) {
sub3.Newline();
}
}
sub3.Print(terminator);
penalty_multiline_start_next_line += AssessPenalty(sub3.String());
int penalty = penalty_multiline_start_next_line;
bool fits_on_current_line = false;
if (penalty_one_line < penalty_multiline_start_next_line ||
penalty_multiline_start_same_line < penalty_multiline_start_next_line) {
fits_on_current_line = true;
penalty = penalty_one_line;
if (penalty_multiline_start_same_line < penalty_one_line) {
penalty = penalty_multiline_start_same_line;
force_multiline = true;
}
} else {
force_multiline = true;
}
if (list.size() == 0 && !force_multiline) {
// No elements, and not forcing newlines, print nothing.
} else {
if (penalty_multiline_start_next_line < penalty_multiline_start_same_line) {
stack_.push_back(IndentState(margin() + kIndentSize * 2,
continuation_requires_indent, false));
Newline();
} else {
stack_.push_back(
IndentState(CurrentColumn(), continuation_requires_indent, false));
}
for (size_t i = 0; i < list.size(); ++i) {
const auto& x = list[i];
if (i > 0) {
if (fits_on_current_line && !force_multiline)
Print(" ");
else
Newline();
}
bool want_comma = i < list.size() - 1 && !x->AsBlockComment();
Expr(x.get(), kPrecedenceLowest, want_comma ? "," : std::string());
CHECK(!x->comments() || x->comments()->after().empty());
if (i < list.size() - 1) {
if (!want_comma)
Newline();
}
}
// Trailing comments.
if (end->comments() && !end->comments()->before().empty()) {
if (!list.empty())
Newline();
for (const auto& c : end->comments()->before()) {
Newline();
TrimAndPrintToken(c);
}
Newline();
}
stack_.pop_back();
}
// Defer any end of line comment until we reach the newline.
if (end->comments() && !end->comments()->suffix().empty()) {
std::copy(end->comments()->suffix().begin(),
end->comments()->suffix().end(), std::back_inserter(comments_));
}
Print(")");
Print(suffix);
if (have_block) {
Print(" ");
Sequence(kSequenceStyleBracedBlock, func_call->block()->statements(),
func_call->block()->End(), false);
}
return penalty + (CurrentLine() - start_line) * GetPenaltyForLineBreak();
}
void Printer::InitializeSub(Printer* sub) {
sub->stack_ = stack_;
sub->comments_ = comments_;
sub->penalty_depth_ = penalty_depth_;
sub->Print(std::string(CurrentColumn(), 'x'));
}
template <class PARSENODE>
bool Printer::ListWillBeMultiline(
const std::vector<std::unique_ptr<PARSENODE>>& list,
const ParseNode* end) {
if (list.size() > 1)
return true;
if (end && end->comments() && !end->comments()->before().empty())
return true;
// If there's before or suffix line comments, make sure we have a place to put
// them.
for (const auto& i : list) {
if (i->comments() && (!i->comments()->before().empty() ||
!i->comments()->suffix().empty())) {
return true;
}
}
// When a scope is used as a list entry, it's too complicated to go one a
// single line (the block will always be formatted multiline itself).
if (list.size() >= 1 && list[0]->AsBlock())
return true;
return false;
}
void DoFormat(const ParseNode* root,
TreeDumpMode dump_tree,
std::string* output,
std::string* dump_output) {
if (dump_tree == TreeDumpMode::kPlainText) {
std::ostringstream os;
RenderToText(root->GetJSONNode(), 0, os);
*dump_output = os.str();
} else if (dump_tree == TreeDumpMode::kJSON) {
std::string os;
base::JSONWriter::WriteWithOptions(
root->GetJSONNode(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &os);
*dump_output = os;
}
Printer pr;
pr.Block(root);
*output = pr.String();
}
} // namespace
bool FormatJsonToString(const std::string& json, std::string* output) {
base::JSONReader reader;
std::unique_ptr<base::Value> json_root = reader.Read(json);
std::unique_ptr<ParseNode> root = ParseNode::BuildFromJSON(*json_root);
DoFormat(root.get(), TreeDumpMode::kInactive, output, nullptr);
return true;
}
bool FormatStringToString(const std::string& input,
TreeDumpMode dump_tree,
std::string* output,
std::string* dump_output) {
SourceFile source_file;
InputFile file(source_file);
file.SetContents(input);
Err err;
// Tokenize.
std::vector<Token> tokens =
Tokenizer::Tokenize(&file, &err, WhitespaceTransform::kInvalidToSpace);
if (err.has_error()) {
err.PrintToStdout();
return false;
}
// Parse.
std::unique_ptr<ParseNode> parse_node = Parser::Parse(tokens, &err);
if (err.has_error()) {
err.PrintToStdout();
return false;
}
DoFormat(parse_node.get(), dump_tree, output, dump_output);
return true;
}
int RunFormat(const std::vector<std::string>& args) {
#if defined(OS_WIN)
// Set to binary mode to prevent converting newlines to \r\n.
_setmode(_fileno(stdout), _O_BINARY);
_setmode(_fileno(stderr), _O_BINARY);
#endif
bool dry_run =
base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDryRun);
TreeDumpMode dump_tree = TreeDumpMode::kInactive;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree)) {
std::string tree_type =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kSwitchDumpTree);
if (tree_type == kSwitchTreeTypeJSON) {
dump_tree = TreeDumpMode::kJSON;
} else if (tree_type.empty() || tree_type == kSwitchTreeTypeText) {
dump_tree = TreeDumpMode::kPlainText;
} else {
Err(Location(), tree_type +
" is an invalid value for --dump-tree. Specify "
"\"" +
kSwitchTreeTypeText + "\" or \"" +
kSwitchTreeTypeJSON + "\".\n")
.PrintToStdout();
return 1;
}
}
bool from_stdin =
base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchStdin);
if (dry_run) {
// --dry-run only works with an actual file to compare to.
from_stdin = false;
}
bool quiet =
base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kQuiet);
if (from_stdin) {
if (args.size() != 0) {
Err(Location(), "Expecting no arguments when reading from stdin.\n")
.PrintToStdout();
return 1;
}
std::string input = ReadStdin();
std::string output;
std::string dump_output;
if (!FormatStringToString(input, dump_tree, &output, &dump_output))
return 1;
printf("%s", dump_output.c_str());
printf("%s", output.c_str());
return 0;
}
if (args.size() == 0) {
Err(Location(), "Expecting one or more arguments, see `gn help format`.\n")
.PrintToStdout();
return 1;
}
Setup setup;
SourceDir source_dir =
SourceDirForCurrentDirectory(setup.build_settings().root_path());
if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchReadTree)) {
std::string tree_type =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kSwitchReadTree);
if (tree_type != kSwitchTreeTypeJSON) {
Err(Location(), "Only json supported for read-tree.\n").PrintToStdout();
return 1;
}
if (args.size() != 1) {
Err(Location(),
"Expect exactly one .gn when reading tree from json on stdin.\n")
.PrintToStdout();
return 1;
}
Err err;
SourceFile file =
source_dir.ResolveRelativeFile(Value(nullptr, args[0]), &err);
if (err.has_error()) {
err.PrintToStdout();
return 1;
}
base::FilePath to_format = setup.build_settings().GetFullPath(file);
std::string output;
FormatJsonToString(ReadStdin(), &output);
if (base::WriteFile(to_format, output.data(),
static_cast<int>(output.size())) == -1) {
Err(Location(), std::string("Failed to write output to \"") +
FilePathToUTF8(to_format) + std::string("\"."))
.PrintToStdout();
return 1;
}
if (!quiet) {
printf("Wrote rebuilt from json to '%s'.\n",
FilePathToUTF8(to_format).c_str());
}
return 0;
}
// TODO(scottmg): Eventually, this list of files should be processed in
// parallel.
int exit_code = 0;
for (const auto& arg : args) {
Err err;
SourceFile file = source_dir.ResolveRelativeFile(Value(nullptr, arg), &err);
if (err.has_error()) {
err.PrintToStdout();
exit_code = 1;
continue;
}
base::FilePath to_format = setup.build_settings().GetFullPath(file);
std::string original_contents;
if (!base::ReadFileToString(to_format, &original_contents)) {
Err(Location(),
std::string("Couldn't read \"") + FilePathToUTF8(to_format))
.PrintToStdout();
exit_code = 1;
continue;
}
std::string output_string;
std::string dump_output_string;
if (!FormatStringToString(original_contents, dump_tree, &output_string,
&dump_output_string)) {
exit_code = 1;
continue;
}
printf("%s", dump_output_string.c_str());
if (dump_tree == TreeDumpMode::kInactive) {
if (dry_run) {
if (original_contents != output_string) {
printf("%s\n", arg.c_str());
exit_code = 2;
}
continue;
}
// Update the file in-place.
if (original_contents != output_string) {
if (base::WriteFile(to_format, output_string.data(),
static_cast<int>(output_string.size())) == -1) {
Err(Location(),
std::string("Failed to write formatted output back to \"") +
FilePathToUTF8(to_format) + std::string("\"."))
.PrintToStdout();
exit_code = 1;
continue;
}
if (!quiet) {
printf("Wrote formatted to '%s'.\n",
FilePathToUTF8(to_format).c_str());
}
}
}
}
return exit_code;
}
} // namespace commands