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