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