[clang-tools-extra] [llvm] [clangd] Add code action to generate getter and setter for a field in a C++ class (PR #157727)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 9 11:19:33 PDT 2025
https://github.com/GuillaumeF0 updated https://github.com/llvm/llvm-project/pull/157727
>From 4042478aac711d34ef396f54ad314a092b967778 Mon Sep 17 00:00:00 2001
From: Guillaume Frognier <guillaume.frognier at gmail.com>
Date: Tue, 9 Sep 2025 19:25:43 +0200
Subject: [PATCH 1/2] [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",
>From 2d233427c9074da3c26e9afe14dde4c4ec23573f Mon Sep 17 00:00:00 2001
From: Guillaume Frognier <guillaume.frognier at gmail.com>
Date: Tue, 9 Sep 2025 20:19:00 +0200
Subject: [PATCH 2/2] [clangd] Add code action to generate getter and setter
for a field in a C++ class
---
clang-tools-extra/clangd/refactor/GenerateAccessorBase.cpp | 2 +-
clang-tools-extra/clangd/refactor/GenerateAccessorBase.h | 2 +-
clang-tools-extra/clangd/refactor/tweaks/GenerateSetter.cpp | 2 +-
.../secondary/clang-tools-extra/clangd/refactor/tweaks/BUILD.gn | 1 +
.../gn/secondary/clang-tools-extra/clangd/unittests/BUILD.gn | 1 +
5 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/clangd/refactor/GenerateAccessorBase.cpp b/clang-tools-extra/clangd/refactor/GenerateAccessorBase.cpp
index d7d21051babb4..ecfeab71c4184 100644
--- a/clang-tools-extra/clangd/refactor/GenerateAccessorBase.cpp
+++ b/clang-tools-extra/clangd/refactor/GenerateAccessorBase.cpp
@@ -1,4 +1,4 @@
-//===--- GenerateAccessor.cpp - Base class for getter/setter generation ---===//
+//===--- GenerateAccessorBase.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.
diff --git a/clang-tools-extra/clangd/refactor/GenerateAccessorBase.h b/clang-tools-extra/clangd/refactor/GenerateAccessorBase.h
index eda198353329c..9bb8186e6c972 100644
--- a/clang-tools-extra/clangd/refactor/GenerateAccessorBase.h
+++ b/clang-tools-extra/clangd/refactor/GenerateAccessorBase.h
@@ -1,7 +1,7 @@
#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 -----===//
+//===--- GenerateAccessorBase.h ----------------------------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/clang-tools-extra/clangd/refactor/tweaks/GenerateSetter.cpp b/clang-tools-extra/clangd/refactor/tweaks/GenerateSetter.cpp
index 0127f5968b333..d3c43b9d6aad5 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/GenerateSetter.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/GenerateSetter.cpp
@@ -33,7 +33,7 @@ namespace {
//
// Method's prefix can be configured with Style.SetterPrefix.
// Method's parameter prefix can be configured with
-// GetterSetter.SetterParameterPrefix.
+// Style.SetterParameterPrefix.
//
// We place the method inline, other tweaks are available to outline it.
class GenerateSetter : public GenerateAccessorBase {
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 35647f9648552..cba3e0cadf80a 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
@@ -28,6 +28,7 @@ source_set("tweaks") {
"ExtractFunction.cpp",
"ExtractVariable.cpp",
"GenerateGetterTests.cpp"
+ "GenerateSetterTests.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 332fbe9f86744..88c5116cebac0 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
@@ -143,6 +143,7 @@ unittest("ClangdTests") {
"tweaks/ExtractFunctionTests.cpp",
"tweaks/ExtractVariableTests.cpp",
"tweaks/GenerateGetterTests.cpp"
+ "tweaks/GenerateSetterTests.cpp"
"tweaks/MemberwiseConstructorTests.cpp",
"tweaks/ObjCLocalizeStringLiteralTests.cpp",
"tweaks/ObjCMemberwiseInitializerTests.cpp",
More information about the llvm-commits
mailing list