1 //===--- ExpandMacro.cpp -----------------------------------------*- C++-*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "refactor/Tweak.h"
10 #include "clang/Basic/SourceLocation.h"
11 #include "clang/Basic/SourceManager.h"
12 #include "clang/Basic/TokenKinds.h"
13 #include "clang/Tooling/Core/Replacement.h"
14 #include "clang/Tooling/Syntax/Tokens.h"
15 #include "llvm/ADT/ArrayRef.h"
16 #include "llvm/ADT/STLExtras.h"
17 #include "llvm/Support/Error.h"
23 /// Replaces a reference to a macro under the cursor with its expansion.
25 /// #define FOO(X) X+X
29 /// #define FOO(X) X+X
31 class ExpandMacro
: public Tweak
{
33 const char *id() const final
;
34 llvm::StringLiteral
kind() const override
{
35 return CodeAction::REFACTOR_KIND
;
38 bool prepare(const Selection
&Inputs
) override
;
39 Expected
<Tweak::Effect
> apply(const Selection
&Inputs
) override
;
40 std::string
title() const override
;
43 syntax::TokenBuffer::Expansion Expansion
;
44 std::string MacroName
;
47 REGISTER_TWEAK(ExpandMacro
)
49 /// Finds a spelled token that the cursor is pointing at.
50 static const syntax::Token
*
51 findTokenUnderCursor(const SourceManager
&SM
,
52 llvm::ArrayRef
<syntax::Token
> Spelled
,
53 unsigned CursorOffset
) {
54 // Find the token that strats after the offset, then look at a previous one.
55 auto *It
= llvm::partition_point(Spelled
, [&](const syntax::Token
&T
) {
56 assert(T
.location().isFileID());
57 return SM
.getFileOffset(T
.location()) <= CursorOffset
;
59 if (It
== Spelled
.begin())
61 // Check the token we found actually touches the cursor position.
63 return It
->range(SM
).touches(CursorOffset
) ? It
: nullptr;
66 static const syntax::Token
*
67 findIdentifierUnderCursor(const syntax::TokenBuffer
&Tokens
,
68 SourceLocation Cursor
) {
69 assert(Cursor
.isFileID());
71 auto &SM
= Tokens
.sourceManager();
72 auto Spelled
= Tokens
.spelledTokens(SM
.getFileID(Cursor
));
74 auto *T
= findTokenUnderCursor(SM
, Spelled
, SM
.getFileOffset(Cursor
));
77 if (T
->kind() == tok::identifier
)
79 // Also try the previous token when the cursor is at the boundary, e.g.
82 if (T
== Spelled
.begin())
85 if (T
->endLocation() != Cursor
|| T
->kind() != tok::identifier
)
90 bool ExpandMacro::prepare(const Selection
&Inputs
) {
91 // FIXME: we currently succeed on selection at the end of the token, e.g.
92 // 'FOO[[ ]]BAR'. We should not trigger in that case.
94 // Find a token under the cursor.
95 auto *T
= findIdentifierUnderCursor(Inputs
.AST
->getTokens(), Inputs
.Cursor
);
96 // We are interested only in identifiers, other tokens can't be macro names.
99 // If the identifier is a macro we will find the corresponding expansion.
100 auto Expansion
= Inputs
.AST
->getTokens().expansionStartingAt(T
);
103 this->MacroName
= std::string(T
->text(Inputs
.AST
->getSourceManager()));
104 this->Expansion
= *Expansion
;
108 Expected
<Tweak::Effect
> ExpandMacro::apply(const Selection
&Inputs
) {
109 auto &SM
= Inputs
.AST
->getSourceManager();
111 std::string Replacement
;
112 for (const syntax::Token
&T
: Expansion
.Expanded
) {
113 Replacement
+= T
.text(SM
);
116 if (!Replacement
.empty()) {
117 assert(Replacement
.back() == ' ');
118 Replacement
.pop_back();
121 CharSourceRange MacroRange
=
122 CharSourceRange::getCharRange(Expansion
.Spelled
.front().location(),
123 Expansion
.Spelled
.back().endLocation());
125 tooling::Replacements Reps
;
126 llvm::cantFail(Reps
.add(tooling::Replacement(SM
, MacroRange
, Replacement
)));
127 return Effect::mainFileEdit(SM
, std::move(Reps
));
130 std::string
ExpandMacro::title() const {
131 return std::string(llvm::formatv("Expand macro '{0}'", MacroName
));
135 } // namespace clangd