summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra/clang-tidy
diff options
context:
space:
mode:
authorEric Fiselier <eric@efcs.ca>2019-07-29 21:38:56 +0000
committerEric Fiselier <eric@efcs.ca>2019-07-29 21:38:56 +0000
commita2a6f853662172a04bd3e2a444dd606d32b15bba (patch)
tree53a427e68b7b02ab74b6a822ec03988b776712ce /clang-tools-extra/clang-tidy
parent9a5fbc816341ccb92ddde90dfb16f411c1cff9c8 (diff)
downloadbcm5719-llvm-a2a6f853662172a04bd3e2a444dd606d32b15bba.tar.gz
bcm5719-llvm-a2a6f853662172a04bd3e2a444dd606d32b15bba.zip
[clang-tidy]: Google: new check 'google-upgrade-googletest-case'
Introduce a new check to upgrade user code based on API changes in Googletest. The check finds uses of old Googletest APIs with "case" in their name and replaces them with the new APIs named with "suite". Patch by Alex Strelnikov (strel@google.com) Reviewed as D62977. llvm-svn: 367263
Diffstat (limited to 'clang-tools-extra/clang-tidy')
-rw-r--r--clang-tools-extra/clang-tidy/google/CMakeLists.txt1
-rw-r--r--clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp3
-rw-r--r--clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.cpp354
-rw-r--r--clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.h40
4 files changed, 398 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/google/CMakeLists.txt b/clang-tools-extra/clang-tidy/google/CMakeLists.txt
index b78088cf06b..08368932da5 100644
--- a/clang-tools-extra/clang-tidy/google/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/google/CMakeLists.txt
@@ -17,6 +17,7 @@ add_clang_library(clangTidyGoogleModule
OverloadedUnaryAndCheck.cpp
TodoCommentCheck.cpp
UnnamedNamespaceInHeaderCheck.cpp
+ UpgradeGoogletestCaseCheck.cpp
UsingNamespaceDirectiveCheck.cpp
LINK_LIBS
diff --git a/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp b/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp
index 1e3410fb8a5..4c6b791f8cd 100644
--- a/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp
@@ -27,6 +27,7 @@
#include "OverloadedUnaryAndCheck.h"
#include "TodoCommentCheck.h"
#include "UnnamedNamespaceInHeaderCheck.h"
+#include "UpgradeGoogletestCaseCheck.h"
#include "UsingNamespaceDirectiveCheck.h"
using namespace clang::ast_matchers;
@@ -79,6 +80,8 @@ class GoogleModule : public ClangTidyModule {
CheckFactories
.registerCheck<clang::tidy::readability::NamespaceCommentCheck>(
"google-readability-namespace-comments");
+ CheckFactories.registerCheck<UpgradeGoogletestCaseCheck>(
+ "google-upgrade-googletest-case");
}
ClangTidyOptions getModuleOptions() override {
diff --git a/clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.cpp b/clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.cpp
new file mode 100644
index 00000000000..955a888139a
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.cpp
@@ -0,0 +1,354 @@
+//===--- UpgradeGoogletestCaseCheck.cpp - clang-tidy ----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UpgradeGoogletestCaseCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace google {
+
+static const llvm::StringRef RenameCaseToSuiteMessage =
+ "Google Test APIs named with 'case' are deprecated; use equivalent APIs "
+ "named with 'suite'";
+
+static llvm::Optional<llvm::StringRef>
+getNewMacroName(llvm::StringRef MacroName) {
+ std::pair<llvm::StringRef, llvm::StringRef> ReplacementMap[] = {
+ {"TYPED_TEST_CASE", "TYPED_TEST_SUITE"},
+ {"TYPED_TEST_CASE_P", "TYPED_TEST_SUITE_P"},
+ {"REGISTER_TYPED_TEST_CASE_P", "REGISTER_TYPED_TEST_SUITE_P"},
+ {"INSTANTIATE_TYPED_TEST_CASE_P", "INSTANTIATE_TYPED_TEST_SUITE_P"},
+ {"INSTANTIATE_TEST_CASE_P", "INSTANTIATE_TEST_SUITE_P"},
+ };
+
+ for (auto &Mapping : ReplacementMap) {
+ if (MacroName == Mapping.first)
+ return Mapping.second;
+ }
+
+ return llvm::None;
+}
+
+namespace {
+
+class UpgradeGoogletestCasePPCallback : public PPCallbacks {
+public:
+ UpgradeGoogletestCasePPCallback(UpgradeGoogletestCaseCheck *Check,
+ Preprocessor *PP)
+ : ReplacementFound(false), Check(Check), PP(PP) {}
+
+ void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
+ SourceRange Range, const MacroArgs *) override {
+ macroUsed(MacroNameTok, MD, Range.getBegin(), CheckAction::Rename);
+ }
+
+ void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD,
+ const MacroDirective *Undef) override {
+ if (Undef != nullptr)
+ macroUsed(MacroNameTok, MD, Undef->getLocation(), CheckAction::Warn);
+ }
+
+ void MacroDefined(const Token &MacroNameTok,
+ const MacroDirective *MD) override {
+ if (!ReplacementFound && MD != nullptr) {
+ // We check if the newly defined macro is one of the target replacements.
+ // This ensures that the check creates warnings only if it is including a
+ // recent enough version of Google Test.
+ llvm::StringRef FileName = PP->getSourceManager().getFilename(
+ MD->getMacroInfo()->getDefinitionLoc());
+ ReplacementFound = FileName.endswith("gtest/gtest-typed-test.h") &&
+ PP->getSpelling(MacroNameTok) == "TYPED_TEST_SUITE";
+ }
+ }
+
+ void Defined(const Token &MacroNameTok, const MacroDefinition &MD,
+ SourceRange Range) override {
+ macroUsed(MacroNameTok, MD, Range.getBegin(), CheckAction::Warn);
+ }
+
+ void Ifdef(SourceLocation Loc, const Token &MacroNameTok,
+ const MacroDefinition &MD) override {
+ macroUsed(MacroNameTok, MD, Loc, CheckAction::Warn);
+ }
+
+ void Ifndef(SourceLocation Loc, const Token &MacroNameTok,
+ const MacroDefinition &MD) override {
+ macroUsed(MacroNameTok, MD, Loc, CheckAction::Warn);
+ }
+
+private:
+ enum class CheckAction { Warn, Rename };
+
+ void macroUsed(const clang::Token &MacroNameTok, const MacroDefinition &MD,
+ SourceLocation Loc, CheckAction Action) {
+ if (!ReplacementFound)
+ return;
+
+ std::string Name = PP->getSpelling(MacroNameTok);
+
+ llvm::Optional<llvm::StringRef> Replacement = getNewMacroName(Name);
+ if (!Replacement)
+ return;
+
+ llvm::StringRef FileName = PP->getSourceManager().getFilename(
+ MD.getMacroInfo()->getDefinitionLoc());
+ if (!FileName.endswith("gtest/gtest-typed-test.h"))
+ return;
+
+ DiagnosticBuilder Diag = Check->diag(Loc, RenameCaseToSuiteMessage);
+
+ if (Action == CheckAction::Rename)
+ Diag << FixItHint::CreateReplacement(
+ CharSourceRange::getTokenRange(Loc, Loc), *Replacement);
+ }
+
+ bool ReplacementFound;
+ UpgradeGoogletestCaseCheck *Check;
+ Preprocessor *PP;
+};
+
+} // namespace
+
+void UpgradeGoogletestCaseCheck::registerPPCallbacks(const SourceManager &,
+ Preprocessor *PP,
+ Preprocessor *) {
+ if (!getLangOpts().CPlusPlus)
+ return;
+
+ PP->addPPCallbacks(
+ llvm::make_unique<UpgradeGoogletestCasePPCallback>(this, PP));
+}
+
+void UpgradeGoogletestCaseCheck::registerMatchers(MatchFinder *Finder) {
+ if (!getLangOpts().CPlusPlus)
+ return;
+
+ auto LocationFilter =
+ unless(isExpansionInFileMatching("gtest/gtest(-typed-test)?\\.h$"));
+
+ // Matchers for the member functions that are being renamed. In each matched
+ // Google Test class, we check for the existence of one new method name. This
+ // makes sure the check gives warnings only if the included version of Google
+ // Test is recent enough.
+ auto Methods =
+ cxxMethodDecl(
+ anyOf(
+ cxxMethodDecl(
+ hasAnyName("SetUpTestCase", "TearDownTestCase"),
+ ofClass(
+ cxxRecordDecl(isSameOrDerivedFrom(cxxRecordDecl(
+ hasName("::testing::Test"),
+ hasMethod(hasName("SetUpTestSuite")))))
+ .bind("class"))),
+ cxxMethodDecl(
+ hasName("test_case_name"),
+ ofClass(
+ cxxRecordDecl(isSameOrDerivedFrom(cxxRecordDecl(
+ hasName("::testing::TestInfo"),
+ hasMethod(hasName("test_suite_name")))))
+ .bind("class"))),
+ cxxMethodDecl(
+ hasAnyName("OnTestCaseStart", "OnTestCaseEnd"),
+ ofClass(cxxRecordDecl(
+ isSameOrDerivedFrom(cxxRecordDecl(
+ hasName("::testing::TestEventListener"),
+ hasMethod(hasName("OnTestSuiteStart")))))
+ .bind("class"))),
+ cxxMethodDecl(
+ hasAnyName("current_test_case", "successful_test_case_count",
+ "failed_test_case_count", "total_test_case_count",
+ "test_case_to_run_count", "GetTestCase"),
+ ofClass(cxxRecordDecl(
+ isSameOrDerivedFrom(cxxRecordDecl(
+ hasName("::testing::UnitTest"),
+ hasMethod(hasName("current_test_suite")))))
+ .bind("class")))))
+ .bind("method");
+
+ Finder->addMatcher(expr(anyOf(callExpr(callee(Methods)).bind("call"),
+ declRefExpr(to(Methods)).bind("ref")),
+ LocationFilter),
+ this);
+
+ Finder->addMatcher(
+ usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(Methods)), LocationFilter)
+ .bind("using"),
+ this);
+
+ Finder->addMatcher(cxxMethodDecl(Methods, LocationFilter), this);
+
+ // Matchers for `TestCase` -> `TestSuite`. The fact that `TestCase` is an
+ // alias and not a class declaration ensures we only match with a recent
+ // enough version of Google Test.
+ auto TestCaseTypeAlias =
+ typeAliasDecl(hasName("::testing::TestCase")).bind("test-case");
+ Finder->addMatcher(
+ typeLoc(loc(qualType(typedefType(hasDeclaration(TestCaseTypeAlias)))),
+ unless(hasAncestor(decl(isImplicit()))), LocationFilter)
+ .bind("typeloc"),
+ this);
+ Finder->addMatcher(
+ usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(TestCaseTypeAlias)))
+ .bind("using"),
+ this);
+}
+
+static llvm::StringRef getNewMethodName(llvm::StringRef CurrentName) {
+ std::pair<llvm::StringRef, llvm::StringRef> ReplacementMap[] = {
+ {"SetUpTestCase", "SetUpTestSuite"},
+ {"TearDownTestCase", "TearDownTestSuite"},
+ {"test_case_name", "test_suite_name"},
+ {"OnTestCaseStart", "OnTestSuiteStart"},
+ {"OnTestCaseEnd", "OnTestSuiteEnd"},
+ {"current_test_case", "current_test_suite"},
+ {"successful_test_case_count", "successful_test_suite_count"},
+ {"failed_test_case_count", "failed_test_suite_count"},
+ {"total_test_case_count", "total_test_suite_count"},
+ {"test_case_to_run_count", "test_suite_to_run_count"},
+ {"GetTestCase", "GetTestSuite"}};
+
+ for (auto &Mapping : ReplacementMap) {
+ if (CurrentName == Mapping.first)
+ return Mapping.second;
+ }
+
+ llvm_unreachable("Unexpected function name");
+}
+
+template <typename NodeType>
+static bool isInInstantiation(const NodeType &Node,
+ const MatchFinder::MatchResult &Result) {
+ return !match(isInTemplateInstantiation(), Node, *Result.Context).empty();
+}
+
+template <typename NodeType>
+static bool isInTemplate(const NodeType &Node,
+ const MatchFinder::MatchResult &Result) {
+ internal::Matcher<NodeType> IsInsideTemplate =
+ hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));
+ return !match(IsInsideTemplate, Node, *Result.Context).empty();
+}
+
+static bool
+derivedTypeHasReplacementMethod(const MatchFinder::MatchResult &Result,
+ llvm::StringRef ReplacementMethod) {
+ const auto *Class = Result.Nodes.getNodeAs<CXXRecordDecl>("class");
+ return !match(cxxRecordDecl(
+ unless(isExpansionInFileMatching(
+ "gtest/gtest(-typed-test)?\\.h$")),
+ hasMethod(cxxMethodDecl(hasName(ReplacementMethod)))),
+ *Class, *Result.Context)
+ .empty();
+}
+
+static CharSourceRange
+getAliasNameRange(const MatchFinder::MatchResult &Result) {
+ if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {
+ return CharSourceRange::getTokenRange(
+ Using->getNameInfo().getSourceRange());
+ }
+ return CharSourceRange::getTokenRange(
+ Result.Nodes.getNodeAs<TypeLoc>("typeloc")->getSourceRange());
+}
+
+void UpgradeGoogletestCaseCheck::check(const MatchFinder::MatchResult &Result) {
+ llvm::StringRef ReplacementText;
+ CharSourceRange ReplacementRange;
+ if (const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>("method")) {
+ ReplacementText = getNewMethodName(Method->getName());
+
+ bool IsInInstantiation;
+ bool IsInTemplate;
+ bool AddFix = true;
+ if (const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call")) {
+ const auto *Callee = llvm::cast<MemberExpr>(Call->getCallee());
+ ReplacementRange = CharSourceRange::getTokenRange(Callee->getMemberLoc(),
+ Callee->getMemberLoc());
+ IsInInstantiation = isInInstantiation(*Call, Result);
+ IsInTemplate = isInTemplate<Stmt>(*Call, Result);
+ } else if (const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("ref")) {
+ ReplacementRange =
+ CharSourceRange::getTokenRange(Ref->getNameInfo().getSourceRange());
+ IsInInstantiation = isInInstantiation(*Ref, Result);
+ IsInTemplate = isInTemplate<Stmt>(*Ref, Result);
+ } else if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {
+ ReplacementRange =
+ CharSourceRange::getTokenRange(Using->getNameInfo().getSourceRange());
+ IsInInstantiation = isInInstantiation(*Using, Result);
+ IsInTemplate = isInTemplate<Decl>(*Using, Result);
+ } else {
+ // This branch means we have matched a function declaration / definition
+ // either for a function from googletest or for a function in a derived
+ // class.
+
+ ReplacementRange = CharSourceRange::getTokenRange(
+ Method->getNameInfo().getSourceRange());
+ IsInInstantiation = isInInstantiation(*Method, Result);
+ IsInTemplate = isInTemplate<Decl>(*Method, Result);
+
+ // If the type of the matched method is strictly derived from a googletest
+ // type and has both the old and new member function names, then we cannot
+ // safely rename (or delete) the old name version.
+ AddFix = !derivedTypeHasReplacementMethod(Result, ReplacementText);
+ }
+
+ if (IsInInstantiation) {
+ if (MatchedTemplateLocations.count(
+ ReplacementRange.getBegin().getRawEncoding()) == 0) {
+ // For each location matched in a template instantiation, we check if
+ // the location can also be found in `MatchedTemplateLocations`. If it
+ // is not found, that means the expression did not create a match
+ // without the instantiation and depends on template parameters. A
+ // manual fix is probably required so we provide only a warning.
+ diag(ReplacementRange.getBegin(), RenameCaseToSuiteMessage);
+ }
+ return;
+ }
+
+ if (IsInTemplate) {
+ // We gather source locations from template matches not in template
+ // instantiations for future matches.
+ MatchedTemplateLocations.insert(
+ ReplacementRange.getBegin().getRawEncoding());
+ }
+
+ if (!AddFix) {
+ diag(ReplacementRange.getBegin(), RenameCaseToSuiteMessage);
+ return;
+ }
+ } else {
+ // This is a match for `TestCase` to `TestSuite` refactoring.
+ assert(Result.Nodes.getNodeAs<TypeAliasDecl>("test-case") != nullptr);
+ ReplacementText = "TestSuite";
+ ReplacementRange = getAliasNameRange(Result);
+
+ // We do not need to keep track of template instantiations for this branch,
+ // because we are matching a `TypeLoc` for the alias declaration. Templates
+ // will only be instantiated with the true type name, `TestSuite`.
+ }
+
+ DiagnosticBuilder Diag =
+ diag(ReplacementRange.getBegin(), RenameCaseToSuiteMessage);
+
+ ReplacementRange = Lexer::makeFileCharRange(
+ ReplacementRange, *Result.SourceManager, Result.Context->getLangOpts());
+ if (ReplacementRange.isInvalid())
+ // An invalid source range likely means we are inside a macro body. A manual
+ // fix is likely needed so we do not create a fix-it hint.
+ return;
+
+ Diag << FixItHint::CreateReplacement(ReplacementRange, ReplacementText);
+}
+
+} // namespace google
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.h b/clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.h
new file mode 100644
index 00000000000..0fbd9fdf26d
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/google/UpgradeGoogletestCaseCheck.h
@@ -0,0 +1,40 @@
+//===--- UpgradeGoogletestCaseCheck.h - clang-tidy --------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UPGRADEGOOGLETESTCASECHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UPGRADEGOOGLETESTCASECHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang {
+namespace tidy {
+namespace google {
+
+/// Finds uses of deprecated Googletest APIs with names containing "case" and
+/// replaces them with equivalent names containing "suite".
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/google-upgrade-googletest-case.html
+class UpgradeGoogletestCaseCheck : public ClangTidyCheck {
+public:
+ UpgradeGoogletestCaseCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ llvm::DenseSet<unsigned> MatchedTemplateLocations;
+};
+
+} // namespace google
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UPGRADEGOOGLETESTCASECHECK_H
OpenPOWER on IntegriCloud