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. AtomicChange(llvm::StringRef FilePath,llvm::StringRef 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. getKey()68 const std::string &getKey() const { return Key; } 69 70 /// Returns the path of the file containing this atomic change. getFilePath()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. setError(llvm::StringRef Error)77 void setError(llvm::StringRef Error) { this->Error = std::string(Error); } 78 79 /// Returns whether an error has been set on this list. hasError()80 bool hasError() const { return !Error.empty(); } 81 82 /// Returns the error message or an empty string if it does not exist. getError()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. getReplacements()117 const Replacements &getReplacements() const { return Replaces; } 118 getInsertedHeaders()119 llvm::ArrayRef<std::string> getInsertedHeaders() const { 120 return InsertedHeaders; 121 } 122 getRemovedHeaders()123 llvm::ArrayRef<std::string> getRemovedHeaders() const { 124 return RemovedHeaders; 125 } 126 getMetadata()127 const llvm::Any &getMetadata() const { return Metadata; } 128 129 private: AtomicChange()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