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.
enumerateTokenSpans(const syntax::Tree * Root,ProcessTokensFn Callback)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
rangeOfExpanded(const syntax::Arena & A,llvm::ArrayRef<syntax::Token> Expanded)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
computeReplacements(const syntax::Arena & A,const syntax::TranslationUnit & TU)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