1 | |
2 | |
3 | |
4 | |
5 | |
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(Offset.getFID()); |
26 | Loc = Loc.getLocWithOffset(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(Loc, Loc.getLocWithOffset(Length)); |
34 | } |
35 | |
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)); |
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, Offs)) || |
55 | ( afterToken && !canInsertAfterToken(loc, Offs, loc))) { |
56 | IsCommitable = false; |
57 | return false; |
58 | } |
59 | |
60 | addInsert(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, RangeOffs, RangeLen)) { |
70 | IsCommitable = false; |
71 | return false; |
72 | } |
73 | |
74 | FileOffset Offs; |
75 | if ((!afterToken && !canInsert(loc, Offs)) || |
76 | ( afterToken && !canInsertAfterToken(loc, Offs, loc))) { |
77 | IsCommitable = false; |
78 | return false; |
79 | } |
80 | |
81 | if (PPRec && |
82 | PPRec->areInDifferentConditionalDirectiveRegion(loc, range.getBegin())) { |
83 | IsCommitable = false; |
84 | return false; |
85 | } |
86 | |
87 | addInsertFromRange(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(range.getBegin(), Offs, Len); |
100 | return true; |
101 | } |
102 | |
103 | bool Commit::insertWrap(StringRef before, CharSourceRange range, |
104 | StringRef after) { |
105 | bool commitableBefore = insert(range.getBegin(), before, , |
106 | ); |
107 | bool commitableAfter; |
108 | if (range.isTokenRange()) |
109 | commitableAfter = insertAfterToken(range.getEnd(), after); |
110 | else |
111 | commitableAfter = insert(range.getEnd(), 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(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) { |
123 | IsCommitable = false; |
124 | return false; |
125 | } |
126 | |
127 | addRemove(range.getBegin(), Offs, Len); |
128 | addInsert(range.getBegin(), Offs, text, 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, OuterBegin, OuterLen)) { |
137 | IsCommitable = false; |
138 | return false; |
139 | } |
140 | |
141 | FileOffset InnerBegin; |
142 | unsigned InnerLen; |
143 | if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) { |
144 | IsCommitable = false; |
145 | return false; |
146 | } |
147 | |
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; |
155 | return false; |
156 | } |
157 | |
158 | addRemove(range.getBegin(), |
159 | OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset()); |
160 | addRemove(replacementRange.getEnd(), |
161 | InnerEnd, 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, replacementText, Offs, Len)) { |
173 | IsCommitable = false; |
174 | return false; |
175 | } |
176 | |
177 | addRemove(loc, Offs, Len); |
178 | addInsert(loc, Offs, text, 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(StrAlloc); |
192 | data.BeforePrev = beforePreviousInsertions; |
193 | CachedEdits.push_back(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(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(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, &loc); |
231 | |
232 | const SourceManager &SM = SourceMgr; |
233 | loc = SM.getTopMacroCallerLoc(loc); |
234 | |
235 | if (loc.isMacroID()) |
236 | if (!isAtStartOfMacroExpansion(loc, &loc)) |
237 | return false; |
238 | |
239 | if (SM.isInSystemHeader(loc)) |
240 | return false; |
241 | |
242 | std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc); |
243 | if (locInfo.first.isInvalid()) |
244 | return false; |
245 | offs = FileOffset(locInfo.first, locInfo.second); |
246 | return canInsertInOffset(loc, 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); |
256 | unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts); |
257 | AfterLoc = loc.getLocWithOffset(tokLen); |
258 | |
259 | if (loc.isMacroID()) |
260 | isAtEndOfMacroExpansion(loc, &loc); |
261 | |
262 | const SourceManager &SM = SourceMgr; |
263 | loc = SM.getTopMacroCallerLoc(loc); |
264 | |
265 | if (loc.isMacroID()) |
266 | if (!isAtEndOfMacroExpansion(loc, &loc)) |
267 | return false; |
268 | |
269 | if (SM.isInSystemHeader(loc)) |
270 | return false; |
271 | |
272 | loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts); |
273 | if (loc.isInvalid()) |
274 | return false; |
275 | |
276 | std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc); |
277 | if (locInfo.first.isInvalid()) |
278 | return false; |
279 | offs = FileOffset(locInfo.first, locInfo.second); |
280 | return canInsertInOffset(loc, 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(act.Length)) |
288 | return false; |
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, 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(range.getBegin()) || |
306 | SM.isInSystemHeader(range.getEnd())) |
307 | return false; |
308 | |
309 | if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange())) |
310 | return false; |
311 | |
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) |
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)) |
328 | return false; |
329 | |
330 | |
331 | bool invalidTemp = false; |
332 | StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp); |
333 | if (invalidTemp) |
334 | return false; |
335 | |
336 | Len = text.size(); |
337 | return file.substr(Offs.getOffset()).startswith(text); |
338 | } |
339 | |
340 | bool Commit::isAtStartOfMacroExpansion(SourceLocation loc, |
341 | SourceLocation *MacroBegin) const { |
342 | return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin); |
343 | } |
344 | |
345 | bool Commit::isAtEndOfMacroExpansion(SourceLocation loc, |
346 | SourceLocation *MacroEnd) const { |
347 | return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd); |
348 | } |
349 | |