| // 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/align.h" | |
| #include <sstream> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/str_split.h" | |
| #include "common/formatting/format_token.h" | |
| #include "common/formatting/token_partition_tree.h" | |
| #include "common/formatting/token_partition_tree_test_utils.h" | |
| #include "common/formatting/unwrapped_line_test_utils.h" | |
| #include "common/text/tree_builder_test_util.h" | |
| #include "common/util/range.h" | |
| #include "common/util/spacer.h" | |
| #include "common/util/value_saver.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| namespace verible { | |
| namespace { | |
| using ::testing::ElementsAre; | |
| TEST(AlignmentPolicyTest, StringRepresentation) { | |
| std::ostringstream stream; | |
| stream << AlignmentPolicy::kAlign; | |
| EXPECT_EQ(stream.str(), "align"); | |
| EXPECT_EQ(AbslUnparseFlag(AlignmentPolicy::kAlign), "align"); | |
| } | |
| TEST(AlignmentPolicyTest, InvalidEnum) { | |
| AlignmentPolicy policy; | |
| std::string error; | |
| EXPECT_FALSE(AbslParseFlag("invalid", &policy, &error)); | |
| } | |
| // Helper class that initializes an array of tokens to be partitioned | |
| // into TokenPartitionTree. | |
| class AlignmentTestFixture : public ::testing::Test, | |
| public UnwrappedLineMemoryHandler { | |
| public: | |
| explicit AlignmentTestFixture(absl::string_view text) | |
| : sample_(text), | |
| tokens_(absl::StrSplit(sample_, absl::ByAnyChar(" \n"), | |
| absl::SkipEmpty())) { | |
| for (const auto token : tokens_) { | |
| ftokens_.emplace_back(TokenInfo{1, token}); | |
| } | |
| // sample_ is the memory-owning string buffer | |
| CreateTokenInfosExternalStringBuffer(ftokens_); | |
| } | |
| protected: | |
| const std::string sample_; | |
| const std::vector<absl::string_view> tokens_; | |
| std::vector<TokenInfo> ftokens_; | |
| }; | |
| static const AlignmentColumnProperties FlushLeft(true); | |
| static const AlignmentColumnProperties FlushRight(false); | |
| class TokenColumnizer : public ColumnSchemaScanner { | |
| public: | |
| TokenColumnizer() = default; | |
| void Visit(const SyntaxTreeNode& node) final { | |
| ColumnSchemaScanner::Visit(node); | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| // Let each token occupy its own column. | |
| ReserveNewColumn(leaf, FlushLeft); | |
| } | |
| }; | |
| class TokenColumnizerRightFlushed : public ColumnSchemaScanner { | |
| public: | |
| TokenColumnizerRightFlushed() = default; | |
| void Visit(const SyntaxTreeNode& node) final { | |
| ColumnSchemaScanner::Visit(node); | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| // Let each token occupy its own column. | |
| ReserveNewColumn(leaf, FlushRight); | |
| } | |
| }; | |
| class TabularAlignTokenTest : public AlignmentTestFixture { | |
| public: | |
| TabularAlignTokenTest() | |
| : AlignmentTestFixture("one two three four five six") {} | |
| }; | |
| static bool IgnoreNone(const TokenPartitionTree&) { return false; } | |
| static std::vector<verible::TaggedTokenPartitionRange> | |
| PartitionBetweenBlankLines(const TokenPartitionRange& range) { | |
| // Don't care about the subtype tag. | |
| return GetSubpartitionsBetweenBlankLinesSingleTag(range, 0); | |
| } | |
| static const ExtractAlignmentGroupsFunction kDefaultAlignmentHandler = | |
| ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, &IgnoreNone, | |
| AlignmentCellScannerGenerator<TokenColumnizer>(), | |
| AlignmentPolicy::kAlign); | |
| static const ExtractAlignmentGroupsFunction kFlushLeftAlignmentHandler = | |
| ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, &IgnoreNone, | |
| AlignmentCellScannerGenerator<TokenColumnizer>(), | |
| AlignmentPolicy::kFlushLeft); | |
| static const ExtractAlignmentGroupsFunction kPreserveAlignmentHandler = | |
| ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, &IgnoreNone, | |
| AlignmentCellScannerGenerator<TokenColumnizer>(), | |
| AlignmentPolicy::kPreserve); | |
| static const ExtractAlignmentGroupsFunction kInferAlignmentHandler = | |
| ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, &IgnoreNone, | |
| AlignmentCellScannerGenerator<TokenColumnizer>(), | |
| AlignmentPolicy::kInferUserIntent); | |
| TEST_F(TabularAlignTokenTest, EmptyPartitionRange) { | |
| const auto begin = pre_format_tokens_.begin(); | |
| UnwrappedLine all(0, begin); | |
| all.SpanUpToToken(pre_format_tokens_.end()); | |
| using tree_type = TokenPartitionTree; | |
| tree_type partition{all}; // no children subpartitions | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kDefaultAlignmentHandler, | |
| &partition); | |
| // Not crashing is success. | |
| // Ideally, we would like to verify that partition was *not* modified | |
| // by making a deep copy and then checking DeepEqual, however, | |
| // a deep copy of UnwrappedLine does NOT copy the PreFormatTokens, | |
| // so they end up pointing to the same ranges anyway. | |
| } | |
| class MatrixTreeAlignmentTestFixture : public AlignmentTestFixture { | |
| public: | |
| MatrixTreeAlignmentTestFixture(absl::string_view text) | |
| : AlignmentTestFixture(text), | |
| syntax_tree_(nullptr), // for subclasses to initialize | |
| partition_(/* temporary */ UnwrappedLine()) {} | |
| std::string Render() { | |
| std::ostringstream stream; | |
| for (auto& child : partition_.Children()) { | |
| const auto policy = child.Value().PartitionPolicy(); | |
| if (policy == PartitionPolicyEnum::kAlreadyFormatted) { | |
| ApplyAlreadyFormattedPartitionPropertiesToTokens(&child, | |
| &pre_format_tokens_); | |
| } | |
| stream << FormattedExcerpt(child.Value()) << std::endl; | |
| } | |
| return stream.str(); | |
| } | |
| protected: | |
| // Syntax tree from which token partition originates. | |
| SymbolPtr syntax_tree_; | |
| // Format token partitioning (what would be the result of TreeUnwrapper). | |
| TokenPartitionTree partition_; | |
| }; | |
| class Sparse3x3MatrixAlignmentTest : public MatrixTreeAlignmentTestFixture { | |
| public: | |
| Sparse3x3MatrixAlignmentTest( | |
| absl::string_view text = "one two three four five six") | |
| : MatrixTreeAlignmentTestFixture(text) { | |
| // From the sample_ text, each pair of tokens will span a subpartition. | |
| // Construct a 2-level partition that looks like this: | |
| // | |
| // | | one | two | | |
| // | three | | four | | |
| // | five | six | | | |
| // | |
| // where blank cells represent positional nullptrs in the syntax tree. | |
| syntax_tree_ = TNode(1, | |
| TNode(2, // | |
| nullptr, // | |
| Leaf(1, tokens_[0]), // | |
| Leaf(1, tokens_[1])), | |
| TNode(2, // | |
| Leaf(1, tokens_[2]), // | |
| nullptr, // | |
| Leaf(1, tokens_[3])), | |
| TNode(2, // | |
| Leaf(1, tokens_[4]), // | |
| Leaf(1, tokens_[5]), // | |
| nullptr)); | |
| // Establish format token ranges per partition. | |
| const auto begin = pre_format_tokens_.begin(); | |
| UnwrappedLine all(0, begin); | |
| all.SpanUpToToken(pre_format_tokens_.end()); | |
| all.SetOrigin(&*syntax_tree_); | |
| UnwrappedLine child1(0, begin); | |
| child1.SpanUpToToken(begin + 2); | |
| child1.SetOrigin(DescendPath(*syntax_tree_, {0})); | |
| UnwrappedLine child2(0, begin + 2); | |
| child2.SpanUpToToken(begin + 4); | |
| child2.SetOrigin(DescendPath(*syntax_tree_, {1})); | |
| UnwrappedLine child3(0, begin + 4); | |
| child3.SpanUpToToken(begin + 6); | |
| child3.SetOrigin(DescendPath(*syntax_tree_, {2})); | |
| // Construct 2-level token partition. | |
| using tree_type = TokenPartitionTree; | |
| partition_ = tree_type{ | |
| all, | |
| tree_type{child1}, | |
| tree_type{child2}, | |
| tree_type{child3}, | |
| }; | |
| } | |
| }; | |
| TEST_F(Sparse3x3MatrixAlignmentTest, ZeroInterTokenPadding) { | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kDefaultAlignmentHandler, | |
| &partition_); | |
| // Verify string rendering of result. | |
| // Here, spaces_required before every token is 0, so expect no padding | |
| // between columns. | |
| EXPECT_EQ(Render(), // | |
| " onetwo\n" | |
| "three four\n" | |
| "five six\n"); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, AlignmentPolicyFlushLeft) { | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kFlushLeftAlignmentHandler, | |
| &partition_); | |
| EXPECT_EQ(Render(), // minimum spaces in this example is 0 | |
| "onetwo\n" | |
| "threefour\n" | |
| "fivesix\n"); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, AlignmentPolicyPreserve) { | |
| // Set previous-token string pointers to preserve spaces. | |
| ConnectPreFormatTokensPreservedSpaceStarts(sample_.data(), | |
| &pre_format_tokens_); | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kPreserveAlignmentHandler, | |
| &partition_); | |
| EXPECT_EQ(Render(), // original spacing was 1 | |
| "one two\n" | |
| "three four\n" | |
| "five six\n"); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, OneInterTokenPadding) { | |
| // Require 1 space between tokens. | |
| // Will have no effect on the first token in each partition. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kDefaultAlignmentHandler, | |
| &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| " one two\n" | |
| "three four\n" | |
| "five six\n"); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, OneInterTokenPaddingExceptFront) { | |
| // Require 1 space between tokens, except ones at the beginning of partitions. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| pre_format_tokens_[0].before.spaces_required = 0; | |
| pre_format_tokens_[2].before.spaces_required = 0; | |
| pre_format_tokens_[4].before.spaces_required = 0; | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kDefaultAlignmentHandler, | |
| &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| " one two\n" | |
| "three four\n" | |
| "five six\n"); | |
| } | |
| static const ExtractAlignmentGroupsFunction kFlushRightAlignmentHandler = | |
| ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, &IgnoreNone, | |
| AlignmentCellScannerGenerator<TokenColumnizerRightFlushed>(), | |
| AlignmentPolicy::kAlign); | |
| TEST_F(Sparse3x3MatrixAlignmentTest, RightFlushed) { | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kFlushRightAlignmentHandler, | |
| &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| " one two\n" | |
| "three four\n" | |
| " five six\n"); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, OneInterTokenPaddingWithIndent) { | |
| // Require 1 space between tokens, except ones at the beginning of partitions. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| // Indent each partition. | |
| for (auto& child : partition_.Children()) { | |
| child.Value().SetIndentationSpaces(4); | |
| } | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kDefaultAlignmentHandler, | |
| &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| " one two\n" | |
| " three four\n" | |
| " five six\n"); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, IgnoreCommentLine) { | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| // Leave the 'commented' line indented. | |
| pre_format_tokens_[2].before.break_decision = SpacingOptions::MustWrap; | |
| partition_.Children()[1].Value().SetIndentationSpaces(1); | |
| // Pretend lines that begin with "three" are to be ignored, like comments. | |
| auto ignore_threes = [](const TokenPartitionTree& partition) { | |
| return partition.Value().TokensRange().front().Text() == "three"; | |
| }; | |
| const ExtractAlignmentGroupsFunction handler = ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, ignore_threes, | |
| AlignmentCellScannerGenerator<TokenColumnizer>(), | |
| AlignmentPolicy::kAlign); | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), handler, &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| " one two\n" // is aligned | |
| " three four\n" // this line does not participate in alignment | |
| "five six\n" // is aligned | |
| ); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, CompletelyDisabledNoAlignment) { | |
| // Disabled ranges use original spacing | |
| ConnectPreFormatTokensPreservedSpaceStarts(sample_.data(), | |
| &pre_format_tokens_); | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| TabularAlignTokens(40, sample_, | |
| // Alignment disabled over entire range. | |
| ByteOffsetSet({{0, static_cast<int>(sample_.length())}}), | |
| kDefaultAlignmentHandler, &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| "one two\n" | |
| "three four\n" | |
| "five six\n"); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, CompletelyDisabledNoAlignmentWithIndent) { | |
| // Disabled ranges use original spacing | |
| ConnectPreFormatTokensPreservedSpaceStarts(sample_.data(), | |
| &pre_format_tokens_); | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| for (auto& child : partition_.Children()) { | |
| child.Value().SetIndentationSpaces(3); | |
| } | |
| pre_format_tokens_[0].before.break_decision = SpacingOptions::MustWrap; | |
| pre_format_tokens_[2].before.break_decision = SpacingOptions::MustWrap; | |
| pre_format_tokens_[4].before.break_decision = SpacingOptions::MustWrap; | |
| TabularAlignTokens(40, sample_, | |
| // Alignment disabled over entire range. | |
| ByteOffsetSet({{0, static_cast<int>(sample_.length())}}), | |
| kDefaultAlignmentHandler, &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| " one two\n" | |
| " three four\n" | |
| " five six\n"); | |
| } | |
| class Sparse3x3MatrixAlignmentMoreSpacesTest | |
| : public Sparse3x3MatrixAlignmentTest { | |
| public: | |
| Sparse3x3MatrixAlignmentMoreSpacesTest() | |
| : Sparse3x3MatrixAlignmentTest("one two\nthree four\nfive six") { | |
| // This is needed for preservation of original spacing. | |
| ConnectPreFormatTokensPreservedSpaceStarts(sample_.data(), | |
| &pre_format_tokens_); | |
| } | |
| }; | |
| TEST_F(Sparse3x3MatrixAlignmentMoreSpacesTest, | |
| PartiallyDisabledIndentButPreserveOtherSpaces) { | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| for (auto& child : partition_.Children()) { | |
| child.Value().SetIndentationSpaces(1); | |
| } | |
| pre_format_tokens_[0].before.break_decision = SpacingOptions::MustWrap; | |
| pre_format_tokens_[2].before.break_decision = SpacingOptions::MustWrap; | |
| pre_format_tokens_[4].before.break_decision = SpacingOptions::MustWrap; | |
| TabularAlignTokens( | |
| 40, sample_, | |
| // Alignment disabled over line 2 | |
| ByteOffsetSet({{static_cast<int>(sample_.find_first_of('\n') + 1), | |
| static_cast<int>(sample_.find("four") + 4)}}), | |
| kDefaultAlignmentHandler, &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), | |
| // all lines indented properly but internal spacing preserved | |
| " one two\n" | |
| " three four\n" | |
| " five six\n"); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, PartiallyDisabledNoAlignment) { | |
| // Disabled ranges use original spacing | |
| ConnectPreFormatTokensPreservedSpaceStarts(sample_.data(), | |
| &pre_format_tokens_); | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| int midpoint = sample_.length() / 2; | |
| TabularAlignTokens(40, sample_, | |
| // Alignment disabled over partial range. | |
| ByteOffsetSet({{midpoint, midpoint + 1}}), | |
| kDefaultAlignmentHandler, &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| "one two\n" | |
| "three four\n" | |
| "five six\n"); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, DisabledByColumnLimit) { | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| TabularAlignTokens(13, sample_, ByteOffsetSet(), | |
| // Column limit chosen to be smaller than sum of columns' | |
| // widths. 5 (no left padding) +4 +5 = 14, so we choose 13 | |
| kDefaultAlignmentHandler, &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| "one two\n" | |
| "three four\n" | |
| "five six\n"); | |
| } | |
| TEST_F(Sparse3x3MatrixAlignmentTest, DisabledByColumnLimitIndented) { | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| for (auto& child : partition_.Children()) { | |
| child.Value().SetIndentationSpaces(3); | |
| } | |
| TabularAlignTokens( | |
| 16, sample_, ByteOffsetSet(), | |
| // Column limit chosen to be smaller than sum of columns' widths. | |
| // 3 (indent) +5 (no left padding) +4 +5 = 17, so we choose 16 | |
| kDefaultAlignmentHandler, &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| "one two\n" | |
| "three four\n" | |
| "five six\n"); | |
| } | |
| class MultiAlignmentGroupTest : public AlignmentTestFixture { | |
| public: | |
| MultiAlignmentGroupTest() | |
| : AlignmentTestFixture("one two three four\n\nfive seven six eight"), | |
| // From the sample_ text, each pair of tokens will span a subpartition. | |
| // Construct a 2-level partition that looks like this (grouped): | |
| // | |
| // | | one | two | | |
| // | three | | four | | |
| // | |
| // | five | seven | | | |
| // | | six | eight | | |
| // | |
| // where blank cells represent positional nullptrs in the syntax tree. | |
| syntax_tree_(TNode(1, | |
| TNode(2, // | |
| nullptr, // | |
| Leaf(1, tokens_[0]), // | |
| Leaf(1, tokens_[1])), | |
| TNode(2, // | |
| Leaf(1, tokens_[2]), // | |
| nullptr, // | |
| Leaf(1, tokens_[3])), | |
| TNode(2, // | |
| Leaf(1, tokens_[4]), // | |
| Leaf(1, tokens_[5]), // | |
| nullptr), | |
| TNode(2, // | |
| nullptr, // | |
| Leaf(1, tokens_[6]), // | |
| Leaf(1, tokens_[7])))), | |
| partition_(/* temporary */ UnwrappedLine()) { | |
| // Establish format token ranges per partition. | |
| const auto begin = pre_format_tokens_.begin(); | |
| UnwrappedLine all(0, begin); | |
| all.SpanUpToToken(pre_format_tokens_.end()); | |
| all.SetOrigin(&*syntax_tree_); | |
| UnwrappedLine child1(0, begin); | |
| child1.SpanUpToToken(begin + 2); | |
| child1.SetOrigin(DescendPath(*syntax_tree_, {0})); | |
| UnwrappedLine child2(0, begin + 2); | |
| child2.SpanUpToToken(begin + 4); | |
| child2.SetOrigin(DescendPath(*syntax_tree_, {1})); | |
| UnwrappedLine child3(0, begin + 4); | |
| child3.SpanUpToToken(begin + 6); | |
| child3.SetOrigin(DescendPath(*syntax_tree_, {2})); | |
| UnwrappedLine child4(0, begin + 6); | |
| child4.SpanUpToToken(begin + 8); | |
| child4.SetOrigin(DescendPath(*syntax_tree_, {3})); | |
| // Construct 2-level token partition. | |
| using tree_type = TokenPartitionTree; | |
| partition_ = tree_type{ | |
| all, | |
| tree_type{child1}, | |
| tree_type{child2}, | |
| tree_type{child3}, | |
| tree_type{child4}, | |
| }; | |
| } | |
| std::string Render() { | |
| std::ostringstream stream; | |
| int position = 0; | |
| const absl::string_view text(sample_); | |
| for (auto& child : partition_.Children()) { | |
| const auto policy = child.Value().PartitionPolicy(); | |
| if (policy == PartitionPolicyEnum::kAlreadyFormatted) { | |
| ApplyAlreadyFormattedPartitionPropertiesToTokens(&child, | |
| &pre_format_tokens_); | |
| } | |
| // emulate preserving vertical spacing | |
| const auto tokens_range = child.Value().TokensRange(); | |
| const auto front_offset = tokens_range.front().token->left(text); | |
| const absl::string_view spaces = | |
| text.substr(position, front_offset - position); | |
| const auto newlines = | |
| std::max<int>(std::count(spaces.begin(), spaces.end(), '\n') - 1, 0); | |
| stream << Spacer(newlines, '\n'); | |
| stream << FormattedExcerpt(child.Value()) << std::endl; | |
| position = tokens_range.back().token->right(text); | |
| } | |
| return stream.str(); | |
| } | |
| protected: | |
| // Syntax tree from which token partition originates. | |
| SymbolPtr syntax_tree_; | |
| // Format token partitioning (what would be the result of TreeUnwrapper). | |
| TokenPartitionTree partition_; | |
| }; | |
| TEST_F(MultiAlignmentGroupTest, BlankLineSeparatedGroups) { | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kDefaultAlignmentHandler, | |
| &partition_); | |
| // Verify string rendering of result. | |
| EXPECT_EQ(Render(), // | |
| " one two\n" | |
| "three four\n" | |
| "\n" // preserve blank line | |
| "five seven\n" | |
| " six eight\n"); | |
| } | |
| // TODO(fangism): test case that demonstrates repeated constructs in a deeper | |
| // syntax tree. | |
| // TODO(fangism): test case for demonstrating flush-right | |
| class GetPartitionAlignmentSubrangesTestFixture : public AlignmentTestFixture { | |
| public: | |
| GetPartitionAlignmentSubrangesTestFixture() | |
| : AlignmentTestFixture( | |
| "ignore match nomatch match match match nomatch nomatch match " | |
| "ignore match"), | |
| syntax_tree_(TNode( | |
| 1, // one token per partition for simplicity | |
| TNode(2, Leaf(1, tokens_[0])), // | |
| TNode(2, Leaf(1, tokens_[1])), // singleton range too short here | |
| TNode(2, Leaf(1, tokens_[2])), // | |
| TNode(2, Leaf(1, tokens_[3])), // expect match from here | |
| TNode(2, Leaf(1, tokens_[4])), // ... | |
| TNode(2, Leaf(1, tokens_[5])), // ... to here (inclusive) | |
| TNode(2, Leaf(1, tokens_[6])), // | |
| TNode(2, Leaf(1, tokens_[7])), // | |
| TNode(2, Leaf(1, tokens_[8])), // and from here to the end(). | |
| TNode(2, Leaf(1, tokens_[9])), // ... | |
| TNode(2, Leaf(1, tokens_[10])))), // ... | |
| partition_(/* temporary */ UnwrappedLine()) { | |
| // Establish format token ranges per partition. | |
| const auto begin = pre_format_tokens_.begin(); | |
| UnwrappedLine all(0, begin); | |
| all.SpanUpToToken(pre_format_tokens_.end()); | |
| all.SetOrigin(&*syntax_tree_); | |
| std::vector<UnwrappedLine> uwlines; | |
| for (size_t i = 0; i < pre_format_tokens_.size(); ++i) { | |
| uwlines.emplace_back(0, begin + i); | |
| uwlines.back().SpanUpToToken(begin + i + 1); | |
| uwlines.back().SetOrigin(DescendPath(*syntax_tree_, {i})); | |
| } | |
| // Construct 2-level token partition. | |
| using tree_type = TokenPartitionTree; | |
| partition_ = tree_type{ | |
| all, | |
| tree_type{uwlines[0]}, | |
| tree_type{uwlines[1]}, // one match not enough | |
| tree_type{uwlines[2]}, | |
| tree_type{uwlines[3]}, // start of match | |
| tree_type{uwlines[4]}, // ... | |
| tree_type{uwlines[5]}, // ... | |
| tree_type{uwlines[6]}, // end of match | |
| tree_type{uwlines[7]}, | |
| tree_type{uwlines[8]}, // start of match | |
| tree_type{uwlines[9]}, // ... | |
| tree_type{uwlines[10]}, // ... | |
| }; | |
| } | |
| protected: | |
| static AlignmentGroupAction PartitionSelector( | |
| const TokenPartitionTree& partition) { | |
| const absl::string_view text = | |
| partition.Value().TokensRange().front().Text(); | |
| if (text == "match") { | |
| return AlignmentGroupAction::kMatch; | |
| } else if (text == "nomatch") { | |
| return AlignmentGroupAction::kNoMatch; | |
| } else { | |
| return AlignmentGroupAction::kIgnore; | |
| } | |
| } | |
| protected: | |
| // Syntax tree from which token partition originates. | |
| SymbolPtr syntax_tree_; | |
| // Format token partitioning (what would be the result of TreeUnwrapper). | |
| TokenPartitionTree partition_; | |
| }; | |
| TEST_F(GetPartitionAlignmentSubrangesTestFixture, VariousRanges) { | |
| const TokenPartitionRange children(partition_.Children().begin(), | |
| partition_.Children().end()); | |
| const std::vector<TaggedTokenPartitionRange> ranges( | |
| GetPartitionAlignmentSubranges(children, [](const TokenPartitionTree& | |
| partition) { | |
| // Don't care about the subtype tag. | |
| return AlignedPartitionClassification{PartitionSelector(partition), 0}; | |
| })); | |
| using P = std::pair<int, int>; | |
| std::vector<P> range_indices; | |
| for (const auto& range : ranges) { | |
| range_indices.push_back(SubRangeIndices(range.range, children)); | |
| } | |
| EXPECT_THAT(range_indices, ElementsAre(P(3, 6), P(8, 11))); | |
| } | |
| class GetPartitionAlignmentSubrangesSubtypedTestFixture | |
| : public AlignmentTestFixture { | |
| public: | |
| GetPartitionAlignmentSubrangesSubtypedTestFixture() | |
| : AlignmentTestFixture( | |
| "match:X match:X match:X match:Y match:Y match:Y nomatch match:Z " | |
| "match:X match:Z match:Z ignore match:Y match:Y"), | |
| syntax_tree_(TNode( | |
| 1, // one token per partition for simplicity | |
| TNode(2, Leaf(1, tokens_[0])), // match:X start | |
| TNode(2, Leaf(1, tokens_[1])), // ... | |
| TNode(2, Leaf(1, tokens_[2])), // match:X end | |
| TNode(2, Leaf(1, tokens_[3])), // match:Y start | |
| TNode(2, Leaf(1, tokens_[4])), // ... | |
| TNode(2, Leaf(1, tokens_[5])), // match:Y end | |
| TNode(2, Leaf(1, tokens_[6])), // | |
| TNode(2, Leaf(1, tokens_[7])), // match:Z start/end (too small) | |
| TNode(2, Leaf(1, tokens_[8])), // match:X start/end (too small) | |
| TNode(2, Leaf(1, tokens_[9])), // match:Z start | |
| TNode(2, Leaf(1, tokens_[10])), // match:Z end | |
| TNode(2, Leaf(1, tokens_[11])), // | |
| TNode(2, Leaf(1, tokens_[12])), // match:Y start | |
| TNode(2, Leaf(1, tokens_[13])))), // match:Y end | |
| partition_(/* temporary */ UnwrappedLine()) { | |
| // Establish format token ranges per partition. | |
| const auto begin = pre_format_tokens_.begin(); | |
| UnwrappedLine all(0, begin); | |
| all.SpanUpToToken(pre_format_tokens_.end()); | |
| all.SetOrigin(&*syntax_tree_); | |
| std::vector<UnwrappedLine> uwlines; | |
| for (size_t i = 0; i < pre_format_tokens_.size(); ++i) { | |
| uwlines.emplace_back(0, begin + i); | |
| uwlines.back().SpanUpToToken(begin + i + 1); | |
| uwlines.back().SetOrigin(DescendPath(*syntax_tree_, {i})); | |
| } | |
| // Construct 2-level token partition. | |
| using tree_type = TokenPartitionTree; | |
| partition_ = tree_type{ | |
| all, | |
| tree_type{uwlines[0]}, // match:X start | |
| tree_type{uwlines[1]}, // ... | |
| tree_type{uwlines[2]}, // match:X end | |
| tree_type{uwlines[3]}, // match:Y start | |
| tree_type{uwlines[4]}, // ... | |
| tree_type{uwlines[5]}, // match:Y end | |
| tree_type{uwlines[6]}, | |
| tree_type{uwlines[7]}, // match:Z start/end (no group) | |
| tree_type{uwlines[8]}, // match:X start/end (no group) | |
| tree_type{uwlines[9]}, // match:Z start | |
| tree_type{uwlines[10]}, // match:Z end | |
| tree_type{uwlines[11]}, | |
| tree_type{uwlines[12]}, // match:Y start | |
| tree_type{uwlines[13]}, // match:Y end | |
| }; | |
| } | |
| protected: | |
| static AlignedPartitionClassification PartitionSelector( | |
| const TokenPartitionTree& partition) { | |
| const absl::string_view text = | |
| partition.Value().TokensRange().front().Text(); | |
| if (absl::StartsWith(text, "match")) { | |
| const auto toks = absl::StrSplit(text, absl::ByChar(':')); | |
| CHECK(toks.begin() != toks.end()); | |
| absl::string_view last = *std::next(toks.begin()); | |
| // Use the first character after the : as the subtype, so 'X', 'Y', 'Z'. | |
| return {AlignmentGroupAction::kMatch, static_cast<int>(last.front())}; | |
| } else if (text == "nomatch") { | |
| return {AlignmentGroupAction::kNoMatch}; | |
| } else { | |
| return {AlignmentGroupAction::kIgnore}; | |
| } | |
| } | |
| protected: | |
| // Syntax tree from which token partition originates. | |
| SymbolPtr syntax_tree_; | |
| // Format token partitioning (what would be the result of TreeUnwrapper). | |
| TokenPartitionTree partition_; | |
| }; | |
| TEST_F(GetPartitionAlignmentSubrangesSubtypedTestFixture, VariousRanges) { | |
| const TokenPartitionRange children(partition_.Children().begin(), | |
| partition_.Children().end()); | |
| const std::vector<TaggedTokenPartitionRange> ranges( | |
| GetPartitionAlignmentSubranges(children, &PartitionSelector)); | |
| using P = std::pair<int, int>; | |
| std::vector<P> range_indices; | |
| for (const auto& range : ranges) { | |
| range_indices.push_back(SubRangeIndices(range.range, children)); | |
| } | |
| EXPECT_THAT(range_indices, | |
| ElementsAre(P(0, 3), P(3, 6), P(9, 12), P(12, 14))); | |
| // Check subtype tags. | |
| ASSERT_EQ(ranges.size(), 4); | |
| EXPECT_EQ(ranges[0].match_subtype, 'X'); // [0,3) | |
| EXPECT_EQ(ranges[1].match_subtype, 'Y'); // [3,6) | |
| EXPECT_EQ(ranges[2].match_subtype, 'Z'); // [9,12) | |
| EXPECT_EQ(ranges[3].match_subtype, 'Y'); // [12,14) | |
| } | |
| class Dense2x2MatrixAlignmentTest : public MatrixTreeAlignmentTestFixture { | |
| public: | |
| Dense2x2MatrixAlignmentTest(absl::string_view text = "one two three four") | |
| : MatrixTreeAlignmentTestFixture(text) { | |
| CHECK_EQ(tokens_.size(), 4); | |
| // Need to know original spacing to be able to infer user-intent. | |
| ConnectPreFormatTokensPreservedSpaceStarts(sample_.data(), | |
| &pre_format_tokens_); | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| // Default to append, so we can see the effect of falling-back to | |
| // preserve-spacing behavior. | |
| ftoken.before.break_decision = SpacingOptions::MustAppend; | |
| } | |
| // From the sample_ text, each pair of tokens will span a subpartition. | |
| // Construct a 2-level partition that looks like this: | |
| // | |
| // | one | two | | |
| // | three | four | | |
| // | |
| syntax_tree_ = TNode(1, | |
| TNode(2, // | |
| Leaf(1, tokens_[0]), // | |
| Leaf(1, tokens_[1])), | |
| TNode(2, // | |
| Leaf(1, tokens_[2]), // | |
| Leaf(1, tokens_[3]))); | |
| // Establish format token ranges per partition. | |
| const auto begin = pre_format_tokens_.begin(); | |
| UnwrappedLine all(0, begin); | |
| all.SpanUpToToken(pre_format_tokens_.end()); | |
| all.SetOrigin(&*syntax_tree_); | |
| UnwrappedLine child1(0, begin); | |
| child1.SpanUpToToken(begin + 2); | |
| child1.SetOrigin(DescendPath(*syntax_tree_, {0})); | |
| UnwrappedLine child2(0, begin + 2); | |
| child2.SpanUpToToken(begin + 4); | |
| child2.SetOrigin(DescendPath(*syntax_tree_, {1})); | |
| // Construct 2-level token partition. | |
| using tree_type = TokenPartitionTree; | |
| partition_ = tree_type{ | |
| all, | |
| tree_type{child1}, | |
| tree_type{child2}, | |
| }; | |
| } | |
| }; | |
| class InferSmallAlignDifferenceTest : public Dense2x2MatrixAlignmentTest { | |
| public: | |
| InferSmallAlignDifferenceTest() | |
| : Dense2x2MatrixAlignmentTest("one two three four") {} | |
| }; | |
| TEST_F(InferSmallAlignDifferenceTest, DifferenceSufficientlySmall) { | |
| // aligned matrix: | |
| // | one | two | | |
| // | three | four | | |
| // | |
| // flushed-left looks only different by a maximum of 2 spaces ("one" vs. | |
| // "three", so just align it. | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kInferAlignmentHandler, | |
| &partition_); | |
| EXPECT_EQ(Render(), // | |
| "one two\n" // | |
| "three four\n"); | |
| } | |
| class InferFlushLeftTest : public Dense2x2MatrixAlignmentTest { | |
| public: | |
| InferFlushLeftTest() | |
| : Dense2x2MatrixAlignmentTest("one two threeeee four") {} | |
| }; | |
| TEST_F(InferFlushLeftTest, DifferenceSufficientlySmall) { | |
| // aligned matrix: | |
| // | one | two | | |
| // | threeeee | four | | |
| // | |
| // Aligned vs. flushed contains a spacing difference of 4, | |
| // So this avoids triggering alignment due to small differences. | |
| // The original text contains no error greater than 2 spaces relative to | |
| // flush-left, therefore flush-left. | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kInferAlignmentHandler, | |
| &partition_); | |
| EXPECT_EQ(Render(), // | |
| "one two\n" // | |
| "threeeee four\n"); | |
| } | |
| class InferForceAlignTest : public Dense2x2MatrixAlignmentTest { | |
| public: | |
| InferForceAlignTest() | |
| : Dense2x2MatrixAlignmentTest("one two threeeee four") {} | |
| }; | |
| TEST_F(InferForceAlignTest, DifferenceSufficientlySmall) { | |
| // aligned matrix: | |
| // | one | two | | |
| // | threeeee | four | | |
| // | |
| // The original text contains 4 excess spaces before "four" which should | |
| // trigger alignment. | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kInferAlignmentHandler, | |
| &partition_); | |
| EXPECT_EQ(Render(), // | |
| "one two\n" // | |
| "threeeee four\n"); | |
| } | |
| class InferAmbiguousAlignIntentTest : public Dense2x2MatrixAlignmentTest { | |
| public: | |
| InferAmbiguousAlignIntentTest() | |
| : Dense2x2MatrixAlignmentTest("one two threeeee four") {} | |
| }; | |
| TEST_F(InferAmbiguousAlignIntentTest, DifferenceSufficientlySmall) { | |
| // aligned matrix: | |
| // | one | two | | |
| // | threeeee | four | | |
| // | |
| // The original text contains only 3 excess spaces before "four" which should | |
| // not trigger alignment, but fall back to preserving original spacing. | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kInferAlignmentHandler, | |
| &partition_); | |
| EXPECT_EQ(Render(), // | |
| "one two\n" // | |
| "threeeee four\n"); | |
| } | |
| // Creates columns tree with the same layout as the syntax tree. | |
| // Columns created for tokens ',' have `contains_delimiter` set. | |
| template <const AlignmentColumnProperties& props> | |
| class SyntaxTreeColumnizer : public ColumnSchemaScanner { | |
| public: | |
| SyntaxTreeColumnizer() = default; | |
| void Visit(const SyntaxTreeNode& node) final { | |
| ColumnPositionTree* column; | |
| if (!current_column_) | |
| column = ReserveNewColumn(node, props); | |
| else | |
| column = ReserveNewColumn(current_column_, node, props); | |
| ValueSaver<ColumnPositionTree*> current_column_saver(¤t_column_, | |
| column); | |
| ColumnSchemaScanner::Visit(node); | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| AlignmentColumnProperties local_props = props; | |
| if (leaf.get().text() == ",") local_props.contains_delimiter = true; | |
| if (!current_column_) | |
| ReserveNewColumn(leaf, local_props); | |
| else | |
| ReserveNewColumn(current_column_, leaf, local_props); | |
| } | |
| private: | |
| ColumnPositionTree* current_column_ = nullptr; | |
| }; | |
| class SubcolumnsTreeAlignmentTest : public MatrixTreeAlignmentTestFixture { | |
| public: | |
| SubcolumnsTreeAlignmentTest(absl::string_view text = | |
| "zero\n" | |
| "( one two three )\n" | |
| "( four ( five six ) seven )\n" | |
| "( eight ( ( nine ) ten ) )\n" | |
| "( eleven nineteen-ninety-nine 2k )\n") | |
| : MatrixTreeAlignmentTestFixture(text) { | |
| // Columns tree: | |
| // | |
| // ┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃ | |
| // ┃┄┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃┄┃ | |
| // ┊ ┃┄┄┄┄┄┄┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃┄┄┄┄┄┃ ┊ | |
| // ┊ ┊ ┃┄┃┄┄┄┄┄┄┄┄┄┄┄┄┃┄┃ ┊ ┊ ┊ | |
| // ┊ ┊ ┊ ┃┄┄┄┄┄┄┄┄┃┄┄┄┃ ┊ ┊ ┊ ┊ | |
| // ┊ ┊ ┊ ┃┄┃┄┄┄┄┃┄┃ ┊ ┊ ┊ ┊ ┊ | |
| // ┊ ┊ ┊ ┊ ┃┄┄┄┄┃ ┊ ┊ ┊ ┊ ┊ ┊ | |
| // ┃zero ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┊ ┃ | |
| // ┃(┃one ┃two┊ ┊ ┊ ┊ ┊ ┃three┃)┃ | |
| // ┃(┃four ┃(┃five ┊ ┃six┃)┃ ┃seven┃)┃ | |
| // ┃(┃eight ┃(┃(┃nine┃)┃ten┃)┃ ┃ ┃)┃ | |
| // ┃(┃eleven┃nineteen-ninety-nine┃2k ┃)┃ | |
| // ╵ ╵ ╵ ╵ ╵ ╵ ╵ ╵ ╵ ╵ ╵ ╵ | |
| syntax_tree_ = TNode(0); | |
| UnwrappedLine all(0, pre_format_tokens_.begin()); | |
| all.SpanUpToToken(pre_format_tokens_.end()); | |
| all.SetOrigin(syntax_tree_.get()); | |
| partition_ = TokenPartitionTree{all}; | |
| auto token_iter = pre_format_tokens_.begin(); | |
| while (true) { | |
| UnwrappedLine uwline(0, token_iter); | |
| SymbolPtr item = ParseItem(&token_iter, pre_format_tokens_.end()); | |
| if (!item) { | |
| break; | |
| } | |
| uwline.SpanUpToToken(token_iter); | |
| uwline.SetOrigin(item.get()); | |
| partition_.Children().emplace_back(std::move(uwline)); | |
| SymbolCastToNode(*syntax_tree_).AppendChild(std::move(item)); | |
| } | |
| } | |
| private: | |
| SymbolPtr ParseList( | |
| std::vector<verible::PreFormatToken>::iterator* it, | |
| const std::vector<verible::PreFormatToken>::iterator& end) { | |
| SymbolPtr list = TNode(0); | |
| SymbolPtr item; | |
| while ((item = ParseItem(it, end)) != nullptr) { | |
| SymbolCastToNode(*list).AppendChild(std::move(item)); | |
| } | |
| return list; | |
| } | |
| SymbolPtr ParseItem( | |
| std::vector<verible::PreFormatToken>::iterator* it, | |
| const std::vector<verible::PreFormatToken>::iterator& end) { | |
| CHECK_NOTNULL(it); | |
| if (*it == end) return SymbolPtr(nullptr); | |
| if ((*it)->Text() == "(") { | |
| SymbolPtr lp = Leaf(1, (*it)->Text()); | |
| ++*it; | |
| CHECK(*it != end); | |
| SymbolPtr list = ParseList(it, end); | |
| CHECK(*it != end); | |
| CHECK_EQ((*it)->Text(), ")"); | |
| SymbolPtr rp = Leaf(1, (*it)->Text()); | |
| ++*it; | |
| return TNode(1, std::move(lp), std::move(list), std::move(rp)); | |
| } else if ((*it)->Text() == ")") { | |
| return SymbolPtr(nullptr); | |
| } else { | |
| SymbolPtr leaf = Leaf(0, (*it)->Text()); | |
| ++*it; | |
| return leaf; | |
| } | |
| } | |
| }; | |
| static const ExtractAlignmentGroupsFunction kLeftAligningTreeAlignmentHandler = | |
| ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, &IgnoreNone, | |
| AlignmentCellScannerGenerator<SyntaxTreeColumnizer<FlushLeft>>(), | |
| AlignmentPolicy::kAlign); | |
| static const ExtractAlignmentGroupsFunction kRightAligningTreeAlignmentHandler = | |
| ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, &IgnoreNone, | |
| AlignmentCellScannerGenerator<SyntaxTreeColumnizer<FlushRight>>(), | |
| AlignmentPolicy::kAlign); | |
| static const ExtractAlignmentGroupsFunction kFlushLeftTreeAlignmentHandler = | |
| ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, &IgnoreNone, | |
| AlignmentCellScannerGenerator<SyntaxTreeColumnizer<FlushLeft>>(), | |
| AlignmentPolicy::kFlushLeft); | |
| static const ExtractAlignmentGroupsFunction kPreserveTreeAlignmentHandler = | |
| ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, &IgnoreNone, | |
| AlignmentCellScannerGenerator<SyntaxTreeColumnizer<FlushLeft>>(), | |
| AlignmentPolicy::kPreserve); | |
| TEST_F(SubcolumnsTreeAlignmentTest, ZeroInterTokenPadding) { | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), | |
| kLeftAligningTreeAlignmentHandler, &partition_); | |
| EXPECT_EQ(Render(), // | |
| "zero\n" | |
| "(one two three)\n" | |
| "(four (five six) seven)\n" | |
| "(eight ((nine)ten) )\n" | |
| "(elevennineteen-ninety-nine2k )\n"); | |
| } | |
| TEST_F(SubcolumnsTreeAlignmentTest, AlignmentPolicyFlushLeft) { | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), | |
| kFlushLeftTreeAlignmentHandler, &partition_); | |
| EXPECT_EQ(Render(), // | |
| "zero\n" | |
| "(onetwothree)\n" | |
| "(four(fivesix)seven)\n" | |
| "(eight((nine)ten))\n" | |
| "(elevennineteen-ninety-nine2k)\n"); | |
| } | |
| TEST_F(SubcolumnsTreeAlignmentTest, AlignmentPolicyPreserve) { | |
| ConnectPreFormatTokensPreservedSpaceStarts(sample_.data(), | |
| &pre_format_tokens_); | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), | |
| kPreserveTreeAlignmentHandler, &partition_); | |
| EXPECT_EQ(Render(), // | |
| "zero\n" | |
| "( one two three )\n" | |
| "( four ( five six ) seven )\n" | |
| "( eight ( ( nine ) ten ) )\n" | |
| "( eleven nineteen-ninety-nine 2k )\n"); | |
| } | |
| TEST_F(SubcolumnsTreeAlignmentTest, OneInterTokenPadding) { | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), | |
| kLeftAligningTreeAlignmentHandler, &partition_); | |
| EXPECT_EQ(Render(), // | |
| "zero\n" | |
| "( one two three )\n" | |
| "( four ( five six ) seven )\n" | |
| "( eight ( ( nine ) ten ) )\n" | |
| "( eleven nineteen-ninety-nine 2k )\n"); | |
| } | |
| TEST_F(SubcolumnsTreeAlignmentTest, OneInterTokenPaddingExceptFront) { | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| // Find first token of each line and require 0 spaces before them. | |
| for (auto& line : partition_.Children()) { | |
| const auto tokens = line.Value().TokensRange(); | |
| if (!tokens.empty()) { | |
| const PreFormatToken& front = tokens.front(); | |
| auto mutable_token = | |
| std::find_if(pre_format_tokens_.begin(), pre_format_tokens_.end(), | |
| [&](const PreFormatToken& ftoken) { | |
| return BoundsEqual(ftoken.Text(), front.Text()); | |
| }); | |
| if (mutable_token != pre_format_tokens_.end()) { | |
| mutable_token->before.spaces_required = 0; | |
| } | |
| } | |
| } | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), | |
| kLeftAligningTreeAlignmentHandler, &partition_); | |
| EXPECT_EQ(Render(), // | |
| "zero\n" | |
| "( one two three )\n" | |
| "( four ( five six ) seven )\n" | |
| "( eight ( ( nine ) ten ) )\n" | |
| "( eleven nineteen-ninety-nine 2k )\n"); | |
| } | |
| TEST_F(SubcolumnsTreeAlignmentTest, RightFlushed) { | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), | |
| kRightAligningTreeAlignmentHandler, &partition_); | |
| EXPECT_EQ(Render(), // | |
| " zero\n" | |
| "( one two three )\n" | |
| "( four ( five six ) seven )\n" | |
| "( eight ( ( nine ) ten ) )\n" | |
| "( eleven nineteen-ninety-nine 2k )\n"); | |
| } | |
| TEST_F(SubcolumnsTreeAlignmentTest, | |
| RightFlushedOneInterTokenPaddingWithIndent) { | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| for (auto& line : partition_.Children()) { | |
| line.Value().SetIndentationSpaces(2); | |
| } | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), | |
| kRightAligningTreeAlignmentHandler, &partition_); | |
| EXPECT_EQ(Render(), // | |
| " zero\n" | |
| " ( one two three )\n" | |
| " ( four ( five six ) seven )\n" | |
| " ( eight ( ( nine ) ten ) )\n" | |
| " ( eleven nineteen-ninety-nine 2k )\n"); | |
| } | |
| class MultiSubcolumnsTreeAlignmentTest : public SubcolumnsTreeAlignmentTest { | |
| public: | |
| MultiSubcolumnsTreeAlignmentTest(absl::string_view text = | |
| "zero\n" | |
| "( one two three )\n" | |
| "( four ( five six ) seven )\n" | |
| "\n" | |
| "( eight ( ( nine ) ten ) )\n" | |
| "( eleven nineteen-ninety-nine 2k )\n") | |
| : SubcolumnsTreeAlignmentTest(text) {} | |
| std::string Render() { | |
| std::ostringstream stream; | |
| int position = 0; | |
| const absl::string_view text(sample_); | |
| for (auto& child : partition_.Children()) { | |
| const auto policy = child.Value().PartitionPolicy(); | |
| if (policy == PartitionPolicyEnum::kAlreadyFormatted) { | |
| ApplyAlreadyFormattedPartitionPropertiesToTokens(&child, | |
| &pre_format_tokens_); | |
| } | |
| // emulate preserving vertical spacing | |
| const auto tokens_range = child.Value().TokensRange(); | |
| const auto front_offset = tokens_range.front().token->left(text); | |
| const absl::string_view spaces = | |
| text.substr(position, front_offset - position); | |
| const auto newlines = | |
| std::max<int>(std::count(spaces.begin(), spaces.end(), '\n') - 1, 0); | |
| stream << Spacer(newlines, '\n'); | |
| stream << FormattedExcerpt(child.Value()) << std::endl; | |
| position = tokens_range.back().token->right(text); | |
| } | |
| return stream.str(); | |
| } | |
| }; | |
| TEST_F(MultiSubcolumnsTreeAlignmentTest, BlankLineSeparatedGroups) { | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| } | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), | |
| kLeftAligningTreeAlignmentHandler, &partition_); | |
| EXPECT_EQ(Render(), // | |
| "zero\n" | |
| "( one two three )\n" | |
| "( four ( five six ) seven )\n" | |
| "\n" | |
| "( eight ( ( nine ) ten ) )\n" | |
| "( eleven nineteen-ninety-nine 2k )\n"); | |
| } | |
| class InferSubcolumnsTreeAlignmentTest : public SubcolumnsTreeAlignmentTest { | |
| public: | |
| InferSubcolumnsTreeAlignmentTest( | |
| absl::string_view text = | |
| "zero\n" | |
| "( one two three )\n" | |
| "( four ( five six ) seven )\n" | |
| "( eight ( ( nine ) ten ) )\n" | |
| "( eleven nineteen-ninety-nine 2k )\n") | |
| : SubcolumnsTreeAlignmentTest(text) { | |
| // Need to know original spacing to be able to infer user-intent. | |
| ConnectPreFormatTokensPreservedSpaceStarts(sample_.data(), | |
| &pre_format_tokens_); | |
| // Require 1 space between tokens. | |
| for (auto& ftoken : pre_format_tokens_) { | |
| ftoken.before.spaces_required = 1; | |
| // Default to append, so we can see the effect of falling-back to | |
| // preserve-spacing behavior. | |
| ftoken.before.break_decision = SpacingOptions::MustAppend; | |
| } | |
| } | |
| }; | |
| static const ExtractAlignmentGroupsFunction kInferTreeAlignmentHandler = | |
| ExtractAlignmentGroupsAdapter( | |
| &PartitionBetweenBlankLines, &IgnoreNone, | |
| AlignmentCellScannerGenerator<SyntaxTreeColumnizer<FlushLeft>>(), | |
| AlignmentPolicy::kInferUserIntent); | |
| TEST_F(InferSubcolumnsTreeAlignmentTest, InferUserIntent) { | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), kInferTreeAlignmentHandler, | |
| &partition_); | |
| EXPECT_EQ(Render(), // | |
| "zero\n" | |
| "( one two three )\n" | |
| "( four ( five six ) seven )\n" | |
| "( eight ( ( nine ) ten ) )\n" | |
| "( eleven nineteen-ninety-nine 2k )\n"); | |
| } | |
| class SubcolumnsTreeWithDelimitersTest : public SubcolumnsTreeAlignmentTest { | |
| public: | |
| SubcolumnsTreeWithDelimitersTest(absl::string_view text = | |
| "( One Two , )\n" | |
| "( Three Four )\n" | |
| "\n" | |
| "( Seven Eight , )\n" | |
| "( Five Six )\n") | |
| : SubcolumnsTreeAlignmentTest(text) {} | |
| }; | |
| TEST_F(SubcolumnsTreeWithDelimitersTest, ContainsDelimiterTest) { | |
| TabularAlignTokens(40, sample_, ByteOffsetSet(), | |
| kLeftAligningTreeAlignmentHandler, &partition_); | |
| EXPECT_EQ(Render(), // | |
| "(One Two,)\n" | |
| "(ThreeFour)\n" | |
| "(SevenEight,)\n" | |
| "(Five Six )\n"); | |
| } | |
| template <class Tree> | |
| struct ColumnsTreeFormatterTestCase { | |
| Tree input; | |
| absl::string_view expected; | |
| }; | |
| TEST(ColumnsTreeFormatter, ColumnPositionTreePrinter) { | |
| using T = ColumnPositionTree; | |
| using V = ColumnPositionEntry; | |
| static const TokenInfo kFooToken(1, "foo"); | |
| static const ColumnsTreeFormatterTestCase<T> kTestCases[] = { | |
| { | |
| T(V{{}, kFooToken, FlushLeft}), | |
| "", | |
| }, | |
| { | |
| T(V{{0, 1, 2}, kFooToken, FlushRight}), | |
| "", | |
| }, | |
| { | |
| T(V{{}, kFooToken, FlushLeft}, // | |
| T(V{{0}, kFooToken, FlushLeft}), // | |
| T(V{{1}, kFooToken, FlushRight}), // | |
| T(V{{42}, kFooToken, FlushLeft})), | |
| "| < 0 < | > 1 > | < 42 < |\n", | |
| }, | |
| { | |
| T(V{{}, kFooToken, FlushLeft}, // | |
| T(V{{0}, kFooToken, FlushLeft}), // | |
| T(V{{1}, kFooToken, FlushRight}, // | |
| T(V{{1, 2}, kFooToken, FlushLeft}), // | |
| T(V{{1, 1}, kFooToken, FlushLeft}), // | |
| T(V{{1, 3, 3}, kFooToken, FlushLeft}, // | |
| T(V{{1, 3, 3, 1}, kFooToken, FlushLeft})), // | |
| T(V{{2, 4, 2}, kFooToken, FlushRight}))), | |
| "| < 0 < | >>>>>>>>>>>>>>>>> 1 >>>>>>>>>>>>>>>>>> |\n" | |
| " | < .2 < | < .1 < | < .3.3 < | > 2.4.2 > |\n" | |
| " | << .1 << |\n", | |
| }, | |
| { | |
| T(V{{}, kFooToken, FlushLeft}, // | |
| T(V{{0}, kFooToken, FlushLeft}), // | |
| T(V{{1}, kFooToken, FlushLeft}), // | |
| T(V{{42}, kFooToken, FlushLeft}, // | |
| T(V{{3, 4, 5}, kFooToken, FlushLeft}, // not a subpath | |
| T(V{{8}, kFooToken, FlushRight}))), // not a subpath | |
| T(V{{2}, kFooToken, FlushLeft})), | |
| "| < 0 < | < 1 < | << 42 <<< | < 2 < |\n" | |
| " | < 3.4.5 < |\n" | |
| " | >>> 8 >>> |\n", | |
| }, | |
| { | |
| T(V{{}, kFooToken, FlushLeft}, // | |
| T(V{{0}, kFooToken, FlushLeft}, // | |
| T(V{{0, 0}, kFooToken, FlushLeft})), // | |
| T(V{{1}, kFooToken, FlushRight}), // | |
| T(V{{2}, kFooToken, FlushLeft}, // | |
| T(V{{2, 0}, kFooToken, FlushLeft}))), | |
| "| < 0 << | > 1 > | < 2 << |\n" | |
| "| < .0 < | | < .0 < |\n", | |
| }, | |
| }; | |
| for (const auto& test_case : kTestCases) { | |
| std::ostringstream stream; | |
| stream << test_case.input; | |
| EXPECT_EQ(stream.str(), test_case.expected); | |
| } | |
| } | |
| // Delimiter that matches text outside of substrings between 'start' and 'stop' | |
| // (inclusive). | |
| class OutsideCharPairs { | |
| public: | |
| explicit OutsideCharPairs(char start, char stop) | |
| : start_(start), stop_(stop) {} | |
| absl::string_view Find(absl::string_view text, size_t pos) const { | |
| if (text[pos] == start_) { | |
| const size_t stop_pos = text.find(stop_, pos + 1); | |
| if (stop_pos == absl::string_view::npos) | |
| return absl::string_view(text.data() + text.size(), 0); | |
| const size_t start_pos = text.find(start_, stop_pos + 1); | |
| if (start_pos == absl::string_view::npos) | |
| return text.substr(stop_pos + 1); | |
| return text.substr(stop_pos + 1, start_pos - stop_pos - 1); | |
| } | |
| const size_t start_pos = text.find(start_, pos); | |
| if (start_pos == absl::string_view::npos) return text.substr(pos); | |
| return text.substr(pos, start_pos - pos); | |
| } | |
| private: | |
| const char start_; | |
| const char stop_; | |
| }; | |
| class FormatUsingOriginalSpacingTest : public ::testing::Test, | |
| public UnwrappedLineMemoryHandler { | |
| public: | |
| explicit FormatUsingOriginalSpacingTest( | |
| absl::string_view text = | |
| "<NoSpacing><nospacing>" | |
| " <1Space> <1space>" | |
| " <4Spaces> <4spaces>" | |
| "\n<1NL>\n<1nl>" | |
| "\n <1NL+7Spaces>\n <1nl+7spaces>" | |
| "\n\n <2NL+2Spaces>\n\n <2nl+2spaces>" | |
| "\n \n\n <1NL+1Space+2NL+2Spaces>\n \n\n <1nl+1space+2nl+2spaces>") | |
| : sample_(text), | |
| tokens_(absl::StrSplit(sample_, OutsideCharPairs('<', '>'), | |
| absl::SkipEmpty())) { | |
| for (const auto token : tokens_) { | |
| ftokens_.emplace_back(TokenInfo{1, token}); | |
| } | |
| // sample_ is the memory-owning string buffer | |
| CreateTokenInfosExternalStringBuffer(ftokens_); | |
| ConnectPreFormatTokensPreservedSpaceStarts(sample_.data(), | |
| &pre_format_tokens_); | |
| } | |
| protected: | |
| void RunTestCase(TokenPartitionTree actual, | |
| const TokenPartitionTree expected) { | |
| TokenPartitionTree::subnodes_type nodes; | |
| nodes.push_back(std::move(actual)); | |
| FormatUsingOriginalSpacing(TokenPartitionRange(nodes.begin(), nodes.end())); | |
| EXPECT_PRED_FORMAT2(TokenPartitionTreesEqualPredFormat, nodes[0], expected); | |
| } | |
| const std::string sample_; | |
| const std::vector<absl::string_view> tokens_; | |
| std::vector<TokenInfo> ftokens_; | |
| }; | |
| TEST_F(FormatUsingOriginalSpacingTest, NoSpacing) { | |
| using TPT = TokenPartitionTreeBuilder; | |
| RunTestCase(TPT(3, {0, 2}, PartitionPolicyEnum::kTabularAlignment) | |
| .build(pre_format_tokens_), | |
| TPT(3, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(0, {0, 1}, PartitionPolicyEnum::kInline), | |
| TPT(0, {1, 2}, PartitionPolicyEnum::kInline), | |
| }) | |
| .build(pre_format_tokens_)); | |
| } | |
| TEST_F(FormatUsingOriginalSpacingTest, OneSpace) { | |
| using TPT = TokenPartitionTreeBuilder; | |
| RunTestCase(TPT(3, {2, 4}, PartitionPolicyEnum::kTabularAlignment) | |
| .build(pre_format_tokens_), | |
| TPT(3, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(0, {2, 3}, PartitionPolicyEnum::kInline), | |
| TPT(1, {3, 4}, PartitionPolicyEnum::kInline), | |
| }) | |
| .build(pre_format_tokens_)); | |
| } | |
| TEST_F(FormatUsingOriginalSpacingTest, FourSpaces) { | |
| using TPT = TokenPartitionTreeBuilder; | |
| RunTestCase(TPT(3, {4, 6}, PartitionPolicyEnum::kTabularAlignment) | |
| .build(pre_format_tokens_), | |
| TPT(3, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(0, {4, 5}, PartitionPolicyEnum::kInline), | |
| TPT(4, {5, 6}, PartitionPolicyEnum::kInline), | |
| }) | |
| .build(pre_format_tokens_)); | |
| } | |
| TEST_F(FormatUsingOriginalSpacingTest, OneNL) { | |
| using TPT = TokenPartitionTreeBuilder; | |
| RunTestCase(TPT(3, {6, 8}, PartitionPolicyEnum::kTabularAlignment) | |
| .build(pre_format_tokens_), | |
| TPT(3, PartitionPolicyEnum::kAlwaysExpand, | |
| { | |
| TPT(3, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(0, {6, 7}, PartitionPolicyEnum::kInline), | |
| }), | |
| TPT(0, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(0, {7, 8}, PartitionPolicyEnum::kInline), | |
| }), | |
| }) | |
| .build(pre_format_tokens_)); | |
| } | |
| TEST_F(FormatUsingOriginalSpacingTest, OneNLSevenSpaces) { | |
| using TPT = TokenPartitionTreeBuilder; | |
| RunTestCase(TPT(3, {8, 10}, PartitionPolicyEnum::kTabularAlignment) | |
| .build(pre_format_tokens_), | |
| TPT(3, PartitionPolicyEnum::kAlwaysExpand, | |
| { | |
| TPT(3, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(0, {8, 9}, PartitionPolicyEnum::kInline), | |
| }), | |
| TPT(0, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(7, {9, 10}, PartitionPolicyEnum::kInline), | |
| }), | |
| }) | |
| .build(pre_format_tokens_)); | |
| } | |
| TEST_F(FormatUsingOriginalSpacingTest, TwoNLTwoSpaces) { | |
| using TPT = TokenPartitionTreeBuilder; | |
| RunTestCase(TPT(3, {10, 12}, PartitionPolicyEnum::kTabularAlignment) | |
| .build(pre_format_tokens_), | |
| TPT(3, PartitionPolicyEnum::kAlwaysExpand, | |
| { | |
| TPT(3, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(0, {10, 11}, PartitionPolicyEnum::kInline), | |
| }), | |
| TPT(0, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(2, {11, 12}, PartitionPolicyEnum::kInline), | |
| }), | |
| }) | |
| .build(pre_format_tokens_)); | |
| } | |
| TEST_F(FormatUsingOriginalSpacingTest, OneNLOneSpaceTwoNLTwoSpaces) { | |
| using TPT = TokenPartitionTreeBuilder; | |
| RunTestCase(TPT(3, {12, 14}, PartitionPolicyEnum::kTabularAlignment) | |
| .build(pre_format_tokens_), | |
| TPT(3, PartitionPolicyEnum::kAlwaysExpand, | |
| { | |
| TPT(3, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(0, {12, 13}, PartitionPolicyEnum::kInline), | |
| }), | |
| TPT(0, PartitionPolicyEnum::kAlreadyFormatted, | |
| { | |
| TPT(2, {13, 14}, PartitionPolicyEnum::kInline), | |
| }), | |
| }) | |
| .build(pre_format_tokens_)); | |
| } | |
| } // namespace | |
| } // namespace verible |