diff --git a/clang-tools-extra/clangd/refactor/tweaks/AddDoxygenComment.cpp b/clang-tools-extra/clangd/refactor/tweaks/AddDoxygenComment.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/refactor/tweaks/AddDoxygenComment.cpp @@ -0,0 +1,147 @@ +//===--- AddDoxydgenComment.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 "AST.h" +#include "Selection.h" +#include "refactor/InsertionPoint.h" +#include "refactor/Tweak.h" +#include "support/Logger.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/DeclCXX.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { +namespace { + +// A tweak that adds a doxygen comment to the function declaration +// +// Given: +// int func(int x, char const* s, Bar bar) {} +// the tweak add the doxygen comment to the function in the form +// /*! +// * @brief +// * @param x +// * @param s +// * @param bar +// * @return +// */ +// int func(int x, char const* s, Bar bar) {} +// if the function return type is void no @return is added to the doxygen +// comment +// +class AddDoxygenComment : public Tweak { +public: + const char *id() const 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: + FunctionDecl const *FD; + std::string buildCommentCode() const; + static bool isCommentExist(FunctionDecl const *FD); +}; + +REGISTER_TWEAK(AddDoxygenComment) + +std::string AddDoxygenComment::title() const { + return "Add doxygen comment to the function declaration"; +} + +bool AddDoxygenComment::isCommentExist(FunctionDecl const *FD) { + if (RawComment *Comment = + FD->getASTContext().getRawCommentForDeclNoCache(FD)) { + return true; + } + return false; +} + +bool AddDoxygenComment::prepare(const Selection &Inputs) { + if (!Inputs.AST->getLangOpts().CPlusPlus) { + return false; + } + if (auto *N = Inputs.ASTSelection.commonAncestor()) { + if (FunctionDecl const *FD = N->ASTNode.get()) { + if (isCommentExist(FD)) { + return false; + } + this->FD = FD; + return true; + } + for (auto *P = N->Parent; P != nullptr; P = P->Parent) { + if (FunctionDecl const *FD = P->ASTNode.get()) { + if (isCommentExist(FD)) { + return false; + } + this->FD = FD; + return true; + } + } + } + return false; +} + +std::string AddDoxygenComment::buildCommentCode() const { + std::string Res; + llvm::raw_string_ostream OS(Res); + OS << R"cpp( +/*! + * @brief + * +)cpp"; + for (auto const *P : FD->parameters()) { + OS << "* @param " << P->getName() << '\n'; + } + if (auto const *RT = FD->getReturnType().getTypePtr()) { + if (!RT->isVoidType()) { + OS << "* @return\n"; + } + } + OS << "*/\n"; + return Res; +} + +Expected AddDoxygenComment::apply(const Selection &Inputs) { + auto &SrcMgr = Inputs.AST->getSourceManager(); + std::vector Anchors = {{[this](Decl const *D) { + if (auto const *FD = + llvm::dyn_cast(D)) { + if (FD == this->FD) { + return true; + } + } + return false; + }, + Anchor::Above}}; + auto const *Parent = FD->getParent(); + if (!Parent) { + return error("Parent for the FuncDecl is nullptr"); + } + // we cannot use here insertDecl as if the Parent is TranslationUnitDecl + // it will fail, as TranslationUnitDecl does not have a valid location + auto Loc = insertionPoint(*Parent, Anchors); + if (Loc.isInvalid()) { + return error("Location for Declatation {0} is invalid", + Parent->getDeclKindName()); + } + tooling::Replacement Replacement(SrcMgr, Loc, 0, buildCommentCode()); + return Effect::mainFileEdit(SrcMgr, + tooling::Replacements{std::move(Replacement)}); +} +} // namespace +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt --- a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt +++ b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt @@ -12,6 +12,7 @@ # $ to a list of sources, see # clangd/tool/CMakeLists.txt for an example. add_clang_library(clangDaemonTweaks OBJECT + AddDoxygenComment.cpp AddUsing.cpp AnnotateHighlightings.cpp DumpAST.cpp diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -107,6 +107,7 @@ support/TestTracer.cpp support/TraceTests.cpp + tweaks/AddDoxygenCommentTests.cpp tweaks/AddUsingTests.cpp tweaks/AnnotateHighlightingsTests.cpp tweaks/DefineInlineTests.cpp diff --git a/clang-tools-extra/clangd/unittests/tweaks/AddDoxygenCommentTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/AddDoxygenCommentTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/tweaks/AddDoxygenCommentTests.cpp @@ -0,0 +1,98 @@ +//===-- AddDoxygenCommentTests.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 "TweakTesting.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +TWEAK_TEST(AddDoxygenComment); + +TEST_F(AddDoxygenCommentTest, AvailableUnavailable) { + EXPECT_AVAILABLE(R"cpp( + void bar(char b) {^ } + [[int foo(int x) { }]] + namespace ns1 { + void bar(char b) {^ } + [[int foo(int x) { }]] + } + )cpp"); + EXPECT_UNAVAILABLE(R"cpp( + namespace ns1 { + /*! + */ + void bar(char b) {^ } + // comment + [[int foo(int x) { }]] + /// comment + [[int meaw(int x) { }]] + } + )cpp"); +} + +TEST_F(AddDoxygenCommentTest, Apply1) { + EXPECT_EQ(apply(R"cpp( +namespace ns1 { + int foo(int y, char* s) { + ^int x; + return x; + } +} + )cpp"), + R"cpp( +namespace ns1 { + +/*! + * @brief + * +* @param y +* @param s +* @return +*/ +int foo(int y, char* s) { + int x; + return x; + } +} + )cpp"); +} + +TEST_F(AddDoxygenCommentTest, Apply2) { + EXPECT_EQ(apply(R"cpp( +void foo(); +void ^bar(); +namespace ns1 { + int foo(int y, char* s) { + int x; + return x; + } +} +)cpp"), + R"cpp( +void foo(); + +/*! + * @brief + * +*/ +void bar(); +namespace ns1 { + int foo(int y, char* s) { + int x; + return x; + } +} +)cpp"); +} + +} // namespace +} // namespace clangd +} // namespace clang