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/ADT/StringSwitch.h" 23 #include "llvm/Support/CommandLine.h" 24 #include "llvm/Support/FileSystem.h" 25 #include "llvm/Support/InitLLVM.h" 26 #include "llvm/Support/Process.h" 27 #include <fstream> 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", 57 cl::desc("<start line>:<end line> - format a range of\n" 58 "lines (both 1-based).\n" 59 "Multiple ranges can be formatted by specifying\n" 60 "several -lines arguments.\n" 61 "Can't be used with -offset and -length.\n" 62 "Can only be used with one input file."), 63 cl::cat(ClangFormatCategory)); 64 static cl::opt<std::string> 65 Style("style", cl::desc(clang::format::StyleOptionHelpDescription), 66 cl::init(clang::format::DefaultFormatStyle), 67 cl::cat(ClangFormatCategory)); 68 static cl::opt<std::string> 69 FallbackStyle("fallback-style", 70 cl::desc("The name of the predefined style used as a\n" 71 "fallback in case clang-format is invoked with\n" 72 "-style=file, but can not find the .clang-format\n" 73 "file to use.\n" 74 "Use -fallback-style=none to skip formatting."), 75 cl::init(clang::format::DefaultFallbackStyle), 76 cl::cat(ClangFormatCategory)); 77 78 static cl::opt<std::string> AssumeFileName( 79 "assume-filename", 80 cl::desc("Override filename used to determine the language.\n" 81 "When reading from stdin, clang-format assumes this\n" 82 "filename to determine the language."), 83 cl::init("<stdin>"), cl::cat(ClangFormatCategory)); 84 85 static cl::opt<bool> Inplace("i", 86 cl::desc("Inplace edit <file>s, if specified."), 87 cl::cat(ClangFormatCategory)); 88 89 static cl::opt<bool> OutputXML("output-replacements-xml", 90 cl::desc("Output replacements as XML."), 91 cl::cat(ClangFormatCategory)); 92 static cl::opt<bool> 93 DumpConfig("dump-config", 94 cl::desc("Dump configuration options to stdout and exit.\n" 95 "Can be used with -style option."), 96 cl::cat(ClangFormatCategory)); 97 static cl::opt<unsigned> 98 Cursor("cursor", 99 cl::desc("The position of the cursor when invoking\n" 100 "clang-format from an editor integration"), 101 cl::init(0), cl::cat(ClangFormatCategory)); 102 103 static cl::opt<bool> SortIncludes( 104 "sort-includes", 105 cl::desc("If set, overrides the include sorting behavior determined by the " 106 "SortIncludes style flag"), 107 cl::cat(ClangFormatCategory)); 108 109 static cl::opt<std::string> QualifierAlignment( 110 "qualifier-alignment", 111 cl::desc( 112 "If set, overrides the qualifier alignment style determined by the " 113 "QualifierAlignment style flag"), 114 cl::init(""), cl::cat(ClangFormatCategory)); 115 116 static cl::opt<std::string> 117 Files("files", cl::desc("Provide a list of files to run clang-format"), 118 cl::init(""), cl::cat(ClangFormatCategory)); 119 120 static cl::opt<bool> 121 Verbose("verbose", cl::desc("If set, shows the list of processed files"), 122 cl::cat(ClangFormatCategory)); 123 124 // Use --dry-run to match other LLVM tools when you mean do it but don't 125 // actually do it 126 static cl::opt<bool> 127 DryRun("dry-run", 128 cl::desc("If set, do not actually make the formatting changes"), 129 cl::cat(ClangFormatCategory)); 130 131 // Use -n as a common command as an alias for --dry-run. (git and make use -n) 132 static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"), 133 cl::cat(ClangFormatCategory), cl::aliasopt(DryRun), 134 cl::NotHidden); 135 136 // Emulate being able to turn on/off the warning. 137 static cl::opt<bool> 138 WarnFormat("Wclang-format-violations", 139 cl::desc("Warnings about individual formatting changes needed. " 140 "Used only with --dry-run or -n"), 141 cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden); 142 143 static cl::opt<bool> 144 NoWarnFormat("Wno-clang-format-violations", 145 cl::desc("Do not warn about individual formatting changes " 146 "needed. Used only with --dry-run or -n"), 147 cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden); 148 149 static cl::opt<unsigned> ErrorLimit( 150 "ferror-limit", 151 cl::desc("Set the maximum number of clang-format errors to emit before " 152 "stopping (0 = no limit). Used only with --dry-run or -n"), 153 cl::init(0), cl::cat(ClangFormatCategory)); 154 155 static cl::opt<bool> 156 WarningsAsErrors("Werror", 157 cl::desc("If set, changes formatting warnings to errors"), 158 cl::cat(ClangFormatCategory)); 159 160 namespace { 161 enum class WNoError { Unknown }; 162 } 163 164 static cl::bits<WNoError> WNoErrorList( 165 "Wno-error", 166 cl::desc("If set don't error out on the specified warning type."), 167 cl::values( 168 clEnumValN(WNoError::Unknown, "unknown", 169 "If set, unknown format options are only warned about.\n" 170 "This can be used to enable formatting, even if the\n" 171 "configuration contains unknown (newer) options.\n" 172 "Use with caution, as this might lead to dramatically\n" 173 "differing format depending on an option being\n" 174 "supported or not.")), 175 cl::cat(ClangFormatCategory)); 176 177 static cl::opt<bool> 178 ShowColors("fcolor-diagnostics", 179 cl::desc("If set, and on a color-capable terminal controls " 180 "whether or not to print diagnostics in color"), 181 cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden); 182 183 static cl::opt<bool> 184 NoShowColors("fno-color-diagnostics", 185 cl::desc("If set, and on a color-capable terminal controls " 186 "whether or not to print diagnostics in color"), 187 cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden); 188 189 static cl::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"), 190 cl::cat(ClangFormatCategory)); 191 192 namespace clang { 193 namespace format { 194 195 static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source, 196 SourceManager &Sources, FileManager &Files, 197 llvm::vfs::InMemoryFileSystem *MemFS) { 198 MemFS->addFileNoOwn(FileName, 0, Source); 199 auto File = Files.getOptionalFileRef(FileName); 200 assert(File && "File not added to MemFS?"); 201 return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User); 202 } 203 204 // Parses <start line>:<end line> input to a pair of line numbers. 205 // Returns true on error. 206 static bool parseLineRange(StringRef Input, unsigned &FromLine, 207 unsigned &ToLine) { 208 std::pair<StringRef, StringRef> LineRange = Input.split(':'); 209 return LineRange.first.getAsInteger(0, FromLine) || 210 LineRange.second.getAsInteger(0, ToLine); 211 } 212 213 static bool fillRanges(MemoryBuffer *Code, 214 std::vector<tooling::Range> &Ranges) { 215 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 216 new llvm::vfs::InMemoryFileSystem); 217 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 218 DiagnosticsEngine Diagnostics( 219 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 220 new DiagnosticOptions); 221 SourceManager Sources(Diagnostics, Files); 222 FileID ID = createInMemoryFile("<irrelevant>", *Code, Sources, Files, 223 InMemoryFileSystem.get()); 224 if (!LineRanges.empty()) { 225 if (!Offsets.empty() || !Lengths.empty()) { 226 errs() << "error: cannot use -lines with -offset/-length\n"; 227 return true; 228 } 229 230 for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) { 231 unsigned FromLine, ToLine; 232 if (parseLineRange(LineRanges[i], FromLine, ToLine)) { 233 errs() << "error: invalid <start line>:<end line> pair\n"; 234 return true; 235 } 236 if (FromLine > ToLine) { 237 errs() << "error: start line should be less than end line\n"; 238 return true; 239 } 240 SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1); 241 SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX); 242 if (Start.isInvalid() || End.isInvalid()) 243 return true; 244 unsigned Offset = Sources.getFileOffset(Start); 245 unsigned Length = Sources.getFileOffset(End) - Offset; 246 Ranges.push_back(tooling::Range(Offset, Length)); 247 } 248 return false; 249 } 250 251 if (Offsets.empty()) 252 Offsets.push_back(0); 253 if (Offsets.size() != Lengths.size() && 254 !(Offsets.size() == 1 && Lengths.empty())) { 255 errs() << "error: number of -offset and -length arguments must match.\n"; 256 return true; 257 } 258 for (unsigned i = 0, e = Offsets.size(); i != e; ++i) { 259 if (Offsets[i] >= Code->getBufferSize()) { 260 errs() << "error: offset " << Offsets[i] << " is outside the file\n"; 261 return true; 262 } 263 SourceLocation Start = 264 Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]); 265 SourceLocation End; 266 if (i < Lengths.size()) { 267 if (Offsets[i] + Lengths[i] > Code->getBufferSize()) { 268 errs() << "error: invalid length " << Lengths[i] 269 << ", offset + length (" << Offsets[i] + Lengths[i] 270 << ") is outside the file.\n"; 271 return true; 272 } 273 End = Start.getLocWithOffset(Lengths[i]); 274 } else { 275 End = Sources.getLocForEndOfFile(ID); 276 } 277 unsigned Offset = Sources.getFileOffset(Start); 278 unsigned Length = Sources.getFileOffset(End) - Offset; 279 Ranges.push_back(tooling::Range(Offset, Length)); 280 } 281 return false; 282 } 283 284 static void outputReplacementXML(StringRef Text) { 285 // FIXME: When we sort includes, we need to make sure the stream is correct 286 // utf-8. 287 size_t From = 0; 288 size_t Index; 289 while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) { 290 outs() << Text.substr(From, Index - From); 291 switch (Text[Index]) { 292 case '\n': 293 outs() << " "; 294 break; 295 case '\r': 296 outs() << " "; 297 break; 298 case '<': 299 outs() << "<"; 300 break; 301 case '&': 302 outs() << "&"; 303 break; 304 default: 305 llvm_unreachable("Unexpected character encountered!"); 306 } 307 From = Index + 1; 308 } 309 outs() << Text.substr(From); 310 } 311 312 static void outputReplacementsXML(const Replacements &Replaces) { 313 for (const auto &R : Replaces) { 314 outs() << "<replacement " 315 << "offset='" << R.getOffset() << "' " 316 << "length='" << R.getLength() << "'>"; 317 outputReplacementXML(R.getReplacementText()); 318 outs() << "</replacement>\n"; 319 } 320 } 321 322 static bool 323 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName, 324 const std::unique_ptr<llvm::MemoryBuffer> &Code) { 325 if (Replaces.empty()) 326 return false; 327 328 unsigned Errors = 0; 329 if (WarnFormat && !NoWarnFormat) { 330 llvm::SourceMgr Mgr; 331 const char *StartBuf = Code->getBufferStart(); 332 333 Mgr.AddNewSourceBuffer( 334 MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc()); 335 for (const auto &R : Replaces) { 336 SMDiagnostic Diag = Mgr.GetMessage( 337 SMLoc::getFromPointer(StartBuf + R.getOffset()), 338 WarningsAsErrors ? SourceMgr::DiagKind::DK_Error 339 : SourceMgr::DiagKind::DK_Warning, 340 "code should be clang-formatted [-Wclang-format-violations]"); 341 342 Diag.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors)); 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 class ClangFormatDiagConsumer : public DiagnosticConsumer { 370 virtual void anchor() {} 371 372 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 373 const Diagnostic &Info) override { 374 375 SmallVector<char, 16> vec; 376 Info.FormatDiagnostic(vec); 377 errs() << "clang-format error:" << vec << "\n"; 378 } 379 }; 380 381 // Returns true on error. 382 static bool format(StringRef FileName) { 383 if (!OutputXML && Inplace && FileName == "-") { 384 errs() << "error: cannot use -i when reading from stdin.\n"; 385 return false; 386 } 387 // On Windows, overwriting a file with an open file mapping doesn't work, 388 // so read the whole file into memory when formatting in-place. 389 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 390 !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName) 391 : MemoryBuffer::getFileOrSTDIN(FileName); 392 if (std::error_code EC = CodeOrErr.getError()) { 393 errs() << EC.message() << "\n"; 394 return true; 395 } 396 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get()); 397 if (Code->getBufferSize() == 0) 398 return false; // Empty files are formatted correctly. 399 400 StringRef BufStr = Code->getBuffer(); 401 402 const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr); 403 404 if (InvalidBOM) { 405 errs() << "error: encoding with unsupported byte order mark \"" 406 << InvalidBOM << "\" detected"; 407 if (FileName != "-") 408 errs() << " in file '" << FileName << "'"; 409 errs() << ".\n"; 410 return true; 411 } 412 413 std::vector<tooling::Range> Ranges; 414 if (fillRanges(Code.get(), Ranges)) 415 return true; 416 StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName; 417 if (AssumedFileName.empty()) { 418 llvm::errs() << "error: empty filenames are not allowed\n"; 419 return true; 420 } 421 422 llvm::Expected<FormatStyle> FormatStyle = 423 getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(), 424 nullptr, WNoErrorList.isSet(WNoError::Unknown)); 425 if (!FormatStyle) { 426 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 427 return true; 428 } 429 430 StringRef QualifierAlignmentOrder = QualifierAlignment; 431 432 FormatStyle->QualifierAlignment = 433 StringSwitch<FormatStyle::QualifierAlignmentStyle>( 434 QualifierAlignmentOrder.lower()) 435 .Case("right", FormatStyle::QAS_Right) 436 .Case("left", FormatStyle::QAS_Left) 437 .Default(FormatStyle->QualifierAlignment); 438 439 if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) 440 FormatStyle->QualifierOrder = {"const", "volatile", "type"}; 441 else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) 442 FormatStyle->QualifierOrder = {"type", "const", "volatile"}; 443 else if (QualifierAlignmentOrder.contains("type")) { 444 FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom; 445 SmallVector<StringRef> Qualifiers; 446 QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1, 447 /*KeepEmpty=*/false); 448 FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()}; 449 } 450 451 if (SortIncludes.getNumOccurrences() != 0) { 452 if (SortIncludes) 453 FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive; 454 else 455 FormatStyle->SortIncludes = FormatStyle::SI_Never; 456 } 457 unsigned CursorPosition = Cursor; 458 Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges, 459 AssumedFileName, &CursorPosition); 460 461 // To format JSON insert a variable to trick the code into thinking its 462 // JavaScript. 463 if (FormatStyle->isJson() && !FormatStyle->DisableFormat) { 464 auto Err = Replaces.add(tooling::Replacement( 465 tooling::Replacement(AssumedFileName, 0, 0, "x = "))); 466 if (Err) { 467 llvm::errs() << "Bad Json variable insertion\n"; 468 } 469 } 470 471 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); 472 if (!ChangedCode) { 473 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; 474 return true; 475 } 476 // Get new affected ranges after sorting `#includes`. 477 Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges); 478 FormattingAttemptStatus Status; 479 Replacements FormatChanges = 480 reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status); 481 Replaces = Replaces.merge(FormatChanges); 482 if (OutputXML || DryRun) { 483 if (DryRun) { 484 return emitReplacementWarnings(Replaces, AssumedFileName, Code); 485 } else { 486 outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition); 487 } 488 } else { 489 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 490 new llvm::vfs::InMemoryFileSystem); 491 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 492 493 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions()); 494 ClangFormatDiagConsumer IgnoreDiagnostics; 495 DiagnosticsEngine Diagnostics( 496 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, 497 &IgnoreDiagnostics, false); 498 SourceManager Sources(Diagnostics, Files); 499 FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files, 500 InMemoryFileSystem.get()); 501 Rewriter Rewrite(Sources, LangOptions()); 502 tooling::applyAllReplacements(Replaces, Rewrite); 503 if (Inplace) { 504 if (Rewrite.overwriteChangedFiles()) 505 return true; 506 } else { 507 if (Cursor.getNumOccurrences() != 0) { 508 outs() << "{ \"Cursor\": " 509 << FormatChanges.getShiftedCodePosition(CursorPosition) 510 << ", \"IncompleteFormat\": " 511 << (Status.FormatComplete ? "false" : "true"); 512 if (!Status.FormatComplete) 513 outs() << ", \"Line\": " << Status.Line; 514 outs() << " }\n"; 515 } 516 Rewrite.getEditBuffer(ID).write(outs()); 517 } 518 } 519 return false; 520 } 521 522 } // namespace format 523 } // namespace clang 524 525 static void PrintVersion(raw_ostream &OS) { 526 OS << clang::getClangToolFullVersion("clang-format") << '\n'; 527 } 528 529 // Dump the configuration. 530 static int dumpConfig() { 531 StringRef FileName; 532 std::unique_ptr<llvm::MemoryBuffer> Code; 533 if (FileNames.empty()) { 534 // We can't read the code to detect the language if there's no 535 // file name, so leave Code empty here. 536 FileName = AssumeFileName; 537 } else { 538 // Read in the code in case the filename alone isn't enough to 539 // detect the language. 540 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 541 MemoryBuffer::getFileOrSTDIN(FileNames[0]); 542 if (std::error_code EC = CodeOrErr.getError()) { 543 llvm::errs() << EC.message() << "\n"; 544 return 1; 545 } 546 FileName = (FileNames[0] == "-") ? AssumeFileName : FileNames[0]; 547 Code = std::move(CodeOrErr.get()); 548 } 549 llvm::Expected<clang::format::FormatStyle> FormatStyle = 550 clang::format::getStyle(Style, FileName, FallbackStyle, 551 Code ? Code->getBuffer() : ""); 552 if (!FormatStyle) { 553 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 554 return 1; 555 } 556 std::string Config = clang::format::configurationAsText(*FormatStyle); 557 outs() << Config << "\n"; 558 return 0; 559 } 560 561 int main(int argc, const char **argv) { 562 llvm::InitLLVM X(argc, argv); 563 564 cl::HideUnrelatedOptions(ClangFormatCategory); 565 566 cl::SetVersionPrinter(PrintVersion); 567 cl::ParseCommandLineOptions( 568 argc, argv, 569 "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# " 570 "code.\n\n" 571 "If no arguments are specified, it formats the code from standard input\n" 572 "and writes the result to the standard output.\n" 573 "If <file>s are given, it reformats the files. If -i is specified\n" 574 "together with <file>s, the files are edited in-place. Otherwise, the\n" 575 "result is written to the standard output.\n"); 576 577 if (Help) { 578 cl::PrintHelpMessage(); 579 return 0; 580 } 581 582 if (DumpConfig) { 583 return dumpConfig(); 584 } 585 586 if (!Files.empty()) { 587 std::ifstream ExternalFileOfFiles{std::string(Files)}; 588 std::string Line; 589 unsigned LineNo = 1; 590 while (std::getline(ExternalFileOfFiles, Line)) { 591 FileNames.push_back(Line); 592 LineNo++; 593 } 594 errs() << "Clang-formating " << LineNo << " files\n"; 595 } 596 597 bool Error = false; 598 if (FileNames.empty()) { 599 Error = clang::format::format("-"); 600 return Error ? 1 : 0; 601 } 602 if (FileNames.size() != 1 && 603 (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) { 604 errs() << "error: -offset, -length and -lines can only be used for " 605 "single file.\n"; 606 return 1; 607 } 608 609 unsigned FileNo = 1; 610 for (const auto &FileName : FileNames) { 611 if (Verbose) 612 errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] " 613 << FileName << "\n"; 614 Error |= clang::format::format(FileName); 615 } 616 return Error ? 1 : 0; 617 } 618