blob: b03fb50b383ea940cc34dbb59c83bcabb43bd980 [file] [log] [blame]
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +00001//===--- Refactoring.cpp - Framework for clang refactoring tools ----------===//
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// Implements tools to support refactorings.
11//
12//===----------------------------------------------------------------------===//
13
Douglas Gregor02c23eb2012-10-23 22:26:28 +000014#include "clang/Basic/DiagnosticOptions.h"
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000015#include "clang/Basic/FileManager.h"
16#include "clang/Basic/SourceManager.h"
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000017#include "clang/Frontend/TextDiagnosticPrinter.h"
18#include "clang/Lex/Lexer.h"
Ted Kremenek305c6132012-09-01 05:09:24 +000019#include "clang/Rewrite/Core/Rewriter.h"
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000020#include "clang/Tooling/Refactoring.h"
21#include "llvm/Support/raw_os_ostream.h"
22
23namespace clang {
24namespace tooling {
25
26static const char * const InvalidLocation = "";
27
28Replacement::Replacement()
Daniel Jasper8a999452013-05-16 10:40:07 +000029 : FilePath(InvalidLocation) {}
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000030
Daniel Jasper8a999452013-05-16 10:40:07 +000031Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length,
32 StringRef ReplacementText)
33 : FilePath(FilePath), ReplacementRange(Offset, Length),
34 ReplacementText(ReplacementText) {}
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000035
36Replacement::Replacement(SourceManager &Sources, SourceLocation Start,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +000037 unsigned Length, StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000038 setFromSourceLocation(Sources, Start, Length, ReplacementText);
39}
40
41Replacement::Replacement(SourceManager &Sources, const CharSourceRange &Range,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +000042 StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000043 setFromSourceRange(Sources, Range, ReplacementText);
44}
45
46bool Replacement::isApplicable() const {
47 return FilePath != InvalidLocation;
48}
49
50bool Replacement::apply(Rewriter &Rewrite) const {
51 SourceManager &SM = Rewrite.getSourceMgr();
52 const FileEntry *Entry = SM.getFileManager().getFile(FilePath);
53 if (Entry == NULL)
54 return false;
55 FileID ID;
56 // FIXME: Use SM.translateFile directly.
57 SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1);
58 ID = Location.isValid() ?
59 SM.getFileID(Location) :
60 SM.createFileID(Entry, SourceLocation(), SrcMgr::C_User);
61 // FIXME: We cannot check whether Offset + Length is in the file, as
62 // the remapping API is not public in the RewriteBuffer.
63 const SourceLocation Start =
64 SM.getLocForStartOfFile(ID).
Daniel Jasper8a999452013-05-16 10:40:07 +000065 getLocWithOffset(ReplacementRange.getOffset());
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000066 // ReplaceText returns false on success.
67 // ReplaceText only fails if the source location is not a file location, in
68 // which case we already returned false earlier.
Daniel Jasper8a999452013-05-16 10:40:07 +000069 bool RewriteSucceeded = !Rewrite.ReplaceText(
70 Start, ReplacementRange.getLength(), ReplacementText);
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000071 assert(RewriteSucceeded);
72 return RewriteSucceeded;
73}
74
Manuel Klimek5d51e882012-05-30 16:04:29 +000075std::string Replacement::toString() const {
76 std::string result;
77 llvm::raw_string_ostream stream(result);
Daniel Jasper8a999452013-05-16 10:40:07 +000078 stream << FilePath << ": " << ReplacementRange.getOffset() << ":+"
79 << ReplacementRange.getLength() << ":\"" << ReplacementText << "\"";
Manuel Klimek5d51e882012-05-30 16:04:29 +000080 return result;
81}
82
Edwin Vane05e4af02013-08-16 12:18:53 +000083bool operator<(const Replacement &LHS, const Replacement &RHS) {
84 if (LHS.getOffset() != RHS.getOffset())
85 return LHS.getOffset() < RHS.getOffset();
86 if (LHS.getLength() != RHS.getLength())
87 return LHS.getLength() < RHS.getLength();
88 if (LHS.getFilePath() != RHS.getFilePath())
89 return LHS.getFilePath() < RHS.getFilePath();
90 return LHS.getReplacementText() < RHS.getReplacementText();
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000091}
92
Edwin Vane05e4af02013-08-16 12:18:53 +000093bool operator==(const Replacement &LHS, const Replacement &RHS) {
94 return LHS.getOffset() == RHS.getOffset() &&
95 LHS.getLength() == RHS.getLength() &&
96 LHS.getFilePath() == RHS.getFilePath() &&
97 LHS.getReplacementText() == RHS.getReplacementText();
Edwin Vaned5692db2013-08-08 13:31:14 +000098}
99
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000100void Replacement::setFromSourceLocation(SourceManager &Sources,
101 SourceLocation Start, unsigned Length,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +0000102 StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000103 const std::pair<FileID, unsigned> DecomposedLocation =
104 Sources.getDecomposedLoc(Start);
105 const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first);
106 this->FilePath = Entry != NULL ? Entry->getName() : InvalidLocation;
Daniel Jasper8a999452013-05-16 10:40:07 +0000107 this->ReplacementRange = Range(DecomposedLocation.second, Length);
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000108 this->ReplacementText = ReplacementText;
109}
110
111// FIXME: This should go into the Lexer, but we need to figure out how
112// to handle ranges for refactoring in general first - there is no obvious
113// good way how to integrate this into the Lexer yet.
114static int getRangeSize(SourceManager &Sources, const CharSourceRange &Range) {
115 SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin());
116 SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd());
117 std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin);
118 std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd);
119 if (Start.first != End.first) return -1;
120 if (Range.isTokenRange())
121 End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources,
122 LangOptions());
123 return End.second - Start.second;
124}
125
126void Replacement::setFromSourceRange(SourceManager &Sources,
127 const CharSourceRange &Range,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +0000128 StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000129 setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()),
130 getRangeSize(Sources, Range), ReplacementText);
131}
132
David Blaikie76a2ea32013-07-17 18:29:58 +0000133bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000134 bool Result = true;
135 for (Replacements::const_iterator I = Replaces.begin(),
136 E = Replaces.end();
137 I != E; ++I) {
138 if (I->isApplicable()) {
139 Result = I->apply(Rewrite) && Result;
140 } else {
141 Result = false;
142 }
143 }
144 return Result;
145}
146
Edwin Vaneb58cfd92013-08-13 17:38:19 +0000147// FIXME: Remove this function when Replacements is implemented as std::vector
148// instead of std::set.
149bool applyAllReplacements(const std::vector<Replacement> &Replaces,
150 Rewriter &Rewrite) {
151 bool Result = true;
152 for (std::vector<Replacement>::const_iterator I = Replaces.begin(),
153 E = Replaces.end();
154 I != E; ++I) {
155 if (I->isApplicable()) {
156 Result = I->apply(Rewrite) && Result;
157 } else {
158 Result = false;
159 }
160 }
161 return Result;
162}
163
David Blaikie76a2ea32013-07-17 18:29:58 +0000164std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) {
Daniel Jasper8a999452013-05-16 10:40:07 +0000165 FileManager Files((FileSystemOptions()));
166 DiagnosticsEngine Diagnostics(
167 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
168 new DiagnosticOptions);
169 Diagnostics.setClient(new TextDiagnosticPrinter(
170 llvm::outs(), &Diagnostics.getDiagnosticOptions()));
171 SourceManager SourceMgr(Diagnostics, Files);
172 Rewriter Rewrite(SourceMgr, LangOptions());
173 llvm::MemoryBuffer *Buf = llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>");
174 const clang::FileEntry *Entry =
175 Files.getVirtualFile("<stdin>", Buf->getBufferSize(), 0);
176 SourceMgr.overrideFileContents(Entry, Buf);
177 FileID ID =
178 SourceMgr.createFileID(Entry, SourceLocation(), clang::SrcMgr::C_User);
David Blaikie76a2ea32013-07-17 18:29:58 +0000179 for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end();
180 I != E; ++I) {
Daniel Jasper8a999452013-05-16 10:40:07 +0000181 Replacement Replace("<stdin>", I->getOffset(), I->getLength(),
182 I->getReplacementText());
183 if (!Replace.apply(Rewrite))
184 return "";
185 }
186 std::string Result;
187 llvm::raw_string_ostream OS(Result);
188 Rewrite.getEditBuffer(ID).write(OS);
189 OS.flush();
190 return Result;
191}
192
Daniel Jasper6bd3b932013-05-21 12:21:39 +0000193unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) {
194 unsigned NewPosition = Position;
195 for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E;
196 ++I) {
197 if (I->getOffset() >= Position)
198 break;
199 if (I->getOffset() + I->getLength() > Position)
200 NewPosition += I->getOffset() + I->getLength() - Position;
201 NewPosition += I->getReplacementText().size() - I->getLength();
202 }
203 return NewPosition;
204}
205
Edwin Vanea778cde2013-08-27 15:44:26 +0000206// FIXME: Remove this function when Replacements is implemented as std::vector
207// instead of std::set.
208unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces,
209 unsigned Position) {
210 unsigned NewPosition = Position;
211 for (std::vector<Replacement>::const_iterator I = Replaces.begin(),
212 E = Replaces.end();
213 I != E; ++I) {
214 if (I->getOffset() >= Position)
215 break;
216 if (I->getOffset() + I->getLength() > Position)
217 NewPosition += I->getOffset() + I->getLength() - Position;
218 NewPosition += I->getReplacementText().size() - I->getLength();
219 }
220 return NewPosition;
221}
222
Edwin Vaned5692db2013-08-08 13:31:14 +0000223void deduplicate(std::vector<Replacement> &Replaces,
224 std::vector<Range> &Conflicts) {
225 if (Replaces.empty())
226 return;
227
228 // Deduplicate
Edwin Vane05e4af02013-08-16 12:18:53 +0000229 std::sort(Replaces.begin(), Replaces.end());
Edwin Vaned5692db2013-08-08 13:31:14 +0000230 std::vector<Replacement>::iterator End =
231 std::unique(Replaces.begin(), Replaces.end());
232 Replaces.erase(End, Replaces.end());
233
234 // Detect conflicts
235 Range ConflictRange(Replaces.front().getOffset(),
236 Replaces.front().getLength());
237 unsigned ConflictStart = 0;
238 unsigned ConflictLength = 1;
239 for (unsigned i = 1; i < Replaces.size(); ++i) {
240 Range Current(Replaces[i].getOffset(), Replaces[i].getLength());
241 if (ConflictRange.overlapsWith(Current)) {
242 // Extend conflicted range
243 ConflictRange = Range(ConflictRange.getOffset(),
Edwin Vane95f07662013-08-13 16:26:44 +0000244 std::max(ConflictRange.getLength(),
245 Current.getOffset() + Current.getLength() -
246 ConflictRange.getOffset()));
Edwin Vaned5692db2013-08-08 13:31:14 +0000247 ++ConflictLength;
248 } else {
249 if (ConflictLength > 1)
250 Conflicts.push_back(Range(ConflictStart, ConflictLength));
251 ConflictRange = Current;
252 ConflictStart = i;
253 ConflictLength = 1;
254 }
255 }
256
257 if (ConflictLength > 1)
258 Conflicts.push_back(Range(ConflictStart, ConflictLength));
259}
260
261
Edwin Vaned088a5f2013-01-11 17:04:55 +0000262RefactoringTool::RefactoringTool(const CompilationDatabase &Compilations,
263 ArrayRef<std::string> SourcePaths)
264 : ClangTool(Compilations, SourcePaths) {}
265
266Replacements &RefactoringTool::getReplacements() { return Replace; }
267
268int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) {
269 if (int Result = run(ActionFactory)) {
270 return Result;
271 }
272
273 LangOptions DefaultLangOptions;
274 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
275 TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
276 DiagnosticsEngine Diagnostics(
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +0000277 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()),
Edwin Vaned088a5f2013-01-11 17:04:55 +0000278 &*DiagOpts, &DiagnosticPrinter, false);
279 SourceManager Sources(Diagnostics, getFiles());
280 Rewriter Rewrite(Sources, DefaultLangOptions);
281
282 if (!applyAllReplacements(Rewrite)) {
283 llvm::errs() << "Skipped some replacements.\n";
284 }
285
286 return saveRewrittenFiles(Rewrite);
287}
288
289bool RefactoringTool::applyAllReplacements(Rewriter &Rewrite) {
290 return tooling::applyAllReplacements(Replace, Rewrite);
291}
292
293int RefactoringTool::saveRewrittenFiles(Rewriter &Rewrite) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000294 for (Rewriter::buffer_iterator I = Rewrite.buffer_begin(),
295 E = Rewrite.buffer_end();
296 I != E; ++I) {
297 // FIXME: This code is copied from the FixItRewriter.cpp - I think it should
298 // go into directly into Rewriter (there we also have the Diagnostics to
299 // handle the error cases better).
300 const FileEntry *Entry =
301 Rewrite.getSourceMgr().getFileEntryForID(I->first);
302 std::string ErrorInfo;
Rafael Espindolad965f952013-07-16 19:44:23 +0000303 llvm::raw_fd_ostream FileStream(Entry->getName(), ErrorInfo,
304 llvm::sys::fs::F_Binary);
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000305 if (!ErrorInfo.empty())
Edwin Vaned088a5f2013-01-11 17:04:55 +0000306 return 1;
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000307 I->second.write(FileStream);
308 FileStream.flush();
309 }
Edwin Vaned088a5f2013-01-11 17:04:55 +0000310 return 0;
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000311}
312
313} // end namespace tooling
314} // end namespace clang