1 //===----- EditedSource.cpp - Collection of source edits ------------------===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
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"
19 using namespace clang;
22 void EditsReceiver::remove(CharSourceRange range) {
23 replace(range, StringRef());
26 void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,
27 SourceLocation &ExpansionLoc,
28 IdentifierInfo *&II) {
29 assert(SourceMgr.isMacroArgExpansion(Loc));
30 SourceLocation DefArgLoc = SourceMgr.getImmediateExpansionRange(Loc).first;
31 ExpansionLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
33 StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),
34 Buf, SourceMgr, LangOpts);
36 if (!ArgName.empty()) {
37 II = &IdentTable.get(ArgName);
41 void EditedSource::startingCommit() {}
43 void EditedSource::finishedCommit() {
44 for (auto &ExpArg : CurrCommitMacroArgExps) {
45 SourceLocation ExpLoc;
47 std::tie(ExpLoc, II) = ExpArg;
48 auto &ArgNames = ExpansionToArgMap[ExpLoc.getRawEncoding()];
49 if (std::find(ArgNames.begin(), ArgNames.end(), II) == ArgNames.end()) {
50 ArgNames.push_back(II);
53 CurrCommitMacroArgExps.clear();
56 StringRef EditedSource::copyString(const Twine &twine) {
57 SmallString<128> Data;
58 return copyString(twine.toStringRef(Data));
61 bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
62 FileEditsTy::iterator FA = getActionForOffset(Offs);
63 if (FA != FileEdits.end()) {
64 if (FA->first != Offs)
65 return false; // position has been removed.
68 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
70 SourceLocation ExpLoc;
71 deconstructMacroArgLoc(OrigLoc, ExpLoc, II);
72 auto I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
73 if (I != ExpansionToArgMap.end() &&
74 std::find(I->second.begin(), I->second.end(), II) != I->second.end()) {
75 // Trying to write in a macro argument input that has already been
76 // written by a previous commit for another expansion of the same macro
77 // argument name. For example:
80 // #define MAC(x) ((x)+(x))
84 // A commit modified the macro argument 'a' due to the first '(x)'
85 // expansion inside the macro definition, and a subsequent commit tried
86 // to modify 'a' again for the second '(x)' expansion. The edits of the
87 // second commit will be rejected.
95 bool EditedSource::commitInsert(SourceLocation OrigLoc,
96 FileOffset Offs, StringRef text,
97 bool beforePreviousInsertions) {
98 if (!canInsertInOffset(OrigLoc, Offs))
103 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
105 SourceLocation ExpLoc;
106 deconstructMacroArgLoc(OrigLoc, ExpLoc, II);
108 CurrCommitMacroArgExps.emplace_back(ExpLoc, II);
111 FileEdit &FA = FileEdits[Offs];
112 if (FA.Text.empty()) {
113 FA.Text = copyString(text);
117 if (beforePreviousInsertions)
118 FA.Text = copyString(Twine(text) + FA.Text);
120 FA.Text = copyString(Twine(FA.Text) + text);
125 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
127 FileOffset InsertFromRangeOffs, unsigned Len,
128 bool beforePreviousInsertions) {
132 SmallString<128> StrVec;
133 FileOffset BeginOffs = InsertFromRangeOffs;
134 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
135 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
136 if (I != FileEdits.begin())
139 for (; I != FileEdits.end(); ++I) {
140 FileEdit &FA = I->second;
141 FileOffset B = I->first;
142 FileOffset E = B.getWithOffset(FA.RemoveLen);
156 for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
157 FileEdit &FA = I->second;
158 FileOffset B = I->first;
159 FileOffset E = B.getWithOffset(FA.RemoveLen);
162 bool Invalid = false;
163 StringRef text = getSourceText(BeginOffs, B, Invalid);
172 if (BeginOffs < EndOffs) {
173 bool Invalid = false;
174 StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
180 return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
183 void EditedSource::commitRemove(SourceLocation OrigLoc,
184 FileOffset BeginOffs, unsigned Len) {
188 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
189 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
190 if (I != FileEdits.begin())
193 for (; I != FileEdits.end(); ++I) {
194 FileEdit &FA = I->second;
195 FileOffset B = I->first;
196 FileOffset E = B.getWithOffset(FA.RemoveLen);
202 FileOffset TopBegin, TopEnd;
203 FileEdit *TopFA = nullptr;
205 if (I == FileEdits.end()) {
206 FileEditsTy::iterator
207 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
208 NewI->second.RemoveLen = Len;
212 FileEdit &FA = I->second;
213 FileOffset B = I->first;
214 FileOffset E = B.getWithOffset(FA.RemoveLen);
216 FileEditsTy::iterator
217 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
218 TopBegin = BeginOffs;
220 TopFA = &NewI->second;
221 TopFA->RemoveLen = Len;
226 if (TopEnd >= EndOffs)
228 unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
230 TopFA->RemoveLen += diff;
232 TopFA->Text = StringRef();
236 while (I != FileEdits.end()) {
237 FileEdit &FA = I->second;
238 FileOffset B = I->first;
239 FileOffset E = B.getWithOffset(FA.RemoveLen);
245 FileEdits.erase(I++);
250 unsigned diff = E.getOffset() - TopEnd.getOffset();
252 TopFA->RemoveLen += diff;
260 bool EditedSource::commit(const Commit &commit) {
261 if (!commit.isCommitable())
265 EditedSource &Editor;
266 CommitRAII(EditedSource &Editor) : Editor(Editor) {
267 Editor.startingCommit();
270 Editor.finishedCommit();
274 for (edit::Commit::edit_iterator
275 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
276 const edit::Commit::Edit &edit = *I;
278 case edit::Commit::Act_Insert:
279 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
281 case edit::Commit::Act_InsertFromRange:
282 commitInsertFromRange(edit.OrigLoc, edit.Offset,
283 edit.InsertFromRangeOffs, edit.Length,
286 case edit::Commit::Act_Remove:
287 commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
295 // \brief Returns true if it is ok to make the two given characters adjacent.
296 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
297 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
298 // making two '<' adjacent.
299 return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
300 Lexer::isIdentifierBodyChar(right, LangOpts));
303 /// \brief Returns true if it is ok to eliminate the trailing whitespace between
304 /// the given characters.
305 static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
306 const LangOptions &LangOpts) {
307 if (!canBeJoined(left, right, LangOpts))
309 if (isWhitespace(left) || isWhitespace(right))
311 if (canBeJoined(beforeWSpace, right, LangOpts))
312 return false; // the whitespace was intentional, keep it.
316 /// \brief Check the range that we are going to remove and:
317 /// -Remove any trailing whitespace if possible.
318 /// -Insert a space if removing the range is going to mess up the source tokens.
319 static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
320 SourceLocation Loc, FileOffset offs,
321 unsigned &len, StringRef &text) {
322 assert(len && text.empty());
323 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
324 if (BeginTokLoc != Loc)
325 return; // the range is not at the beginning of a token, keep the range.
327 bool Invalid = false;
328 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
332 unsigned begin = offs.getOffset();
333 unsigned end = begin + len;
335 // Do not try to extend the removal if we're at the end of the buffer already.
336 if (end == buffer.size())
339 assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
341 // FIXME: Remove newline.
344 if (buffer[end] == ' ')
349 if (buffer[end] == ' ') {
350 assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
351 "buffer not zero-terminated!");
352 if (canRemoveWhitespace(/*left=*/buffer[begin-1],
353 /*beforeWSpace=*/buffer[end-1],
354 /*right=*/buffer.data()[end + 1], // zero-terminated
360 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
364 static void applyRewrite(EditsReceiver &receiver,
365 StringRef text, FileOffset offs, unsigned len,
366 const SourceManager &SM, const LangOptions &LangOpts) {
367 assert(offs.getFID().isValid());
368 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
369 Loc = Loc.getLocWithOffset(offs.getOffset());
370 assert(Loc.isFileID());
373 adjustRemoval(SM, LangOpts, Loc, offs, len, text);
375 CharSourceRange range = CharSourceRange::getCharRange(Loc,
376 Loc.getLocWithOffset(len));
380 receiver.remove(range);
385 receiver.replace(range, text);
387 receiver.insert(Loc, text);
390 void EditedSource::applyRewrites(EditsReceiver &receiver) {
391 SmallString<128> StrVec;
392 FileOffset CurOffs, CurEnd;
395 if (FileEdits.empty())
398 FileEditsTy::iterator I = FileEdits.begin();
400 StrVec = I->second.Text;
401 CurLen = I->second.RemoveLen;
402 CurEnd = CurOffs.getWithOffset(CurLen);
405 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
406 FileOffset offs = I->first;
407 FileEdit act = I->second;
408 assert(offs >= CurEnd);
410 if (offs == CurEnd) {
412 CurLen += act.RemoveLen;
413 CurEnd.getWithOffset(act.RemoveLen);
417 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts);
420 CurLen = act.RemoveLen;
421 CurEnd = CurOffs.getWithOffset(CurLen);
424 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts);
427 void EditedSource::clearRewrites() {
432 StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
434 assert(BeginOffs.getFID() == EndOffs.getFID());
435 assert(BeginOffs <= EndOffs);
436 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
437 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
438 assert(BLoc.isFileID());
440 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
441 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
442 SourceMgr, LangOpts, &Invalid);
445 EditedSource::FileEditsTy::iterator
446 EditedSource::getActionForOffset(FileOffset Offs) {
447 FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
448 if (I == FileEdits.begin())
449 return FileEdits.end();
451 FileEdit &FA = I->second;
452 FileOffset B = I->first;
453 FileOffset E = B.getWithOffset(FA.RemoveLen);
454 if (Offs >= B && Offs < E)
457 return FileEdits.end();