reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
//===--- ExpandMacro.cpp -----------------------------------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "refactor/Tweak.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Syntax/Tokens.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include <string>
namespace clang {
namespace clangd {
namespace {

/// Replaces a reference to a macro under the cursor with its expansion.
/// Before:
///   #define FOO(X) X+X
///   FOO(10*a)
///   ^^^
/// After:
///   #define FOO(X) X+X
///   10*a+10*a
class ExpandMacro : public Tweak {
public:
  const char *id() const override final;
  Intent intent() const override { return Intent::Refactor; }

  bool prepare(const Selection &Inputs) override;
  Expected<Tweak::Effect> apply(const Selection &Inputs) override;
  std::string title() const override;

private:
  syntax::TokenBuffer::Expansion Expansion;
  std::string MacroName;
};

REGISTER_TWEAK(ExpandMacro)

/// Finds a spelled token that the cursor is pointing at.
static const syntax::Token *
findTokenUnderCursor(const SourceManager &SM,
                     llvm::ArrayRef<syntax::Token> Spelled,
                     unsigned CursorOffset) {
  // Find the token that strats after the offset, then look at a previous one.
  auto It = llvm::partition_point(Spelled, [&](const syntax::Token &T) {
    assert(T.location().isFileID());
    return SM.getFileOffset(T.location()) <= CursorOffset;
  });
  if (It == Spelled.begin())
    return nullptr;
  // Check the token we found actually touches the cursor position.
  --It;
  return It->range(SM).touches(CursorOffset) ? It : nullptr;
}

static const syntax::Token *
findIdentifierUnderCursor(const syntax::TokenBuffer &Tokens,
                          SourceLocation Cursor) {
  assert(Cursor.isFileID());

  auto &SM = Tokens.sourceManager();
  auto Spelled = Tokens.spelledTokens(SM.getFileID(Cursor));

  auto *T = findTokenUnderCursor(SM, Spelled, SM.getFileOffset(Cursor));
  if (!T)
    return nullptr;
  if (T->kind() == tok::identifier)
    return T;
  // Also try the previous token when the cursor is at the boundary, e.g.
  //   FOO^()
  //   FOO^+
  if (T == Spelled.begin())
    return nullptr;
  --T;
  if (T->endLocation() != Cursor || T->kind() != tok::identifier)
    return nullptr;
  return T;
}

bool ExpandMacro::prepare(const Selection &Inputs) {
  // FIXME: we currently succeed on selection at the end of the token, e.g.
  //        'FOO[[ ]]BAR'. We should not trigger in that case.

  // Find a token under the cursor.
  auto *T = findIdentifierUnderCursor(Inputs.AST.getTokens(), Inputs.Cursor);
  // We are interested only in identifiers, other tokens can't be macro names.
  if (!T)
    return false;
  // If the identifier is a macro we will find the corresponding expansion.
  auto Expansion = Inputs.AST.getTokens().expansionStartingAt(T);
  if (!Expansion)
    return false;
  this->MacroName = T->text(Inputs.AST.getSourceManager());
  this->Expansion = *Expansion;
  return true;
}

Expected<Tweak::Effect> ExpandMacro::apply(const Selection &Inputs) {
  auto &SM = Inputs.AST.getSourceManager();

  std::string Replacement;
  for (const syntax::Token &T : Expansion.Expanded) {
    Replacement += T.text(SM);
    Replacement += " ";
  }
  if (!Replacement.empty()) {
    assert(Replacement.back() == ' ');
    Replacement.pop_back();
  }

  CharSourceRange MacroRange =
      CharSourceRange::getCharRange(Expansion.Spelled.front().location(),
                                    Expansion.Spelled.back().endLocation());

  tooling::Replacements Reps;
  llvm::cantFail(Reps.add(tooling::Replacement(SM, MacroRange, Replacement)));
  return Effect::mainFileEdit(SM, std::move(Reps));
}

std::string ExpandMacro::title() const {
  return llvm::formatv("Expand macro '{0}'", MacroName);
}

} // namespace
} // namespace clangd
} // namespace clang