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 @@ -20,6 +20,7 @@ ExpandMacro.cpp ExtractFunction.cpp ExtractVariable.cpp + ObjCLocalizeStringLiteral.cpp RawStringLiteral.cpp RemoveUsingNamespace.cpp SwapIfBranches.cpp diff --git a/clang-tools-extra/clangd/refactor/tweaks/ObjCLocalizeStringLiteral.cpp b/clang-tools-extra/clangd/refactor/tweaks/ObjCLocalizeStringLiteral.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/refactor/tweaks/ObjCLocalizeStringLiteral.cpp @@ -0,0 +1,85 @@ +//===--- ObjcLocalizeStringLiteral.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 "Logger.h" +#include "ParsedAST.h" +#include "SourceCode.h" +#include "refactor/Tweak.h" +#include "clang/AST/ExprObjC.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { +namespace { + +/// Wraps an Objective-C string literal with the NSLocalizedString macro. +/// Before: +/// @"description" +/// ^^^ +/// After: +/// NSLocalizedString(@"description", @"") +class ObjCLocalizeStringLiteral : public Tweak { +public: + const char *id() const override final; + Intent intent() const override { return Intent::Refactor; } + + bool prepare(const Selection &Inputs) override; + Expected apply(const Selection &Inputs) override; + std::string title() const override; + +private: + const clang::ObjCStringLiteral *Str = nullptr; +}; + +REGISTER_TWEAK(ObjCLocalizeStringLiteral) + +bool ObjCLocalizeStringLiteral::prepare(const Selection &Inputs) { + const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor(); + if (!N) + return false; + // Allow the refactoring even if the user selected only the C string part + // of the expression. + if (N->ASTNode.get()) { + if (N->Parent) + N = N->Parent; + } + Str = dyn_cast_or_null(N->ASTNode.get()); + return Str; +} + +Expected +ObjCLocalizeStringLiteral::apply(const Selection &Inputs) { + auto &SM = Inputs.AST.getSourceManager(); + auto &LangOpts = Inputs.AST.getASTContext().getLangOpts(); + auto Reps = tooling::Replacements(tooling::Replacement( + SM, CharSourceRange::getCharRange(Str->getBeginLoc()), + "NSLocalizedString(", LangOpts)); + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Str->getEndLoc(), 0, Inputs.AST.getSourceManager(), LangOpts); + if (auto Err = Reps.add(tooling::Replacement( + SM, CharSourceRange::getCharRange(EndLoc), ", @\"\")", LangOpts))) + return std::move(Err); + return Effect::mainFileEdit(SM, std::move(Reps)); +} + +std::string ObjCLocalizeStringLiteral::title() const { + return "Wrap in NSLocalizedString"; +} + +} // namespace +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/unittests/TweakTests.cpp b/clang-tools-extra/clangd/unittests/TweakTests.cpp --- a/clang-tools-extra/clangd/unittests/TweakTests.cpp +++ b/clang-tools-extra/clangd/unittests/TweakTests.cpp @@ -122,6 +122,25 @@ EXPECT_EQ(apply(Input), Output); } +TWEAK_TEST(ObjCLocalizeStringLiteral); +TEST_F(ObjCLocalizeStringLiteralTest, Test) { + ExtraArgs.push_back("-x"); + ExtraArgs.push_back("objective-c"); + + // Ensure the the action can be initiated in the string literal. + EXPECT_AVAILABLE(R"(id x = ^[[@[[^"^t^est^"]]]];)"); + + // Ensure that the action can't be initiated in other places. + EXPECT_UNAVAILABLE(R"([[i^d ^[[x]] ^= @"test";^]])"); + + // Ensure that the action is not available for regular C strings. + EXPECT_UNAVAILABLE(R"(const char * x= "^test";)"); + + const char *Input = R"(id x = [[@"test"]];)"; + const char *Output = R"(id x = NSLocalizedString(@"test", @"");)"; + EXPECT_EQ(apply(Input), Output); +} + TWEAK_TEST(DumpAST); TEST_F(DumpASTTest, Test) { EXPECT_AVAILABLE("^int f^oo() { re^turn 2 ^+ 2; }");