blob: 5a967872c30568a8044c2a506ac22f4489f0204c [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 "verilog/formatting/comment_controls.h"
#include <initializer_list>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/strings/str_join.h"
#include "common/strings/line_column_map.h"
#include "common/text/token_info_test_util.h"
#include "verilog/analysis/verilog_analyzer.h"
namespace verilog {
namespace formatter {
namespace {
using ::testing::ElementsAre;
using verible::ByteOffsetSet;
using verible::ExpectedTokenInfo;
using verible::LineColumnMap;
using verible::LineNumberSet;
using verible::TokenInfoTestData;
TEST(DisableFormattingRangesTest, EmptyFile) {
VerilogAnalyzer analyzer("", "<file>");
EXPECT_TRUE(analyzer.Tokenize().ok());
const auto disable_ranges = DisableFormattingRanges(
analyzer.Data().Contents(), analyzer.Data().TokenStream());
EXPECT_TRUE(disable_ranges.empty());
EXPECT_THAT(disable_ranges, ElementsAre());
}
TEST(DisableFormattingRangesTest, NonEmptyNoDisabling) {
VerilogAnalyzer analyzer("xxx yyy;", "<file>");
EXPECT_TRUE(analyzer.Tokenize().ok());
const auto disable_ranges = DisableFormattingRanges(
analyzer.Data().Contents(), analyzer.Data().TokenStream());
EXPECT_TRUE(disable_ranges.empty());
EXPECT_THAT(disable_ranges, ElementsAre());
}
enum {
kOff = 99 // any non-zero value, to tag the disabled ranges
};
struct DisableRangeTestData : public TokenInfoTestData {
ByteOffsetSet expected;
DisableRangeTestData(std::initializer_list<ExpectedTokenInfo> fragments)
: TokenInfoTestData{fragments} {
// convert expected_tokens into expected ranges
const auto tokens = FindImportantTokens();
const absl::string_view base(code);
for (const auto& t : tokens) {
expected.Add({t.left(base), t.right(base)});
}
}
};
TEST(DisableFormattingRangesTest, FormatOnNoEffect) {
// By default, nothing is disabled, formatter is on for entire file, so these
// should have no effect.
const char* kTestCases[] = {
"xxx yyy;\n // verilog_format: on\n",
"xxx yyy;\n /* verilog_format: on */\n",
"xxx yyy;\n// verilog_format: on\n//verilog_format:on\n",
"xxx yyy;\n // verilog_format: other\n",
"xxx yyy;\n // verilog_format:\n", // no command
};
for (const auto* code : kTestCases) {
VerilogAnalyzer analyzer(code, "<file>");
EXPECT_TRUE(analyzer.Tokenize().ok());
const auto disable_ranges = DisableFormattingRanges(
analyzer.Data().Contents(), analyzer.Data().TokenStream());
EXPECT_TRUE(disable_ranges.empty());
}
}
TEST(DisableFormattingRangesTest, FormatOffDisableToEndEOLComment) {
const DisableRangeTestData kTestCases[] = {
{"xxx yyy;\n // verilog_format: off\n"}, // range to EOF is empty
{"xxx yyy;\n // verilog_format: off\n", {kOff, "\n"}},
{"xxx yyy;\n // verilog_format: off \n", {kOff, "\n"}},
{"xxx yyy;\n // verilog_format: off\n", {kOff, "\n "}},
{"xxx yyy;\n //verilog_format: off\n", {kOff, "\n"}},
{"xxx yyy;\n //verilog_format:off\n", {kOff, "\n"}},
{"xxx yyy;\n // verilog_format:off\n", {kOff, "\n"}},
{"xxx yyy;\n // verilog_format: off // reason why\n", {kOff, "\n"}},
{"xxx yyy;\n // verilog_format: off\n",
{kOff, "\t// verilog_format: off again\n"}},
};
for (const auto& test : kTestCases) {
VerilogAnalyzer analyzer(test.code, "<file>");
EXPECT_TRUE(analyzer.Tokenize().ok());
const auto disable_ranges = DisableFormattingRanges(
analyzer.Data().Contents(), analyzer.Data().TokenStream());
EXPECT_EQ(disable_ranges, test.expected);
}
}
TEST(DisableFormattingRangesTest, FormatOffDisableToEndBlockComment) {
const DisableRangeTestData kTestCases[] = {
{"xxx yyy;\n /* verilog_format: off */", {kOff, "\n"}},
{"xxx yyy;\n /* verilog_format: off */", {kOff, " \n"}},
{"xxx yyy;\n /*verilog_format: off */", {kOff, "\n "}},
{"xxx yyy;\n /* verilog_format:off */", {kOff, "\n "}},
{"xxx yyy;\n /*verilog_format:off */", {kOff, "\n "}},
{"xxx yyy;\n /***** verilog_format: off ****/",
{kOff, "\n "}},
{"xxx yyy;\n /* verilog_format: off : reason why... */",
{kOff, "\n\t\t"}},
{"xxx yyy;\n /* verilog_format: off // reason why... */",
{kOff, "\n \t"}},
{" /* verilog_format: off */", {kOff, "/* verilog_format:on */"}, "\n"},
{" /* verilog_format: off */", {kOff, " /* verilog_format:on */"}, "\n"},
{" /* verilog_format: off */",
{kOff, " \t /* verilog_format:on */"},
"\n"},
{" /* verilog_format: off */",
{kOff, "\n/* verilog_format:on */"},
"\n"},
{" /* verilog_format: off */",
{kOff, "\n\n/* verilog_format:on */"},
"\n"},
};
for (const auto& test : kTestCases) {
VerilogAnalyzer analyzer(test.code, "<file>");
EXPECT_TRUE(analyzer.Tokenize().ok());
const auto disable_ranges = DisableFormattingRanges(
analyzer.Data().Contents(), analyzer.Data().TokenStream());
EXPECT_EQ(disable_ranges, test.expected);
}
}
TEST(DisableFormattingRangesTest, FormatOffVarious) {
const DisableRangeTestData kTestCases[] = {
{// one disabled interval, very brief (off and on again)
"xxx yyy;\n"
"// verilog_format: off\n",
{kOff, "// verilog_format: on"},
"\n"
"ppp qqq;\n"},
{// one disabled interval affecting one line (extra blank line)
"xxx yyy;\n"
"// verilog_format: off\n",
{kOff, "\n// verilog_format: on"},
"\n"
"ppp qqq;\n"},
{// one disabled interval affecting multiple lines
"xxx yyy;\n"
"// verilog_format: off\n",
{kOff, "\n\n\n// verilog_format: on"},
"\n"
"ppp qqq;\n"},
{// disable to end-of-file, second command is neither on/off
"xxx yyy;\n"
"// verilog_format: off\n",
{kOff,
"// verilog_format: other\n"
"ppp qqq;\n"}},
{// one disabled interval in the middle
"xxx yyy;\n"
"// verilog_format: off\n",
{kOff, "zzz www;\n// verilog_format: on"},
"\n"
"ppp qqq;\n"},
{// one disabled interval in the middle
"xxx yyy;\n"
"/* verilog_format: off */",
{kOff, "\nzzz www;\n/* verilog_format: on */"},
"\n"
"ppp qqq;\n"},
{// null interval
"xxx yyy;\n"
"/* verilog_format: off */",
{kOff, "/* verilog_format: on */"},
"\n"
"ppp qqq;\n"},
{// two disabled intervals
"xxx yyy;\n"
"// verilog_format: off\n",
{kOff, "zzz www;\n// verilog_format: on"},
"\n"
"ppp qqq;\n"
"// verilog_format:off\n",
{kOff, "aa bb;\n// verilog_format:on"},
"\n"
"cc dd;\n"},
};
for (const auto& test : kTestCases) {
VerilogAnalyzer analyzer(test.code, "<file>");
EXPECT_TRUE(analyzer.Tokenize().ok());
const auto disable_ranges = DisableFormattingRanges(
analyzer.Data().Contents(), analyzer.Data().TokenStream());
EXPECT_EQ(disable_ranges, test.expected) << "code:\n" << test.code;
}
}
struct DisabledBytesTestCase {
absl::string_view text;
LineNumberSet enabled_lines;
ByteOffsetSet expected_bytes;
};
TEST(EnabledLinesToDisabledByteRangesTest, AllCases) {
const DisabledBytesTestCase kTestCases[] = {
{"", {}, {}}, // empty text
{"aaaa\n"
"bbbbbb\n"
"cccc\n",
{}, // no disabled lines
{}},
{
"aaaa\n"
"bbbbbb\n"
"cccc\n",
{{1, 2}}, // enabled first line only
{{5, 17}} // disable all other lines
},
{
"aaaa\n"
"bbbbbb\n"
"cccc\n",
{{2, 3}}, // enabled second line only
{{0, 5}, {12, 17}} // disable all other lines
},
{
"aaaa\n"
"bbbbbb\n"
"cccc\n",
{{3, 4}}, // enabled third line only
{{0, 12}} // disable all other lines
},
{
"aaaa\n"
"bbbbbb\n"
"cccc\n",
{{1, 3}}, // enabled first two lines only
{{12, 17}} // disable all other lines
},
{
"aaaa\n"
"bbbbbb\n"
"cccc\n",
{{2, 4}}, // enabled last two lines only
{{0, 5}} // disable all other lines
},
{
"aaaa\n"
"bbbbbb\n"
"cccc\n",
{{1, 4}}, // enabled no lines only
{} // disable no lines
},
{
"aaaa\n"
"bbbbbb\n"
"cccc\n",
{{0, 5}}, // excess range
{} // disable no lines
},
{
"aaaa\n"
"bbbbbb\n"
"cccc", // missing terminating '\n' (POSIX)
{{1, 4}}, // excess range
{} // disable no lines
},
{
"aaaa\n"
"bbbbbb\n"
"cccc", // missing terminating '\n' (POSIX)
{{0, 5}}, // excess range
{} // disable no lines
},
{
"aaaa\n"
"bbbbbb\n"
"cccc", // missing terminating '\n' (POSIX)
{{4, 8}}, // excess range
{{0, 12}} // disable all (whole) lines
},
{
"aaaa\n"
"bbbbbb\n"
"cccc\n",
{{4, 8}}, // range outside, interpret as disable all other lines
{{0, 17}} // disable all lines
},
};
for (const auto& test : kTestCases) {
LineColumnMap line_map(test.text);
const ByteOffsetSet result(
EnabledLinesToDisabledByteRanges(test.enabled_lines, line_map));
EXPECT_EQ(result, test.expected_bytes)
<< "lines: " << test.enabled_lines << "\ncolumn map: "
<< absl::StrJoin(line_map.GetBeginningOfLineOffsets(), ",",
absl::StreamFormatter());
}
}
struct FormatWhitespaceTestCase {
absl::string_view full_text;
std::pair<int, int> substring_range;
ByteOffsetSet disabled_ranges;
absl::string_view expected;
};
TEST(FormatWhitespaceWithDisabledByteRangesTest, InvalidSubstring) {
const absl::string_view foo("foo"), bar("bar");
std::ostringstream stream;
EXPECT_DEATH(FormatWhitespaceWithDisabledByteRanges(foo, bar, {}, stream),
"IsSubRange");
}
TEST(FormatWhitespaceWithDisabledByteRangesTest, EmptyStrings) {
// The only special character in these functions/tests is '\n',
// everything else is treated the same, space or not.
// We use nonspace characters for positional readability.
const FormatWhitespaceTestCase kTestCases[] = {
{"", {0, 0}, {}, ""},
{"\n", {0, 0}, {}, ""},
{"\n", {0, 1}, {}, "\n"},
{"\n\n", {0, 1}, {}, "\n"},
{"\n\n", {1, 2}, {}, "\n"},
{"\n\n", {1, 1}, {}, "\n"}, // space text is ""
{"\n\n", {0, 2}, {}, "\n\n"},
{"\n\n", {0, 2}, {{0, 1}}, "\n\n"},
{"\n\n", {0, 2}, {{1, 2}}, "\n\n"},
{"\n\n", {0, 2}, {{0, 2}}, "\n\n"},
{"abcd", {0, 2}, {}, ""},
{"abcd", {1, 3}, {}, "\n"},
{"abcd", {1, 3}, {{0, 1}, {3, 4}}, "\n"},
{"abcd", {1, 3}, {{0, 4}}, "bc"},
{"abcd", {0, 2}, {{0, 4}}, "ab"},
{"abcd", {2, 4}, {{0, 4}}, "cd"},
{"abcd", {1, 3}, {{0, 2}}, "b\n"}, // semi-disabled
{"abcd", {1, 3}, {{2, 4}}, "c\n"}, // semi-disabled
{"abcd", {0, 0}, {{0, 4}}, ""},
{"abcd", {1, 1}, {{0, 4}}, ""},
{"abcd", {0, 0}, {}, ""},
{"abcd", {1, 1}, {}, "\n"},
{"ab\ncd\nef\n", {2, 5}, {}, "\n"},
{"ab\ncd\nef\n", {2, 6}, {}, "\n\n"},
{"ab\ncd\nef\n", {3, 6}, {}, "\n"},
{"ab\ncd\nef\n", {3, 7}, {}, "\n"},
{"ab\ncd\nef\n", {2, 5}, {{0, 9}}, "\ncd"},
{"ab\ncd\nef\n", {2, 6}, {{0, 9}}, "\ncd\n"},
{"ab\ncd\nef\n", {3, 6}, {{0, 9}}, "cd\n"},
{"ab\ncd\nef\n", {3, 7}, {{0, 9}}, "cd\ne"},
{"ab\ncd\nef\n", {3, 9}, {{0, 9}}, "cd\nef\n"},
{"ab\ncd\nef\n", {3, 9}, {}, "\n\n"},
{"ab\ncd\nef\n", {3, 9}, {{3, 4}}, "c\n\n"},
{"ab\ncd\nef\n", {3, 9}, {{4, 5}}, "d\n\n"},
{"ab\ncd\nef\n", {3, 9}, {{5, 6}}, "\n\n"},
{"ab\ncd\nef\n", {3, 9}, {{6, 7}}, "\ne\n"},
{"ab\ncd\nef\n", {3, 9}, {{7, 8}}, "\nf\n"},
{"ab\ncd\nef\n", {3, 9}, {{8, 9}}, "\n\n"},
{"ab\ncd\nef\n", {2, 5}, {{0, 3}}, "\n"},
{"ab\ncd\nef\n", {2, 6}, {{0, 3}}, "\n\n"},
{"ab\ncd\nef\n", {3, 6}, {{0, 3}}, "\n"},
{"ab\ncd\nef\n", {3, 6}, {{5, 6}}, "\n"},
{"ab\ncd\nef\n", {3, 6}, {{5, 9}}, "\n"},
{"ab\ncd\nef\n", {3, 6}, {{6, 9}}, "\n"},
};
for (const auto& test : kTestCases) {
std::ostringstream stream;
const auto substr = test.full_text.substr(
test.substring_range.first,
test.substring_range.second - test.substring_range.first);
FormatWhitespaceWithDisabledByteRanges(test.full_text, substr,
test.disabled_ranges, stream);
EXPECT_EQ(stream.str(), test.expected)
<< "text: \"" << test.full_text << "\", sub: \"" << substr
<< "\", disabled: " << test.disabled_ranges;
}
}
} // namespace
} // namespace formatter
} // namespace verilog