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 "ParsedAST.h"
10 #include "SourceCode.h"
11 #include "refactor/Tweak.h"
12 #include "support/Logger.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;
intent() const38   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 
REGISTER_TWEAK(ObjCLocalizeStringLiteral)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>
apply(const Selection & Inputs)65 ObjCLocalizeStringLiteral::apply(const Selection &Inputs) {
66   auto *AST = Inputs.AST;
67   auto &SM = AST->getSourceManager();
68   const auto &TB = AST->getTokens();
69   auto Toks = TB.spelledForExpanded(TB.expandedTokens(Str->getSourceRange()));
70   if (!Toks || Toks->empty())
71     return llvm::createStringError(llvm::inconvertibleErrorCode(),
72                                    "Failed to find tokens to replace.");
73   // Insert `NSLocalizedString(` before the literal.
74   auto Reps = tooling::Replacements(tooling::Replacement(
75       SM, Toks->front().location(), 0, "NSLocalizedString("));
76   // Insert `, @"")` after the literal.
77   if (auto Err = Reps.add(
78           tooling::Replacement(SM, Toks->back().endLocation(), 0, ", @\"\")")))
79     return std::move(Err);
80   return Effect::mainFileEdit(SM, std::move(Reps));
81 }
82 
title() const83 std::string ObjCLocalizeStringLiteral::title() const {
84   return "Wrap in NSLocalizedString";
85 }
86 
87 } // namespace
88 } // namespace clangd
89 } // namespace clang
90