| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "common/formatting/state_node.h" | |
| #include <memory> | |
| #include <stack> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/memory/memory.h" | |
| #include "absl/strings/string_view.h" | |
| #include "common/formatting/basic_format_style.h" | |
| #include "common/formatting/format_token.h" | |
| #include "common/formatting/unwrapped_line.h" | |
| #include "common/formatting/unwrapped_line_test_utils.h" | |
| #include "common/util/logging.h" | |
| #include "gtest/gtest.h" | |
| namespace verible { | |
| namespace { | |
| std::string RenderFormattedText(const StateNode& path, | |
| const UnwrappedLine& uwline) { | |
| FormattedExcerpt formatted_line(uwline); | |
| // Discard tokens that have not yet been explored in search. | |
| formatted_line.MutableTokens().resize(path.Depth()); | |
| path.ReconstructFormatDecisions(&formatted_line); | |
| return formatted_line.Render(); | |
| } | |
| struct StateNodeTestFixture : public UnwrappedLineMemoryHandler, | |
| public ::testing::Test { | |
| StateNodeTestFixture() { | |
| style.column_limit = 30; | |
| style.indentation_spaces = 2; | |
| style.wrap_spaces = 4; | |
| style.over_column_limit_penalty = 200; | |
| } | |
| void Initialize(int d, const std::vector<TokenInfo>& tokens) { | |
| CreateTokenInfos(tokens); | |
| uwline = std::make_unique<UnwrappedLine>(d * style.indentation_spaces, | |
| pre_format_tokens_.begin()); | |
| AddFormatTokens(uwline.get()); | |
| } | |
| void InitializeExternalTextBuffer(int d, | |
| const std::vector<TokenInfo>& tokens) { | |
| CreateTokenInfosExternalStringBuffer(tokens); | |
| uwline = std::make_unique<UnwrappedLine>(d * style.indentation_spaces, | |
| pre_format_tokens_.begin()); | |
| AddFormatTokens(uwline.get()); | |
| } | |
| std::string Render(const StateNode& path, const UnwrappedLine& uwline) const { | |
| return RenderFormattedText(path, uwline); | |
| } | |
| BasicFormatStyle style; | |
| std::unique_ptr<UnwrappedLine> uwline; | |
| }; | |
| // Tests that root StateNode of search can be initialized with full | |
| // sequence of FormatTokens. | |
| TEST_F(StateNodeTestFixture, ConstructionWithEmptyFormatTokens) { | |
| static const int kInitialIndent = 3; | |
| const std::vector<TokenInfo> tokens; | |
| Initialize(kInitialIndent, tokens); // empty tokens | |
| StateNode s(*uwline, style); | |
| EXPECT_TRUE(s.Done()); // because there is nothing to search | |
| EXPECT_EQ(s.current_column, kInitialIndent * style.indentation_spaces); | |
| EXPECT_EQ(s.wrap_column_positions.size(), 1); | |
| EXPECT_EQ(s.wrap_column_positions.top(), | |
| s.current_column + style.wrap_spaces); | |
| EXPECT_EQ(s.spacing_choice, SpacingDecision::Append); | |
| EXPECT_EQ(s.next(), nullptr); | |
| EXPECT_EQ(s.cumulative_cost, 0); | |
| EXPECT_TRUE(s.IsRootState()); | |
| } | |
| // Tests that root StateNode of search can initialize to an array | |
| // of FormatTokens. | |
| TEST_F(StateNodeTestFixture, ConstructionWithOneFormatToken) { | |
| static const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{0, "token1"}}; | |
| Initialize(kInitialIndent, tokens); | |
| StateNode s(*uwline, style); | |
| EXPECT_TRUE(s.Done()); // nothing to do after first and only token | |
| EXPECT_EQ(s.current_column, kInitialIndent * style.indentation_spaces + | |
| tokens[0].text().length()); | |
| EXPECT_EQ(s.wrap_column_positions.size(), 1); | |
| EXPECT_EQ(s.wrap_column_positions.top(), | |
| kInitialIndent * style.indentation_spaces + style.wrap_spaces); | |
| EXPECT_EQ(s.spacing_choice, SpacingDecision::Append); | |
| EXPECT_EQ(s.next(), nullptr); | |
| EXPECT_EQ(s.cumulative_cost, 0); | |
| EXPECT_TRUE(s.IsRootState()); | |
| } | |
| // Tests that the first token can be marked as preserve, when disabling | |
| // formatting. | |
| TEST_F(StateNodeTestFixture, ConstructionWithPreserveLeadingSpace) { | |
| static const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{0, "token1"}}; | |
| Initialize(kInitialIndent, tokens); | |
| // One way of disabling formatting is setting break_decision to Preserve. | |
| pre_format_tokens_.front().before.break_decision = SpacingOptions::Preserve; | |
| StateNode s(*uwline, style); | |
| EXPECT_TRUE(s.Done()); // nothing to do after first and only token | |
| EXPECT_EQ(s.current_column, tokens[0].text().length()); | |
| EXPECT_EQ(s.spacing_choice, SpacingDecision::Preserve); | |
| EXPECT_EQ(s.next(), nullptr); | |
| EXPECT_EQ(s.cumulative_cost, 0); | |
| EXPECT_TRUE(s.IsRootState()); | |
| } | |
| // Tests new state can be built on top of previous state, appending token to | |
| // current line. | |
| TEST_F(StateNodeTestFixture, ConstructionAppendingPrevState) { | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{0, "token1"}, {1, "TT2"}}; | |
| Initialize(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 1; | |
| ftokens[1].before.break_penalty = 5; | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; // 2 | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); | |
| EXPECT_EQ(parent_state->cumulative_cost, 0); | |
| EXPECT_EQ(parent_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(parent_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_TRUE(parent_state->IsRootState()); | |
| const auto& child_state = parent_state; | |
| { | |
| // Second token, also appended to same line as first: | |
| auto child2_state = std::make_shared<StateNode>(child_state, style, | |
| SpacingDecision::Append); | |
| EXPECT_EQ(child2_state->next(), child_state.get()); | |
| EXPECT_EQ(child2_state->current_column, | |
| child_state->current_column + // 8 + | |
| ftokens[1].before.spaces_required + // 1 + | |
| tokens[1].text().length() // 3: "TT2" | |
| ); | |
| EXPECT_EQ(child2_state->cumulative_cost, 0); | |
| EXPECT_FALSE(child2_state->IsRootState()); | |
| EXPECT_EQ(Render(*child2_state, *uwline), " token1 TT2"); | |
| } | |
| { | |
| // Second token, but wrapped onto next line: | |
| auto child2_state = | |
| std::make_shared<StateNode>(child_state, style, SpacingDecision::Wrap); | |
| EXPECT_EQ(child2_state->next(), child_state.get()); | |
| EXPECT_EQ(child2_state->current_column, | |
| initial_column + // 2 + | |
| style.wrap_spaces + // 4 + | |
| tokens[1].text().length() // 3: "TT2" | |
| ); | |
| EXPECT_EQ(child2_state->cumulative_cost, ftokens[1].before.break_penalty); | |
| EXPECT_FALSE(child2_state->IsRootState()); | |
| EXPECT_EQ(Render(*child2_state, *uwline), | |
| " token1\n" | |
| " TT2"); | |
| } | |
| } | |
| // Tests that preserving spaces results in correct column position. | |
| TEST_F(StateNodeTestFixture, ConstructionPreserveSpacesFromPrevStateNoGap) { | |
| const absl::string_view text("aaabbb"); // no gap between "aaa" and "bbb" | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{0, text.substr(0, 3)}, | |
| {1, text.substr(3, 3)}}; | |
| InitializeExternalTextBuffer(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 4; // ignored because of preserving | |
| ftokens[1].before.preserved_space_start = ftokens[0].Text().end(); | |
| ftokens[1].before.break_penalty = 5; // ignored because of preserving | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; // 2 | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); // 2 + 3 | |
| EXPECT_EQ(parent_state->cumulative_cost, 0); | |
| EXPECT_TRUE(parent_state->IsRootState()); | |
| // Appended with preserved spaces from original text. | |
| auto child_state = std::make_shared<StateNode>(parent_state, style, | |
| SpacingDecision::Preserve); | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| EXPECT_EQ(child_state->current_column, | |
| parent_state->current_column + // 5 + | |
| tokens[1].text().length() // 3 | |
| ); | |
| EXPECT_EQ(child_state->cumulative_cost, parent_state->cumulative_cost); | |
| EXPECT_FALSE(child_state->IsRootState()); | |
| EXPECT_EQ(Render(*child_state, *uwline), " aaabbb"); | |
| } | |
| // Tests that preserving spaces results in correct column position. | |
| TEST_F(StateNodeTestFixture, ConstructionPreserveSpacesFromPrevStateSpaces) { | |
| const absl::string_view text( | |
| "aaa bbb"); // 4 spaces between "aaa" and "bbb" | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{1, text.substr(0, 3)}, | |
| {2, text.substr(7, 3)}}; | |
| InitializeExternalTextBuffer(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 2; // ignored because of preserving | |
| ftokens[1].before.preserved_space_start = ftokens[0].Text().end(); | |
| ftokens[1].before.break_penalty = 5; // ignored because of preserving | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; // 2 | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); // 2 + 3 | |
| EXPECT_EQ(parent_state->cumulative_cost, 0); | |
| EXPECT_TRUE(parent_state->IsRootState()); | |
| // Appended with preserved spaces from original text. | |
| auto child_state = std::make_shared<StateNode>(parent_state, style, | |
| SpacingDecision::Preserve); | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| EXPECT_EQ(child_state->current_column, | |
| parent_state->current_column + // 5 + | |
| 4 + // spaces | |
| tokens[1].text().length() // 3 | |
| ); | |
| EXPECT_EQ(child_state->cumulative_cost, parent_state->cumulative_cost); | |
| EXPECT_FALSE(child_state->IsRootState()); | |
| EXPECT_EQ(Render(*child_state, *uwline), " aaa bbb"); | |
| } | |
| // Tests that preserving spaces results in correct column position. | |
| TEST_F(StateNodeTestFixture, ConstructionPreserveSpacesFromPrevStateNewline) { | |
| const absl::string_view text("aaa \n bbb"); // newline in between | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{1, text.substr(0, 3)}, | |
| {2, text.substr(7, 3)}}; | |
| InitializeExternalTextBuffer(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 2; // ignored because of preserving | |
| ftokens[1].before.preserved_space_start = ftokens[0].Text().end(); | |
| ftokens[1].before.break_penalty = 5; // ignored because of preserving | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; // 2 | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); // 2 + 3 | |
| EXPECT_EQ(parent_state->cumulative_cost, 0); | |
| EXPECT_TRUE(parent_state->IsRootState()); | |
| // Appended with preserved spaces from original text. | |
| auto child_state = std::make_shared<StateNode>(parent_state, style, | |
| SpacingDecision::Preserve); | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| EXPECT_EQ(child_state->current_column, | |
| 1 + // space after last newline | |
| tokens[1].text().length() // 3 | |
| ); | |
| EXPECT_EQ(child_state->cumulative_cost, parent_state->cumulative_cost); | |
| EXPECT_FALSE(child_state->IsRootState()); | |
| EXPECT_EQ(Render(*child_state, *uwline), " aaa \n bbb"); | |
| } | |
| // Tests new state can be built on top of previous state, appending token to | |
| // current line. | |
| TEST_F(StateNodeTestFixture, ConstructionAppendingPrevStateWithGroupBalancing) { | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = { | |
| {0, "function_caller"}, {1, "("}, {2, "11"}, {3, ")"}}; | |
| Initialize(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 1; | |
| ftokens[1].before.break_penalty = 10; | |
| ftokens[1].balancing = verible::GroupBalancing::Open; | |
| ftokens[2].before.spaces_required = 1; | |
| ftokens[2].before.break_penalty = 2; | |
| ftokens[3].balancing = verible::GroupBalancing::Close; | |
| ftokens[3].before.spaces_required = 1; | |
| ftokens[3].before.break_penalty = 3; | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); | |
| EXPECT_EQ(parent_state->cumulative_cost, 0); | |
| EXPECT_EQ(parent_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(parent_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*parent_state, *uwline), " function_caller"); | |
| const auto& child_state = parent_state; | |
| { | |
| // Second token, also appended to same line as first: | |
| // > function_caller ( | |
| // > ^-- next wrap should be here | |
| auto child2_state = std::make_shared<StateNode>(child_state, style, | |
| SpacingDecision::Append); | |
| EXPECT_EQ(child2_state->next(), child_state.get()); | |
| EXPECT_EQ(child2_state->current_column, | |
| child_state->current_column + // 17 + | |
| ftokens[1].before.spaces_required + // 1 + | |
| tokens[1].text().length() // 1: "(" | |
| ); | |
| EXPECT_EQ(child2_state->cumulative_cost, 0); | |
| EXPECT_EQ(child2_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(child2_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*child2_state, *uwline), " function_caller ("); | |
| { | |
| // Third token, also appended to same line: | |
| // > function_caller ( 11 | |
| // > ^-- next wrap should be here | |
| auto child3_state = std::make_shared<StateNode>(child2_state, style, | |
| SpacingDecision::Append); | |
| EXPECT_EQ(child3_state->next(), child2_state.get()); | |
| EXPECT_EQ(child3_state->current_column, | |
| child2_state->current_column + // 19 + | |
| ftokens[2].before.spaces_required + // 1 + | |
| tokens[2].text().length() // 2: "11" | |
| ); | |
| EXPECT_EQ(child3_state->cumulative_cost, 0); | |
| EXPECT_EQ(child3_state->wrap_column_positions.size(), 2); | |
| EXPECT_EQ(child3_state->wrap_column_positions.top(), | |
| child2_state->current_column); | |
| EXPECT_EQ(Render(*child3_state, *uwline), " function_caller ( 11"); | |
| { | |
| // Fourth token, also appended to same line: | |
| // > function_caller ( 11 ) | |
| // > ^-- next wrap should be here, after closing balance group | |
| auto child4_state = std::make_shared<StateNode>( | |
| child3_state, style, SpacingDecision::Append); | |
| EXPECT_EQ(child4_state->next(), child3_state.get()); | |
| EXPECT_EQ(child4_state->current_column, | |
| child3_state->current_column + // 22 + | |
| ftokens[3].before.spaces_required + // 1 + | |
| tokens[3].text().length() // 1: ")" | |
| ); | |
| EXPECT_EQ(child4_state->cumulative_cost, 0); | |
| EXPECT_EQ(child4_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(child4_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*child4_state, *uwline), " function_caller ( 11 )"); | |
| } | |
| { | |
| // Fourth token, wrapped onto next line: | |
| // > function_caller ( 11 | |
| // > ) | |
| // > ^-- next wrap should be here, after closing balance group | |
| // TODO(fangism): should this be aligned with corresponding open-group? | |
| // > ) // aligned with open-group | |
| // As-is, it is not because we pop the column stack on close-group | |
| // first, which is not an unreasonable choice. | |
| auto child4_state = std::make_shared<StateNode>(child3_state, style, | |
| SpacingDecision::Wrap); | |
| EXPECT_EQ(child4_state->next(), child3_state.get()); | |
| EXPECT_EQ(child4_state->current_column, | |
| child2_state->wrap_column_positions | |
| .top() + // not a typo: child2_state | |
| tokens[3].text().length()); | |
| EXPECT_EQ(child4_state->cumulative_cost, | |
| ftokens[3].before.break_penalty); | |
| EXPECT_EQ(child4_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(child4_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*child4_state, *uwline), | |
| " function_caller ( 11\n" | |
| " )"); | |
| } | |
| } | |
| { | |
| // Third token, wrapped onto next line: | |
| // > function_caller ( | |
| // > 11 | |
| // > ^-- next wrap should be here | |
| auto child3_state = std::make_shared<StateNode>(child2_state, style, | |
| SpacingDecision::Wrap); | |
| EXPECT_EQ(child3_state->next(), child2_state.get()); | |
| EXPECT_EQ(child3_state->current_column, | |
| initial_column + style.wrap_spaces + tokens[2].text().length()); | |
| EXPECT_EQ(child3_state->cumulative_cost, ftokens[2].before.break_penalty); | |
| EXPECT_EQ(child3_state->wrap_column_positions.size(), 2); | |
| EXPECT_EQ(child3_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces * 2); | |
| EXPECT_EQ(Render(*child3_state, *uwline), | |
| " function_caller (\n" | |
| " 11"); | |
| { | |
| // Fourth token, appended onto same line: | |
| // > function_caller ( | |
| // > 11 ) | |
| // > ^-- next wrap should be here | |
| auto child4_state = std::make_shared<StateNode>( | |
| child3_state, style, SpacingDecision::Append); | |
| EXPECT_EQ(child4_state->next(), child3_state.get()); | |
| EXPECT_EQ(child4_state->current_column, | |
| child3_state->current_column + // 8 | |
| ftokens[3].before.spaces_required + // 1 | |
| tokens[3].text().length() // 1: ")" | |
| ); | |
| EXPECT_EQ(child4_state->cumulative_cost, child3_state->cumulative_cost); | |
| EXPECT_EQ(child4_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(child4_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*child4_state, *uwline), | |
| " function_caller (\n" | |
| " 11 )"); | |
| } | |
| { | |
| // Fourth token, wrapped onto next line: | |
| // > function_caller ( | |
| // > 11 | |
| // > ) | |
| // > ^-- next wrap should be here | |
| auto child4_state = std::make_shared<StateNode>(child3_state, style, | |
| SpacingDecision::Wrap); | |
| EXPECT_EQ(child4_state->next(), child3_state.get()); | |
| EXPECT_EQ( | |
| child4_state->current_column, | |
| initial_column + style.wrap_spaces + tokens[3].text().length()); | |
| EXPECT_EQ( | |
| child4_state->cumulative_cost, | |
| child3_state->cumulative_cost + ftokens[3].before.break_penalty); | |
| EXPECT_EQ(child4_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(child4_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*child4_state, *uwline), | |
| " function_caller (\n" | |
| " 11\n" | |
| " )"); | |
| } | |
| } | |
| } | |
| { | |
| // Second token, but wrapped onto next line: | |
| // > function_caller | |
| // > ( | |
| // > ^-- next wrap should be here | |
| auto child2_state = | |
| std::make_shared<StateNode>(child_state, style, SpacingDecision::Wrap); | |
| EXPECT_EQ(child2_state->next(), child_state.get()); | |
| EXPECT_EQ(child2_state->current_column, | |
| initial_column + // 2 + | |
| style.wrap_spaces + // 4 + | |
| tokens[1].text().length() // 1: "(" | |
| ); | |
| EXPECT_EQ(child2_state->cumulative_cost, ftokens[1].before.break_penalty); | |
| // wrap_column_positions stack is pushed *after* seeing the next token. | |
| EXPECT_EQ(child2_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(child2_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*child2_state, *uwline), | |
| " function_caller\n" | |
| " ("); | |
| { | |
| // Third token, appended to same line: | |
| // > function_caller | |
| // > ( 11 | |
| // > ^-- next wrap should be here | |
| auto child3_state = std::make_shared<StateNode>(child2_state, style, | |
| SpacingDecision::Append); | |
| EXPECT_EQ(child3_state->next(), child2_state.get()); | |
| EXPECT_EQ(child3_state->current_column, | |
| child2_state->current_column + // 7 | |
| ftokens[2].before.spaces_required + // 1 | |
| tokens[2].text().length() // 2: "11" | |
| ); | |
| EXPECT_EQ(child3_state->cumulative_cost, child2_state->cumulative_cost); | |
| EXPECT_EQ(child3_state->wrap_column_positions.size(), 2); | |
| EXPECT_EQ(child3_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces + tokens[1].text().length()); | |
| EXPECT_EQ(Render(*child3_state, *uwline), | |
| " function_caller\n" | |
| " ( 11"); | |
| { | |
| // Fourth token, appended to same line: | |
| // > function_caller | |
| // > ( 11 ) | |
| // > ^-- next wrap should be here | |
| auto child4_state = std::make_shared<StateNode>( | |
| child3_state, style, SpacingDecision::Append); | |
| EXPECT_EQ(child4_state->next(), child3_state.get()); | |
| EXPECT_EQ(child4_state->current_column, | |
| child3_state->current_column + // 10 | |
| ftokens[3].before.spaces_required + // 1 | |
| tokens[3].text().length() // 1: ")" | |
| ); | |
| EXPECT_EQ(child4_state->cumulative_cost, child3_state->cumulative_cost); | |
| EXPECT_EQ(child4_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(child4_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*child4_state, *uwline), | |
| " function_caller\n" | |
| " ( 11 )"); | |
| } | |
| { | |
| // Fourth token, wrapped onto next line: | |
| // > function_caller | |
| // > ( 11 | |
| // > ) | |
| // > ^-- next wrap should be here | |
| auto child4_state = std::make_shared<StateNode>(child3_state, style, | |
| SpacingDecision::Wrap); | |
| EXPECT_EQ(child4_state->next(), child3_state.get()); | |
| EXPECT_EQ(child4_state->current_column, | |
| child2_state->wrap_column_positions.top() + | |
| tokens[3].text().length() // 1: ")" | |
| ); | |
| EXPECT_EQ( | |
| child4_state->cumulative_cost, | |
| child3_state->cumulative_cost + ftokens[3].before.break_penalty); | |
| EXPECT_EQ(child4_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(child4_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*child4_state, *uwline), | |
| " function_caller\n" | |
| " ( 11\n" | |
| " )"); | |
| } | |
| } | |
| { | |
| // Third token, wrapped onto next line: | |
| // > function_caller | |
| // > ( | |
| // > 11 | |
| // > ^-- next wrap should be here | |
| auto child3_state = std::make_shared<StateNode>(child2_state, style, | |
| SpacingDecision::Wrap); | |
| EXPECT_EQ(child3_state->next(), child2_state.get()); | |
| EXPECT_EQ(child3_state->current_column, | |
| initial_column + (style.wrap_spaces * 2) + // 10 | |
| tokens[2].text().length() // 2: "11" | |
| ); | |
| EXPECT_EQ( | |
| child3_state->cumulative_cost, | |
| child2_state->cumulative_cost + ftokens[2].before.break_penalty); | |
| EXPECT_EQ(child3_state->wrap_column_positions.size(), 2); | |
| EXPECT_EQ(child3_state->wrap_column_positions.top(), | |
| initial_column + (style.wrap_spaces * 2)); | |
| EXPECT_EQ(Render(*child3_state, *uwline), | |
| " function_caller\n" | |
| " (\n" | |
| " 11"); | |
| { | |
| // Fourth token, appended to same line: | |
| // > function_caller | |
| // > ( | |
| // > 11 ) | |
| // > ^-- next wrap should be here | |
| auto child4_state = std::make_shared<StateNode>( | |
| child3_state, style, SpacingDecision::Append); | |
| EXPECT_EQ(child4_state->next(), child3_state.get()); | |
| EXPECT_EQ(child4_state->current_column, | |
| child3_state->current_column + // 10 | |
| ftokens[3].before.spaces_required + // 1 | |
| tokens[3].text().length() // 1: ")" | |
| ); | |
| EXPECT_EQ(child4_state->cumulative_cost, child3_state->cumulative_cost); | |
| EXPECT_EQ(child4_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(child4_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*child4_state, *uwline), | |
| " function_caller\n" | |
| " (\n" | |
| " 11 )"); | |
| } | |
| { | |
| // Fourth token, wrapped onto next line: | |
| // > function_caller | |
| // > ( | |
| // > 11 | |
| // > ) | |
| // > ^-- next wrap should be here | |
| auto child4_state = std::make_shared<StateNode>(child3_state, style, | |
| SpacingDecision::Wrap); | |
| EXPECT_EQ(child4_state->next(), child3_state.get()); | |
| EXPECT_EQ(child4_state->current_column, | |
| child_state->wrap_column_positions.top() + | |
| tokens[3].text().length() // 1: ")" | |
| ); | |
| EXPECT_EQ( | |
| child4_state->cumulative_cost, | |
| child3_state->cumulative_cost + ftokens[3].before.break_penalty); | |
| EXPECT_EQ(child4_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(child4_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_EQ(Render(*child4_state, *uwline), | |
| " function_caller\n" | |
| " (\n" | |
| " 11\n" | |
| " )"); | |
| } | |
| } | |
| } | |
| } | |
| // Tests new state can be built on top of previous state, appending token to | |
| // current line, where second token exceeds line length. | |
| TEST_F(StateNodeTestFixture, ConstructionAppendingPrevStateOverflow) { | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{0, "aaaaaaaaaaaaaaaaaaaaa"}, | |
| {1, "bbbbbbbbbbbbbbbbbb"}}; | |
| Initialize(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 1; | |
| ftokens[1].before.break_penalty = 8; | |
| // First token on line: | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); | |
| EXPECT_EQ(parent_state->cumulative_cost, 0); | |
| auto child_state = parent_state; | |
| { | |
| // Second token, also appended to same line as first: | |
| auto child2_state = std::make_shared<StateNode>(child_state, style, | |
| SpacingDecision::Append); | |
| EXPECT_EQ(child2_state->next(), child_state.get()); | |
| EXPECT_EQ(child2_state->current_column, | |
| child_state->current_column + // 8 + | |
| ftokens[1].before.spaces_required + // 1 + | |
| tokens[1].text().length() // 3: "TT2" | |
| ); | |
| EXPECT_EQ(child2_state->cumulative_cost, style.over_column_limit_penalty + | |
| child2_state->current_column - | |
| style.column_limit); | |
| } | |
| { | |
| // Second token, but wrapped onto a new line: | |
| auto child2_state = | |
| std::make_shared<StateNode>(child_state, style, SpacingDecision::Wrap); | |
| EXPECT_EQ(child2_state->next(), child_state.get()); | |
| EXPECT_EQ(child2_state->current_column, | |
| initial_column + // 2 + | |
| style.wrap_spaces + // 4 + | |
| tokens[1].text().length()); | |
| EXPECT_EQ(child2_state->cumulative_cost, ftokens[1].before.break_penalty); | |
| } | |
| } | |
| // Tests that column positions account for multiline tokens. | |
| TEST_F(StateNodeTestFixture, MultiLineTokenFront) { | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{0, "a23456789\nb234"}}; | |
| Initialize(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| // First token on line: | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| 4 /* length("b234") */); | |
| EXPECT_EQ(parent_state->cumulative_cost, 0); | |
| EXPECT_EQ(RenderFormattedText(*parent_state, *uwline), " a23456789\nb234"); | |
| } | |
| // Tests that newly calculated column positions account for multiline tokens. | |
| TEST_F(StateNodeTestFixture, MultiLineToken) { | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{0, "a23456789012345678901"}, | |
| {1, "b2345\nc234567890123"}}; | |
| Initialize(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 1; | |
| ftokens[1].before.break_penalty = 8; | |
| // First token on line: | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); | |
| EXPECT_EQ(parent_state->cumulative_cost, 0); | |
| { | |
| // Second token, also appended to same line as first: | |
| auto child_state = std::make_shared<StateNode>(parent_state, style, | |
| SpacingDecision::Append); | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| EXPECT_EQ(child_state->current_column, | |
| 13 // length("c2345...."), no wrapping indentation | |
| ); | |
| // no over-column-limit penalty | |
| EXPECT_EQ(child_state->cumulative_cost, 0); | |
| EXPECT_EQ(RenderFormattedText(*child_state, *uwline), | |
| " a23456789012345678901 b2345\nc234567890123"); | |
| } | |
| { | |
| // Second token, but wrapped onto a new line: | |
| auto child_state = | |
| std::make_shared<StateNode>(parent_state, style, SpacingDecision::Wrap); | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| EXPECT_EQ(child_state->current_column, | |
| 13 // length("c2345...."), no wrapping indentation | |
| ); | |
| // no over-column-limit penalty | |
| EXPECT_EQ(child_state->cumulative_cost, ftokens[1].before.break_penalty); | |
| EXPECT_EQ(RenderFormattedText(*child_state, *uwline), | |
| // indent level 1 + wrapping indentation | |
| " a23456789012345678901\n b2345\nc234567890123"); | |
| } | |
| } | |
| // Tests that multiline tokens are penalized based on overflow before first \n. | |
| TEST_F(StateNodeTestFixture, MultiLineTokenOverflow) { | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{0, "a23456789012345678901"}, | |
| {1, "b234567\nc234567890"}}; | |
| Initialize(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 1; | |
| ftokens[1].before.break_penalty = 8; | |
| // First token on line: | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); | |
| EXPECT_EQ(parent_state->cumulative_cost, 0); | |
| { | |
| // Second token, also appended to same line as first: | |
| auto child_state = std::make_shared<StateNode>(parent_state, style, | |
| SpacingDecision::Append); | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| EXPECT_EQ(child_state->current_column, | |
| 10 // length("c2345...."), no wrapping indentation | |
| ); | |
| // only over by 1 | |
| EXPECT_EQ(child_state->cumulative_cost, | |
| style.over_column_limit_penalty + parent_state->current_column + | |
| ftokens[1].before.spaces_required + | |
| 7 /* length("b23...7") */ - style.column_limit); | |
| EXPECT_EQ(RenderFormattedText(*child_state, *uwline), | |
| " a23456789012345678901 b234567\nc234567890"); | |
| } | |
| { | |
| // Second token, but wrapped onto a new line: | |
| auto child_state = | |
| std::make_shared<StateNode>(parent_state, style, SpacingDecision::Wrap); | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| EXPECT_EQ(child_state->current_column, | |
| 10 // length("c2345...."), no wrapping indentation | |
| ); | |
| // no over-column-limit penalty | |
| EXPECT_EQ(child_state->cumulative_cost, ftokens[1].before.break_penalty); | |
| EXPECT_EQ(RenderFormattedText(*child_state, *uwline), | |
| // indent level 1 + wrapping indentation | |
| " a23456789012345678901\n b234567\nc234567890"); | |
| } | |
| } | |
| // Tests new state can be built on top of previous state, wrapping token to | |
| // new line. | |
| TEST_F(StateNodeTestFixture, ConstructionWrappingLinePrevState) { | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = {{0, "token1"}, {1, "token2"}}; | |
| Initialize(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[1].before.break_penalty = 7; | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); | |
| EXPECT_EQ(parent_state->cumulative_cost, 0); | |
| // Wrap the next token onto a new line. | |
| auto child_state = | |
| std::make_shared<StateNode>(parent_state, style, SpacingDecision::Wrap); | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| EXPECT_EQ(child_state->current_column, | |
| initial_column + style.wrap_spaces + tokens[1].text().length()); | |
| EXPECT_EQ(child_state->cumulative_cost, ftokens[1].before.break_penalty); | |
| EXPECT_EQ(Render(*child_state, *uwline), | |
| " token1\n" | |
| " token2"); | |
| } | |
| // Tests that the default next-state appends when a token fits (column limit), | |
| // and wraps when it doesn't fit. | |
| TEST_F(StateNodeTestFixture, AppendIfItFitsTryToAppend) { | |
| const int kInitialIndent = 1; | |
| // Tokens stay under column limit. | |
| const std::vector<TokenInfo> tokens = { | |
| {0, "xxxxxXXXXX"}, | |
| {1, "yyyyyYYYYY"}, | |
| {2, "zzzzzZZZZZ"}, | |
| }; | |
| Initialize(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 1; | |
| ftokens[2].before.spaces_required = 1; | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; // 2 | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); | |
| EXPECT_EQ(parent_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(parent_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_TRUE(parent_state->IsRootState()); | |
| // Second token, also appended to same line as first: | |
| auto child_state = StateNode::AppendIfItFits(parent_state, style); | |
| EXPECT_EQ(child_state->spacing_choice, SpacingDecision::Append); | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| EXPECT_EQ(child_state->current_column, | |
| parent_state->current_column + // 12 + | |
| ftokens[1].before.spaces_required + // 1 + | |
| tokens[1].text().length() // 10 = 23 (< 30) | |
| ); | |
| EXPECT_FALSE(child_state->IsRootState()); | |
| // Third token, doesn't fit, and will be wrapped. | |
| auto child2_state = StateNode::AppendIfItFits(child_state, style); | |
| EXPECT_EQ(child2_state->spacing_choice, SpacingDecision::Wrap); | |
| EXPECT_EQ(child2_state->next(), child_state.get()); | |
| EXPECT_EQ(child2_state->current_column, | |
| initial_column + style.wrap_spaces + tokens[2].text().length()); | |
| } | |
| // Tests that the default next-state respects forced line breaks. | |
| TEST_F(StateNodeTestFixture, AppendIfItFitsForcedWrap) { | |
| const int kInitialIndent = 1; | |
| const std::vector<TokenInfo> tokens = { | |
| {0, "xxxxxXXXXX"}, | |
| {1, "yyyyyYYYYY"}, | |
| }; | |
| Initialize(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 1; | |
| // Tokens stay under column limit, but here, we force a wrap. | |
| ftokens[1].before.break_decision = SpacingOptions::MustWrap; | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; // 2 | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); | |
| EXPECT_EQ(parent_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(parent_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_TRUE(parent_state->IsRootState()); | |
| // Second token, forced to wrap onto new line. | |
| auto child_state = StateNode::AppendIfItFits(parent_state, style); | |
| EXPECT_EQ(child_state->spacing_choice, SpacingDecision::Wrap); | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| EXPECT_EQ(child_state->current_column, | |
| initial_column + style.wrap_spaces + tokens[0].text().length()); | |
| EXPECT_FALSE(child_state->IsRootState()); | |
| } | |
| // Tests that QuickFinish repeatedly applies AppendIfItFits. | |
| TEST_F(StateNodeTestFixture, QuickFinish) { | |
| const int kInitialIndent = 1; | |
| // Tokens stay under column limit. | |
| const std::vector<TokenInfo> tokens = { | |
| {0, "xxxxxXXXXX"}, | |
| {1, "yyyyyYYYYY"}, | |
| {2, "zzzzzZZZZZ"}, | |
| }; | |
| Initialize(kInitialIndent, tokens); | |
| auto& ftokens = pre_format_tokens_; | |
| ftokens[0].before.spaces_required = 1; | |
| ftokens[1].before.spaces_required = 1; | |
| ftokens[2].before.spaces_required = 1; | |
| auto parent_state = std::make_shared<StateNode>(*uwline, style); | |
| const int initial_column = kInitialIndent * style.indentation_spaces; // 2 | |
| EXPECT_EQ(ABSL_DIE_IF_NULL(parent_state)->current_column, | |
| initial_column + tokens[0].text().length()); | |
| EXPECT_EQ(parent_state->wrap_column_positions.size(), 1); | |
| EXPECT_EQ(parent_state->wrap_column_positions.top(), | |
| initial_column + style.wrap_spaces); | |
| EXPECT_TRUE(parent_state->IsRootState()); | |
| auto final_state = StateNode::QuickFinish(parent_state, style); | |
| // Checking up the ancestry chain of previous states | |
| // Third token, doesn't fit, and will be wrapped. | |
| EXPECT_EQ(final_state->spacing_choice, SpacingDecision::Wrap); | |
| // Second token, also appended to same line as first: | |
| const auto* child_state = final_state->next(); | |
| EXPECT_EQ(child_state->spacing_choice, SpacingDecision::Append); | |
| // Second state is decended from initial state. | |
| EXPECT_EQ(child_state->next(), parent_state.get()); | |
| } | |
| // Tests that equal cumulative penalty does not count as less. | |
| TEST_F(StateNodeTestFixture, OperatorLessSelf) { | |
| const std::vector<TokenInfo> tokens; | |
| Initialize(0, tokens); | |
| StateNode s(*uwline, style); | |
| EXPECT_FALSE(s < s); | |
| } | |
| // Tests for inequality of cumulative penalties. | |
| TEST_F(StateNodeTestFixture, OperatorLessUnequal) { | |
| const std::vector<TokenInfo> tokens; | |
| Initialize(0, tokens); | |
| StateNode s(*uwline, style); | |
| s.cumulative_cost = 3; | |
| StateNode t(*uwline, style); | |
| t.cumulative_cost = 4; | |
| EXPECT_TRUE(s < t); | |
| EXPECT_FALSE(t < s); | |
| } | |
| // Test that string representation is as expected. | |
| TEST_F(StateNodeTestFixture, Stringify) { | |
| const std::vector<TokenInfo> tokens; | |
| Initialize(0, tokens); | |
| StateNode::path_type path; | |
| StateNode s(*uwline, style); | |
| s.spacing_choice = SpacingDecision::Wrap; | |
| s.current_column = 7; | |
| s.cumulative_cost = 11; | |
| s.wrap_column_positions.top() = 3; | |
| std::ostringstream stream; | |
| stream << s; | |
| EXPECT_EQ(stream.str(), "spacing:wrap, col@7, cost=11, [...3]"); | |
| } | |
| } // namespace | |
| } // namespace verible |