10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/PPCallbacks.h"
13 #include "clang/Lex/Preprocessor.h"
22 "Google Test APIs named with 'case' are deprecated; use equivalent APIs "
25 static llvm::Optional<llvm::StringRef>
27 std::pair<llvm::StringRef, llvm::StringRef> ReplacementMap[] = {
28 {
"TYPED_TEST_CASE",
"TYPED_TEST_SUITE"},
29 {
"TYPED_TEST_CASE_P",
"TYPED_TEST_SUITE_P"},
30 {
"REGISTER_TYPED_TEST_CASE_P",
"REGISTER_TYPED_TEST_SUITE_P"},
31 {
"INSTANTIATE_TYPED_TEST_CASE_P",
"INSTANTIATE_TYPED_TEST_SUITE_P"},
32 {
"INSTANTIATE_TEST_CASE_P",
"INSTANTIATE_TEST_SUITE_P"},
35 for (
auto &Mapping : ReplacementMap) {
36 if (MacroName == Mapping.first)
37 return Mapping.second;
45 class UpgradeGoogletestCasePPCallback :
public PPCallbacks {
47 UpgradeGoogletestCasePPCallback(UpgradeGoogletestCaseCheck *Check,
49 : ReplacementFound(false), Check(Check), PP(PP) {}
51 void MacroExpands(
const Token &MacroNameTok,
const MacroDefinition &
MD,
52 SourceRange
Range,
const MacroArgs *)
override {
53 macroUsed(MacroNameTok,
MD,
Range.getBegin(), CheckAction::Rename);
56 void MacroUndefined(
const Token &MacroNameTok,
const MacroDefinition &
MD,
57 const MacroDirective *Undef)
override {
59 macroUsed(MacroNameTok,
MD, Undef->getLocation(), CheckAction::Warn);
62 void MacroDefined(
const Token &MacroNameTok,
63 const MacroDirective *
MD)
override {
64 if (!ReplacementFound &&
MD !=
nullptr) {
68 llvm::StringRef
FileName = PP->getSourceManager().getFilename(
69 MD->getMacroInfo()->getDefinitionLoc());
70 ReplacementFound =
FileName.endswith(
"gtest/gtest-typed-test.h") &&
71 PP->getSpelling(MacroNameTok) ==
"TYPED_TEST_SUITE";
75 void Defined(
const Token &MacroNameTok,
const MacroDefinition &
MD,
76 SourceRange
Range)
override {
77 macroUsed(MacroNameTok,
MD,
Range.getBegin(), CheckAction::Warn);
80 void Ifdef(SourceLocation
Loc,
const Token &MacroNameTok,
81 const MacroDefinition &
MD)
override {
82 macroUsed(MacroNameTok,
MD,
Loc, CheckAction::Warn);
85 void Ifndef(SourceLocation
Loc,
const Token &MacroNameTok,
86 const MacroDefinition &
MD)
override {
87 macroUsed(MacroNameTok,
MD,
Loc, CheckAction::Warn);
91 enum class CheckAction { Warn, Rename };
93 void macroUsed(
const clang::Token &MacroNameTok,
const MacroDefinition &
MD,
95 if (!ReplacementFound)
98 std::string
Name = PP->getSpelling(MacroNameTok);
104 llvm::StringRef
FileName = PP->getSourceManager().getFilename(
105 MD.getMacroInfo()->getDefinitionLoc());
106 if (!
FileName.endswith(
"gtest/gtest-typed-test.h"))
111 if (
Action == CheckAction::Rename)
112 Diag << FixItHint::CreateReplacement(
113 CharSourceRange::getTokenRange(
Loc,
Loc), *Replacement);
116 bool ReplacementFound;
117 UpgradeGoogletestCaseCheck *Check;
123 void UpgradeGoogletestCaseCheck::registerPPCallbacks(
const SourceManager &,
127 std::make_unique<UpgradeGoogletestCasePPCallback>(
this, PP));
130 void UpgradeGoogletestCaseCheck::registerMatchers(MatchFinder *Finder) {
131 auto LocationFilter =
132 unless(isExpansionInFileMatching(
"gtest/gtest(-typed-test)?\\.h$"));
142 hasAnyName(
"SetUpTestCase",
"TearDownTestCase"),
144 cxxRecordDecl(isSameOrDerivedFrom(cxxRecordDecl(
145 hasName(
"::testing::Test"),
146 hasMethod(hasName(
"SetUpTestSuite")))))
149 hasName(
"test_case_name"),
151 cxxRecordDecl(isSameOrDerivedFrom(cxxRecordDecl(
152 hasName(
"::testing::TestInfo"),
153 hasMethod(hasName(
"test_suite_name")))))
156 hasAnyName(
"OnTestCaseStart",
"OnTestCaseEnd"),
157 ofClass(cxxRecordDecl(
158 isSameOrDerivedFrom(cxxRecordDecl(
159 hasName(
"::testing::TestEventListener"),
160 hasMethod(hasName(
"OnTestSuiteStart")))))
163 hasAnyName(
"current_test_case",
"successful_test_case_count",
164 "failed_test_case_count",
"total_test_case_count",
165 "test_case_to_run_count",
"GetTestCase"),
166 ofClass(cxxRecordDecl(
167 isSameOrDerivedFrom(cxxRecordDecl(
168 hasName(
"::testing::UnitTest"),
169 hasMethod(hasName(
"current_test_suite")))))
173 Finder->addMatcher(expr(anyOf(callExpr(callee(Methods)).bind(
"call"),
174 declRefExpr(to(Methods)).bind(
"ref")),
179 usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(Methods)), LocationFilter)
183 Finder->addMatcher(cxxMethodDecl(Methods, LocationFilter),
this);
188 auto TestCaseTypeAlias =
189 typeAliasDecl(hasName(
"::testing::TestCase")).bind(
"test-case");
191 typeLoc(loc(qualType(typedefType(hasDeclaration(TestCaseTypeAlias)))),
192 unless(hasAncestor(decl(isImplicit()))), LocationFilter)
196 usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(TestCaseTypeAlias)))
202 std::pair<llvm::StringRef, llvm::StringRef> ReplacementMap[] = {
203 {
"SetUpTestCase",
"SetUpTestSuite"},
204 {
"TearDownTestCase",
"TearDownTestSuite"},
205 {
"test_case_name",
"test_suite_name"},
206 {
"OnTestCaseStart",
"OnTestSuiteStart"},
207 {
"OnTestCaseEnd",
"OnTestSuiteEnd"},
208 {
"current_test_case",
"current_test_suite"},
209 {
"successful_test_case_count",
"successful_test_suite_count"},
210 {
"failed_test_case_count",
"failed_test_suite_count"},
211 {
"total_test_case_count",
"total_test_suite_count"},
212 {
"test_case_to_run_count",
"test_suite_to_run_count"},
213 {
"GetTestCase",
"GetTestSuite"}};
215 for (
auto &Mapping : ReplacementMap) {
216 if (CurrentName == Mapping.first)
217 return Mapping.second;
220 llvm_unreachable(
"Unexpected function name");
223 template <
typename NodeType>
225 const MatchFinder::MatchResult &Result) {
226 return !
match(isInTemplateInstantiation(), Node, *Result.Context).empty();
229 template <
typename NodeType>
231 const MatchFinder::MatchResult &Result) {
232 internal::Matcher<NodeType> IsInsideTemplate =
233 hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));
234 return !
match(IsInsideTemplate, Node, *Result.Context).empty();
239 llvm::StringRef ReplacementMethod) {
240 const auto *Class = Result.Nodes.getNodeAs<CXXRecordDecl>(
"class");
241 return !
match(cxxRecordDecl(
242 unless(isExpansionInFileMatching(
243 "gtest/gtest(-typed-test)?\\.h$")),
244 hasMethod(cxxMethodDecl(hasName(ReplacementMethod)))),
245 *Class, *Result.Context)
249 static CharSourceRange
251 if (
const auto *Using = Result.Nodes.getNodeAs<UsingDecl>(
"using")) {
252 return CharSourceRange::getTokenRange(
253 Using->getNameInfo().getSourceRange());
255 return CharSourceRange::getTokenRange(
256 Result.Nodes.getNodeAs<TypeLoc>(
"typeloc")->getSourceRange());
259 void UpgradeGoogletestCaseCheck::check(
const MatchFinder::MatchResult &Result) {
260 llvm::StringRef ReplacementText;
261 CharSourceRange ReplacementRange;
262 if (
const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>(
"method")) {
265 bool IsInInstantiation;
268 if (
const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>(
"call")) {
269 const auto *Callee = llvm::cast<MemberExpr>(Call->getCallee());
270 ReplacementRange = CharSourceRange::getTokenRange(Callee->getMemberLoc(),
271 Callee->getMemberLoc());
273 IsInTemplate = isInTemplate<Stmt>(*Call, Result);
274 }
else if (
const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>(
"ref")) {
276 CharSourceRange::getTokenRange(Ref->getNameInfo().getSourceRange());
278 IsInTemplate = isInTemplate<Stmt>(*Ref, Result);
279 }
else if (
const auto *Using = Result.Nodes.getNodeAs<UsingDecl>(
"using")) {
281 CharSourceRange::getTokenRange(Using->getNameInfo().getSourceRange());
283 IsInTemplate = isInTemplate<Decl>(*Using, Result);
289 ReplacementRange = CharSourceRange::getTokenRange(
290 Method->getNameInfo().getSourceRange());
292 IsInTemplate = isInTemplate<Decl>(*Method, Result);
300 if (IsInInstantiation) {
301 if (MatchedTemplateLocations.count(
302 ReplacementRange.getBegin().getRawEncoding()) == 0) {
316 MatchedTemplateLocations.insert(
317 ReplacementRange.getBegin().getRawEncoding());
326 assert(Result.Nodes.getNodeAs<TypeAliasDecl>(
"test-case") !=
nullptr);
327 ReplacementText =
"TestSuite";
335 DiagnosticBuilder Diag =
338 ReplacementRange = Lexer::makeFileCharRange(
339 ReplacementRange, *Result.SourceManager, Result.Context->getLangOpts());
340 if (ReplacementRange.isInvalid())
345 Diag << FixItHint::CreateReplacement(ReplacementRange, ReplacementText);