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 static const llvm::StringMap<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 if (
const auto MappingIt = ReplacementMap.find(MacroName);
35 MappingIt != ReplacementMap.end())
36 return MappingIt->second;
42class UpgradeGoogletestCasePPCallback :
public PPCallbacks {
44 UpgradeGoogletestCasePPCallback(UpgradeGoogletestCaseCheck *Check,
46 : Check(Check), PP(PP) {}
48 void MacroExpands(
const Token &MacroNameTok,
const MacroDefinition &MD,
49 SourceRange Range,
const MacroArgs *)
override {
50 macroUsed(MacroNameTok, MD, Range.getBegin(), CheckAction::Rename);
53 void MacroUndefined(
const Token &MacroNameTok,
const MacroDefinition &MD,
54 const MacroDirective *Undef)
override {
56 macroUsed(MacroNameTok, MD, Undef->getLocation(), CheckAction::Warn);
59 void MacroDefined(
const Token &MacroNameTok,
60 const MacroDirective *MD)
override {
61 if (!ReplacementFound && MD !=
nullptr) {
65 const llvm::StringRef FileName = PP->getSourceManager().getFilename(
66 MD->getMacroInfo()->getDefinitionLoc());
67 ReplacementFound = FileName.ends_with(
"gtest/gtest-typed-test.h") &&
68 PP->getSpelling(MacroNameTok) ==
"TYPED_TEST_SUITE";
72 void Defined(
const Token &MacroNameTok,
const MacroDefinition &MD,
73 SourceRange Range)
override {
74 macroUsed(MacroNameTok, MD, Range.getBegin(), CheckAction::Warn);
77 void Ifdef(SourceLocation Loc,
const Token &MacroNameTok,
78 const MacroDefinition &MD)
override {
79 macroUsed(MacroNameTok, MD, Loc, CheckAction::Warn);
82 void Ifndef(SourceLocation Loc,
const Token &MacroNameTok,
83 const MacroDefinition &MD)
override {
84 macroUsed(MacroNameTok, MD, Loc, CheckAction::Warn);
88 enum class CheckAction { Warn, Rename };
90 void macroUsed(
const clang::Token &MacroNameTok,
const MacroDefinition &MD,
91 SourceLocation Loc, CheckAction Action) {
92 if (!ReplacementFound)
95 const std::string Name = PP->getSpelling(MacroNameTok);
101 const llvm::StringRef FileName = PP->getSourceManager().getFilename(
102 MD.getMacroInfo()->getDefinitionLoc());
103 if (!FileName.ends_with(
"gtest/gtest-typed-test.h"))
108 if (Action == CheckAction::Rename)
109 Diag << FixItHint::CreateReplacement(
110 CharSourceRange::getTokenRange(Loc, Loc), *Replacement);
113 bool ReplacementFound =
false;
114 UpgradeGoogletestCaseCheck *Check;
124 std::make_unique<UpgradeGoogletestCasePPCallback>(
this, PP));
128 auto LocationFilter =
129 unless(isExpansionInFileMatching(
"gtest/gtest(-typed-test)?\\.h$"));
139 hasAnyName(
"SetUpTestCase",
"TearDownTestCase"),
141 cxxRecordDecl(isSameOrDerivedFrom(cxxRecordDecl(
142 hasName(
"::testing::Test"),
143 hasMethod(hasName(
"SetUpTestSuite")))))
146 hasName(
"test_case_name"),
148 cxxRecordDecl(isSameOrDerivedFrom(cxxRecordDecl(
149 hasName(
"::testing::TestInfo"),
150 hasMethod(hasName(
"test_suite_name")))))
153 hasAnyName(
"OnTestCaseStart",
"OnTestCaseEnd"),
154 ofClass(cxxRecordDecl(
155 isSameOrDerivedFrom(cxxRecordDecl(
156 hasName(
"::testing::TestEventListener"),
157 hasMethod(hasName(
"OnTestSuiteStart")))))
160 hasAnyName(
"current_test_case",
"successful_test_case_count",
161 "failed_test_case_count",
"total_test_case_count",
162 "test_case_to_run_count",
"GetTestCase"),
163 ofClass(cxxRecordDecl(
164 isSameOrDerivedFrom(cxxRecordDecl(
165 hasName(
"::testing::UnitTest"),
166 hasMethod(hasName(
"current_test_suite")))))
170 Finder->addMatcher(expr(anyOf(callExpr(callee(Methods)).bind(
"call"),
171 declRefExpr(to(Methods)).bind(
"ref")),
176 usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(Methods)), LocationFilter)
180 Finder->addMatcher(cxxMethodDecl(Methods, LocationFilter),
this);
185 auto TestCaseTypeAlias =
186 typeAliasDecl(hasName(
"::testing::TestCase")).bind(
"test-case");
188 typeLoc(loc(qualType(typedefType(hasDeclaration(TestCaseTypeAlias)))),
189 unless(hasAncestor(decl(isImplicit()))), LocationFilter)
193 usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(TestCaseTypeAlias)))
197 typeLoc(loc(usingType(hasUnderlyingType(
198 typedefType(hasDeclaration(TestCaseTypeAlias))))),
199 unless(hasAncestor(decl(isImplicit()))), LocationFilter)
205 static const llvm::StringMap<llvm::StringRef> ReplacementMap = {
206 {
"SetUpTestCase",
"SetUpTestSuite"},
207 {
"TearDownTestCase",
"TearDownTestSuite"},
208 {
"test_case_name",
"test_suite_name"},
209 {
"OnTestCaseStart",
"OnTestSuiteStart"},
210 {
"OnTestCaseEnd",
"OnTestSuiteEnd"},
211 {
"current_test_case",
"current_test_suite"},
212 {
"successful_test_case_count",
"successful_test_suite_count"},
213 {
"failed_test_case_count",
"failed_test_suite_count"},
214 {
"total_test_case_count",
"total_test_suite_count"},
215 {
"test_case_to_run_count",
"test_suite_to_run_count"},
216 {
"GetTestCase",
"GetTestSuite"}};
218 if (
const auto MappingIt = ReplacementMap.find(CurrentName);
219 MappingIt != ReplacementMap.end())
220 return MappingIt->second;
222 llvm_unreachable(
"Unexpected function name");
225template <
typename NodeType>
227 const MatchFinder::MatchResult &Result) {
228 return !match(isInTemplateInstantiation(), Node, *Result.Context).empty();
231template <
typename NodeType>
233 const MatchFinder::MatchResult &Result) {
234 const internal::Matcher<NodeType> IsInsideTemplate =
235 hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));
236 return !match(IsInsideTemplate, Node, *Result.Context).empty();
241 llvm::StringRef ReplacementMethod) {
242 const auto *Class = Result.Nodes.getNodeAs<CXXRecordDecl>(
"class");
243 return !match(cxxRecordDecl(
244 unless(isExpansionInFileMatching(
245 "gtest/gtest(-typed-test)?\\.h$")),
246 hasMethod(cxxMethodDecl(hasName(ReplacementMethod)))),
247 *Class, *Result.Context)
251static CharSourceRange
253 if (
const auto *Using = Result.Nodes.getNodeAs<UsingDecl>(
"using")) {
254 return CharSourceRange::getTokenRange(
255 Using->getNameInfo().getSourceRange());
257 TypeLoc TL = *Result.Nodes.getNodeAs<TypeLoc>(
"typeloc");
258 if (
auto QTL = TL.getAs<QualifiedTypeLoc>())
259 TL = QTL.getUnqualifiedLoc();
261 if (
auto TTL = TL.getAs<TypedefTypeLoc>())
262 return CharSourceRange::getTokenRange(TTL.getNameLoc());
263 return CharSourceRange::getTokenRange(TL.castAs<UsingTypeLoc>().getNameLoc());
267 llvm::StringRef ReplacementText;
268 CharSourceRange ReplacementRange;
269 if (
const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>(
"method")) {
272 bool IsInInstantiation =
false;
273 bool IsInTemplate =
false;
275 if (
const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>(
"call")) {
276 const auto *Callee = llvm::cast<MemberExpr>(Call->getCallee());
277 ReplacementRange = CharSourceRange::getTokenRange(Callee->getMemberLoc(),
278 Callee->getMemberLoc());
281 }
else if (
const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>(
"ref")) {
283 CharSourceRange::getTokenRange(Ref->getNameInfo().getSourceRange());
286 }
else if (
const auto *Using = Result.Nodes.getNodeAs<UsingDecl>(
"using")) {
288 CharSourceRange::getTokenRange(Using->getNameInfo().getSourceRange());
296 ReplacementRange = CharSourceRange::getTokenRange(
297 Method->getNameInfo().getSourceRange());
307 if (IsInInstantiation) {
308 if (!MatchedTemplateLocations.contains(ReplacementRange.getBegin())) {
322 MatchedTemplateLocations.insert(ReplacementRange.getBegin());
331 assert(Result.Nodes.getNodeAs<TypeAliasDecl>(
"test-case") !=
nullptr);
332 ReplacementText =
"TestSuite";
340 const DiagnosticBuilder Diag =
343 ReplacementRange = Lexer::makeFileCharRange(
344 ReplacementRange, *Result.SourceManager, Result.Context->getLangOpts());
345 if (ReplacementRange.isInvalid())
350 Diag << FixItHint::CreateReplacement(ReplacementRange, ReplacementText);
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
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)