diff options
Diffstat (limited to 'lib/Tooling/RefactoringCallbacks.cpp')
-rw-r--r-- | lib/Tooling/RefactoringCallbacks.cpp | 154 |
1 files changed, 144 insertions, 10 deletions
diff --git a/lib/Tooling/RefactoringCallbacks.cpp b/lib/Tooling/RefactoringCallbacks.cpp index e900c23e4f64..9fd333ca554e 100644 --- a/lib/Tooling/RefactoringCallbacks.cpp +++ b/lib/Tooling/RefactoringCallbacks.cpp @@ -9,8 +9,13 @@ // // //===----------------------------------------------------------------------===// -#include "clang/Lex/Lexer.h" #include "clang/Tooling/RefactoringCallbacks.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" + +using llvm::StringError; +using llvm::make_error; namespace clang { namespace tooling { @@ -20,18 +25,62 @@ tooling::Replacements &RefactoringCallback::getReplacements() { return Replace; } -static Replacement replaceStmtWithText(SourceManager &Sources, - const Stmt &From, +ASTMatchRefactorer::ASTMatchRefactorer( + std::map<std::string, Replacements> &FileToReplaces) + : FileToReplaces(FileToReplaces) {} + +void ASTMatchRefactorer::addDynamicMatcher( + const ast_matchers::internal::DynTypedMatcher &Matcher, + RefactoringCallback *Callback) { + MatchFinder.addDynamicMatcher(Matcher, Callback); + Callbacks.push_back(Callback); +} + +class RefactoringASTConsumer : public ASTConsumer { +public: + explicit RefactoringASTConsumer(ASTMatchRefactorer &Refactoring) + : Refactoring(Refactoring) {} + + void HandleTranslationUnit(ASTContext &Context) override { + // The ASTMatchRefactorer is re-used between translation units. + // Clear the matchers so that each Replacement is only emitted once. + for (const auto &Callback : Refactoring.Callbacks) { + Callback->getReplacements().clear(); + } + Refactoring.MatchFinder.matchAST(Context); + for (const auto &Callback : Refactoring.Callbacks) { + for (const auto &Replacement : Callback->getReplacements()) { + llvm::Error Err = + Refactoring.FileToReplaces[Replacement.getFilePath()].add( + Replacement); + if (Err) { + llvm::errs() << "Skipping replacement " << Replacement.toString() + << " due to this error:\n" + << toString(std::move(Err)) << "\n"; + } + } + } + } + +private: + ASTMatchRefactorer &Refactoring; +}; + +std::unique_ptr<ASTConsumer> ASTMatchRefactorer::newASTConsumer() { + return llvm::make_unique<RefactoringASTConsumer>(*this); +} + +static Replacement replaceStmtWithText(SourceManager &Sources, const Stmt &From, StringRef Text) { - return tooling::Replacement(Sources, CharSourceRange::getTokenRange( - From.getSourceRange()), Text); + return tooling::Replacement( + Sources, CharSourceRange::getTokenRange(From.getSourceRange()), Text); } -static Replacement replaceStmtWithStmt(SourceManager &Sources, - const Stmt &From, +static Replacement replaceStmtWithStmt(SourceManager &Sources, const Stmt &From, const Stmt &To) { - return replaceStmtWithText(Sources, From, Lexer::getSourceText( - CharSourceRange::getTokenRange(To.getSourceRange()), - Sources, LangOptions())); + return replaceStmtWithText( + Sources, From, + Lexer::getSourceText(CharSourceRange::getTokenRange(To.getSourceRange()), + Sources, LangOptions())); } ReplaceStmtWithText::ReplaceStmtWithText(StringRef FromId, StringRef ToText) @@ -103,5 +152,90 @@ void ReplaceIfStmtWithItsBody::run( } } +ReplaceNodeWithTemplate::ReplaceNodeWithTemplate( + llvm::StringRef FromId, std::vector<TemplateElement> Template) + : FromId(FromId), Template(std::move(Template)) {} + +llvm::Expected<std::unique_ptr<ReplaceNodeWithTemplate>> +ReplaceNodeWithTemplate::create(StringRef FromId, StringRef ToTemplate) { + std::vector<TemplateElement> ParsedTemplate; + for (size_t Index = 0; Index < ToTemplate.size();) { + if (ToTemplate[Index] == '$') { + if (ToTemplate.substr(Index, 2) == "$$") { + Index += 2; + ParsedTemplate.push_back( + TemplateElement{TemplateElement::Literal, "$"}); + } else if (ToTemplate.substr(Index, 2) == "${") { + size_t EndOfIdentifier = ToTemplate.find("}", Index); + if (EndOfIdentifier == std::string::npos) { + return make_error<StringError>( + "Unterminated ${...} in replacement template near " + + ToTemplate.substr(Index), + llvm::inconvertibleErrorCode()); + } + std::string SourceNodeName = + ToTemplate.substr(Index + 2, EndOfIdentifier - Index - 2); + ParsedTemplate.push_back( + TemplateElement{TemplateElement::Identifier, SourceNodeName}); + Index = EndOfIdentifier + 1; + } else { + return make_error<StringError>( + "Invalid $ in replacement template near " + + ToTemplate.substr(Index), + llvm::inconvertibleErrorCode()); + } + } else { + size_t NextIndex = ToTemplate.find('$', Index + 1); + ParsedTemplate.push_back( + TemplateElement{TemplateElement::Literal, + ToTemplate.substr(Index, NextIndex - Index)}); + Index = NextIndex; + } + } + return std::unique_ptr<ReplaceNodeWithTemplate>( + new ReplaceNodeWithTemplate(FromId, std::move(ParsedTemplate))); +} + +void ReplaceNodeWithTemplate::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + const auto &NodeMap = Result.Nodes.getMap(); + + std::string ToText; + for (const auto &Element : Template) { + switch (Element.Type) { + case TemplateElement::Literal: + ToText += Element.Value; + break; + case TemplateElement::Identifier: { + auto NodeIter = NodeMap.find(Element.Value); + if (NodeIter == NodeMap.end()) { + llvm::errs() << "Node " << Element.Value + << " used in replacement template not bound in Matcher \n"; + llvm::report_fatal_error("Unbound node in replacement template."); + } + CharSourceRange Source = + CharSourceRange::getTokenRange(NodeIter->second.getSourceRange()); + ToText += Lexer::getSourceText(Source, *Result.SourceManager, + Result.Context->getLangOpts()); + break; + } + } + } + if (NodeMap.count(FromId) == 0) { + llvm::errs() << "Node to be replaced " << FromId + << " not bound in query.\n"; + llvm::report_fatal_error("FromId node not bound in MatchResult"); + } + auto Replacement = + tooling::Replacement(*Result.SourceManager, &NodeMap.at(FromId), ToText, + Result.Context->getLangOpts()); + llvm::Error Err = Replace.add(Replacement); + if (Err) { + llvm::errs() << "Query and replace failed in " << Replacement.getFilePath() + << "! " << llvm::toString(std::move(Err)) << "\n"; + llvm::report_fatal_error("Replacement failed"); + } +} + } // end namespace tooling } // end namespace clang |