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 = FE->getName(); 204 Key = FilePath + ":" + std::to_string(FileIDAndOffset.second); 205 } 206 207 AtomicChange::AtomicChange(std::string Key, std::string FilePath, 208 std::string Error, 209 std::vector<std::string> InsertedHeaders, 210 std::vector<std::string> RemovedHeaders, 211 clang::tooling::Replacements Replaces) 212 : Key(std::move(Key)), FilePath(std::move(FilePath)), 213 Error(std::move(Error)), InsertedHeaders(std::move(InsertedHeaders)), 214 RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) { 215 } 216 217 bool AtomicChange::operator==(const AtomicChange &Other) const { 218 if (Key != Other.Key || FilePath != Other.FilePath || Error != Other.Error) 219 return false; 220 if (!(Replaces == Other.Replaces)) 221 return false; 222 // FXIME: Compare header insertions/removals. 223 return true; 224 } 225 226 std::string AtomicChange::toYAMLString() { 227 std::string YamlContent; 228 llvm::raw_string_ostream YamlContentStream(YamlContent); 229 230 llvm::yaml::Output YAML(YamlContentStream); 231 YAML << *this; 232 YamlContentStream.flush(); 233 return YamlContent; 234 } 235 236 AtomicChange AtomicChange::convertFromYAML(llvm::StringRef YAMLContent) { 237 NormalizedAtomicChange NE; 238 llvm::yaml::Input YAML(YAMLContent); 239 YAML >> NE; 240 AtomicChange E(NE.Key, NE.FilePath, NE.Error, NE.InsertedHeaders, 241 NE.RemovedHeaders, tooling::Replacements()); 242 for (const auto &R : NE.Replaces) { 243 llvm::Error Err = E.Replaces.add(R); 244 if (Err) 245 llvm_unreachable( 246 "Failed to add replacement when Converting YAML to AtomicChange."); 247 llvm::consumeError(std::move(Err)); 248 } 249 return E; 250 } 251 252 llvm::Error AtomicChange::replace(const SourceManager &SM, 253 const CharSourceRange &Range, 254 llvm::StringRef ReplacementText) { 255 return Replaces.add(Replacement(SM, Range, ReplacementText)); 256 } 257 258 llvm::Error AtomicChange::replace(const SourceManager &SM, SourceLocation Loc, 259 unsigned Length, llvm::StringRef Text) { 260 return Replaces.add(Replacement(SM, Loc, Length, Text)); 261 } 262 263 llvm::Error AtomicChange::insert(const SourceManager &SM, SourceLocation Loc, 264 llvm::StringRef Text, bool InsertAfter) { 265 if (Text.empty()) 266 return llvm::Error::success(); 267 Replacement R(SM, Loc, 0, Text); 268 llvm::Error Err = Replaces.add(R); 269 if (Err) { 270 return llvm::handleErrors( 271 std::move(Err), [&](const ReplacementError &RE) -> llvm::Error { 272 if (RE.get() != replacement_error::insert_conflict) 273 return llvm::make_error<ReplacementError>(RE); 274 unsigned NewOffset = Replaces.getShiftedCodePosition(R.getOffset()); 275 if (!InsertAfter) 276 NewOffset -= 277 RE.getExistingReplacement()->getReplacementText().size(); 278 Replacement NewR(R.getFilePath(), NewOffset, 0, Text); 279 Replaces = Replaces.merge(Replacements(NewR)); 280 return llvm::Error::success(); 281 }); 282 } 283 return llvm::Error::success(); 284 } 285 286 void AtomicChange::addHeader(llvm::StringRef Header) { 287 InsertedHeaders.push_back(Header); 288 } 289 290 void AtomicChange::removeHeader(llvm::StringRef Header) { 291 RemovedHeaders.push_back(Header); 292 } 293 294 llvm::Expected<std::string> 295 applyAtomicChanges(llvm::StringRef FilePath, llvm::StringRef Code, 296 llvm::ArrayRef<AtomicChange> Changes, 297 const ApplyChangesSpec &Spec) { 298 llvm::Expected<Replacements> HeaderReplacements = 299 createReplacementsForHeaders(FilePath, Code, Changes, Spec.Style); 300 if (!HeaderReplacements) 301 return make_string_error( 302 "Failed to create replacements for header changes: " + 303 llvm::toString(HeaderReplacements.takeError())); 304 305 llvm::Expected<Replacements> Replaces = 306 combineReplacementsInChanges(FilePath, Changes); 307 if (!Replaces) 308 return make_string_error("Failed to combine replacements in all changes: " + 309 llvm::toString(Replaces.takeError())); 310 311 Replacements AllReplaces = std::move(*Replaces); 312 for (const auto &R : *HeaderReplacements) { 313 llvm::Error Err = AllReplaces.add(R); 314 if (Err) 315 return make_string_error( 316 "Failed to combine existing replacements with header replacements: " + 317 llvm::toString(std::move(Err))); 318 } 319 320 if (Spec.Cleanup) { 321 llvm::Expected<Replacements> CleanReplaces = 322 format::cleanupAroundReplacements(Code, AllReplaces, Spec.Style); 323 if (!CleanReplaces) 324 return make_string_error("Failed to cleanup around replacements: " + 325 llvm::toString(CleanReplaces.takeError())); 326 AllReplaces = std::move(*CleanReplaces); 327 } 328 329 // Apply all replacements. 330 llvm::Expected<std::string> ChangedCode = 331 applyAllReplacements(Code, AllReplaces); 332 if (!ChangedCode) 333 return make_string_error("Failed to apply all replacements: " + 334 llvm::toString(ChangedCode.takeError())); 335 336 // Sort inserted headers. This is done even if other formatting is turned off 337 // as incorrectly sorted headers are always just wrong, it's not a matter of 338 // taste. 339 Replacements HeaderSortingReplacements = format::sortIncludes( 340 Spec.Style, *ChangedCode, AllReplaces.getAffectedRanges(), FilePath); 341 ChangedCode = applyAllReplacements(*ChangedCode, HeaderSortingReplacements); 342 if (!ChangedCode) 343 return make_string_error( 344 "Failed to apply replacements for sorting includes: " + 345 llvm::toString(ChangedCode.takeError())); 346 347 AllReplaces = AllReplaces.merge(HeaderSortingReplacements); 348 349 std::vector<Range> FormatRanges = getRangesForFormating( 350 *ChangedCode, Spec.Style.ColumnLimit, Spec.Format, AllReplaces); 351 if (!FormatRanges.empty()) { 352 Replacements FormatReplacements = 353 format::reformat(Spec.Style, *ChangedCode, FormatRanges, FilePath); 354 ChangedCode = applyAllReplacements(*ChangedCode, FormatReplacements); 355 if (!ChangedCode) 356 return make_string_error( 357 "Failed to apply replacements for formatting changed code: " + 358 llvm::toString(ChangedCode.takeError())); 359 } 360 return ChangedCode; 361 } 362 363 } // end namespace tooling 364 } // end namespace clang 365