10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Lex/PPCallbacks.h"
13#include "clang/Lex/Preprocessor.h"
21 "Google Test APIs named with 'case' are deprecated; use equivalent APIs "
24static std::optional<llvm::StringRef>
26 std::pair<llvm::StringRef, llvm::StringRef> ReplacementMap[] = {
27 {
"TYPED_TEST_CASE",
"TYPED_TEST_SUITE"},
28 {
"TYPED_TEST_CASE_P",
"TYPED_TEST_SUITE_P"},
29 {
"REGISTER_TYPED_TEST_CASE_P",
"REGISTER_TYPED_TEST_SUITE_P"},
30 {
"INSTANTIATE_TYPED_TEST_CASE_P",
"INSTANTIATE_TYPED_TEST_SUITE_P"},
31 {
"INSTANTIATE_TEST_CASE_P",
"INSTANTIATE_TEST_SUITE_P"},
34 for (
auto &Mapping : ReplacementMap) {
36 return Mapping.second;
44class UpgradeGoogletestCasePPCallback :
public PPCallbacks {
46 UpgradeGoogletestCasePPCallback(UpgradeGoogletestCaseCheck *Check,
48 : Check(Check), PP(PP) {}
50 void MacroExpands(
const Token &MacroNameTok,
const MacroDefinition &MD,
51 SourceRange Range,
const MacroArgs *)
override {
52 macroUsed(MacroNameTok, MD,
Range.getBegin(), CheckAction::Rename);
55 void MacroUndefined(
const Token &MacroNameTok,
const MacroDefinition &MD,
56 const MacroDirective *Undef)
override {
58 macroUsed(MacroNameTok, MD, Undef->getLocation(), CheckAction::Warn);
61 void MacroDefined(
const Token &MacroNameTok,
62 const MacroDirective *MD)
override {
63 if (!ReplacementFound && MD !=
nullptr) {
67 llvm::StringRef
FileName = PP->getSourceManager().getFilename(
68 MD->getMacroInfo()->getDefinitionLoc());
69 ReplacementFound =
FileName.ends_with(
"gtest/gtest-typed-test.h") &&
70 PP->getSpelling(MacroNameTok) ==
"TYPED_TEST_SUITE";
74 void Defined(
const Token &MacroNameTok,
const MacroDefinition &MD,
75 SourceRange Range)
override {
76 macroUsed(MacroNameTok, MD,
Range.getBegin(), CheckAction::Warn);
79 void Ifdef(SourceLocation
Loc,
const Token &MacroNameTok,
80 const MacroDefinition &MD)
override {
81 macroUsed(MacroNameTok, MD,
Loc, CheckAction::Warn);
84 void Ifndef(SourceLocation
Loc,
const Token &MacroNameTok,
85 const MacroDefinition &MD)
override {
86 macroUsed(MacroNameTok, MD,
Loc, CheckAction::Warn);
90 enum class CheckAction { Warn, Rename };
92 void macroUsed(
const clang::Token &MacroNameTok,
const MacroDefinition &MD,
94 if (!ReplacementFound)
97 std::string
Name = PP->getSpelling(MacroNameTok);
103 llvm::StringRef
FileName = PP->getSourceManager().getFilename(
104 MD.getMacroInfo()->getDefinitionLoc());
105 if (!
FileName.ends_with(
"gtest/gtest-typed-test.h"))
110 if (
Action == CheckAction::Rename)
111 Diag << FixItHint::CreateReplacement(
112 CharSourceRange::getTokenRange(
Loc,
Loc), *Replacement);
115 bool ReplacementFound =
false;
116 UpgradeGoogletestCaseCheck *Check;
126 std::make_unique<UpgradeGoogletestCasePPCallback>(
this, PP));
130 auto LocationFilter =
131 unless(isExpansionInFileMatching(
"gtest/gtest(-typed-test)?\\.h$"));
141 hasAnyName(
"SetUpTestCase",
"TearDownTestCase"),
143 cxxRecordDecl(isSameOrDerivedFrom(cxxRecordDecl(
144 hasName(
"::testing::Test"),
145 hasMethod(hasName(
"SetUpTestSuite")))))
148 hasName(
"test_case_name"),
150 cxxRecordDecl(isSameOrDerivedFrom(cxxRecordDecl(
151 hasName(
"::testing::TestInfo"),
152 hasMethod(hasName(
"test_suite_name")))))
155 hasAnyName(
"OnTestCaseStart",
"OnTestCaseEnd"),
156 ofClass(cxxRecordDecl(
157 isSameOrDerivedFrom(cxxRecordDecl(
158 hasName(
"::testing::TestEventListener"),
159 hasMethod(hasName(
"OnTestSuiteStart")))))
162 hasAnyName(
"current_test_case",
"successful_test_case_count",
163 "failed_test_case_count",
"total_test_case_count",
164 "test_case_to_run_count",
"GetTestCase"),
165 ofClass(cxxRecordDecl(
166 isSameOrDerivedFrom(cxxRecordDecl(
167 hasName(
"::testing::UnitTest"),
168 hasMethod(hasName(
"current_test_suite")))))
172 Finder->addMatcher(expr(anyOf(callExpr(callee(Methods)).bind(
"call"),
173 declRefExpr(to(Methods)).bind(
"ref")),
178 usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(Methods)), LocationFilter)
182 Finder->addMatcher(cxxMethodDecl(Methods, LocationFilter),
this);
187 auto TestCaseTypeAlias =
188 typeAliasDecl(hasName(
"::testing::TestCase")).bind(
"test-case");
190 typeLoc(loc(qualType(typedefType(hasDeclaration(TestCaseTypeAlias)))),
191 unless(hasAncestor(decl(isImplicit()))), LocationFilter)
195 usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(TestCaseTypeAlias)))
199 typeLoc(loc(usingType(hasUnderlyingType(
200 typedefType(hasDeclaration(TestCaseTypeAlias))))),
201 unless(hasAncestor(decl(isImplicit()))), LocationFilter)
207 std::pair<llvm::StringRef, llvm::StringRef> ReplacementMap[] = {
208 {
"SetUpTestCase",
"SetUpTestSuite"},
209 {
"TearDownTestCase",
"TearDownTestSuite"},
210 {
"test_case_name",
"test_suite_name"},
211 {
"OnTestCaseStart",
"OnTestSuiteStart"},
212 {
"OnTestCaseEnd",
"OnTestSuiteEnd"},
213 {
"current_test_case",
"current_test_suite"},
214 {
"successful_test_case_count",
"successful_test_suite_count"},
215 {
"failed_test_case_count",
"failed_test_suite_count"},
216 {
"total_test_case_count",
"total_test_suite_count"},
217 {
"test_case_to_run_count",
"test_suite_to_run_count"},
218 {
"GetTestCase",
"GetTestSuite"}};
220 for (
auto &Mapping : ReplacementMap) {
221 if (CurrentName == Mapping.first)
222 return Mapping.second;
225 llvm_unreachable(
"Unexpected function name");
228template <
typename NodeType>
230 const MatchFinder::MatchResult &Result) {
231 return !match(isInTemplateInstantiation(),
Node, *Result.Context).empty();
234template <
typename NodeType>
236 const MatchFinder::MatchResult &Result) {
237 internal::Matcher<NodeType> IsInsideTemplate =
238 hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));
239 return !match(IsInsideTemplate,
Node, *Result.Context).empty();
244 llvm::StringRef ReplacementMethod) {
245 const auto *Class = Result.Nodes.getNodeAs<CXXRecordDecl>(
"class");
246 return !match(cxxRecordDecl(
247 unless(isExpansionInFileMatching(
248 "gtest/gtest(-typed-test)?\\.h$")),
249 hasMethod(cxxMethodDecl(hasName(ReplacementMethod)))),
250 *Class, *Result.Context)
254static CharSourceRange
256 if (
const auto *Using = Result.Nodes.getNodeAs<UsingDecl>(
"using")) {
257 return CharSourceRange::getTokenRange(
258 Using->getNameInfo().getSourceRange());
260 return CharSourceRange::getTokenRange(
261 Result.Nodes.getNodeAs<TypeLoc>(
"typeloc")->getSourceRange());
265 llvm::StringRef ReplacementText;
266 CharSourceRange ReplacementRange;
267 if (
const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>(
"method")) {
270 bool IsInInstantiation =
false;
271 bool IsInTemplate =
false;
273 if (
const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>(
"call")) {
274 const auto *Callee = llvm::cast<MemberExpr>(Call->getCallee());
275 ReplacementRange = CharSourceRange::getTokenRange(Callee->getMemberLoc(),
276 Callee->getMemberLoc());
278 IsInTemplate = isInTemplate<Stmt>(*Call, Result);
279 }
else if (
const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>(
"ref")) {
281 CharSourceRange::getTokenRange(Ref->getNameInfo().getSourceRange());
283 IsInTemplate = isInTemplate<Stmt>(*Ref, Result);
284 }
else if (
const auto *Using = Result.Nodes.getNodeAs<UsingDecl>(
"using")) {
286 CharSourceRange::getTokenRange(Using->getNameInfo().getSourceRange());
288 IsInTemplate = isInTemplate<Decl>(*Using, Result);
294 ReplacementRange = CharSourceRange::getTokenRange(
295 Method->getNameInfo().getSourceRange());
297 IsInTemplate = isInTemplate<Decl>(*Method, Result);
305 if (IsInInstantiation) {
306 if (MatchedTemplateLocations.count(ReplacementRange.getBegin()) == 0) {
320 MatchedTemplateLocations.insert(ReplacementRange.getBegin());
329 assert(Result.Nodes.getNodeAs<TypeAliasDecl>(
"test-case") !=
nullptr);
330 ReplacementText =
"TestSuite";
338 DiagnosticBuilder Diag =
341 ReplacementRange = Lexer::makeFileCharRange(
342 ReplacementRange, *Result.SourceManager, Result.Context->getLangOpts());
343 if (ReplacementRange.isInvalid())
348 Diag << FixItHint::CreateReplacement(ReplacementRange, ReplacementText);
llvm::SmallString< 256U > Name
CharSourceRange Range
SourceRange for the file name.
::clang::DynTypedNode Node
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
static bool derivedTypeHasReplacementMethod(const MatchFinder::MatchResult &Result, llvm::StringRef ReplacementMethod)
static llvm::StringRef getNewMethodName(llvm::StringRef CurrentName)
static const llvm::StringRef RenameCaseToSuiteMessage
static bool isInInstantiation(const NodeType &Node, const MatchFinder::MatchResult &Result)
static std::optional< llvm::StringRef > getNewMacroName(llvm::StringRef MacroName)
static CharSourceRange getAliasNameRange(const MatchFinder::MatchResult &Result)
static bool isInTemplate(const NodeType &Node, const MatchFinder::MatchResult &Result)