1 //===--- 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 14 #include "clang/Basic/DiagnosticOptions.h" 15 #include "clang/Basic/FileManager.h" 16 #include "clang/Basic/SourceManager.h" 17 #include "clang/Frontend/TextDiagnosticPrinter.h" 18 #include "clang/Lex/Lexer.h" 19 #include "clang/Rewrite/Core/Rewriter.h" 20 #include "clang/Tooling/Refactoring.h" 21 #include "llvm/Support/raw_os_ostream.h" 22 23 namespace clang { 24 namespace tooling { 25 26 static const char * const InvalidLocation = ""; 27 28 Replacement::Replacement() 29 : FilePath(InvalidLocation) {} 30 31 Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length, 32 StringRef ReplacementText) 33 : FilePath(FilePath), ReplacementRange(Offset, Length), 34 ReplacementText(ReplacementText) {} 35 36 Replacement::Replacement(SourceManager &Sources, SourceLocation Start, 37 unsigned Length, StringRef ReplacementText) { 38 setFromSourceLocation(Sources, Start, Length, ReplacementText); 39 } 40 41 Replacement::Replacement(SourceManager &Sources, const CharSourceRange &Range, 42 StringRef ReplacementText) { 43 setFromSourceRange(Sources, Range, ReplacementText); 44 } 45 46 bool Replacement::isApplicable() const { 47 return FilePath != InvalidLocation; 48 } 49 50 bool 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). 65 getLocWithOffset(ReplacementRange.getOffset()); 66 // 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. 69 bool RewriteSucceeded = !Rewrite.ReplaceText( 70 Start, ReplacementRange.getLength(), ReplacementText); 71 assert(RewriteSucceeded); 72 return RewriteSucceeded; 73 } 74 75 std::string Replacement::toString() const { 76 std::string result; 77 llvm::raw_string_ostream stream(result); 78 stream << FilePath << ": " << ReplacementRange.getOffset() << ":+" 79 << ReplacementRange.getLength() << ":\"" << ReplacementText << "\""; 80 return result; 81 } 82 83 bool Replacement::Less::operator()(const Replacement &R1, 84 const Replacement &R2) const { 85 if (R1.FilePath != R2.FilePath) return R1.FilePath < R2.FilePath; 86 if (R1.ReplacementRange.getOffset() != R2.ReplacementRange.getOffset()) 87 return R1.ReplacementRange.getOffset() < R2.ReplacementRange.getOffset(); 88 if (R1.ReplacementRange.getLength() != R2.ReplacementRange.getLength()) 89 return R1.ReplacementRange.getLength() < R2.ReplacementRange.getLength(); 90 return R1.ReplacementText < R2.ReplacementText; 91 } 92 93 bool Replacement::operator==(const Replacement &Other) const { 94 return ReplacementRange.getOffset() == Other.ReplacementRange.getOffset() && 95 ReplacementRange.getLength() == Other.ReplacementRange.getLength() && 96 FilePath == Other.FilePath && ReplacementText == Other.ReplacementText; 97 } 98 99 void Replacement::setFromSourceLocation(SourceManager &Sources, 100 SourceLocation Start, unsigned Length, 101 StringRef ReplacementText) { 102 const std::pair<FileID, unsigned> DecomposedLocation = 103 Sources.getDecomposedLoc(Start); 104 const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first); 105 this->FilePath = Entry != NULL ? Entry->getName() : InvalidLocation; 106 this->ReplacementRange = Range(DecomposedLocation.second, Length); 107 this->ReplacementText = ReplacementText; 108 } 109 110 // FIXME: This should go into the Lexer, but we need to figure out how 111 // to handle ranges for refactoring in general first - there is no obvious 112 // good way how to integrate this into the Lexer yet. 113 static int getRangeSize(SourceManager &Sources, const CharSourceRange &Range) { 114 SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin()); 115 SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd()); 116 std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin); 117 std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd); 118 if (Start.first != End.first) return -1; 119 if (Range.isTokenRange()) 120 End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources, 121 LangOptions()); 122 return End.second - Start.second; 123 } 124 125 void Replacement::setFromSourceRange(SourceManager &Sources, 126 const CharSourceRange &Range, 127 StringRef ReplacementText) { 128 setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()), 129 getRangeSize(Sources, Range), ReplacementText); 130 } 131 132 bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) { 133 bool Result = true; 134 for (Replacements::const_iterator I = Replaces.begin(), 135 E = Replaces.end(); 136 I != E; ++I) { 137 if (I->isApplicable()) { 138 Result = I->apply(Rewrite) && Result; 139 } else { 140 Result = false; 141 } 142 } 143 return Result; 144 } 145 146 std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) { 147 FileManager Files((FileSystemOptions())); 148 DiagnosticsEngine Diagnostics( 149 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 150 new DiagnosticOptions); 151 Diagnostics.setClient(new TextDiagnosticPrinter( 152 llvm::outs(), &Diagnostics.getDiagnosticOptions())); 153 SourceManager SourceMgr(Diagnostics, Files); 154 Rewriter Rewrite(SourceMgr, LangOptions()); 155 llvm::MemoryBuffer *Buf = llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>"); 156 const clang::FileEntry *Entry = 157 Files.getVirtualFile("<stdin>", Buf->getBufferSize(), 0); 158 SourceMgr.overrideFileContents(Entry, Buf); 159 FileID ID = 160 SourceMgr.createFileID(Entry, SourceLocation(), clang::SrcMgr::C_User); 161 for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end(); 162 I != E; ++I) { 163 Replacement Replace("<stdin>", I->getOffset(), I->getLength(), 164 I->getReplacementText()); 165 if (!Replace.apply(Rewrite)) 166 return ""; 167 } 168 std::string Result; 169 llvm::raw_string_ostream OS(Result); 170 Rewrite.getEditBuffer(ID).write(OS); 171 OS.flush(); 172 return Result; 173 } 174 175 unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) { 176 unsigned NewPosition = Position; 177 for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E; 178 ++I) { 179 if (I->getOffset() >= Position) 180 break; 181 if (I->getOffset() + I->getLength() > Position) 182 NewPosition += I->getOffset() + I->getLength() - Position; 183 NewPosition += I->getReplacementText().size() - I->getLength(); 184 } 185 return NewPosition; 186 } 187 188 void deduplicate(std::vector<Replacement> &Replaces, 189 std::vector<Range> &Conflicts) { 190 if (Replaces.empty()) 191 return; 192 193 // Deduplicate 194 std::sort(Replaces.begin(), Replaces.end(), Replacement::Less()); 195 std::vector<Replacement>::iterator End = 196 std::unique(Replaces.begin(), Replaces.end()); 197 Replaces.erase(End, Replaces.end()); 198 199 // Detect conflicts 200 Range ConflictRange(Replaces.front().getOffset(), 201 Replaces.front().getLength()); 202 unsigned ConflictStart = 0; 203 unsigned ConflictLength = 1; 204 for (unsigned i = 1; i < Replaces.size(); ++i) { 205 Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); 206 if (ConflictRange.overlapsWith(Current)) { 207 // Extend conflicted range 208 ConflictRange = Range(ConflictRange.getOffset(), 209 Current.getOffset() + Current.getLength() - 210 ConflictRange.getOffset()); 211 ++ConflictLength; 212 } else { 213 if (ConflictLength > 1) 214 Conflicts.push_back(Range(ConflictStart, ConflictLength)); 215 ConflictRange = Current; 216 ConflictStart = i; 217 ConflictLength = 1; 218 } 219 } 220 221 if (ConflictLength > 1) 222 Conflicts.push_back(Range(ConflictStart, ConflictLength)); 223 } 224 225 226 RefactoringTool::RefactoringTool(const CompilationDatabase &Compilations, 227 ArrayRef<std::string> SourcePaths) 228 : ClangTool(Compilations, SourcePaths) {} 229 230 Replacements &RefactoringTool::getReplacements() { return Replace; } 231 232 int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) { 233 if (int Result = run(ActionFactory)) { 234 return Result; 235 } 236 237 LangOptions DefaultLangOptions; 238 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); 239 TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts); 240 DiagnosticsEngine Diagnostics( 241 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), 242 &*DiagOpts, &DiagnosticPrinter, false); 243 SourceManager Sources(Diagnostics, getFiles()); 244 Rewriter Rewrite(Sources, DefaultLangOptions); 245 246 if (!applyAllReplacements(Rewrite)) { 247 llvm::errs() << "Skipped some replacements.\n"; 248 } 249 250 return saveRewrittenFiles(Rewrite); 251 } 252 253 bool RefactoringTool::applyAllReplacements(Rewriter &Rewrite) { 254 return tooling::applyAllReplacements(Replace, Rewrite); 255 } 256 257 int RefactoringTool::saveRewrittenFiles(Rewriter &Rewrite) { 258 for (Rewriter::buffer_iterator I = Rewrite.buffer_begin(), 259 E = Rewrite.buffer_end(); 260 I != E; ++I) { 261 // FIXME: This code is copied from the FixItRewriter.cpp - I think it should 262 // go into directly into Rewriter (there we also have the Diagnostics to 263 // handle the error cases better). 264 const FileEntry *Entry = 265 Rewrite.getSourceMgr().getFileEntryForID(I->first); 266 std::string ErrorInfo; 267 llvm::raw_fd_ostream FileStream(Entry->getName(), ErrorInfo, 268 llvm::sys::fs::F_Binary); 269 if (!ErrorInfo.empty()) 270 return 1; 271 I->second.write(FileStream); 272 FileStream.flush(); 273 } 274 return 0; 275 } 276 277 } // end namespace tooling 278 } // end namespace clang 279