Home | History | Annotate | Line # | Download | only in Refactoring
      1      1.1  joerg //===--- AtomicChange.cpp - AtomicChange implementation -----------------*- C++ -*-===//
      2      1.1  joerg //
      3      1.1  joerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
      4      1.1  joerg // See https://llvm.org/LICENSE.txt for license information.
      5      1.1  joerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
      6      1.1  joerg //
      7      1.1  joerg //===----------------------------------------------------------------------===//
      8      1.1  joerg 
      9      1.1  joerg #include "clang/Tooling/Refactoring/AtomicChange.h"
     10      1.1  joerg #include "clang/Tooling/ReplacementsYaml.h"
     11      1.1  joerg #include "llvm/Support/YAMLTraits.h"
     12      1.1  joerg #include <string>
     13      1.1  joerg 
     14      1.1  joerg LLVM_YAML_IS_SEQUENCE_VECTOR(clang::tooling::AtomicChange)
     15      1.1  joerg 
     16      1.1  joerg namespace {
     17      1.1  joerg /// Helper to (de)serialize an AtomicChange since we don't have direct
     18      1.1  joerg /// access to its data members.
     19      1.1  joerg /// Data members of a normalized AtomicChange can be directly mapped from/to
     20      1.1  joerg /// YAML string.
     21      1.1  joerg struct NormalizedAtomicChange {
     22      1.1  joerg   NormalizedAtomicChange() = default;
     23      1.1  joerg 
     24      1.1  joerg   NormalizedAtomicChange(const llvm::yaml::IO &) {}
     25      1.1  joerg 
     26      1.1  joerg   // This converts AtomicChange's internal implementation of the replacements
     27      1.1  joerg   // set to a vector of replacements.
     28      1.1  joerg   NormalizedAtomicChange(const llvm::yaml::IO &,
     29      1.1  joerg                          const clang::tooling::AtomicChange &E)
     30      1.1  joerg       : Key(E.getKey()), FilePath(E.getFilePath()), Error(E.getError()),
     31      1.1  joerg         InsertedHeaders(E.getInsertedHeaders()),
     32      1.1  joerg         RemovedHeaders(E.getRemovedHeaders()),
     33      1.1  joerg         Replaces(E.getReplacements().begin(), E.getReplacements().end()) {}
     34      1.1  joerg 
     35      1.1  joerg   // This is not expected to be called but needed for template instantiation.
     36      1.1  joerg   clang::tooling::AtomicChange denormalize(const llvm::yaml::IO &) {
     37      1.1  joerg     llvm_unreachable("Do not convert YAML to AtomicChange directly with '>>'. "
     38      1.1  joerg                      "Use AtomicChange::convertFromYAML instead.");
     39      1.1  joerg   }
     40      1.1  joerg   std::string Key;
     41      1.1  joerg   std::string FilePath;
     42      1.1  joerg   std::string Error;
     43      1.1  joerg   std::vector<std::string> InsertedHeaders;
     44      1.1  joerg   std::vector<std::string> RemovedHeaders;
     45      1.1  joerg   std::vector<clang::tooling::Replacement> Replaces;
     46      1.1  joerg };
     47      1.1  joerg } // anonymous namespace
     48      1.1  joerg 
     49      1.1  joerg namespace llvm {
     50      1.1  joerg namespace yaml {
     51      1.1  joerg 
     52      1.1  joerg /// Specialized MappingTraits to describe how an AtomicChange is
     53      1.1  joerg /// (de)serialized.
     54      1.1  joerg template <> struct MappingTraits<NormalizedAtomicChange> {
     55      1.1  joerg   static void mapping(IO &Io, NormalizedAtomicChange &Doc) {
     56      1.1  joerg     Io.mapRequired("Key", Doc.Key);
     57      1.1  joerg     Io.mapRequired("FilePath", Doc.FilePath);
     58      1.1  joerg     Io.mapRequired("Error", Doc.Error);
     59      1.1  joerg     Io.mapRequired("InsertedHeaders", Doc.InsertedHeaders);
     60      1.1  joerg     Io.mapRequired("RemovedHeaders", Doc.RemovedHeaders);
     61      1.1  joerg     Io.mapRequired("Replacements", Doc.Replaces);
     62      1.1  joerg   }
     63      1.1  joerg };
     64      1.1  joerg 
     65      1.1  joerg /// Specialized MappingTraits to describe how an AtomicChange is
     66      1.1  joerg /// (de)serialized.
     67      1.1  joerg template <> struct MappingTraits<clang::tooling::AtomicChange> {
     68      1.1  joerg   static void mapping(IO &Io, clang::tooling::AtomicChange &Doc) {
     69      1.1  joerg     MappingNormalization<NormalizedAtomicChange, clang::tooling::AtomicChange>
     70      1.1  joerg         Keys(Io, Doc);
     71      1.1  joerg     Io.mapRequired("Key", Keys->Key);
     72      1.1  joerg     Io.mapRequired("FilePath", Keys->FilePath);
     73      1.1  joerg     Io.mapRequired("Error", Keys->Error);
     74      1.1  joerg     Io.mapRequired("InsertedHeaders", Keys->InsertedHeaders);
     75      1.1  joerg     Io.mapRequired("RemovedHeaders", Keys->RemovedHeaders);
     76      1.1  joerg     Io.mapRequired("Replacements", Keys->Replaces);
     77      1.1  joerg   }
     78      1.1  joerg };
     79      1.1  joerg 
     80      1.1  joerg } // end namespace yaml
     81      1.1  joerg } // end namespace llvm
     82      1.1  joerg 
     83      1.1  joerg namespace clang {
     84      1.1  joerg namespace tooling {
     85      1.1  joerg namespace {
     86      1.1  joerg 
     87      1.1  joerg // Returns true if there is any line that violates \p ColumnLimit in range
     88      1.1  joerg // [Start, End].
     89      1.1  joerg bool violatesColumnLimit(llvm::StringRef Code, unsigned ColumnLimit,
     90      1.1  joerg                          unsigned Start, unsigned End) {
     91      1.1  joerg   auto StartPos = Code.rfind('\n', Start);
     92      1.1  joerg   StartPos = (StartPos == llvm::StringRef::npos) ? 0 : StartPos + 1;
     93      1.1  joerg 
     94      1.1  joerg   auto EndPos = Code.find("\n", End);
     95      1.1  joerg   if (EndPos == llvm::StringRef::npos)
     96      1.1  joerg     EndPos = Code.size();
     97      1.1  joerg 
     98      1.1  joerg   llvm::SmallVector<llvm::StringRef, 8> Lines;
     99      1.1  joerg   Code.substr(StartPos, EndPos - StartPos).split(Lines, '\n');
    100      1.1  joerg   for (llvm::StringRef Line : Lines)
    101      1.1  joerg     if (Line.size() > ColumnLimit)
    102      1.1  joerg       return true;
    103      1.1  joerg   return false;
    104      1.1  joerg }
    105      1.1  joerg 
    106      1.1  joerg std::vector<Range>
    107      1.1  joerg getRangesForFormating(llvm::StringRef Code, unsigned ColumnLimit,
    108      1.1  joerg                       ApplyChangesSpec::FormatOption Format,
    109      1.1  joerg                       const clang::tooling::Replacements &Replaces) {
    110      1.1  joerg   // kNone suppresses formatting entirely.
    111      1.1  joerg   if (Format == ApplyChangesSpec::kNone)
    112      1.1  joerg     return {};
    113      1.1  joerg   std::vector<clang::tooling::Range> Ranges;
    114      1.1  joerg   // This works assuming that replacements are ordered by offset.
    115      1.1  joerg   // FIXME: use `getAffectedRanges()` to calculate when it does not include '\n'
    116      1.1  joerg   // at the end of an insertion in affected ranges.
    117      1.1  joerg   int Offset = 0;
    118      1.1  joerg   for (const clang::tooling::Replacement &R : Replaces) {
    119      1.1  joerg     int Start = R.getOffset() + Offset;
    120      1.1  joerg     int End = Start + R.getReplacementText().size();
    121      1.1  joerg     if (!R.getReplacementText().empty() &&
    122      1.1  joerg         R.getReplacementText().back() == '\n' && R.getLength() == 0 &&
    123      1.1  joerg         R.getOffset() > 0 && R.getOffset() <= Code.size() &&
    124      1.1  joerg         Code[R.getOffset() - 1] == '\n')
    125      1.1  joerg       // If we are inserting at the start of a line and the replacement ends in
    126      1.1  joerg       // a newline, we don't need to format the subsequent line.
    127      1.1  joerg       --End;
    128      1.1  joerg     Offset += R.getReplacementText().size() - R.getLength();
    129      1.1  joerg 
    130      1.1  joerg     if (Format == ApplyChangesSpec::kAll ||
    131      1.1  joerg         violatesColumnLimit(Code, ColumnLimit, Start, End))
    132      1.1  joerg       Ranges.emplace_back(Start, End - Start);
    133      1.1  joerg   }
    134      1.1  joerg   return Ranges;
    135      1.1  joerg }
    136      1.1  joerg 
    137      1.1  joerg inline llvm::Error make_string_error(const llvm::Twine &Message) {
    138      1.1  joerg   return llvm::make_error<llvm::StringError>(Message,
    139      1.1  joerg                                              llvm::inconvertibleErrorCode());
    140      1.1  joerg }
    141      1.1  joerg 
    142      1.1  joerg // Creates replacements for inserting/deleting #include headers.
    143      1.1  joerg llvm::Expected<Replacements>
    144      1.1  joerg createReplacementsForHeaders(llvm::StringRef FilePath, llvm::StringRef Code,
    145      1.1  joerg                              llvm::ArrayRef<AtomicChange> Changes,
    146      1.1  joerg                              const format::FormatStyle &Style) {
    147      1.1  joerg   // Create header insertion/deletion replacements to be cleaned up
    148      1.1  joerg   // (i.e. converted to real insertion/deletion replacements).
    149      1.1  joerg   Replacements HeaderReplacements;
    150      1.1  joerg   for (const auto &Change : Changes) {
    151      1.1  joerg     for (llvm::StringRef Header : Change.getInsertedHeaders()) {
    152      1.1  joerg       std::string EscapedHeader =
    153      1.1  joerg           Header.startswith("<") || Header.startswith("\"")
    154      1.1  joerg               ? Header.str()
    155      1.1  joerg               : ("\"" + Header + "\"").str();
    156      1.1  joerg       std::string ReplacementText = "#include " + EscapedHeader;
    157      1.1  joerg       // Offset UINT_MAX and length 0 indicate that the replacement is a header
    158      1.1  joerg       // insertion.
    159      1.1  joerg       llvm::Error Err = HeaderReplacements.add(
    160      1.1  joerg           tooling::Replacement(FilePath, UINT_MAX, 0, ReplacementText));
    161      1.1  joerg       if (Err)
    162      1.1  joerg         return std::move(Err);
    163      1.1  joerg     }
    164      1.1  joerg     for (const std::string &Header : Change.getRemovedHeaders()) {
    165      1.1  joerg       // Offset UINT_MAX and length 1 indicate that the replacement is a header
    166      1.1  joerg       // deletion.
    167      1.1  joerg       llvm::Error Err =
    168      1.1  joerg           HeaderReplacements.add(Replacement(FilePath, UINT_MAX, 1, Header));
    169      1.1  joerg       if (Err)
    170      1.1  joerg         return std::move(Err);
    171      1.1  joerg     }
    172      1.1  joerg   }
    173      1.1  joerg 
    174      1.1  joerg   // cleanupAroundReplacements() converts header insertions/deletions into
    175      1.1  joerg   // actual replacements that add/remove headers at the right location.
    176      1.1  joerg   return clang::format::cleanupAroundReplacements(Code, HeaderReplacements,
    177      1.1  joerg                                                   Style);
    178      1.1  joerg }
    179      1.1  joerg 
    180      1.1  joerg // Combine replacements in all Changes as a `Replacements`. This ignores the
    181      1.1  joerg // file path in all replacements and replaces them with \p FilePath.
    182      1.1  joerg llvm::Expected<Replacements>
    183      1.1  joerg combineReplacementsInChanges(llvm::StringRef FilePath,
    184      1.1  joerg                              llvm::ArrayRef<AtomicChange> Changes) {
    185      1.1  joerg   Replacements Replaces;
    186      1.1  joerg   for (const auto &Change : Changes)
    187      1.1  joerg     for (const auto &R : Change.getReplacements())
    188      1.1  joerg       if (auto Err = Replaces.add(Replacement(
    189      1.1  joerg               FilePath, R.getOffset(), R.getLength(), R.getReplacementText())))
    190      1.1  joerg         return std::move(Err);
    191      1.1  joerg   return Replaces;
    192      1.1  joerg }
    193      1.1  joerg 
    194      1.1  joerg } // end namespace
    195      1.1  joerg 
    196      1.1  joerg AtomicChange::AtomicChange(const SourceManager &SM,
    197      1.1  joerg                            SourceLocation KeyPosition) {
    198      1.1  joerg   const FullSourceLoc FullKeyPosition(KeyPosition, SM);
    199      1.1  joerg   std::pair<FileID, unsigned> FileIDAndOffset =
    200      1.1  joerg       FullKeyPosition.getSpellingLoc().getDecomposedLoc();
    201      1.1  joerg   const FileEntry *FE = SM.getFileEntryForID(FileIDAndOffset.first);
    202      1.1  joerg   assert(FE && "Cannot create AtomicChange with invalid location.");
    203  1.1.1.2  joerg   FilePath = std::string(FE->getName());
    204      1.1  joerg   Key = FilePath + ":" + std::to_string(FileIDAndOffset.second);
    205      1.1  joerg }
    206      1.1  joerg 
    207  1.1.1.2  joerg AtomicChange::AtomicChange(const SourceManager &SM, SourceLocation KeyPosition,
    208  1.1.1.2  joerg                            llvm::Any M)
    209  1.1.1.2  joerg     : AtomicChange(SM, KeyPosition) {
    210  1.1.1.2  joerg   Metadata = std::move(M);
    211  1.1.1.2  joerg }
    212  1.1.1.2  joerg 
    213      1.1  joerg AtomicChange::AtomicChange(std::string Key, std::string FilePath,
    214      1.1  joerg                            std::string Error,
    215      1.1  joerg                            std::vector<std::string> InsertedHeaders,
    216      1.1  joerg                            std::vector<std::string> RemovedHeaders,
    217      1.1  joerg                            clang::tooling::Replacements Replaces)
    218      1.1  joerg     : Key(std::move(Key)), FilePath(std::move(FilePath)),
    219      1.1  joerg       Error(std::move(Error)), InsertedHeaders(std::move(InsertedHeaders)),
    220      1.1  joerg       RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) {
    221      1.1  joerg }
    222      1.1  joerg 
    223      1.1  joerg bool AtomicChange::operator==(const AtomicChange &Other) const {
    224      1.1  joerg   if (Key != Other.Key || FilePath != Other.FilePath || Error != Other.Error)
    225      1.1  joerg     return false;
    226      1.1  joerg   if (!(Replaces == Other.Replaces))
    227      1.1  joerg     return false;
    228      1.1  joerg   // FXIME: Compare header insertions/removals.
    229      1.1  joerg   return true;
    230      1.1  joerg }
    231      1.1  joerg 
    232      1.1  joerg std::string AtomicChange::toYAMLString() {
    233      1.1  joerg   std::string YamlContent;
    234      1.1  joerg   llvm::raw_string_ostream YamlContentStream(YamlContent);
    235      1.1  joerg 
    236      1.1  joerg   llvm::yaml::Output YAML(YamlContentStream);
    237      1.1  joerg   YAML << *this;
    238      1.1  joerg   YamlContentStream.flush();
    239      1.1  joerg   return YamlContent;
    240      1.1  joerg }
    241      1.1  joerg 
    242      1.1  joerg AtomicChange AtomicChange::convertFromYAML(llvm::StringRef YAMLContent) {
    243      1.1  joerg   NormalizedAtomicChange NE;
    244      1.1  joerg   llvm::yaml::Input YAML(YAMLContent);
    245      1.1  joerg   YAML >> NE;
    246      1.1  joerg   AtomicChange E(NE.Key, NE.FilePath, NE.Error, NE.InsertedHeaders,
    247      1.1  joerg                  NE.RemovedHeaders, tooling::Replacements());
    248      1.1  joerg   for (const auto &R : NE.Replaces) {
    249      1.1  joerg     llvm::Error Err = E.Replaces.add(R);
    250      1.1  joerg     if (Err)
    251      1.1  joerg       llvm_unreachable(
    252      1.1  joerg           "Failed to add replacement when Converting YAML to AtomicChange.");
    253      1.1  joerg     llvm::consumeError(std::move(Err));
    254      1.1  joerg   }
    255      1.1  joerg   return E;
    256      1.1  joerg }
    257      1.1  joerg 
    258      1.1  joerg llvm::Error AtomicChange::replace(const SourceManager &SM,
    259      1.1  joerg                                   const CharSourceRange &Range,
    260      1.1  joerg                                   llvm::StringRef ReplacementText) {
    261      1.1  joerg   return Replaces.add(Replacement(SM, Range, ReplacementText));
    262      1.1  joerg }
    263      1.1  joerg 
    264      1.1  joerg llvm::Error AtomicChange::replace(const SourceManager &SM, SourceLocation Loc,
    265      1.1  joerg                                   unsigned Length, llvm::StringRef Text) {
    266      1.1  joerg   return Replaces.add(Replacement(SM, Loc, Length, Text));
    267      1.1  joerg }
    268      1.1  joerg 
    269      1.1  joerg llvm::Error AtomicChange::insert(const SourceManager &SM, SourceLocation Loc,
    270      1.1  joerg                                  llvm::StringRef Text, bool InsertAfter) {
    271      1.1  joerg   if (Text.empty())
    272      1.1  joerg     return llvm::Error::success();
    273      1.1  joerg   Replacement R(SM, Loc, 0, Text);
    274      1.1  joerg   llvm::Error Err = Replaces.add(R);
    275      1.1  joerg   if (Err) {
    276      1.1  joerg     return llvm::handleErrors(
    277      1.1  joerg         std::move(Err), [&](const ReplacementError &RE) -> llvm::Error {
    278      1.1  joerg           if (RE.get() != replacement_error::insert_conflict)
    279      1.1  joerg             return llvm::make_error<ReplacementError>(RE);
    280      1.1  joerg           unsigned NewOffset = Replaces.getShiftedCodePosition(R.getOffset());
    281      1.1  joerg           if (!InsertAfter)
    282      1.1  joerg             NewOffset -=
    283      1.1  joerg                 RE.getExistingReplacement()->getReplacementText().size();
    284      1.1  joerg           Replacement NewR(R.getFilePath(), NewOffset, 0, Text);
    285      1.1  joerg           Replaces = Replaces.merge(Replacements(NewR));
    286      1.1  joerg           return llvm::Error::success();
    287      1.1  joerg         });
    288      1.1  joerg   }
    289      1.1  joerg   return llvm::Error::success();
    290      1.1  joerg }
    291      1.1  joerg 
    292      1.1  joerg void AtomicChange::addHeader(llvm::StringRef Header) {
    293  1.1.1.2  joerg   InsertedHeaders.push_back(std::string(Header));
    294      1.1  joerg }
    295      1.1  joerg 
    296      1.1  joerg void AtomicChange::removeHeader(llvm::StringRef Header) {
    297  1.1.1.2  joerg   RemovedHeaders.push_back(std::string(Header));
    298      1.1  joerg }
    299      1.1  joerg 
    300      1.1  joerg llvm::Expected<std::string>
    301      1.1  joerg applyAtomicChanges(llvm::StringRef FilePath, llvm::StringRef Code,
    302      1.1  joerg                    llvm::ArrayRef<AtomicChange> Changes,
    303      1.1  joerg                    const ApplyChangesSpec &Spec) {
    304      1.1  joerg   llvm::Expected<Replacements> HeaderReplacements =
    305      1.1  joerg       createReplacementsForHeaders(FilePath, Code, Changes, Spec.Style);
    306      1.1  joerg   if (!HeaderReplacements)
    307      1.1  joerg     return make_string_error(
    308      1.1  joerg         "Failed to create replacements for header changes: " +
    309      1.1  joerg         llvm::toString(HeaderReplacements.takeError()));
    310      1.1  joerg 
    311      1.1  joerg   llvm::Expected<Replacements> Replaces =
    312      1.1  joerg       combineReplacementsInChanges(FilePath, Changes);
    313      1.1  joerg   if (!Replaces)
    314      1.1  joerg     return make_string_error("Failed to combine replacements in all changes: " +
    315      1.1  joerg                              llvm::toString(Replaces.takeError()));
    316      1.1  joerg 
    317      1.1  joerg   Replacements AllReplaces = std::move(*Replaces);
    318      1.1  joerg   for (const auto &R : *HeaderReplacements) {
    319      1.1  joerg     llvm::Error Err = AllReplaces.add(R);
    320      1.1  joerg     if (Err)
    321      1.1  joerg       return make_string_error(
    322      1.1  joerg           "Failed to combine existing replacements with header replacements: " +
    323      1.1  joerg           llvm::toString(std::move(Err)));
    324      1.1  joerg   }
    325      1.1  joerg 
    326      1.1  joerg   if (Spec.Cleanup) {
    327      1.1  joerg     llvm::Expected<Replacements> CleanReplaces =
    328      1.1  joerg         format::cleanupAroundReplacements(Code, AllReplaces, Spec.Style);
    329      1.1  joerg     if (!CleanReplaces)
    330      1.1  joerg       return make_string_error("Failed to cleanup around replacements: " +
    331      1.1  joerg                                llvm::toString(CleanReplaces.takeError()));
    332      1.1  joerg     AllReplaces = std::move(*CleanReplaces);
    333      1.1  joerg   }
    334      1.1  joerg 
    335      1.1  joerg   // Apply all replacements.
    336      1.1  joerg   llvm::Expected<std::string> ChangedCode =
    337      1.1  joerg       applyAllReplacements(Code, AllReplaces);
    338      1.1  joerg   if (!ChangedCode)
    339      1.1  joerg     return make_string_error("Failed to apply all replacements: " +
    340      1.1  joerg                              llvm::toString(ChangedCode.takeError()));
    341      1.1  joerg 
    342      1.1  joerg   // Sort inserted headers. This is done even if other formatting is turned off
    343      1.1  joerg   // as incorrectly sorted headers are always just wrong, it's not a matter of
    344      1.1  joerg   // taste.
    345      1.1  joerg   Replacements HeaderSortingReplacements = format::sortIncludes(
    346      1.1  joerg       Spec.Style, *ChangedCode, AllReplaces.getAffectedRanges(), FilePath);
    347      1.1  joerg   ChangedCode = applyAllReplacements(*ChangedCode, HeaderSortingReplacements);
    348      1.1  joerg   if (!ChangedCode)
    349      1.1  joerg     return make_string_error(
    350      1.1  joerg         "Failed to apply replacements for sorting includes: " +
    351      1.1  joerg         llvm::toString(ChangedCode.takeError()));
    352      1.1  joerg 
    353      1.1  joerg   AllReplaces = AllReplaces.merge(HeaderSortingReplacements);
    354      1.1  joerg 
    355      1.1  joerg   std::vector<Range> FormatRanges = getRangesForFormating(
    356      1.1  joerg       *ChangedCode, Spec.Style.ColumnLimit, Spec.Format, AllReplaces);
    357      1.1  joerg   if (!FormatRanges.empty()) {
    358      1.1  joerg     Replacements FormatReplacements =
    359      1.1  joerg         format::reformat(Spec.Style, *ChangedCode, FormatRanges, FilePath);
    360      1.1  joerg     ChangedCode = applyAllReplacements(*ChangedCode, FormatReplacements);
    361      1.1  joerg     if (!ChangedCode)
    362      1.1  joerg       return make_string_error(
    363      1.1  joerg           "Failed to apply replacements for formatting changed code: " +
    364      1.1  joerg           llvm::toString(ChangedCode.takeError()));
    365      1.1  joerg   }
    366      1.1  joerg   return ChangedCode;
    367      1.1  joerg }
    368      1.1  joerg 
    369      1.1  joerg } // end namespace tooling
    370      1.1  joerg } // end namespace clang
    371