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