https://github.com/GuillaumeF0 created https://github.com/llvm/llvm-project/pull/157727
None >From 4042478aac711d34ef396f54ad314a092b967778 Mon Sep 17 00:00:00 2001 From: Guillaume Frognier <guillaume.frogn...@gmail.com> Date: Tue, 9 Sep 2025 19:25:43 +0200 Subject: [PATCH] [clangd] Add code action to generate getter and setter for a field in a C++ class --- clang-tools-extra/clangd/CMakeLists.txt | 1 + clang-tools-extra/clangd/Config.h | 8 + clang-tools-extra/clangd/ConfigCompile.cpp | 21 +- clang-tools-extra/clangd/ConfigFragment.h | 6 + clang-tools-extra/clangd/ConfigYAML.cpp | 12 + .../clangd/refactor/GenerateAccessorBase.cpp | 214 ++++++++++++++++++ .../clangd/refactor/GenerateAccessorBase.h | 54 +++++ .../clangd/refactor/tweaks/CMakeLists.txt | 2 + .../clangd/refactor/tweaks/GenerateGetter.cpp | 72 ++++++ .../clangd/refactor/tweaks/GenerateSetter.cpp | 93 ++++++++ .../clangd/unittests/CMakeLists.txt | 2 + .../unittests/tweaks/GenerateGetterTests.cpp | 129 +++++++++++ .../unittests/tweaks/GenerateSetterTests.cpp | 162 +++++++++++++ .../clangd/refactor/tweaks/BUILD.gn | 1 + .../clangd/unittests/BUILD.gn | 1 + 15 files changed, 774 insertions(+), 4 deletions(-) create mode 100644 clang-tools-extra/clangd/refactor/GenerateAccessorBase.cpp create mode 100644 clang-tools-extra/clangd/refactor/GenerateAccessorBase.h create mode 100644 clang-tools-extra/clangd/refactor/tweaks/GenerateGetter.cpp create mode 100644 clang-tools-extra/clangd/refactor/tweaks/GenerateSetter.cpp create mode 100644 clang-tools-extra/clangd/unittests/tweaks/GenerateGetterTests.cpp create mode 100644 clang-tools-extra/clangd/unittests/tweaks/GenerateSetterTests.cpp diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index fb3f05329be21..ce72b09d47f40 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -144,6 +144,7 @@ add_clang_library(clangDaemon STATIC index/dex/PostingList.cpp index/dex/Trigram.cpp + refactor/GenerateAccessorBase.cpp refactor/InsertionPoint.cpp refactor/Rename.cpp refactor/Tweak.cpp diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h index 01997cee08515..c006b06a5c9b2 100644 --- a/clang-tools-extra/clangd/Config.h +++ b/clang-tools-extra/clangd/Config.h @@ -133,6 +133,14 @@ struct Config { // List of matcher functions for inserting certain headers with <> or "". std::vector<std::function<bool(llvm::StringRef)>> QuotedHeaders; std::vector<std::function<bool(llvm::StringRef)>> AngledHeaders; + + // Getter and setter's prefixes + // Prefix for a get accessor e.g. "get" + std::string GetterPrefix; + // Prefix for a set accessor e.g. "set" + std::string SetterPrefix; + // Prefix for a set accessor's parameter e.g. "new" + std::string SetterParameterPrefix; } Style; /// controls the completion options for argument lists. diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp index 962a48bcb7671..7ecc1f610a9b2 100644 --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -508,6 +508,19 @@ struct FragmentCompiler { C.Style.AngledHeaders.emplace_back(AngledFilter); }); } + if (F.GetterPrefix) + Out.Apply.push_back([Value(**F.GetterPrefix)](const Params &, Config &C) { + C.Style.GetterPrefix = Value; + }); + if (F.SetterPrefix) + Out.Apply.push_back([Value(**F.SetterPrefix)](const Params &, Config &C) { + C.Style.SetterPrefix = Value; + }); + if (F.SetterParameterPrefix) + Out.Apply.push_back( + [Value(**F.SetterParameterPrefix)](const Params &, Config &C) { + C.Style.SetterParameterPrefix = Value; + }); } auto compileHeaderRegexes(llvm::ArrayRef<Located<std::string>> HeaderPatterns) @@ -564,10 +577,10 @@ struct FragmentCompiler { auto Fast = isFastTidyCheck(Str); if (!Fast.has_value()) { diag(Warning, - llvm::formatv( - "Latency of clang-tidy check '{0}' is not known. " - "It will only run if ClangTidy.FastCheckFilter is Loose or None", - Str) + llvm::formatv("Latency of clang-tidy check '{0}' is not known. " + "It will only run if ClangTidy.FastCheckFilter is " + "Loose or None", + Str) .str(), Arg.Range); } else if (!*Fast) { diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h index 2afeb36574b21..36e9584ed6d2d 100644 --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -326,6 +326,12 @@ struct Fragment { /// Matching is performed against the absolute path of the header /// within the project. std::vector<Located<std::string>> AngledHeaders; + /// Getter generation prefix i.e. "get". + std::optional<Located<std::string>> GetterPrefix; + /// Setter generation prefix i.e. "set". + std::optional<Located<std::string>> SetterPrefix; + /// Setter generation parameter prefix i.e. "new". + std::optional<Located<std::string>> SetterParameterPrefix; }; StyleBlock Style; diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp index 392cf19b05a55..fee7436fe9e3e 100644 --- a/clang-tools-extra/clangd/ConfigYAML.cpp +++ b/clang-tools-extra/clangd/ConfigYAML.cpp @@ -130,6 +130,18 @@ class Parser { if (auto Values = scalarValues(N)) F.AngledHeaders = std::move(*Values); }); + Dict.handle("GetterPrefix", [&](Node &N) { + if (auto Value = scalarValue(N, "GetterPrefix")) + F.GetterPrefix = *Value; + }); + Dict.handle("SetterPrefix", [&](Node &N) { + if (auto Value = scalarValue(N, "SetterPrefix")) + F.SetterPrefix = *Value; + }); + Dict.handle("SetterParameterPrefix", [&](Node &N) { + if (auto Value = scalarValue(N, "SetterParameterPrefix")) + F.SetterParameterPrefix = *Value; + }); Dict.parse(N); } diff --git a/clang-tools-extra/clangd/refactor/GenerateAccessorBase.cpp b/clang-tools-extra/clangd/refactor/GenerateAccessorBase.cpp new file mode 100644 index 0000000000000..d7d21051babb4 --- /dev/null +++ b/clang-tools-extra/clangd/refactor/GenerateAccessorBase.cpp @@ -0,0 +1,214 @@ +//===--- GenerateAccessor.cpp - Base class for getter/setter generation ---===// +// +// 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 "GenerateAccessorBase.h" +#include "AST.h" +#include "Config.h" +#include "ParsedAST.h" +#include "refactor/InsertionPoint.h" +#include "refactor/Tweak.h" +#include "support/Logger.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Stmt.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include <string> + +namespace clang { +namespace clangd { + +Expected<Tweak::Effect> GenerateAccessorBase::apply(const Selection &Inputs) { + // Prefer to place the new method ... + std::vector<Anchor> Anchors = { + // On top of fields declaration + {[](const Decl *D) { return llvm::isa<FieldDecl>(D); }, Anchor::Above}, + // At the bottom of public section + {[](const Decl *D) { return D->getAccess() == AS_public; }, + Anchor::Below}, + // Fallback: At the end of class + {[](const Decl *D) { return true; }, Anchor::Below}, + }; + + std::string Code = buildCode(); + + auto Edit = insertDecl(Code, *Class, std::move(Anchors), AS_public); + if (!Edit) + return Edit.takeError(); + + return Effect::mainFileEdit(Inputs.AST->getSourceManager(), + tooling::Replacements{std::move(*Edit)}); +} + +bool GenerateAccessorBase::prepare(const Selection &Inputs) { + // This tweak is available for C++ only. + if (!Inputs.AST->getLangOpts().CPlusPlus) + return false; + + if (auto *N = Inputs.ASTSelection.commonAncestor()) { + Field = N->ASTNode.get<FieldDecl>(); + } + + // Trigger only on Field declaration. + if (!Field || !Field->getIdentifier()) + return false; + + // No setter for constant field, by extension no action for generate getter + // will be provided. + if (Field->getType().isConstQualified()) + return false; + + // Trigger only inside a class declaration. + Class = dyn_cast<CXXRecordDecl>(Field->getParent()); + if (!Class || !Class->isThisDeclarationADefinition()) + return false; + + // Define accessor's name, field's base name and check if field has any + // prefix or suffix. + build(); + + // Trigger only if the class does not already have this method. + for (const auto *M : Class->methods()) { + if (M->getName() == AccessorName) { + return false; + } + } + + dlog("GenerateAccessorBase for {0}?", Field->getName()); + + return true; +} + +void GenerateAccessorBase::build() { + // Retrieve clang-tidy options for field's prefix and suffix in order to + // determine the base name of the field. Do not take Hungarian notation into + // account. + const auto &ClangTiddyOptions = + Config::current().Diagnostics.ClangTidy.CheckOptions; + + auto GetOption = [&ClangTiddyOptions](llvm::StringRef Key) -> std::string { + auto It = + ClangTiddyOptions.find("readability-identifier-naming." + Key.str()); + return It != ClangTiddyOptions.end() ? It->second : ""; + }; + + std::string FieldPrefix; + std::string FieldSuffix; + // Visibility (public/protected/private) + switch (Field->getAccessUnsafe()) { + case AS_private: + FieldPrefix = GetOption("PrivateMemberPrefix"); + FieldSuffix = GetOption("PrivateMemberSuffix"); + break; + case AS_protected: + FieldPrefix = GetOption("ProtectedMemberPrefix"); + FieldSuffix = GetOption("ProtectedMemberSuffix"); + break; + case AS_public: + FieldPrefix = GetOption("PublicMemberPrefix"); + FieldSuffix = GetOption("PublicMemberSuffix"); + break; + case AS_none: + break; + } + if (FieldPrefix.empty() && FieldSuffix.empty()) { + FieldPrefix = GetOption("MemberPrefix"); + FieldSuffix = GetOption("MemberSuffix"); + } + + llvm::StringRef BaseNameRef = Field->getName(); + if (!FieldPrefix.empty()) { + FieldWithPreffixOrSuffix |= BaseNameRef.consume_front(FieldPrefix); + } + if (!FieldSuffix.empty()) { + FieldWithPreffixOrSuffix |= BaseNameRef.consume_back(FieldSuffix); + } + + std::string FieldBaseNameLocal = BaseNameRef.str(); + FieldBaseName = FieldBaseNameLocal; + + // Get user-configured getter/setter prefix. + std::string AccessorPrefix = retrieveAccessorPrefix(); + if (AccessorPrefix.empty()) { + AccessorName = FieldBaseNameLocal; + return; + } + + // Define getter method case. + std::string MethodCase = GetOption("PublicMethodCase"); + if (MethodCase.empty()) + MethodCase = GetOption("MethodCase"); + if (MethodCase.empty()) + MethodCase = GetOption("FunctionCase"); + + if (MethodCase == "lower_case") { + toLower(AccessorPrefix); + toLower(FieldBaseNameLocal); + AccessorName = AccessorPrefix + '_' + FieldBaseNameLocal; + return; + } + if (MethodCase == "UPPER_CASE") { + toUpper(AccessorPrefix); + toUpper(FieldBaseNameLocal); + AccessorName = AccessorPrefix + '_' + FieldBaseNameLocal; + return; + } + if (MethodCase == "camelBack") { + toLower(AccessorPrefix); + toCamelCase(FieldBaseNameLocal); + AccessorName = AccessorPrefix + FieldBaseNameLocal; + return; + } + if (MethodCase == "CamelCase") { + toCamelCase(AccessorPrefix); + toCamelCase(FieldBaseNameLocal); + AccessorName = AccessorPrefix + FieldBaseNameLocal; + return; + } + if (MethodCase == "camel_Snake_Back") { + toLower(AccessorPrefix); + toCamelCase(FieldBaseNameLocal); + AccessorName = AccessorPrefix + '_' + FieldBaseNameLocal; + return; + } + if (MethodCase == "Camel_Snake_Case") { + toCamelCase(AccessorPrefix); + toCamelCase(FieldBaseNameLocal); + AccessorName = AccessorPrefix + '_' + FieldBaseNameLocal; + return; + } + if (MethodCase == "Leading_upper_snake_case") { + toCamelCase(AccessorPrefix); + toLower(FieldBaseNameLocal); + AccessorName = AccessorPrefix + '_' + FieldBaseNameLocal; + return; + } + FieldBaseNameLocal[0] = llvm::toUpper(FieldBaseNameLocal[0]); + AccessorName = AccessorPrefix + FieldBaseNameLocal; +} + +void GenerateAccessorBase::toLower(std::string &s) const { + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return llvm::toLower(c); }); +} + +void GenerateAccessorBase::toUpper(std::string &s) const { + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return llvm::toUpper(c); }); +} + +void GenerateAccessorBase::toCamelCase(std::string &s) const { + s[0] = llvm::toUpper(s[0]); + std::transform(s.begin() + 1, s.end(), s.begin() + 1, + [](unsigned char c) { return llvm::toLower(c); }); +} + +} // namespace clangd +} // namespace clang \ No newline at end of file diff --git a/clang-tools-extra/clangd/refactor/GenerateAccessorBase.h b/clang-tools-extra/clangd/refactor/GenerateAccessorBase.h new file mode 100644 index 0000000000000..eda198353329c --- /dev/null +++ b/clang-tools-extra/clangd/refactor/GenerateAccessorBase.h @@ -0,0 +1,54 @@ +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_GENERATEACCESSORBASE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_GENERATEACCESSORBASE_H + +//===--- GenerateAccessor.h - Base class for getter/setter generation -----===// +// +// 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/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "llvm/ADT/StringRef.h" +#include <string> + +namespace clang { +namespace clangd { + +class GenerateAccessorBase : public Tweak { +public: + const char *id() const override = 0; + llvm::StringLiteral kind() const override = 0; + std::string title() const override = 0; + + bool prepare(const Selection &Inputs) override; + + Expected<Effect> apply(const Selection &Inputs) override; + +protected: + virtual std::string buildCode() const = 0; + + void build(); + + virtual std::string retrieveAccessorPrefix() const = 0; + + void toLower(std::string &s) const; + + void toUpper(std::string &s) const; + + void toCamelCase(std::string &s) const; + + const CXXRecordDecl *Class = nullptr; + const FieldDecl *Field = nullptr; + std::string AccessorName; + std::string FieldBaseName; + bool FieldWithPreffixOrSuffix = false; +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_GENERATEACCESSORBASE_H diff --git a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt index 1d6e38088ad67..943ddededa5f9 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt +++ b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt @@ -21,6 +21,8 @@ add_clang_library(clangDaemonTweaks OBJECT ExpandMacro.cpp ExtractFunction.cpp ExtractVariable.cpp + GenerateGetter.cpp + GenerateSetter.cpp MemberwiseConstructor.cpp ObjCLocalizeStringLiteral.cpp ObjCMemberwiseInitializer.cpp diff --git a/clang-tools-extra/clangd/refactor/tweaks/GenerateGetter.cpp b/clang-tools-extra/clangd/refactor/tweaks/GenerateGetter.cpp new file mode 100644 index 0000000000000..10a6727366611 --- /dev/null +++ b/clang-tools-extra/clangd/refactor/tweaks/GenerateGetter.cpp @@ -0,0 +1,72 @@ +//===--- GenerateGetter.cpp - Generate getter methods ---------------------===// +// +// 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 "Config.h" +#include "refactor/GenerateAccessorBase.h" +#include "clang/AST/Decl.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include <string> + +static constexpr const char *GetterPrefixDefault = "get"; + +namespace clang { +namespace clangd { +namespace { + +// A tweak that generates a getter for a field. +// +// Given: +// struct S { int x; }; +// Produces: +// int getX() const { return x; } +// +// Method's prefix can be configured with Style.GetterPrefix. +// +// We place the method inline, other tweaks are available to outline it. +class GenerateGetter : public GenerateAccessorBase { +public: + const char *id() const final; + llvm::StringLiteral kind() const override { + return CodeAction::REFACTOR_KIND; + } + std::string title() const override { return "Generate getter"; } + +private: + std::string buildCode() const override { + // Field type + QualType T = Field->getType().getLocalUnqualifiedType(); + + std::string S; + llvm::raw_string_ostream OS(S); + llvm::StringRef FieldName = Field->getName(); + + OS << printType(T, *Class) << " " << AccessorName << "() const { return " + << FieldName << "; }\n"; + return S; + } + + std::string retrieveAccessorPrefix() const override { + // Get user-configured getter prefix. + std::string GetterPrefix = Config::current().Style.GetterPrefix; + if (!GetterPrefix.empty()) { + return GetterPrefix; + } + if (FieldWithPreffixOrSuffix) { + return ""; + } + return GetterPrefixDefault; + } +}; + +REGISTER_TWEAK(GenerateGetter) + +} // namespace +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/refactor/tweaks/GenerateSetter.cpp b/clang-tools-extra/clangd/refactor/tweaks/GenerateSetter.cpp new file mode 100644 index 0000000000000..0127f5968b333 --- /dev/null +++ b/clang-tools-extra/clangd/refactor/tweaks/GenerateSetter.cpp @@ -0,0 +1,93 @@ +//===--- GenerateSetter.cpp - Generate setter methods ---------------------===// +// +// 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 "Config.h" +#include "refactor/GenerateAccessorBase.cpp" +#include "refactor/GenerateAccessorBase.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/OpenMPClause.h" +#include "clang/AST/TypeBase.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include <string> + +static constexpr const char *SetterPrefixDefault = "set"; +static constexpr const char *SetterParameterPrefixDefault = "new"; + +namespace clang { +namespace clangd { +namespace { + +// A tweak that generates a setter for a field. +// +// Given: +// struct S { int x; }; +// Produces: +// void setX(int newX) { x = newX; } +// +// Method's prefix can be configured with Style.SetterPrefix. +// Method's parameter prefix can be configured with +// GetterSetter.SetterParameterPrefix. +// +// We place the method inline, other tweaks are available to outline it. +class GenerateSetter : public GenerateAccessorBase { +public: + const char *id() const final; + llvm::StringLiteral kind() const override { + return CodeAction::REFACTOR_KIND; + } + std::string title() const override { return "Generate setter"; } + +private: + std::string buildCode() const override { + QualType T = Field->getType().getLocalUnqualifiedType(); + auto &Context = Class->getASTContext(); + + std::string S; + llvm::raw_string_ostream OS(S); + + OS << "void " << AccessorName << "("; + + // Use const-ref if type is not trivially copiable or its size is larger + // than the size of a pointer + if (!T.isTriviallyCopyableType(Context) || + Context.getTypeSize(T) > Context.getTypeSize(Context.VoidPtrTy)) { + OS << "const " << printType(T, *Class); + if (!T->isReferenceType()) + OS << " &"; + } else + OS << printType(T, *Class) << " "; + + std::string SetterParameterPrefix = + Config::current().Style.SetterParameterPrefix; + if (SetterParameterPrefix.empty() && !FieldWithPreffixOrSuffix) { + SetterParameterPrefix = SetterParameterPrefixDefault; + } + llvm::StringRef FieldName = Field->getName(); + OS << SetterParameterPrefix << FieldBaseName << ") { " << FieldName << " = " + << SetterParameterPrefix << FieldBaseName << "; }\n"; + return S; + } + + std::string retrieveAccessorPrefix() const override { + // Get user-configured setter prefix + std::string SetterPrefix = Config::current().Style.SetterPrefix; + if (SetterPrefix.empty()) { + return SetterPrefixDefault; + } + return SetterPrefix; + } + +}; // namespace + +REGISTER_TWEAK(GenerateSetter) + +} // namespace +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt index 9656eeaeb37ce..3dbae94560ee3 100644 --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -129,6 +129,8 @@ add_unittest(ClangdUnitTests ClangdTests tweaks/ExpandMacroTests.cpp tweaks/ExtractFunctionTests.cpp tweaks/ExtractVariableTests.cpp + tweaks/GenerateGetterTests.cpp + tweaks/GenerateSetterTests.cpp tweaks/MemberwiseConstructorTests.cpp tweaks/ObjCLocalizeStringLiteralTests.cpp tweaks/ObjCMemberwiseInitializerTests.cpp diff --git a/clang-tools-extra/clangd/unittests/tweaks/GenerateGetterTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/GenerateGetterTests.cpp new file mode 100644 index 0000000000000..6542d6dfeaad4 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/tweaks/GenerateGetterTests.cpp @@ -0,0 +1,129 @@ +//===-- GenerateGetterTests.cpp -------------------------------------------===// +// +// 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 "Config.h" +#include "TweakTesting.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +TWEAK_TEST(GenerateGetter); + +TEST_F(GenerateGetterTest, Availability) { + // available on class member: + EXPECT_AVAILABLE("struct S { int ^x, y; };"); + EXPECT_AVAILABLE("class S { private: int ^x; };"); + EXPECT_AVAILABLE("class S { protected: int ^x; };"); + EXPECT_AVAILABLE("union S { int ^x; };"); + EXPECT_AVAILABLE("struct S { int ^x = 0; };"); + // available on forward member: + EXPECT_AVAILABLE("/*error-ok*/class Forward; class A { Forward ^f; };"); + // available on pointer type: + EXPECT_AVAILABLE("class Forward; class A { Forward *^f; };"); + // available on reference type: + EXPECT_AVAILABLE("class A { int &^f; };"); + + // unavailable outside class member: + EXPECT_UNAVAILABLE("^struct ^S ^{ int f^oo(); ^int x, y; };"); + // unavailable if method already exists: + EXPECT_UNAVAILABLE("struct S { int getX(); int ^x, y; };"); + // unavailable on constant type: + EXPECT_UNAVAILABLE("class S { const int ^x, y; };"); + // unavailable on static member: + EXPECT_UNAVAILABLE("struct S { static int ^x; };"); +} + +TEST_F(GenerateGetterTest, Edits) { + auto RunGetterTest = [&](llvm::StringRef GetterPrefix, + llvm::StringMap<std::string> Options, + llvm::StringRef Input, llvm::StringRef Expected) { + Config Cfg; + if (!GetterPrefix.empty()) + Cfg.Style.GetterPrefix = GetterPrefix.str(); + + for (auto &KV : Options) + Cfg.Diagnostics.ClangTidy.CheckOptions.insert_or_assign( + ("readability-identifier-naming." + KV.getKey()).str(), + KV.getValue()); + + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + EXPECT_EQ(apply(Input.str()), Expected.str()); + }; + + Header = R"cpp( + struct Foo { + char a; + char b; + char c; + }; + )cpp"; + + // Comply with style configuration: + RunGetterTest("summon", + { + {"PublicMemberPrefix", "m_"}, + {"PublicMemberSuffix", "_s"}, + {"PublicMethodCase", "CamelCase"}, + }, + "struct S{ int m_m^ember_s;};", + "struct S{ int SummonMember() const { return m_member_s; }\n" + "int m_member_s;};"); + + RunGetterTest("get", + { + {"PrivateMemberPrefix", "_"}, + {"PrivateMemberSuffix", ""}, + {"PublicMethodCase", "camelBack"}, + }, + "class S { int ^_member; };", + "class S { int _member; public:\n" + "int getMember() const { return _member; }\n};"); + + // Member prefix and suffix comply with style precedence: + RunGetterTest( + "", + { + {"PrivateMemberPrefix", "pre_"}, + {"PrivateMemberSuffix", "_post"}, + {"MemberPrefix", "not_used"}, + {"MemberSuffix", "not_used"}, + {"MethodCase", "lower_case"}, + }, + "class S { public: S(); private: Foo *pre_foo^_post; };", + "class S { public: S(); Foo * foo() const { return pre_foo_post; }\n" + "private: Foo *pre_foo_post; };"); + + // Method case comply with style precedence: + RunGetterTest("get", + { + {"ProtectedMemberPrefix", "pre_"}, + {"ProtectedMemberSuffix", "_post"}, + {"PublicMethodCase", "Leading_upper_snake_case"}, + {"MethodCase", "lower_case"}, + }, + "class S { public: Foo &Get_foo(); protected: Foo " + "&pre_foo_^post; };", + "unavailable"); + + // Don't comply to unrelated member suffix, then fallback to default getter + // prefix + RunGetterTest( + "", + { + {"ProtectedMemberSuffix", "_post"}, + }, + "class S { public: S(); private: Foo foo^_post; };", + "class S { public: S(); Foo getFoo_post() const { return foo_post; }\n" + "private: Foo foo_post; };"); +} + +} // namespace +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/tweaks/GenerateSetterTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/GenerateSetterTests.cpp new file mode 100644 index 0000000000000..1f3b72db33f3b --- /dev/null +++ b/clang-tools-extra/clangd/unittests/tweaks/GenerateSetterTests.cpp @@ -0,0 +1,162 @@ +//===-- GenerateSetterTests.cpp -------------------------------------------===// +// +// 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 "Config.h" +#include "TweakTesting.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +TWEAK_TEST(GenerateSetter); + +TEST_F(GenerateSetterTest, Availability) { + // available on class member: + EXPECT_AVAILABLE("struct S { int ^x, y; };"); + EXPECT_AVAILABLE("class S { private: int ^x; };"); + EXPECT_AVAILABLE("class S { protected: int ^x; };"); + EXPECT_AVAILABLE("union S { int ^x; };"); + EXPECT_AVAILABLE("struct S { int ^x = 0; };"); + // available on forward member: + EXPECT_AVAILABLE("/*error-ok*/class Forward; class A { Forward ^f; };"); + // available on pointer type: + EXPECT_AVAILABLE("class Forward; class A { Forward *^f; };"); + // available on reference type: + EXPECT_AVAILABLE("class A { int &^f; };"); + + // unavailable outside class member: + EXPECT_UNAVAILABLE("^struct ^S ^{ int f^oo(); ^int x, y; };"); + // unavailable if method already exists: + EXPECT_UNAVAILABLE("struct S { int setX(); int ^x, y; };"); + // unavailable on constant type: + EXPECT_UNAVAILABLE("class S { const int ^x, y; };"); + // unavailable on static member: + EXPECT_UNAVAILABLE("struct S { static int ^x; };"); +} + +TEST_F(GenerateSetterTest, Edits) { + auto RunSetterTest = [&](llvm::StringRef SetterPrefix, + llvm::StringMap<std::string> Options, + llvm::StringRef Input, llvm::StringRef Expected) { + Config Cfg; + if (!SetterPrefix.empty()) + Cfg.Style.SetterPrefix = SetterPrefix.str(); + + for (auto &KV : Options) + Cfg.Diagnostics.ClangTidy.CheckOptions.insert_or_assign( + ("readability-identifier-naming." + KV.getKey()).str(), + KV.getValue()); + + WithContextValue WithCfg(Config::Key, std::move(Cfg)); + EXPECT_EQ(apply(Input.str()), Expected.str()); + }; + + Header = R"cpp( + struct Foo { + char a; + char b; + char c; + }; + struct Bigfoo { + long a; + long b; + long c; + long d; + }; + )cpp"; + + // Comply with style configuration: + RunSetterTest( + "put", + { + {"PublicMemberPrefix", "m_"}, + {"PublicMemberSuffix", "_s"}, + {"PublicMethodCase", "CamelCase"}, + }, + "struct S{ int m_m^ember_s;};", + "struct S{ void PutMember(int member) { m_member_s = member; }\n" + "int m_member_s;};"); + + RunSetterTest("set", + { + {"PrivateMemberPrefix", "_"}, + {"PrivateMemberSuffix", ""}, + {"PublicMethodCase", "camelBack"}, + }, + "class S { int ^_member; };", + "class S { int _member; public:\n" + "void setMember(int member) { _member = member; }\n};"); + + // Use const-ref on non trivially copiable member: + RunSetterTest( + "", + { + {"PrivateMemberPrefix", "m_"}, + {"PrivateMemberSuffix", ""}, + {"PublicMethodCase", "camelBack"}, + }, + "class S { Bigfoo ^m_bigfoo; };", + "class S { Bigfoo m_bigfoo; public:\n" + "void setBigfoo(const Bigfoo &bigfoo) { m_bigfoo = bigfoo; }\n};"); + + // Use const-ref on non trivially copiable reference member (don't duplicate + // the reference): + RunSetterTest( + "", + { + {"PrivateMemberPrefix", "m_"}, + {"PrivateMemberSuffix", ""}, + {"PublicMethodCase", "camelBack"}, + }, + "class S { Bigfoo &^m_bigfoo; };", + "class S { Bigfoo &m_bigfoo; public:\n" + "void setBigfoo(const Bigfoo &bigfoo) { m_bigfoo = bigfoo; }\n};"); + + // Member prefix and suffix comply with style precedence: + RunSetterTest("", + { + {"PrivateMemberPrefix", "pre_"}, + {"PrivateMemberSuffix", "_post"}, + {"MemberPrefix", "not_used"}, + {"MemberSuffix", "not_used"}, + {"MethodCase", "lower_case"}, + }, + "class S { public: S(); private: Foo *pre_foo^_post; };", + "class S { public: S(); void set_foo(Foo * foo) { " + "pre_foo_post = foo; }\n" + "private: Foo *pre_foo_post; };"); + + // Method case comply with style precedence: + RunSetterTest( + "set", + { + {"ProtectedMemberPrefix", "pre_"}, + {"ProtectedMemberSuffix", "_post"}, + {"PublicMethodCase", "Leading_upper_snake_case"}, + {"MethodCase", "lower_case"}, + }, + "class S { public: void Set_foo(const Foo &foo); protected: Foo " + "&pre_foo_^post; };", + "unavailable"); + + // Don't comply to unrelated member suffix, then fallback to default setter + // prefix + RunSetterTest("", + { + {"ProtectedMemberSuffix", "_post"}, + }, + "class S { public: S(); private: Foo foo^_post; };", + "class S { public: S(); void setFoo_post(Foo newfoo_post) { " + "foo_post = newfoo_post; }\n" + "private: Foo foo_post; };"); +} + +} // namespace +} // namespace clangd +} // namespace clang diff --git a/llvm/utils/gn/secondary/clang-tools-extra/clangd/refactor/tweaks/BUILD.gn b/llvm/utils/gn/secondary/clang-tools-extra/clangd/refactor/tweaks/BUILD.gn index defa12c240deb..35647f9648552 100644 --- a/llvm/utils/gn/secondary/clang-tools-extra/clangd/refactor/tweaks/BUILD.gn +++ b/llvm/utils/gn/secondary/clang-tools-extra/clangd/refactor/tweaks/BUILD.gn @@ -27,6 +27,7 @@ source_set("tweaks") { "ExpandMacro.cpp", "ExtractFunction.cpp", "ExtractVariable.cpp", + "GenerateGetterTests.cpp" "MemberwiseConstructor.cpp", "ObjCLocalizeStringLiteral.cpp", "ObjCMemberwiseInitializer.cpp", diff --git a/llvm/utils/gn/secondary/clang-tools-extra/clangd/unittests/BUILD.gn b/llvm/utils/gn/secondary/clang-tools-extra/clangd/unittests/BUILD.gn index 8aba04a4fc47d..332fbe9f86744 100644 --- a/llvm/utils/gn/secondary/clang-tools-extra/clangd/unittests/BUILD.gn +++ b/llvm/utils/gn/secondary/clang-tools-extra/clangd/unittests/BUILD.gn @@ -142,6 +142,7 @@ unittest("ClangdTests") { "tweaks/ExpandMacroTests.cpp", "tweaks/ExtractFunctionTests.cpp", "tweaks/ExtractVariableTests.cpp", + "tweaks/GenerateGetterTests.cpp" "tweaks/MemberwiseConstructorTests.cpp", "tweaks/ObjCLocalizeStringLiteralTests.cpp", "tweaks/ObjCMemberwiseInitializerTests.cpp", _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits