clang 19.0.0git
Commit.cpp
Go to the documentation of this file.
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"
15#include "clang/Lex/Lexer.h"
17#include "llvm/ADT/StringRef.h"
18#include <cassert>
19#include <utility>
20
21using namespace clang;
22using namespace edit;
23
25 SourceLocation Loc = SM.getLocForStartOfFile(Offset.getFID());
27 assert(Loc.isFileID());
28 return Loc;
29}
30
32 SourceLocation Loc = getFileLocation(SM);
33 return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
34}
35
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
44 : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()),
45 PPRec(Editor.getPPCondDirectiveRecord()),
46 Editor(&Editor) {}
47
48bool 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
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
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
103bool Commit::insertWrap(StringRef before, CharSourceRange range,
104 StringRef after) {
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);
110 else
111 commitableAfter = insert(range.getEnd(), after);
112
113 return commitableBefore && commitableAfter;
114}
115
116bool 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
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
165bool 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
182void 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
196void 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
212void 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
225bool 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
249bool 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
283bool 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.
289 }
290
291 if (!Editor)
292 return true;
293 return Editor->canInsertInOffset(OrigLoc, Offs);
294}
295
296bool 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
323bool 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 // Try to load the file buffer.
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()).starts_with(text);
338}
339
340bool Commit::isAtStartOfMacroExpansion(SourceLocation loc,
341 SourceLocation *MacroBegin) const {
342 return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin);
343}
344
345bool Commit::isAtEndOfMacroExpansion(SourceLocation loc,
346 SourceLocation *MacroEnd) const {
347 return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd);
348}
#define SM(sm)
Definition: Cuda.cpp:82
Forward-declares and imports various common LLVM datatypes that clang wants to use unqualified.
Defines the clang::SourceLocation class and associated facilities.
Defines the SourceManager interface.
Represents a character-granular source range.
static CharSourceRange getCharRange(SourceRange R)
SourceLocation getEnd() const
static bool isAtStartOfMacroExpansion(SourceLocation loc, const SourceManager &SM, const LangOptions &LangOpts, SourceLocation *MacroBegin=nullptr)
Returns true if the given MacroID location points at the first token of the macro expansion.
Definition: Lexer.cpp:872
static bool isAtEndOfMacroExpansion(SourceLocation loc, const SourceManager &SM, const LangOptions &LangOpts, SourceLocation *MacroEnd=nullptr)
Returns true if the given MacroID location points at the last token of the macro expansion.
Definition: Lexer.cpp:894
static CharSourceRange makeFileCharRange(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
Accepts a range and returns a character range with file locations.
Definition: Lexer.cpp:955
static unsigned MeasureTokenLength(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
MeasureTokenLength - Relex the token at the specified location and return its length in bytes in the ...
Definition: Lexer.cpp:499
static SourceLocation getLocForEndOfToken(SourceLocation Loc, unsigned Offset, const SourceManager &SM, const LangOptions &LangOpts)
Computes the source location just past the end of the token at this source location.
Definition: Lexer.cpp:850
bool areInDifferentConditionalDirectiveRegion(SourceLocation LHS, SourceLocation RHS) const
Returns true if the given locations are in different regions, separated by conditional directive bloc...
bool rangeIntersectsConditionalDirective(SourceRange Range) const
Returns true if the given range intersects with a conditional directive.
Encodes a location in the source.
SourceLocation getLocWithOffset(IntTy Offset) const
Return a source location with the specified offset from this SourceLocation.
This class handles loading and caching of source files into memory.
StringRef getBufferData(FileID FID, bool *Invalid=nullptr) const
Return a StringRef to the source buffer data for the specified FileID.
SourceLocation getSpellingLoc(SourceLocation Loc) const
Given a SourceLocation object, return the spelling location referenced by the ID.
bool replaceWithInner(CharSourceRange range, CharSourceRange innerRange)
Definition: Commit.cpp:132
bool insertWrap(StringRef before, CharSourceRange range, StringRef after)
Definition: Commit.cpp:103
bool insertFromRange(SourceLocation loc, CharSourceRange range, bool afterToken=false, bool beforePreviousInsertions=false)
Definition: Commit.cpp:64
bool insert(SourceLocation loc, StringRef text, bool afterToken=false, bool beforePreviousInsertions=false)
Definition: Commit.cpp:48
Commit(EditedSource &Editor)
Definition: Commit.cpp:43
bool insertAfterToken(SourceLocation loc, StringRef text, bool beforePreviousInsertions=false)
Definition: Commit.h:73
bool remove(CharSourceRange range)
Definition: Commit.cpp:91
bool replace(CharSourceRange range, StringRef text)
Definition: Commit.cpp:116
bool replaceText(SourceLocation loc, StringRef text, StringRef replacementText)
Definition: Commit.cpp:165
bool canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs)
FileOffset getWithOffset(unsigned offset) const
Definition: FileOffset.h:31
unsigned getOffset() const
Definition: FileOffset.h:29
FileID getFID() const
Definition: FileOffset.h:28
RangeSelector range(RangeSelector Begin, RangeSelector End)
DEPRECATED. Use enclose.
Definition: RangeSelector.h:41
EditGenerator edit(ASTEdit E)
Generates a single (specified) edit.
Definition: RewriteRule.cpp:84
The JSON file list parser is used to communicate input to InstallAPI.
SourceLocation getFileLocation(SourceManager &SM) const
Definition: Commit.cpp:24
CharSourceRange getFileRange(SourceManager &SM) const
Definition: Commit.cpp:31
CharSourceRange getInsertFromRange(SourceManager &SM) const
Definition: Commit.cpp:36