UpgradeGoogletestCaseCheck.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. //===--- UpgradeGoogletestCaseCheck.cpp - clang-tidy ----------------------===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. #include "UpgradeGoogletestCaseCheck.h"
  9. #include "clang/AST/ASTContext.h"
  10. #include "clang/ASTMatchers/ASTMatchFinder.h"
  11. #include "clang/Lex/PPCallbacks.h"
  12. #include "clang/Lex/Preprocessor.h"
  13. #include <optional>
  14. using namespace clang::ast_matchers;
  15. namespace clang::tidy::google {
  16. static const llvm::StringRef RenameCaseToSuiteMessage =
  17. "Google Test APIs named with 'case' are deprecated; use equivalent APIs "
  18. "named with 'suite'";
  19. static std::optional<llvm::StringRef>
  20. getNewMacroName(llvm::StringRef MacroName) {
  21. std::pair<llvm::StringRef, llvm::StringRef> ReplacementMap[] = {
  22. {"TYPED_TEST_CASE", "TYPED_TEST_SUITE"},
  23. {"TYPED_TEST_CASE_P", "TYPED_TEST_SUITE_P"},
  24. {"REGISTER_TYPED_TEST_CASE_P", "REGISTER_TYPED_TEST_SUITE_P"},
  25. {"INSTANTIATE_TYPED_TEST_CASE_P", "INSTANTIATE_TYPED_TEST_SUITE_P"},
  26. {"INSTANTIATE_TEST_CASE_P", "INSTANTIATE_TEST_SUITE_P"},
  27. };
  28. for (auto &Mapping : ReplacementMap) {
  29. if (MacroName == Mapping.first)
  30. return Mapping.second;
  31. }
  32. return std::nullopt;
  33. }
  34. namespace {
  35. class UpgradeGoogletestCasePPCallback : public PPCallbacks {
  36. public:
  37. UpgradeGoogletestCasePPCallback(UpgradeGoogletestCaseCheck *Check,
  38. Preprocessor *PP)
  39. : ReplacementFound(false), Check(Check), PP(PP) {}
  40. void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
  41. SourceRange Range, const MacroArgs *) override {
  42. macroUsed(MacroNameTok, MD, Range.getBegin(), CheckAction::Rename);
  43. }
  44. void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD,
  45. const MacroDirective *Undef) override {
  46. if (Undef != nullptr)
  47. macroUsed(MacroNameTok, MD, Undef->getLocation(), CheckAction::Warn);
  48. }
  49. void MacroDefined(const Token &MacroNameTok,
  50. const MacroDirective *MD) override {
  51. if (!ReplacementFound && MD != nullptr) {
  52. // We check if the newly defined macro is one of the target replacements.
  53. // This ensures that the check creates warnings only if it is including a
  54. // recent enough version of Google Test.
  55. llvm::StringRef FileName = PP->getSourceManager().getFilename(
  56. MD->getMacroInfo()->getDefinitionLoc());
  57. ReplacementFound = FileName.endswith("gtest/gtest-typed-test.h") &&
  58. PP->getSpelling(MacroNameTok) == "TYPED_TEST_SUITE";
  59. }
  60. }
  61. void Defined(const Token &MacroNameTok, const MacroDefinition &MD,
  62. SourceRange Range) override {
  63. macroUsed(MacroNameTok, MD, Range.getBegin(), CheckAction::Warn);
  64. }
  65. void Ifdef(SourceLocation Loc, const Token &MacroNameTok,
  66. const MacroDefinition &MD) override {
  67. macroUsed(MacroNameTok, MD, Loc, CheckAction::Warn);
  68. }
  69. void Ifndef(SourceLocation Loc, const Token &MacroNameTok,
  70. const MacroDefinition &MD) override {
  71. macroUsed(MacroNameTok, MD, Loc, CheckAction::Warn);
  72. }
  73. private:
  74. enum class CheckAction { Warn, Rename };
  75. void macroUsed(const clang::Token &MacroNameTok, const MacroDefinition &MD,
  76. SourceLocation Loc, CheckAction Action) {
  77. if (!ReplacementFound)
  78. return;
  79. std::string Name = PP->getSpelling(MacroNameTok);
  80. std::optional<llvm::StringRef> Replacement = getNewMacroName(Name);
  81. if (!Replacement)
  82. return;
  83. llvm::StringRef FileName = PP->getSourceManager().getFilename(
  84. MD.getMacroInfo()->getDefinitionLoc());
  85. if (!FileName.endswith("gtest/gtest-typed-test.h"))
  86. return;
  87. DiagnosticBuilder Diag = Check->diag(Loc, RenameCaseToSuiteMessage);
  88. if (Action == CheckAction::Rename)
  89. Diag << FixItHint::CreateReplacement(
  90. CharSourceRange::getTokenRange(Loc, Loc), *Replacement);
  91. }
  92. bool ReplacementFound;
  93. UpgradeGoogletestCaseCheck *Check;
  94. Preprocessor *PP;
  95. };
  96. } // namespace
  97. void UpgradeGoogletestCaseCheck::registerPPCallbacks(const SourceManager &,
  98. Preprocessor *PP,
  99. Preprocessor *) {
  100. PP->addPPCallbacks(
  101. std::make_unique<UpgradeGoogletestCasePPCallback>(this, PP));
  102. }
  103. void UpgradeGoogletestCaseCheck::registerMatchers(MatchFinder *Finder) {
  104. auto LocationFilter =
  105. unless(isExpansionInFileMatching("gtest/gtest(-typed-test)?\\.h$"));
  106. // Matchers for the member functions that are being renamed. In each matched
  107. // Google Test class, we check for the existence of one new method name. This
  108. // makes sure the check gives warnings only if the included version of Google
  109. // Test is recent enough.
  110. auto Methods =
  111. cxxMethodDecl(
  112. anyOf(
  113. cxxMethodDecl(
  114. hasAnyName("SetUpTestCase", "TearDownTestCase"),
  115. ofClass(
  116. cxxRecordDecl(isSameOrDerivedFrom(cxxRecordDecl(
  117. hasName("::testing::Test"),
  118. hasMethod(hasName("SetUpTestSuite")))))
  119. .bind("class"))),
  120. cxxMethodDecl(
  121. hasName("test_case_name"),
  122. ofClass(
  123. cxxRecordDecl(isSameOrDerivedFrom(cxxRecordDecl(
  124. hasName("::testing::TestInfo"),
  125. hasMethod(hasName("test_suite_name")))))
  126. .bind("class"))),
  127. cxxMethodDecl(
  128. hasAnyName("OnTestCaseStart", "OnTestCaseEnd"),
  129. ofClass(cxxRecordDecl(
  130. isSameOrDerivedFrom(cxxRecordDecl(
  131. hasName("::testing::TestEventListener"),
  132. hasMethod(hasName("OnTestSuiteStart")))))
  133. .bind("class"))),
  134. cxxMethodDecl(
  135. hasAnyName("current_test_case", "successful_test_case_count",
  136. "failed_test_case_count", "total_test_case_count",
  137. "test_case_to_run_count", "GetTestCase"),
  138. ofClass(cxxRecordDecl(
  139. isSameOrDerivedFrom(cxxRecordDecl(
  140. hasName("::testing::UnitTest"),
  141. hasMethod(hasName("current_test_suite")))))
  142. .bind("class")))))
  143. .bind("method");
  144. Finder->addMatcher(expr(anyOf(callExpr(callee(Methods)).bind("call"),
  145. declRefExpr(to(Methods)).bind("ref")),
  146. LocationFilter),
  147. this);
  148. Finder->addMatcher(
  149. usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(Methods)), LocationFilter)
  150. .bind("using"),
  151. this);
  152. Finder->addMatcher(cxxMethodDecl(Methods, LocationFilter), this);
  153. // Matchers for `TestCase` -> `TestSuite`. The fact that `TestCase` is an
  154. // alias and not a class declaration ensures we only match with a recent
  155. // enough version of Google Test.
  156. auto TestCaseTypeAlias =
  157. typeAliasDecl(hasName("::testing::TestCase")).bind("test-case");
  158. Finder->addMatcher(
  159. typeLoc(loc(qualType(typedefType(hasDeclaration(TestCaseTypeAlias)))),
  160. unless(hasAncestor(decl(isImplicit()))), LocationFilter)
  161. .bind("typeloc"),
  162. this);
  163. Finder->addMatcher(
  164. usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(TestCaseTypeAlias)))
  165. .bind("using"),
  166. this);
  167. Finder->addMatcher(
  168. typeLoc(loc(usingType(hasUnderlyingType(
  169. typedefType(hasDeclaration(TestCaseTypeAlias))))),
  170. unless(hasAncestor(decl(isImplicit()))), LocationFilter)
  171. .bind("typeloc"),
  172. this);
  173. }
  174. static llvm::StringRef getNewMethodName(llvm::StringRef CurrentName) {
  175. std::pair<llvm::StringRef, llvm::StringRef> ReplacementMap[] = {
  176. {"SetUpTestCase", "SetUpTestSuite"},
  177. {"TearDownTestCase", "TearDownTestSuite"},
  178. {"test_case_name", "test_suite_name"},
  179. {"OnTestCaseStart", "OnTestSuiteStart"},
  180. {"OnTestCaseEnd", "OnTestSuiteEnd"},
  181. {"current_test_case", "current_test_suite"},
  182. {"successful_test_case_count", "successful_test_suite_count"},
  183. {"failed_test_case_count", "failed_test_suite_count"},
  184. {"total_test_case_count", "total_test_suite_count"},
  185. {"test_case_to_run_count", "test_suite_to_run_count"},
  186. {"GetTestCase", "GetTestSuite"}};
  187. for (auto &Mapping : ReplacementMap) {
  188. if (CurrentName == Mapping.first)
  189. return Mapping.second;
  190. }
  191. llvm_unreachable("Unexpected function name");
  192. }
  193. template <typename NodeType>
  194. static bool isInInstantiation(const NodeType &Node,
  195. const MatchFinder::MatchResult &Result) {
  196. return !match(isInTemplateInstantiation(), Node, *Result.Context).empty();
  197. }
  198. template <typename NodeType>
  199. static bool isInTemplate(const NodeType &Node,
  200. const MatchFinder::MatchResult &Result) {
  201. internal::Matcher<NodeType> IsInsideTemplate =
  202. hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));
  203. return !match(IsInsideTemplate, Node, *Result.Context).empty();
  204. }
  205. static bool
  206. derivedTypeHasReplacementMethod(const MatchFinder::MatchResult &Result,
  207. llvm::StringRef ReplacementMethod) {
  208. const auto *Class = Result.Nodes.getNodeAs<CXXRecordDecl>("class");
  209. return !match(cxxRecordDecl(
  210. unless(isExpansionInFileMatching(
  211. "gtest/gtest(-typed-test)?\\.h$")),
  212. hasMethod(cxxMethodDecl(hasName(ReplacementMethod)))),
  213. *Class, *Result.Context)
  214. .empty();
  215. }
  216. static CharSourceRange
  217. getAliasNameRange(const MatchFinder::MatchResult &Result) {
  218. if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {
  219. return CharSourceRange::getTokenRange(
  220. Using->getNameInfo().getSourceRange());
  221. }
  222. return CharSourceRange::getTokenRange(
  223. Result.Nodes.getNodeAs<TypeLoc>("typeloc")->getSourceRange());
  224. }
  225. void UpgradeGoogletestCaseCheck::check(const MatchFinder::MatchResult &Result) {
  226. llvm::StringRef ReplacementText;
  227. CharSourceRange ReplacementRange;
  228. if (const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>("method")) {
  229. ReplacementText = getNewMethodName(Method->getName());
  230. bool IsInInstantiation;
  231. bool IsInTemplate;
  232. bool AddFix = true;
  233. if (const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call")) {
  234. const auto *Callee = llvm::cast<MemberExpr>(Call->getCallee());
  235. ReplacementRange = CharSourceRange::getTokenRange(Callee->getMemberLoc(),
  236. Callee->getMemberLoc());
  237. IsInInstantiation = isInInstantiation(*Call, Result);
  238. IsInTemplate = isInTemplate<Stmt>(*Call, Result);
  239. } else if (const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("ref")) {
  240. ReplacementRange =
  241. CharSourceRange::getTokenRange(Ref->getNameInfo().getSourceRange());
  242. IsInInstantiation = isInInstantiation(*Ref, Result);
  243. IsInTemplate = isInTemplate<Stmt>(*Ref, Result);
  244. } else if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {
  245. ReplacementRange =
  246. CharSourceRange::getTokenRange(Using->getNameInfo().getSourceRange());
  247. IsInInstantiation = isInInstantiation(*Using, Result);
  248. IsInTemplate = isInTemplate<Decl>(*Using, Result);
  249. } else {
  250. // This branch means we have matched a function declaration / definition
  251. // either for a function from googletest or for a function in a derived
  252. // class.
  253. ReplacementRange = CharSourceRange::getTokenRange(
  254. Method->getNameInfo().getSourceRange());
  255. IsInInstantiation = isInInstantiation(*Method, Result);
  256. IsInTemplate = isInTemplate<Decl>(*Method, Result);
  257. // If the type of the matched method is strictly derived from a googletest
  258. // type and has both the old and new member function names, then we cannot
  259. // safely rename (or delete) the old name version.
  260. AddFix = !derivedTypeHasReplacementMethod(Result, ReplacementText);
  261. }
  262. if (IsInInstantiation) {
  263. if (MatchedTemplateLocations.count(ReplacementRange.getBegin()) == 0) {
  264. // For each location matched in a template instantiation, we check if
  265. // the location can also be found in `MatchedTemplateLocations`. If it
  266. // is not found, that means the expression did not create a match
  267. // without the instantiation and depends on template parameters. A
  268. // manual fix is probably required so we provide only a warning.
  269. diag(ReplacementRange.getBegin(), RenameCaseToSuiteMessage);
  270. }
  271. return;
  272. }
  273. if (IsInTemplate) {
  274. // We gather source locations from template matches not in template
  275. // instantiations for future matches.
  276. MatchedTemplateLocations.insert(ReplacementRange.getBegin());
  277. }
  278. if (!AddFix) {
  279. diag(ReplacementRange.getBegin(), RenameCaseToSuiteMessage);
  280. return;
  281. }
  282. } else {
  283. // This is a match for `TestCase` to `TestSuite` refactoring.
  284. assert(Result.Nodes.getNodeAs<TypeAliasDecl>("test-case") != nullptr);
  285. ReplacementText = "TestSuite";
  286. ReplacementRange = getAliasNameRange(Result);
  287. // We do not need to keep track of template instantiations for this branch,
  288. // because we are matching a `TypeLoc` for the alias declaration. Templates
  289. // will only be instantiated with the true type name, `TestSuite`.
  290. }
  291. DiagnosticBuilder Diag =
  292. diag(ReplacementRange.getBegin(), RenameCaseToSuiteMessage);
  293. ReplacementRange = Lexer::makeFileCharRange(
  294. ReplacementRange, *Result.SourceManager, Result.Context->getLangOpts());
  295. if (ReplacementRange.isInvalid())
  296. // An invalid source range likely means we are inside a macro body. A manual
  297. // fix is likely needed so we do not create a fix-it hint.
  298. return;
  299. Diag << FixItHint::CreateReplacement(ReplacementRange, ReplacementText);
  300. }
  301. } // namespace clang::tidy::google