ExpandMacro.cpp
4.19 KB
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
//===--- 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