135 lines · cpp
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-exception6//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(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 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(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} // namespace91 92tooling::Replacements93syntax::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