1 //===- Commit.cpp - A unit of 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/Commit.h"
10 #include "clang/Basic/LLVM.h"
11 #include "clang/Basic/SourceLocation.h"
12 #include "clang/Basic/SourceManager.h"
13 #include "clang/Edit/EditedSource.h"
14 #include "clang/Edit/FileOffset.h"
15 #include "clang/Lex/Lexer.h"
16 #include "clang/Lex/PPConditionalDirectiveRecord.h"
17 #include "llvm/ADT/StringRef.h"
21 using namespace clang
;
24 SourceLocation
Commit::Edit::getFileLocation(SourceManager
&SM
) const {
25 SourceLocation Loc
= SM
.getLocForStartOfFile(Offset
.getFID());
26 Loc
= Loc
.getLocWithOffset(Offset
.getOffset());
27 assert(Loc
.isFileID());
31 CharSourceRange
Commit::Edit::getFileRange(SourceManager
&SM
) const {
32 SourceLocation Loc
= getFileLocation(SM
);
33 return CharSourceRange::getCharRange(Loc
, Loc
.getLocWithOffset(Length
));
36 CharSourceRange
Commit::Edit::getInsertFromRange(SourceManager
&SM
) const {
37 SourceLocation Loc
= SM
.getLocForStartOfFile(InsertFromRangeOffs
.getFID());
38 Loc
= Loc
.getLocWithOffset(InsertFromRangeOffs
.getOffset());
39 assert(Loc
.isFileID());
40 return CharSourceRange::getCharRange(Loc
, Loc
.getLocWithOffset(Length
));
43 Commit::Commit(EditedSource
&Editor
)
44 : SourceMgr(Editor
.getSourceManager()), LangOpts(Editor
.getLangOpts()),
45 PPRec(Editor
.getPPCondDirectiveRecord()),
48 bool Commit::insert(SourceLocation loc
, StringRef text
,
49 bool afterToken
, bool beforePreviousInsertions
) {
54 if ((!afterToken
&& !canInsert(loc
, Offs
)) ||
55 ( afterToken
&& !canInsertAfterToken(loc
, Offs
, loc
))) {
60 addInsert(loc
, Offs
, text
, beforePreviousInsertions
);
64 bool Commit::insertFromRange(SourceLocation loc
,
65 CharSourceRange range
,
66 bool afterToken
, bool beforePreviousInsertions
) {
69 if (!canRemoveRange(range
, RangeOffs
, RangeLen
)) {
75 if ((!afterToken
&& !canInsert(loc
, Offs
)) ||
76 ( afterToken
&& !canInsertAfterToken(loc
, Offs
, loc
))) {
82 PPRec
->areInDifferentConditionalDirectiveRegion(loc
, range
.getBegin())) {
87 addInsertFromRange(loc
, Offs
, RangeOffs
, RangeLen
, beforePreviousInsertions
);
91 bool Commit::remove(CharSourceRange range
) {
94 if (!canRemoveRange(range
, Offs
, Len
)) {
99 addRemove(range
.getBegin(), Offs
, Len
);
103 bool Commit::insertWrap(StringRef before
, CharSourceRange range
,
105 bool commitableBefore
= insert(range
.getBegin(), before
, /*afterToken=*/false,
106 /*beforePreviousInsertions=*/true);
107 bool commitableAfter
;
108 if (range
.isTokenRange())
109 commitableAfter
= insertAfterToken(range
.getEnd(), after
);
111 commitableAfter
= insert(range
.getEnd(), after
);
113 return commitableBefore
&& commitableAfter
;
116 bool Commit::replace(CharSourceRange range
, StringRef text
) {
118 return remove(range
);
122 if (!canInsert(range
.getBegin(), Offs
) || !canRemoveRange(range
, Offs
, Len
)) {
123 IsCommitable
= false;
127 addRemove(range
.getBegin(), Offs
, Len
);
128 addInsert(range
.getBegin(), Offs
, text
, false);
132 bool Commit::replaceWithInner(CharSourceRange range
,
133 CharSourceRange replacementRange
) {
134 FileOffset OuterBegin
;
136 if (!canRemoveRange(range
, OuterBegin
, OuterLen
)) {
137 IsCommitable
= false;
141 FileOffset InnerBegin
;
143 if (!canRemoveRange(replacementRange
, InnerBegin
, InnerLen
)) {
144 IsCommitable
= false;
148 FileOffset OuterEnd
= OuterBegin
.getWithOffset(OuterLen
);
149 FileOffset InnerEnd
= InnerBegin
.getWithOffset(InnerLen
);
150 if (OuterBegin
.getFID() != InnerBegin
.getFID() ||
151 InnerBegin
< OuterBegin
||
152 InnerBegin
> OuterEnd
||
153 InnerEnd
> OuterEnd
) {
154 IsCommitable
= false;
158 addRemove(range
.getBegin(),
159 OuterBegin
, InnerBegin
.getOffset() - OuterBegin
.getOffset());
160 addRemove(replacementRange
.getEnd(),
161 InnerEnd
, OuterEnd
.getOffset() - InnerEnd
.getOffset());
165 bool Commit::replaceText(SourceLocation loc
, StringRef text
,
166 StringRef replacementText
) {
167 if (text
.empty() || replacementText
.empty())
172 if (!canReplaceText(loc
, replacementText
, Offs
, Len
)) {
173 IsCommitable
= false;
177 addRemove(loc
, Offs
, Len
);
178 addInsert(loc
, Offs
, text
, false);
182 void Commit::addInsert(SourceLocation OrigLoc
, FileOffset Offs
, StringRef text
,
183 bool beforePreviousInsertions
) {
188 data
.Kind
= Act_Insert
;
189 data
.OrigLoc
= OrigLoc
;
191 data
.Text
= text
.copy(StrAlloc
);
192 data
.BeforePrev
= beforePreviousInsertions
;
193 CachedEdits
.push_back(data
);
196 void Commit::addInsertFromRange(SourceLocation OrigLoc
, FileOffset Offs
,
197 FileOffset RangeOffs
, unsigned RangeLen
,
198 bool beforePreviousInsertions
) {
203 data
.Kind
= Act_InsertFromRange
;
204 data
.OrigLoc
= OrigLoc
;
206 data
.InsertFromRangeOffs
= RangeOffs
;
207 data
.Length
= RangeLen
;
208 data
.BeforePrev
= beforePreviousInsertions
;
209 CachedEdits
.push_back(data
);
212 void Commit::addRemove(SourceLocation OrigLoc
,
213 FileOffset Offs
, unsigned Len
) {
218 data
.Kind
= Act_Remove
;
219 data
.OrigLoc
= OrigLoc
;
222 CachedEdits
.push_back(data
);
225 bool Commit::canInsert(SourceLocation loc
, FileOffset
&offs
) {
230 isAtStartOfMacroExpansion(loc
, &loc
);
232 const SourceManager
&SM
= SourceMgr
;
233 loc
= SM
.getTopMacroCallerLoc(loc
);
236 if (!isAtStartOfMacroExpansion(loc
, &loc
))
239 if (SM
.isInSystemHeader(loc
))
242 std::pair
<FileID
, unsigned> locInfo
= SM
.getDecomposedLoc(loc
);
243 if (locInfo
.first
.isInvalid())
245 offs
= FileOffset(locInfo
.first
, locInfo
.second
);
246 return canInsertInOffset(loc
, offs
);
249 bool Commit::canInsertAfterToken(SourceLocation loc
, FileOffset
&offs
,
250 SourceLocation
&AfterLoc
) {
255 SourceLocation spellLoc
= SourceMgr
.getSpellingLoc(loc
);
256 unsigned tokLen
= Lexer::MeasureTokenLength(spellLoc
, SourceMgr
, LangOpts
);
257 AfterLoc
= loc
.getLocWithOffset(tokLen
);
260 isAtEndOfMacroExpansion(loc
, &loc
);
262 const SourceManager
&SM
= SourceMgr
;
263 loc
= SM
.getTopMacroCallerLoc(loc
);
266 if (!isAtEndOfMacroExpansion(loc
, &loc
))
269 if (SM
.isInSystemHeader(loc
))
272 loc
= Lexer::getLocForEndOfToken(loc
, 0, SourceMgr
, LangOpts
);
276 std::pair
<FileID
, unsigned> locInfo
= SM
.getDecomposedLoc(loc
);
277 if (locInfo
.first
.isInvalid())
279 offs
= FileOffset(locInfo
.first
, locInfo
.second
);
280 return canInsertInOffset(loc
, offs
);
283 bool Commit::canInsertInOffset(SourceLocation OrigLoc
, FileOffset Offs
) {
284 for (const auto &act
: CachedEdits
)
285 if (act
.Kind
== Act_Remove
) {
286 if (act
.Offset
.getFID() == Offs
.getFID() &&
287 Offs
> act
.Offset
&& Offs
< act
.Offset
.getWithOffset(act
.Length
))
288 return false; // position has been removed.
293 return Editor
->canInsertInOffset(OrigLoc
, Offs
);
296 bool Commit::canRemoveRange(CharSourceRange range
,
297 FileOffset
&Offs
, unsigned &Len
) {
298 const SourceManager
&SM
= SourceMgr
;
299 range
= Lexer::makeFileCharRange(range
, SM
, LangOpts
);
300 if (range
.isInvalid())
303 if (range
.getBegin().isMacroID() || range
.getEnd().isMacroID())
305 if (SM
.isInSystemHeader(range
.getBegin()) ||
306 SM
.isInSystemHeader(range
.getEnd()))
309 if (PPRec
&& PPRec
->rangeIntersectsConditionalDirective(range
.getAsRange()))
312 std::pair
<FileID
, unsigned> beginInfo
= SM
.getDecomposedLoc(range
.getBegin());
313 std::pair
<FileID
, unsigned> endInfo
= SM
.getDecomposedLoc(range
.getEnd());
314 if (beginInfo
.first
!= endInfo
.first
||
315 beginInfo
.second
> endInfo
.second
)
318 Offs
= FileOffset(beginInfo
.first
, beginInfo
.second
);
319 Len
= endInfo
.second
- beginInfo
.second
;
323 bool Commit::canReplaceText(SourceLocation loc
, StringRef text
,
324 FileOffset
&Offs
, unsigned &Len
) {
325 assert(!text
.empty());
327 if (!canInsert(loc
, Offs
))
330 // Try to load the file buffer.
331 bool invalidTemp
= false;
332 StringRef file
= SourceMgr
.getBufferData(Offs
.getFID(), &invalidTemp
);
337 return file
.substr(Offs
.getOffset()).startswith(text
);
340 bool Commit::isAtStartOfMacroExpansion(SourceLocation loc
,
341 SourceLocation
*MacroBegin
) const {
342 return Lexer::isAtStartOfMacroExpansion(loc
, SourceMgr
, LangOpts
, MacroBegin
);
345 bool Commit::isAtEndOfMacroExpansion(SourceLocation loc
,
346 SourceLocation
*MacroEnd
) const {
347 return Lexer::isAtEndOfMacroExpansion(loc
, SourceMgr
, LangOpts
, MacroEnd
);