Index: include/clang/Tooling/Core/Replacement.h =================================================================== --- /dev/null +++ include/clang/Tooling/Core/Replacement.h @@ -0,0 +1,228 @@ +//===--- Replacement.h - Framework for clang refactoring tools --*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Classes supporting refactorings that span multiple translation units. +// While single translation unit refactorings are supported via the Rewriter, +// when refactoring multiple translation units changes must be stored in a +// SourceManager independent form, duplicate changes need to be removed, and +// all changes must be applied at once at the end of the refactoring so that +// the code is always parseable. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_CORE_REPLACEMENT_H +#define LLVM_CLANG_TOOLING_CORE_REPLACEMENT_H + +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace clang { + +class Rewriter; + +namespace tooling { + +/// \brief A source range independent of the \c SourceManager. +class Range { +public: + Range() : Offset(0), Length(0) {} + Range(unsigned Offset, unsigned Length) : Offset(Offset), Length(Length) {} + + /// \brief Accessors. + /// @{ + unsigned getOffset() const { return Offset; } + unsigned getLength() const { return Length; } + /// @} + + /// \name Range Predicates + /// @{ + /// \brief Whether this range overlaps with \p RHS or not. + bool overlapsWith(Range RHS) const { + return Offset + Length > RHS.Offset && Offset < RHS.Offset + RHS.Length; + } + + /// \brief Whether this range contains \p RHS or not. + bool contains(Range RHS) const { + return RHS.Offset >= Offset && + (RHS.Offset + RHS.Length) <= (Offset + Length); + } + /// @} + +private: + unsigned Offset; + unsigned Length; +}; + +/// \brief A text replacement. +/// +/// Represents a SourceManager independent replacement of a range of text in a +/// specific file. +class Replacement { +public: + /// \brief Creates an invalid (not applicable) replacement. + Replacement(); + + /// \brief Creates a replacement of the range [Offset, Offset+Length) in + /// FilePath with ReplacementText. + /// + /// \param FilePath A source file accessible via a SourceManager. + /// \param Offset The byte offset of the start of the range in the file. + /// \param Length The length of the range in bytes. + Replacement(StringRef FilePath, unsigned Offset, + unsigned Length, StringRef ReplacementText); + + /// \brief Creates a Replacement of the range [Start, Start+Length) with + /// ReplacementText. + Replacement(const SourceManager &Sources, SourceLocation Start, unsigned Length, + StringRef ReplacementText); + + /// \brief Creates a Replacement of the given range with ReplacementText. + Replacement(const SourceManager &Sources, const CharSourceRange &Range, + StringRef ReplacementText); + + /// \brief Creates a Replacement of the node with ReplacementText. + template + Replacement(const SourceManager &Sources, const Node &NodeToReplace, + StringRef ReplacementText); + + /// \brief Returns whether this replacement can be applied to a file. + /// + /// Only replacements that are in a valid file can be applied. + bool isApplicable() const; + + /// \brief Accessors. + /// @{ + StringRef getFilePath() const { return FilePath; } + unsigned getOffset() const { return ReplacementRange.getOffset(); } + unsigned getLength() const { return ReplacementRange.getLength(); } + StringRef getReplacementText() const { return ReplacementText; } + /// @} + + /// \brief Applies the replacement on the Rewriter. + bool apply(Rewriter &Rewrite) const; + + /// \brief Returns a human readable string representation. + std::string toString() const; + + private: + void setFromSourceLocation(const SourceManager &Sources, SourceLocation Start, + unsigned Length, StringRef ReplacementText); + void setFromSourceRange(const SourceManager &Sources, + const CharSourceRange &Range, + StringRef ReplacementText); + + std::string FilePath; + Range ReplacementRange; + std::string ReplacementText; +}; + +/// \brief Less-than operator between two Replacements. +bool operator<(const Replacement &LHS, const Replacement &RHS); + +/// \brief Equal-to operator between two Replacements. +bool operator==(const Replacement &LHS, const Replacement &RHS); + +/// \brief A set of Replacements. +/// FIXME: Change to a vector and deduplicate in the RefactoringTool. +typedef std::set Replacements; + +/// \brief Apply all replacements in \p Replaces to the Rewriter \p Rewrite. +/// +/// Replacement applications happen independently of the success of +/// other applications. +/// +/// \returns true if all replacements apply. false otherwise. +bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite); + +/// \brief Apply all replacements in \p Replaces to the Rewriter \p Rewrite. +/// +/// Replacement applications happen independently of the success of +/// other applications. +/// +/// \returns true if all replacements apply. false otherwise. +bool applyAllReplacements(const std::vector &Replaces, + Rewriter &Rewrite); + +/// \brief Applies all replacements in \p Replaces to \p Code. +/// +/// This completely ignores the path stored in each replacement. If one or more +/// replacements cannot be applied, this returns an empty \c string. +std::string applyAllReplacements(StringRef Code, const Replacements &Replaces); + +/// \brief Calculates how a code \p Position is shifted when \p Replaces are +/// applied. +unsigned shiftedCodePosition(const Replacements& Replaces, unsigned Position); + +/// \brief Calculates how a code \p Position is shifted when \p Replaces are +/// applied. +/// +/// \pre Replaces[i].getOffset() <= Replaces[i+1].getOffset(). +unsigned shiftedCodePosition(const std::vector &Replaces, + unsigned Position); + +/// \brief Removes duplicate Replacements and reports if Replacements conflict +/// with one another. All Replacements are assumed to be in the same file. +/// +/// \post Replaces[i].getOffset() <= Replaces[i+1].getOffset(). +/// +/// This function sorts \p Replaces so that conflicts can be reported simply by +/// offset into \p Replaces and number of elements in the conflict. +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 Apply all replacements in \p Replaces to the Rewriter \p Rewrite. +/// +/// Replacement applications happen independently of the success of +/// other applications. +/// +/// \returns true if all replacements apply. false otherwise. +bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite); + +/// \brief Apply all replacements in \p Replaces to the Rewriter \p Rewrite. +/// +/// Replacement applications happen independently of the success of +/// other applications. +/// +/// \returns true if all replacements apply. false otherwise. +bool applyAllReplacements(const std::vector &Replaces, + Rewriter &Rewrite); + +/// \brief Applies all replacements in \p Replaces to \p Code. +/// +/// This completely ignores the path stored in each replacement. If one or more +/// replacements cannot be applied, this returns an empty \c string. +std::string applyAllReplacements(StringRef Code, const Replacements &Replaces); + +template +Replacement::Replacement(const SourceManager &Sources, + const Node &NodeToReplace, StringRef ReplacementText) { + const CharSourceRange Range = + CharSourceRange::getTokenRange(NodeToReplace->getSourceRange()); + setFromSourceRange(Sources, Range, ReplacementText); +} + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_CORE_REPLACEMENT_H Index: include/clang/Tooling/Refactoring.h =================================================================== --- include/clang/Tooling/Refactoring.h +++ include/clang/Tooling/Refactoring.h @@ -19,180 +19,16 @@ #ifndef LLVM_CLANG_TOOLING_REFACTORING_H #define LLVM_CLANG_TOOLING_REFACTORING_H -#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Core/Replacement.h" #include "clang/Tooling/Tooling.h" -#include "llvm/ADT/StringRef.h" -#include #include namespace clang { class Rewriter; -class SourceLocation; namespace tooling { -/// \brief A source range independent of the \c SourceManager. -class Range { -public: - Range() : Offset(0), Length(0) {} - Range(unsigned Offset, unsigned Length) : Offset(Offset), Length(Length) {} - - /// \brief Accessors. - /// @{ - unsigned getOffset() const { return Offset; } - unsigned getLength() const { return Length; } - /// @} - - /// \name Range Predicates - /// @{ - /// \brief Whether this range overlaps with \p RHS or not. - bool overlapsWith(Range RHS) const { - return Offset + Length > RHS.Offset && Offset < RHS.Offset + RHS.Length; - } - - /// \brief Whether this range contains \p RHS or not. - bool contains(Range RHS) const { - return RHS.Offset >= Offset && - (RHS.Offset + RHS.Length) <= (Offset + Length); - } - /// @} - -private: - unsigned Offset; - unsigned Length; -}; - -/// \brief A text replacement. -/// -/// Represents a SourceManager independent replacement of a range of text in a -/// specific file. -class Replacement { -public: - /// \brief Creates an invalid (not applicable) replacement. - Replacement(); - - /// \brief Creates a replacement of the range [Offset, Offset+Length) in - /// FilePath with ReplacementText. - /// - /// \param FilePath A source file accessible via a SourceManager. - /// \param Offset The byte offset of the start of the range in the file. - /// \param Length The length of the range in bytes. - Replacement(StringRef FilePath, unsigned Offset, - unsigned Length, StringRef ReplacementText); - - /// \brief Creates a Replacement of the range [Start, Start+Length) with - /// ReplacementText. - Replacement(const SourceManager &Sources, SourceLocation Start, unsigned Length, - StringRef ReplacementText); - - /// \brief Creates a Replacement of the given range with ReplacementText. - Replacement(const SourceManager &Sources, const CharSourceRange &Range, - StringRef ReplacementText); - - /// \brief Creates a Replacement of the node with ReplacementText. - template - Replacement(const SourceManager &Sources, const Node &NodeToReplace, - StringRef ReplacementText); - - /// \brief Returns whether this replacement can be applied to a file. - /// - /// Only replacements that are in a valid file can be applied. - bool isApplicable() const; - - /// \brief Accessors. - /// @{ - StringRef getFilePath() const { return FilePath; } - unsigned getOffset() const { return ReplacementRange.getOffset(); } - unsigned getLength() const { return ReplacementRange.getLength(); } - StringRef getReplacementText() const { return ReplacementText; } - /// @} - - /// \brief Applies the replacement on the Rewriter. - bool apply(Rewriter &Rewrite) const; - - /// \brief Returns a human readable string representation. - std::string toString() const; - - private: - void setFromSourceLocation(const SourceManager &Sources, SourceLocation Start, - unsigned Length, StringRef ReplacementText); - void setFromSourceRange(const SourceManager &Sources, - const CharSourceRange &Range, - StringRef ReplacementText); - - std::string FilePath; - Range ReplacementRange; - std::string ReplacementText; -}; - -/// \brief Less-than operator between two Replacements. -bool operator<(const Replacement &LHS, const Replacement &RHS); - -/// \brief Equal-to operator between two Replacements. -bool operator==(const Replacement &LHS, const Replacement &RHS); - -/// \brief A set of Replacements. -/// FIXME: Change to a vector and deduplicate in the RefactoringTool. -typedef std::set Replacements; - -/// \brief Apply all replacements in \p Replaces to the Rewriter \p Rewrite. -/// -/// Replacement applications happen independently of the success of -/// other applications. -/// -/// \returns true if all replacements apply. false otherwise. -bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite); - -/// \brief Apply all replacements in \p Replaces to the Rewriter \p Rewrite. -/// -/// Replacement applications happen independently of the success of -/// other applications. -/// -/// \returns true if all replacements apply. false otherwise. -bool applyAllReplacements(const std::vector &Replaces, - Rewriter &Rewrite); - -/// \brief Applies all replacements in \p Replaces to \p Code. -/// -/// This completely ignores the path stored in each replacement. If one or more -/// replacements cannot be applied, this returns an empty \c string. -std::string applyAllReplacements(StringRef Code, const Replacements &Replaces); - -/// \brief Calculates how a code \p Position is shifted when \p Replaces are -/// applied. -unsigned shiftedCodePosition(const Replacements& Replaces, unsigned Position); - -/// \brief Calculates how a code \p Position is shifted when \p Replaces are -/// applied. -/// -/// \pre Replaces[i].getOffset() <= Replaces[i+1].getOffset(). -unsigned shiftedCodePosition(const std::vector &Replaces, - unsigned Position); - -/// \brief Removes duplicate Replacements and reports if Replacements conflict -/// with one another. All Replacements are assumed to be in the same file. -/// -/// \post Replaces[i].getOffset() <= Replaces[i+1].getOffset(). -/// -/// This function sorts \p Replaces so that conflicts can be reported simply by -/// offset into \p Replaces and number of elements in the conflict. -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 @@ -230,14 +66,6 @@ Replacements Replace; }; -template -Replacement::Replacement(const SourceManager &Sources, - const Node &NodeToReplace, StringRef ReplacementText) { - const CharSourceRange Range = - CharSourceRange::getTokenRange(NodeToReplace->getSourceRange()); - setFromSourceRange(Sources, Range, ReplacementText); -} - } // end namespace tooling } // end namespace clang Index: lib/Format/CMakeLists.txt =================================================================== --- lib/Format/CMakeLists.txt +++ lib/Format/CMakeLists.txt @@ -5,7 +5,6 @@ ContinuationIndenter.cpp Format.cpp FormatToken.cpp - Refactoring.cpp TokenAnnotator.cpp UnwrappedLineParser.cpp WhitespaceManager.cpp @@ -13,4 +12,5 @@ LINK_LIBS clangBasic clangLex + clangToolingCore ) Index: lib/Tooling/CMakeLists.txt =================================================================== --- lib/Tooling/CMakeLists.txt +++ lib/Tooling/CMakeLists.txt @@ -1,5 +1,7 @@ set(LLVM_LINK_COMPONENTS support) +add_subdirectory(Core) + add_clang_library(clangTooling ArgumentsAdjusters.cpp CommonOptionsParser.cpp Index: lib/Tooling/Core/CMakeLists.txt =================================================================== --- /dev/null +++ lib/Tooling/Core/CMakeLists.txt @@ -0,0 +1,10 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangToolingCore + Replacement.cpp + + LINK_LIBS + clangBasic + clangDriver + clangRewrite + ) Index: lib/Tooling/Core/Replacement.cpp =================================================================== --- /dev/null +++ lib/Tooling/Core/Replacement.cpp @@ -0,0 +1,287 @@ +//===--- Replacement.cpp - Framework for clang refactoring tools ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements classes to support/store refactorings. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_os_ostream.h" + +namespace clang { +namespace tooling { + +static const char * const InvalidLocation = ""; + +Replacement::Replacement() + : FilePath(InvalidLocation) {} + +Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length, + StringRef ReplacementText) + : FilePath(FilePath), ReplacementRange(Offset, Length), + ReplacementText(ReplacementText) {} + +Replacement::Replacement(const SourceManager &Sources, SourceLocation Start, + unsigned Length, StringRef ReplacementText) { + setFromSourceLocation(Sources, Start, Length, ReplacementText); +} + +Replacement::Replacement(const SourceManager &Sources, + const CharSourceRange &Range, + StringRef ReplacementText) { + setFromSourceRange(Sources, Range, ReplacementText); +} + +bool Replacement::isApplicable() const { + return FilePath != InvalidLocation; +} + +bool Replacement::apply(Rewriter &Rewrite) const { + SourceManager &SM = Rewrite.getSourceMgr(); + const FileEntry *Entry = SM.getFileManager().getFile(FilePath); + if (!Entry) + return false; + FileID ID; + // FIXME: Use SM.translateFile directly. + SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1); + ID = Location.isValid() ? + SM.getFileID(Location) : + SM.createFileID(Entry, SourceLocation(), SrcMgr::C_User); + // FIXME: We cannot check whether Offset + Length is in the file, as + // the remapping API is not public in the RewriteBuffer. + const SourceLocation Start = + SM.getLocForStartOfFile(ID). + getLocWithOffset(ReplacementRange.getOffset()); + // ReplaceText returns false on success. + // ReplaceText only fails if the source location is not a file location, in + // which case we already returned false earlier. + bool RewriteSucceeded = !Rewrite.ReplaceText( + Start, ReplacementRange.getLength(), ReplacementText); + assert(RewriteSucceeded); + return RewriteSucceeded; +} + +std::string Replacement::toString() const { + std::string result; + llvm::raw_string_ostream stream(result); + stream << FilePath << ": " << ReplacementRange.getOffset() << ":+" + << ReplacementRange.getLength() << ":\"" << ReplacementText << "\""; + return result; +} + +bool operator<(const Replacement &LHS, const Replacement &RHS) { + if (LHS.getOffset() != RHS.getOffset()) + return LHS.getOffset() < RHS.getOffset(); + if (LHS.getLength() != RHS.getLength()) + return LHS.getLength() < RHS.getLength(); + if (LHS.getFilePath() != RHS.getFilePath()) + return LHS.getFilePath() < RHS.getFilePath(); + return LHS.getReplacementText() < RHS.getReplacementText(); +} + +bool operator==(const Replacement &LHS, const Replacement &RHS) { + return LHS.getOffset() == RHS.getOffset() && + LHS.getLength() == RHS.getLength() && + LHS.getFilePath() == RHS.getFilePath() && + LHS.getReplacementText() == RHS.getReplacementText(); +} + +void Replacement::setFromSourceLocation(const SourceManager &Sources, + SourceLocation Start, unsigned Length, + StringRef ReplacementText) { + const std::pair DecomposedLocation = + Sources.getDecomposedLoc(Start); + const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first); + if (Entry) { + // Make FilePath absolute so replacements can be applied correctly when + // relative paths for files are used. + llvm::SmallString<256> FilePath(Entry->getName()); + std::error_code EC = llvm::sys::fs::make_absolute(FilePath); + this->FilePath = EC ? FilePath.c_str() : Entry->getName(); + } else { + this->FilePath = InvalidLocation; + } + this->ReplacementRange = Range(DecomposedLocation.second, Length); + this->ReplacementText = ReplacementText; +} + +// FIXME: This should go into the Lexer, but we need to figure out how +// to handle ranges for refactoring in general first - there is no obvious +// good way how to integrate this into the Lexer yet. +static int getRangeSize(const SourceManager &Sources, + const CharSourceRange &Range) { + SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin()); + SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd()); + std::pair Start = Sources.getDecomposedLoc(SpellingBegin); + std::pair End = Sources.getDecomposedLoc(SpellingEnd); + if (Start.first != End.first) return -1; + if (Range.isTokenRange()) + End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources, + LangOptions()); + return End.second - Start.second; +} + +void Replacement::setFromSourceRange(const SourceManager &Sources, + const CharSourceRange &Range, + StringRef ReplacementText) { + setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()), + getRangeSize(Sources, Range), ReplacementText); +} + +unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) { + unsigned NewPosition = Position; + for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E; + ++I) { + if (I->getOffset() >= Position) + break; + if (I->getOffset() + I->getLength() > Position) + NewPosition += I->getOffset() + I->getLength() - Position; + NewPosition += I->getReplacementText().size() - I->getLength(); + } + return NewPosition; +} + +// FIXME: Remove this function when Replacements is implemented as std::vector +// instead of std::set. +unsigned shiftedCodePosition(const std::vector &Replaces, + unsigned Position) { + unsigned NewPosition = Position; + for (std::vector::const_iterator I = Replaces.begin(), + E = Replaces.end(); + I != E; ++I) { + if (I->getOffset() >= Position) + break; + if (I->getOffset() + I->getLength() > Position) + NewPosition += I->getOffset() + I->getLength() - Position; + NewPosition += I->getReplacementText().size() - I->getLength(); + } + return NewPosition; +} + +void deduplicate(std::vector &Replaces, + std::vector &Conflicts) { + if (Replaces.empty()) + return; + + auto LessNoPath = [](const Replacement &LHS, const Replacement &RHS) { + if (LHS.getOffset() != RHS.getOffset()) + return LHS.getOffset() < RHS.getOffset(); + if (LHS.getLength() != RHS.getLength()) + return LHS.getLength() < RHS.getLength(); + return LHS.getReplacementText() < RHS.getReplacementText(); + }; + + auto EqualNoPath = [](const Replacement &LHS, const Replacement &RHS) { + return LHS.getOffset() == RHS.getOffset() && + LHS.getLength() == RHS.getLength() && + LHS.getReplacementText() == RHS.getReplacementText(); + }; + + // Deduplicate. We don't want to deduplicate based on the path as we assume + // that all replacements refer to the same file (or are symlinks). + std::sort(Replaces.begin(), Replaces.end(), LessNoPath); + Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath), + Replaces.end()); + + // Detect conflicts + Range ConflictRange(Replaces.front().getOffset(), + Replaces.front().getLength()); + unsigned ConflictStart = 0; + unsigned ConflictLength = 1; + for (unsigned i = 1; i < Replaces.size(); ++i) { + Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); + if (ConflictRange.overlapsWith(Current)) { + // Extend conflicted range + ConflictRange = Range(ConflictRange.getOffset(), + std::max(ConflictRange.getLength(), + Current.getOffset() + Current.getLength() - + ConflictRange.getOffset())); + ++ConflictLength; + } else { + if (ConflictLength > 1) + Conflicts.push_back(Range(ConflictStart, ConflictLength)); + ConflictRange = Current; + ConflictStart = i; + ConflictLength = 1; + } + } + + if (ConflictLength > 1) + Conflicts.push_back(Range(ConflictStart, ConflictLength)); +} + +bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) { + bool Result = true; + for (Replacements::const_iterator I = Replaces.begin(), + E = Replaces.end(); + I != E; ++I) { + if (I->isApplicable()) { + Result = I->apply(Rewrite) && Result; + } else { + Result = false; + } + } + return Result; +} + +// FIXME: Remove this function when Replacements is implemented as std::vector +// instead of std::set. +bool applyAllReplacements(const std::vector &Replaces, + Rewriter &Rewrite) { + bool Result = true; + for (std::vector::const_iterator I = Replaces.begin(), + E = Replaces.end(); + I != E; ++I) { + if (I->isApplicable()) { + Result = I->apply(Rewrite) && Result; + } else { + Result = false; + } + } + return Result; +} + +std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) { + FileManager Files((FileSystemOptions())); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs), + new DiagnosticOptions); + SourceManager SourceMgr(Diagnostics, Files); + Rewriter Rewrite(SourceMgr, LangOptions()); + std::unique_ptr Buf = + llvm::MemoryBuffer::getMemBuffer(Code, ""); + const clang::FileEntry *Entry = + Files.getVirtualFile("", Buf->getBufferSize(), 0); + SourceMgr.overrideFileContents(Entry, std::move(Buf)); + FileID ID = + SourceMgr.createFileID(Entry, SourceLocation(), clang::SrcMgr::C_User); + for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end(); + I != E; ++I) { + Replacement Replace("", I->getOffset(), I->getLength(), + I->getReplacementText()); + if (!Replace.apply(Rewrite)) + return ""; + } + std::string Result; + llvm::raw_string_ostream OS(Result); + Rewrite.getEditBuffer(ID).write(OS); + OS.flush(); + return Result; +} + +} // end namespace tooling +} // end namespace clang + Index: lib/Tooling/Refactoring.cpp =================================================================== --- lib/Tooling/Refactoring.cpp +++ lib/Tooling/Refactoring.cpp @@ -14,7 +14,6 @@ #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" -#include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Lex/Lexer.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/Tooling/Refactoring.h" @@ -25,294 +24,6 @@ namespace clang { namespace tooling { -static const char * const InvalidLocation = ""; - -Replacement::Replacement() - : FilePath(InvalidLocation) {} - -Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length, - StringRef ReplacementText) - : FilePath(FilePath), ReplacementRange(Offset, Length), - ReplacementText(ReplacementText) {} - -Replacement::Replacement(const SourceManager &Sources, SourceLocation Start, - unsigned Length, StringRef ReplacementText) { - setFromSourceLocation(Sources, Start, Length, ReplacementText); -} - -Replacement::Replacement(const SourceManager &Sources, - const CharSourceRange &Range, - StringRef ReplacementText) { - setFromSourceRange(Sources, Range, ReplacementText); -} - -bool Replacement::isApplicable() const { - return FilePath != InvalidLocation; -} - -bool Replacement::apply(Rewriter &Rewrite) const { - SourceManager &SM = Rewrite.getSourceMgr(); - const FileEntry *Entry = SM.getFileManager().getFile(FilePath); - if (!Entry) - return false; - FileID ID; - // FIXME: Use SM.translateFile directly. - SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1); - ID = Location.isValid() ? - SM.getFileID(Location) : - SM.createFileID(Entry, SourceLocation(), SrcMgr::C_User); - // FIXME: We cannot check whether Offset + Length is in the file, as - // the remapping API is not public in the RewriteBuffer. - const SourceLocation Start = - SM.getLocForStartOfFile(ID). - getLocWithOffset(ReplacementRange.getOffset()); - // ReplaceText returns false on success. - // ReplaceText only fails if the source location is not a file location, in - // which case we already returned false earlier. - bool RewriteSucceeded = !Rewrite.ReplaceText( - Start, ReplacementRange.getLength(), ReplacementText); - assert(RewriteSucceeded); - return RewriteSucceeded; -} - -std::string Replacement::toString() const { - std::string result; - llvm::raw_string_ostream stream(result); - stream << FilePath << ": " << ReplacementRange.getOffset() << ":+" - << ReplacementRange.getLength() << ":\"" << ReplacementText << "\""; - return result; -} - -bool operator<(const Replacement &LHS, const Replacement &RHS) { - if (LHS.getOffset() != RHS.getOffset()) - return LHS.getOffset() < RHS.getOffset(); - if (LHS.getLength() != RHS.getLength()) - return LHS.getLength() < RHS.getLength(); - if (LHS.getFilePath() != RHS.getFilePath()) - return LHS.getFilePath() < RHS.getFilePath(); - return LHS.getReplacementText() < RHS.getReplacementText(); -} - -bool operator==(const Replacement &LHS, const Replacement &RHS) { - return LHS.getOffset() == RHS.getOffset() && - LHS.getLength() == RHS.getLength() && - LHS.getFilePath() == RHS.getFilePath() && - LHS.getReplacementText() == RHS.getReplacementText(); -} - -void Replacement::setFromSourceLocation(const SourceManager &Sources, - SourceLocation Start, unsigned Length, - StringRef ReplacementText) { - const std::pair DecomposedLocation = - Sources.getDecomposedLoc(Start); - const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first); - if (Entry) { - // Make FilePath absolute so replacements can be applied correctly when - // relative paths for files are used. - llvm::SmallString<256> FilePath(Entry->getName()); - std::error_code EC = llvm::sys::fs::make_absolute(FilePath); - this->FilePath = EC ? FilePath.c_str() : Entry->getName(); - } else { - this->FilePath = InvalidLocation; - } - this->ReplacementRange = Range(DecomposedLocation.second, Length); - this->ReplacementText = ReplacementText; -} - -// FIXME: This should go into the Lexer, but we need to figure out how -// to handle ranges for refactoring in general first - there is no obvious -// good way how to integrate this into the Lexer yet. -static int getRangeSize(const SourceManager &Sources, - const CharSourceRange &Range) { - SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin()); - SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd()); - std::pair Start = Sources.getDecomposedLoc(SpellingBegin); - std::pair End = Sources.getDecomposedLoc(SpellingEnd); - if (Start.first != End.first) return -1; - if (Range.isTokenRange()) - End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources, - LangOptions()); - return End.second - Start.second; -} - -void Replacement::setFromSourceRange(const SourceManager &Sources, - const CharSourceRange &Range, - StringRef ReplacementText) { - setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()), - getRangeSize(Sources, Range), ReplacementText); -} - -bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) { - bool Result = true; - for (Replacements::const_iterator I = Replaces.begin(), - E = Replaces.end(); - I != E; ++I) { - if (I->isApplicable()) { - Result = I->apply(Rewrite) && Result; - } else { - Result = false; - } - } - return Result; -} - -// FIXME: Remove this function when Replacements is implemented as std::vector -// instead of std::set. -bool applyAllReplacements(const std::vector &Replaces, - Rewriter &Rewrite) { - bool Result = true; - for (std::vector::const_iterator I = Replaces.begin(), - E = Replaces.end(); - I != E; ++I) { - if (I->isApplicable()) { - Result = I->apply(Rewrite) && Result; - } else { - Result = false; - } - } - return Result; -} - -std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) { - FileManager Files((FileSystemOptions())); - DiagnosticsEngine Diagnostics( - IntrusiveRefCntPtr(new DiagnosticIDs), - new DiagnosticOptions); - Diagnostics.setClient(new TextDiagnosticPrinter( - llvm::outs(), &Diagnostics.getDiagnosticOptions())); - SourceManager SourceMgr(Diagnostics, Files); - Rewriter Rewrite(SourceMgr, LangOptions()); - std::unique_ptr Buf = - llvm::MemoryBuffer::getMemBuffer(Code, ""); - const clang::FileEntry *Entry = - Files.getVirtualFile("", Buf->getBufferSize(), 0); - SourceMgr.overrideFileContents(Entry, std::move(Buf)); - FileID ID = - SourceMgr.createFileID(Entry, SourceLocation(), clang::SrcMgr::C_User); - for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end(); - I != E; ++I) { - Replacement Replace("", I->getOffset(), I->getLength(), - I->getReplacementText()); - if (!Replace.apply(Rewrite)) - return ""; - } - std::string Result; - llvm::raw_string_ostream OS(Result); - Rewrite.getEditBuffer(ID).write(OS); - OS.flush(); - return Result; -} - -unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) { - unsigned NewPosition = Position; - for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E; - ++I) { - if (I->getOffset() >= Position) - break; - if (I->getOffset() + I->getLength() > Position) - NewPosition += I->getOffset() + I->getLength() - Position; - NewPosition += I->getReplacementText().size() - I->getLength(); - } - return NewPosition; -} - -// FIXME: Remove this function when Replacements is implemented as std::vector -// instead of std::set. -unsigned shiftedCodePosition(const std::vector &Replaces, - unsigned Position) { - unsigned NewPosition = Position; - for (std::vector::const_iterator I = Replaces.begin(), - E = Replaces.end(); - I != E; ++I) { - if (I->getOffset() >= Position) - break; - if (I->getOffset() + I->getLength() > Position) - NewPosition += I->getOffset() + I->getLength() - Position; - NewPosition += I->getReplacementText().size() - I->getLength(); - } - return NewPosition; -} - -void deduplicate(std::vector &Replaces, - std::vector &Conflicts) { - if (Replaces.empty()) - return; - - auto LessNoPath = [](const Replacement &LHS, const Replacement &RHS) { - if (LHS.getOffset() != RHS.getOffset()) - return LHS.getOffset() < RHS.getOffset(); - if (LHS.getLength() != RHS.getLength()) - return LHS.getLength() < RHS.getLength(); - return LHS.getReplacementText() < RHS.getReplacementText(); - }; - - auto EqualNoPath = [](const Replacement &LHS, const Replacement &RHS) { - return LHS.getOffset() == RHS.getOffset() && - LHS.getLength() == RHS.getLength() && - LHS.getReplacementText() == RHS.getReplacementText(); - }; - - // Deduplicate. We don't want to deduplicate based on the path as we assume - // that all replacements refer to the same file (or are symlinks). - std::sort(Replaces.begin(), Replaces.end(), LessNoPath); - Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath), - Replaces.end()); - - // Detect conflicts - Range ConflictRange(Replaces.front().getOffset(), - Replaces.front().getLength()); - unsigned ConflictStart = 0; - unsigned ConflictLength = 1; - for (unsigned i = 1; i < Replaces.size(); ++i) { - Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); - if (ConflictRange.overlapsWith(Current)) { - // Extend conflicted range - ConflictRange = Range(ConflictRange.getOffset(), - std::max(ConflictRange.getLength(), - Current.getOffset() + Current.getLength() - - ConflictRange.getOffset())); - ++ConflictLength; - } else { - if (ConflictLength > 1) - Conflicts.push_back(Range(ConflictStart, ConflictLength)); - ConflictRange = Current; - ConflictStart = i; - ConflictLength = 1; - } - } - - if (ConflictLength > 1) - Conflicts.push_back(Range(ConflictStart, ConflictLength)); -} - - -RefactoringTool::RefactoringTool(const CompilationDatabase &Compilations, - ArrayRef SourcePaths) - : ClangTool(Compilations, SourcePaths) {} - -Replacements &RefactoringTool::getReplacements() { return Replace; } - -int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) { - if (int Result = run(ActionFactory)) { - return Result; - } - - LangOptions DefaultLangOptions; - IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); - TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts); - DiagnosticsEngine Diagnostics( - IntrusiveRefCntPtr(new DiagnosticIDs()), - &*DiagOpts, &DiagnosticPrinter, false); - SourceManager Sources(Diagnostics, getFiles()); - Rewriter Rewrite(Sources, DefaultLangOptions); - - if (!applyAllReplacements(Rewrite)) { - llvm::errs() << "Skipped some replacements.\n"; - } - - return saveRewrittenFiles(Rewrite); -} - bool RefactoringTool::applyAllReplacements(Rewriter &Rewrite) { return tooling::applyAllReplacements(Replace, Rewrite); } Index: tools/clang-format/CMakeLists.txt =================================================================== --- tools/clang-format/CMakeLists.txt +++ tools/clang-format/CMakeLists.txt @@ -9,7 +9,6 @@ clangFormat clangLex clangRewrite - clangTooling ) install(TARGETS clang-format RUNTIME DESTINATION bin) Index: unittests/Format/CMakeLists.txt =================================================================== --- unittests/Format/CMakeLists.txt +++ unittests/Format/CMakeLists.txt @@ -11,5 +11,5 @@ target_link_libraries(FormatTests clangFormat - clangTooling + clangToolingCore )