From d9a2f127a8277690a5653aade1077039c77be716 Mon Sep 17 00:00:00 2001 From: Alexey Bataev Date: Wed, 3 Apr 2019 17:34:04 +0000 Subject: [PATCH] Revert "[LibTooling] Add Transformer, a library for source-to-source transformations." This reverts commit r357576 to fix the problem with the cyclic dependencies between libTooling and libToolingRefactor. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@357612 91177308-0d34-0410-b5e6-96231b3b80d8 --- .../clang/Tooling/Refactoring/Transformer.h | 210 ---------- lib/Tooling/Refactoring/CMakeLists.txt | 1 - lib/Tooling/Refactoring/Transformer.cpp | 204 --------- unittests/Tooling/CMakeLists.txt | 1 - unittests/Tooling/TransformerTest.cpp | 389 ------------------ 5 files changed, 805 deletions(-) delete mode 100644 include/clang/Tooling/Refactoring/Transformer.h delete mode 100644 lib/Tooling/Refactoring/Transformer.cpp delete mode 100644 unittests/Tooling/TransformerTest.cpp diff --git a/include/clang/Tooling/Refactoring/Transformer.h b/include/clang/Tooling/Refactoring/Transformer.h deleted file mode 100644 index bae1a753ac..0000000000 --- a/include/clang/Tooling/Refactoring/Transformer.h +++ /dev/null @@ -1,210 +0,0 @@ -//===--- Transformer.h - Clang source-rewriting library ---------*- 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 -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// Defines a library supporting the concise specification of clang-based -/// source-to-source transformations. -/// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLING_REFACTOR_TRANSFORMER_H_ -#define LLVM_CLANG_TOOLING_REFACTOR_TRANSFORMER_H_ - -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/ASTMatchers/ASTMatchersInternal.h" -#include "clang/Tooling/Refactoring/AtomicChange.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/Support/Error.h" -#include -#include -#include -#include -#include -#include - -namespace clang { -namespace tooling { -/// Determines the part of the AST node to replace. We support this to work -/// around the fact that the AST does not differentiate various syntactic -/// elements into their own nodes, so users can specify them relative to a node, -/// instead. -enum class NodePart { - /// The node itself. - Node, - /// Given a \c MemberExpr, selects the member's token. - Member, - /// Given a \c NamedDecl or \c CxxCtorInitializer, selects that token of the - /// relevant name, not including qualifiers. - Name, -}; - -using TextGenerator = - std::function; - -/// Description of a source-code transformation. -// -// A *rewrite rule* describes a transformation of source code. It has the -// following components: -// -// * Matcher: the pattern term, expressed as clang matchers (with Transformer -// extensions). -// -// * Target: the source code impacted by the rule. This identifies an AST node, -// or part thereof (\c TargetPart), whose source range indicates the extent of -// the replacement applied by the replacement term. By default, the extent is -// the node matched by the pattern term (\c NodePart::Node). Target's are -// typed (\c TargetKind), which guides the determination of the node extent -// and might, in the future, statically constrain the set of eligible -// NodeParts for a given node. -// -// * Replacement: a function that produces a replacement string for the target, -// based on the match result. -// -// * Explanation: explanation of the rewrite. This will be displayed to the -// user, where possible (for example, in clang-tidy fix descriptions). -// -// Rules have an additional, implicit, component: the parameters. These are -// portions of the pattern which are left unspecified, yet named so that we can -// reference them in the replacement term. The structure of parameters can be -// partially or even fully specified, in which case they serve just to identify -// matched nodes for later reference rather than abstract over portions of the -// AST. However, in all cases, we refer to named portions of the pattern as -// parameters. -// -// RewriteRule is constructed in a "fluent" style, by creating a builder and -// chaining setters of individual components. -// \code -// RewriteRule MyRule = buildRule(functionDecl(...)).replaceWith(...); -// \endcode -// -// The \c Transformer class should then be used to apply the rewrite rule and -// obtain the corresponding replacements. -struct RewriteRule { - // `Matcher` describes the context of this rule. It should always be bound to - // at least `RootId`. The builder class below takes care of this - // binding. Here, we bind it to a trivial Matcher to enable the default - // constructor, since DynTypedMatcher has no default constructor. - ast_matchers::internal::DynTypedMatcher Matcher = ast_matchers::stmt(); - // The (bound) id of the node whose source will be replaced. This id should - // never be the empty string. - std::string Target; - ast_type_traits::ASTNodeKind TargetKind; - NodePart TargetPart; - TextGenerator Replacement; - TextGenerator Explanation; - - // Id used as the default target of each match. The node described by the - // matcher is guaranteed to be bound to this id, for all rewrite rules - // constructed with the builder class. - static constexpr llvm::StringLiteral RootId = "___root___"; -}; - -/// A fluent builder class for \c RewriteRule. See comments on \c RewriteRule. -class RewriteRuleBuilder { - RewriteRule Rule; - -public: - RewriteRuleBuilder(ast_matchers::internal::DynTypedMatcher M) { - M.setAllowBind(true); - // `tryBind` is guaranteed to succeed, because `AllowBind` was set to true. - Rule.Matcher = *M.tryBind(RewriteRule::RootId); - Rule.Target = RewriteRule::RootId; - Rule.TargetKind = M.getSupportedKind(); - Rule.TargetPart = NodePart::Node; - } - - /// (Implicit) "build" operator to build a RewriteRule from this builder. - operator RewriteRule() && { return std::move(Rule); } - - // Sets the target kind based on a clang AST node type. - template RewriteRuleBuilder as(); - - template - RewriteRuleBuilder change(llvm::StringRef Target, - NodePart Part = NodePart::Node); - - RewriteRuleBuilder replaceWith(TextGenerator Replacement); - RewriteRuleBuilder replaceWith(std::string Replacement) { - return replaceWith(text(std::move(Replacement))); - } - - RewriteRuleBuilder because(TextGenerator Explanation); - RewriteRuleBuilder because(std::string Explanation) { - return because(text(std::move(Explanation))); - } - -private: - // Wraps a string as a TextGenerator. - static TextGenerator text(std::string M) { - return [M](const ast_matchers::MatchFinder::MatchResult &) { return M; }; - } -}; - -/// Convenience factory functions for starting construction of a \c RewriteRule. -inline RewriteRuleBuilder buildRule(ast_matchers::internal::DynTypedMatcher M) { - return RewriteRuleBuilder(std::move(M)); -} - -template RewriteRuleBuilder RewriteRuleBuilder::as() { - Rule.TargetKind = ast_type_traits::ASTNodeKind::getFromNodeKind(); - return *this; -} - -template -RewriteRuleBuilder RewriteRuleBuilder::change(llvm::StringRef TargetId, - NodePart Part) { - Rule.Target = TargetId; - Rule.TargetKind = ast_type_traits::ASTNodeKind::getFromNodeKind(); - Rule.TargetPart = Part; - return *this; -} - -/// A source "transformation," represented by a character range in the source to -/// be replaced and a corresponding replacement string. -struct Transformation { - CharSourceRange Range; - std::string Replacement; -}; - -/// Attempts to apply a rule to a match. Returns an empty transformation if the -/// match is not eligible for rewriting (certain interactions with macros, for -/// example). Fails if any invariants are violated relating to bound nodes in -/// the match. -Expected -applyRewriteRule(const RewriteRule &Rule, - const ast_matchers::MatchFinder::MatchResult &Match); - -/// Handles the matcher and callback registration for a single rewrite rule, as -/// defined by the arguments of the constructor. -class Transformer : public ast_matchers::MatchFinder::MatchCallback { -public: - using ChangeConsumer = - std::function; - - /// \param Consumer Receives each successful rewrites as an \c AtomicChange. - Transformer(RewriteRule Rule, ChangeConsumer Consumer) - : Rule(std::move(Rule)), Consumer(std::move(Consumer)) {} - - /// N.B. Passes `this` pointer to `MatchFinder`. So, this object should not - /// be moved after this call. - void registerMatchers(ast_matchers::MatchFinder *MatchFinder); - - /// Not called directly by users -- called by the framework, via base class - /// pointer. - void run(const ast_matchers::MatchFinder::MatchResult &Result) override; - -private: - RewriteRule Rule; - /// Receives each successful rewrites as an \c AtomicChange. - ChangeConsumer Consumer; -}; -} // namespace tooling -} // namespace clang - -#endif // LLVM_CLANG_TOOLING_REFACTOR_TRANSFORMER_H_ diff --git a/lib/Tooling/Refactoring/CMakeLists.txt b/lib/Tooling/Refactoring/CMakeLists.txt index e500283998..402b5d3c6a 100644 --- a/lib/Tooling/Refactoring/CMakeLists.txt +++ b/lib/Tooling/Refactoring/CMakeLists.txt @@ -12,7 +12,6 @@ add_clang_library(clangToolingRefactor Rename/USRFinder.cpp Rename/USRFindingAction.cpp Rename/USRLocFinder.cpp - Transformer.cpp LINK_LIBS clangAST diff --git a/lib/Tooling/Refactoring/Transformer.cpp b/lib/Tooling/Refactoring/Transformer.cpp deleted file mode 100644 index 4899dfd41f..0000000000 --- a/lib/Tooling/Refactoring/Transformer.cpp +++ /dev/null @@ -1,204 +0,0 @@ -//===--- Transformer.cpp - Transformer library implementation ---*- 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 -// -//===----------------------------------------------------------------------===// - -#include "clang/Tooling/Refactoring/Transformer.h" -#include "clang/AST/Expr.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/Basic/Diagnostic.h" -#include "clang/Basic/SourceLocation.h" -#include "clang/Rewrite/Core/Rewriter.h" -#include "clang/Tooling/FixIt.h" -#include "clang/Tooling/Refactoring.h" -#include "clang/Tooling/Refactoring/AtomicChange.h" -#include "llvm/ADT/Optional.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/Error.h" -#include -#include -#include -#include - -using namespace clang; -using namespace tooling; - -using ast_matchers::MatchFinder; -using ast_type_traits::ASTNodeKind; -using ast_type_traits::DynTypedNode; -using llvm::Error; -using llvm::Expected; -using llvm::Optional; -using llvm::StringError; -using llvm::StringRef; -using llvm::Twine; - -using MatchResult = MatchFinder::MatchResult; - -// Did the text at this location originate in a macro definition (aka. body)? -// For example, -// -// #define NESTED(x) x -// #define MACRO(y) { int y = NESTED(3); } -// if (true) MACRO(foo) -// -// The if statement expands to -// -// if (true) { int foo = 3; } -// ^ ^ -// Loc1 Loc2 -// -// For SourceManager SM, SM.isMacroArgExpansion(Loc1) and -// SM.isMacroArgExpansion(Loc2) are both true, but isOriginMacroBody(sm, Loc1) -// is false, because "foo" originated in the source file (as an argument to a -// macro), whereas isOriginMacroBody(SM, Loc2) is true, because "3" originated -// in the definition of MACRO. -static bool isOriginMacroBody(const clang::SourceManager &SM, - clang::SourceLocation Loc) { - while (Loc.isMacroID()) { - if (SM.isMacroBodyExpansion(Loc)) - return true; - // Otherwise, it must be in an argument, so we continue searching up the - // invocation stack. getImmediateMacroCallerLoc() gives the location of the - // argument text, inside the call text. - Loc = SM.getImmediateMacroCallerLoc(Loc); - } - return false; -} - -static llvm::Error invalidArgumentError(Twine Message) { - return llvm::make_error(llvm::errc::invalid_argument, Message); -} - -static llvm::Error typeError(StringRef Id, const ASTNodeKind &Kind, - Twine Message) { - return invalidArgumentError( - Message + " (node id=" + Id + " kind=" + Kind.asStringRef() + ")"); -} - -static llvm::Error missingPropertyError(StringRef Id, Twine Description, - StringRef Property) { - return invalidArgumentError(Description + " requires property '" + Property + - "' (node id=" + Id + ")"); -} - -static Expected -getTargetRange(StringRef Target, const DynTypedNode &Node, ASTNodeKind Kind, - NodePart TargetPart, ASTContext &Context) { - switch (TargetPart) { - case NodePart::Node: { - // For non-expression statements, associate any trailing semicolon with the - // statement text. However, if the target was intended as an expression (as - // indicated by its kind) then we do not associate any trailing semicolon - // with it. We only associate the exact expression text. - if (Node.get() != nullptr) { - auto ExprKind = ASTNodeKind::getFromNodeKind(); - if (!ExprKind.isBaseOf(Kind)) - return fixit::getExtendedRange(Node, tok::TokenKind::semi, Context); - } - return CharSourceRange::getTokenRange(Node.getSourceRange()); - } - case NodePart::Member: - if (auto *M = Node.get()) - return CharSourceRange::getTokenRange( - M->getMemberNameInfo().getSourceRange()); - return typeError(Target, Node.getNodeKind(), - "NodePart::Member applied to non-MemberExpr"); - case NodePart::Name: - if (const auto *D = Node.get()) { - if (!D->getDeclName().isIdentifier()) - return missingPropertyError(Target, "NodePart::Name", "identifier"); - SourceLocation L = D->getLocation(); - auto R = CharSourceRange::getTokenRange(L, L); - // Verify that the range covers exactly the name. - // FIXME: extend this code to support cases like `operator +` or - // `foo` for which this range will be too short. Doing so will - // require subcasing `NamedDecl`, because it doesn't provide virtual - // access to the \c DeclarationNameInfo. - if (fixit::internal::getText(R, Context) != D->getName()) - return CharSourceRange(); - return R; - } - if (const auto *E = Node.get()) { - if (!E->getNameInfo().getName().isIdentifier()) - return missingPropertyError(Target, "NodePart::Name", "identifier"); - SourceLocation L = E->getLocation(); - return CharSourceRange::getTokenRange(L, L); - } - if (const auto *I = Node.get()) { - if (!I->isMemberInitializer() && I->isWritten()) - return missingPropertyError(Target, "NodePart::Name", - "explicit member initializer"); - SourceLocation L = I->getMemberLocation(); - return CharSourceRange::getTokenRange(L, L); - } - return typeError( - Target, Node.getNodeKind(), - "NodePart::Name applied to neither DeclRefExpr, NamedDecl nor " - "CXXCtorInitializer"); - } - llvm_unreachable("Unexpected case in NodePart type."); -} - -Expected -tooling::applyRewriteRule(const RewriteRule &Rule, - const ast_matchers::MatchFinder::MatchResult &Match) { - if (Match.Context->getDiagnostics().hasErrorOccurred()) - return Transformation(); - - auto &NodesMap = Match.Nodes.getMap(); - auto It = NodesMap.find(Rule.Target); - assert (It != NodesMap.end() && "Rule.Target must be bound in the match."); - - Expected TargetOrErr = - getTargetRange(Rule.Target, It->second, Rule.TargetKind, Rule.TargetPart, - *Match.Context); - if (auto Err = TargetOrErr.takeError()) - return std::move(Err); - auto &Target = *TargetOrErr; - if (Target.isInvalid() || - isOriginMacroBody(*Match.SourceManager, Target.getBegin())) - return Transformation(); - - return Transformation{Target, Rule.Replacement(Match)}; -} - -constexpr llvm::StringLiteral RewriteRule::RootId; - -RewriteRuleBuilder RewriteRuleBuilder::replaceWith(TextGenerator T) { - Rule.Replacement = std::move(T); - return *this; -} - -RewriteRuleBuilder RewriteRuleBuilder::because(TextGenerator T) { - Rule.Explanation = std::move(T); - return *this; -} - -void Transformer::registerMatchers(MatchFinder *MatchFinder) { - MatchFinder->addDynamicMatcher(Rule.Matcher, this); -} - -void Transformer::run(const MatchResult &Result) { - auto ChangeOrErr = applyRewriteRule(Rule, Result); - if (auto Err = ChangeOrErr.takeError()) { - llvm::errs() << "Rewrite failed: " << llvm::toString(std::move(Err)) - << "\n"; - return; - } - auto &Change = *ChangeOrErr; - auto &Range = Change.Range; - if (Range.isInvalid()) { - // No rewrite applied (but no error encountered either). - return; - } - AtomicChange AC(*Result.SourceManager, Range.getBegin()); - if (auto Err = AC.replace(*Result.SourceManager, Range, Change.Replacement)) - AC.setError(llvm::toString(std::move(Err))); - Consumer(AC); -} diff --git a/unittests/Tooling/CMakeLists.txt b/unittests/Tooling/CMakeLists.txt index 7b13ac58bf..7619c7fb23 100644 --- a/unittests/Tooling/CMakeLists.txt +++ b/unittests/Tooling/CMakeLists.txt @@ -50,7 +50,6 @@ add_clang_unittest(ToolingTests ReplacementsYamlTest.cpp RewriterTest.cpp ToolingTest.cpp - TransformerTest.cpp ) target_link_libraries(ToolingTests diff --git a/unittests/Tooling/TransformerTest.cpp b/unittests/Tooling/TransformerTest.cpp deleted file mode 100644 index a25ce32c45..0000000000 --- a/unittests/Tooling/TransformerTest.cpp +++ /dev/null @@ -1,389 +0,0 @@ -//===- unittest/Tooling/TransformerTest.cpp -------------------------------===// -// -// 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/Tooling/Refactoring/Transformer.h" - -#include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/Tooling/Tooling.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace tooling { -namespace { -using ast_matchers::anyOf; -using ast_matchers::argumentCountIs; -using ast_matchers::callee; -using ast_matchers::callExpr; -using ast_matchers::cxxMemberCallExpr; -using ast_matchers::cxxMethodDecl; -using ast_matchers::cxxRecordDecl; -using ast_matchers::declRefExpr; -using ast_matchers::expr; -using ast_matchers::functionDecl; -using ast_matchers::hasAnyName; -using ast_matchers::hasArgument; -using ast_matchers::hasDeclaration; -using ast_matchers::hasElse; -using ast_matchers::hasName; -using ast_matchers::hasType; -using ast_matchers::ifStmt; -using ast_matchers::member; -using ast_matchers::memberExpr; -using ast_matchers::namedDecl; -using ast_matchers::on; -using ast_matchers::pointsTo; -using ast_matchers::to; -using ast_matchers::unless; - -using llvm::StringRef; - -constexpr char KHeaderContents[] = R"cc( - struct string { - string(const char*); - char* c_str(); - int size(); - }; - int strlen(const char*); - - namespace proto { - struct PCFProto { - int foo(); - }; - struct ProtoCommandLineFlag : PCFProto { - PCFProto& GetProto(); - }; - } // namespace proto -)cc"; - -static ast_matchers::internal::Matcher -isOrPointsTo(const clang::ast_matchers::DeclarationMatcher &TypeMatcher) { - return anyOf(hasDeclaration(TypeMatcher), pointsTo(TypeMatcher)); -} - -static std::string format(StringRef Code) { - const std::vector Ranges(1, Range(0, Code.size())); - auto Style = format::getLLVMStyle(); - const auto Replacements = format::reformat(Style, Code, Ranges); - auto Formatted = applyAllReplacements(Code, Replacements); - if (!Formatted) { - ADD_FAILURE() << "Could not format code: " - << llvm::toString(Formatted.takeError()); - return std::string(); - } - return *Formatted; -} - -static void compareSnippets(StringRef Expected, - const llvm::Optional &MaybeActual) { - ASSERT_TRUE(MaybeActual) << "Rewrite failed. Expecting: " << Expected; - auto Actual = *MaybeActual; - std::string HL = "#include \"header.h\"\n"; - auto I = Actual.find(HL); - if (I != std::string::npos) - Actual.erase(I, HL.size()); - EXPECT_EQ(format(Expected), format(Actual)); -} - -// FIXME: consider separating this class into its own file(s). -class ClangRefactoringTestBase : public testing::Test { -protected: - void appendToHeader(StringRef S) { FileContents[0].second += S; } - - void addFile(StringRef Filename, StringRef Content) { - FileContents.emplace_back(Filename, Content); - } - - llvm::Optional rewrite(StringRef Input) { - std::string Code = ("#include \"header.h\"\n" + Input).str(); - auto Factory = newFrontendActionFactory(&MatchFinder); - if (!runToolOnCodeWithArgs( - Factory->create(), Code, std::vector(), "input.cc", - "clang-tool", std::make_shared(), - FileContents)) { - return None; - } - auto ChangedCodeOrErr = - applyAtomicChanges("input.cc", Code, Changes, ApplyChangesSpec()); - if (auto Err = ChangedCodeOrErr.takeError()) { - llvm::errs() << "Change failed: " << llvm::toString(std::move(Err)) - << "\n"; - return None; - } - return *ChangedCodeOrErr; - } - - void testRule(RewriteRule Rule, StringRef Input, StringRef Expected) { - Transformer T(std::move(Rule), - [this](const AtomicChange &C) { Changes.push_back(C); }); - T.registerMatchers(&MatchFinder); - compareSnippets(Expected, rewrite(Input)); - } - - clang::ast_matchers::MatchFinder MatchFinder; - AtomicChanges Changes; - -private: - FileContentMappings FileContents = {{"header.h", ""}}; -}; - -class TransformerTest : public ClangRefactoringTestBase { -protected: - TransformerTest() { appendToHeader(KHeaderContents); } -}; - -// Given string s, change strlen($s.c_str()) to $s.size(). -static RewriteRule ruleStrlenSize() { - StringRef StringExpr = "strexpr"; - auto StringType = namedDecl(hasAnyName("::basic_string", "::string")); - return buildRule( - callExpr( - callee(functionDecl(hasName("strlen"))), - hasArgument(0, cxxMemberCallExpr( - on(expr(hasType(isOrPointsTo(StringType))) - .bind(StringExpr)), - callee(cxxMethodDecl(hasName("c_str"))))))) - // Specify the intended type explicitly, because the matcher "type" of - // `callExpr()` is `Stmt`, not `Expr`. - .as() - .replaceWith("REPLACED") - .because("Use size() method directly on string."); -} - -TEST_F(TransformerTest, StrlenSize) { - std::string Input = "int f(string s) { return strlen(s.c_str()); }"; - std::string Expected = "int f(string s) { return REPLACED; }"; - testRule(ruleStrlenSize(), Input, Expected); -} - -// Tests that no change is applied when a match is not expected. -TEST_F(TransformerTest, NoMatch) { - std::string Input = "int f(string s) { return s.size(); }"; - testRule(ruleStrlenSize(), Input, Input); -} - -// Tests that expressions in macro arguments are rewritten (when applicable). -TEST_F(TransformerTest, StrlenSizeMacro) { - std::string Input = R"cc( -#define ID(e) e - int f(string s) { return ID(strlen(s.c_str())); })cc"; - std::string Expected = R"cc( -#define ID(e) e - int f(string s) { return ID(REPLACED); })cc"; - testRule(ruleStrlenSize(), Input, Expected); -} - -// Tests replacing an expression. -TEST_F(TransformerTest, Flag) { - StringRef Flag = "flag"; - RewriteRule Rule = - buildRule( - cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl(hasName( - "proto::ProtoCommandLineFlag")))) - .bind(Flag)), - unless(callee(cxxMethodDecl(hasName("GetProto")))))) - .change(Flag) - .replaceWith("EXPR") - .because("Use GetProto() to access proto fields."); - - std::string Input = R"cc( - proto::ProtoCommandLineFlag flag; - int x = flag.foo(); - int y = flag.GetProto().foo(); - )cc"; - std::string Expected = R"cc( - proto::ProtoCommandLineFlag flag; - int x = EXPR.foo(); - int y = flag.GetProto().foo(); - )cc"; - - testRule(std::move(Rule), Input, Expected); -} - -TEST_F(TransformerTest, NodePartNameNamedDecl) { - StringRef Fun = "fun"; - RewriteRule Rule = buildRule(functionDecl(hasName("bad")).bind(Fun)) - .change(Fun, NodePart::Name) - .replaceWith("good"); - - std::string Input = R"cc( - int bad(int x); - int bad(int x) { return x * x; } - )cc"; - std::string Expected = R"cc( - int good(int x); - int good(int x) { return x * x; } - )cc"; - - testRule(Rule, Input, Expected); -} - -TEST_F(TransformerTest, NodePartNameDeclRef) { - std::string Input = R"cc( - template - T bad(T x) { - return x; - } - int neutral(int x) { return bad(x) * x; } - )cc"; - std::string Expected = R"cc( - template - T bad(T x) { - return x; - } - int neutral(int x) { return good(x) * x; } - )cc"; - - StringRef Ref = "ref"; - testRule(buildRule(declRefExpr(to(functionDecl(hasName("bad")))).bind(Ref)) - .change(Ref, NodePart::Name) - .replaceWith("good"), - Input, Expected); -} - -TEST_F(TransformerTest, NodePartNameDeclRefFailure) { - std::string Input = R"cc( - struct Y { - int operator*(); - }; - int neutral(int x) { - Y y; - int (Y::*ptr)() = &Y::operator*; - return *y + x; - } - )cc"; - - StringRef Ref = "ref"; - testRule(buildRule(declRefExpr(to(functionDecl())).bind(Ref)) - .change(Ref, NodePart::Name) - .replaceWith("good"), - Input, Input); -} - -TEST_F(TransformerTest, NodePartMember) { - StringRef E = "expr"; - RewriteRule Rule = buildRule(memberExpr(member(hasName("bad"))).bind(E)) - .change(E, NodePart::Member) - .replaceWith("good"); - - std::string Input = R"cc( - struct S { - int bad; - }; - int g() { - S s; - return s.bad; - } - )cc"; - std::string Expected = R"cc( - struct S { - int bad; - }; - int g() { - S s; - return s.good; - } - )cc"; - - testRule(Rule, Input, Expected); -} - -TEST_F(TransformerTest, NodePartMemberQualified) { - std::string Input = R"cc( - struct S { - int bad; - int good; - }; - struct T : public S { - int bad; - }; - int g() { - T t; - return t.S::bad; - } - )cc"; - std::string Expected = R"cc( - struct S { - int bad; - int good; - }; - struct T : public S { - int bad; - }; - int g() { - T t; - return t.S::good; - } - )cc"; - - StringRef E = "expr"; - testRule(buildRule(memberExpr().bind(E)) - .change(E, NodePart::Member) - .replaceWith("good"), - Input, Expected); -} - -TEST_F(TransformerTest, NodePartMemberMultiToken) { - std::string Input = R"cc( - struct Y { - int operator*(); - int good(); - template void foo(T t); - }; - int neutral(int x) { - Y y; - y.template foo(3); - return y.operator *(); - } - )cc"; - std::string Expected = R"cc( - struct Y { - int operator*(); - int good(); - template void foo(T t); - }; - int neutral(int x) { - Y y; - y.template good(3); - return y.good(); - } - )cc"; - - StringRef MemExpr = "member"; - testRule(buildRule(memberExpr().bind(MemExpr)) - .change(MemExpr, NodePart::Member) - .replaceWith("good"), - Input, Expected); -} - -// -// Negative tests (where we expect no transformation to occur). -// - -TEST_F(TransformerTest, NoTransformationInMacro) { - std::string Input = R"cc( -#define MACRO(str) strlen((str).c_str()) - int f(string s) { return MACRO(s); })cc"; - testRule(ruleStrlenSize(), Input, Input); -} - -// This test handles the corner case where a macro called within another macro -// expands to matching code, but the matched code is an argument to the nested -// macro. A simple check of isMacroArgExpansion() vs. isMacroBodyExpansion() -// will get this wrong, and transform the code. This test verifies that no such -// transformation occurs. -TEST_F(TransformerTest, NoTransformationInNestedMacro) { - std::string Input = R"cc( -#define NESTED(e) e -#define MACRO(str) NESTED(strlen((str).c_str())) - int f(string s) { return MACRO(s); })cc"; - testRule(ruleStrlenSize(), Input, Input); -} -} // namespace -} // namespace tooling -} // namespace clang -- 2.40.0