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) {
35 if (MacroName == Mapping.first)
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,
93 SourceLocation Loc, CheckAction Action) {
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 TypeLoc TL = *Result.Nodes.getNodeAs<TypeLoc>(
"typeloc");
261 if (
auto QTL = TL.getAs<QualifiedTypeLoc>())
262 TL = QTL.getUnqualifiedLoc();
264 if (
auto TTL = TL.getAs<TypedefTypeLoc>())
265 return CharSourceRange::getTokenRange(TTL.getNameLoc());
266 return CharSourceRange::getTokenRange(TL.castAs<UsingTypeLoc>().getNameLoc());
270 llvm::StringRef ReplacementText;
271 CharSourceRange ReplacementRange;
272 if (
const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>(
"method")) {
275 bool IsInInstantiation =
false;
276 bool IsInTemplate =
false;
278 if (
const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>(
"call")) {
279 const auto *Callee = llvm::cast<MemberExpr>(Call->getCallee());
280 ReplacementRange = CharSourceRange::getTokenRange(Callee->getMemberLoc(),
281 Callee->getMemberLoc());
284 }
else if (
const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>(
"ref")) {
286 CharSourceRange::getTokenRange(Ref->getNameInfo().getSourceRange());
289 }
else if (
const auto *Using = Result.Nodes.getNodeAs<UsingDecl>(
"using")) {
291 CharSourceRange::getTokenRange(Using->getNameInfo().getSourceRange());
299 ReplacementRange = CharSourceRange::getTokenRange(
300 Method->getNameInfo().getSourceRange());
310 if (IsInInstantiation) {
311 if (!MatchedTemplateLocations.contains(ReplacementRange.getBegin())) {
325 MatchedTemplateLocations.insert(ReplacementRange.getBegin());
334 assert(Result.Nodes.getNodeAs<TypeAliasDecl>(
"test-case") !=
nullptr);
335 ReplacementText =
"TestSuite";
343 DiagnosticBuilder Diag =
346 ReplacementRange = Lexer::makeFileCharRange(
347 ReplacementRange, *Result.SourceManager, Result.Context->getLangOpts());
348 if (ReplacementRange.isInvalid())
353 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)