blob: de1bbf7f744483c575339d00fab7e2dc4458bfac [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/analysis/syntax_tree_linter.h"
#include <memory>
#include <vector>
#include "common/analysis/lint_rule_status.h"
#include "common/analysis/syntax_tree_lint_rule.h"
#include "common/text/concrete_syntax_leaf.h"
#include "common/text/concrete_syntax_tree.h"
#include "common/text/symbol.h"
#include "common/text/syntax_tree_context.h"
#include "common/text/token_info.h"
#include "common/text/tree_builder_test_util.h"
#include "gtest/gtest.h"
namespace verible {
namespace {
// Simple rule for testing that verifies that all leafs contained in tree
// are tagged as a certain value. This value is passed in via the constructor
class AllLeavesMustBeN : public SyntaxTreeLintRule {
public:
// Constructor that takes n, the number that this rule is checking for
explicit AllLeavesMustBeN(int n) : target_(n) {}
// When handling leaf, check that it has target tag and report a violation
// is not
void HandleLeaf(const SyntaxTreeLeaf& leaf,
const SyntaxTreeContext& context) final {
if (leaf.get().token_enum() != target_) {
violations_.insert(LintViolation(leaf.get(), "", context));
}
}
// Do not operate on nodes
void HandleNode(const SyntaxTreeNode& node,
const SyntaxTreeContext& context) final {}
LintRuleStatus Report() const final { return LintRuleStatus(violations_); }
private:
std::set<LintViolation> violations_;
int target_;
};
std::unique_ptr<SyntaxTreeLintRule> MakeRuleN(int n) {
return std::unique_ptr<SyntaxTreeLintRule>(new AllLeavesMustBeN(n));
}
TEST(SyntaxTreeLinterTest, BasicUsageCorrect) {
const SymbolPtr root = Node(XLeaf(2), XLeaf(2), Node(XLeaf(2)), XLeaf(2));
SyntaxTreeLinter linter;
linter.AddRule(MakeRuleN(2));
ASSERT_NE(root, nullptr);
linter.Lint(*root);
std::vector<LintRuleStatus> statuses = linter.ReportStatus();
EXPECT_EQ(statuses.size(), 1);
EXPECT_TRUE(statuses[0].isOk());
EXPECT_EQ(statuses[0].violations.size(), 0);
}
TEST(SyntaxTreeLinterTest, BasicUsageFailure) {
SymbolPtr root = Node(XLeaf(2), XLeaf(2), Node(XLeaf(2)), XLeaf(3));
SyntaxTreeLinter linter;
linter.AddRule(MakeRuleN(2));
ASSERT_NE(root, nullptr);
linter.Lint(*root);
std::vector<LintRuleStatus> statuses = linter.ReportStatus();
EXPECT_EQ(statuses.size(), 1);
EXPECT_FALSE(statuses[0].isOk());
EXPECT_EQ(statuses[0].violations.size(), 1);
}
TEST(SyntaxTreeLinterTest, MultipleRules) {
constexpr absl::string_view text("abcd");
SymbolPtr root =
Node(Leaf(2, text.substr(0, 1)), Leaf(2, text.substr(1, 1)),
Node(Leaf(2, text.substr(2, 1))), Leaf(2, text.substr(3, 1)));
SyntaxTreeLinter linter;
linter.AddRule(MakeRuleN(2));
linter.AddRule(MakeRuleN(3));
ASSERT_NE(root, nullptr);
linter.Lint(*root);
std::vector<LintRuleStatus> statuses = linter.ReportStatus();
EXPECT_EQ(statuses.size(), 2);
EXPECT_TRUE(statuses[0].isOk());
EXPECT_FALSE(statuses[1].isOk());
EXPECT_EQ(statuses[0].violations.size(), 0);
EXPECT_EQ(statuses[1].violations.size(), 4);
}
// Simple testing rule that verifies that every node's leaf children have tags
// that are in ascending order
class ChildrenLeavesAscending : public SyntaxTreeLintRule {
public:
// Do not process leaves
void HandleLeaf(const SyntaxTreeLeaf& leaf,
const SyntaxTreeContext& context) final {}
// When handling nodes, iterate through all children and check that tags
// are ascending. Report leafs nodes that have leaves that are out of order.
void HandleNode(const SyntaxTreeNode& node,
const SyntaxTreeContext& context) final {
int last_tag = 0;
for (const auto& child : node.children()) {
if (child->Kind() == SymbolKind::kLeaf) {
const SyntaxTreeLeaf* leaf_child =
down_cast<SyntaxTreeLeaf*>(child.get());
const int current_tag = leaf_child->get().token_enum();
if (current_tag >= last_tag) {
last_tag = current_tag;
} else {
violations_.insert(LintViolation(leaf_child->get(), "", context));
return;
}
}
}
}
LintRuleStatus Report() const final { return LintRuleStatus(violations_); }
private:
std::set<LintViolation> violations_;
};
std::unique_ptr<SyntaxTreeLintRule> MakeAscending() {
return std::unique_ptr<SyntaxTreeLintRule>(new ChildrenLeavesAscending());
}
TEST(SyntaxTreeLinterTest, AscendingSuccess) {
constexpr absl::string_view text("abcde");
SymbolPtr root =
Node(Leaf(1, text.substr(0, 1)), Leaf(4, text.substr(1, 1)),
Node(Leaf(2, text.substr(2, 1)), Leaf(10, text.substr(3, 1))),
Leaf(7, text.substr(4, 1)));
SyntaxTreeLinter linter;
linter.AddRule(MakeAscending());
ASSERT_NE(root.get(), nullptr);
linter.Lint(*root.get());
std::vector<LintRuleStatus> statuses = linter.ReportStatus();
EXPECT_EQ(statuses.size(), 1);
EXPECT_TRUE(statuses[0].isOk());
}
TEST(SyntaxTreeLinterTest, AscendingFailsOnce) {
constexpr absl::string_view text("abcde");
SymbolPtr root =
Node(Leaf(1, text.substr(0, 1)), Leaf(4, text.substr(1, 1)),
Node(Leaf(2, text.substr(2, 1)), Leaf(10, text.substr(3, 1))),
Leaf(1, text.substr(4, 1)));
SyntaxTreeLinter linter;
linter.AddRule(MakeAscending());
ASSERT_NE(root.get(), nullptr);
linter.Lint(*root.get());
std::vector<LintRuleStatus> statuses = linter.ReportStatus();
EXPECT_EQ(statuses.size(), 1);
EXPECT_FALSE(statuses[0].isOk());
EXPECT_EQ(statuses[0].violations.size(), 1);
}
TEST(SyntaxTreeLinterTest, AscendingFailsTwice) {
constexpr absl::string_view text("abcde");
SymbolPtr root =
Node(Leaf(1, text.substr(0, 1)), Leaf(4, text.substr(1, 1)),
Node(Leaf(210, text.substr(2, 1)), Leaf(10, text.substr(3, 1))),
Leaf(1, text.substr(4, 1)));
SyntaxTreeLinter linter;
linter.AddRule(MakeAscending());
ASSERT_NE(root.get(), nullptr);
linter.Lint(*root.get());
std::vector<LintRuleStatus> statuses = linter.ReportStatus();
EXPECT_EQ(statuses.size(), 1);
EXPECT_FALSE(statuses[0].isOk());
EXPECT_EQ(statuses[0].violations.size(), 2);
}
TEST(SyntaxTreeLinterTest, HeterogenousTests) {
constexpr absl::string_view text("abcde");
SymbolPtr root =
Node(Leaf(1, text.substr(0, 1)), Leaf(4, text.substr(1, 1)),
Node(Leaf(210, text.substr(2, 1)), Leaf(10, text.substr(3, 1))),
Leaf(1, text.substr(4, 1)));
SyntaxTreeLinter linter;
linter.AddRule(MakeAscending());
linter.AddRule(MakeRuleN(1));
ASSERT_NE(root.get(), nullptr);
linter.Lint(*root.get());
std::vector<LintRuleStatus> statuses = linter.ReportStatus();
EXPECT_EQ(statuses.size(), 2);
EXPECT_FALSE(statuses[0].isOk());
EXPECT_EQ(statuses[0].violations.size(), 2);
EXPECT_FALSE(statuses[1].isOk());
EXPECT_EQ(statuses[1].violations.size(), 3);
}
// Simple testing rule that verifies that each leaf's tag is the same as its
// depth in the tree.
class TagMatchesContextDepth : public SyntaxTreeLintRule {
public:
// When handling a leaf, check leaf's tag vs the size of its context
void HandleLeaf(const SyntaxTreeLeaf& leaf,
const SyntaxTreeContext& context) final {
if (static_cast<size_t>(leaf.get().token_enum()) != context.size()) {
violations_.insert(LintViolation(leaf.get(), "", context));
}
}
// Do not process nodes
void HandleNode(const SyntaxTreeNode& node,
const SyntaxTreeContext& context) final {}
LintRuleStatus Report() const final { return LintRuleStatus(violations_); }
private:
std::set<LintViolation> violations_;
};
std::unique_ptr<SyntaxTreeLintRule> MakeDepth() {
return std::unique_ptr<SyntaxTreeLintRule>(new TagMatchesContextDepth());
}
TEST(SyntaxTreeLinterTest, DepthFails) {
constexpr absl::string_view text("abcde");
SymbolPtr root =
Node(Leaf(1, text.substr(0, 1)), Leaf(4, text.substr(1, 1)),
Node(Leaf(210, text.substr(2, 1)), Leaf(10, text.substr(3, 1))),
Leaf(1, text.substr(4, 1)));
SyntaxTreeLinter linter;
linter.AddRule(MakeDepth());
ASSERT_NE(root.get(), nullptr);
linter.Lint(*root.get());
std::vector<LintRuleStatus> statuses = linter.ReportStatus();
EXPECT_EQ(statuses.size(), 1);
EXPECT_FALSE(statuses[0].isOk());
EXPECT_EQ(statuses[0].violations.size(), 3);
}
TEST(SyntaxTreeLinterTest, DepthSuccess) {
constexpr absl::string_view text("abcde");
SymbolPtr root =
Node(Leaf(1, text.substr(0, 1)), Leaf(1, text.substr(1, 1)),
Node(Leaf(2, text.substr(2, 1)), Leaf(2, text.substr(3, 1))),
Leaf(1, text.substr(4, 1)));
SyntaxTreeLinter linter;
linter.AddRule(MakeDepth());
ASSERT_NE(root.get(), nullptr);
linter.Lint(*root.get());
std::vector<LintRuleStatus> statuses = linter.ReportStatus();
EXPECT_EQ(statuses.size(), 1);
EXPECT_TRUE(statuses[0].isOk());
EXPECT_EQ(statuses[0].violations.size(), 0);
}
} // namespace
} // namespace verible