Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
14 | pmbaty | 1 | //===- Replacement.h - Framework for clang refactoring tools ----*- C++ -*-===// |
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 | // Classes supporting refactorings that span multiple translation units. |
||
10 | // While single translation unit refactorings are supported via the Rewriter, |
||
11 | // when refactoring multiple translation units changes must be stored in a |
||
12 | // SourceManager independent form, duplicate changes need to be removed, and |
||
13 | // all changes must be applied at once at the end of the refactoring so that |
||
14 | // the code is always parseable. |
||
15 | // |
||
16 | //===----------------------------------------------------------------------===// |
||
17 | |||
18 | #ifndef LLVM_CLANG_TOOLING_CORE_REPLACEMENT_H |
||
19 | #define LLVM_CLANG_TOOLING_CORE_REPLACEMENT_H |
||
20 | |||
21 | #include "clang/Basic/LangOptions.h" |
||
22 | #include "clang/Basic/SourceLocation.h" |
||
23 | #include "llvm/ADT/StringRef.h" |
||
24 | #include "llvm/Support/Compiler.h" |
||
25 | #include "llvm/Support/Error.h" |
||
26 | #include "llvm/Support/raw_ostream.h" |
||
27 | #include <map> |
||
28 | #include <optional> |
||
29 | #include <set> |
||
30 | #include <string> |
||
31 | #include <system_error> |
||
32 | #include <utility> |
||
33 | #include <vector> |
||
34 | |||
35 | namespace clang { |
||
36 | |||
37 | class FileManager; |
||
38 | class Rewriter; |
||
39 | class SourceManager; |
||
40 | |||
41 | namespace tooling { |
||
42 | |||
43 | /// A source range independent of the \c SourceManager. |
||
44 | class Range { |
||
45 | public: |
||
46 | Range() = default; |
||
47 | Range(unsigned Offset, unsigned Length) : Offset(Offset), Length(Length) {} |
||
48 | |||
49 | /// Accessors. |
||
50 | /// @{ |
||
51 | unsigned getOffset() const { return Offset; } |
||
52 | unsigned getLength() const { return Length; } |
||
53 | /// @} |
||
54 | |||
55 | /// \name Range Predicates |
||
56 | /// @{ |
||
57 | /// Whether this range overlaps with \p RHS or not. |
||
58 | bool overlapsWith(Range RHS) const { |
||
59 | return Offset + Length > RHS.Offset && Offset < RHS.Offset + RHS.Length; |
||
60 | } |
||
61 | |||
62 | /// Whether this range contains \p RHS or not. |
||
63 | bool contains(Range RHS) const { |
||
64 | return RHS.Offset >= Offset && |
||
65 | (RHS.Offset + RHS.Length) <= (Offset + Length); |
||
66 | } |
||
67 | |||
68 | /// Whether this range equals to \p RHS or not. |
||
69 | bool operator==(const Range &RHS) const { |
||
70 | return Offset == RHS.getOffset() && Length == RHS.getLength(); |
||
71 | } |
||
72 | /// @} |
||
73 | |||
74 | private: |
||
75 | unsigned Offset = 0; |
||
76 | unsigned Length = 0; |
||
77 | }; |
||
78 | |||
79 | /// A text replacement. |
||
80 | /// |
||
81 | /// Represents a SourceManager independent replacement of a range of text in a |
||
82 | /// specific file. |
||
83 | class Replacement { |
||
84 | public: |
||
85 | /// Creates an invalid (not applicable) replacement. |
||
86 | Replacement(); |
||
87 | |||
88 | /// Creates a replacement of the range [Offset, Offset+Length) in |
||
89 | /// FilePath with ReplacementText. |
||
90 | /// |
||
91 | /// \param FilePath A source file accessible via a SourceManager. |
||
92 | /// \param Offset The byte offset of the start of the range in the file. |
||
93 | /// \param Length The length of the range in bytes. |
||
94 | Replacement(StringRef FilePath, unsigned Offset, unsigned Length, |
||
95 | StringRef ReplacementText); |
||
96 | |||
97 | /// Creates a Replacement of the range [Start, Start+Length) with |
||
98 | /// ReplacementText. |
||
99 | Replacement(const SourceManager &Sources, SourceLocation Start, |
||
100 | unsigned Length, StringRef ReplacementText); |
||
101 | |||
102 | /// Creates a Replacement of the given range with ReplacementText. |
||
103 | Replacement(const SourceManager &Sources, const CharSourceRange &Range, |
||
104 | StringRef ReplacementText, |
||
105 | const LangOptions &LangOpts = LangOptions()); |
||
106 | |||
107 | /// Creates a Replacement of the node with ReplacementText. |
||
108 | template <typename Node> |
||
109 | Replacement(const SourceManager &Sources, const Node &NodeToReplace, |
||
110 | StringRef ReplacementText, |
||
111 | const LangOptions &LangOpts = LangOptions()); |
||
112 | |||
113 | /// Returns whether this replacement can be applied to a file. |
||
114 | /// |
||
115 | /// Only replacements that are in a valid file can be applied. |
||
116 | bool isApplicable() const; |
||
117 | |||
118 | /// Accessors. |
||
119 | /// @{ |
||
120 | StringRef getFilePath() const { return FilePath; } |
||
121 | unsigned getOffset() const { return ReplacementRange.getOffset(); } |
||
122 | unsigned getLength() const { return ReplacementRange.getLength(); } |
||
123 | StringRef getReplacementText() const { return ReplacementText; } |
||
124 | /// @} |
||
125 | |||
126 | /// Applies the replacement on the Rewriter. |
||
127 | bool apply(Rewriter &Rewrite) const; |
||
128 | |||
129 | /// Returns a human readable string representation. |
||
130 | std::string toString() const; |
||
131 | |||
132 | private: |
||
133 | void setFromSourceLocation(const SourceManager &Sources, SourceLocation Start, |
||
134 | unsigned Length, StringRef ReplacementText); |
||
135 | void setFromSourceRange(const SourceManager &Sources, |
||
136 | const CharSourceRange &Range, |
||
137 | StringRef ReplacementText, |
||
138 | const LangOptions &LangOpts); |
||
139 | |||
140 | std::string FilePath; |
||
141 | Range ReplacementRange; |
||
142 | std::string ReplacementText; |
||
143 | }; |
||
144 | |||
145 | enum class replacement_error { |
||
146 | fail_to_apply = 0, |
||
147 | wrong_file_path, |
||
148 | overlap_conflict, |
||
149 | insert_conflict, |
||
150 | }; |
||
151 | |||
152 | /// Carries extra error information in replacement-related llvm::Error, |
||
153 | /// e.g. fail applying replacements and replacements conflict. |
||
154 | class ReplacementError : public llvm::ErrorInfo<ReplacementError> { |
||
155 | public: |
||
156 | ReplacementError(replacement_error Err) : Err(Err) {} |
||
157 | |||
158 | /// Constructs an error related to an existing replacement. |
||
159 | ReplacementError(replacement_error Err, Replacement Existing) |
||
160 | : Err(Err), ExistingReplacement(std::move(Existing)) {} |
||
161 | |||
162 | /// Constructs an error related to a new replacement and an existing |
||
163 | /// replacement in a set of replacements. |
||
164 | ReplacementError(replacement_error Err, Replacement New, Replacement Existing) |
||
165 | : Err(Err), NewReplacement(std::move(New)), |
||
166 | ExistingReplacement(std::move(Existing)) {} |
||
167 | |||
168 | std::string message() const override; |
||
169 | |||
170 | void log(raw_ostream &OS) const override { OS << message(); } |
||
171 | |||
172 | replacement_error get() const { return Err; } |
||
173 | |||
174 | static char ID; |
||
175 | |||
176 | const std::optional<Replacement> &getNewReplacement() const { |
||
177 | return NewReplacement; |
||
178 | } |
||
179 | |||
180 | const std::optional<Replacement> &getExistingReplacement() const { |
||
181 | return ExistingReplacement; |
||
182 | } |
||
183 | |||
184 | private: |
||
185 | // Users are not expected to use error_code. |
||
186 | std::error_code convertToErrorCode() const override { |
||
187 | return llvm::inconvertibleErrorCode(); |
||
188 | } |
||
189 | |||
190 | replacement_error Err; |
||
191 | |||
192 | // A new replacement, which is to expected be added into a set of |
||
193 | // replacements, that is causing problem. |
||
194 | std::optional<Replacement> NewReplacement; |
||
195 | |||
196 | // An existing replacement in a replacements set that is causing problem. |
||
197 | std::optional<Replacement> ExistingReplacement; |
||
198 | }; |
||
199 | |||
200 | /// Less-than operator between two Replacements. |
||
201 | bool operator<(const Replacement &LHS, const Replacement &RHS); |
||
202 | |||
203 | /// Equal-to operator between two Replacements. |
||
204 | bool operator==(const Replacement &LHS, const Replacement &RHS); |
||
205 | inline bool operator!=(const Replacement &LHS, const Replacement &RHS) { |
||
206 | return !(LHS == RHS); |
||
207 | } |
||
208 | |||
209 | /// Maintains a set of replacements that are conflict-free. |
||
210 | /// Two replacements are considered conflicts if they overlap or have the same |
||
211 | /// offset (i.e. order-dependent). |
||
212 | class Replacements { |
||
213 | private: |
||
214 | using ReplacementsImpl = std::set<Replacement>; |
||
215 | |||
216 | public: |
||
217 | using const_iterator = ReplacementsImpl::const_iterator; |
||
218 | using const_reverse_iterator = ReplacementsImpl::const_reverse_iterator; |
||
219 | |||
220 | Replacements() = default; |
||
221 | |||
222 | explicit Replacements(const Replacement &R) { Replaces.insert(R); } |
||
223 | |||
224 | /// Adds a new replacement \p R to the current set of replacements. |
||
225 | /// \p R must have the same file path as all existing replacements. |
||
226 | /// Returns `success` if the replacement is successfully inserted; otherwise, |
||
227 | /// it returns an llvm::Error, i.e. there is a conflict between R and the |
||
228 | /// existing replacements (i.e. they are order-dependent) or R's file path is |
||
229 | /// different from the filepath of existing replacements. Callers must |
||
230 | /// explicitly check the Error returned, and the returned error can be |
||
231 | /// converted to a string message with `llvm::toString()`. This prevents users |
||
232 | /// from adding order-dependent replacements. To control the order in which |
||
233 | /// order-dependent replacements are applied, use merge({R}) with R referring |
||
234 | /// to the changed code after applying all existing replacements. |
||
235 | /// Two replacements A and B are considered order-independent if applying them |
||
236 | /// in either order produces the same result. Note that the range of the |
||
237 | /// replacement that is applied later still refers to the original code. |
||
238 | /// These include (but not restricted to) replacements that: |
||
239 | /// - don't overlap (being directly adjacent is fine) and |
||
240 | /// - are overlapping deletions. |
||
241 | /// - are insertions at the same offset and applying them in either order |
||
242 | /// has the same effect, i.e. X + Y = Y + X when inserting X and Y |
||
243 | /// respectively. |
||
244 | /// - are identical replacements, i.e. applying the same replacement twice |
||
245 | /// is equivalent to applying it once. |
||
246 | /// Examples: |
||
247 | /// 1. Replacement A(0, 0, "a") and B(0, 0, "aa") are order-independent since |
||
248 | /// applying them in either order gives replacement (0, 0, "aaa"). |
||
249 | /// However, A(0, 0, "a") and B(0, 0, "b") are order-dependent since |
||
250 | /// applying A first gives (0, 0, "ab") while applying B first gives (B, A, |
||
251 | /// "ba"). |
||
252 | /// 2. Replacement A(0, 2, "123") and B(0, 2, "123") are order-independent |
||
253 | /// since applying them in either order gives (0, 2, "123"). |
||
254 | /// 3. Replacement A(0, 3, "123") and B(2, 3, "321") are order-independent |
||
255 | /// since either order gives (0, 5, "12321"). |
||
256 | /// 4. Replacement A(0, 3, "ab") and B(0, 3, "ab") are order-independent since |
||
257 | /// applying the same replacement twice is equivalent to applying it once. |
||
258 | /// Replacements with offset UINT_MAX are special - we do not detect conflicts |
||
259 | /// for such replacements since users may add them intentionally as a special |
||
260 | /// category of replacements. |
||
261 | llvm::Error add(const Replacement &R); |
||
262 | |||
263 | /// Merges \p Replaces into the current replacements. \p Replaces |
||
264 | /// refers to code after applying the current replacements. |
||
265 | [[nodiscard]] Replacements merge(const Replacements &Replaces) const; |
||
266 | |||
267 | // Returns the affected ranges in the changed code. |
||
268 | std::vector<Range> getAffectedRanges() const; |
||
269 | |||
270 | // Returns the new offset in the code after replacements being applied. |
||
271 | // Note that if there is an insertion at Offset in the current replacements, |
||
272 | // \p Offset will be shifted to Offset + Length in inserted text. |
||
273 | unsigned getShiftedCodePosition(unsigned Position) const; |
||
274 | |||
275 | unsigned size() const { return Replaces.size(); } |
||
276 | |||
277 | void clear() { Replaces.clear(); } |
||
278 | |||
279 | bool empty() const { return Replaces.empty(); } |
||
280 | |||
281 | const_iterator begin() const { return Replaces.begin(); } |
||
282 | |||
283 | const_iterator end() const { return Replaces.end(); } |
||
284 | |||
285 | const_reverse_iterator rbegin() const { return Replaces.rbegin(); } |
||
286 | |||
287 | const_reverse_iterator rend() const { return Replaces.rend(); } |
||
288 | |||
289 | bool operator==(const Replacements &RHS) const { |
||
290 | return Replaces == RHS.Replaces; |
||
291 | } |
||
292 | |||
293 | private: |
||
294 | Replacements(const_iterator Begin, const_iterator End) |
||
295 | : Replaces(Begin, End) {} |
||
296 | |||
297 | // Returns `R` with new range that refers to code after `Replaces` being |
||
298 | // applied. |
||
299 | Replacement getReplacementInChangedCode(const Replacement &R) const; |
||
300 | |||
301 | // Returns a set of replacements that is equivalent to the current |
||
302 | // replacements by merging all adjacent replacements. Two sets of replacements |
||
303 | // are considered equivalent if they have the same effect when they are |
||
304 | // applied. |
||
305 | Replacements getCanonicalReplacements() const; |
||
306 | |||
307 | // If `R` and all existing replacements are order-independent, then merge it |
||
308 | // with `Replaces` and returns the merged replacements; otherwise, returns an |
||
309 | // error. |
||
310 | llvm::Expected<Replacements> |
||
311 | mergeIfOrderIndependent(const Replacement &R) const; |
||
312 | |||
313 | ReplacementsImpl Replaces; |
||
314 | }; |
||
315 | |||
316 | /// Apply all replacements in \p Replaces to the Rewriter \p Rewrite. |
||
317 | /// |
||
318 | /// Replacement applications happen independently of the success of |
||
319 | /// other applications. |
||
320 | /// |
||
321 | /// \returns true if all replacements apply. false otherwise. |
||
322 | bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite); |
||
323 | |||
324 | /// Applies all replacements in \p Replaces to \p Code. |
||
325 | /// |
||
326 | /// This completely ignores the path stored in each replacement. If all |
||
327 | /// replacements are applied successfully, this returns the code with |
||
328 | /// replacements applied; otherwise, an llvm::Error carrying llvm::StringError |
||
329 | /// is returned (the Error message can be converted to string using |
||
330 | /// `llvm::toString()` and 'std::error_code` in the `Error` should be ignored). |
||
331 | llvm::Expected<std::string> applyAllReplacements(StringRef Code, |
||
332 | const Replacements &Replaces); |
||
333 | |||
334 | /// Collection of Replacements generated from a single translation unit. |
||
335 | struct TranslationUnitReplacements { |
||
336 | /// Name of the main source for the translation unit. |
||
337 | std::string MainSourceFile; |
||
338 | |||
339 | std::vector<Replacement> Replacements; |
||
340 | }; |
||
341 | |||
342 | /// Calculates the new ranges after \p Replaces are applied. These |
||
343 | /// include both the original \p Ranges and the affected ranges of \p Replaces |
||
344 | /// in the new code. |
||
345 | /// |
||
346 | /// \pre Replacements must be for the same file. |
||
347 | /// |
||
348 | /// \return The new ranges after \p Replaces are applied. The new ranges will be |
||
349 | /// sorted and non-overlapping. |
||
350 | std::vector<Range> |
||
351 | calculateRangesAfterReplacements(const Replacements &Replaces, |
||
352 | const std::vector<Range> &Ranges); |
||
353 | |||
354 | /// If there are multiple <File, Replacements> pairs with the same file |
||
355 | /// entry, we only keep one pair and discard the rest. |
||
356 | /// If a file does not exist, its corresponding replacements will be ignored. |
||
357 | std::map<std::string, Replacements> groupReplacementsByFile( |
||
358 | FileManager &FileMgr, |
||
359 | const std::map<std::string, Replacements> &FileToReplaces); |
||
360 | |||
361 | template <typename Node> |
||
362 | Replacement::Replacement(const SourceManager &Sources, |
||
363 | const Node &NodeToReplace, StringRef ReplacementText, |
||
364 | const LangOptions &LangOpts) { |
||
365 | const CharSourceRange Range = |
||
366 | CharSourceRange::getTokenRange(NodeToReplace->getSourceRange()); |
||
367 | setFromSourceRange(Sources, Range, ReplacementText, LangOpts); |
||
368 | } |
||
369 | |||
370 | } // namespace tooling |
||
371 | |||
372 | } // namespace clang |
||
373 | |||
374 | #endif // LLVM_CLANG_TOOLING_CORE_REPLACEMENT_H |