[clang-tools-extra] r365331 - [clangd] A code tweak to expand a macro

Ilya Biryukov via cfe-commits cfe-commits at lists.llvm.org
Mon Jul 8 08:25:16 PDT 2019


Author: ibiryukov
Date: Mon Jul  8 08:25:16 2019
New Revision: 365331

URL: http://llvm.org/viewvc/llvm-project?rev=365331&view=rev
Log:
[clangd] A code tweak to expand a macro

Reviewers: sammccall

Reviewed By: sammccall

Subscribers: mgorny, MaskRay, jkorous, arphaman, kadircet, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D61681

Added:
    clang-tools-extra/trunk/clangd/refactor/tweaks/ExpandMacro.cpp
Modified:
    clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt
    clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp

Modified: clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt?rev=365331&r1=365330&r2=365331&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/clangd/refactor/tweaks/CMakeLists.txt Mon Jul  8 08:25:16 2019
@@ -14,6 +14,7 @@ set(LLVM_LINK_COMPONENTS
 add_clang_library(clangDaemonTweaks OBJECT
   AnnotateHighlightings.cpp
   DumpAST.cpp
+  ExpandMacro.cpp
   RawStringLiteral.cpp
   SwapIfBranches.cpp
 
@@ -22,4 +23,5 @@ add_clang_library(clangDaemonTweaks OBJE
   clangBasic
   clangDaemon
   clangToolingCore
+  clangToolingSyntax
   )

Added: clang-tools-extra/trunk/clangd/refactor/tweaks/ExpandMacro.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/refactor/tweaks/ExpandMacro.cpp?rev=365331&view=auto
==============================================================================
--- clang-tools-extra/trunk/clangd/refactor/tweaks/ExpandMacro.cpp (added)
+++ clang-tools-extra/trunk/clangd/refactor/tweaks/ExpandMacro.cpp Mon Jul  8 08:25:16 2019
@@ -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 <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.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

Modified: clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp?rev=365331&r1=365330&r2=365331&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp (original)
+++ clang-tools-extra/trunk/clangd/unittests/TweakTests.cpp Mon Jul  8 08:25:16 2019
@@ -286,6 +286,99 @@ TEST(TweakTest, AnnotateHighlightings) {
   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




More information about the cfe-commits mailing list