Add refactoring callbacks to make common kinds of refactorings easy.

llvm-svn: 160255
This commit is contained in:
Daniel Jasper 2012-07-16 09:18:17 +00:00
parent 3dd6c81492
commit 7e22282b68
6 changed files with 273 additions and 3 deletions

View File

@ -0,0 +1,90 @@
//===--- RefactoringCallbacks.h - Structural query framework ----*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Provides callbacks to make common kinds of refactorings easy.
//
// The general idea is to construct a matcher expression that describes a
// subtree match on the AST and then replace the corresponding source code
// either by some specific text or some other AST node.
//
// Example:
// int main(int argc, char **argv) {
// ClangTool Tool(argc, argv);
// MatchFinder Finder;
// ReplaceStmtWithText Callback("integer", "42");
// Finder.AddMatcher(id("integer", expression(integerLiteral())), Callback);
// return Tool.run(newFrontendActionFactory(&Finder));
// }
//
// This will replace all integer literals with "42".
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_AST_MATCHERS_REFACTORING_CALLBACKS_H
#define LLVM_CLANG_AST_MATCHERS_REFACTORING_CALLBACKS_H
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Tooling/Refactoring.h"
namespace clang {
namespace ast_matchers {
/// \brief Base class for RefactoringCallbacks.
///
/// Collects \c tooling::Replacements while running.
class RefactoringCallback : public MatchFinder::MatchCallback {
public:
RefactoringCallback();
tooling::Replacements &getReplacements();
protected:
tooling::Replacements Replace;
};
/// \brief Replace the text of the statement bound to \c FromId with the text in
/// \c ToText.
class ReplaceStmtWithText : public RefactoringCallback {
public:
ReplaceStmtWithText(StringRef FromId, StringRef ToText);
virtual void run(const MatchFinder::MatchResult &Result);
private:
std::string FromId;
std::string ToText;
};
/// \brief Replace the text of the statement bound to \c FromId with the text of
/// the statement bound to \c ToId.
class ReplaceStmtWithStmt : public RefactoringCallback {
public:
ReplaceStmtWithStmt(StringRef FromId, StringRef ToId);
virtual void run(const MatchFinder::MatchResult &Result);
private:
std::string FromId;
std::string ToId;
};
/// \brief Replace an if-statement bound to \c Id with the outdented text of its
/// body, choosing the consequent or the alternative based on whether
/// \c PickTrueBranch is true.
class ReplaceIfStmtWithItsBody : public RefactoringCallback {
public:
ReplaceIfStmtWithItsBody(StringRef Id, bool PickTrueBranch);
virtual void run(const MatchFinder::MatchResult &Result);
private:
std::string Id;
const bool PickTrueBranch;
};
} // end namespace ast_matchers
} // end namespace clang
#endif // LLVM_CLANG_AST_MATCHERS_REFACTORING_CALLBACKS_H

View File

@ -86,7 +86,8 @@ FrontendActionFactory *newFrontendActionFactory();
/// FrontendActionFactory *FactoryAdapter =
/// newFrontendActionFactory(&Factory);
template <typename FactoryT>
FrontendActionFactory *newFrontendActionFactory(FactoryT *ConsumerFactory);
inline FrontendActionFactory *newFrontendActionFactory(
FactoryT *ConsumerFactory);
/// \brief Runs (and deletes) the tool on 'Code' with the -fsyntax-only flag.
///
@ -202,7 +203,8 @@ FrontendActionFactory *newFrontendActionFactory() {
}
template <typename FactoryT>
FrontendActionFactory *newFrontendActionFactory(FactoryT *ConsumerFactory) {
inline FrontendActionFactory *newFrontendActionFactory(
FactoryT *ConsumerFactory) {
class FrontendActionFactoryAdapter : public FrontendActionFactory {
public:
explicit FrontendActionFactoryAdapter(FactoryT *ConsumerFactory)

View File

@ -4,6 +4,7 @@ set(LLVM_USED_LIBS clangBasic clangAST)
add_clang_library(clangASTMatchers
ASTMatchFinder.cpp
ASTMatchersInternal.cpp
RefactoringCallbacks.cpp
)
add_dependencies(clangASTMatchers

View File

@ -0,0 +1,78 @@
//===--- RefactoringCallbacks.cpp - Structural query framework ------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//
//===----------------------------------------------------------------------===//
#include "clang/Lex/Lexer.h"
#include "clang/ASTMatchers/RefactoringCallbacks.h"
namespace clang {
namespace ast_matchers {
RefactoringCallback::RefactoringCallback() {}
tooling::Replacements &RefactoringCallback::getReplacements() {
return Replace;
}
static tooling::Replacement replaceStmtWithText(SourceManager &Sources,
const Stmt &From,
StringRef Text) {
return tooling::Replacement(Sources, CharSourceRange::getTokenRange(
From.getSourceRange()), Text);
}
static tooling::Replacement replaceStmtWithStmt(SourceManager &Sources,
const Stmt &From,
const Stmt &To) {
return replaceStmtWithText(Sources, From, Lexer::getSourceText(
CharSourceRange::getTokenRange(To.getSourceRange()),
Sources, LangOptions()));
}
ReplaceStmtWithText::ReplaceStmtWithText(StringRef FromId, StringRef ToText)
: FromId(FromId), ToText(ToText) {}
void ReplaceStmtWithText::run(const MatchFinder::MatchResult &Result) {
if (const Stmt *FromMatch = Result.Nodes.getStmtAs<Stmt>(FromId)) {
Replace.insert(tooling::Replacement(
*Result.SourceManager,
CharSourceRange::getTokenRange(FromMatch->getSourceRange()),
ToText));
}
}
ReplaceStmtWithStmt::ReplaceStmtWithStmt(StringRef FromId, StringRef ToId)
: FromId(FromId), ToId(ToId) {}
void ReplaceStmtWithStmt::run(const MatchFinder::MatchResult &Result) {
const Stmt *FromMatch = Result.Nodes.getStmtAs<Stmt>(FromId);
const Stmt *ToMatch = Result.Nodes.getStmtAs<Stmt>(ToId);
if (FromMatch && ToMatch)
Replace.insert(replaceStmtWithStmt(
*Result.SourceManager, *FromMatch, *ToMatch));
}
ReplaceIfStmtWithItsBody::ReplaceIfStmtWithItsBody(StringRef Id,
bool PickTrueBranch)
: Id(Id), PickTrueBranch(PickTrueBranch) {}
void ReplaceIfStmtWithItsBody::run(const MatchFinder::MatchResult &Result) {
if (const IfStmt *Node = Result.Nodes.getStmtAs<IfStmt>(Id)) {
const Stmt *Body = PickTrueBranch ? Node->getThen() : Node->getElse();
if (Body) {
Replace.insert(replaceStmtWithStmt(*Result.SourceManager, *Node, *Body));
} else if (!PickTrueBranch) {
// If we want to use the 'else'-branch, but it doesn't exist, delete
// the whole 'if'.
Replace.insert(replaceStmtWithText(*Result.SourceManager, *Node, ""));
}
}
}
} // end namespace ast_matchers
} // end namespace clang

View File

@ -1,5 +1,6 @@
add_clang_unittest(ASTMatchersTests
ASTMatchersTest.cpp)
ASTMatchersTest.cpp
RefactoringCallbacksTest.cpp)
target_link_libraries(ASTMatchersTests
gtest gtest_main clangASTMatchers clangTooling)

View File

@ -0,0 +1,98 @@
//===- unittest/ASTMatchers/RefactoringCallbacksTest.cpp ------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/RefactoringCallbacks.h"
#include "../Tooling/RewriterTestContext.h"
#include "gtest/gtest.h"
namespace clang {
namespace ast_matchers {
template <typename T>
void expectRewritten(const std::string &Code,
const std::string &Expected,
const T &AMatcher,
RefactoringCallback &Callback) {
MatchFinder Finder;
Finder.addMatcher(AMatcher, &Callback);
OwningPtr<tooling::FrontendActionFactory> Factory(
tooling::newFrontendActionFactory(&Finder));
ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), Code))
<< "Parsing error in \"" << Code << "\"";
RewriterTestContext Context;
FileID ID = Context.createInMemoryFile("input.cc", Code);
EXPECT_TRUE(tooling::applyAllReplacements(Callback.getReplacements(),
Context.Rewrite));
EXPECT_EQ(Expected, Context.getRewrittenText(ID));
}
TEST(RefactoringCallbacksTest, ReplacesStmtsWithString) {
std::string Code = "void f() { int i = 1; }";
std::string Expected = "void f() { ; }";
ReplaceStmtWithText Callback("id", ";");
expectRewritten(Code, Expected, id("id", declarationStatement()), Callback);
}
TEST(RefactoringCallbacksTest, ReplacesStmtsInCalledMacros) {
std::string Code = "#define A void f() { int i = 1; }\nA";
std::string Expected = "#define A void f() { ; }\nA";
ReplaceStmtWithText Callback("id", ";");
expectRewritten(Code, Expected, id("id", declarationStatement()), Callback);
}
TEST(RefactoringCallbacksTest, IgnoresStmtsInUncalledMacros) {
std::string Code = "#define A void f() { int i = 1; }";
std::string Expected = "#define A void f() { int i = 1; }";
ReplaceStmtWithText Callback("id", ";");
expectRewritten(Code, Expected, id("id", declarationStatement()), Callback);
}
TEST(RefactoringCallbacksTest, ReplacesInteger) {
std::string Code = "void f() { int i = 1; }";
std::string Expected = "void f() { int i = 2; }";
ReplaceStmtWithText Callback("id", "2");
expectRewritten(Code, Expected, id("id", expression(integerLiteral())),
Callback);
}
TEST(RefactoringCallbacksTest, ReplacesStmtWithStmt) {
std::string Code = "void f() { int i = false ? 1 : i * 2; }";
std::string Expected = "void f() { int i = i * 2; }";
ReplaceStmtWithStmt Callback("always-false", "should-be");
expectRewritten(Code, Expected,
id("always-false", conditionalOperator(
hasCondition(boolLiteral(equals(false))),
hasFalseExpression(id("should-be", expression())))),
Callback);
}
TEST(RefactoringCallbacksTest, ReplacesIfStmt) {
std::string Code = "bool a; void f() { if (a) f(); else a = true; }";
std::string Expected = "bool a; void f() { f(); }";
ReplaceIfStmtWithItsBody Callback("id", true);
expectRewritten(Code, Expected,
id("id", ifStmt(
hasCondition(implicitCast(hasSourceExpression(
declarationReference(to(variable(hasName("a"))))))))),
Callback);
}
TEST(RefactoringCallbacksTest, RemovesEntireIfOnEmptyElse) {
std::string Code = "void f() { if (false) int i = 0; }";
std::string Expected = "void f() { }";
ReplaceIfStmtWithItsBody Callback("id", false);
expectRewritten(Code, Expected,
id("id", ifStmt(hasCondition(boolLiteral(equals(false))))),
Callback);
}
} // end namespace ast_matchers
} // end namespace clang