diff --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt @@ -13,6 +13,7 @@ LexerUtils.cpp NamespaceAliaser.cpp OptionsUtils.cpp + TransformerTidy.cpp TypeTraits.cpp UsingInserter.cpp @@ -22,4 +23,5 @@ clangBasic clangLex clangTidy + clangToolingRefactor ) diff --git a/clang-tools-extra/clang-tidy/utils/TransformerTidy.h b/clang-tools-extra/clang-tidy/utils/TransformerTidy.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/TransformerTidy.h @@ -0,0 +1,49 @@ +//===---------- TransformerTidy.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_TIDY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_TRANSFORMER_TIDY_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 rewrite rule. +// +// For example, given a RewriteRule `MyCheckAsRewriteRule`, one can define your +// tidy check as follows: +// +// class MyTidyCheck : public TransformerTidy { +// public: +// MyTidyCheck(StringRef Name, ClangTidyContext *Context) +// : TransformerTidy(MyCheckAsRewriteRule, Name, Context) {} +// }; +class TransformerTidy : public ClangTidyCheck { +public: + TransformerTidy(tooling::RewriteRule R, StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), Rule(std::move(R)) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + tooling::RewriteRule Rule; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_TRANSFORMER_TIDY_H diff --git a/clang-tools-extra/clang-tidy/utils/TransformerTidy.cpp b/clang-tools-extra/clang-tidy/utils/TransformerTidy.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/TransformerTidy.cpp @@ -0,0 +1,58 @@ +//===---------- TransformerTidy.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 "TransformerTidy.h" + +namespace clang { +namespace tidy { +namespace utils { + +void TransformerTidy::registerMatchers(ast_matchers::MatchFinder *Finder) { + Finder->addDynamicMatcher(Rule.Matcher, this); +} + +void TransformerTidy::check( + const ast_matchers::MatchFinder::MatchResult &Result) { + // Verify the existence and validity of the AST node that roots this rule. + ast_matchers::BoundNodes::IdToNodeMap &NodesMap = Result.Nodes.getMap(); + auto Root = NodesMap.find(tooling::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."); + + Expected> Transformations = + tooling::translateEdits(Result, Rule.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 (Rule.Explanation) { + if (Expected Explanation = Rule.Explanation(Result)) { + Message = *Explanation; + } else { + llvm::errs() << "Error in explanation: " + << llvm::toString(Explanation.takeError()) << "\n"; + } + } + DiagnosticBuilder Diag = diag(RootLoc, Message); + for (const tooling::Transformation &T : *Transformations) { + Diag << FixItHint::CreateReplacement(T.Range, T.Replacement); + } +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt --- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt @@ -17,6 +17,7 @@ OverlappingReplacementsTest.cpp UsingInserterTest.cpp ReadabilityModuleTest.cpp + TransformerTidyTest.cpp ) target_link_libraries(ClangTidyTests @@ -36,4 +37,5 @@ clangTidyUtils clangTooling clangToolingCore + clangToolingRefactor ) diff --git a/clang-tools-extra/unittests/clang-tidy/TransformerTidyTest.cpp b/clang-tools-extra/unittests/clang-tidy/TransformerTidyTest.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-tidy/TransformerTidyTest.cpp @@ -0,0 +1,63 @@ +//===---- TransformerTidyTest.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/TransformerTidy.h" +#include "ClangTidyTest.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/Refactoring/Stencil.h" +#include "clang/Tooling/Refactoring/Transformer.h" +#include "gtest/gtest.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace { +// Invert the code of an if-statement, while maintaining its semantics. +tooling::RewriteRule invertIf() { + using namespace ::clang::ast_matchers; + using tooling::change; + using tooling::stencil::cat; + using tooling::stencil::node; + using tooling::stencil::sNode; + + StringRef C = "C", "T", E = "E"; + return tooling::makeRule( + ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)), + hasElse(stmt().bind(E))), + change(cat("if(!(", node(C), ")) ", sNode(E), " else ", sNode(T)))); +} + +class IfInverterTidy : public TransformerTidy { +public: + IfInverterTidy(StringRef Name, ClangTidyContext *Context) + : TransformerTidy(invertIf(), Name, Context) {} +}; + +// Basic test of using a rewrite rule as a ClangTidy. +TEST(TransformerTidyTest, 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