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(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 | |
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(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 | |
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(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 | |