1 //===-- clang-format/ClangFormat.cpp - Clang format tool ------------------===// 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 /// \file 11 /// \brief This file implements a clang-format tool that automatically formats 12 /// (fragments of) C++ code. 13 /// 14 //===----------------------------------------------------------------------===// 15 16 #include "clang/Basic/Diagnostic.h" 17 #include "clang/Basic/DiagnosticOptions.h" 18 #include "clang/Basic/FileManager.h" 19 #include "clang/Basic/SourceManager.h" 20 #include "clang/Basic/Version.h" 21 #include "clang/Format/Format.h" 22 #include "clang/Rewrite/Core/Rewriter.h" 23 #include "llvm/ADT/StringMap.h" 24 #include "llvm/Support/CommandLine.h" 25 #include "llvm/Support/Debug.h" 26 #include "llvm/Support/FileSystem.h" 27 #include "llvm/Support/Signals.h" 28 29 using namespace llvm; 30 using clang::tooling::Replacements; 31 32 static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden); 33 34 // Mark all our options with this category, everything else (except for -version 35 // and -help) will be hidden. 36 static cl::OptionCategory ClangFormatCategory("Clang-format options"); 37 38 static cl::list<unsigned> 39 Offsets("offset", 40 cl::desc("Format a range starting at this byte offset.\n" 41 "Multiple ranges can be formatted by specifying\n" 42 "several -offset and -length pairs.\n" 43 "Can only be used with one input file."), 44 cl::cat(ClangFormatCategory)); 45 static cl::list<unsigned> 46 Lengths("length", 47 cl::desc("Format a range of this length (in bytes).\n" 48 "Multiple ranges can be formatted by specifying\n" 49 "several -offset and -length pairs.\n" 50 "When only a single -offset is specified without\n" 51 "-length, clang-format will format up to the end\n" 52 "of the file.\n" 53 "Can only be used with one input file."), 54 cl::cat(ClangFormatCategory)); 55 static cl::list<std::string> 56 LineRanges("lines", cl::desc("<start line>:<end line> - format a range of\n" 57 "lines (both 1-based).\n" 58 "Multiple ranges can be formatted by specifying\n" 59 "several -lines arguments.\n" 60 "Can't be used with -offset and -length.\n" 61 "Can only be used with one input file."), 62 cl::cat(ClangFormatCategory)); 63 static cl::opt<std::string> 64 Style("style", 65 cl::desc(clang::format::StyleOptionHelpDescription), 66 cl::init("file"), cl::cat(ClangFormatCategory)); 67 static cl::opt<std::string> 68 FallbackStyle("fallback-style", 69 cl::desc("The name of the predefined style used as a\n" 70 "fallback in case clang-format is invoked with\n" 71 "-style=file, but can not find the .clang-format\n" 72 "file to use.\n" 73 "Use -fallback-style=none to skip formatting."), 74 cl::init("LLVM"), cl::cat(ClangFormatCategory)); 75 76 static cl::opt<std::string> 77 AssumeFileName("assume-filename", 78 cl::desc("When reading from stdin, clang-format assumes this\n" 79 "filename to look for a style config file (with\n" 80 "-style=file) and to determine the language."), 81 cl::init("<stdin>"), cl::cat(ClangFormatCategory)); 82 83 static cl::opt<bool> Inplace("i", 84 cl::desc("Inplace edit <file>s, if specified."), 85 cl::cat(ClangFormatCategory)); 86 87 static cl::opt<bool> OutputXML("output-replacements-xml", 88 cl::desc("Output replacements as XML."), 89 cl::cat(ClangFormatCategory)); 90 static cl::opt<bool> 91 DumpConfig("dump-config", 92 cl::desc("Dump configuration options to stdout and exit.\n" 93 "Can be used with -style option."), 94 cl::cat(ClangFormatCategory)); 95 static cl::opt<unsigned> 96 Cursor("cursor", 97 cl::desc("The position of the cursor when invoking\n" 98 "clang-format from an editor integration"), 99 cl::init(0), cl::cat(ClangFormatCategory)); 100 101 static cl::opt<bool> SortIncludes("sort-includes", 102 cl::desc("Sort touched include lines"), 103 cl::cat(ClangFormatCategory)); 104 105 static cl::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"), 106 cl::cat(ClangFormatCategory)); 107 108 namespace clang { 109 namespace format { 110 111 static FileID createInMemoryFile(StringRef FileName, MemoryBuffer *Source, 112 SourceManager &Sources, FileManager &Files) { 113 const FileEntry *Entry = Files.getVirtualFile(FileName, 114 Source->getBufferSize(), 0); 115 Sources.overrideFileContents(Entry, Source, true); 116 return Sources.createFileID(Entry, SourceLocation(), SrcMgr::C_User); 117 } 118 119 // Parses <start line>:<end line> input to a pair of line numbers. 120 // Returns true on error. 121 static bool parseLineRange(StringRef Input, unsigned &FromLine, 122 unsigned &ToLine) { 123 std::pair<StringRef, StringRef> LineRange = Input.split(':'); 124 return LineRange.first.getAsInteger(0, FromLine) || 125 LineRange.second.getAsInteger(0, ToLine); 126 } 127 128 static bool fillRanges(MemoryBuffer *Code, 129 std::vector<tooling::Range> &Ranges) { 130 FileManager Files((FileSystemOptions())); 131 DiagnosticsEngine Diagnostics( 132 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 133 new DiagnosticOptions); 134 SourceManager Sources(Diagnostics, Files); 135 FileID ID = createInMemoryFile("<irrelevant>", Code, Sources, Files); 136 if (!LineRanges.empty()) { 137 if (!Offsets.empty() || !Lengths.empty()) { 138 llvm::errs() << "error: cannot use -lines with -offset/-length\n"; 139 return true; 140 } 141 142 for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) { 143 unsigned FromLine, ToLine; 144 if (parseLineRange(LineRanges[i], FromLine, ToLine)) { 145 llvm::errs() << "error: invalid <start line>:<end line> pair\n"; 146 return true; 147 } 148 if (FromLine > ToLine) { 149 llvm::errs() << "error: start line should be less than end line\n"; 150 return true; 151 } 152 SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1); 153 SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX); 154 if (Start.isInvalid() || End.isInvalid()) 155 return true; 156 unsigned Offset = Sources.getFileOffset(Start); 157 unsigned Length = Sources.getFileOffset(End) - Offset; 158 Ranges.push_back(tooling::Range(Offset, Length)); 159 } 160 return false; 161 } 162 163 if (Offsets.empty()) 164 Offsets.push_back(0); 165 if (Offsets.size() != Lengths.size() && 166 !(Offsets.size() == 1 && Lengths.empty())) { 167 llvm::errs() 168 << "error: number of -offset and -length arguments must match.\n"; 169 return true; 170 } 171 for (unsigned i = 0, e = Offsets.size(); i != e; ++i) { 172 if (Offsets[i] >= Code->getBufferSize()) { 173 llvm::errs() << "error: offset " << Offsets[i] 174 << " is outside the file\n"; 175 return true; 176 } 177 SourceLocation Start = 178 Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]); 179 SourceLocation End; 180 if (i < Lengths.size()) { 181 if (Offsets[i] + Lengths[i] > Code->getBufferSize()) { 182 llvm::errs() << "error: invalid length " << Lengths[i] 183 << ", offset + length (" << Offsets[i] + Lengths[i] 184 << ") is outside the file.\n"; 185 return true; 186 } 187 End = Start.getLocWithOffset(Lengths[i]); 188 } else { 189 End = Sources.getLocForEndOfFile(ID); 190 } 191 unsigned Offset = Sources.getFileOffset(Start); 192 unsigned Length = Sources.getFileOffset(End) - Offset; 193 Ranges.push_back(tooling::Range(Offset, Length)); 194 } 195 return false; 196 } 197 198 static void outputReplacementXML(StringRef Text) { 199 size_t From = 0; 200 size_t Index; 201 while ((Index = Text.find_first_of("\n\r", From)) != StringRef::npos) { 202 llvm::outs() << Text.substr(From, Index - From); 203 switch (Text[Index]) { 204 case '\n': 205 llvm::outs() << " "; 206 break; 207 case '\r': 208 llvm::outs() << " "; 209 break; 210 default: 211 llvm_unreachable("Unexpected character encountered!"); 212 } 213 From = Index + 1; 214 } 215 llvm::outs() << Text.substr(From); 216 } 217 218 static void outputReplacementsXML(const Replacements &Replaces) { 219 for (const auto &R : Replaces) { 220 outs() << "<replacement " 221 << "offset='" << R.getOffset() << "' " 222 << "length='" << R.getLength() << "'>"; 223 outputReplacementXML(R.getReplacementText()); 224 outs() << "</replacement>\n"; 225 } 226 } 227 228 // Returns true on error. 229 static bool format(StringRef FileName) { 230 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 231 MemoryBuffer::getFileOrSTDIN(FileName); 232 if (std::error_code EC = CodeOrErr.getError()) { 233 llvm::errs() << EC.message() << "\n"; 234 return true; 235 } 236 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get()); 237 if (Code->getBufferSize() == 0) 238 return false; // Empty files are formatted correctly. 239 std::vector<tooling::Range> Ranges; 240 if (fillRanges(Code.get(), Ranges)) 241 return true; 242 StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName; 243 FormatStyle FormatStyle = getStyle(Style, AssumedFileName, FallbackStyle); 244 Replacements Replaces; 245 std::string ChangedCode; 246 if (SortIncludes) { 247 Replaces = 248 sortIncludes(FormatStyle, Code->getBuffer(), Ranges, AssumedFileName); 249 ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); 250 for (const auto &R : Replaces) 251 Ranges.push_back({R.getOffset(), R.getLength()}); 252 } else { 253 ChangedCode = Code->getBuffer().str(); 254 } 255 256 bool IncompleteFormat = false; 257 Replaces = tooling::mergeReplacements( 258 Replaces, reformat(FormatStyle, ChangedCode, Ranges, AssumedFileName, 259 &IncompleteFormat)); 260 if (OutputXML) { 261 llvm::outs() << "<?xml version='1.0'?>\n<replacements " 262 "xml:space='preserve' incomplete_format='" 263 << (IncompleteFormat ? "true" : "false") << "'>\n"; 264 if (Cursor.getNumOccurrences() != 0) 265 llvm::outs() << "<cursor>" 266 << tooling::shiftedCodePosition(Replaces, Cursor) 267 << "</cursor>\n"; 268 269 outputReplacementsXML(Replaces); 270 llvm::outs() << "</replacements>\n"; 271 } else { 272 FileManager Files((FileSystemOptions())); 273 DiagnosticsEngine Diagnostics( 274 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 275 new DiagnosticOptions); 276 SourceManager Sources(Diagnostics, Files); 277 FileID ID = createInMemoryFile(AssumedFileName, Code.get(), Sources, Files); 278 Rewriter Rewrite(Sources, LangOptions()); 279 tooling::applyAllReplacements(Replaces, Rewrite); 280 if (Inplace) { 281 if (FileName == "-") 282 llvm::errs() << "error: cannot use -i when reading from stdin.\n"; 283 else if (Rewrite.overwriteChangedFiles()) 284 return true; 285 } else { 286 if (Cursor.getNumOccurrences() != 0) 287 outs() << "{ \"Cursor\": " 288 << tooling::shiftedCodePosition(Replaces, Cursor) 289 << ", \"IncompleteFormat\": " 290 << (IncompleteFormat ? "true" : "false") << " }\n"; 291 Rewrite.getEditBuffer(ID).write(outs()); 292 } 293 } 294 return false; 295 } 296 297 } // namespace format 298 } // namespace clang 299 300 static void PrintVersion() { 301 raw_ostream &OS = outs(); 302 OS << clang::getClangToolFullVersion("clang-format") << '\n'; 303 } 304 305 int main(int argc, const char **argv) { 306 llvm::sys::PrintStackTraceOnErrorSignal(); 307 308 cl::HideUnrelatedOptions(ClangFormatCategory); 309 310 cl::SetVersionPrinter(PrintVersion); 311 cl::ParseCommandLineOptions( 312 argc, argv, 313 "A tool to format C/C++/Obj-C code.\n\n" 314 "If no arguments are specified, it formats the code from standard input\n" 315 "and writes the result to the standard output.\n" 316 "If <file>s are given, it reformats the files. If -i is specified\n" 317 "together with <file>s, the files are edited in-place. Otherwise, the\n" 318 "result is written to the standard output.\n"); 319 320 if (Help) 321 cl::PrintHelpMessage(); 322 323 if (DumpConfig) { 324 std::string Config = 325 clang::format::configurationAsText(clang::format::getStyle( 326 Style, FileNames.empty() ? AssumeFileName : FileNames[0], 327 FallbackStyle)); 328 llvm::outs() << Config << "\n"; 329 return 0; 330 } 331 332 bool Error = false; 333 switch (FileNames.size()) { 334 case 0: 335 Error = clang::format::format("-"); 336 break; 337 case 1: 338 Error = clang::format::format(FileNames[0]); 339 break; 340 default: 341 if (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty()) { 342 llvm::errs() << "error: -offset, -length and -lines can only be used for " 343 "single file.\n"; 344 return 1; 345 } 346 for (unsigned i = 0; i < FileNames.size(); ++i) 347 Error |= clang::format::format(FileNames[i]); 348 break; 349 } 350 return Error ? 1 : 0; 351 } 352 353