1 //===- EditedSource.cpp - Collection of source edits ----------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "clang/Edit/EditedSource.h"
10 #include "clang/Basic/CharInfo.h"
11 #include "clang/Basic/LLVM.h"
12 #include "clang/Basic/SourceLocation.h"
13 #include "clang/Basic/SourceManager.h"
14 #include "clang/Edit/Commit.h"
15 #include "clang/Edit/EditsReceiver.h"
16 #include "clang/Edit/FileOffset.h"
17 #include "clang/Lex/Lexer.h"
18 #include "llvm/ADT/STLExtras.h"
19 #include "llvm/ADT/SmallString.h"
20 #include "llvm/ADT/StringRef.h"
21 #include "llvm/ADT/Twine.h"
27 using namespace clang
;
30 void EditsReceiver::remove(CharSourceRange range
) {
31 replace(range
, StringRef());
34 void EditedSource::deconstructMacroArgLoc(SourceLocation Loc
,
35 SourceLocation
&ExpansionLoc
,
36 MacroArgUse
&ArgUse
) {
37 assert(SourceMgr
.isMacroArgExpansion(Loc
));
38 SourceLocation DefArgLoc
=
39 SourceMgr
.getImmediateExpansionRange(Loc
).getBegin();
40 SourceLocation ImmediateExpansionLoc
=
41 SourceMgr
.getImmediateExpansionRange(DefArgLoc
).getBegin();
42 ExpansionLoc
= ImmediateExpansionLoc
;
43 while (SourceMgr
.isMacroBodyExpansion(ExpansionLoc
))
45 SourceMgr
.getImmediateExpansionRange(ExpansionLoc
).getBegin();
47 StringRef ArgName
= Lexer::getSpelling(SourceMgr
.getSpellingLoc(DefArgLoc
),
48 Buf
, SourceMgr
, LangOpts
);
49 ArgUse
= MacroArgUse
{nullptr, SourceLocation(), SourceLocation()};
51 ArgUse
= {&IdentTable
.get(ArgName
), ImmediateExpansionLoc
,
52 SourceMgr
.getSpellingLoc(DefArgLoc
)};
55 void EditedSource::startingCommit() {}
57 void EditedSource::finishedCommit() {
58 for (auto &ExpArg
: CurrCommitMacroArgExps
) {
59 SourceLocation ExpLoc
;
61 std::tie(ExpLoc
, ArgUse
) = ExpArg
;
62 auto &ArgUses
= ExpansionToArgMap
[ExpLoc
];
63 if (!llvm::is_contained(ArgUses
, ArgUse
))
64 ArgUses
.push_back(ArgUse
);
66 CurrCommitMacroArgExps
.clear();
69 StringRef
EditedSource::copyString(const Twine
&twine
) {
70 SmallString
<128> Data
;
71 return copyString(twine
.toStringRef(Data
));
74 bool EditedSource::canInsertInOffset(SourceLocation OrigLoc
, FileOffset Offs
) {
75 FileEditsTy::iterator FA
= getActionForOffset(Offs
);
76 if (FA
!= FileEdits
.end()) {
77 if (FA
->first
!= Offs
)
78 return false; // position has been removed.
81 if (SourceMgr
.isMacroArgExpansion(OrigLoc
)) {
82 SourceLocation ExpLoc
;
84 deconstructMacroArgLoc(OrigLoc
, ExpLoc
, ArgUse
);
85 auto I
= ExpansionToArgMap
.find(ExpLoc
);
86 if (I
!= ExpansionToArgMap
.end() &&
87 llvm::any_of(I
->second
, [&](const MacroArgUse
&U
) {
88 return ArgUse
.Identifier
== U
.Identifier
&&
89 std::tie(ArgUse
.ImmediateExpansionLoc
, ArgUse
.UseLoc
) !=
90 std::tie(U
.ImmediateExpansionLoc
, U
.UseLoc
);
92 // Trying to write in a macro argument input that has already been
93 // written by a previous commit for another expansion of the same macro
94 // argument name. For example:
97 // #define MAC(x) ((x)+(x))
101 // A commit modified the macro argument 'a' due to the first '(x)'
102 // expansion inside the macro definition, and a subsequent commit tried
103 // to modify 'a' again for the second '(x)' expansion. The edits of the
104 // second commit will be rejected.
111 bool EditedSource::commitInsert(SourceLocation OrigLoc
,
112 FileOffset Offs
, StringRef text
,
113 bool beforePreviousInsertions
) {
114 if (!canInsertInOffset(OrigLoc
, Offs
))
119 if (SourceMgr
.isMacroArgExpansion(OrigLoc
)) {
121 SourceLocation ExpLoc
;
122 deconstructMacroArgLoc(OrigLoc
, ExpLoc
, ArgUse
);
123 if (ArgUse
.Identifier
)
124 CurrCommitMacroArgExps
.emplace_back(ExpLoc
, ArgUse
);
127 FileEdit
&FA
= FileEdits
[Offs
];
128 if (FA
.Text
.empty()) {
129 FA
.Text
= copyString(text
);
133 if (beforePreviousInsertions
)
134 FA
.Text
= copyString(Twine(text
) + FA
.Text
);
136 FA
.Text
= copyString(Twine(FA
.Text
) + text
);
141 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc
,
143 FileOffset InsertFromRangeOffs
, unsigned Len
,
144 bool beforePreviousInsertions
) {
148 SmallString
<128> StrVec
;
149 FileOffset BeginOffs
= InsertFromRangeOffs
;
150 FileOffset EndOffs
= BeginOffs
.getWithOffset(Len
);
151 FileEditsTy::iterator I
= FileEdits
.upper_bound(BeginOffs
);
152 if (I
!= FileEdits
.begin())
155 for (; I
!= FileEdits
.end(); ++I
) {
156 FileEdit
&FA
= I
->second
;
157 FileOffset B
= I
->first
;
158 FileOffset E
= B
.getWithOffset(FA
.RemoveLen
);
172 for (; I
!= FileEdits
.end() && EndOffs
> I
->first
; ++I
) {
173 FileEdit
&FA
= I
->second
;
174 FileOffset B
= I
->first
;
175 FileOffset E
= B
.getWithOffset(FA
.RemoveLen
);
178 bool Invalid
= false;
179 StringRef text
= getSourceText(BeginOffs
, B
, Invalid
);
188 if (BeginOffs
< EndOffs
) {
189 bool Invalid
= false;
190 StringRef text
= getSourceText(BeginOffs
, EndOffs
, Invalid
);
196 return commitInsert(OrigLoc
, Offs
, StrVec
, beforePreviousInsertions
);
199 void EditedSource::commitRemove(SourceLocation OrigLoc
,
200 FileOffset BeginOffs
, unsigned Len
) {
204 FileOffset EndOffs
= BeginOffs
.getWithOffset(Len
);
205 FileEditsTy::iterator I
= FileEdits
.upper_bound(BeginOffs
);
206 if (I
!= FileEdits
.begin())
209 for (; I
!= FileEdits
.end(); ++I
) {
210 FileEdit
&FA
= I
->second
;
211 FileOffset B
= I
->first
;
212 FileOffset E
= B
.getWithOffset(FA
.RemoveLen
);
218 FileOffset TopBegin
, TopEnd
;
219 FileEdit
*TopFA
= nullptr;
221 if (I
== FileEdits
.end()) {
222 FileEditsTy::iterator
223 NewI
= FileEdits
.insert(I
, std::make_pair(BeginOffs
, FileEdit()));
224 NewI
->second
.RemoveLen
= Len
;
228 FileEdit
&FA
= I
->second
;
229 FileOffset B
= I
->first
;
230 FileOffset E
= B
.getWithOffset(FA
.RemoveLen
);
232 FileEditsTy::iterator
233 NewI
= FileEdits
.insert(I
, std::make_pair(BeginOffs
, FileEdit()));
234 TopBegin
= BeginOffs
;
236 TopFA
= &NewI
->second
;
237 TopFA
->RemoveLen
= Len
;
242 if (TopEnd
>= EndOffs
)
244 unsigned diff
= EndOffs
.getOffset() - TopEnd
.getOffset();
246 TopFA
->RemoveLen
+= diff
;
248 TopFA
->Text
= StringRef();
252 while (I
!= FileEdits
.end()) {
253 FileEdit
&FA
= I
->second
;
254 FileOffset B
= I
->first
;
255 FileOffset E
= B
.getWithOffset(FA
.RemoveLen
);
261 FileEdits
.erase(I
++);
266 unsigned diff
= E
.getOffset() - TopEnd
.getOffset();
268 TopFA
->RemoveLen
+= diff
;
276 bool EditedSource::commit(const Commit
&commit
) {
277 if (!commit
.isCommitable())
281 EditedSource
&Editor
;
283 CommitRAII(EditedSource
&Editor
) : Editor(Editor
) {
284 Editor
.startingCommit();
288 Editor
.finishedCommit();
292 for (edit::Commit::edit_iterator
293 I
= commit
.edit_begin(), E
= commit
.edit_end(); I
!= E
; ++I
) {
294 const edit::Commit::Edit
&edit
= *I
;
296 case edit::Commit::Act_Insert
:
297 commitInsert(edit
.OrigLoc
, edit
.Offset
, edit
.Text
, edit
.BeforePrev
);
299 case edit::Commit::Act_InsertFromRange
:
300 commitInsertFromRange(edit
.OrigLoc
, edit
.Offset
,
301 edit
.InsertFromRangeOffs
, edit
.Length
,
304 case edit::Commit::Act_Remove
:
305 commitRemove(edit
.OrigLoc
, edit
.Offset
, edit
.Length
);
313 // Returns true if it is ok to make the two given characters adjacent.
314 static bool canBeJoined(char left
, char right
, const LangOptions
&LangOpts
) {
315 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
316 // making two '<' adjacent.
317 return !(Lexer::isAsciiIdentifierContinueChar(left
, LangOpts
) &&
318 Lexer::isAsciiIdentifierContinueChar(right
, LangOpts
));
321 /// Returns true if it is ok to eliminate the trailing whitespace between
322 /// the given characters.
323 static bool canRemoveWhitespace(char left
, char beforeWSpace
, char right
,
324 const LangOptions
&LangOpts
) {
325 if (!canBeJoined(left
, right
, LangOpts
))
327 if (isWhitespace(left
) || isWhitespace(right
))
329 if (canBeJoined(beforeWSpace
, right
, LangOpts
))
330 return false; // the whitespace was intentional, keep it.
334 /// Check the range that we are going to remove and:
335 /// -Remove any trailing whitespace if possible.
336 /// -Insert a space if removing the range is going to mess up the source tokens.
337 static void adjustRemoval(const SourceManager
&SM
, const LangOptions
&LangOpts
,
338 SourceLocation Loc
, FileOffset offs
,
339 unsigned &len
, StringRef
&text
) {
340 assert(len
&& text
.empty());
341 SourceLocation BeginTokLoc
= Lexer::GetBeginningOfToken(Loc
, SM
, LangOpts
);
342 if (BeginTokLoc
!= Loc
)
343 return; // the range is not at the beginning of a token, keep the range.
345 bool Invalid
= false;
346 StringRef buffer
= SM
.getBufferData(offs
.getFID(), &Invalid
);
350 unsigned begin
= offs
.getOffset();
351 unsigned end
= begin
+ len
;
353 // Do not try to extend the removal if we're at the end of the buffer already.
354 if (end
== buffer
.size())
357 assert(begin
< buffer
.size() && end
< buffer
.size() && "Invalid range!");
359 // FIXME: Remove newline.
362 if (buffer
[end
] == ' ')
367 if (buffer
[end
] == ' ') {
368 assert((end
+ 1 != buffer
.size() || buffer
.data()[end
+ 1] == 0) &&
369 "buffer not zero-terminated!");
370 if (canRemoveWhitespace(/*left=*/buffer
[begin
-1],
371 /*beforeWSpace=*/buffer
[end
-1],
372 /*right=*/buffer
.data()[end
+ 1], // zero-terminated
378 if (!canBeJoined(buffer
[begin
-1], buffer
[end
], LangOpts
))
382 static void applyRewrite(EditsReceiver
&receiver
,
383 StringRef text
, FileOffset offs
, unsigned len
,
384 const SourceManager
&SM
, const LangOptions
&LangOpts
,
385 bool shouldAdjustRemovals
) {
386 assert(offs
.getFID().isValid());
387 SourceLocation Loc
= SM
.getLocForStartOfFile(offs
.getFID());
388 Loc
= Loc
.getLocWithOffset(offs
.getOffset());
389 assert(Loc
.isFileID());
391 if (text
.empty() && shouldAdjustRemovals
)
392 adjustRemoval(SM
, LangOpts
, Loc
, offs
, len
, text
);
394 CharSourceRange range
= CharSourceRange::getCharRange(Loc
,
395 Loc
.getLocWithOffset(len
));
399 receiver
.remove(range
);
404 receiver
.replace(range
, text
);
406 receiver
.insert(Loc
, text
);
409 void EditedSource::applyRewrites(EditsReceiver
&receiver
,
410 bool shouldAdjustRemovals
) {
411 SmallString
<128> StrVec
;
412 FileOffset CurOffs
, CurEnd
;
415 if (FileEdits
.empty())
418 FileEditsTy::iterator I
= FileEdits
.begin();
420 StrVec
= I
->second
.Text
;
421 CurLen
= I
->second
.RemoveLen
;
422 CurEnd
= CurOffs
.getWithOffset(CurLen
);
425 for (FileEditsTy::iterator E
= FileEdits
.end(); I
!= E
; ++I
) {
426 FileOffset offs
= I
->first
;
427 FileEdit act
= I
->second
;
428 assert(offs
>= CurEnd
);
430 if (offs
== CurEnd
) {
432 CurLen
+= act
.RemoveLen
;
433 CurEnd
.getWithOffset(act
.RemoveLen
);
437 applyRewrite(receiver
, StrVec
, CurOffs
, CurLen
, SourceMgr
, LangOpts
,
438 shouldAdjustRemovals
);
441 CurLen
= act
.RemoveLen
;
442 CurEnd
= CurOffs
.getWithOffset(CurLen
);
445 applyRewrite(receiver
, StrVec
, CurOffs
, CurLen
, SourceMgr
, LangOpts
,
446 shouldAdjustRemovals
);
449 void EditedSource::clearRewrites() {
454 StringRef
EditedSource::getSourceText(FileOffset BeginOffs
, FileOffset EndOffs
,
456 assert(BeginOffs
.getFID() == EndOffs
.getFID());
457 assert(BeginOffs
<= EndOffs
);
458 SourceLocation BLoc
= SourceMgr
.getLocForStartOfFile(BeginOffs
.getFID());
459 BLoc
= BLoc
.getLocWithOffset(BeginOffs
.getOffset());
460 assert(BLoc
.isFileID());
462 ELoc
= BLoc
.getLocWithOffset(EndOffs
.getOffset() - BeginOffs
.getOffset());
463 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc
, ELoc
),
464 SourceMgr
, LangOpts
, &Invalid
);
467 EditedSource::FileEditsTy::iterator
468 EditedSource::getActionForOffset(FileOffset Offs
) {
469 FileEditsTy::iterator I
= FileEdits
.upper_bound(Offs
);
470 if (I
== FileEdits
.begin())
471 return FileEdits
.end();
473 FileEdit
&FA
= I
->second
;
474 FileOffset B
= I
->first
;
475 FileOffset E
= B
.getWithOffset(FA
.RemoveLen
);
476 if (Offs
>= B
&& Offs
< E
)
479 return FileEdits
.end();