blob: f986ac706583e2a70856566287dbaa7918f14621 [file] [log] [blame]
// 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(&current_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