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