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