Commit.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. //===- Commit.cpp - A unit of edits ---------------------------------------===//
  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/Edit/Commit.h"
  9. #include "clang/Basic/LLVM.h"
  10. #include "clang/Basic/SourceLocation.h"
  11. #include "clang/Basic/SourceManager.h"
  12. #include "clang/Edit/EditedSource.h"
  13. #include "clang/Edit/FileOffset.h"
  14. #include "clang/Lex/Lexer.h"
  15. #include "clang/Lex/PPConditionalDirectiveRecord.h"
  16. #include "llvm/ADT/StringRef.h"
  17. #include <cassert>
  18. #include <utility>
  19. using namespace clang;
  20. using namespace edit;
  21. SourceLocation Commit::Edit::getFileLocation(SourceManager &SM) const {
  22. SourceLocation Loc = SM.getLocForStartOfFile(Offset.getFID());
  23. Loc = Loc.getLocWithOffset(Offset.getOffset());
  24. assert(Loc.isFileID());
  25. return Loc;
  26. }
  27. CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const {
  28. SourceLocation Loc = getFileLocation(SM);
  29. return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
  30. }
  31. CharSourceRange Commit::Edit::getInsertFromRange(SourceManager &SM) const {
  32. SourceLocation Loc = SM.getLocForStartOfFile(InsertFromRangeOffs.getFID());
  33. Loc = Loc.getLocWithOffset(InsertFromRangeOffs.getOffset());
  34. assert(Loc.isFileID());
  35. return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
  36. }
  37. Commit::Commit(EditedSource &Editor)
  38. : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()),
  39. PPRec(Editor.getPPCondDirectiveRecord()),
  40. Editor(&Editor) {}
  41. bool Commit::insert(SourceLocation loc, StringRef text,
  42. bool afterToken, bool beforePreviousInsertions) {
  43. if (text.empty())
  44. return true;
  45. FileOffset Offs;
  46. if ((!afterToken && !canInsert(loc, Offs)) ||
  47. ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
  48. IsCommitable = false;
  49. return false;
  50. }
  51. addInsert(loc, Offs, text, beforePreviousInsertions);
  52. return true;
  53. }
  54. bool Commit::insertFromRange(SourceLocation loc,
  55. CharSourceRange range,
  56. bool afterToken, bool beforePreviousInsertions) {
  57. FileOffset RangeOffs;
  58. unsigned RangeLen;
  59. if (!canRemoveRange(range, RangeOffs, RangeLen)) {
  60. IsCommitable = false;
  61. return false;
  62. }
  63. FileOffset Offs;
  64. if ((!afterToken && !canInsert(loc, Offs)) ||
  65. ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
  66. IsCommitable = false;
  67. return false;
  68. }
  69. if (PPRec &&
  70. PPRec->areInDifferentConditionalDirectiveRegion(loc, range.getBegin())) {
  71. IsCommitable = false;
  72. return false;
  73. }
  74. addInsertFromRange(loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions);
  75. return true;
  76. }
  77. bool Commit::remove(CharSourceRange range) {
  78. FileOffset Offs;
  79. unsigned Len;
  80. if (!canRemoveRange(range, Offs, Len)) {
  81. IsCommitable = false;
  82. return false;
  83. }
  84. addRemove(range.getBegin(), Offs, Len);
  85. return true;
  86. }
  87. bool Commit::insertWrap(StringRef before, CharSourceRange range,
  88. StringRef after) {
  89. bool commitableBefore = insert(range.getBegin(), before, /*afterToken=*/false,
  90. /*beforePreviousInsertions=*/true);
  91. bool commitableAfter;
  92. if (range.isTokenRange())
  93. commitableAfter = insertAfterToken(range.getEnd(), after);
  94. else
  95. commitableAfter = insert(range.getEnd(), after);
  96. return commitableBefore && commitableAfter;
  97. }
  98. bool Commit::replace(CharSourceRange range, StringRef text) {
  99. if (text.empty())
  100. return remove(range);
  101. FileOffset Offs;
  102. unsigned Len;
  103. if (!canInsert(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) {
  104. IsCommitable = false;
  105. return false;
  106. }
  107. addRemove(range.getBegin(), Offs, Len);
  108. addInsert(range.getBegin(), Offs, text, false);
  109. return true;
  110. }
  111. bool Commit::replaceWithInner(CharSourceRange range,
  112. CharSourceRange replacementRange) {
  113. FileOffset OuterBegin;
  114. unsigned OuterLen;
  115. if (!canRemoveRange(range, OuterBegin, OuterLen)) {
  116. IsCommitable = false;
  117. return false;
  118. }
  119. FileOffset InnerBegin;
  120. unsigned InnerLen;
  121. if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) {
  122. IsCommitable = false;
  123. return false;
  124. }
  125. FileOffset OuterEnd = OuterBegin.getWithOffset(OuterLen);
  126. FileOffset InnerEnd = InnerBegin.getWithOffset(InnerLen);
  127. if (OuterBegin.getFID() != InnerBegin.getFID() ||
  128. InnerBegin < OuterBegin ||
  129. InnerBegin > OuterEnd ||
  130. InnerEnd > OuterEnd) {
  131. IsCommitable = false;
  132. return false;
  133. }
  134. addRemove(range.getBegin(),
  135. OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset());
  136. addRemove(replacementRange.getEnd(),
  137. InnerEnd, OuterEnd.getOffset() - InnerEnd.getOffset());
  138. return true;
  139. }
  140. bool Commit::replaceText(SourceLocation loc, StringRef text,
  141. StringRef replacementText) {
  142. if (text.empty() || replacementText.empty())
  143. return true;
  144. FileOffset Offs;
  145. unsigned Len;
  146. if (!canReplaceText(loc, replacementText, Offs, Len)) {
  147. IsCommitable = false;
  148. return false;
  149. }
  150. addRemove(loc, Offs, Len);
  151. addInsert(loc, Offs, text, false);
  152. return true;
  153. }
  154. void Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text,
  155. bool beforePreviousInsertions) {
  156. if (text.empty())
  157. return;
  158. Edit data;
  159. data.Kind = Act_Insert;
  160. data.OrigLoc = OrigLoc;
  161. data.Offset = Offs;
  162. data.Text = text.copy(StrAlloc);
  163. data.BeforePrev = beforePreviousInsertions;
  164. CachedEdits.push_back(data);
  165. }
  166. void Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs,
  167. FileOffset RangeOffs, unsigned RangeLen,
  168. bool beforePreviousInsertions) {
  169. if (RangeLen == 0)
  170. return;
  171. Edit data;
  172. data.Kind = Act_InsertFromRange;
  173. data.OrigLoc = OrigLoc;
  174. data.Offset = Offs;
  175. data.InsertFromRangeOffs = RangeOffs;
  176. data.Length = RangeLen;
  177. data.BeforePrev = beforePreviousInsertions;
  178. CachedEdits.push_back(data);
  179. }
  180. void Commit::addRemove(SourceLocation OrigLoc,
  181. FileOffset Offs, unsigned Len) {
  182. if (Len == 0)
  183. return;
  184. Edit data;
  185. data.Kind = Act_Remove;
  186. data.OrigLoc = OrigLoc;
  187. data.Offset = Offs;
  188. data.Length = Len;
  189. CachedEdits.push_back(data);
  190. }
  191. bool Commit::canInsert(SourceLocation loc, FileOffset &offs) {
  192. if (loc.isInvalid())
  193. return false;
  194. if (loc.isMacroID())
  195. isAtStartOfMacroExpansion(loc, &loc);
  196. const SourceManager &SM = SourceMgr;
  197. loc = SM.getTopMacroCallerLoc(loc);
  198. if (loc.isMacroID())
  199. if (!isAtStartOfMacroExpansion(loc, &loc))
  200. return false;
  201. if (SM.isInSystemHeader(loc))
  202. return false;
  203. std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
  204. if (locInfo.first.isInvalid())
  205. return false;
  206. offs = FileOffset(locInfo.first, locInfo.second);
  207. return canInsertInOffset(loc, offs);
  208. }
  209. bool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs,
  210. SourceLocation &AfterLoc) {
  211. if (loc.isInvalid())
  212. return false;
  213. SourceLocation spellLoc = SourceMgr.getSpellingLoc(loc);
  214. unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts);
  215. AfterLoc = loc.getLocWithOffset(tokLen);
  216. if (loc.isMacroID())
  217. isAtEndOfMacroExpansion(loc, &loc);
  218. const SourceManager &SM = SourceMgr;
  219. loc = SM.getTopMacroCallerLoc(loc);
  220. if (loc.isMacroID())
  221. if (!isAtEndOfMacroExpansion(loc, &loc))
  222. return false;
  223. if (SM.isInSystemHeader(loc))
  224. return false;
  225. loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts);
  226. if (loc.isInvalid())
  227. return false;
  228. std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
  229. if (locInfo.first.isInvalid())
  230. return false;
  231. offs = FileOffset(locInfo.first, locInfo.second);
  232. return canInsertInOffset(loc, offs);
  233. }
  234. bool Commit::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
  235. for (const auto &act : CachedEdits)
  236. if (act.Kind == Act_Remove) {
  237. if (act.Offset.getFID() == Offs.getFID() &&
  238. Offs > act.Offset && Offs < act.Offset.getWithOffset(act.Length))
  239. return false; // position has been removed.
  240. }
  241. if (!Editor)
  242. return true;
  243. return Editor->canInsertInOffset(OrigLoc, Offs);
  244. }
  245. bool Commit::canRemoveRange(CharSourceRange range,
  246. FileOffset &Offs, unsigned &Len) {
  247. const SourceManager &SM = SourceMgr;
  248. range = Lexer::makeFileCharRange(range, SM, LangOpts);
  249. if (range.isInvalid())
  250. return false;
  251. if (range.getBegin().isMacroID() || range.getEnd().isMacroID())
  252. return false;
  253. if (SM.isInSystemHeader(range.getBegin()) ||
  254. SM.isInSystemHeader(range.getEnd()))
  255. return false;
  256. if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange()))
  257. return false;
  258. std::pair<FileID, unsigned> beginInfo = SM.getDecomposedLoc(range.getBegin());
  259. std::pair<FileID, unsigned> endInfo = SM.getDecomposedLoc(range.getEnd());
  260. if (beginInfo.first != endInfo.first ||
  261. beginInfo.second > endInfo.second)
  262. return false;
  263. Offs = FileOffset(beginInfo.first, beginInfo.second);
  264. Len = endInfo.second - beginInfo.second;
  265. return true;
  266. }
  267. bool Commit::canReplaceText(SourceLocation loc, StringRef text,
  268. FileOffset &Offs, unsigned &Len) {
  269. assert(!text.empty());
  270. if (!canInsert(loc, Offs))
  271. return false;
  272. // Try to load the file buffer.
  273. bool invalidTemp = false;
  274. StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp);
  275. if (invalidTemp)
  276. return false;
  277. Len = text.size();
  278. return file.substr(Offs.getOffset()).startswith(text);
  279. }
  280. bool Commit::isAtStartOfMacroExpansion(SourceLocation loc,
  281. SourceLocation *MacroBegin) const {
  282. return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin);
  283. }
  284. bool Commit::isAtEndOfMacroExpansion(SourceLocation loc,
  285. SourceLocation *MacroEnd) const {
  286. return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd);
  287. }