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
9 #include "UpgradeGoogletestCaseCheck.h"
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;
115 UpgradeGoogletestCaseCheck *Check;
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")) {
256 return CharSourceRange::getTokenRange(
257 Using->getNameInfo().getSourceRange());
258 }
259 return CharSourceRange::getTokenRange(
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
Loc
SourceLocation Loc
&#39;#&#39; location in the include directive
Definition: IncludeOrderCheck.cpp:37
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::google::getNewMethodName
static llvm::StringRef getNewMethodName(llvm::StringRef CurrentName)
Definition: UpgradeGoogletestCaseCheck.cpp:205
clang::tidy::google::isInTemplate
static bool isInTemplate(const NodeType &Node, const MatchFinder::MatchResult &Result)
Definition: UpgradeGoogletestCaseCheck.cpp:234
clang::tidy::google::isInInstantiation
static bool isInInstantiation(const NodeType &Node, const MatchFinder::MatchResult &Result)
Definition: UpgradeGoogletestCaseCheck.cpp:228
clang::tidy::google::RenameCaseToSuiteMessage
static const llvm::StringRef RenameCaseToSuiteMessage
Definition: UpgradeGoogletestCaseCheck.cpp:19
clang::clangd::match
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
Definition: TestIndex.cpp:94
clang::tidy::google::getNewMacroName
static llvm::Optional< llvm::StringRef > getNewMacroName(llvm::StringRef MacroName)
Definition: UpgradeGoogletestCaseCheck.cpp:24
Action
llvm::unique_function< void()> Action
Definition: TUScheduler.cpp:234
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:27
FileName
PathRef FileName
Definition: CodeComplete.cpp:1018
clang::tidy::google::UpgradeGoogletestCaseCheck
Finds uses of deprecated Googletest APIs with names containing "case" and replaces them with equivale...
Definition: UpgradeGoogletestCaseCheck.h:23
clang::clangd::getTokenRange
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:227
UpgradeGoogletestCaseCheck.h
clang::tidy::google::getAliasNameRange
static CharSourceRange getAliasNameRange(const MatchFinder::MatchResult &Result)
Definition: UpgradeGoogletestCaseCheck.cpp:254
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::doc::MD
static GeneratorRegistry::Add< MDGenerator > MD(MDGenerator::Format, "Generator for MD output.")
Range
CharSourceRange Range
SourceRange for the file name.
Definition: IncludeOrderCheck.cpp:38
clang::tidy::google::derivedTypeHasReplacementMethod
static bool derivedTypeHasReplacementMethod(const MatchFinder::MatchResult &Result, llvm::StringRef ReplacementMethod)
Definition: UpgradeGoogletestCaseCheck.cpp:242
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidyCheck.cpp:21