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>
19 namespace clang {
20 namespace clangd {
21 namespace {
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
31 class ExpandMacro : public Tweak {
32 public:
33   const char *id() const override final;
kind() const34   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 
42 private:
43   syntax::TokenBuffer::Expansion Expansion;
44   std::string MacroName;
45 };
46 
REGISTER_TWEAK(ExpandMacro) const47 REGISTER_TWEAK(ExpandMacro)
48 
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;
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(CursorOffset) ? It : nullptr;
64 }
65 
66 static const syntax::Token *
findIdentifierUnderCursor(const syntax::TokenBuffer & Tokens,SourceLocation Cursor)67 findIdentifierUnderCursor(const syntax::TokenBuffer &Tokens,
68                           SourceLocation Cursor) {
69   assert(Cursor.isFileID());
70 
71   auto &SM = Tokens.sourceManager();
72   auto Spelled = Tokens.spelledTokens(SM.getFileID(Cursor));
73 
74   auto *T = findTokenUnderCursor(SM, Spelled, SM.getFileOffset(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 
prepare(const Selection & Inputs)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.
93 
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.
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(T);
101   if (!Expansion)
102     return false;
103   this->MacroName = std::string(T->text(Inputs.AST->getSourceManager()));
104   this->Expansion = *Expansion;
105   return true;
106 }
107 
apply(const Selection & Inputs)108 Expected<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(Expansion.Spelled.front().location(),
123                                     Expansion.Spelled.back().endLocation());
124 
125   tooling::Replacements Reps;
126   llvm::cantFail(Reps.add(tooling::Replacement(SM, MacroRange, Replacement)));
127   return Effect::mainFileEdit(SM, std::move(Reps));
128 }
129 
title() const130 std::string ExpandMacro::title() const {
131   return std::string(llvm::formatv("Expand macro '{0}'", MacroName));
132 }
133 
134 } // namespace
135 } // namespace clangd
136 } // namespace clang
137