blob: df9600e78c76c6f4b954e0a3c56aa4f4df48330e [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"
Ariel J. Bernald11344a2013-10-01 14:59:00 +000021#include "llvm/Support/FileSystem.h"
22#include "llvm/Support/Path.h"
Stephen Hines651f13c2014-04-23 16:59:28 -070023#include "llvm/Support/raw_os_ostream.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
Stephen Hines651f13c2014-04-23 16:59:28 -070038Replacement::Replacement(const 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
Stephen Hines651f13c2014-04-23 16:59:28 -070043Replacement::Replacement(const SourceManager &Sources,
44 const CharSourceRange &Range,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +000045 StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000046 setFromSourceRange(Sources, Range, ReplacementText);
47}
48
49bool Replacement::isApplicable() const {
50 return FilePath != InvalidLocation;
51}
52
53bool Replacement::apply(Rewriter &Rewrite) const {
54 SourceManager &SM = Rewrite.getSourceMgr();
55 const FileEntry *Entry = SM.getFileManager().getFile(FilePath);
56 if (Entry == NULL)
57 return false;
58 FileID ID;
59 // FIXME: Use SM.translateFile directly.
60 SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1);
61 ID = Location.isValid() ?
62 SM.getFileID(Location) :
63 SM.createFileID(Entry, SourceLocation(), SrcMgr::C_User);
64 // FIXME: We cannot check whether Offset + Length is in the file, as
65 // the remapping API is not public in the RewriteBuffer.
66 const SourceLocation Start =
67 SM.getLocForStartOfFile(ID).
Daniel Jasper8a999452013-05-16 10:40:07 +000068 getLocWithOffset(ReplacementRange.getOffset());
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000069 // ReplaceText returns false on success.
70 // ReplaceText only fails if the source location is not a file location, in
71 // which case we already returned false earlier.
Daniel Jasper8a999452013-05-16 10:40:07 +000072 bool RewriteSucceeded = !Rewrite.ReplaceText(
73 Start, ReplacementRange.getLength(), ReplacementText);
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000074 assert(RewriteSucceeded);
75 return RewriteSucceeded;
76}
77
Manuel Klimek5d51e882012-05-30 16:04:29 +000078std::string Replacement::toString() const {
79 std::string result;
80 llvm::raw_string_ostream stream(result);
Daniel Jasper8a999452013-05-16 10:40:07 +000081 stream << FilePath << ": " << ReplacementRange.getOffset() << ":+"
82 << ReplacementRange.getLength() << ":\"" << ReplacementText << "\"";
Manuel Klimek5d51e882012-05-30 16:04:29 +000083 return result;
84}
85
Edwin Vane05e4af02013-08-16 12:18:53 +000086bool operator<(const Replacement &LHS, const Replacement &RHS) {
87 if (LHS.getOffset() != RHS.getOffset())
88 return LHS.getOffset() < RHS.getOffset();
89 if (LHS.getLength() != RHS.getLength())
90 return LHS.getLength() < RHS.getLength();
91 if (LHS.getFilePath() != RHS.getFilePath())
92 return LHS.getFilePath() < RHS.getFilePath();
93 return LHS.getReplacementText() < RHS.getReplacementText();
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +000094}
95
Edwin Vane05e4af02013-08-16 12:18:53 +000096bool operator==(const Replacement &LHS, const Replacement &RHS) {
97 return LHS.getOffset() == RHS.getOffset() &&
98 LHS.getLength() == RHS.getLength() &&
99 LHS.getFilePath() == RHS.getFilePath() &&
100 LHS.getReplacementText() == RHS.getReplacementText();
Edwin Vaned5692db2013-08-08 13:31:14 +0000101}
102
Stephen Hines651f13c2014-04-23 16:59:28 -0700103void Replacement::setFromSourceLocation(const SourceManager &Sources,
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000104 SourceLocation Start, unsigned Length,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +0000105 StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000106 const std::pair<FileID, unsigned> DecomposedLocation =
107 Sources.getDecomposedLoc(Start);
108 const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first);
Ariel J. Bernald11344a2013-10-01 14:59:00 +0000109 if (Entry != NULL) {
110 // Make FilePath absolute so replacements can be applied correctly when
Ariel J. Bernal19b60a52013-10-18 19:48:31 +0000111 // relative paths for files are used.
112 llvm::SmallString<256> FilePath(Entry->getName());
113 llvm::error_code EC = llvm::sys::fs::make_absolute(FilePath);
114 this->FilePath = EC ? FilePath.c_str() : Entry->getName();
Ariel J. Bernalb71aa7a2013-10-09 16:09:23 +0000115 } else {
Ariel J. Bernald11344a2013-10-01 14:59:00 +0000116 this->FilePath = InvalidLocation;
Ariel J. Bernalb71aa7a2013-10-09 16:09:23 +0000117 }
Daniel Jasper8a999452013-05-16 10:40:07 +0000118 this->ReplacementRange = Range(DecomposedLocation.second, Length);
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000119 this->ReplacementText = ReplacementText;
120}
121
122// FIXME: This should go into the Lexer, but we need to figure out how
123// to handle ranges for refactoring in general first - there is no obvious
124// good way how to integrate this into the Lexer yet.
Stephen Hines651f13c2014-04-23 16:59:28 -0700125static int getRangeSize(const SourceManager &Sources,
126 const CharSourceRange &Range) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000127 SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin());
128 SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd());
129 std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin);
130 std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd);
131 if (Start.first != End.first) return -1;
132 if (Range.isTokenRange())
133 End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources,
134 LangOptions());
135 return End.second - Start.second;
136}
137
Stephen Hines651f13c2014-04-23 16:59:28 -0700138void Replacement::setFromSourceRange(const SourceManager &Sources,
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000139 const CharSourceRange &Range,
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +0000140 StringRef ReplacementText) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000141 setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()),
142 getRangeSize(Sources, Range), ReplacementText);
143}
144
David Blaikie76a2ea32013-07-17 18:29:58 +0000145bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) {
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000146 bool Result = true;
147 for (Replacements::const_iterator I = Replaces.begin(),
148 E = Replaces.end();
149 I != E; ++I) {
150 if (I->isApplicable()) {
151 Result = I->apply(Rewrite) && Result;
152 } else {
153 Result = false;
154 }
155 }
156 return Result;
157}
158
Edwin Vaneb58cfd92013-08-13 17:38:19 +0000159// FIXME: Remove this function when Replacements is implemented as std::vector
160// instead of std::set.
161bool applyAllReplacements(const std::vector<Replacement> &Replaces,
162 Rewriter &Rewrite) {
163 bool Result = true;
164 for (std::vector<Replacement>::const_iterator I = Replaces.begin(),
165 E = Replaces.end();
166 I != E; ++I) {
167 if (I->isApplicable()) {
168 Result = I->apply(Rewrite) && Result;
169 } else {
170 Result = false;
171 }
172 }
173 return Result;
174}
175
David Blaikie76a2ea32013-07-17 18:29:58 +0000176std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) {
Daniel Jasper8a999452013-05-16 10:40:07 +0000177 FileManager Files((FileSystemOptions()));
178 DiagnosticsEngine Diagnostics(
179 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
180 new DiagnosticOptions);
181 Diagnostics.setClient(new TextDiagnosticPrinter(
182 llvm::outs(), &Diagnostics.getDiagnosticOptions()));
183 SourceManager SourceMgr(Diagnostics, Files);
184 Rewriter Rewrite(SourceMgr, LangOptions());
185 llvm::MemoryBuffer *Buf = llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>");
186 const clang::FileEntry *Entry =
187 Files.getVirtualFile("<stdin>", Buf->getBufferSize(), 0);
188 SourceMgr.overrideFileContents(Entry, Buf);
189 FileID ID =
190 SourceMgr.createFileID(Entry, SourceLocation(), clang::SrcMgr::C_User);
David Blaikie76a2ea32013-07-17 18:29:58 +0000191 for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end();
192 I != E; ++I) {
Daniel Jasper8a999452013-05-16 10:40:07 +0000193 Replacement Replace("<stdin>", I->getOffset(), I->getLength(),
194 I->getReplacementText());
195 if (!Replace.apply(Rewrite))
196 return "";
197 }
198 std::string Result;
199 llvm::raw_string_ostream OS(Result);
200 Rewrite.getEditBuffer(ID).write(OS);
201 OS.flush();
202 return Result;
203}
204
Daniel Jasper6bd3b932013-05-21 12:21:39 +0000205unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) {
206 unsigned NewPosition = Position;
207 for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E;
208 ++I) {
209 if (I->getOffset() >= Position)
210 break;
211 if (I->getOffset() + I->getLength() > Position)
212 NewPosition += I->getOffset() + I->getLength() - Position;
213 NewPosition += I->getReplacementText().size() - I->getLength();
214 }
215 return NewPosition;
216}
217
Edwin Vanea778cde2013-08-27 15:44:26 +0000218// FIXME: Remove this function when Replacements is implemented as std::vector
219// instead of std::set.
220unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces,
221 unsigned Position) {
222 unsigned NewPosition = Position;
223 for (std::vector<Replacement>::const_iterator I = Replaces.begin(),
224 E = Replaces.end();
225 I != E; ++I) {
226 if (I->getOffset() >= Position)
227 break;
228 if (I->getOffset() + I->getLength() > Position)
229 NewPosition += I->getOffset() + I->getLength() - Position;
230 NewPosition += I->getReplacementText().size() - I->getLength();
231 }
232 return NewPosition;
233}
234
Edwin Vaned5692db2013-08-08 13:31:14 +0000235void deduplicate(std::vector<Replacement> &Replaces,
236 std::vector<Range> &Conflicts) {
237 if (Replaces.empty())
238 return;
239
240 // Deduplicate
Edwin Vane05e4af02013-08-16 12:18:53 +0000241 std::sort(Replaces.begin(), Replaces.end());
Edwin Vaned5692db2013-08-08 13:31:14 +0000242 std::vector<Replacement>::iterator End =
243 std::unique(Replaces.begin(), Replaces.end());
244 Replaces.erase(End, Replaces.end());
245
246 // Detect conflicts
247 Range ConflictRange(Replaces.front().getOffset(),
248 Replaces.front().getLength());
249 unsigned ConflictStart = 0;
250 unsigned ConflictLength = 1;
251 for (unsigned i = 1; i < Replaces.size(); ++i) {
252 Range Current(Replaces[i].getOffset(), Replaces[i].getLength());
253 if (ConflictRange.overlapsWith(Current)) {
254 // Extend conflicted range
255 ConflictRange = Range(ConflictRange.getOffset(),
Edwin Vane95f07662013-08-13 16:26:44 +0000256 std::max(ConflictRange.getLength(),
257 Current.getOffset() + Current.getLength() -
258 ConflictRange.getOffset()));
Edwin Vaned5692db2013-08-08 13:31:14 +0000259 ++ConflictLength;
260 } else {
261 if (ConflictLength > 1)
262 Conflicts.push_back(Range(ConflictStart, ConflictLength));
263 ConflictRange = Current;
264 ConflictStart = i;
265 ConflictLength = 1;
266 }
267 }
268
269 if (ConflictLength > 1)
270 Conflicts.push_back(Range(ConflictStart, ConflictLength));
271}
272
273
Edwin Vaned088a5f2013-01-11 17:04:55 +0000274RefactoringTool::RefactoringTool(const CompilationDatabase &Compilations,
275 ArrayRef<std::string> SourcePaths)
276 : ClangTool(Compilations, SourcePaths) {}
277
278Replacements &RefactoringTool::getReplacements() { return Replace; }
279
280int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) {
281 if (int Result = run(ActionFactory)) {
282 return Result;
283 }
284
285 LangOptions DefaultLangOptions;
286 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
287 TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
288 DiagnosticsEngine Diagnostics(
Dmitri Gribenkocfa88f82013-01-12 19:30:44 +0000289 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()),
Edwin Vaned088a5f2013-01-11 17:04:55 +0000290 &*DiagOpts, &DiagnosticPrinter, false);
291 SourceManager Sources(Diagnostics, getFiles());
292 Rewriter Rewrite(Sources, DefaultLangOptions);
293
294 if (!applyAllReplacements(Rewrite)) {
295 llvm::errs() << "Skipped some replacements.\n";
296 }
297
298 return saveRewrittenFiles(Rewrite);
299}
300
301bool RefactoringTool::applyAllReplacements(Rewriter &Rewrite) {
302 return tooling::applyAllReplacements(Replace, Rewrite);
303}
304
305int RefactoringTool::saveRewrittenFiles(Rewriter &Rewrite) {
Alp Toker6bf97fb2013-10-29 08:32:41 +0000306 return Rewrite.overwriteChangedFiles() ? 1 : 0;
Manuel Klimekf9d4cbd2012-05-23 16:29:20 +0000307}
308
309} // end namespace tooling
310} // end namespace clang