xref: /freebsd/contrib/llvm-project/clang/lib/Tooling/Syntax/ComputeReplacements.cpp (revision 994c943a0098e38bff96f9f9169cbb38542c39c1)
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