1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
//===--- 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;
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)
/// 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 = 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));
}
} // namespace
} // namespace clangd
} // namespace clang
|