AtomicChange.cpp 14 KB

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