You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
8.8 KiB
241 lines
8.8 KiB
//===--- RefactoringCallbacks.cpp - Structural query framework ------------===//
|
|
//
|
|
// 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/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 {
|
|
|
|
RefactoringCallback::RefactoringCallback() {}
|
|
tooling::Replacements &RefactoringCallback::getReplacements() {
|
|
return Replace;
|
|
}
|
|
|
|
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[std::string(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 std::make_unique<RefactoringASTConsumer>(*this);
|
|
}
|
|
|
|
static Replacement replaceStmtWithText(SourceManager &Sources, const Stmt &From,
|
|
StringRef Text) {
|
|
return tooling::Replacement(
|
|
Sources, CharSourceRange::getTokenRange(From.getSourceRange()), Text);
|
|
}
|
|
static 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(std::string(FromId)), ToText(std::string(ToText)) {}
|
|
|
|
void ReplaceStmtWithText::run(
|
|
const ast_matchers::MatchFinder::MatchResult &Result) {
|
|
if (const Stmt *FromMatch = Result.Nodes.getNodeAs<Stmt>(FromId)) {
|
|
auto Err = Replace.add(tooling::Replacement(
|
|
*Result.SourceManager,
|
|
CharSourceRange::getTokenRange(FromMatch->getSourceRange()), ToText));
|
|
// FIXME: better error handling. For now, just print error message in the
|
|
// release version.
|
|
if (Err) {
|
|
llvm::errs() << llvm::toString(std::move(Err)) << "\n";
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
ReplaceStmtWithStmt::ReplaceStmtWithStmt(StringRef FromId, StringRef ToId)
|
|
: FromId(std::string(FromId)), ToId(std::string(ToId)) {}
|
|
|
|
void ReplaceStmtWithStmt::run(
|
|
const ast_matchers::MatchFinder::MatchResult &Result) {
|
|
const Stmt *FromMatch = Result.Nodes.getNodeAs<Stmt>(FromId);
|
|
const Stmt *ToMatch = Result.Nodes.getNodeAs<Stmt>(ToId);
|
|
if (FromMatch && ToMatch) {
|
|
auto Err = Replace.add(
|
|
replaceStmtWithStmt(*Result.SourceManager, *FromMatch, *ToMatch));
|
|
// FIXME: better error handling. For now, just print error message in the
|
|
// release version.
|
|
if (Err) {
|
|
llvm::errs() << llvm::toString(std::move(Err)) << "\n";
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
ReplaceIfStmtWithItsBody::ReplaceIfStmtWithItsBody(StringRef Id,
|
|
bool PickTrueBranch)
|
|
: Id(std::string(Id)), PickTrueBranch(PickTrueBranch) {}
|
|
|
|
void ReplaceIfStmtWithItsBody::run(
|
|
const ast_matchers::MatchFinder::MatchResult &Result) {
|
|
if (const IfStmt *Node = Result.Nodes.getNodeAs<IfStmt>(Id)) {
|
|
const Stmt *Body = PickTrueBranch ? Node->getThen() : Node->getElse();
|
|
if (Body) {
|
|
auto Err =
|
|
Replace.add(replaceStmtWithStmt(*Result.SourceManager, *Node, *Body));
|
|
// FIXME: better error handling. For now, just print error message in the
|
|
// release version.
|
|
if (Err) {
|
|
llvm::errs() << llvm::toString(std::move(Err)) << "\n";
|
|
assert(false);
|
|
}
|
|
} else if (!PickTrueBranch) {
|
|
// If we want to use the 'else'-branch, but it doesn't exist, delete
|
|
// the whole 'if'.
|
|
auto Err =
|
|
Replace.add(replaceStmtWithText(*Result.SourceManager, *Node, ""));
|
|
// FIXME: better error handling. For now, just print error message in the
|
|
// release version.
|
|
if (Err) {
|
|
llvm::errs() << llvm::toString(std::move(Err)) << "\n";
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ReplaceNodeWithTemplate::ReplaceNodeWithTemplate(
|
|
llvm::StringRef FromId, std::vector<TemplateElement> Template)
|
|
: FromId(std::string(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 = std::string(
|
|
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,
|
|
std::string(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
|