Index: clang-tools-extra/trunk/clang-tidy/utils/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/clang-tidy/utils/CMakeLists.txt +++ clang-tools-extra/trunk/clang-tidy/utils/CMakeLists.txt @@ -13,6 +13,7 @@ LexerUtils.cpp NamespaceAliaser.cpp OptionsUtils.cpp + TransformerClangTidyCheck.cpp TypeTraits.cpp UsingInserter.cpp @@ -22,4 +23,5 @@ clangBasic clangLex clangTidy + clangToolingRefactor ) Index: clang-tools-extra/trunk/clang-tidy/utils/TransformerClangTidyCheck.h =================================================================== --- clang-tools-extra/trunk/clang-tidy/utils/TransformerClangTidyCheck.h +++ clang-tools-extra/trunk/clang-tidy/utils/TransformerClangTidyCheck.h @@ -0,0 +1,49 @@ +//===---------- TransformerClangTidyCheck.h - 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_TRANSFORMER_CLANG_TIDY_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_TRANSFORMER_CLANG_TIDY_CHECK_H + +#include "../ClangTidy.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/Refactoring/Transformer.h" +#include +#include + +namespace clang { +namespace tidy { +namespace utils { + +// A base class for defining a ClangTidy check based on a `RewriteRule`. +// +// For example, given a rule `MyCheckAsRewriteRule`, one can define a tidy check +// as follows: +// +// class MyCheck : public TransformerClangTidyCheck { +// public: +// MyCheck(StringRef Name, ClangTidyContext *Context) +// : TransformerClangTidyCheck(MyCheckAsRewriteRule, Name, Context) {} +// }; +class TransformerClangTidyCheck : public ClangTidyCheck { +public: + TransformerClangTidyCheck(tooling::RewriteRule R, StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), Rule(std::move(R)) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) final; + void check(const ast_matchers::MatchFinder::MatchResult &Result) final; + +private: + tooling::RewriteRule Rule; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_TRANSFORMER_CLANG_TIDY_CHECK_H Index: clang-tools-extra/trunk/clang-tidy/utils/TransformerClangTidyCheck.cpp =================================================================== --- clang-tools-extra/trunk/clang-tidy/utils/TransformerClangTidyCheck.cpp +++ clang-tools-extra/trunk/clang-tidy/utils/TransformerClangTidyCheck.cpp @@ -0,0 +1,63 @@ +//===---------- TransformerClangTidyCheck.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 "TransformerClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace utils { +using tooling::RewriteRule; + +void TransformerClangTidyCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + Finder->addDynamicMatcher(tooling::detail::buildMatcher(Rule), this); +} + +void TransformerClangTidyCheck::check( + const ast_matchers::MatchFinder::MatchResult &Result) { + if (Result.Context->getDiagnostics().hasErrorOccurred()) + return; + + // Verify the existence and validity of the AST node that roots this rule. + const ast_matchers::BoundNodes::IDToNodeMap &NodesMap = Result.Nodes.getMap(); + auto Root = NodesMap.find(RewriteRule::RootID); + assert(Root != NodesMap.end() && "Transformation failed: missing root node."); + SourceLocation RootLoc = Result.SourceManager->getExpansionLoc( + Root->second.getSourceRange().getBegin()); + assert(RootLoc.isValid() && "Invalid location for Root node of match."); + + RewriteRule::Case Case = tooling::detail::findSelectedCase(Result, Rule); + Expected> Transformations = + tooling::detail::translateEdits(Result, Case.Edits); + if (!Transformations) { + llvm::errs() << "Rewrite failed: " + << llvm::toString(Transformations.takeError()) << "\n"; + return; + } + + // No rewrite applied, but no error encountered either. + if (Transformations->empty()) + return; + + StringRef Message = "no explanation"; + if (Case.Explanation) { + if (Expected E = Case.Explanation(Result)) + Message = *E; + else + llvm::errs() << "Error in explanation: " << llvm::toString(E.takeError()) + << "\n"; + } + DiagnosticBuilder Diag = diag(RootLoc, Message); + for (const auto &T : *Transformations) { + Diag << FixItHint::CreateReplacement(T.Range, T.Replacement); + } +} + +} // namespace utils +} // namespace tidy +} // namespace clang Index: clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt +++ clang-tools-extra/trunk/unittests/clang-tidy/CMakeLists.txt @@ -17,6 +17,7 @@ OverlappingReplacementsTest.cpp UsingInserterTest.cpp ReadabilityModuleTest.cpp + TransformerClangTidyCheckTest.cpp ) target_link_libraries(ClangTidyTests @@ -36,4 +37,5 @@ clangTidyUtils clangTooling clangToolingCore + clangToolingRefactor ) Index: clang-tools-extra/trunk/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp +++ clang-tools-extra/trunk/unittests/clang-tidy/TransformerClangTidyCheckTest.cpp @@ -0,0 +1,68 @@ +//===---- TransformerClangTidyCheckTest.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 "../clang-tidy/utils/TransformerClangTidyCheck.h" +#include "ClangTidyTest.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/Refactoring/RangeSelector.h" +#include "clang/Tooling/Refactoring/Stencil.h" +#include "clang/Tooling/Refactoring/Transformer.h" +#include "gtest/gtest.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace { +using tooling::RewriteRule; + +// Invert the code of an if-statement, while maintaining its semantics. +RewriteRule invertIf() { + using namespace ::clang::ast_matchers; + using tooling::change; + using tooling::node; + using tooling::statement; + using tooling::stencil::cat; + + StringRef C = "C", T = "T", E = "E"; + return tooling::makeRule(ifStmt(hasCondition(expr().bind(C)), + hasThen(stmt().bind(T)), + hasElse(stmt().bind(E))), + change(statement(RewriteRule::RootID), + cat("if(!(", node(C), ")) ", statement(E), + " else ", statement(T)))); +} + +class IfInverterCheck : public TransformerClangTidyCheck { +public: + IfInverterCheck(StringRef Name, ClangTidyContext *Context) + : TransformerClangTidyCheck(invertIf(), Name, Context) {} +}; + +// Basic test of using a rewrite rule as a ClangTidy. +TEST(TransformerClangTidyCheckTest, Basic) { + const std::string Input = R"cc( + void log(const char* msg); + void foo() { + if (10 > 1.0) + log("oh no!"); + else + log("ok"); + } + )cc"; + const std::string Expected = R"( + void log(const char* msg); + void foo() { + if(!(10 > 1.0)) log("ok"); else log("oh no!"); + } + )"; + EXPECT_EQ(Expected, test::runCheckOnCode(Input)); +} +} // namespace +} // namespace utils +} // namespace tidy +} // namespace clang