EditedSource.cpp revision f50b8f0a8ea2a6537c112390bc4054eba390326b
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/SourceManager.h" 12#include "clang/Edit/Commit.h" 13#include "clang/Edit/EditsReceiver.h" 14#include "clang/Lex/Lexer.h" 15#include "llvm/ADT/SmallString.h" 16#include "llvm/ADT/Twine.h" 17#include <cctype> 18 19using namespace clang; 20using namespace edit; 21 22void EditsReceiver::remove(CharSourceRange range) { 23 replace(range, StringRef()); 24} 25 26StringRef EditedSource::copyString(const Twine &twine) { 27 llvm::SmallString<128> Data; 28 return copyString(twine.toStringRef(Data)); 29} 30 31bool 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 53bool 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 85bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc, 86 FileOffset Offs, 87 FileOffset InsertFromRangeOffs, unsigned Len, 88 bool beforePreviousInsertions) { 89 if (Len == 0) 90 return true; 91 92 llvm::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 143void 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 ++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 218bool 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 243static inline bool isIdentifierChar(char c, const LangOptions &LangOpts) { 244 return std::isalnum(c) || c == '_' || (c == '$' && LangOpts.DollarIdents); 245} 246 247// \brief Returns true if it is ok to make the two given characters adjacent. 248static bool canBeJoined(char left, char right, const LangOptions &LangOpts) { 249 // FIXME: Should use the Lexer to make sure we don't allow stuff like 250 // making two '<' adjacent. 251 return !(isIdentifierChar(left, LangOpts) && 252 isIdentifierChar(right, LangOpts)); 253} 254 255/// \brief Returns true if it is ok to eliminate the trailing whitespace between 256/// the given characters. 257static bool canRemoveWhitespace(char left, char beforeWSpace, char right, 258 const LangOptions &LangOpts) { 259 if (!canBeJoined(left, right, LangOpts)) 260 return false; 261 if (std::isspace(left) || std::isspace(right)) 262 return true; 263 if (canBeJoined(beforeWSpace, right, LangOpts)) 264 return false; // the whitespace was intentional, keep it. 265 return true; 266} 267 268/// \brief Check the range that we are going to remove and: 269/// -Remove any trailing whitespace if possible. 270/// -Insert a space if removing the range is going to mess up the source tokens. 271static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts, 272 SourceLocation Loc, FileOffset offs, 273 unsigned &len, StringRef &text) { 274 assert(len && text.empty()); 275 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts); 276 if (BeginTokLoc != Loc) 277 return; // the range is not at the beginning of a token, keep the range. 278 279 bool Invalid = false; 280 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid); 281 if (Invalid) 282 return; 283 284 unsigned begin = offs.getOffset(); 285 unsigned end = begin + len; 286 287 // FIXME: Remove newline. 288 289 if (begin == 0) { 290 if (buffer[end] == ' ') 291 ++len; 292 return; 293 } 294 295 if (buffer[end] == ' ') { 296 if (canRemoveWhitespace(/*left=*/buffer[begin-1], 297 /*beforeWSpace=*/buffer[end-1], 298 /*right=*/buffer[end+1], 299 LangOpts)) 300 ++len; 301 return; 302 } 303 304 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts)) 305 text = " "; 306} 307 308static void applyRewrite(EditsReceiver &receiver, 309 StringRef text, FileOffset offs, unsigned len, 310 const SourceManager &SM, const LangOptions &LangOpts) { 311 assert(!offs.getFID().isInvalid()); 312 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID()); 313 Loc = Loc.getLocWithOffset(offs.getOffset()); 314 assert(Loc.isFileID()); 315 316 if (text.empty()) 317 adjustRemoval(SM, LangOpts, Loc, offs, len, text); 318 319 CharSourceRange range = CharSourceRange::getCharRange(Loc, 320 Loc.getLocWithOffset(len)); 321 322 if (text.empty()) { 323 assert(len); 324 receiver.remove(range); 325 return; 326 } 327 328 if (len) 329 receiver.replace(range, text); 330 else 331 receiver.insert(Loc, text); 332} 333 334void EditedSource::applyRewrites(EditsReceiver &receiver) { 335 llvm::SmallString<128> StrVec; 336 FileOffset CurOffs, CurEnd; 337 unsigned CurLen; 338 339 if (FileEdits.empty()) 340 return; 341 342 FileEditsTy::iterator I = FileEdits.begin(); 343 CurOffs = I->first; 344 StrVec = I->second.Text; 345 CurLen = I->second.RemoveLen; 346 CurEnd = CurOffs.getWithOffset(CurLen); 347 ++I; 348 349 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) { 350 FileOffset offs = I->first; 351 FileEdit act = I->second; 352 assert(offs >= CurEnd); 353 354 if (offs == CurEnd) { 355 StrVec += act.Text; 356 CurLen += act.RemoveLen; 357 CurEnd.getWithOffset(act.RemoveLen); 358 continue; 359 } 360 361 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts); 362 CurOffs = offs; 363 StrVec = act.Text; 364 CurLen = act.RemoveLen; 365 CurEnd = CurOffs.getWithOffset(CurLen); 366 } 367 368 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts); 369} 370 371void EditedSource::clearRewrites() { 372 FileEdits.clear(); 373 StrAlloc.Reset(); 374} 375 376StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs, 377 bool &Invalid) { 378 assert(BeginOffs.getFID() == EndOffs.getFID()); 379 assert(BeginOffs <= EndOffs); 380 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID()); 381 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset()); 382 assert(BLoc.isFileID()); 383 SourceLocation 384 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset()); 385 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc), 386 SourceMgr, LangOpts, &Invalid); 387} 388 389EditedSource::FileEditsTy::iterator 390EditedSource::getActionForOffset(FileOffset Offs) { 391 FileEditsTy::iterator I = FileEdits.upper_bound(Offs); 392 if (I == FileEdits.begin()) 393 return FileEdits.end(); 394 --I; 395 FileEdit &FA = I->second; 396 FileOffset B = I->first; 397 FileOffset E = B.getWithOffset(FA.RemoveLen); 398 if (Offs >= B && Offs < E) 399 return I; 400 401 return FileEdits.end(); 402} 403