123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- //===--- AtomicChange.cpp - AtomicChange implementation -----------------*- 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 "clang/Tooling/Refactoring/AtomicChange.h"
- #include "clang/Tooling/ReplacementsYaml.h"
- #include "llvm/Support/YAMLTraits.h"
- #include <string>
- LLVM_YAML_IS_SEQUENCE_VECTOR(clang::tooling::AtomicChange)
- namespace {
- /// Helper to (de)serialize an AtomicChange since we don't have direct
- /// access to its data members.
- /// Data members of a normalized AtomicChange can be directly mapped from/to
- /// YAML string.
- struct NormalizedAtomicChange {
- NormalizedAtomicChange() = default;
- NormalizedAtomicChange(const llvm::yaml::IO &) {}
- // This converts AtomicChange's internal implementation of the replacements
- // set to a vector of replacements.
- NormalizedAtomicChange(const llvm::yaml::IO &,
- const clang::tooling::AtomicChange &E)
- : Key(E.getKey()), FilePath(E.getFilePath()), Error(E.getError()),
- InsertedHeaders(E.getInsertedHeaders()),
- RemovedHeaders(E.getRemovedHeaders()),
- Replaces(E.getReplacements().begin(), E.getReplacements().end()) {}
- // This is not expected to be called but needed for template instantiation.
- clang::tooling::AtomicChange denormalize(const llvm::yaml::IO &) {
- llvm_unreachable("Do not convert YAML to AtomicChange directly with '>>'. "
- "Use AtomicChange::convertFromYAML instead.");
- }
- std::string Key;
- std::string FilePath;
- std::string Error;
- std::vector<std::string> InsertedHeaders;
- std::vector<std::string> RemovedHeaders;
- std::vector<clang::tooling::Replacement> Replaces;
- };
- } // anonymous namespace
- namespace llvm {
- namespace yaml {
- /// Specialized MappingTraits to describe how an AtomicChange is
- /// (de)serialized.
- template <> struct MappingTraits<NormalizedAtomicChange> {
- static void mapping(IO &Io, NormalizedAtomicChange &Doc) {
- Io.mapRequired("Key", Doc.Key);
- Io.mapRequired("FilePath", Doc.FilePath);
- Io.mapRequired("Error", Doc.Error);
- Io.mapRequired("InsertedHeaders", Doc.InsertedHeaders);
- Io.mapRequired("RemovedHeaders", Doc.RemovedHeaders);
- Io.mapRequired("Replacements", Doc.Replaces);
- }
- };
- /// Specialized MappingTraits to describe how an AtomicChange is
- /// (de)serialized.
- template <> struct MappingTraits<clang::tooling::AtomicChange> {
- static void mapping(IO &Io, clang::tooling::AtomicChange &Doc) {
- MappingNormalization<NormalizedAtomicChange, clang::tooling::AtomicChange>
- Keys(Io, Doc);
- Io.mapRequired("Key", Keys->Key);
- Io.mapRequired("FilePath", Keys->FilePath);
- Io.mapRequired("Error", Keys->Error);
- Io.mapRequired("InsertedHeaders", Keys->InsertedHeaders);
- Io.mapRequired("RemovedHeaders", Keys->RemovedHeaders);
- Io.mapRequired("Replacements", Keys->Replaces);
- }
- };
- } // end namespace yaml
- } // end namespace llvm
- namespace clang {
- namespace tooling {
- namespace {
- // Returns true if there is any line that violates \p ColumnLimit in range
- // [Start, End].
- bool violatesColumnLimit(llvm::StringRef Code, unsigned ColumnLimit,
- unsigned Start, unsigned End) {
- auto StartPos = Code.rfind('\n', Start);
- StartPos = (StartPos == llvm::StringRef::npos) ? 0 : StartPos + 1;
- auto EndPos = Code.find("\n", End);
- if (EndPos == llvm::StringRef::npos)
- EndPos = Code.size();
- llvm::SmallVector<llvm::StringRef, 8> Lines;
- Code.substr(StartPos, EndPos - StartPos).split(Lines, '\n');
- for (llvm::StringRef Line : Lines)
- if (Line.size() > ColumnLimit)
- return true;
- return false;
- }
- std::vector<Range>
- getRangesForFormating(llvm::StringRef Code, unsigned ColumnLimit,
- ApplyChangesSpec::FormatOption Format,
- const clang::tooling::Replacements &Replaces) {
- // kNone suppresses formatting entirely.
- if (Format == ApplyChangesSpec::kNone)
- return {};
- std::vector<clang::tooling::Range> Ranges;
- // This works assuming that replacements are ordered by offset.
- // FIXME: use `getAffectedRanges()` to calculate when it does not include '\n'
- // at the end of an insertion in affected ranges.
- int Offset = 0;
- for (const clang::tooling::Replacement &R : Replaces) {
- int Start = R.getOffset() + Offset;
- int End = Start + R.getReplacementText().size();
- if (!R.getReplacementText().empty() &&
- R.getReplacementText().back() == '\n' && R.getLength() == 0 &&
- R.getOffset() > 0 && R.getOffset() <= Code.size() &&
- Code[R.getOffset() - 1] == '\n')
- // If we are inserting at the start of a line and the replacement ends in
- // a newline, we don't need to format the subsequent line.
- --End;
- Offset += R.getReplacementText().size() - R.getLength();
- if (Format == ApplyChangesSpec::kAll ||
- violatesColumnLimit(Code, ColumnLimit, Start, End))
- Ranges.emplace_back(Start, End - Start);
- }
- return Ranges;
- }
- inline llvm::Error make_string_error(const llvm::Twine &Message) {
- return llvm::make_error<llvm::StringError>(Message,
- llvm::inconvertibleErrorCode());
- }
- // Creates replacements for inserting/deleting #include headers.
- llvm::Expected<Replacements>
- createReplacementsForHeaders(llvm::StringRef FilePath, llvm::StringRef Code,
- llvm::ArrayRef<AtomicChange> Changes,
- const format::FormatStyle &Style) {
- // Create header insertion/deletion replacements to be cleaned up
- // (i.e. converted to real insertion/deletion replacements).
- Replacements HeaderReplacements;
- for (const auto &Change : Changes) {
- for (llvm::StringRef Header : Change.getInsertedHeaders()) {
- std::string EscapedHeader =
- Header.startswith("<") || Header.startswith("\"")
- ? Header.str()
- : ("\"" + Header + "\"").str();
- std::string ReplacementText = "#include " + EscapedHeader;
- // Offset UINT_MAX and length 0 indicate that the replacement is a header
- // insertion.
- llvm::Error Err = HeaderReplacements.add(
- tooling::Replacement(FilePath, UINT_MAX, 0, ReplacementText));
- if (Err)
- return std::move(Err);
- }
- for (const std::string &Header : Change.getRemovedHeaders()) {
- // Offset UINT_MAX and length 1 indicate that the replacement is a header
- // deletion.
- llvm::Error Err =
- HeaderReplacements.add(Replacement(FilePath, UINT_MAX, 1, Header));
- if (Err)
- return std::move(Err);
- }
- }
- // cleanupAroundReplacements() converts header insertions/deletions into
- // actual replacements that add/remove headers at the right location.
- return clang::format::cleanupAroundReplacements(Code, HeaderReplacements,
- Style);
- }
- // Combine replacements in all Changes as a `Replacements`. This ignores the
- // file path in all replacements and replaces them with \p FilePath.
- llvm::Expected<Replacements>
- combineReplacementsInChanges(llvm::StringRef FilePath,
- llvm::ArrayRef<AtomicChange> Changes) {
- Replacements Replaces;
- for (const auto &Change : Changes)
- for (const auto &R : Change.getReplacements())
- if (auto Err = Replaces.add(Replacement(
- FilePath, R.getOffset(), R.getLength(), R.getReplacementText())))
- return std::move(Err);
- return Replaces;
- }
- } // end namespace
- AtomicChange::AtomicChange(const SourceManager &SM,
- SourceLocation KeyPosition) {
- const FullSourceLoc FullKeyPosition(KeyPosition, SM);
- std::pair<FileID, unsigned> FileIDAndOffset =
- FullKeyPosition.getSpellingLoc().getDecomposedLoc();
- const FileEntry *FE = SM.getFileEntryForID(FileIDAndOffset.first);
- assert(FE && "Cannot create AtomicChange with invalid location.");
- FilePath = std::string(FE->getName());
- Key = FilePath + ":" + std::to_string(FileIDAndOffset.second);
- }
- AtomicChange::AtomicChange(const SourceManager &SM, SourceLocation KeyPosition,
- llvm::Any M)
- : AtomicChange(SM, KeyPosition) {
- Metadata = std::move(M);
- }
- AtomicChange::AtomicChange(std::string Key, std::string FilePath,
- std::string Error,
- std::vector<std::string> InsertedHeaders,
- std::vector<std::string> RemovedHeaders,
- clang::tooling::Replacements Replaces)
- : Key(std::move(Key)), FilePath(std::move(FilePath)),
- Error(std::move(Error)), InsertedHeaders(std::move(InsertedHeaders)),
- RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) {
- }
- bool AtomicChange::operator==(const AtomicChange &Other) const {
- if (Key != Other.Key || FilePath != Other.FilePath || Error != Other.Error)
- return false;
- if (!(Replaces == Other.Replaces))
- return false;
- // FXIME: Compare header insertions/removals.
- return true;
- }
- std::string AtomicChange::toYAMLString() {
- std::string YamlContent;
- llvm::raw_string_ostream YamlContentStream(YamlContent);
- llvm::yaml::Output YAML(YamlContentStream);
- YAML << *this;
- YamlContentStream.flush();
- return YamlContent;
- }
- AtomicChange AtomicChange::convertFromYAML(llvm::StringRef YAMLContent) {
- NormalizedAtomicChange NE;
- llvm::yaml::Input YAML(YAMLContent);
- YAML >> NE;
- AtomicChange E(NE.Key, NE.FilePath, NE.Error, NE.InsertedHeaders,
- NE.RemovedHeaders, tooling::Replacements());
- for (const auto &R : NE.Replaces) {
- llvm::Error Err = E.Replaces.add(R);
- if (Err)
- llvm_unreachable(
- "Failed to add replacement when Converting YAML to AtomicChange.");
- llvm::consumeError(std::move(Err));
- }
- return E;
- }
- llvm::Error AtomicChange::replace(const SourceManager &SM,
- const CharSourceRange &Range,
- llvm::StringRef ReplacementText) {
- return Replaces.add(Replacement(SM, Range, ReplacementText));
- }
- llvm::Error AtomicChange::replace(const SourceManager &SM, SourceLocation Loc,
- unsigned Length, llvm::StringRef Text) {
- return Replaces.add(Replacement(SM, Loc, Length, Text));
- }
- llvm::Error AtomicChange::insert(const SourceManager &SM, SourceLocation Loc,
- llvm::StringRef Text, bool InsertAfter) {
- if (Text.empty())
- return llvm::Error::success();
- Replacement R(SM, Loc, 0, Text);
- llvm::Error Err = Replaces.add(R);
- if (Err) {
- return llvm::handleErrors(
- std::move(Err), [&](const ReplacementError &RE) -> llvm::Error {
- if (RE.get() != replacement_error::insert_conflict)
- return llvm::make_error<ReplacementError>(RE);
- unsigned NewOffset = Replaces.getShiftedCodePosition(R.getOffset());
- if (!InsertAfter)
- NewOffset -=
- RE.getExistingReplacement()->getReplacementText().size();
- Replacement NewR(R.getFilePath(), NewOffset, 0, Text);
- Replaces = Replaces.merge(Replacements(NewR));
- return llvm::Error::success();
- });
- }
- return llvm::Error::success();
- }
- void AtomicChange::addHeader(llvm::StringRef Header) {
- InsertedHeaders.push_back(std::string(Header));
- }
- void AtomicChange::removeHeader(llvm::StringRef Header) {
- RemovedHeaders.push_back(std::string(Header));
- }
- llvm::Expected<std::string>
- applyAtomicChanges(llvm::StringRef FilePath, llvm::StringRef Code,
- llvm::ArrayRef<AtomicChange> Changes,
- const ApplyChangesSpec &Spec) {
- llvm::Expected<Replacements> HeaderReplacements =
- createReplacementsForHeaders(FilePath, Code, Changes, Spec.Style);
- if (!HeaderReplacements)
- return make_string_error(
- "Failed to create replacements for header changes: " +
- llvm::toString(HeaderReplacements.takeError()));
- llvm::Expected<Replacements> Replaces =
- combineReplacementsInChanges(FilePath, Changes);
- if (!Replaces)
- return make_string_error("Failed to combine replacements in all changes: " +
- llvm::toString(Replaces.takeError()));
- Replacements AllReplaces = std::move(*Replaces);
- for (const auto &R : *HeaderReplacements) {
- llvm::Error Err = AllReplaces.add(R);
- if (Err)
- return make_string_error(
- "Failed to combine existing replacements with header replacements: " +
- llvm::toString(std::move(Err)));
- }
- if (Spec.Cleanup) {
- llvm::Expected<Replacements> CleanReplaces =
- format::cleanupAroundReplacements(Code, AllReplaces, Spec.Style);
- if (!CleanReplaces)
- return make_string_error("Failed to cleanup around replacements: " +
- llvm::toString(CleanReplaces.takeError()));
- AllReplaces = std::move(*CleanReplaces);
- }
- // Apply all replacements.
- llvm::Expected<std::string> ChangedCode =
- applyAllReplacements(Code, AllReplaces);
- if (!ChangedCode)
- return make_string_error("Failed to apply all replacements: " +
- llvm::toString(ChangedCode.takeError()));
- // Sort inserted headers. This is done even if other formatting is turned off
- // as incorrectly sorted headers are always just wrong, it's not a matter of
- // taste.
- Replacements HeaderSortingReplacements = format::sortIncludes(
- Spec.Style, *ChangedCode, AllReplaces.getAffectedRanges(), FilePath);
- ChangedCode = applyAllReplacements(*ChangedCode, HeaderSortingReplacements);
- if (!ChangedCode)
- return make_string_error(
- "Failed to apply replacements for sorting includes: " +
- llvm::toString(ChangedCode.takeError()));
- AllReplaces = AllReplaces.merge(HeaderSortingReplacements);
- std::vector<Range> FormatRanges = getRangesForFormating(
- *ChangedCode, Spec.Style.ColumnLimit, Spec.Format, AllReplaces);
- if (!FormatRanges.empty()) {
- Replacements FormatReplacements =
- format::reformat(Spec.Style, *ChangedCode, FormatRanges, FilePath);
- ChangedCode = applyAllReplacements(*ChangedCode, FormatReplacements);
- if (!ChangedCode)
- return make_string_error(
- "Failed to apply replacements for formatting changed code: " +
- llvm::toString(ChangedCode.takeError()));
- }
- return ChangedCode;
- }
- } // end namespace tooling
- } // end namespace clang
|