1 //===-- clang-format/ClangFormat.cpp - Clang format tool ------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 /// 9 /// \file 10 /// This file implements a clang-format tool that automatically formats 11 /// (fragments of) C++ code. 12 /// 13 //===----------------------------------------------------------------------===// 14 15 #include "clang/Basic/Diagnostic.h" 16 #include "clang/Basic/DiagnosticOptions.h" 17 #include "clang/Basic/FileManager.h" 18 #include "clang/Basic/SourceManager.h" 19 #include "clang/Basic/Version.h" 20 #include "clang/Format/Format.h" 21 #include "clang/Rewrite/Core/Rewriter.h" 22 #include "llvm/Support/CommandLine.h" 23 #include "llvm/Support/FileSystem.h" 24 #include "llvm/Support/InitLLVM.h" 25 #include "llvm/Support/Process.h" 26 27 using namespace llvm; 28 using clang::tooling::Replacements; 29 30 static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden); 31 32 // Mark all our options with this category, everything else (except for -version 33 // and -help) will be hidden. 34 static cl::OptionCategory ClangFormatCategory("Clang-format options"); 35 36 static cl::list<unsigned> 37 Offsets("offset", 38 cl::desc("Format a range starting at this byte offset.\n" 39 "Multiple ranges can be formatted by specifying\n" 40 "several -offset and -length pairs.\n" 41 "Can only be used with one input file."), 42 cl::cat(ClangFormatCategory)); 43 static cl::list<unsigned> 44 Lengths("length", 45 cl::desc("Format a range of this length (in bytes).\n" 46 "Multiple ranges can be formatted by specifying\n" 47 "several -offset and -length pairs.\n" 48 "When only a single -offset is specified without\n" 49 "-length, clang-format will format up to the end\n" 50 "of the file.\n" 51 "Can only be used with one input file."), 52 cl::cat(ClangFormatCategory)); 53 static cl::list<std::string> 54 LineRanges("lines", 55 cl::desc("<start line>:<end line> - format a range of\n" 56 "lines (both 1-based).\n" 57 "Multiple ranges can be formatted by specifying\n" 58 "several -lines arguments.\n" 59 "Can't be used with -offset and -length.\n" 60 "Can only be used with one input file."), 61 cl::cat(ClangFormatCategory)); 62 static cl::opt<std::string> 63 Style("style", cl::desc(clang::format::StyleOptionHelpDescription), 64 cl::init(clang::format::DefaultFormatStyle), 65 cl::cat(ClangFormatCategory)); 66 static cl::opt<std::string> 67 FallbackStyle("fallback-style", 68 cl::desc("The name of the predefined style used as a\n" 69 "fallback in case clang-format is invoked with\n" 70 "-style=file, but can not find the .clang-format\n" 71 "file to use.\n" 72 "Use -fallback-style=none to skip formatting."), 73 cl::init(clang::format::DefaultFallbackStyle), 74 cl::cat(ClangFormatCategory)); 75 76 static cl::opt<std::string> AssumeFileName( 77 "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( 102 "sort-includes", 103 cl::desc("If set, overrides the include sorting behavior determined by the " 104 "SortIncludes style flag"), 105 cl::cat(ClangFormatCategory)); 106 107 static cl::opt<bool> 108 Verbose("verbose", cl::desc("If set, shows the list of processed files"), 109 cl::cat(ClangFormatCategory)); 110 111 // Use --dry-run to match other LLVM tools when you mean do it but don't 112 // actually do it 113 static cl::opt<bool> 114 DryRun("dry-run", 115 cl::desc("If set, do not actually make the formatting changes"), 116 cl::cat(ClangFormatCategory)); 117 118 // Use -n as a common command as an alias for --dry-run. (git and make use -n) 119 static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"), 120 cl::cat(ClangFormatCategory), cl::aliasopt(DryRun), 121 cl::NotHidden); 122 123 // Emulate being able to turn on/off the warning. 124 static cl::opt<bool> 125 WarnFormat("Wclang-format-violations", 126 cl::desc("Warnings about individual formatting changes needed. " 127 "Used only with --dry-run or -n"), 128 cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden); 129 130 static cl::opt<bool> 131 NoWarnFormat("Wno-clang-format-violations", 132 cl::desc("Do not warn about individual formatting changes " 133 "needed. Used only with --dry-run or -n"), 134 cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden); 135 136 static cl::opt<unsigned> ErrorLimit( 137 "ferror-limit", 138 cl::desc("Set the maximum number of clang-format errors to emit before " 139 "stopping (0 = no limit). Used only with --dry-run or -n"), 140 cl::init(0), cl::cat(ClangFormatCategory)); 141 142 static cl::opt<bool> 143 WarningsAsErrors("Werror", 144 cl::desc("If set, changes formatting warnings to errors"), 145 cl::cat(ClangFormatCategory)); 146 147 static cl::opt<bool> 148 ShowColors("fcolor-diagnostics", 149 cl::desc("If set, and on a color-capable terminal controls " 150 "whether or not to print diagnostics in color"), 151 cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden); 152 153 static cl::opt<bool> 154 NoShowColors("fno-color-diagnostics", 155 cl::desc("If set, and on a color-capable terminal controls " 156 "whether or not to print diagnostics in color"), 157 cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden); 158 159 static cl::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"), 160 cl::cat(ClangFormatCategory)); 161 162 namespace clang { 163 namespace format { 164 165 static FileID createInMemoryFile(StringRef FileName, MemoryBuffer *Source, 166 SourceManager &Sources, FileManager &Files, 167 llvm::vfs::InMemoryFileSystem *MemFS) { 168 MemFS->addFileNoOwn(FileName, 0, Source); 169 auto File = Files.getFile(FileName); 170 return Sources.createFileID(File ? *File : nullptr, SourceLocation(), 171 SrcMgr::C_User); 172 } 173 174 // Parses <start line>:<end line> input to a pair of line numbers. 175 // Returns true on error. 176 static bool parseLineRange(StringRef Input, unsigned &FromLine, 177 unsigned &ToLine) { 178 std::pair<StringRef, StringRef> LineRange = Input.split(':'); 179 return LineRange.first.getAsInteger(0, FromLine) || 180 LineRange.second.getAsInteger(0, ToLine); 181 } 182 183 static bool fillRanges(MemoryBuffer *Code, 184 std::vector<tooling::Range> &Ranges) { 185 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 186 new llvm::vfs::InMemoryFileSystem); 187 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 188 DiagnosticsEngine Diagnostics( 189 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 190 new DiagnosticOptions); 191 SourceManager Sources(Diagnostics, Files); 192 FileID ID = createInMemoryFile("<irrelevant>", Code, Sources, Files, 193 InMemoryFileSystem.get()); 194 if (!LineRanges.empty()) { 195 if (!Offsets.empty() || !Lengths.empty()) { 196 errs() << "error: cannot use -lines with -offset/-length\n"; 197 return true; 198 } 199 200 for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) { 201 unsigned FromLine, ToLine; 202 if (parseLineRange(LineRanges[i], FromLine, ToLine)) { 203 errs() << "error: invalid <start line>:<end line> pair\n"; 204 return true; 205 } 206 if (FromLine > ToLine) { 207 errs() << "error: start line should be less than end line\n"; 208 return true; 209 } 210 SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1); 211 SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX); 212 if (Start.isInvalid() || End.isInvalid()) 213 return true; 214 unsigned Offset = Sources.getFileOffset(Start); 215 unsigned Length = Sources.getFileOffset(End) - Offset; 216 Ranges.push_back(tooling::Range(Offset, Length)); 217 } 218 return false; 219 } 220 221 if (Offsets.empty()) 222 Offsets.push_back(0); 223 if (Offsets.size() != Lengths.size() && 224 !(Offsets.size() == 1 && Lengths.empty())) { 225 errs() << "error: number of -offset and -length arguments must match.\n"; 226 return true; 227 } 228 for (unsigned i = 0, e = Offsets.size(); i != e; ++i) { 229 if (Offsets[i] >= Code->getBufferSize()) { 230 errs() << "error: offset " << Offsets[i] << " is outside the file\n"; 231 return true; 232 } 233 SourceLocation Start = 234 Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]); 235 SourceLocation End; 236 if (i < Lengths.size()) { 237 if (Offsets[i] + Lengths[i] > Code->getBufferSize()) { 238 errs() << "error: invalid length " << Lengths[i] 239 << ", offset + length (" << Offsets[i] + Lengths[i] 240 << ") is outside the file.\n"; 241 return true; 242 } 243 End = Start.getLocWithOffset(Lengths[i]); 244 } else { 245 End = Sources.getLocForEndOfFile(ID); 246 } 247 unsigned Offset = Sources.getFileOffset(Start); 248 unsigned Length = Sources.getFileOffset(End) - Offset; 249 Ranges.push_back(tooling::Range(Offset, Length)); 250 } 251 return false; 252 } 253 254 static void outputReplacementXML(StringRef Text) { 255 // FIXME: When we sort includes, we need to make sure the stream is correct 256 // utf-8. 257 size_t From = 0; 258 size_t Index; 259 while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) { 260 outs() << Text.substr(From, Index - From); 261 switch (Text[Index]) { 262 case '\n': 263 outs() << " "; 264 break; 265 case '\r': 266 outs() << " "; 267 break; 268 case '<': 269 outs() << "<"; 270 break; 271 case '&': 272 outs() << "&"; 273 break; 274 default: 275 llvm_unreachable("Unexpected character encountered!"); 276 } 277 From = Index + 1; 278 } 279 outs() << Text.substr(From); 280 } 281 282 static void outputReplacementsXML(const Replacements &Replaces) { 283 for (const auto &R : Replaces) { 284 outs() << "<replacement " 285 << "offset='" << R.getOffset() << "' " 286 << "length='" << R.getLength() << "'>"; 287 outputReplacementXML(R.getReplacementText()); 288 outs() << "</replacement>\n"; 289 } 290 } 291 292 static bool 293 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName, 294 const std::unique_ptr<llvm::MemoryBuffer> &Code) { 295 if (Replaces.empty()) { 296 return false; 297 } 298 299 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); 300 DiagOpts->ShowColors = (ShowColors && !NoShowColors); 301 302 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); 303 IntrusiveRefCntPtr<DiagnosticsEngine> Diags( 304 new DiagnosticsEngine(DiagID, &*DiagOpts)); 305 306 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 307 new llvm::vfs::InMemoryFileSystem); 308 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 309 SourceManager Sources(*Diags, Files); 310 FileID FileID = createInMemoryFile(AssumedFileName, Code.get(), Sources, 311 Files, InMemoryFileSystem.get()); 312 313 FileManager &FileMgr = Sources.getFileManager(); 314 llvm::ErrorOr<const FileEntry *> FileEntryPtr = 315 FileMgr.getFile(AssumedFileName); 316 317 unsigned Errors = 0; 318 if (WarnFormat && !NoWarnFormat) { 319 for (const auto &R : Replaces) { 320 PresumedLoc PLoc = Sources.getPresumedLoc( 321 Sources.getLocForStartOfFile(FileID).getLocWithOffset(R.getOffset())); 322 323 SourceLocation LineBegin = 324 Sources.translateFileLineCol(FileEntryPtr.get(), PLoc.getLine(), 1); 325 SourceLocation NextLineBegin = Sources.translateFileLineCol( 326 FileEntryPtr.get(), PLoc.getLine() + 1, 1); 327 328 const char *StartBuf = Sources.getCharacterData(LineBegin); 329 const char *EndBuf = Sources.getCharacterData(NextLineBegin); 330 331 StringRef Line(StartBuf, (EndBuf - StartBuf) - 1); 332 333 SMDiagnostic Diags( 334 llvm::SourceMgr(), SMLoc(), AssumedFileName, PLoc.getLine(), 335 PLoc.getColumn(), 336 WarningsAsErrors ? SourceMgr::DiagKind::DK_Error 337 : SourceMgr::DiagKind::DK_Warning, 338 "code should be clang-formatted [-Wclang-format-violations]", Line, 339 ArrayRef<std::pair<unsigned, unsigned>>()); 340 341 Diags.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors)); 342 Errors++; 343 if (ErrorLimit && Errors >= ErrorLimit) 344 break; 345 } 346 } 347 return WarningsAsErrors; 348 } 349 350 static void outputXML(const Replacements &Replaces, 351 const Replacements &FormatChanges, 352 const FormattingAttemptStatus &Status, 353 const cl::opt<unsigned> &Cursor, 354 unsigned CursorPosition) { 355 outs() << "<?xml version='1.0'?>\n<replacements " 356 "xml:space='preserve' incomplete_format='" 357 << (Status.FormatComplete ? "false" : "true") << "'"; 358 if (!Status.FormatComplete) 359 outs() << " line='" << Status.Line << "'"; 360 outs() << ">\n"; 361 if (Cursor.getNumOccurrences() != 0) 362 outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition) 363 << "</cursor>\n"; 364 365 outputReplacementsXML(Replaces); 366 outs() << "</replacements>\n"; 367 } 368 369 // Returns true on error. 370 static bool format(StringRef FileName) { 371 if (!OutputXML && Inplace && FileName == "-") { 372 errs() << "error: cannot use -i when reading from stdin.\n"; 373 return false; 374 } 375 // On Windows, overwriting a file with an open file mapping doesn't work, 376 // so read the whole file into memory when formatting in-place. 377 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 378 !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName) 379 : MemoryBuffer::getFileOrSTDIN(FileName); 380 if (std::error_code EC = CodeOrErr.getError()) { 381 errs() << EC.message() << "\n"; 382 return true; 383 } 384 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get()); 385 if (Code->getBufferSize() == 0) 386 return false; // Empty files are formatted correctly. 387 388 StringRef BufStr = Code->getBuffer(); 389 390 const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr); 391 392 if (InvalidBOM) { 393 errs() << "error: encoding with unsupported byte order mark \"" 394 << InvalidBOM << "\" detected"; 395 if (FileName != "-") 396 errs() << " in file '" << FileName << "'"; 397 errs() << ".\n"; 398 return true; 399 } 400 401 std::vector<tooling::Range> Ranges; 402 if (fillRanges(Code.get(), Ranges)) 403 return true; 404 StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName; 405 406 llvm::Expected<FormatStyle> FormatStyle = 407 getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer()); 408 if (!FormatStyle) { 409 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 410 return true; 411 } 412 413 if (SortIncludes.getNumOccurrences() != 0) 414 FormatStyle->SortIncludes = SortIncludes; 415 unsigned CursorPosition = Cursor; 416 Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges, 417 AssumedFileName, &CursorPosition); 418 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); 419 if (!ChangedCode) { 420 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; 421 return true; 422 } 423 // Get new affected ranges after sorting `#includes`. 424 Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges); 425 FormattingAttemptStatus Status; 426 Replacements FormatChanges = 427 reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status); 428 Replaces = Replaces.merge(FormatChanges); 429 if (OutputXML || DryRun) { 430 if (DryRun) { 431 return emitReplacementWarnings(Replaces, AssumedFileName, Code); 432 } else { 433 outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition); 434 } 435 } else { 436 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 437 new llvm::vfs::InMemoryFileSystem); 438 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 439 DiagnosticsEngine Diagnostics( 440 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 441 new DiagnosticOptions); 442 SourceManager Sources(Diagnostics, Files); 443 FileID ID = createInMemoryFile(AssumedFileName, Code.get(), Sources, Files, 444 InMemoryFileSystem.get()); 445 Rewriter Rewrite(Sources, LangOptions()); 446 tooling::applyAllReplacements(Replaces, Rewrite); 447 if (Inplace) { 448 if (Rewrite.overwriteChangedFiles()) 449 return true; 450 } else { 451 if (Cursor.getNumOccurrences() != 0) { 452 outs() << "{ \"Cursor\": " 453 << FormatChanges.getShiftedCodePosition(CursorPosition) 454 << ", \"IncompleteFormat\": " 455 << (Status.FormatComplete ? "false" : "true"); 456 if (!Status.FormatComplete) 457 outs() << ", \"Line\": " << Status.Line; 458 outs() << " }\n"; 459 } 460 Rewrite.getEditBuffer(ID).write(outs()); 461 } 462 } 463 return false; 464 } 465 466 } // namespace format 467 } // namespace clang 468 469 static void PrintVersion(raw_ostream &OS) { 470 OS << clang::getClangToolFullVersion("clang-format") << '\n'; 471 } 472 473 // Dump the configuration. 474 static int dumpConfig() { 475 StringRef FileName; 476 std::unique_ptr<llvm::MemoryBuffer> Code; 477 if (FileNames.empty()) { 478 // We can't read the code to detect the language if there's no 479 // file name, so leave Code empty here. 480 FileName = AssumeFileName; 481 } else { 482 // Read in the code in case the filename alone isn't enough to 483 // detect the language. 484 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 485 MemoryBuffer::getFileOrSTDIN(FileNames[0]); 486 if (std::error_code EC = CodeOrErr.getError()) { 487 llvm::errs() << EC.message() << "\n"; 488 return 1; 489 } 490 FileName = (FileNames[0] == "-") ? AssumeFileName : FileNames[0]; 491 Code = std::move(CodeOrErr.get()); 492 } 493 llvm::Expected<clang::format::FormatStyle> FormatStyle = 494 clang::format::getStyle(Style, FileName, FallbackStyle, 495 Code ? Code->getBuffer() : ""); 496 if (!FormatStyle) { 497 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 498 return 1; 499 } 500 std::string Config = clang::format::configurationAsText(*FormatStyle); 501 outs() << Config << "\n"; 502 return 0; 503 } 504 505 int main(int argc, const char **argv) { 506 llvm::InitLLVM X(argc, argv); 507 508 cl::HideUnrelatedOptions(ClangFormatCategory); 509 510 cl::SetVersionPrinter(PrintVersion); 511 cl::ParseCommandLineOptions( 512 argc, argv, 513 "A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.\n\n" 514 "If no arguments are specified, it formats the code from standard input\n" 515 "and writes the result to the standard output.\n" 516 "If <file>s are given, it reformats the files. If -i is specified\n" 517 "together with <file>s, the files are edited in-place. Otherwise, the\n" 518 "result is written to the standard output.\n"); 519 520 if (Help) { 521 cl::PrintHelpMessage(); 522 return 0; 523 } 524 525 if (DumpConfig) { 526 return dumpConfig(); 527 } 528 529 bool Error = false; 530 if (FileNames.empty()) { 531 Error = clang::format::format("-"); 532 return Error ? 1 : 0; 533 } 534 if (FileNames.size() != 1 && 535 (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) { 536 errs() << "error: -offset, -length and -lines can only be used for " 537 "single file.\n"; 538 return 1; 539 } 540 for (const auto &FileName : FileNames) { 541 if (Verbose) 542 errs() << "Formatting " << FileName << "\n"; 543 Error |= clang::format::format(FileName); 544 } 545 return Error ? 1 : 0; 546 } 547