1 //===- ComputeReplacements.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 #include "clang/Tooling/Core/Replacement.h"
9 #include "clang/Tooling/Syntax/Mutations.h"
10 #include "clang/Tooling/Syntax/TokenBufferTokenManager.h"
11 #include "clang/Tooling/Syntax/Tokens.h"
12 #include "clang/Tooling/Syntax/Tree.h"
13 #include "llvm/Support/Error.h"
14 
15 using namespace clang;
16 
17 namespace {
18 using ProcessTokensFn = llvm::function_ref<void(llvm::ArrayRef<syntax::Token>,
19                                                 bool /*IsOriginal*/)>;
20 /// Enumerates spans of tokens from the tree consecutively laid out in memory.
21 void enumerateTokenSpans(const syntax::Tree *Root,
22                          const syntax::TokenBufferTokenManager &STM,
23                          ProcessTokensFn Callback) {
24   struct Enumerator {
25     Enumerator(const syntax::TokenBufferTokenManager &STM,
26                ProcessTokensFn Callback)
27         : STM(STM), SpanBegin(nullptr), SpanEnd(nullptr), SpanIsOriginal(false),
28           Callback(Callback) {}
29 
30     void run(const syntax::Tree *Root) {
31       process(Root);
32       // Report the last span to the user.
33       if (SpanBegin)
34         Callback(llvm::makeArrayRef(SpanBegin, SpanEnd), SpanIsOriginal);
35     }
36 
37   private:
38     void process(const syntax::Node *N) {
39       if (auto *T = dyn_cast<syntax::Tree>(N)) {
40         for (const auto *C = T->getFirstChild(); C != nullptr;
41              C = C->getNextSibling())
42           process(C);
43         return;
44       }
45 
46       auto *L = cast<syntax::Leaf>(N);
47       if (SpanEnd == STM.getToken(L->getTokenKey()) &&
48           SpanIsOriginal == L->isOriginal()) {
49         // Extend the current span.
50         ++SpanEnd;
51         return;
52       }
53       // Report the current span to the user.
54       if (SpanBegin)
55         Callback(llvm::makeArrayRef(SpanBegin, SpanEnd), SpanIsOriginal);
56       // Start recording a new span.
57       SpanBegin = STM.getToken(L->getTokenKey());
58       SpanEnd = SpanBegin + 1;
59       SpanIsOriginal = L->isOriginal();
60     }
61 
62     const syntax::TokenBufferTokenManager &STM;
63     const syntax::Token *SpanBegin;
64     const syntax::Token *SpanEnd;
65     bool SpanIsOriginal;
66     ProcessTokensFn Callback;
67   };
68 
69   return Enumerator(STM, Callback).run(Root);
70 }
71 
72 syntax::FileRange rangeOfExpanded(const syntax::TokenBufferTokenManager &STM,
73                                   llvm::ArrayRef<syntax::Token> Expanded) {
74   const auto &Buffer = STM.tokenBuffer();
75   const auto &SM = STM.sourceManager();
76 
77   // Check that \p Expanded actually points into expanded tokens.
78   assert(Buffer.expandedTokens().begin() <= Expanded.begin());
79   assert(Expanded.end() < Buffer.expandedTokens().end());
80 
81   if (Expanded.empty())
82     // (!) empty tokens must always point before end().
83     return syntax::FileRange(
84         SM, SM.getExpansionLoc(Expanded.begin()->location()), /*Length=*/0);
85 
86   auto Spelled = Buffer.spelledForExpanded(Expanded);
87   assert(Spelled && "could not find spelled tokens for expanded");
88   return syntax::Token::range(SM, Spelled->front(), Spelled->back());
89 }
90 } // namespace
91 
92 tooling::Replacements
93 syntax::computeReplacements(const TokenBufferTokenManager &TBTM,
94                             const syntax::TranslationUnit &TU) {
95   const auto &Buffer = TBTM.tokenBuffer();
96   const auto &SM = TBTM.sourceManager();
97 
98   tooling::Replacements Replacements;
99   // Text inserted by the replacement we are building now.
100   std::string Replacement;
101   auto emitReplacement = [&](llvm::ArrayRef<syntax::Token> ReplacedRange) {
102     if (ReplacedRange.empty() && Replacement.empty())
103       return;
104     llvm::cantFail(Replacements.add(tooling::Replacement(
105         SM, rangeOfExpanded(TBTM, ReplacedRange).toCharRange(SM),
106         Replacement)));
107     Replacement = "";
108   };
109   const syntax::Token *NextOriginal = Buffer.expandedTokens().begin();
110   enumerateTokenSpans(
111       &TU, TBTM, [&](llvm::ArrayRef<syntax::Token> Tokens, bool IsOriginal) {
112         if (!IsOriginal) {
113           Replacement +=
114               syntax::Token::range(SM, Tokens.front(), Tokens.back()).text(SM);
115           return;
116         }
117         assert(NextOriginal <= Tokens.begin());
118         // We are looking at a span of original tokens.
119         if (NextOriginal != Tokens.begin()) {
120           // There is a gap, record a replacement or deletion.
121           emitReplacement(llvm::makeArrayRef(NextOriginal, Tokens.begin()));
122         } else {
123           // No gap, but we may have pending insertions. Emit them now.
124           emitReplacement(llvm::makeArrayRef(NextOriginal, /*Length=*/0));
125         }
126         NextOriginal = Tokens.end();
127       });
128 
129   // We might have pending replacements at the end of file. If so, emit them.
130   emitReplacement(llvm::makeArrayRef(
131       NextOriginal, Buffer.expandedTokens().drop_back().end()));
132 
133   return Replacements;
134 }
135