diff --git a/clang-tools-extra/clang-apply-replacements/CMakeLists.txt b/clang-tools-extra/clang-apply-replacements/CMakeLists.txt --- a/clang-tools-extra/clang-apply-replacements/CMakeLists.txt +++ b/clang-tools-extra/clang-apply-replacements/CMakeLists.txt @@ -9,6 +9,7 @@ clangAST clangBasic clangRewrite + clangTooling clangToolingCore clangToolingRefactoring ) diff --git a/clang/include/clang/Tooling/ReplacementsYaml.h b/clang/include/clang/Tooling/ReplacementsYaml.h --- a/clang/include/clang/Tooling/ReplacementsYaml.h +++ b/clang/include/clang/Tooling/ReplacementsYaml.h @@ -35,17 +35,16 @@ NormalizedReplacement(const IO &, const clang::tooling::Replacement &R) : FilePath(R.getFilePath()), Offset(R.getOffset()), - Length(R.getLength()), ReplacementText(R.getReplacementText()) { - size_t lineBreakPos = ReplacementText.find('\n'); - while (lineBreakPos != std::string::npos) { - ReplacementText.replace(lineBreakPos, 1, "\n\n"); - lineBreakPos = ReplacementText.find('\n', lineBreakPos + 2); - } - } + Length(R.getLength()), + ReplacementText(escape(R.getReplacementText())) {} + + std::string escape(StringRef Str); + + std::string unescape(StringRef Str); clang::tooling::Replacement denormalize(const IO &) { return clang::tooling::Replacement(FilePath, Offset, Length, - ReplacementText); + unescape(ReplacementText)); } std::string FilePath; diff --git a/clang/lib/Tooling/CMakeLists.txt b/clang/lib/Tooling/CMakeLists.txt --- a/clang/lib/Tooling/CMakeLists.txt +++ b/clang/lib/Tooling/CMakeLists.txt @@ -25,6 +25,7 @@ JSONCompilationDatabase.cpp Refactoring.cpp RefactoringCallbacks.cpp + ReplacementsYaml.cpp StandaloneExecution.cpp Tooling.cpp diff --git a/clang/lib/Tooling/ReplacementsYaml.cpp b/clang/lib/Tooling/ReplacementsYaml.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Tooling/ReplacementsYaml.cpp @@ -0,0 +1,74 @@ +//===-- ReplacementsYaml.cpp -- Serialiazation for Replacements -----------===// +// +// 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 "clang/Tooling/ReplacementsYaml.h" + +namespace llvm { +namespace yaml { + +namespace { +struct Escapes { + char EscapeChar; + char WrittenChar; +}; +} // namespace + +static constexpr Escapes EscapeChars[] = { + {'\n', 'n'}, {'\r', 'r'}, {'\t', 't'}, {'\\', '\\'}}; + +std::string +MappingTraits::NormalizedReplacement::escape( + StringRef Str) { + std::string Res; + llvm::raw_string_ostream Builder(Res); + for (char C : Str) { + if (llvm::none_of(EscapeChars, [&](Escapes Escape) { + if (C == Escape.EscapeChar) { + Builder << '\\' << Escape.WrittenChar; + return true; + } + return false; + })) { + Builder << C; + } + } + return Res; +} + +std::string +MappingTraits::NormalizedReplacement::unescape( + StringRef Str) { + std::string Res; + llvm::raw_string_ostream Builder(Res); + while (!Str.empty()) { + if (Str.consume_front("\\")) { + if (Str.empty()) { + Builder << '\\'; + break; + } + char C = Str.front(); + Str = Str.drop_front(); + if (llvm::none_of(EscapeChars, [&](Escapes Escape) { + if (C == Escape.WrittenChar) { + Builder << Escape.EscapeChar; + return true; + } + return false; + })) { + Builder << '\\' << C; + } + continue; + } + Builder << Str.front(); + Str = Str.drop_front(); + } + return Res; +} + +} // namespace yaml +} // namespace llvm diff --git a/clang/unittests/Tooling/ReplacementsYamlTest.cpp b/clang/unittests/Tooling/ReplacementsYamlTest.cpp --- a/clang/unittests/Tooling/ReplacementsYamlTest.cpp +++ b/clang/unittests/Tooling/ReplacementsYamlTest.cpp @@ -46,28 +46,36 @@ YamlContentStream.str().c_str()); } -TEST(ReplacementsYamlTest, serializesNewLines) { - TranslationUnitReplacements Doc; +TEST(ReplacementsYamlTest, handlesEscaped) { + TranslationUnitReplacements Doc, NewDoc; Doc.MainSourceFile = "/path/to/source.cpp"; - Doc.Replacements.emplace_back("/path/to/file1.h", 0, 0, "#include \n"); + const StringRef FilePath = "/path/to/file1.h"; + Doc.Replacements.emplace_back(FilePath, 0, 0, "#include \n"); + Doc.Replacements.emplace_back(FilePath, 0, 0, "'\\\t\r\n"); - std::string YamlContent; - llvm::raw_string_ostream YamlContentStream(YamlContent); + SmallString<512> YamlContent; + llvm::raw_svector_ostream YamlContentStream(YamlContent); yaml::Output YAML(YamlContentStream); YAML << Doc; + yaml::Input IYAML(YamlContent); + IYAML >> NewDoc; - // NOTE: If this test starts to fail for no obvious reason, check whitespace. - ASSERT_STREQ("---\n" - "MainSourceFile: '/path/to/source.cpp'\n" - "Replacements:\n" - " - FilePath: '/path/to/file1.h'\n" - " Offset: 0\n" - " Length: 0\n" - " ReplacementText: '#include \n\n'\n" - "...\n", - YamlContentStream.str().c_str()); + ASSERT_FALSE(IYAML.error()); + ASSERT_EQ(Doc.MainSourceFile, NewDoc.MainSourceFile); + ASSERT_EQ(Doc.Replacements.size(), NewDoc.Replacements.size()); + if (Doc.Replacements.size() == NewDoc.Replacements.size()) { + for (auto DocR = Doc.Replacements.begin(), + NewDocR = NewDoc.Replacements.begin(), + DocE = Doc.Replacements.end(); + DocR != DocE; DocR++, NewDocR++) { + ASSERT_EQ(DocR->getFilePath(), NewDocR->getFilePath()); + ASSERT_EQ(DocR->getLength(), NewDocR->getLength()); + ASSERT_EQ(DocR->getOffset(), NewDocR->getOffset()); + ASSERT_EQ(DocR->getReplacementText(), NewDocR->getReplacementText()); + } + } } TEST(ReplacementsYamlTest, deserializesReplacements) {