1 //===--- AtomicChange.h - AtomicChange class --------------------*- 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 //  This file defines AtomicChange which is used to create a set of source
10 //  changes, e.g. replacements and header insertions.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #ifndef LLVM_CLANG_TOOLING_REFACTORING_ATOMICCHANGE_H
15 #define LLVM_CLANG_TOOLING_REFACTORING_ATOMICCHANGE_H
16 
17 #include "clang/Basic/SourceManager.h"
18 #include "clang/Format/Format.h"
19 #include "clang/Tooling/Core/Replacement.h"
20 #include "llvm/ADT/Any.h"
21 #include "llvm/ADT/StringRef.h"
22 #include "llvm/Support/Error.h"
23 
24 namespace clang {
25 namespace tooling {
26 
27 /// An atomic change is used to create and group a set of source edits,
28 /// e.g. replacements or header insertions. Edits in an AtomicChange should be
29 /// related, e.g. replacements for the same type reference and the corresponding
30 /// header insertion/deletion.
31 ///
32 /// An AtomicChange is uniquely identified by a key and will either be fully
33 /// applied or not applied at all.
34 ///
35 /// Calling setError on an AtomicChange stores the error message and marks it as
36 /// bad, i.e. none of its source edits will be applied.
37 class AtomicChange {
38 public:
39   /// Creates an atomic change around \p KeyPosition with the key being a
40   /// concatenation of the file name and the offset of \p KeyPosition.
41   /// \p KeyPosition should be the location of the key syntactical element that
42   /// is being changed, e.g. the call to a refactored method.
43   AtomicChange(const SourceManager &SM, SourceLocation KeyPosition);
44 
45   AtomicChange(const SourceManager &SM, SourceLocation KeyPosition,
46                llvm::Any Metadata);
47 
48   /// Creates an atomic change for \p FilePath with a customized key.
AtomicChange(llvm::StringRef FilePath,llvm::StringRef Key)49   AtomicChange(llvm::StringRef FilePath, llvm::StringRef Key)
50       : Key(Key), FilePath(FilePath) {}
51 
52   AtomicChange(AtomicChange &&) = default;
53   AtomicChange(const AtomicChange &) = default;
54 
55   AtomicChange &operator=(AtomicChange &&) = default;
56   AtomicChange &operator=(const AtomicChange &) = default;
57 
58   bool operator==(const AtomicChange &Other) const;
59 
60   /// Returns the atomic change as a YAML string.
61   std::string toYAMLString();
62 
63   /// Converts a YAML-encoded automic change to AtomicChange.
64   static AtomicChange convertFromYAML(llvm::StringRef YAMLContent);
65 
66   /// Returns the key of this change, which is a concatenation of the
67   /// file name and offset of the key position.
getKey()68   const std::string &getKey() const { return Key; }
69 
70   /// Returns the path of the file containing this atomic change.
getFilePath()71   const std::string &getFilePath() const { return FilePath; }
72 
73   /// If this change could not be created successfully, e.g. because of
74   /// conflicts among replacements, use this to set an error description.
75   /// Thereby, places that cannot be fixed automatically can be gathered when
76   /// applying changes.
setError(llvm::StringRef Error)77   void setError(llvm::StringRef Error) { this->Error = std::string(Error); }
78 
79   /// Returns whether an error has been set on this list.
hasError()80   bool hasError() const { return !Error.empty(); }
81 
82   /// Returns the error message or an empty string if it does not exist.
getError()83   const std::string &getError() const { return Error; }
84 
85   /// Adds a replacement that replaces the given Range with
86   /// ReplacementText.
87   /// \returns An llvm::Error carrying ReplacementError on error.
88   llvm::Error replace(const SourceManager &SM, const CharSourceRange &Range,
89                       llvm::StringRef ReplacementText);
90 
91   /// Adds a replacement that replaces range [Loc, Loc+Length) with
92   /// \p Text.
93   /// \returns An llvm::Error carrying ReplacementError on error.
94   llvm::Error replace(const SourceManager &SM, SourceLocation Loc,
95                       unsigned Length, llvm::StringRef Text);
96 
97   /// Adds a replacement that inserts \p Text at \p Loc. If this
98   /// insertion conflicts with an existing insertion (at the same position),
99   /// this will be inserted before/after the existing insertion depending on
100   /// \p InsertAfter. Users should use `replace` with `Length=0` instead if they
101   /// do not want conflict resolving by default. If the conflicting replacement
102   /// is not an insertion, an error is returned.
103   ///
104   /// \returns An llvm::Error carrying ReplacementError on error.
105   llvm::Error insert(const SourceManager &SM, SourceLocation Loc,
106                      llvm::StringRef Text, bool InsertAfter = true);
107 
108   /// Adds a header into the file that contains the key position.
109   /// Header can be in angle brackets or double quotation marks. By default
110   /// (header is not quoted), header will be surrounded with double quotes.
111   void addHeader(llvm::StringRef Header);
112 
113   /// Removes a header from the file that contains the key position.
114   void removeHeader(llvm::StringRef Header);
115 
116   /// Returns a const reference to existing replacements.
getReplacements()117   const Replacements &getReplacements() const { return Replaces; }
118 
getReplacements()119   Replacements &getReplacements() { return Replaces; }
120 
getInsertedHeaders()121   llvm::ArrayRef<std::string> getInsertedHeaders() const {
122     return InsertedHeaders;
123   }
124 
getRemovedHeaders()125   llvm::ArrayRef<std::string> getRemovedHeaders() const {
126     return RemovedHeaders;
127   }
128 
getMetadata()129   const llvm::Any &getMetadata() const { return Metadata; }
130 
131 private:
AtomicChange()132   AtomicChange() {}
133 
134   AtomicChange(std::string Key, std::string FilePath, std::string Error,
135                std::vector<std::string> InsertedHeaders,
136                std::vector<std::string> RemovedHeaders,
137                clang::tooling::Replacements Replaces);
138 
139   // This uniquely identifies an AtomicChange.
140   std::string Key;
141   std::string FilePath;
142   std::string Error;
143   std::vector<std::string> InsertedHeaders;
144   std::vector<std::string> RemovedHeaders;
145   tooling::Replacements Replaces;
146 
147   // This field stores metadata which is ignored for the purposes of applying
148   // edits to source, but may be useful for other consumers of AtomicChanges. In
149   // particular, consumers can use this to direct how they want to consume each
150   // edit.
151   llvm::Any Metadata;
152 };
153 
154 using AtomicChanges = std::vector<AtomicChange>;
155 
156 // Defines specs for applying changes.
157 struct ApplyChangesSpec {
158   // If true, cleans up redundant/erroneous code around changed code with
159   // clang-format's cleanup functionality, e.g. redundant commas around deleted
160   // parameter or empty namespaces introduced by deletions.
161   bool Cleanup = true;
162 
163   format::FormatStyle Style = format::getNoStyle();
164 
165   // Options for selectively formatting changes with clang-format:
166   // kAll: Format all changed lines.
167   // kNone: Don't format anything.
168   // kViolations: Format lines exceeding the `ColumnLimit` in `Style`.
169   enum FormatOption { kAll, kNone, kViolations };
170 
171   FormatOption Format = kNone;
172 };
173 
174 /// Applies all AtomicChanges in \p Changes to the \p Code.
175 ///
176 /// This completely ignores the file path in each change and replaces them with
177 /// \p FilePath, i.e. callers are responsible for ensuring all changes are for
178 /// the same file.
179 ///
180 /// \returns The changed code if all changes are applied successfully;
181 /// otherwise, an llvm::Error carrying llvm::StringError is returned (the Error
182 /// message can be converted to string with `llvm::toString()` and the
183 /// error_code should be ignored).
184 llvm::Expected<std::string>
185 applyAtomicChanges(llvm::StringRef FilePath, llvm::StringRef Code,
186                    llvm::ArrayRef<AtomicChange> Changes,
187                    const ApplyChangesSpec &Spec);
188 
189 } // end namespace tooling
190 } // end namespace clang
191 
192 #endif // LLVM_CLANG_TOOLING_REFACTORING_ATOMICCHANGE_H
193