137 lines · cpp
1//===--- ExpandMacro.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 9#include "refactor/Tweak.h"10#include "clang/Basic/SourceLocation.h"11#include "clang/Basic/SourceManager.h"12#include "clang/Basic/TokenKinds.h"13#include "clang/Tooling/Core/Replacement.h"14#include "clang/Tooling/Syntax/Tokens.h"15#include "llvm/ADT/ArrayRef.h"16#include "llvm/ADT/STLExtras.h"17#include "llvm/Support/Error.h"18#include <string>19namespace clang {20namespace clangd {21namespace {22 23/// Replaces a reference to a macro under the cursor with its expansion.24/// Before:25/// #define FOO(X) X+X26/// FOO(10*a)27/// ^^^28/// After:29/// #define FOO(X) X+X30/// 10*a+10*a31class ExpandMacro : public Tweak {32public:33 const char *id() const final;34 llvm::StringLiteral kind() const override {35 return CodeAction::REFACTOR_KIND;36 }37 38 bool prepare(const Selection &Inputs) override;39 Expected<Tweak::Effect> apply(const Selection &Inputs) override;40 std::string title() const override;41 42private:43 syntax::TokenBuffer::Expansion Expansion;44 std::string MacroName;45};46 47REGISTER_TWEAK(ExpandMacro)48 49/// Finds a spelled token that the cursor is pointing at.50static const syntax::Token *51findTokenUnderCursor(const SourceManager &SM,52 llvm::ArrayRef<syntax::Token> Spelled,53 unsigned CursorOffset) {54 // Find the token that strats after the offset, then look at a previous one.55 auto *It = llvm::partition_point(Spelled, [&](const syntax::Token &T) {56 assert(T.location().isFileID());57 return SM.getFileOffset(T.location()) <= CursorOffset;58 });59 if (It == Spelled.begin())60 return nullptr;61 // Check the token we found actually touches the cursor position.62 --It;63 return It->range(SM).touches(CursorOffset) ? It : nullptr;64}65 66static const syntax::Token *67findIdentifierUnderCursor(const syntax::TokenBuffer &Tokens,68 SourceLocation Cursor) {69 assert(Cursor.isFileID());70 71 auto &SM = Tokens.sourceManager();72 auto Spelled = Tokens.spelledTokens(SM.getFileID(Cursor));73 74 auto *T = findTokenUnderCursor(SM, Spelled, SM.getFileOffset(Cursor));75 if (!T)76 return nullptr;77 if (T->kind() == tok::identifier)78 return T;79 // Also try the previous token when the cursor is at the boundary, e.g.80 // FOO^()81 // FOO^+82 if (T == Spelled.begin())83 return nullptr;84 --T;85 if (T->endLocation() != Cursor || T->kind() != tok::identifier)86 return nullptr;87 return T;88}89 90bool ExpandMacro::prepare(const Selection &Inputs) {91 // FIXME: we currently succeed on selection at the end of the token, e.g.92 // 'FOO[[ ]]BAR'. We should not trigger in that case.93 94 // Find a token under the cursor.95 auto *T = findIdentifierUnderCursor(Inputs.AST->getTokens(), Inputs.Cursor);96 // We are interested only in identifiers, other tokens can't be macro names.97 if (!T)98 return false;99 // If the identifier is a macro we will find the corresponding expansion.100 auto Expansion = Inputs.AST->getTokens().expansionStartingAt(T);101 if (!Expansion)102 return false;103 this->MacroName = std::string(T->text(Inputs.AST->getSourceManager()));104 this->Expansion = *Expansion;105 return true;106}107 108Expected<Tweak::Effect> ExpandMacro::apply(const Selection &Inputs) {109 auto &SM = Inputs.AST->getSourceManager();110 111 std::string Replacement;112 for (const syntax::Token &T : Expansion.Expanded) {113 Replacement += T.text(SM);114 Replacement += " ";115 }116 if (!Replacement.empty()) {117 assert(Replacement.back() == ' ');118 Replacement.pop_back();119 }120 121 CharSourceRange MacroRange =122 CharSourceRange::getCharRange(Expansion.Spelled.front().location(),123 Expansion.Spelled.back().endLocation());124 125 tooling::Replacements Reps;126 llvm::cantFail(Reps.add(tooling::Replacement(SM, MacroRange, Replacement)));127 return Effect::mainFileEdit(SM, std::move(Reps));128}129 130std::string ExpandMacro::title() const {131 return std::string(llvm::formatv("Expand macro '{0}'", MacroName));132}133 134} // namespace135} // namespace clangd136} // namespace clang137