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,148 @@ +//===--- 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/AST/DeclObjC.h" +#include "clang/AST/Type.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" +#include + +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 +// /// TODO Add description +// /// @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: + std::variant D; + std::string buildCommentCode() const; +}; + +REGISTER_TWEAK(AddDoxygenComment) + +std::string AddDoxygenComment::title() const { + return "Add documentation comment"; +} + +bool AddDoxygenComment::prepare(const Selection &Inputs) { + if (auto *N = Inputs.ASTSelection.commonAncestor()) { + if (const FunctionDecl *FD = N->ASTNode.get()) { + if (FD->getASTContext().getRawCommentForDeclNoCache(FD)) { + return false; + } + const FunctionDecl *Def; + // we are at the definition, but there was a previous declaration + // do not suggest a tweak as it is wrong to suggest it not in the header + if (FD->isDefined(Def) && FD == Def && FD->getPreviousDecl() != nullptr) { + return false; + } + // no tweak for user defined deduction guide + if (llvm::dyn_cast(FD) != nullptr) { + return false; + } + this->D = FD; + return true; + } + if (const ObjCMethodDecl *MD = N->ASTNode.get()) { + if (MD->getASTContext().getRawCommentForDeclNoCache(MD)) { + return false; + } + this->D = MD; + return true; + } + } + return false; +} + +template struct Overloaded : Ts... { + using Ts::operator()...; +}; +template Overloaded(Ts...) -> Overloaded; + +std::string AddDoxygenComment::buildCommentCode() const { + std::string Res; + llvm::raw_string_ostream OS(Res); + OS << "/// TODO Add description\n///\n"; + auto PrintParams = [&OS](llvm::ArrayRef Params, + QualType Ret) { + for (auto const *P : Params) { + if (auto PN = P->getName(); PN.empty()) { + continue; + } + OS << "/// @param " << P->getName() << '\n'; + } + if (auto const *RT = Ret.getTypePtr()) { + if (!RT->isVoidType() && !RT->isObjCIdType()) { + OS << "/// @return\n"; + } + } + }; + std::visit(Overloaded{[&PrintParams](const FunctionDecl *FD) { + PrintParams(FD->parameters(), FD->getReturnType()); + }, + [&PrintParams](const ObjCMethodDecl *MD) { + PrintParams(MD->parameters(), MD->getReturnType()); + }}, + D); + OS << "///\n"; + return Res; +} + +Expected AddDoxygenComment::apply(const Selection &Inputs) { + auto &SrcMgr = Inputs.AST->getSourceManager(); + auto SrcRange = std::visit( + Overloaded{[](const FunctionDecl *FD) { + if (const auto *T = FD->getDescribedFunctionTemplate()) { + return T->getSourceRange(); + } + return FD->getSourceRange(); + }, + [](const ObjCMethodDecl *MD) { return MD->getSourceRange(); }}, + D); + const tooling::Replacement InsertDocComment(SrcMgr, SrcRange.getBegin(), 0, + buildCommentCode()); + return Effect::mainFileEdit( + SrcMgr, tooling::Replacements{std::move(InsertDocComment)}); +} +} // 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 @@ -113,6 +113,7 @@ support/ThreadingTests.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,189 @@ +//===-- 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 { + [[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) { }]] + void baz(char b) {^ } + } + )cpp"); +} + +TEST_F(AddDoxygenCommentTest, AvailableUnavailableOnDefinition) { + EXPECT_AVAILABLE(R"cpp( + void bar^(char b); + void bar(char b) { (void)b; } + )cpp"); + EXPECT_UNAVAILABLE(R"cpp( + void bar(char b); + void bar^(char b) { (void)b; } + )cpp"); +} + +TEST_F(AddDoxygenCommentTest, AvailableUnavailableOnMemberFunc) { + EXPECT_AVAILABLE(R"cpp( + class Foo { + void foo^(){} + void bar^(); + }; + )cpp"); + EXPECT_UNAVAILABLE(R"cpp( + class Foo { + void bar(); + }; + void Foo::bar^(){} + )cpp"); +} + +TEST_F(AddDoxygenCommentTest, AvailableUnavailableOnTemplate) { + EXPECT_AVAILABLE(R"cpp( + template + class Foo { + void foo^(){} + void bar^(); + }; + )cpp"); + EXPECT_UNAVAILABLE(R"cpp( + template + class Foo { + void bar(); + }; + template + void Foo::bar^(){} + )cpp"); +} + +TEST_F(AddDoxygenCommentTest, AvailableUnavailableOnDeductionGuide) { + EXPECT_UNAVAILABLE(R"cpp( + template struct A { A(); A(T); }; + A^() -> A; + )cpp"); +} + +TEST_F(AddDoxygenCommentTest, AvailableUnavailableObjC) { + FileName = "TestTU.m"; + EXPECT_AVAILABLE(R"objc( + @interface Foo + + (id)^fooWithValue:(int)value fooey:(unsigned int)fooey; + @end + )objc"); +} + +TEST_F(AddDoxygenCommentTest, ApplyTemplate) { + EXPECT_EQ(apply(R"cpp( +template +void foo^(T x) {})cpp"), + R"cpp( +/// TODO Add description +/// +/// @param x +/// +template +void foo(T x) {})cpp"); +} + +TEST_F(AddDoxygenCommentTest, ApplyObjCIdReturn) { + FileName = "TestTU.m"; + EXPECT_EQ(apply(R"objc( +@interface Foo ++ (id)^fooWithValue:(int)value fooey:(unsigned int)fooey; +@end)objc"), + R"objc( +@interface Foo +/// TODO Add description +/// +/// @param value +/// @param fooey +/// ++ (id)fooWithValue:(int)value fooey:(unsigned int)fooey; +@end)objc"); +} +TEST_F(AddDoxygenCommentTest, ApplyObjCNonIdReturn) { + FileName = "TestTU.m"; + EXPECT_EQ(apply(R"objc( +@interface Foo ++ (int)^fooWithValue:(int)value fooey:(unsigned int)fooey; +@end)objc"), + R"objc( +@interface Foo +/// TODO Add description +/// +/// @param value +/// @param fooey +/// @return +/// ++ (int)fooWithValue:(int)value fooey:(unsigned int)fooey; +@end)objc"); +} + +TEST_F(AddDoxygenCommentTest, ApplyInsideNS) { + EXPECT_EQ(apply(R"cpp( +namespace ns1 { + int foo^(int y, char* s) { + int x; + return x; + } +} + )cpp"), + R"cpp( +namespace ns1 { + /// TODO Add description +/// +/// @param y +/// @param s +/// @return +/// +int foo(int y, char* s) { + int x; + return x; + } +} + )cpp"); +} + +TEST_F(AddDoxygenCommentTest, ApplyInsideTU) { + EXPECT_EQ(apply(R"cpp( +void foo(); +void ^bar(); +)cpp"), + R"cpp( +void foo(); +/// TODO Add description +/// +/// +void bar(); +)cpp"); +} + +} // namespace +} // namespace clangd +} // namespace clang