blob: 175dbd42acec8738f2c0263a80768a0df0e38bb6 [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"
Ariel J. Bernald11344a2013-10-01 14:59:00 +000022#include "llvm/Support/FileSystem.h"
23#include "llvm/Support/Path.h"
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000024
25namespace clang {
26namespace tooling {
27
28static const char * const InvalidLocation = "";
29
30Replacement::Replacement()
Daniel Jasper8a999452013-05-16 10:40:07 +000031 : FilePath(InvalidLocation) {}
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000032
Daniel Jasper8a999452013-05-16 10:40:07 +000033Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length,
34 StringRef ReplacementText)
35 : FilePath(FilePath), ReplacementRange(Offset, Length),
36 ReplacementText(ReplacementText) {}
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000037
38Replacement::Replacement(SourceManager &Sources, SourceLocation Start,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +000039 unsigned Length, StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000040 setFromSourceLocation(Sources, Start, Length, ReplacementText);
41}
42
43Replacement::Replacement(SourceManager &Sources, const CharSourceRange &Range,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +000044 StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000045 setFromSourceRange(Sources, Range, ReplacementText);
46}
47
48bool Replacement::isApplicable() const {
49 return FilePath != InvalidLocation;
50}
51
52bool Replacement::apply(Rewriter &Rewrite) const {
53 SourceManager &SM = Rewrite.getSourceMgr();
54 const FileEntry *Entry = SM.getFileManager().getFile(FilePath);
55 if (Entry == NULL)
56 return false;
57 FileID ID;
58 // FIXME: Use SM.translateFile directly.
59 SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1);
60 ID = Location.isValid() ?
61 SM.getFileID(Location) :
62 SM.createFileID(Entry, SourceLocation(), SrcMgr::C_User);
63 // FIXME: We cannot check whether Offset + Length is in the file, as
64 // the remapping API is not public in the RewriteBuffer.
65 const SourceLocation Start =
66 SM.getLocForStartOfFile(ID).
Daniel Jasper8a999452013-05-16 10:40:07 +000067 getLocWithOffset(ReplacementRange.getOffset());
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000068 // ReplaceText returns false on success.
69 // ReplaceText only fails if the source location is not a file location, in
70 // which case we already returned false earlier.
Daniel Jasper8a999452013-05-16 10:40:07 +000071 bool RewriteSucceeded = !Rewrite.ReplaceText(
72 Start, ReplacementRange.getLength(), ReplacementText);
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000073 assert(RewriteSucceeded);
74 return RewriteSucceeded;
75}
76
Manuel Klimek5d51e882012-05-30 16:04:29 +000077std::string Replacement::toString() const {
78 std::string result;
79 llvm::raw_string_ostream stream(result);
Daniel Jasper8a999452013-05-16 10:40:07 +000080 stream << FilePath << ": " << ReplacementRange.getOffset() << ":+"
81 << ReplacementRange.getLength() << ":\"" << ReplacementText << "\"";
Manuel Klimek5d51e882012-05-30 16:04:29 +000082 return result;
83}
84
Edwin Vane05e4af02013-08-16 12:18:53 +000085bool operator<(const Replacement &LHS, const Replacement &RHS) {
86 if (LHS.getOffset() != RHS.getOffset())
87 return LHS.getOffset() < RHS.getOffset();
88 if (LHS.getLength() != RHS.getLength())
89 return LHS.getLength() < RHS.getLength();
90 if (LHS.getFilePath() != RHS.getFilePath())
91 return LHS.getFilePath() < RHS.getFilePath();
92 return LHS.getReplacementText() < RHS.getReplacementText();
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000093}
94
Edwin Vane05e4af02013-08-16 12:18:53 +000095bool operator==(const Replacement &LHS, const Replacement &RHS) {
96 return LHS.getOffset() == RHS.getOffset() &&
97 LHS.getLength() == RHS.getLength() &&
98 LHS.getFilePath() == RHS.getFilePath() &&
99 LHS.getReplacementText() == RHS.getReplacementText();
Edwin Vaned5692db2013-08-08 13:31:14 +0000100}
101
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000102void Replacement::setFromSourceLocation(SourceManager &Sources,
103 SourceLocation Start, unsigned Length,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +0000104 StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000105 const std::pair<FileID, unsigned> DecomposedLocation =
106 Sources.getDecomposedLoc(Start);
107 const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first);
Ariel J. Bernald11344a2013-10-01 14:59:00 +0000108 if (Entry != NULL) {
109 // Make FilePath absolute so replacements can be applied correctly when
Ariel J. Bernalb71aa7a2013-10-09 16:09:23 +0000110 // relative paths for files are used. But we don't want to change virtual
111 // files.
112 if (llvm::sys::fs::exists(Entry->getName())) {
113 llvm::SmallString<256> FilePath(Entry->getName());
114 llvm::sys::fs::make_absolute(FilePath);
115 this->FilePath = FilePath.c_str();
116 }
117 else {
118 this->FilePath = Entry->getName();
119 }
120 } else {
Ariel J. Bernald11344a2013-10-01 14:59:00 +0000121 this->FilePath = InvalidLocation;
Ariel J. Bernalb71aa7a2013-10-09 16:09:23 +0000122 }
Daniel Jasper8a999452013-05-16 10:40:07 +0000123 this->ReplacementRange = Range(DecomposedLocation.second, Length);
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000124 this->ReplacementText = ReplacementText;
125}
126
127// FIXME: This should go into the Lexer, but we need to figure out how
128// to handle ranges for refactoring in general first - there is no obvious
129// good way how to integrate this into the Lexer yet.
130static int getRangeSize(SourceManager &Sources, const CharSourceRange &Range) {
131 SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin());
132 SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd());
133 std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin);
134 std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd);
135 if (Start.first != End.first) return -1;
136 if (Range.isTokenRange())
137 End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources,
138 LangOptions());
139 return End.second - Start.second;
140}
141
142void Replacement::setFromSourceRange(SourceManager &Sources,
143 const CharSourceRange &Range,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +0000144 StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000145 setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()),
146 getRangeSize(Sources, Range), ReplacementText);
147}
148
David Blaikie76a2ea32013-07-17 18:29:58 +0000149bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000150 bool Result = true;
151 for (Replacements::const_iterator I = Replaces.begin(),
152 E = Replaces.end();
153 I != E; ++I) {
154 if (I->isApplicable()) {
155 Result = I->apply(Rewrite) && Result;
156 } else {
157 Result = false;
158 }
159 }
160 return Result;
161}
162
Edwin Vaneb58cfd92013-08-13 17:38:19 +0000163// FIXME: Remove this function when Replacements is implemented as std::vector
164// instead of std::set.
165bool applyAllReplacements(const std::vector<Replacement> &Replaces,
166 Rewriter &Rewrite) {
167 bool Result = true;
168 for (std::vector<Replacement>::const_iterator I = Replaces.begin(),
169 E = Replaces.end();
170 I != E; ++I) {
171 if (I->isApplicable()) {
172 Result = I->apply(Rewrite) && Result;
173 } else {
174 Result = false;
175 }
176 }
177 return Result;
178}
179
David Blaikie76a2ea32013-07-17 18:29:58 +0000180std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) {
Daniel Jasper8a999452013-05-16 10:40:07 +0000181 FileManager Files((FileSystemOptions()));
182 DiagnosticsEngine Diagnostics(
183 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
184 new DiagnosticOptions);
185 Diagnostics.setClient(new TextDiagnosticPrinter(
186 llvm::outs(), &Diagnostics.getDiagnosticOptions()));
187 SourceManager SourceMgr(Diagnostics, Files);
188 Rewriter Rewrite(SourceMgr, LangOptions());
189 llvm::MemoryBuffer *Buf = llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>");
190 const clang::FileEntry *Entry =
191 Files.getVirtualFile("<stdin>", Buf->getBufferSize(), 0);
192 SourceMgr.overrideFileContents(Entry, Buf);
193 FileID ID =
194 SourceMgr.createFileID(Entry, SourceLocation(), clang::SrcMgr::C_User);
David Blaikie76a2ea32013-07-17 18:29:58 +0000195 for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end();
196 I != E; ++I) {
Daniel Jasper8a999452013-05-16 10:40:07 +0000197 Replacement Replace("<stdin>", I->getOffset(), I->getLength(),
198 I->getReplacementText());
199 if (!Replace.apply(Rewrite))
200 return "";
201 }
202 std::string Result;
203 llvm::raw_string_ostream OS(Result);
204 Rewrite.getEditBuffer(ID).write(OS);
205 OS.flush();
206 return Result;
207}
208
Daniel Jasper6bd3b932013-05-21 12:21:39 +0000209unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) {
210 unsigned NewPosition = Position;
211 for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E;
212 ++I) {
213 if (I->getOffset() >= Position)
214 break;
215 if (I->getOffset() + I->getLength() > Position)
216 NewPosition += I->getOffset() + I->getLength() - Position;
217 NewPosition += I->getReplacementText().size() - I->getLength();
218 }
219 return NewPosition;
220}
221
Edwin Vanea778cde2013-08-27 15:44:26 +0000222// FIXME: Remove this function when Replacements is implemented as std::vector
223// instead of std::set.
224unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces,
225 unsigned Position) {
226 unsigned NewPosition = Position;
227 for (std::vector<Replacement>::const_iterator I = Replaces.begin(),
228 E = Replaces.end();
229 I != E; ++I) {
230 if (I->getOffset() >= Position)
231 break;
232 if (I->getOffset() + I->getLength() > Position)
233 NewPosition += I->getOffset() + I->getLength() - Position;
234 NewPosition += I->getReplacementText().size() - I->getLength();
235 }
236 return NewPosition;
237}
238
Edwin Vaned5692db2013-08-08 13:31:14 +0000239void deduplicate(std::vector<Replacement> &Replaces,
240 std::vector<Range> &Conflicts) {
241 if (Replaces.empty())
242 return;
243
244 // Deduplicate
Edwin Vane05e4af02013-08-16 12:18:53 +0000245 std::sort(Replaces.begin(), Replaces.end());
Edwin Vaned5692db2013-08-08 13:31:14 +0000246 std::vector<Replacement>::iterator End =
247 std::unique(Replaces.begin(), Replaces.end());
248 Replaces.erase(End, Replaces.end());
249
250 // Detect conflicts
251 Range ConflictRange(Replaces.front().getOffset(),
252 Replaces.front().getLength());
253 unsigned ConflictStart = 0;
254 unsigned ConflictLength = 1;
255 for (unsigned i = 1; i < Replaces.size(); ++i) {
256 Range Current(Replaces[i].getOffset(), Replaces[i].getLength());
257 if (ConflictRange.overlapsWith(Current)) {
258 // Extend conflicted range
259 ConflictRange = Range(ConflictRange.getOffset(),
Edwin Vane95f07662013-08-13 16:26:44 +0000260 std::max(ConflictRange.getLength(),
261 Current.getOffset() + Current.getLength() -
262 ConflictRange.getOffset()));
Edwin Vaned5692db2013-08-08 13:31:14 +0000263 ++ConflictLength;
264 } else {
265 if (ConflictLength > 1)
266 Conflicts.push_back(Range(ConflictStart, ConflictLength));
267 ConflictRange = Current;
268 ConflictStart = i;
269 ConflictLength = 1;
270 }
271 }
272
273 if (ConflictLength > 1)
274 Conflicts.push_back(Range(ConflictStart, ConflictLength));
275}
276
277
Edwin Vaned088a5f2013-01-11 17:04:55 +0000278RefactoringTool::RefactoringTool(const CompilationDatabase &Compilations,
279 ArrayRef<std::string> SourcePaths)
280 : ClangTool(Compilations, SourcePaths) {}
281
282Replacements &RefactoringTool::getReplacements() { return Replace; }
283
284int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) {
285 if (int Result = run(ActionFactory)) {
286 return Result;
287 }
288
289 LangOptions DefaultLangOptions;
290 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
291 TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
292 DiagnosticsEngine Diagnostics(
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +0000293 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()),
Edwin Vaned088a5f2013-01-11 17:04:55 +0000294 &*DiagOpts, &DiagnosticPrinter, false);
295 SourceManager Sources(Diagnostics, getFiles());
296 Rewriter Rewrite(Sources, DefaultLangOptions);
297
298 if (!applyAllReplacements(Rewrite)) {
299 llvm::errs() << "Skipped some replacements.\n";
300 }
301
302 return saveRewrittenFiles(Rewrite);
303}
304
305bool RefactoringTool::applyAllReplacements(Rewriter &Rewrite) {
306 return tooling::applyAllReplacements(Replace, Rewrite);
307}
308
309int RefactoringTool::saveRewrittenFiles(Rewriter &Rewrite) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000310 for (Rewriter::buffer_iterator I = Rewrite.buffer_begin(),
311 E = Rewrite.buffer_end();
312 I != E; ++I) {
313 // FIXME: This code is copied from the FixItRewriter.cpp - I think it should
314 // go into directly into Rewriter (there we also have the Diagnostics to
315 // handle the error cases better).
316 const FileEntry *Entry =
317 Rewrite.getSourceMgr().getFileEntryForID(I->first);
318 std::string ErrorInfo;
Rafael Espindolad965f952013-07-16 19:44:23 +0000319 llvm::raw_fd_ostream FileStream(Entry->getName(), ErrorInfo,
320 llvm::sys::fs::F_Binary);
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000321 if (!ErrorInfo.empty())
Edwin Vaned088a5f2013-01-11 17:04:55 +0000322 return 1;
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000323 I->second.write(FileStream);
324 FileStream.flush();
325 }
Edwin Vaned088a5f2013-01-11 17:04:55 +0000326 return 0;
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000327}
328
329} // end namespace tooling
330} // end namespace clang