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