137 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			137 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			C++
		
	
	
	
//===--- 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;
 | 
						|
  llvm::StringLiteral kind() const override {
 | 
						|
    return CodeAction::REFACTOR_KIND;
 | 
						|
  }
 | 
						|
 | 
						|
  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 = std::string(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 std::string(llvm::formatv("Expand macro '{0}'", MacroName));
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
} // namespace clangd
 | 
						|
} // namespace clang
 |