Index: clangd/refactor/tweaks/CMakeLists.txt =================================================================== --- clangd/refactor/tweaks/CMakeLists.txt +++ clangd/refactor/tweaks/CMakeLists.txt @@ -12,6 +12,7 @@ # $ to a list of sources, see # clangd/tool/CMakeLists.txt for an example. add_clang_library(clangDaemonTweaks OBJECT + RawStringLiteral.cpp SwapIfBranches.cpp LINK_LIBS Index: clangd/refactor/tweaks/RawStringLiteral.cpp =================================================================== --- /dev/null +++ clangd/refactor/tweaks/RawStringLiteral.cpp @@ -0,0 +1,103 @@ +//===--- RawStringLiteral.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 "ClangdUnit.h" +#include "Logger.h" +#include "SourceCode.h" +#include "refactor/Tweak.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.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 { +/// Converts a string literal to a raw string. +/// Before: +/// printf("\"a\"\nb"); +/// ^^^^^^^^^ +/// After: +/// printf(R"("a" +/// b)"); +class RawStringLiteral : public Tweak { +public: + const char *id() const override final; + + bool prepare(const Selection &Inputs) override; + Expected apply(const Selection &Inputs) override; + std::string title() const override; + +private: + const clang::StringLiteral *Str = nullptr; +}; + +REGISTER_TWEAK(RawStringLiteral) + +static bool isNormalString(const StringLiteral &Str, SourceLocation Cursor, + SourceManager &SM) { + // All chunks must be normal ASCII strings, not u8"..." etc. + if (!Str.isAscii()) + return false; + SourceLocation LastTokenBeforeCursor; + for (auto I = Str.tokloc_begin(), E = Str.tokloc_end(); I != E; ++I) { + if (I->isMacroID()) // No tokens in the string may be macro expansions. + return false; + if (SM.isBeforeInTranslationUnit(*I, Cursor) || *I == Cursor) + LastTokenBeforeCursor = *I; + } + // Token we care about must be a normal "string": not raw, u8, etc. + const char* Data = SM.getCharacterData(LastTokenBeforeCursor); + return Data && *Data == '"'; +} + +static bool needsRaw(llvm::StringRef Content) { + return Content.find_first_of("\"\n\t") != StringRef::npos; +} + +static bool canBeRaw(llvm::StringRef Content) { + for (char C : Content) + if (!llvm::isPrint(C) && C != '\n' && C != '\t') + return false; + return !Content.contains(")\""); +} + +bool RawStringLiteral::prepare(const Selection &Inputs) { + const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor(); + if (!N) + return false; + Str = dyn_cast_or_null(N->ASTNode.get()); + return Str && + isNormalString(*Str, Inputs.Cursor, + Inputs.AST.getASTContext().getSourceManager()) && + needsRaw(Str->getBytes()) && canBeRaw(Str->getBytes()); +} + +Expected +RawStringLiteral::apply(const Selection &Inputs) { + return tooling::Replacements( + tooling::Replacement(Inputs.AST.getASTContext().getSourceManager(), Str, + ("R\"(" + Str->getBytes() + ")\"").str(), + Inputs.AST.getASTContext().getLangOpts())); +} + +std::string RawStringLiteral::title() const { return "Convert to raw string"; } + +} // namespace +} // namespace clangd +} // namespace clang + Index: clangd/unittests/TweakTests.cpp =================================================================== --- clangd/unittests/TweakTests.cpp +++ clangd/unittests/TweakTests.cpp @@ -105,9 +105,10 @@ } void checkTransform(llvm::StringRef ID, llvm::StringRef Input, - llvm::StringRef Output) { - EXPECT_THAT_EXPECTED(apply(ID, Input), HasValue(Output)) - << "action id is" << ID; + std::string Output) { + auto Result = apply(ID, Input); + ASSERT_TRUE(bool(Result)) << llvm::toString(Result.takeError()) << Input; + EXPECT_EQ(Output, std::string(*Result)) << Input; } TEST(TweakTest, SwapIfBranches) { @@ -185,6 +186,37 @@ )cpp"); } +TEST(TweakTest, RawStringLiteral) { + llvm::StringLiteral ID = "RawStringLiteral"; + + checkAvailable(ID, R"cpp( + const char *A = ^"^f^o^o^\^n^"; + const char *B = R"(multi )" ^"token " "str\ning"; + )cpp"); + + checkNotAvailable(ID, R"cpp( + const char *A = ^"f^o^o^o^"; // no chars need escaping + const char *B = R"(multi )" ^"token " u8"str\ning"; // not all ascii + const char *C = ^R^"^(^multi )" "token " "str\ning"; // chunk is raw + const char *D = ^"token\n" __FILE__; // chunk is macro expansion + const char *E = ^"a\r\n"; // contains forbidden escape character + )cpp"); + + const char *Input = R"cpp( + const char *X = R"(multi +token)" "\nst^ring\n" "literal"; + } + )cpp"; + const char *Output = R"cpp( + const char *X = R"(multi +token +string +literal)"; + } + )cpp"; + checkTransform(ID, Input, Output); +} + } // namespace } // namespace clangd } // namespace clang