//===--- 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 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 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 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 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