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