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