From 5321e94281f4944d05ab5f90208933afc1e7cae8 Mon Sep 17 00:00:00 2001 From: Edwin Vane Date: Tue, 20 Aug 2013 19:07:21 +0000 Subject: [PATCH] Adding Replacement serialization support Adding a new data structure for storing the Replacements generated for a single translation unit. Structure contains a vector of Replacements as well a field indicating the main source file of the translation unit. An optional 'Context' field allows for tools to provide any information they want about the context the Replacements were generated in. This context is printed, for example, when detecting conflicts during Replacement deduplication. YAML serialization for this data structure is implemented in this patch. Tests are included. Differential Revision: http://llvm-reviews.chandlerc.com/D1422 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@188818 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Tooling/Refactoring.h | 13 +++ include/clang/Tooling/ReplacementsYaml.h | 91 ++++++++++++++++++ unittests/Tooling/CMakeLists.txt | 1 + unittests/Tooling/ReplacementsYamlTest.cpp | 106 +++++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 include/clang/Tooling/ReplacementsYaml.h create mode 100644 unittests/Tooling/ReplacementsYamlTest.cpp diff --git a/include/clang/Tooling/Refactoring.h b/include/clang/Tooling/Refactoring.h index f5b621154f..aef0208771 100644 --- a/include/clang/Tooling/Refactoring.h +++ b/include/clang/Tooling/Refactoring.h @@ -170,6 +170,19 @@ unsigned shiftedCodePosition(const Replacements& Replaces, unsigned Position); void deduplicate(std::vector &Replaces, std::vector &Conflicts); +/// \brief Collection of Replacements generated from a single translation unit. +struct TranslationUnitReplacements { + /// Name of the main source for the translation unit. + std::string MainSourceFile; + + /// A freeform chunk of text to describe the context of the replacements. + /// Will be printed, for example, when detecting conflicts during replacement + /// deduplication. + std::string Context; + + std::vector Replacements; +}; + /// \brief A tool to run refactorings. /// /// This is a refactoring specific version of \see ClangTool. FrontendActions diff --git a/include/clang/Tooling/ReplacementsYaml.h b/include/clang/Tooling/ReplacementsYaml.h new file mode 100644 index 0000000000..0bef5ed982 --- /dev/null +++ b/include/clang/Tooling/ReplacementsYaml.h @@ -0,0 +1,91 @@ +//===-- ReplacementsYaml.h -- Serialiazation for Replacements ---*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file defines the structure of a YAML document for serializing +/// replacements. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_REPLACEMENTS_YAML_H +#define LLVM_CLANG_TOOLING_REPLACEMENTS_YAML_H + +#include "clang/Tooling/Refactoring.h" +#include "llvm/Support/YAMLTraits.h" +#include +#include + +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::tooling::Replacement) + +namespace llvm { +namespace yaml { + +/// \brief ScalarTraits to read/write std::string objects. +template <> struct ScalarTraits { + static void output(const std::string &Val, void *, llvm::raw_ostream &Out) { + // We need to put quotes around the string to make sure special characters + // in the string is not treated as YAML tokens. + std::string NormalizedVal = std::string("\"") + Val + std::string("\""); + Out << NormalizedVal; + } + + static StringRef input(StringRef Scalar, void *, std::string &Val) { + Val = Scalar; + return StringRef(); + } +}; + +/// \brief Specialized MappingTraits to describe how a Replacement is +/// (de)serialized. +template <> struct MappingTraits { + /// \brief Helper to (de)serialize a Replacement since we don't have direct + /// access to its data members. + struct NormalizedReplacement { + NormalizedReplacement(const IO &) + : FilePath(""), Offset(0), Length(0), ReplacementText("") {} + + NormalizedReplacement(const IO &, const clang::tooling::Replacement &R) + : FilePath(R.getFilePath()), Offset(R.getOffset()), + Length(R.getLength()), ReplacementText(R.getReplacementText()) {} + + clang::tooling::Replacement denormalize(const IO &) { + return clang::tooling::Replacement(FilePath, Offset, Length, + ReplacementText); + } + + std::string FilePath; + unsigned int Offset; + unsigned int Length; + std::string ReplacementText; + }; + + static void mapping(IO &Io, clang::tooling::Replacement &R) { + MappingNormalization + Keys(Io, R); + Io.mapRequired("FilePath", Keys->FilePath); + Io.mapRequired("Offset", Keys->Offset); + Io.mapRequired("Length", Keys->Length); + Io.mapRequired("ReplacementText", Keys->ReplacementText); + } +}; + +/// \brief Specialized MappingTraits to describe how a +/// TranslationUnitReplacements is (de)serialized. +template <> struct MappingTraits { + static void mapping(IO &Io, + clang::tooling::TranslationUnitReplacements &Doc) { + Io.mapRequired("MainSourceFile", Doc.MainSourceFile); + Io.mapOptional("Context", Doc.Context, std::string()); + Io.mapRequired("Replacements", Doc.Replacements); + } +}; +} // end namespace yaml +} // end namespace llvm + +#endif // LLVM_CLANG_TOOLING_REPLACEMENTS_YAML_H diff --git a/unittests/Tooling/CMakeLists.txt b/unittests/Tooling/CMakeLists.txt index 245c0599d4..33d7617007 100644 --- a/unittests/Tooling/CMakeLists.txt +++ b/unittests/Tooling/CMakeLists.txt @@ -14,6 +14,7 @@ add_clang_unittest(ToolingTests RefactoringTest.cpp RewriterTest.cpp RefactoringCallbacksTest.cpp + ReplacementsYamlTest.cpp ) target_link_libraries(ToolingTests diff --git a/unittests/Tooling/ReplacementsYamlTest.cpp b/unittests/Tooling/ReplacementsYamlTest.cpp new file mode 100644 index 0000000000..627002a74e --- /dev/null +++ b/unittests/Tooling/ReplacementsYamlTest.cpp @@ -0,0 +1,106 @@ +//===- unittests/Tooling/ReplacementsYamlTest.cpp - Serialization tests ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Tests for serialization of Replacements. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/ReplacementsYaml.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace clang::tooling; + +TEST(ReplacementsYamlTest, serializesReplacements) { + + TranslationUnitReplacements Doc; + + Doc.MainSourceFile = "/path/to/source.cpp"; + Doc.Context = "some context"; + Doc.Replacements + .push_back(Replacement("/path/to/file1.h", 232, 56, "replacement #1")); + Doc.Replacements + .push_back(Replacement("/path/to/file2.h", 301, 2, "replacement #2")); + + std::string YamlContent; + llvm::raw_string_ostream YamlContentStream(YamlContent); + + yaml::Output YAML(YamlContentStream); + YAML << Doc; + + // NOTE: If this test starts to fail for no obvious reason, check whitespace. + ASSERT_STREQ("---\n" + "MainSourceFile: \"/path/to/source.cpp\"\n" + "Context: \"some context\"\n" + "Replacements: \n" // Extra whitespace here! + " - FilePath: \"/path/to/file1.h\"\n" + " Offset: 232\n" + " Length: 56\n" + " ReplacementText: \"replacement #1\"\n" + " - FilePath: \"/path/to/file2.h\"\n" + " Offset: 301\n" + " Length: 2\n" + " ReplacementText: \"replacement #2\"\n" + "...\n", + YamlContentStream.str().c_str()); +} + +TEST(ReplacementsYamlTest, deserializesReplacements) { + std::string YamlContent = "---\n" + "MainSourceFile: \"/path/to/source.cpp\"\n" + "Context: \"some context\"\n" + "Replacements:\n" + " - FilePath: \"/path/to/file1.h\"\n" + " Offset: 232\n" + " Length: 56\n" + " ReplacementText: \"replacement #1\"\n" + " - FilePath: \"/path/to/file2.h\"\n" + " Offset: 301\n" + " Length: 2\n" + " ReplacementText: \"replacement #2\"\n" + "...\n"; + TranslationUnitReplacements DocActual; + yaml::Input YAML(YamlContent); + YAML >> DocActual; + ASSERT_FALSE(YAML.error()); + ASSERT_EQ(2u, DocActual.Replacements.size()); + ASSERT_EQ("/path/to/source.cpp", DocActual.MainSourceFile); + ASSERT_EQ("some context", DocActual.Context); + ASSERT_EQ("/path/to/file1.h", DocActual.Replacements[0].getFilePath()); + ASSERT_EQ(232u, DocActual.Replacements[0].getOffset()); + ASSERT_EQ(56u, DocActual.Replacements[0].getLength()); + ASSERT_EQ("replacement #1", DocActual.Replacements[0].getReplacementText()); + ASSERT_EQ("/path/to/file2.h", DocActual.Replacements[1].getFilePath()); + ASSERT_EQ(301u, DocActual.Replacements[1].getOffset()); + ASSERT_EQ(2u, DocActual.Replacements[1].getLength()); + ASSERT_EQ("replacement #2", DocActual.Replacements[1].getReplacementText()); +} + +TEST(ReplacementsYamlTest, deserializesWithoutContext) { + // Make sure a doc can be read without the context field. + std::string YamlContent = "---\n" + "MainSourceFile: \"/path/to/source.cpp\"\n" + "Replacements:\n" + " - FilePath: \"target_file.h\"\n" + " Offset: 1\n" + " Length: 10\n" + " ReplacementText: \"replacement\"\n" + "...\n"; + TranslationUnitReplacements DocActual; + yaml::Input YAML(YamlContent); + YAML >> DocActual; + ASSERT_FALSE(YAML.error()); + ASSERT_EQ("/path/to/source.cpp", DocActual.MainSourceFile); + ASSERT_EQ(1u, DocActual.Replacements.size()); + ASSERT_EQ(std::string(), DocActual.Context); + ASSERT_EQ("target_file.h", DocActual.Replacements[0].getFilePath()); + ASSERT_EQ(1u, DocActual.Replacements[0].getOffset()); + ASSERT_EQ(10u, DocActual.Replacements[0].getLength()); + ASSERT_EQ("replacement", DocActual.Replacements[0].getReplacementText()); +} -- 2.40.0