253 lines
8.0 KiB
C
253 lines
8.0 KiB
C
|
//===--- 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
|