1 //===----- EditedSource.cpp - Collection of source 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/EditedSource.h" 11 #include "clang/Basic/CharInfo.h" 12 #include "clang/Basic/SourceManager.h" 13 #include "clang/Edit/Commit.h" 14 #include "clang/Edit/EditsReceiver.h" 15 #include "clang/Lex/Lexer.h" 16 #include "llvm/ADT/SmallString.h" 17 #include "llvm/ADT/Twine.h" 18 19 using namespace clang; 20 using namespace edit; 21 22 void EditsReceiver::remove(CharSourceRange range) { 23 replace(range, StringRef()); 24 } 25 26 StringRef EditedSource::copyString(const Twine &twine) { 27 SmallString<128> Data; 28 return copyString(twine.toStringRef(Data)); 29 } 30 31 bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) { 32 FileEditsTy::iterator FA = getActionForOffset(Offs); 33 if (FA != FileEdits.end()) { 34 if (FA->first != Offs) 35 return false; // position has been removed. 36 } 37 38 if (SourceMgr.isMacroArgExpansion(OrigLoc)) { 39 SourceLocation 40 DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first; 41 SourceLocation 42 ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first; 43 llvm::DenseMap<unsigned, SourceLocation>::iterator 44 I = ExpansionToArgMap.find(ExpLoc.getRawEncoding()); 45 if (I != ExpansionToArgMap.end() && I->second != DefArgLoc) 46 return false; // Trying to write in a macro argument input that has 47 // already been written for another argument of the same macro. 48 } 49 50 return true; 51 } 52 53 bool EditedSource::commitInsert(SourceLocation OrigLoc, 54 FileOffset Offs, StringRef text, 55 bool beforePreviousInsertions) { 56 if (!canInsertInOffset(OrigLoc, Offs)) 57 return false; 58 if (text.empty()) 59 return true; 60 61 if (SourceMgr.isMacroArgExpansion(OrigLoc)) { 62 SourceLocation 63 DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first; 64 SourceLocation 65 ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first; 66 ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc; 67 } 68 69 FileEdit &FA = FileEdits[Offs]; 70 if (FA.Text.empty()) { 71 FA.Text = copyString(text); 72 return true; 73 } 74 75 Twine concat; 76 if (beforePreviousInsertions) 77 concat = Twine(text) + FA.Text; 78 else 79 concat = Twine(FA.Text) + text; 80 81 FA.Text = copyString(concat); 82 return true; 83 } 84 85 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc, 86 FileOffset Offs, 87 FileOffset InsertFromRangeOffs, unsigned Len, 88 bool beforePreviousInsertions) { 89 if (Len == 0) 90 return true; 91 92 SmallString<128> StrVec; 93 FileOffset BeginOffs = InsertFromRangeOffs; 94 FileOffset EndOffs = BeginOffs.getWithOffset(Len); 95 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs); 96 if (I != FileEdits.begin()) 97 --I; 98 99 for (; I != FileEdits.end(); ++I) { 100 FileEdit &FA = I->second; 101 FileOffset B = I->first; 102 FileOffset E = B.getWithOffset(FA.RemoveLen); 103 104 if (BeginOffs == B) 105 break; 106 107 if (BeginOffs < E) { 108 if (BeginOffs > B) { 109 BeginOffs = E; 110 ++I; 111 } 112 break; 113 } 114 } 115 116 for (; I != FileEdits.end() && EndOffs > I->first; ++I) { 117 FileEdit &FA = I->second; 118 FileOffset B = I->first; 119 FileOffset E = B.getWithOffset(FA.RemoveLen); 120 121 if (BeginOffs < B) { 122 bool Invalid = false; 123 StringRef text = getSourceText(BeginOffs, B, Invalid); 124 if (Invalid) 125 return false; 126 StrVec += text; 127 } 128 StrVec += FA.Text; 129 BeginOffs = E; 130 } 131 132 if (BeginOffs < EndOffs) { 133 bool Invalid = false; 134 StringRef text = getSourceText(BeginOffs, EndOffs, Invalid); 135 if (Invalid) 136 return false; 137 StrVec += text; 138 } 139 140 return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions); 141 } 142 143 void EditedSource::commitRemove(SourceLocation OrigLoc, 144 FileOffset BeginOffs, unsigned Len) { 145 if (Len == 0) 146 return; 147 148 FileOffset EndOffs = BeginOffs.getWithOffset(Len); 149 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs); 150 if (I != FileEdits.begin()) 151 --I; 152 153 for (; I != FileEdits.end(); ++I) { 154 FileEdit &FA = I->second; 155 FileOffset B = I->first; 156 FileOffset E = B.getWithOffset(FA.RemoveLen); 157 158 if (BeginOffs < E) 159 break; 160 } 161 162 FileOffset TopBegin, TopEnd; 163 FileEdit *TopFA = 0; 164 165 if (I == FileEdits.end()) { 166 FileEditsTy::iterator 167 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit())); 168 NewI->second.RemoveLen = Len; 169 return; 170 } 171 172 FileEdit &FA = I->second; 173 FileOffset B = I->first; 174 FileOffset E = B.getWithOffset(FA.RemoveLen); 175 if (BeginOffs < B) { 176 FileEditsTy::iterator 177 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit())); 178 TopBegin = BeginOffs; 179 TopEnd = EndOffs; 180 TopFA = &NewI->second; 181 TopFA->RemoveLen = Len; 182 } else { 183 TopBegin = B; 184 TopEnd = E; 185 TopFA = &I->second; 186 if (TopEnd >= EndOffs) 187 return; 188 unsigned diff = EndOffs.getOffset() - TopEnd.getOffset(); 189 TopEnd = EndOffs; 190 TopFA->RemoveLen += diff; 191 if (B == BeginOffs) 192 TopFA->Text = StringRef(); 193 ++I; 194 } 195 196 while (I != FileEdits.end()) { 197 FileEdit &FA = I->second; 198 FileOffset B = I->first; 199 FileOffset E = B.getWithOffset(FA.RemoveLen); 200 201 if (B >= TopEnd) 202 break; 203 204 if (E <= TopEnd) { 205 FileEdits.erase(I++); 206 continue; 207 } 208 209 if (B < TopEnd) { 210 unsigned diff = E.getOffset() - TopEnd.getOffset(); 211 TopEnd = E; 212 TopFA->RemoveLen += diff; 213 FileEdits.erase(I); 214 } 215 216 break; 217 } 218 } 219 220 bool EditedSource::commit(const Commit &commit) { 221 if (!commit.isCommitable()) 222 return false; 223 224 for (edit::Commit::edit_iterator 225 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) { 226 const edit::Commit::Edit &edit = *I; 227 switch (edit.Kind) { 228 case edit::Commit::Act_Insert: 229 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev); 230 break; 231 case edit::Commit::Act_InsertFromRange: 232 commitInsertFromRange(edit.OrigLoc, edit.Offset, 233 edit.InsertFromRangeOffs, edit.Length, 234 edit.BeforePrev); 235 break; 236 case edit::Commit::Act_Remove: 237 commitRemove(edit.OrigLoc, edit.Offset, edit.Length); 238 break; 239 } 240 } 241 242 return true; 243 } 244 245 // \brief Returns true if it is ok to make the two given characters adjacent. 246 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) { 247 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like 248 // making two '<' adjacent. 249 return !(Lexer::isIdentifierBodyChar(left, LangOpts) && 250 Lexer::isIdentifierBodyChar(right, LangOpts)); 251 } 252 253 /// \brief Returns true if it is ok to eliminate the trailing whitespace between 254 /// the given characters. 255 static bool canRemoveWhitespace(char left, char beforeWSpace, char right, 256 const LangOptions &LangOpts) { 257 if (!canBeJoined(left, right, LangOpts)) 258 return false; 259 if (isWhitespace(left) || isWhitespace(right)) 260 return true; 261 if (canBeJoined(beforeWSpace, right, LangOpts)) 262 return false; // the whitespace was intentional, keep it. 263 return true; 264 } 265 266 /// \brief Check the range that we are going to remove and: 267 /// -Remove any trailing whitespace if possible. 268 /// -Insert a space if removing the range is going to mess up the source tokens. 269 static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts, 270 SourceLocation Loc, FileOffset offs, 271 unsigned &len, StringRef &text) { 272 assert(len && text.empty()); 273 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts); 274 if (BeginTokLoc != Loc) 275 return; // the range is not at the beginning of a token, keep the range. 276 277 bool Invalid = false; 278 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid); 279 if (Invalid) 280 return; 281 282 unsigned begin = offs.getOffset(); 283 unsigned end = begin + len; 284 285 // FIXME: Remove newline. 286 287 if (begin == 0) { 288 if (buffer[end] == ' ') 289 ++len; 290 return; 291 } 292 293 if (buffer[end] == ' ') { 294 if (canRemoveWhitespace(/*left=*/buffer[begin-1], 295 /*beforeWSpace=*/buffer[end-1], 296 /*right=*/buffer[end+1], 297 LangOpts)) 298 ++len; 299 return; 300 } 301 302 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts)) 303 text = " "; 304 } 305 306 static void applyRewrite(EditsReceiver &receiver, 307 StringRef text, FileOffset offs, unsigned len, 308 const SourceManager &SM, const LangOptions &LangOpts) { 309 assert(!offs.getFID().isInvalid()); 310 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID()); 311 Loc = Loc.getLocWithOffset(offs.getOffset()); 312 assert(Loc.isFileID()); 313 314 if (text.empty()) 315 adjustRemoval(SM, LangOpts, Loc, offs, len, text); 316 317 CharSourceRange range = CharSourceRange::getCharRange(Loc, 318 Loc.getLocWithOffset(len)); 319 320 if (text.empty()) { 321 assert(len); 322 receiver.remove(range); 323 return; 324 } 325 326 if (len) 327 receiver.replace(range, text); 328 else 329 receiver.insert(Loc, text); 330 } 331 332 void EditedSource::applyRewrites(EditsReceiver &receiver) { 333 SmallString<128> StrVec; 334 FileOffset CurOffs, CurEnd; 335 unsigned CurLen; 336 337 if (FileEdits.empty()) 338 return; 339 340 FileEditsTy::iterator I = FileEdits.begin(); 341 CurOffs = I->first; 342 StrVec = I->second.Text; 343 CurLen = I->second.RemoveLen; 344 CurEnd = CurOffs.getWithOffset(CurLen); 345 ++I; 346 347 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) { 348 FileOffset offs = I->first; 349 FileEdit act = I->second; 350 assert(offs >= CurEnd); 351 352 if (offs == CurEnd) { 353 StrVec += act.Text; 354 CurLen += act.RemoveLen; 355 CurEnd.getWithOffset(act.RemoveLen); 356 continue; 357 } 358 359 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts); 360 CurOffs = offs; 361 StrVec = act.Text; 362 CurLen = act.RemoveLen; 363 CurEnd = CurOffs.getWithOffset(CurLen); 364 } 365 366 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts); 367 } 368 369 void EditedSource::clearRewrites() { 370 FileEdits.clear(); 371 StrAlloc.Reset(); 372 } 373 374 StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs, 375 bool &Invalid) { 376 assert(BeginOffs.getFID() == EndOffs.getFID()); 377 assert(BeginOffs <= EndOffs); 378 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID()); 379 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset()); 380 assert(BLoc.isFileID()); 381 SourceLocation 382 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset()); 383 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc), 384 SourceMgr, LangOpts, &Invalid); 385 } 386 387 EditedSource::FileEditsTy::iterator 388 EditedSource::getActionForOffset(FileOffset Offs) { 389 FileEditsTy::iterator I = FileEdits.upper_bound(Offs); 390 if (I == FileEdits.begin()) 391 return FileEdits.end(); 392 --I; 393 FileEdit &FA = I->second; 394 FileOffset B = I->first; 395 FileOffset E = B.getWithOffset(FA.RemoveLen); 396 if (Offs >= B && Offs < E) 397 return I; 398 399 return FileEdits.end(); 400 } 401