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