AtomicChange.cpp 14 KB

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