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