| // 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/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); |
| |
| // Traverse a binary op node tree and apply a callback to each leaf node. |
| void TraverseBinaryOpNode(const ParseNode* node, |
| std::function<void(const ParseNode*)> callback); |
| |
| // 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_; |
| |
| Printer(const Printer&) = delete; |
| Printer& operator=(const Printer&) = delete; |
| }; |
| |
| 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(); |
| if ((binop->op().value() == "=" || binop->op().value() == "+=" || |
| binop->op().value() == "-=") && |
| ident) { |
| const std::string_view lhs = ident->value().value(); |
| if (base::EndsWith(lhs, "sources", base::CompareCase::SENSITIVE) || |
| lhs == "public") { |
| TraverseBinaryOpNode(binop->right(), [](const ParseNode* node) { |
| const ListNode* list = node->AsList(); |
| if (list) |
| const_cast<ListNode*>(list)->SortAsStringsList(); |
| }); |
| } else if (base::EndsWith(lhs, "deps", base::CompareCase::SENSITIVE) || |
| lhs == "visibility") { |
| TraverseBinaryOpNode(binop->right(), [](const ParseNode* node) { |
| const ListNode* list = node->AsList(); |
| if (list) |
| const_cast<ListNode*>(list)->SortAsTargetsList(); |
| }); |
| } |
| } |
| } |
| |
| void Printer::TraverseBinaryOpNode( |
| const ParseNode* node, |
| std::function<void(const ParseNode*)> callback) { |
| const BinaryOpNode* binop = node->AsBinaryOp(); |
| if (binop) { |
| TraverseBinaryOpNode(binop->left(), callback); |
| TraverseBinaryOpNode(binop->right(), callback); |
| } else { |
| callback(node); |
| } |
| } |
| |
| 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 |