Index: cfe/trunk/include/clang/Tooling/Syntax/Tokens.h =================================================================== --- cfe/trunk/include/clang/Tooling/Syntax/Tokens.h +++ cfe/trunk/include/clang/Tooling/Syntax/Tokens.h @@ -66,6 +66,15 @@ unsigned length() const { return End - Begin; } + /// Check if \p Offset is inside the range. + bool contains(unsigned Offset) const { + return Begin <= Offset && Offset < End; + } + /// Check \p Offset is inside the range or equal to its endpoint. + bool touches(unsigned Offset) const { + return Begin <= Offset && Offset <= End; + } + /// Gets the substring that this FileRange refers to. llvm::StringRef text(const SourceManager &SM) const; Index: clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt +++ clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt @@ -14,6 +14,7 @@ add_clang_library(clangDaemonTweaks OBJECT AnnotateHighlightings.cpp DumpAST.cpp + ExpandMacro.cpp RawStringLiteral.cpp SwapIfBranches.cpp @@ -22,4 +23,5 @@ clangBasic clangDaemon clangToolingCore + clangToolingSyntax ) Index: clang-tools-extra/trunk/clangd/refactor/tweaks/ExpandMacro.cpp =================================================================== --- clang-tools-extra/trunk/clangd/refactor/tweaks/ExpandMacro.cpp +++ clang-tools-extra/trunk/clangd/refactor/tweaks/ExpandMacro.cpp @@ -0,0 +1,136 @@ +//===--- 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; + Intent intent() const override { return Intent::Refactor; } + + 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 = T->text(Inputs.AST.getSourceManager()); + this->Expansion = *Expansion; + return true; +} + +Expected ExpandMacro::apply(const Selection &Inputs) { + auto &SM = Inputs.AST.getASTContext().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()); + + Tweak::Effect E; + E.ApplyEdit.emplace(); + llvm::cantFail( + E.ApplyEdit->add(tooling::Replacement(SM, MacroRange, Replacement))); + return E; +} + +std::string ExpandMacro::title() const { + return llvm::formatv("Expand macro '{0}'", MacroName); +} + +} // namespace +} // namespace clangd +} // namespace clang Index: clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp =================================================================== --- clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp +++ clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp @@ -286,6 +286,99 @@ checkTransform(ID, Input, Output); } +TEST(TweakTest, ExpandMacro) { + llvm::StringLiteral ID = "ExpandMacro"; + + // Available on macro names, not available anywhere else. + checkAvailable(ID, R"cpp( +#define FOO 1 2 3 +#define FUNC(X) X+X+X +^F^O^O^ BAR ^F^O^O^ +^F^U^N^C^(1) +)cpp"); + checkNotAvailable(ID, R"cpp( +^#^d^efine^ ^FO^O 1 ^2 ^3^ +FOO ^B^A^R^ FOO ^ +FUNC(^1^)^ +)cpp"); + + // Works as expected on object-like macros. + checkTransform(ID, R"cpp( +#define FOO 1 2 3 +^FOO BAR FOO +)cpp", + R"cpp( +#define FOO 1 2 3 +1 2 3 BAR FOO +)cpp"); + checkTransform(ID, R"cpp( +#define FOO 1 2 3 +FOO BAR ^FOO +)cpp", + R"cpp( +#define FOO 1 2 3 +FOO BAR 1 2 3 +)cpp"); + + // And function-like macros. + checkTransform(ID, R"cpp( +#define FUNC(X) X+X+X +F^UNC(2) +)cpp", + R"cpp( +#define FUNC(X) X+X+X +2 + 2 + 2 +)cpp"); + + // Works on empty macros. + checkTransform(ID, R"cpp( +#define EMPTY +int a ^EMPTY; + )cpp", + R"cpp( +#define EMPTY +int a ; + )cpp"); + checkTransform(ID, R"cpp( +#define EMPTY_FN(X) +int a ^EMPTY_FN(1 2 3); + )cpp", + R"cpp( +#define EMPTY_FN(X) +int a ; + )cpp"); + checkTransform(ID, R"cpp( +#define EMPTY +#define EMPTY_FN(X) +int a = 123 ^EMPTY EMPTY_FN(1); + )cpp", + R"cpp( +#define EMPTY +#define EMPTY_FN(X) +int a = 123 EMPTY_FN(1); + )cpp"); + checkTransform(ID, R"cpp( +#define EMPTY +#define EMPTY_FN(X) +int a = 123 ^EMPTY_FN(1) EMPTY; + )cpp", + R"cpp( +#define EMPTY +#define EMPTY_FN(X) +int a = 123 EMPTY; + )cpp"); + checkTransform(ID, R"cpp( +#define EMPTY +#define EMPTY_FN(X) +int a = 123 EMPTY_FN(1) ^EMPTY; + )cpp", + R"cpp( +#define EMPTY +#define EMPTY_FN(X) +int a = 123 EMPTY_FN(1) ; + )cpp"); +} + } // namespace } // namespace clangd } // namespace clang