// 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 "tools/gn/command_format.h" #include #include #include "base/command_line.h" #include "base/files/file_util.h" #include "base/macros.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "tools/gn/commands.h" #include "tools/gn/filesystem_utils.h" #include "tools/gn/input_file.h" #include "tools/gn/parser.h" #include "tools/gn/scheduler.h" #include "tools/gn/setup.h" #include "tools/gn/source_file.h" #include "tools/gn/tokenizer.h" namespace commands { const char kSwitchDryRun[] = "dry-run"; const char kSwitchDumpTree[] = "dump-tree"; const char kSwitchStdin[] = "stdin"; const char kFormat[] = "format"; const char kFormat_HelpShort[] = "format: Format .gn file."; const char kFormat_Help[] = R"(gn format [--dump-tree] (--stdin | ) 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 Does not change or output anything, but sets the process exit code based on whether output would be different than what's on 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 For debugging, dumps the parse tree to stdout and does not update the file or print formatted output. --stdin Read input from stdin and write to stdout rather than update a file in-place. Examples gn format //some/BUILD.gn gn format some\\BUILD.gn gn format /abspath/some/BUILD.gn gn format --stdin )"; 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(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, kSequenceStyleBlock, kSequenceStyleBracedBlock, }; struct Metrics { Metrics() : first_length(-1), longest_length(-1), multiline(false) {} int first_length; int longest_length; bool multiline; }; // Add to output. void Print(base::StringPiece str); // Add the current margin (as spaces) to the output. void PrintMargin(); void TrimAndPrintToken(const Token& token); // 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(); // Flag assignments to sources, deps, etc. to make their RHSs multiline. void AnnotatePreferredMultilineAssignment(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 // 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 to the output buffer. Returns the type of element // added to the output. 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 // Just for const covariance. void Sequence(SequenceStyle style, const std::vector>& 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 bool ListWillBeMultiline(const std::vector>& list, const ParseNode* end); std::string output_; // Output buffer. std::vector 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 stack_; // Gives the precedence for operators in a BinaryOpNode. std::map 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(base::StringPiece str) { str.AppendToString(&output_); } void Printer::PrintMargin() { output_ += std::string(margin(), ' '); } void Printer::TrimAndPrintToken(const Token& token) { std::string trimmed; TrimWhitespaceASCII(token.value().as_string(), base::TRIM_ALL, &trimmed); Print(trimmed); } void Printer::Newline() { 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. This // will need to be fancier once we enforce 80 col. stack_.push_back(IndentState(CurrentColumn(), false, false)); int i = 0; for (const auto& c : comments_) { if (i > 0) { Trim(); Print("\n"); PrintMargin(); } TrimAndPrintToken(c); ++i; } stack_.pop_back(); comments_.clear(); } 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::AnnotatePreferredMultilineAssignment(const BinaryOpNode* binop) { const IdentifierNode* ident = binop->left()->AsIdentifier(); const ListNode* list = binop->right()->AsList(); // This is somewhat arbitrary, but we include the 'deps'- and 'sources'-like // things, but not flags things. if (binop->op().value() == "=" && ident && list) { const base::StringPiece lhs = ident->value().value(); if (lhs == "data" || lhs == "datadeps" || lhs == "data_deps" || lhs == "deps" || lhs == "inputs" || lhs == "outputs" || lhs == "public" || lhs == "public_deps" || lhs == "sources") { const_cast(list)->set_prefer_multiline(true); } } } void Printer::SortIfSourcesOrDeps(const BinaryOpNode* binop) { if (binop->comments() && !binop->comments()->before().empty() && binop->comments()->before()[0].value().as_string() == "# 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 base::StringPiece lhs = ident->value().value(); if (lhs == "public" || lhs == "sources") const_cast(list)->SortAsStringsList(); else if (lhs == "deps" || lhs == "public_deps") const_cast(list)->SortAsDepsList(); } } bool Printer::ShouldAddBlankLineInBetween(const ParseNode* a, const ParseNode* b) { LocationRange a_range = a->GetRange(); LocationRange b_range = b->GetRange(); // 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_range.end().line_number() + 1) || // Always put a blank line before a block comment. b->AsBlockComment(); } int Printer::CurrentColumn() const { int n = 0; while (n < static_cast(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(); } } 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 lines = base::SplitString( output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); penalty += static_cast(lines.size() - 1) * GetPenaltyForLineBreak(); for (const auto& line : lines) { if (line.size() > kMaximumWidth) penalty += static_cast(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)) { if (line.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->index()); Print("["); Expr(accessor->index(), kPrecedenceLowest, "]"); } } else if (const BinaryOpNode* binop = root->AsBinaryOp()) { CHECK(precedence_.find(binop->op().value()) != precedence_.end()); AnnotatePreferredMultilineAssignment(binop); SortIfSourcesOrDeps(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); } 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. const ListNode* right_as_list = binop->right()->AsList(); if (right_as_list) { if (right_as_list->prefer_multiline() || ListWillBeMultiline(right_as_list->contents(), right_as_list->End())) indent_column = start_column; } else { const BlockNode* right_as_block = binop->right()->AsBlock(); if (right_as_block) 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(" ") + binop->op().value().as_string()); 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, std::string()); sub1.Print(suffix); 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, std::string()); sub2.Print(suffix); 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::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.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, std::string()); } 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, std::string()); } 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->AsConditionNode()) { Print("if ("); // TODO(scottmg): The { needs to be included in the suffix here. Expr(condition->condition(), kPrecedenceLowest, ") "); Sequence(kSequenceStyleBracedBlock, 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()) { bool force_multiline = list->prefer_multiline() && !list->contents().empty(); Sequence( kSequenceStyleList, list->contents(), list->End(), force_multiline); } 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."; } if (parenthesized) Print(")"); // 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 void Printer::Sequence(SequenceStyle style, const std::vector>& list, const ParseNode* end, bool force_multiline) { if (style == kSequenceStyleList) Print("["); else if (style == kSequenceStyleBracedBlock) Print("{"); if (style == kSequenceStyleBlock || style == kSequenceStyleBracedBlock) force_multiline = true; 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 bool Printer::ListWillBeMultiline( const std::vector>& list, const ParseNode* end) { if (list.size() > 1) return true; if (end && end->comments() && !end->comments()->before().empty()) return 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()) 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, bool dump_tree, std::string* output) { if (dump_tree) { std::ostringstream os; root->Print(os, 0); fprintf(stderr, "%s", os.str().c_str()); } Printer pr; pr.Block(root); *output = pr.String(); } std::string ReadStdin() { static const int kBufferSize = 256; char buffer[kBufferSize]; std::string result; while (true) { char* input = nullptr; input = fgets(buffer, kBufferSize, stdin); if (input == nullptr && feof(stdin)) return result; int length = static_cast(strlen(buffer)); if (length == 0) return result; else result += std::string(buffer, length); } } } // namespace bool FormatFileToString(Setup* setup, const SourceFile& file, bool dump_tree, std::string* output) { Err err; const ParseNode* parse_node = setup->scheduler().input_file_manager()->SyncLoadFile( LocationRange(), &setup->build_settings(), file, &err); if (err.has_error()) { err.PrintToStdout(); return false; } DoFormat(parse_node, dump_tree, output); return true; } bool FormatStringToString(const std::string& input, bool dump_tree, std::string* output) { SourceFile source_file; InputFile file(source_file); file.SetContents(input); Err err; // Tokenize. std::vector tokens = Tokenizer::Tokenize(&file, &err); if (err.has_error()) { err.PrintToStdout(); return false; } // Parse. std::unique_ptr parse_node = Parser::Parse(tokens, &err); if (err.has_error()) { err.PrintToStdout(); return false; } DoFormat(parse_node.get(), dump_tree, output); return true; } int RunFormat(const std::vector& args) { bool dry_run = base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDryRun); bool dump_tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree); 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; } 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; if (!FormatStringToString(input, dump_tree, &output)) return 1; printf("%s", output.c_str()); return 0; } // TODO(scottmg): Eventually, this should be a list/spec of files, and they // should all be done in parallel. if (args.size() != 1) { Err(Location(), "Expecting exactly one argument, see `gn help format`.\n") .PrintToStdout(); return 1; } Setup setup; SourceDir source_dir = SourceDirForCurrentDirectory(setup.build_settings().root_path()); Err err; SourceFile file = source_dir.ResolveRelativeFile(Value(nullptr, args[0]), &err); if (err.has_error()) { err.PrintToStdout(); return 1; } std::string output_string; if (FormatFileToString(&setup, file, dump_tree, &output_string)) { if (!dump_tree) { // Update the file in-place. base::FilePath to_write = setup.build_settings().GetFullPath(file); std::string original_contents; if (!base::ReadFileToString(to_write, &original_contents)) { Err(Location(), std::string("Couldn't read \"") + to_write.AsUTF8Unsafe() + std::string("\" for comparison.")).PrintToStdout(); return 1; } if (dry_run) return original_contents == output_string ? 0 : 2; if (original_contents != output_string) { if (base::WriteFile(to_write, output_string.data(), static_cast(output_string.size())) == -1) { Err(Location(), std::string("Failed to write formatted output back to \"") + to_write.AsUTF8Unsafe() + std::string("\".")).PrintToStdout(); return 1; } printf("Wrote formatted to '%s'.\n", to_write.AsUTF8Unsafe().c_str()); } } } return 0; } } // namespace commands