forked from OSchip/llvm-project
135 lines
4.2 KiB
C++
135 lines
4.2 KiB
C++
//===--- ExpandMacro.cpp -----------------------------------------*- 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 "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 {
|
|
|
|
/// Replaces a reference to a macro under the cursor with its expansion.
|
|
/// Before:
|
|
/// #define FOO(X) X+X
|
|
/// FOO(10*a)
|
|
/// ^^^
|
|
/// After:
|
|
/// #define FOO(X) X+X
|
|
/// 10*a+10*a
|
|
class ExpandMacro : public Tweak {
|
|
public:
|
|
const char *id() const override final;
|
|
Intent intent() const override { return Intent::Refactor; }
|
|
|
|
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)
|
|
|
|
/// Finds a spelled token that the cursor is pointing at.
|
|
static const syntax::Token *
|
|
findTokenUnderCursor(const SourceManager &SM,
|
|
llvm::ArrayRef<syntax::Token> Spelled,
|
|
unsigned CursorOffset) {
|
|
// Find the token that strats after the offset, then look at a previous one.
|
|
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;
|
|
// Check the token we found actually touches the cursor position.
|
|
--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;
|
|
// Also try the previous token when the cursor is at the boundary, e.g.
|
|
// FOO^()
|
|
// FOO^+
|
|
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) {
|
|
// FIXME: we currently succeed on selection at the end of the token, e.g.
|
|
// 'FOO[[ ]]BAR'. We should not trigger in that case.
|
|
|
|
// Find a token under the cursor.
|
|
auto *T = findIdentifierUnderCursor(Inputs.AST.getTokens(), Inputs.Cursor);
|
|
// We are interested only in identifiers, other tokens can't be macro names.
|
|
if (!T)
|
|
return false;
|
|
// If the identifier is a macro we will find the corresponding expansion.
|
|
auto Expansion = Inputs.AST.getTokens().expansionStartingAt(T);
|
|
if (!Expansion)
|
|
return false;
|
|
this->MacroName = 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 llvm::formatv("Expand macro '{0}'", MacroName);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|