Skip to content

Commit 27f1244

Browse files
committed
[clangd] Add a tweak refactoring to wrap Objective-C string literals in NSLocalizedString macros
The commit adds a refactoring to Clangd that mimics the existing refactoring action in Xcode that wraps around an Objective-C string literal in an NSLocalizedString macro. Differential Revision: https://reviews.llvm.org/D69543
1 parent c0ee022 commit 27f1244

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ add_clang_library(clangDaemonTweaks OBJECT
2020
ExpandMacro.cpp
2121
ExtractFunction.cpp
2222
ExtractVariable.cpp
23+
ObjCLocalizeStringLiteral.cpp
2324
RawStringLiteral.cpp
2425
RemoveUsingNamespace.cpp
2526
SwapIfBranches.cpp
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//===--- ObjcLocalizeStringLiteral.cpp ---------------------------*- C++-*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "Logger.h"
10+
#include "ParsedAST.h"
11+
#include "SourceCode.h"
12+
#include "refactor/Tweak.h"
13+
#include "clang/AST/ExprObjC.h"
14+
#include "clang/Basic/LangOptions.h"
15+
#include "clang/Basic/SourceLocation.h"
16+
#include "clang/Basic/SourceManager.h"
17+
#include "clang/Tooling/Core/Replacement.h"
18+
#include "llvm/ADT/None.h"
19+
#include "llvm/ADT/Optional.h"
20+
#include "llvm/ADT/StringRef.h"
21+
#include "llvm/ADT/iterator_range.h"
22+
#include "llvm/Support/Casting.h"
23+
#include "llvm/Support/Error.h"
24+
25+
namespace clang {
26+
namespace clangd {
27+
namespace {
28+
29+
/// Wraps an Objective-C string literal with the NSLocalizedString macro.
30+
/// Before:
31+
/// @"description"
32+
/// ^^^
33+
/// After:
34+
/// NSLocalizedString(@"description", @"")
35+
class ObjCLocalizeStringLiteral : public Tweak {
36+
public:
37+
const char *id() const override final;
38+
Intent intent() const override { return Intent::Refactor; }
39+
40+
bool prepare(const Selection &Inputs) override;
41+
Expected<Tweak::Effect> apply(const Selection &Inputs) override;
42+
std::string title() const override;
43+
44+
private:
45+
const clang::ObjCStringLiteral *Str = nullptr;
46+
};
47+
48+
REGISTER_TWEAK(ObjCLocalizeStringLiteral)
49+
50+
bool ObjCLocalizeStringLiteral::prepare(const Selection &Inputs) {
51+
const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
52+
if (!N)
53+
return false;
54+
// Allow the refactoring even if the user selected only the C string part
55+
// of the expression.
56+
if (N->ASTNode.get<StringLiteral>()) {
57+
if (N->Parent)
58+
N = N->Parent;
59+
}
60+
Str = dyn_cast_or_null<ObjCStringLiteral>(N->ASTNode.get<Stmt>());
61+
return Str;
62+
}
63+
64+
Expected<Tweak::Effect>
65+
ObjCLocalizeStringLiteral::apply(const Selection &Inputs) {
66+
auto &SM = Inputs.AST.getSourceManager();
67+
auto &LangOpts = Inputs.AST.getASTContext().getLangOpts();
68+
auto Reps = tooling::Replacements(tooling::Replacement(
69+
SM, CharSourceRange::getCharRange(Str->getBeginLoc()),
70+
"NSLocalizedString(", LangOpts));
71+
SourceLocation EndLoc = Lexer::getLocForEndOfToken(
72+
Str->getEndLoc(), 0, Inputs.AST.getSourceManager(), LangOpts);
73+
if (auto Err = Reps.add(tooling::Replacement(
74+
SM, CharSourceRange::getCharRange(EndLoc), ", @\"\")", LangOpts)))
75+
return std::move(Err);
76+
return Effect::mainFileEdit(SM, std::move(Reps));
77+
}
78+
79+
std::string ObjCLocalizeStringLiteral::title() const {
80+
return "Wrap in NSLocalizedString";
81+
}
82+
83+
} // namespace
84+
} // namespace clangd
85+
} // namespace clang

clang-tools-extra/clangd/unittests/TweakTests.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,25 @@ literal)")cpp";
122122
EXPECT_EQ(apply(Input), Output);
123123
}
124124

125+
TWEAK_TEST(ObjCLocalizeStringLiteral);
126+
TEST_F(ObjCLocalizeStringLiteralTest, Test) {
127+
ExtraArgs.push_back("-x");
128+
ExtraArgs.push_back("objective-c");
129+
130+
// Ensure the the action can be initiated in the string literal.
131+
EXPECT_AVAILABLE(R"(id x = ^[[@[[^"^t^est^"]]]];)");
132+
133+
// Ensure that the action can't be initiated in other places.
134+
EXPECT_UNAVAILABLE(R"([[i^d ^[[x]] ^= @"test";^]])");
135+
136+
// Ensure that the action is not available for regular C strings.
137+
EXPECT_UNAVAILABLE(R"(const char * x= "^test";)");
138+
139+
const char *Input = R"(id x = [[@"test"]];)";
140+
const char *Output = R"(id x = NSLocalizedString(@"test", @"");)";
141+
EXPECT_EQ(apply(Input), Output);
142+
}
143+
125144
TWEAK_TEST(DumpAST);
126145
TEST_F(DumpASTTest, Test) {
127146
EXPECT_AVAILABLE("^int f^oo() { re^turn 2 ^+ 2; }");

0 commit comments

Comments
 (0)