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 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 | |
42 | private: |
43 | syntax::TokenBuffer::Expansion Expansion; |
44 | std::string MacroName; |
45 | }; |
46 | |
47 | 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(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 | |
66 | static const syntax::Token * |
67 | findIdentifierUnderCursor(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 | |
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(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 | |
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(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 | |
130 | std::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 | |