ComputeReplacements.cpp revision 360784
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
13using namespace clang;
14
15namespace {
16using 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.
19void 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 (auto *C = T->firstChild(); C != nullptr; C = C->nextSibling())
36          process(C);
37        return;
38      }
39
40      auto *L = cast<syntax::Leaf>(N);
41      if (SpanEnd == L->token() && SpanIsOriginal == L->isOriginal()) {
42        // Extend the current span.
43        ++SpanEnd;
44        return;
45      }
46      // Report the current span to the user.
47      if (SpanBegin)
48        Callback(llvm::makeArrayRef(SpanBegin, SpanEnd), SpanIsOriginal);
49      // Start recording a new span.
50      SpanBegin = L->token();
51      SpanEnd = SpanBegin + 1;
52      SpanIsOriginal = L->isOriginal();
53    }
54
55    const syntax::Token *SpanBegin;
56    const syntax::Token *SpanEnd;
57    bool SpanIsOriginal;
58    ProcessTokensFn Callback;
59  };
60
61  return Enumerator(Callback).run(Root);
62}
63
64syntax::FileRange rangeOfExpanded(const syntax::Arena &A,
65                                  llvm::ArrayRef<syntax::Token> Expanded) {
66  auto &Buffer = A.tokenBuffer();
67  auto &SM = A.sourceManager();
68
69  // Check that \p Expanded actually points into expanded tokens.
70  assert(Buffer.expandedTokens().begin() <= Expanded.begin());
71  assert(Expanded.end() < Buffer.expandedTokens().end());
72
73  if (Expanded.empty())
74    // (!) empty tokens must always point before end().
75    return syntax::FileRange(
76        SM, SM.getExpansionLoc(Expanded.begin()->location()), /*Length=*/0);
77
78  auto Spelled = Buffer.spelledForExpanded(Expanded);
79  assert(Spelled && "could not find spelled tokens for expanded");
80  return syntax::Token::range(SM, Spelled->front(), Spelled->back());
81}
82} // namespace
83
84tooling::Replacements
85syntax::computeReplacements(const syntax::Arena &A,
86                            const syntax::TranslationUnit &TU) {
87  auto &Buffer = A.tokenBuffer();
88  auto &SM = A.sourceManager();
89
90  tooling::Replacements Replacements;
91  // Text inserted by the replacement we are building now.
92  std::string Replacement;
93  auto emitReplacement = [&](llvm::ArrayRef<syntax::Token> ReplacedRange) {
94    if (ReplacedRange.empty() && Replacement.empty())
95      return;
96    llvm::cantFail(Replacements.add(tooling::Replacement(
97        SM, rangeOfExpanded(A, ReplacedRange).toCharRange(SM), Replacement)));
98    Replacement = "";
99  };
100
101  const syntax::Token *NextOriginal = Buffer.expandedTokens().begin();
102  enumerateTokenSpans(
103      &TU, [&](llvm::ArrayRef<syntax::Token> Tokens, bool IsOriginal) {
104        if (!IsOriginal) {
105          Replacement +=
106              syntax::Token::range(SM, Tokens.front(), Tokens.back()).text(SM);
107          return;
108        }
109        assert(NextOriginal <= Tokens.begin());
110        // We are looking at a span of original tokens.
111        if (NextOriginal != Tokens.begin()) {
112          // There is a gap, record a replacement or deletion.
113          emitReplacement(llvm::makeArrayRef(NextOriginal, Tokens.begin()));
114        } else {
115          // No gap, but we may have pending insertions. Emit them now.
116          emitReplacement(llvm::makeArrayRef(NextOriginal, /*Length=*/0));
117        }
118        NextOriginal = Tokens.end();
119      });
120
121  // We might have pending replacements at the end of file. If so, emit them.
122  emitReplacement(llvm::makeArrayRef(
123      NextOriginal, Buffer.expandedTokens().drop_back().end()));
124
125  return Replacements;
126}
127