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(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>(N)) { 40 for (const auto *C = T->getFirstChild(); C != nullptr; 41 C = C->getNextSibling()) 42 process(C); 43 return; 44 } 45 46 auto *L = cast<syntax::Leaf>(N); 47 if (SpanEnd == STM.getToken(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(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(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, Spelled->front(), 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(Replacements.add(tooling::Replacement( 105 SM, rangeOfExpanded(TBTM, ReplacedRange).toCharRange(SM), 106 Replacement))); 107 Replacement = ""; 108 }; 109 const syntax::Token *NextOriginal = Buffer.expandedTokens().begin(); 110 enumerateTokenSpans( 111 &TU, TBTM, [&](llvm::ArrayRef<syntax::Token> Tokens, bool IsOriginal) { 112 if (!IsOriginal) { 113 Replacement += 114 syntax::Token::range(SM, Tokens.front(), 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