Home | History | Annotate | Line # | Download | only in Refactoring
      1 //===--- AtomicChange.h - AtomicChange class --------------------*- C++ -*-===//
      2 //
      3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
      4 // See https://llvm.org/LICENSE.txt for license information.
      5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
      6 //
      7 //===----------------------------------------------------------------------===//
      8 //
      9 //  This file defines AtomicChange which is used to create a set of source
     10 //  changes, e.g. replacements and header insertions.
     11 //
     12 //===----------------------------------------------------------------------===//
     13 
     14 #ifndef LLVM_CLANG_TOOLING_REFACTOR_ATOMICCHANGE_H
     15 #define LLVM_CLANG_TOOLING_REFACTOR_ATOMICCHANGE_H
     16 
     17 #include "clang/Basic/SourceManager.h"
     18 #include "clang/Format/Format.h"
     19 #include "clang/Tooling/Core/Replacement.h"
     20 #include "llvm/ADT/Any.h"
     21 #include "llvm/ADT/StringRef.h"
     22 #include "llvm/Support/Error.h"
     23 
     24 namespace clang {
     25 namespace tooling {
     26 
     27 /// An atomic change is used to create and group a set of source edits,
     28 /// e.g. replacements or header insertions. Edits in an AtomicChange should be
     29 /// related, e.g. replacements for the same type reference and the corresponding
     30 /// header insertion/deletion.
     31 ///
     32 /// An AtomicChange is uniquely identified by a key and will either be fully
     33 /// applied or not applied at all.
     34 ///
     35 /// Calling setError on an AtomicChange stores the error message and marks it as
     36 /// bad, i.e. none of its source edits will be applied.
     37 class AtomicChange {
     38 public:
     39   /// Creates an atomic change around \p KeyPosition with the key being a
     40   /// concatenation of the file name and the offset of \p KeyPosition.
     41   /// \p KeyPosition should be the location of the key syntactical element that
     42   /// is being changed, e.g. the call to a refactored method.
     43   AtomicChange(const SourceManager &SM, SourceLocation KeyPosition);
     44 
     45   AtomicChange(const SourceManager &SM, SourceLocation KeyPosition,
     46                llvm::Any Metadata);
     47 
     48   /// Creates an atomic change for \p FilePath with a customized key.
     49   AtomicChange(llvm::StringRef FilePath, llvm::StringRef Key)
     50       : Key(Key), FilePath(FilePath) {}
     51 
     52   AtomicChange(AtomicChange &&) = default;
     53   AtomicChange(const AtomicChange &) = default;
     54 
     55   AtomicChange &operator=(AtomicChange &&) = default;
     56   AtomicChange &operator=(const AtomicChange &) = default;
     57 
     58   bool operator==(const AtomicChange &Other) const;
     59 
     60   /// Returns the atomic change as a YAML string.
     61   std::string toYAMLString();
     62 
     63   /// Converts a YAML-encoded automic change to AtomicChange.
     64   static AtomicChange convertFromYAML(llvm::StringRef YAMLContent);
     65 
     66   /// Returns the key of this change, which is a concatenation of the
     67   /// file name and offset of the key position.
     68   const std::string &getKey() const { return Key; }
     69 
     70   /// Returns the path of the file containing this atomic change.
     71   const std::string &getFilePath() const { return FilePath; }
     72 
     73   /// If this change could not be created successfully, e.g. because of
     74   /// conflicts among replacements, use this to set an error description.
     75   /// Thereby, places that cannot be fixed automatically can be gathered when
     76   /// applying changes.
     77   void setError(llvm::StringRef Error) { this->Error = std::string(Error); }
     78 
     79   /// Returns whether an error has been set on this list.
     80   bool hasError() const { return !Error.empty(); }
     81 
     82   /// Returns the error message or an empty string if it does not exist.
     83   const std::string &getError() const { return Error; }
     84 
     85   /// Adds a replacement that replaces the given Range with
     86   /// ReplacementText.
     87   /// \returns An llvm::Error carrying ReplacementError on error.
     88   llvm::Error replace(const SourceManager &SM, const CharSourceRange &Range,
     89                       llvm::StringRef ReplacementText);
     90 
     91   /// Adds a replacement that replaces range [Loc, Loc+Length) with
     92   /// \p Text.
     93   /// \returns An llvm::Error carrying ReplacementError on error.
     94   llvm::Error replace(const SourceManager &SM, SourceLocation Loc,
     95                       unsigned Length, llvm::StringRef Text);
     96 
     97   /// Adds a replacement that inserts \p Text at \p Loc. If this
     98   /// insertion conflicts with an existing insertion (at the same position),
     99   /// this will be inserted before/after the existing insertion depending on
    100   /// \p InsertAfter. Users should use `replace` with `Length=0` instead if they
    101   /// do not want conflict resolving by default. If the conflicting replacement
    102   /// is not an insertion, an error is returned.
    103   ///
    104   /// \returns An llvm::Error carrying ReplacementError on error.
    105   llvm::Error insert(const SourceManager &SM, SourceLocation Loc,
    106                      llvm::StringRef Text, bool InsertAfter = true);
    107 
    108   /// Adds a header into the file that contains the key position.
    109   /// Header can be in angle brackets or double quotation marks. By default
    110   /// (header is not quoted), header will be surrounded with double quotes.
    111   void addHeader(llvm::StringRef Header);
    112 
    113   /// Removes a header from the file that contains the key position.
    114   void removeHeader(llvm::StringRef Header);
    115 
    116   /// Returns a const reference to existing replacements.
    117   const Replacements &getReplacements() const { return Replaces; }
    118 
    119   llvm::ArrayRef<std::string> getInsertedHeaders() const {
    120     return InsertedHeaders;
    121   }
    122 
    123   llvm::ArrayRef<std::string> getRemovedHeaders() const {
    124     return RemovedHeaders;
    125   }
    126 
    127   const llvm::Any &getMetadata() const { return Metadata; }
    128 
    129 private:
    130   AtomicChange() {}
    131 
    132   AtomicChange(std::string Key, std::string FilePath, std::string Error,
    133                std::vector<std::string> InsertedHeaders,
    134                std::vector<std::string> RemovedHeaders,
    135                clang::tooling::Replacements Replaces);
    136 
    137   // This uniquely identifies an AtomicChange.
    138   std::string Key;
    139   std::string FilePath;
    140   std::string Error;
    141   std::vector<std::string> InsertedHeaders;
    142   std::vector<std::string> RemovedHeaders;
    143   tooling::Replacements Replaces;
    144 
    145   // This field stores metadata which is ignored for the purposes of applying
    146   // edits to source, but may be useful for other consumers of AtomicChanges. In
    147   // particular, consumers can use this to direct how they want to consume each
    148   // edit.
    149   llvm::Any Metadata;
    150 };
    151 
    152 using AtomicChanges = std::vector<AtomicChange>;
    153 
    154 // Defines specs for applying changes.
    155 struct ApplyChangesSpec {
    156   // If true, cleans up redundant/erroneous code around changed code with
    157   // clang-format's cleanup functionality, e.g. redundant commas around deleted
    158   // parameter or empty namespaces introduced by deletions.
    159   bool Cleanup = true;
    160 
    161   format::FormatStyle Style = format::getNoStyle();
    162 
    163   // Options for selectively formatting changes with clang-format:
    164   // kAll: Format all changed lines.
    165   // kNone: Don't format anything.
    166   // kViolations: Format lines exceeding the `ColumnLimit` in `Style`.
    167   enum FormatOption { kAll, kNone, kViolations };
    168 
    169   FormatOption Format = kNone;
    170 };
    171 
    172 /// Applies all AtomicChanges in \p Changes to the \p Code.
    173 ///
    174 /// This completely ignores the file path in each change and replaces them with
    175 /// \p FilePath, i.e. callers are responsible for ensuring all changes are for
    176 /// the same file.
    177 ///
    178 /// \returns The changed code if all changes are applied successfully;
    179 /// otherwise, an llvm::Error carrying llvm::StringError is returned (the Error
    180 /// message can be converted to string with `llvm::toString()` and the
    181 /// error_code should be ignored).
    182 llvm::Expected<std::string>
    183 applyAtomicChanges(llvm::StringRef FilePath, llvm::StringRef Code,
    184                    llvm::ArrayRef<AtomicChange> Changes,
    185                    const ApplyChangesSpec &Spec);
    186 
    187 } // end namespace tooling
    188 } // end namespace clang
    189 
    190 #endif // LLVM_CLANG_TOOLING_REFACTOR_ATOMICCHANGE_H
    191