clang API Documentation
00001 //===----- EditedSource.cpp - Collection of source edits ------------------===// 00002 // 00003 // The LLVM Compiler Infrastructure 00004 // 00005 // This file is distributed under the University of Illinois Open Source 00006 // License. See LICENSE.TXT for details. 00007 // 00008 //===----------------------------------------------------------------------===// 00009 00010 #include "clang/Edit/EditedSource.h" 00011 #include "clang/Basic/CharInfo.h" 00012 #include "clang/Basic/SourceManager.h" 00013 #include "clang/Edit/Commit.h" 00014 #include "clang/Edit/EditsReceiver.h" 00015 #include "clang/Lex/Lexer.h" 00016 #include "llvm/ADT/SmallString.h" 00017 #include "llvm/ADT/Twine.h" 00018 00019 using namespace clang; 00020 using namespace edit; 00021 00022 void EditsReceiver::remove(CharSourceRange range) { 00023 replace(range, StringRef()); 00024 } 00025 00026 StringRef EditedSource::copyString(const Twine &twine) { 00027 SmallString<128> Data; 00028 return copyString(twine.toStringRef(Data)); 00029 } 00030 00031 bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) { 00032 FileEditsTy::iterator FA = getActionForOffset(Offs); 00033 if (FA != FileEdits.end()) { 00034 if (FA->first != Offs) 00035 return false; // position has been removed. 00036 } 00037 00038 if (SourceMgr.isMacroArgExpansion(OrigLoc)) { 00039 SourceLocation 00040 DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first; 00041 SourceLocation 00042 ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first; 00043 llvm::DenseMap<unsigned, SourceLocation>::iterator 00044 I = ExpansionToArgMap.find(ExpLoc.getRawEncoding()); 00045 if (I != ExpansionToArgMap.end() && I->second != DefArgLoc) 00046 return false; // Trying to write in a macro argument input that has 00047 // already been written for another argument of the same macro. 00048 } 00049 00050 return true; 00051 } 00052 00053 bool EditedSource::commitInsert(SourceLocation OrigLoc, 00054 FileOffset Offs, StringRef text, 00055 bool beforePreviousInsertions) { 00056 if (!canInsertInOffset(OrigLoc, Offs)) 00057 return false; 00058 if (text.empty()) 00059 return true; 00060 00061 if (SourceMgr.isMacroArgExpansion(OrigLoc)) { 00062 SourceLocation 00063 DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first; 00064 SourceLocation 00065 ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first; 00066 ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc; 00067 } 00068 00069 FileEdit &FA = FileEdits[Offs]; 00070 if (FA.Text.empty()) { 00071 FA.Text = copyString(text); 00072 return true; 00073 } 00074 00075 if (beforePreviousInsertions) 00076 FA.Text = copyString(Twine(text) + FA.Text); 00077 else 00078 FA.Text = copyString(Twine(FA.Text) + text); 00079 00080 return true; 00081 } 00082 00083 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc, 00084 FileOffset Offs, 00085 FileOffset InsertFromRangeOffs, unsigned Len, 00086 bool beforePreviousInsertions) { 00087 if (Len == 0) 00088 return true; 00089 00090 SmallString<128> StrVec; 00091 FileOffset BeginOffs = InsertFromRangeOffs; 00092 FileOffset EndOffs = BeginOffs.getWithOffset(Len); 00093 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs); 00094 if (I != FileEdits.begin()) 00095 --I; 00096 00097 for (; I != FileEdits.end(); ++I) { 00098 FileEdit &FA = I->second; 00099 FileOffset B = I->first; 00100 FileOffset E = B.getWithOffset(FA.RemoveLen); 00101 00102 if (BeginOffs == B) 00103 break; 00104 00105 if (BeginOffs < E) { 00106 if (BeginOffs > B) { 00107 BeginOffs = E; 00108 ++I; 00109 } 00110 break; 00111 } 00112 } 00113 00114 for (; I != FileEdits.end() && EndOffs > I->first; ++I) { 00115 FileEdit &FA = I->second; 00116 FileOffset B = I->first; 00117 FileOffset E = B.getWithOffset(FA.RemoveLen); 00118 00119 if (BeginOffs < B) { 00120 bool Invalid = false; 00121 StringRef text = getSourceText(BeginOffs, B, Invalid); 00122 if (Invalid) 00123 return false; 00124 StrVec += text; 00125 } 00126 StrVec += FA.Text; 00127 BeginOffs = E; 00128 } 00129 00130 if (BeginOffs < EndOffs) { 00131 bool Invalid = false; 00132 StringRef text = getSourceText(BeginOffs, EndOffs, Invalid); 00133 if (Invalid) 00134 return false; 00135 StrVec += text; 00136 } 00137 00138 return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions); 00139 } 00140 00141 void EditedSource::commitRemove(SourceLocation OrigLoc, 00142 FileOffset BeginOffs, unsigned Len) { 00143 if (Len == 0) 00144 return; 00145 00146 FileOffset EndOffs = BeginOffs.getWithOffset(Len); 00147 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs); 00148 if (I != FileEdits.begin()) 00149 --I; 00150 00151 for (; I != FileEdits.end(); ++I) { 00152 FileEdit &FA = I->second; 00153 FileOffset B = I->first; 00154 FileOffset E = B.getWithOffset(FA.RemoveLen); 00155 00156 if (BeginOffs < E) 00157 break; 00158 } 00159 00160 FileOffset TopBegin, TopEnd; 00161 FileEdit *TopFA = nullptr; 00162 00163 if (I == FileEdits.end()) { 00164 FileEditsTy::iterator 00165 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit())); 00166 NewI->second.RemoveLen = Len; 00167 return; 00168 } 00169 00170 FileEdit &FA = I->second; 00171 FileOffset B = I->first; 00172 FileOffset E = B.getWithOffset(FA.RemoveLen); 00173 if (BeginOffs < B) { 00174 FileEditsTy::iterator 00175 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit())); 00176 TopBegin = BeginOffs; 00177 TopEnd = EndOffs; 00178 TopFA = &NewI->second; 00179 TopFA->RemoveLen = Len; 00180 } else { 00181 TopBegin = B; 00182 TopEnd = E; 00183 TopFA = &I->second; 00184 if (TopEnd >= EndOffs) 00185 return; 00186 unsigned diff = EndOffs.getOffset() - TopEnd.getOffset(); 00187 TopEnd = EndOffs; 00188 TopFA->RemoveLen += diff; 00189 if (B == BeginOffs) 00190 TopFA->Text = StringRef(); 00191 ++I; 00192 } 00193 00194 while (I != FileEdits.end()) { 00195 FileEdit &FA = I->second; 00196 FileOffset B = I->first; 00197 FileOffset E = B.getWithOffset(FA.RemoveLen); 00198 00199 if (B >= TopEnd) 00200 break; 00201 00202 if (E <= TopEnd) { 00203 FileEdits.erase(I++); 00204 continue; 00205 } 00206 00207 if (B < TopEnd) { 00208 unsigned diff = E.getOffset() - TopEnd.getOffset(); 00209 TopEnd = E; 00210 TopFA->RemoveLen += diff; 00211 FileEdits.erase(I); 00212 } 00213 00214 break; 00215 } 00216 } 00217 00218 bool EditedSource::commit(const Commit &commit) { 00219 if (!commit.isCommitable()) 00220 return false; 00221 00222 for (edit::Commit::edit_iterator 00223 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) { 00224 const edit::Commit::Edit &edit = *I; 00225 switch (edit.Kind) { 00226 case edit::Commit::Act_Insert: 00227 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev); 00228 break; 00229 case edit::Commit::Act_InsertFromRange: 00230 commitInsertFromRange(edit.OrigLoc, edit.Offset, 00231 edit.InsertFromRangeOffs, edit.Length, 00232 edit.BeforePrev); 00233 break; 00234 case edit::Commit::Act_Remove: 00235 commitRemove(edit.OrigLoc, edit.Offset, edit.Length); 00236 break; 00237 } 00238 } 00239 00240 return true; 00241 } 00242 00243 // \brief Returns true if it is ok to make the two given characters adjacent. 00244 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) { 00245 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like 00246 // making two '<' adjacent. 00247 return !(Lexer::isIdentifierBodyChar(left, LangOpts) && 00248 Lexer::isIdentifierBodyChar(right, LangOpts)); 00249 } 00250 00251 /// \brief Returns true if it is ok to eliminate the trailing whitespace between 00252 /// the given characters. 00253 static bool canRemoveWhitespace(char left, char beforeWSpace, char right, 00254 const LangOptions &LangOpts) { 00255 if (!canBeJoined(left, right, LangOpts)) 00256 return false; 00257 if (isWhitespace(left) || isWhitespace(right)) 00258 return true; 00259 if (canBeJoined(beforeWSpace, right, LangOpts)) 00260 return false; // the whitespace was intentional, keep it. 00261 return true; 00262 } 00263 00264 /// \brief Check the range that we are going to remove and: 00265 /// -Remove any trailing whitespace if possible. 00266 /// -Insert a space if removing the range is going to mess up the source tokens. 00267 static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts, 00268 SourceLocation Loc, FileOffset offs, 00269 unsigned &len, StringRef &text) { 00270 assert(len && text.empty()); 00271 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts); 00272 if (BeginTokLoc != Loc) 00273 return; // the range is not at the beginning of a token, keep the range. 00274 00275 bool Invalid = false; 00276 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid); 00277 if (Invalid) 00278 return; 00279 00280 unsigned begin = offs.getOffset(); 00281 unsigned end = begin + len; 00282 00283 // Do not try to extend the removal if we're at the end of the buffer already. 00284 if (end == buffer.size()) 00285 return; 00286 00287 assert(begin < buffer.size() && end < buffer.size() && "Invalid range!"); 00288 00289 // FIXME: Remove newline. 00290 00291 if (begin == 0) { 00292 if (buffer[end] == ' ') 00293 ++len; 00294 return; 00295 } 00296 00297 if (buffer[end] == ' ') { 00298 if (canRemoveWhitespace(/*left=*/buffer[begin-1], 00299 /*beforeWSpace=*/buffer[end-1], 00300 /*right=*/buffer[end+1], 00301 LangOpts)) 00302 ++len; 00303 return; 00304 } 00305 00306 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts)) 00307 text = " "; 00308 } 00309 00310 static void applyRewrite(EditsReceiver &receiver, 00311 StringRef text, FileOffset offs, unsigned len, 00312 const SourceManager &SM, const LangOptions &LangOpts) { 00313 assert(!offs.getFID().isInvalid()); 00314 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID()); 00315 Loc = Loc.getLocWithOffset(offs.getOffset()); 00316 assert(Loc.isFileID()); 00317 00318 if (text.empty()) 00319 adjustRemoval(SM, LangOpts, Loc, offs, len, text); 00320 00321 CharSourceRange range = CharSourceRange::getCharRange(Loc, 00322 Loc.getLocWithOffset(len)); 00323 00324 if (text.empty()) { 00325 assert(len); 00326 receiver.remove(range); 00327 return; 00328 } 00329 00330 if (len) 00331 receiver.replace(range, text); 00332 else 00333 receiver.insert(Loc, text); 00334 } 00335 00336 void EditedSource::applyRewrites(EditsReceiver &receiver) { 00337 SmallString<128> StrVec; 00338 FileOffset CurOffs, CurEnd; 00339 unsigned CurLen; 00340 00341 if (FileEdits.empty()) 00342 return; 00343 00344 FileEditsTy::iterator I = FileEdits.begin(); 00345 CurOffs = I->first; 00346 StrVec = I->second.Text; 00347 CurLen = I->second.RemoveLen; 00348 CurEnd = CurOffs.getWithOffset(CurLen); 00349 ++I; 00350 00351 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) { 00352 FileOffset offs = I->first; 00353 FileEdit act = I->second; 00354 assert(offs >= CurEnd); 00355 00356 if (offs == CurEnd) { 00357 StrVec += act.Text; 00358 CurLen += act.RemoveLen; 00359 CurEnd.getWithOffset(act.RemoveLen); 00360 continue; 00361 } 00362 00363 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts); 00364 CurOffs = offs; 00365 StrVec = act.Text; 00366 CurLen = act.RemoveLen; 00367 CurEnd = CurOffs.getWithOffset(CurLen); 00368 } 00369 00370 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts); 00371 } 00372 00373 void EditedSource::clearRewrites() { 00374 FileEdits.clear(); 00375 StrAlloc.Reset(); 00376 } 00377 00378 StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs, 00379 bool &Invalid) { 00380 assert(BeginOffs.getFID() == EndOffs.getFID()); 00381 assert(BeginOffs <= EndOffs); 00382 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID()); 00383 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset()); 00384 assert(BLoc.isFileID()); 00385 SourceLocation 00386 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset()); 00387 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc), 00388 SourceMgr, LangOpts, &Invalid); 00389 } 00390 00391 EditedSource::FileEditsTy::iterator 00392 EditedSource::getActionForOffset(FileOffset Offs) { 00393 FileEditsTy::iterator I = FileEdits.upper_bound(Offs); 00394 if (I == FileEdits.begin()) 00395 return FileEdits.end(); 00396 --I; 00397 FileEdit &FA = I->second; 00398 FileOffset B = I->first; 00399 FileOffset E = B.getWithOffset(FA.RemoveLen); 00400 if (Offs >= B && Offs < E) 00401 return I; 00402 00403 return FileEdits.end(); 00404 }