#include "refactor/Tweak.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Syntax/Tokens.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include <string>
namespace clang {
namespace clangd {
namespace {
class ExpandMacro : public Tweak {
public:
const char *id() const final;
llvm::StringLiteral kind() const override {
return CodeAction::REFACTOR_KIND;
}
bool prepare(const Selection &Inputs) override;
Expected<Tweak::Effect> apply(const Selection &Inputs) override;
std::string title() const override;
private:
syntax::TokenBuffer::Expansion Expansion;
std::string MacroName;
};
REGISTER_TWEAK(ExpandMacro)
static const syntax::Token *
findTokenUnderCursor(const SourceManager &SM,
llvm::ArrayRef<syntax::Token> Spelled,
unsigned CursorOffset) {
auto *It = llvm::partition_point(Spelled, [&](const syntax::Token &T) {
assert(T.location().isFileID());
return SM.getFileOffset(T.location()) <= CursorOffset;
});
if (It == Spelled.begin())
return nullptr;
--It;
return It->range(SM).touches(CursorOffset) ? It : nullptr;
}
static const syntax::Token *
findIdentifierUnderCursor(const syntax::TokenBuffer &Tokens,
SourceLocation Cursor) {
assert(Cursor.isFileID());
auto &SM = Tokens.sourceManager();
auto Spelled = Tokens.spelledTokens(SM.getFileID(Cursor));
auto *T = findTokenUnderCursor(SM, Spelled, SM.getFileOffset(Cursor));
if (!T)
return nullptr;
if (T->kind() == tok::identifier)
return T;
if (T == Spelled.begin())
return nullptr;
--T;
if (T->endLocation() != Cursor || T->kind() != tok::identifier)
return nullptr;
return T;
}
bool ExpandMacro::prepare(const Selection &Inputs) {
auto *T = findIdentifierUnderCursor(Inputs.AST->getTokens(), Inputs.Cursor);
if (!T)
return false;
auto Expansion = Inputs.AST->getTokens().expansionStartingAt(T);
if (!Expansion)
return false;
this->MacroName = std::string(T->text(Inputs.AST->getSourceManager()));
this->Expansion = *Expansion;
return true;
}
Expected<Tweak::Effect> ExpandMacro::apply(const Selection &Inputs) {
auto &SM = Inputs.AST->getSourceManager();
std::string Replacement;
for (const syntax::Token &T : Expansion.Expanded) {
Replacement += T.text(SM);
Replacement += " ";
}
if (!Replacement.empty()) {
assert(Replacement.back() == ' ');
Replacement.pop_back();
}
CharSourceRange MacroRange =
CharSourceRange::getCharRange(Expansion.Spelled.front().location(),
Expansion.Spelled.back().endLocation());
tooling::Replacements Reps;
llvm::cantFail(Reps.add(tooling::Replacement(SM, MacroRange, Replacement)));
return Effect::mainFileEdit(SM, std::move(Reps));
}
std::string ExpandMacro::title() const {
return std::string(llvm::formatv("Expand macro '{0}'", MacroName));
}
}
}
}