| // 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 |