1 | //===- Commit.cpp - A unit of edits ---------------------------------------===// |
2 | // |
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 |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
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" |
18 | #include <cassert> |
19 | #include <utility> |
20 | |
21 | using namespace clang; |
22 | using namespace edit; |
23 | |
24 | SourceLocation Commit::Edit::getFileLocation(SourceManager &SM) const { |
25 | SourceLocation Loc = SM.getLocForStartOfFile(FID: Offset.getFID()); |
26 | Loc = Loc.getLocWithOffset(Offset: Offset.getOffset()); |
27 | assert(Loc.isFileID()); |
28 | return Loc; |
29 | } |
30 | |
31 | CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const { |
32 | SourceLocation Loc = getFileLocation(SM); |
33 | return CharSourceRange::getCharRange(B: Loc, E: Loc.getLocWithOffset(Offset: Length)); |
34 | } |
35 | |
36 | CharSourceRange Commit::Edit::getInsertFromRange(SourceManager &SM) const { |
37 | SourceLocation Loc = SM.getLocForStartOfFile(FID: InsertFromRangeOffs.getFID()); |
38 | Loc = Loc.getLocWithOffset(Offset: InsertFromRangeOffs.getOffset()); |
39 | assert(Loc.isFileID()); |
40 | return CharSourceRange::getCharRange(B: Loc, E: Loc.getLocWithOffset(Offset: Length)); |
41 | } |
42 | |
43 | Commit::Commit(EditedSource &Editor) |
44 | : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()), |
45 | PPRec(Editor.getPPCondDirectiveRecord()), |
46 | Editor(&Editor) {} |
47 | |
48 | bool Commit::insert(SourceLocation loc, StringRef text, |
49 | bool afterToken, bool beforePreviousInsertions) { |
50 | if (text.empty()) |
51 | return true; |
52 | |
53 | FileOffset Offs; |
54 | if ((!afterToken && !canInsert(loc, Offset&: Offs)) || |
55 | ( afterToken && !canInsertAfterToken(loc, Offset&: Offs, AfterLoc&: loc))) { |
56 | IsCommitable = false; |
57 | return false; |
58 | } |
59 | |
60 | addInsert(OrigLoc: loc, Offs, text, beforePreviousInsertions); |
61 | return true; |
62 | } |
63 | |
64 | bool Commit::insertFromRange(SourceLocation loc, |
65 | CharSourceRange range, |
66 | bool afterToken, bool beforePreviousInsertions) { |
67 | FileOffset RangeOffs; |
68 | unsigned RangeLen; |
69 | if (!canRemoveRange(range, Offs&: RangeOffs, Len&: RangeLen)) { |
70 | IsCommitable = false; |
71 | return false; |
72 | } |
73 | |
74 | FileOffset Offs; |
75 | if ((!afterToken && !canInsert(loc, Offset&: Offs)) || |
76 | ( afterToken && !canInsertAfterToken(loc, Offset&: Offs, AfterLoc&: loc))) { |
77 | IsCommitable = false; |
78 | return false; |
79 | } |
80 | |
81 | if (PPRec && |
82 | PPRec->areInDifferentConditionalDirectiveRegion(LHS: loc, RHS: range.getBegin())) { |
83 | IsCommitable = false; |
84 | return false; |
85 | } |
86 | |
87 | addInsertFromRange(OrigLoc: loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions); |
88 | return true; |
89 | } |
90 | |
91 | bool Commit::remove(CharSourceRange range) { |
92 | FileOffset Offs; |
93 | unsigned Len; |
94 | if (!canRemoveRange(range, Offs, Len)) { |
95 | IsCommitable = false; |
96 | return false; |
97 | } |
98 | |
99 | addRemove(OrigLoc: range.getBegin(), Offs, Len); |
100 | return true; |
101 | } |
102 | |
103 | bool Commit::insertWrap(StringRef before, CharSourceRange range, |
104 | StringRef after) { |
105 | bool commitableBefore = insert(loc: range.getBegin(), text: before, /*afterToken=*/false, |
106 | /*beforePreviousInsertions=*/true); |
107 | bool commitableAfter; |
108 | if (range.isTokenRange()) |
109 | commitableAfter = insertAfterToken(loc: range.getEnd(), text: after); |
110 | else |
111 | commitableAfter = insert(loc: range.getEnd(), text: after); |
112 | |
113 | return commitableBefore && commitableAfter; |
114 | } |
115 | |
116 | bool Commit::replace(CharSourceRange range, StringRef text) { |
117 | if (text.empty()) |
118 | return remove(range); |
119 | |
120 | FileOffset Offs; |
121 | unsigned Len; |
122 | if (!canInsert(loc: range.getBegin(), Offset&: Offs) || !canRemoveRange(range, Offs, Len)) { |
123 | IsCommitable = false; |
124 | return false; |
125 | } |
126 | |
127 | addRemove(OrigLoc: range.getBegin(), Offs, Len); |
128 | addInsert(OrigLoc: range.getBegin(), Offs, text, beforePreviousInsertions: false); |
129 | return true; |
130 | } |
131 | |
132 | bool Commit::replaceWithInner(CharSourceRange range, |
133 | CharSourceRange replacementRange) { |
134 | FileOffset OuterBegin; |
135 | unsigned OuterLen; |
136 | if (!canRemoveRange(range, Offs&: OuterBegin, Len&: OuterLen)) { |
137 | IsCommitable = false; |
138 | return false; |
139 | } |
140 | |
141 | FileOffset InnerBegin; |
142 | unsigned InnerLen; |
143 | if (!canRemoveRange(range: replacementRange, Offs&: InnerBegin, Len&: InnerLen)) { |
144 | IsCommitable = false; |
145 | return false; |
146 | } |
147 | |
148 | FileOffset OuterEnd = OuterBegin.getWithOffset(offset: OuterLen); |
149 | FileOffset InnerEnd = InnerBegin.getWithOffset(offset: InnerLen); |
150 | if (OuterBegin.getFID() != InnerBegin.getFID() || |
151 | InnerBegin < OuterBegin || |
152 | InnerBegin > OuterEnd || |
153 | InnerEnd > OuterEnd) { |
154 | IsCommitable = false; |
155 | return false; |
156 | } |
157 | |
158 | addRemove(OrigLoc: range.getBegin(), |
159 | Offs: OuterBegin, Len: InnerBegin.getOffset() - OuterBegin.getOffset()); |
160 | addRemove(OrigLoc: replacementRange.getEnd(), |
161 | Offs: InnerEnd, Len: OuterEnd.getOffset() - InnerEnd.getOffset()); |
162 | return true; |
163 | } |
164 | |
165 | bool Commit::replaceText(SourceLocation loc, StringRef text, |
166 | StringRef replacementText) { |
167 | if (text.empty() || replacementText.empty()) |
168 | return true; |
169 | |
170 | FileOffset Offs; |
171 | unsigned Len; |
172 | if (!canReplaceText(loc, text: replacementText, Offs, Len)) { |
173 | IsCommitable = false; |
174 | return false; |
175 | } |
176 | |
177 | addRemove(OrigLoc: loc, Offs, Len); |
178 | addInsert(OrigLoc: loc, Offs, text, beforePreviousInsertions: false); |
179 | return true; |
180 | } |
181 | |
182 | void Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text, |
183 | bool beforePreviousInsertions) { |
184 | if (text.empty()) |
185 | return; |
186 | |
187 | Edit data; |
188 | data.Kind = Act_Insert; |
189 | data.OrigLoc = OrigLoc; |
190 | data.Offset = Offs; |
191 | data.Text = text.copy(A&: StrAlloc); |
192 | data.BeforePrev = beforePreviousInsertions; |
193 | CachedEdits.push_back(Elt: data); |
194 | } |
195 | |
196 | void Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs, |
197 | FileOffset RangeOffs, unsigned RangeLen, |
198 | bool beforePreviousInsertions) { |
199 | if (RangeLen == 0) |
200 | return; |
201 | |
202 | Edit data; |
203 | data.Kind = Act_InsertFromRange; |
204 | data.OrigLoc = OrigLoc; |
205 | data.Offset = Offs; |
206 | data.InsertFromRangeOffs = RangeOffs; |
207 | data.Length = RangeLen; |
208 | data.BeforePrev = beforePreviousInsertions; |
209 | CachedEdits.push_back(Elt: data); |
210 | } |
211 | |
212 | void Commit::addRemove(SourceLocation OrigLoc, |
213 | FileOffset Offs, unsigned Len) { |
214 | if (Len == 0) |
215 | return; |
216 | |
217 | Edit data; |
218 | data.Kind = Act_Remove; |
219 | data.OrigLoc = OrigLoc; |
220 | data.Offset = Offs; |
221 | data.Length = Len; |
222 | CachedEdits.push_back(Elt: data); |
223 | } |
224 | |
225 | bool Commit::canInsert(SourceLocation loc, FileOffset &offs) { |
226 | if (loc.isInvalid()) |
227 | return false; |
228 | |
229 | if (loc.isMacroID()) |
230 | isAtStartOfMacroExpansion(loc, MacroBegin: &loc); |
231 | |
232 | const SourceManager &SM = SourceMgr; |
233 | loc = SM.getTopMacroCallerLoc(Loc: loc); |
234 | |
235 | if (loc.isMacroID()) |
236 | if (!isAtStartOfMacroExpansion(loc, MacroBegin: &loc)) |
237 | return false; |
238 | |
239 | if (SM.isInSystemHeader(Loc: loc)) |
240 | return false; |
241 | |
242 | std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(Loc: loc); |
243 | if (locInfo.first.isInvalid()) |
244 | return false; |
245 | offs = FileOffset(locInfo.first, locInfo.second); |
246 | return canInsertInOffset(OrigLoc: loc, Offs: offs); |
247 | } |
248 | |
249 | bool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs, |
250 | SourceLocation &AfterLoc) { |
251 | if (loc.isInvalid()) |
252 | |
253 | return false; |
254 | |
255 | SourceLocation spellLoc = SourceMgr.getSpellingLoc(Loc: loc); |
256 | unsigned tokLen = Lexer::MeasureTokenLength(Loc: spellLoc, SM: SourceMgr, LangOpts); |
257 | AfterLoc = loc.getLocWithOffset(Offset: tokLen); |
258 | |
259 | if (loc.isMacroID()) |
260 | isAtEndOfMacroExpansion(loc, MacroEnd: &loc); |
261 | |
262 | const SourceManager &SM = SourceMgr; |
263 | loc = SM.getTopMacroCallerLoc(Loc: loc); |
264 | |
265 | if (loc.isMacroID()) |
266 | if (!isAtEndOfMacroExpansion(loc, MacroEnd: &loc)) |
267 | return false; |
268 | |
269 | if (SM.isInSystemHeader(Loc: loc)) |
270 | return false; |
271 | |
272 | loc = Lexer::getLocForEndOfToken(Loc: loc, Offset: 0, SM: SourceMgr, LangOpts); |
273 | if (loc.isInvalid()) |
274 | return false; |
275 | |
276 | std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(Loc: loc); |
277 | if (locInfo.first.isInvalid()) |
278 | return false; |
279 | offs = FileOffset(locInfo.first, locInfo.second); |
280 | return canInsertInOffset(OrigLoc: loc, Offs: offs); |
281 | } |
282 | |
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(offset: act.Length)) |
288 | return false; // position has been removed. |
289 | } |
290 | |
291 | if (!Editor) |
292 | return true; |
293 | return Editor->canInsertInOffset(OrigLoc, Offs); |
294 | } |
295 | |
296 | bool Commit::canRemoveRange(CharSourceRange range, |
297 | FileOffset &Offs, unsigned &Len) { |
298 | const SourceManager &SM = SourceMgr; |
299 | range = Lexer::makeFileCharRange(Range: range, SM, LangOpts); |
300 | if (range.isInvalid()) |
301 | return false; |
302 | |
303 | if (range.getBegin().isMacroID() || range.getEnd().isMacroID()) |
304 | return false; |
305 | if (SM.isInSystemHeader(Loc: range.getBegin()) || |
306 | SM.isInSystemHeader(Loc: range.getEnd())) |
307 | return false; |
308 | |
309 | if (PPRec && PPRec->rangeIntersectsConditionalDirective(Range: range.getAsRange())) |
310 | return false; |
311 | |
312 | std::pair<FileID, unsigned> beginInfo = SM.getDecomposedLoc(Loc: range.getBegin()); |
313 | std::pair<FileID, unsigned> endInfo = SM.getDecomposedLoc(Loc: range.getEnd()); |
314 | if (beginInfo.first != endInfo.first || |
315 | beginInfo.second > endInfo.second) |
316 | return false; |
317 | |
318 | Offs = FileOffset(beginInfo.first, beginInfo.second); |
319 | Len = endInfo.second - beginInfo.second; |
320 | return true; |
321 | } |
322 | |
323 | bool Commit::canReplaceText(SourceLocation loc, StringRef text, |
324 | FileOffset &Offs, unsigned &Len) { |
325 | assert(!text.empty()); |
326 | |
327 | if (!canInsert(loc, offs&: Offs)) |
328 | return false; |
329 | |
330 | // Try to load the file buffer. |
331 | bool invalidTemp = false; |
332 | StringRef file = SourceMgr.getBufferData(FID: Offs.getFID(), Invalid: &invalidTemp); |
333 | if (invalidTemp) |
334 | return false; |
335 | |
336 | Len = text.size(); |
337 | return file.substr(Start: Offs.getOffset()).starts_with(Prefix: text); |
338 | } |
339 | |
340 | bool Commit::isAtStartOfMacroExpansion(SourceLocation loc, |
341 | SourceLocation *MacroBegin) const { |
342 | return Lexer::isAtStartOfMacroExpansion(loc, SM: SourceMgr, LangOpts, MacroBegin); |
343 | } |
344 | |
345 | bool Commit::isAtEndOfMacroExpansion(SourceLocation loc, |
346 | SourceLocation *MacroEnd) const { |
347 | return Lexer::isAtEndOfMacroExpansion(loc, SM: SourceMgr, LangOpts, MacroEnd); |
348 | } |
349 | |