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