llvm-project/clang/unittests/Tooling/TestVisitor.h

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

253 lines
8.0 KiB
C
Raw Normal View History

//===--- TestVisitor.h ------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief Defines utility templates for RecursiveASTVisitor related tests.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_UNITTESTS_TOOLING_TESTVISITOR_H
#define LLVM_CLANG_UNITTESTS_TOOLING_TESTVISITOR_H
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/Tooling.h"
#include "gtest/gtest.h"
#include <vector>
namespace clang {
/// \brief Base class for simple RecursiveASTVisitor based tests.
///
/// This is a drop-in replacement for RecursiveASTVisitor itself, with the
/// additional capability of running it over a snippet of code.
///
/// Visits template instantiations and implicit code by default.
template <typename T>
class TestVisitor : public RecursiveASTVisitor<T> {
public:
TestVisitor() { }
virtual ~TestVisitor() { }
enum Language {
Lang_C,
Lang_CXX98,
Lang_CXX11,
Lang_CXX14,
Lang_CXX17,
Lang_CXX2a,
Lang_OBJC,
Lang_OBJCXX11,
Lang_CXX = Lang_CXX98
};
/// \brief Runs the current AST visitor over the given code.
bool runOver(StringRef Code, Language L = Lang_CXX) {
std::vector<std::string> Args;
switch (L) {
case Lang_C:
Args.push_back("-x");
Args.push_back("c");
break;
case Lang_CXX98: Args.push_back("-std=c++98"); break;
case Lang_CXX11: Args.push_back("-std=c++11"); break;
case Lang_CXX14: Args.push_back("-std=c++14"); break;
case Lang_CXX17: Args.push_back("-std=c++17"); break;
case Lang_CXX2a: Args.push_back("-std=c++2a"); break;
case Lang_OBJC:
Args.push_back("-ObjC");
Args.push_back("-fobjc-runtime=macosx-10.12.0");
break;
case Lang_OBJCXX11:
Args.push_back("-ObjC++");
Args.push_back("-std=c++11");
Args.push_back("-fblocks");
break;
}
return tooling::runToolOnCodeWithArgs(CreateTestAction(), Code, Args);
}
bool shouldVisitTemplateInstantiations() const {
return true;
}
bool shouldVisitImplicitCode() const {
return true;
}
protected:
virtual std::unique_ptr<ASTFrontendAction> CreateTestAction() {
return std::make_unique<TestAction>(this);
}
class FindConsumer : public ASTConsumer {
public:
FindConsumer(TestVisitor *Visitor) : Visitor(Visitor) {}
void HandleTranslationUnit(clang::ASTContext &Context) override {
Visitor->Context = &Context;
Visitor->TraverseDecl(Context.getTranslationUnitDecl());
}
private:
TestVisitor *Visitor;
};
class TestAction : public ASTFrontendAction {
public:
TestAction(TestVisitor *Visitor) : Visitor(Visitor) {}
std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(CompilerInstance &, llvm::StringRef dummy) override {
/// TestConsumer will be deleted by the framework calling us.
return std::make_unique<FindConsumer>(Visitor);
}
protected:
TestVisitor *Visitor;
};
ASTContext *Context;
};
/// \brief A RecursiveASTVisitor to check that certain matches are (or are
/// not) observed during visitation.
///
/// This is a RecursiveASTVisitor for testing the RecursiveASTVisitor itself,
/// and allows simple creation of test visitors running matches on only a small
/// subset of the Visit* methods.
template <typename T, template <typename> class Visitor = TestVisitor>
class ExpectedLocationVisitor : public Visitor<T> {
public:
/// \brief Expect 'Match' *not* to occur at the given 'Line' and 'Column'.
///
/// Any number of matches can be disallowed.
void DisallowMatch(Twine Match, unsigned Line, unsigned Column) {
DisallowedMatches.push_back(MatchCandidate(Match, Line, Column));
}
/// \brief Expect 'Match' to occur at the given 'Line' and 'Column'.
///
/// Any number of expected matches can be set by calling this repeatedly.
/// Each is expected to be matched 'Times' number of times. (This is useful in
/// cases in which different AST nodes can match at the same source code
/// location.)
void ExpectMatch(Twine Match, unsigned Line, unsigned Column,
unsigned Times = 1) {
ExpectedMatches.push_back(ExpectedMatch(Match, Line, Column, Times));
}
/// \brief Checks that all expected matches have been found.
~ExpectedLocationVisitor() override {
for (typename std::vector<ExpectedMatch>::const_iterator
It = ExpectedMatches.begin(), End = ExpectedMatches.end();
It != End; ++It) {
It->ExpectFound();
}
}
protected:
/// \brief Checks an actual match against expected and disallowed matches.
///
/// Implementations are required to call this with appropriate values
/// for 'Name' during visitation.
void Match(StringRef Name, SourceLocation Location) {
const FullSourceLoc FullLocation = this->Context->getFullLoc(Location);
for (typename std::vector<MatchCandidate>::const_iterator
It = DisallowedMatches.begin(), End = DisallowedMatches.end();
It != End; ++It) {
EXPECT_FALSE(It->Matches(Name, FullLocation))
<< "Matched disallowed " << *It;
}
for (typename std::vector<ExpectedMatch>::iterator
It = ExpectedMatches.begin(), End = ExpectedMatches.end();
It != End; ++It) {
It->UpdateFor(Name, FullLocation, this->Context->getSourceManager());
}
}
private:
struct MatchCandidate {
std::string ExpectedName;
unsigned LineNumber;
unsigned ColumnNumber;
MatchCandidate(Twine Name, unsigned LineNumber, unsigned ColumnNumber)
: ExpectedName(Name.str()), LineNumber(LineNumber),
ColumnNumber(ColumnNumber) {
}
bool Matches(StringRef Name, FullSourceLoc const &Location) const {
return MatchesName(Name) && MatchesLocation(Location);
}
bool PartiallyMatches(StringRef Name, FullSourceLoc const &Location) const {
return MatchesName(Name) || MatchesLocation(Location);
}
bool MatchesName(StringRef Name) const {
return Name == ExpectedName;
}
bool MatchesLocation(FullSourceLoc const &Location) const {
return Location.isValid() &&
Location.getSpellingLineNumber() == LineNumber &&
Location.getSpellingColumnNumber() == ColumnNumber;
}
friend std::ostream &operator<<(std::ostream &Stream,
MatchCandidate const &Match) {
return Stream << Match.ExpectedName
<< " at " << Match.LineNumber << ":" << Match.ColumnNumber;
}
};
struct ExpectedMatch {
ExpectedMatch(Twine Name, unsigned LineNumber, unsigned ColumnNumber,
unsigned Times)
: Candidate(Name, LineNumber, ColumnNumber), TimesExpected(Times),
TimesSeen(0) {}
void UpdateFor(StringRef Name, FullSourceLoc Location, SourceManager &SM) {
if (Candidate.Matches(Name, Location)) {
EXPECT_LT(TimesSeen, TimesExpected);
++TimesSeen;
} else if (TimesSeen < TimesExpected &&
Candidate.PartiallyMatches(Name, Location)) {
llvm::raw_string_ostream Stream(PartialMatches);
Stream << ", partial match: \"" << Name << "\" at ";
Location.print(Stream, SM);
}
}
void ExpectFound() const {
EXPECT_EQ(TimesExpected, TimesSeen)
<< "Expected \"" << Candidate.ExpectedName
<< "\" at " << Candidate.LineNumber
<< ":" << Candidate.ColumnNumber << PartialMatches;
}
MatchCandidate Candidate;
std::string PartialMatches;
unsigned TimesExpected;
unsigned TimesSeen;
};
std::vector<MatchCandidate> DisallowedMatches;
std::vector<ExpectedMatch> ExpectedMatches;
};
}
#endif