1//===--- ExpandMacro.cpp -----------------------------------------*- C++-*-===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
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"
18#include <string>
19namespace clang {
20namespace clangd {
21namespace {
22
23/// Replaces a reference to a macro under the cursor with its expansion.
24/// Before:
25/// #define FOO(X) X+X
26/// FOO(10*a)
27/// ^^^
28/// After:
29/// #define FOO(X) X+X
30/// 10*a+10*a
31class ExpandMacro : public Tweak {
32public:
33 const char *id() const final;
34 llvm::StringLiteral kind() const override {
35 return CodeAction::REFACTOR_KIND;
36 }
37
38 bool prepare(const Selection &Inputs) override;
39 Expected<Tweak::Effect> apply(const Selection &Inputs) override;
40 std::string title() const override;
41
42private:
43 syntax::TokenBuffer::Expansion Expansion;
44 std::string MacroName;
45};
46
47REGISTER_TWEAK(ExpandMacro)
48
49/// Finds a spelled token that the cursor is pointing at.
50static const syntax::Token *
51findTokenUnderCursor(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(Range&: Spelled, P: [&](const syntax::Token &T) {
56 assert(T.location().isFileID());
57 return SM.getFileOffset(SpellingLoc: T.location()) <= CursorOffset;
58 });
59 if (It == Spelled.begin())
60 return nullptr;
61 // Check the token we found actually touches the cursor position.
62 --It;
63 return It->range(SM).touches(Offset: CursorOffset) ? It : nullptr;
64}
65
66static const syntax::Token *
67findIdentifierUnderCursor(const syntax::TokenBuffer &Tokens,
68 SourceLocation Cursor) {
69 assert(Cursor.isFileID());
70
71 auto &SM = Tokens.sourceManager();
72 auto Spelled = Tokens.spelledTokens(FID: SM.getFileID(SpellingLoc: Cursor));
73
74 auto *T = findTokenUnderCursor(SM, Spelled, CursorOffset: SM.getFileOffset(SpellingLoc: Cursor));
75 if (!T)
76 return nullptr;
77 if (T->kind() == tok::identifier)
78 return T;
79 // Also try the previous token when the cursor is at the boundary, e.g.
80 // FOO^()
81 // FOO^+
82 if (T == Spelled.begin())
83 return nullptr;
84 --T;
85 if (T->endLocation() != Cursor || T->kind() != tok::identifier)
86 return nullptr;
87 return T;
88}
89
90bool 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.
93
94 // Find a token under the cursor.
95 auto *T = findIdentifierUnderCursor(Tokens: Inputs.AST->getTokens(), Cursor: Inputs.Cursor);
96 // We are interested only in identifiers, other tokens can't be macro names.
97 if (!T)
98 return false;
99 // If the identifier is a macro we will find the corresponding expansion.
100 auto Expansion = Inputs.AST->getTokens().expansionStartingAt(Spelled: T);
101 if (!Expansion)
102 return false;
103 this->MacroName = std::string(T->text(SM: Inputs.AST->getSourceManager()));
104 this->Expansion = *Expansion;
105 return true;
106}
107
108Expected<Tweak::Effect> ExpandMacro::apply(const Selection &Inputs) {
109 auto &SM = Inputs.AST->getSourceManager();
110
111 std::string Replacement;
112 for (const syntax::Token &T : Expansion.Expanded) {
113 Replacement += T.text(SM);
114 Replacement += " ";
115 }
116 if (!Replacement.empty()) {
117 assert(Replacement.back() == ' ');
118 Replacement.pop_back();
119 }
120
121 CharSourceRange MacroRange =
122 CharSourceRange::getCharRange(B: Expansion.Spelled.front().location(),
123 E: Expansion.Spelled.back().endLocation());
124
125 tooling::Replacements Reps;
126 llvm::cantFail(Err: Reps.add(R: tooling::Replacement(SM, MacroRange, Replacement)));
127 return Effect::mainFileEdit(SM, Replacements: std::move(Reps));
128}
129
130std::string ExpandMacro::title() const {
131 return std::string(llvm::formatv(Fmt: "Expand macro '{0}'", Vals: MacroName));
132}
133
134} // namespace
135} // namespace clangd
136} // namespace clang
137

source code of clang-tools-extra/clangd/refactor/tweaks/ExpandMacro.cpp