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
15using namespace clang;
16
17namespace {
18using 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.
21void 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(N: Root);
32 // Report the last span to the user.
33 if (SpanBegin)
34 Callback(llvm::ArrayRef(SpanBegin, SpanEnd), SpanIsOriginal);
35 }
36
37 private:
38 void process(const syntax::Node *N) {
39 if (auto *T = dyn_cast<syntax::Tree>(Val: N)) {
40 for (const auto *C = T->getFirstChild(); C != nullptr;
41 C = C->getNextSibling())
42 process(N: C);
43 return;
44 }
45
46 auto *L = cast<syntax::Leaf>(Val: N);
47 if (SpanEnd == STM.getToken(I: 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::ArrayRef(SpanBegin, SpanEnd), SpanIsOriginal);
56 // Start recording a new span.
57 SpanBegin = STM.getToken(I: 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
72syntax::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(Loc: 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, First: Spelled->front(), Last: Spelled->back());
89}
90} // namespace
91
92tooling::Replacements
93syntax::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(Err: Replacements.add(R: tooling::Replacement(
105 SM, rangeOfExpanded(STM: TBTM, Expanded: ReplacedRange).toCharRange(SM),
106 Replacement)));
107 Replacement = "";
108 };
109 const syntax::Token *NextOriginal = Buffer.expandedTokens().begin();
110 enumerateTokenSpans(
111 Root: &TU, STM: TBTM, Callback: [&](llvm::ArrayRef<syntax::Token> Tokens, bool IsOriginal) {
112 if (!IsOriginal) {
113 Replacement +=
114 syntax::Token::range(SM, First: Tokens.front(), Last: 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::ArrayRef(NextOriginal, Tokens.begin()));
122 } else {
123 // No gap, but we may have pending insertions. Emit them now.
124 emitReplacement(llvm::ArrayRef(NextOriginal, /*Length=*/(size_t)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(
131 llvm::ArrayRef(NextOriginal, Buffer.expandedTokens().drop_back().end()));
132
133 return Replacements;
134}
135