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 "../../lib/Format/MatchFilePath.h" 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/StringSwitch.h" 24 #include "llvm/Support/CommandLine.h" 25 #include "llvm/Support/FileSystem.h" 26 #include "llvm/Support/InitLLVM.h" 27 #include "llvm/Support/Process.h" 28 #include <fstream> 29 30 using namespace llvm; 31 using clang::tooling::Replacements; 32 33 static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden); 34 35 // Mark all our options with this category, everything else (except for -version 36 // and -help) will be hidden. 37 static cl::OptionCategory ClangFormatCategory("Clang-format options"); 38 39 static cl::list<unsigned> 40 Offsets("offset", 41 cl::desc("Format a range starting at this byte offset.\n" 42 "Multiple ranges can be formatted by specifying\n" 43 "several -offset and -length pairs.\n" 44 "Can only be used with one input file."), 45 cl::cat(ClangFormatCategory)); 46 static cl::list<unsigned> 47 Lengths("length", 48 cl::desc("Format a range of this length (in bytes).\n" 49 "Multiple ranges can be formatted by specifying\n" 50 "several -offset and -length pairs.\n" 51 "When only a single -offset is specified without\n" 52 "-length, clang-format will format up to the end\n" 53 "of the file.\n" 54 "Can only be used with one input file."), 55 cl::cat(ClangFormatCategory)); 56 static cl::list<std::string> 57 LineRanges("lines", 58 cl::desc("<start line>:<end line> - format a range of\n" 59 "lines (both 1-based).\n" 60 "Multiple ranges can be formatted by specifying\n" 61 "several -lines arguments.\n" 62 "Can't be used with -offset and -length.\n" 63 "Can only be used with one input file."), 64 cl::cat(ClangFormatCategory)); 65 static cl::opt<std::string> 66 Style("style", cl::desc(clang::format::StyleOptionHelpDescription), 67 cl::init(clang::format::DefaultFormatStyle), 68 cl::cat(ClangFormatCategory)); 69 static cl::opt<std::string> 70 FallbackStyle("fallback-style", 71 cl::desc("The name of the predefined style used as a\n" 72 "fallback in case clang-format is invoked with\n" 73 "-style=file, but can not find the .clang-format\n" 74 "file to use. Defaults to 'LLVM'.\n" 75 "Use -fallback-style=none to skip formatting."), 76 cl::init(clang::format::DefaultFallbackStyle), 77 cl::cat(ClangFormatCategory)); 78 79 static cl::opt<std::string> AssumeFileName( 80 "assume-filename", 81 cl::desc("Set filename used to determine the language and to find\n" 82 ".clang-format file.\n" 83 "Only used when reading from stdin.\n" 84 "If this is not passed, the .clang-format file is searched\n" 85 "relative to the current working directory when reading stdin.\n" 86 "Unrecognized filenames are treated as C++.\n" 87 "supported:\n" 88 " CSharp: .cs\n" 89 " Java: .java\n" 90 " JavaScript: .mjs .js .ts\n" 91 " Json: .json\n" 92 " Objective-C: .m .mm\n" 93 " Proto: .proto .protodevel\n" 94 " TableGen: .td\n" 95 " TextProto: .txtpb .textpb .pb.txt .textproto .asciipb\n" 96 " Verilog: .sv .svh .v .vh"), 97 cl::init("<stdin>"), cl::cat(ClangFormatCategory)); 98 99 static cl::opt<bool> Inplace("i", 100 cl::desc("Inplace edit <file>s, if specified."), 101 cl::cat(ClangFormatCategory)); 102 103 static cl::opt<bool> OutputXML("output-replacements-xml", 104 cl::desc("Output replacements as XML."), 105 cl::cat(ClangFormatCategory)); 106 static cl::opt<bool> 107 DumpConfig("dump-config", 108 cl::desc("Dump configuration options to stdout and exit.\n" 109 "Can be used with -style option."), 110 cl::cat(ClangFormatCategory)); 111 static cl::opt<unsigned> 112 Cursor("cursor", 113 cl::desc("The position of the cursor when invoking\n" 114 "clang-format from an editor integration"), 115 cl::init(0), cl::cat(ClangFormatCategory)); 116 117 static cl::opt<bool> 118 SortIncludes("sort-includes", 119 cl::desc("If set, overrides the include sorting behavior\n" 120 "determined by the SortIncludes style flag"), 121 cl::cat(ClangFormatCategory)); 122 123 static cl::opt<std::string> QualifierAlignment( 124 "qualifier-alignment", 125 cl::desc("If set, overrides the qualifier alignment style\n" 126 "determined by the QualifierAlignment style flag"), 127 cl::init(""), cl::cat(ClangFormatCategory)); 128 129 static cl::opt<std::string> Files( 130 "files", 131 cl::desc("A file containing a list of files to process, one per line."), 132 cl::value_desc("filename"), cl::init(""), cl::cat(ClangFormatCategory)); 133 134 static cl::opt<bool> 135 Verbose("verbose", cl::desc("If set, shows the list of processed files"), 136 cl::cat(ClangFormatCategory)); 137 138 // Use --dry-run to match other LLVM tools when you mean do it but don't 139 // actually do it 140 static cl::opt<bool> 141 DryRun("dry-run", 142 cl::desc("If set, do not actually make the formatting changes"), 143 cl::cat(ClangFormatCategory)); 144 145 // Use -n as a common command as an alias for --dry-run. (git and make use -n) 146 static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"), 147 cl::cat(ClangFormatCategory), cl::aliasopt(DryRun), 148 cl::NotHidden); 149 150 // Emulate being able to turn on/off the warning. 151 static cl::opt<bool> 152 WarnFormat("Wclang-format-violations", 153 cl::desc("Warnings about individual formatting changes needed. " 154 "Used only with --dry-run or -n"), 155 cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden); 156 157 static cl::opt<bool> 158 NoWarnFormat("Wno-clang-format-violations", 159 cl::desc("Do not warn about individual formatting changes " 160 "needed. Used only with --dry-run or -n"), 161 cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden); 162 163 static cl::opt<unsigned> ErrorLimit( 164 "ferror-limit", 165 cl::desc("Set the maximum number of clang-format errors to emit\n" 166 "before stopping (0 = no limit).\n" 167 "Used only with --dry-run or -n"), 168 cl::init(0), cl::cat(ClangFormatCategory)); 169 170 static cl::opt<bool> 171 WarningsAsErrors("Werror", 172 cl::desc("If set, changes formatting warnings to errors"), 173 cl::cat(ClangFormatCategory)); 174 175 namespace { 176 enum class WNoError { Unknown }; 177 } 178 179 static cl::bits<WNoError> WNoErrorList( 180 "Wno-error", 181 cl::desc("If set don't error out on the specified warning type."), 182 cl::values( 183 clEnumValN(WNoError::Unknown, "unknown", 184 "If set, unknown format options are only warned about.\n" 185 "This can be used to enable formatting, even if the\n" 186 "configuration contains unknown (newer) options.\n" 187 "Use with caution, as this might lead to dramatically\n" 188 "differing format depending on an option being\n" 189 "supported or not.")), 190 cl::cat(ClangFormatCategory)); 191 192 static cl::opt<bool> 193 ShowColors("fcolor-diagnostics", 194 cl::desc("If set, and on a color-capable terminal controls " 195 "whether or not to print diagnostics in color"), 196 cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden); 197 198 static cl::opt<bool> 199 NoShowColors("fno-color-diagnostics", 200 cl::desc("If set, and on a color-capable terminal controls " 201 "whether or not to print diagnostics in color"), 202 cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden); 203 204 static cl::list<std::string> FileNames(cl::Positional, 205 cl::desc("[@<file>] [<file> ...]"), 206 cl::cat(ClangFormatCategory)); 207 208 static cl::opt<bool> FailOnIncompleteFormat( 209 "fail-on-incomplete-format", 210 cl::desc("If set, fail with exit code 1 on incomplete format."), 211 cl::init(false), cl::cat(ClangFormatCategory)); 212 213 namespace clang { 214 namespace format { 215 216 static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source, 217 SourceManager &Sources, FileManager &Files, 218 llvm::vfs::InMemoryFileSystem *MemFS) { 219 MemFS->addFileNoOwn(FileName, 0, Source); 220 auto File = Files.getOptionalFileRef(FileName); 221 assert(File && "File not added to MemFS?"); 222 return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User); 223 } 224 225 // Parses <start line>:<end line> input to a pair of line numbers. 226 // Returns true on error. 227 static bool parseLineRange(StringRef Input, unsigned &FromLine, 228 unsigned &ToLine) { 229 std::pair<StringRef, StringRef> LineRange = Input.split(':'); 230 return LineRange.first.getAsInteger(0, FromLine) || 231 LineRange.second.getAsInteger(0, ToLine); 232 } 233 234 static bool fillRanges(MemoryBuffer *Code, 235 std::vector<tooling::Range> &Ranges) { 236 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 237 new llvm::vfs::InMemoryFileSystem); 238 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 239 DiagnosticsEngine Diagnostics( 240 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 241 new DiagnosticOptions); 242 SourceManager Sources(Diagnostics, Files); 243 FileID ID = createInMemoryFile("<irrelevant>", *Code, Sources, Files, 244 InMemoryFileSystem.get()); 245 if (!LineRanges.empty()) { 246 if (!Offsets.empty() || !Lengths.empty()) { 247 errs() << "error: cannot use -lines with -offset/-length\n"; 248 return true; 249 } 250 251 for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) { 252 unsigned FromLine, ToLine; 253 if (parseLineRange(LineRanges[i], FromLine, ToLine)) { 254 errs() << "error: invalid <start line>:<end line> pair\n"; 255 return true; 256 } 257 if (FromLine < 1) { 258 errs() << "error: start line should be at least 1\n"; 259 return true; 260 } 261 if (FromLine > ToLine) { 262 errs() << "error: start line should not exceed end line\n"; 263 return true; 264 } 265 SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1); 266 SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX); 267 if (Start.isInvalid() || End.isInvalid()) 268 return true; 269 unsigned Offset = Sources.getFileOffset(Start); 270 unsigned Length = Sources.getFileOffset(End) - Offset; 271 Ranges.push_back(tooling::Range(Offset, Length)); 272 } 273 return false; 274 } 275 276 if (Offsets.empty()) 277 Offsets.push_back(0); 278 if (Offsets.size() != Lengths.size() && 279 !(Offsets.size() == 1 && Lengths.empty())) { 280 errs() << "error: number of -offset and -length arguments must match.\n"; 281 return true; 282 } 283 for (unsigned i = 0, e = Offsets.size(); i != e; ++i) { 284 if (Offsets[i] >= Code->getBufferSize()) { 285 errs() << "error: offset " << Offsets[i] << " is outside the file\n"; 286 return true; 287 } 288 SourceLocation Start = 289 Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]); 290 SourceLocation End; 291 if (i < Lengths.size()) { 292 if (Offsets[i] + Lengths[i] > Code->getBufferSize()) { 293 errs() << "error: invalid length " << Lengths[i] 294 << ", offset + length (" << Offsets[i] + Lengths[i] 295 << ") is outside the file.\n"; 296 return true; 297 } 298 End = Start.getLocWithOffset(Lengths[i]); 299 } else { 300 End = Sources.getLocForEndOfFile(ID); 301 } 302 unsigned Offset = Sources.getFileOffset(Start); 303 unsigned Length = Sources.getFileOffset(End) - Offset; 304 Ranges.push_back(tooling::Range(Offset, Length)); 305 } 306 return false; 307 } 308 309 static void outputReplacementXML(StringRef Text) { 310 // FIXME: When we sort includes, we need to make sure the stream is correct 311 // utf-8. 312 size_t From = 0; 313 size_t Index; 314 while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) { 315 outs() << Text.substr(From, Index - From); 316 switch (Text[Index]) { 317 case '\n': 318 outs() << " "; 319 break; 320 case '\r': 321 outs() << " "; 322 break; 323 case '<': 324 outs() << "<"; 325 break; 326 case '&': 327 outs() << "&"; 328 break; 329 default: 330 llvm_unreachable("Unexpected character encountered!"); 331 } 332 From = Index + 1; 333 } 334 outs() << Text.substr(From); 335 } 336 337 static void outputReplacementsXML(const Replacements &Replaces) { 338 for (const auto &R : Replaces) { 339 outs() << "<replacement " << "offset='" << R.getOffset() << "' " 340 << "length='" << R.getLength() << "'>"; 341 outputReplacementXML(R.getReplacementText()); 342 outs() << "</replacement>\n"; 343 } 344 } 345 346 static bool 347 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName, 348 const std::unique_ptr<llvm::MemoryBuffer> &Code) { 349 if (Replaces.empty()) 350 return false; 351 352 unsigned Errors = 0; 353 if (WarnFormat && !NoWarnFormat) { 354 llvm::SourceMgr Mgr; 355 const char *StartBuf = Code->getBufferStart(); 356 357 Mgr.AddNewSourceBuffer( 358 MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc()); 359 for (const auto &R : Replaces) { 360 SMDiagnostic Diag = Mgr.GetMessage( 361 SMLoc::getFromPointer(StartBuf + R.getOffset()), 362 WarningsAsErrors ? SourceMgr::DiagKind::DK_Error 363 : SourceMgr::DiagKind::DK_Warning, 364 "code should be clang-formatted [-Wclang-format-violations]"); 365 366 Diag.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors)); 367 if (ErrorLimit && ++Errors >= ErrorLimit) 368 break; 369 } 370 } 371 return WarningsAsErrors; 372 } 373 374 static void outputXML(const Replacements &Replaces, 375 const Replacements &FormatChanges, 376 const FormattingAttemptStatus &Status, 377 const cl::opt<unsigned> &Cursor, 378 unsigned CursorPosition) { 379 outs() << "<?xml version='1.0'?>\n<replacements " 380 "xml:space='preserve' incomplete_format='" 381 << (Status.FormatComplete ? "false" : "true") << "'"; 382 if (!Status.FormatComplete) 383 outs() << " line='" << Status.Line << "'"; 384 outs() << ">\n"; 385 if (Cursor.getNumOccurrences() != 0) { 386 outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition) 387 << "</cursor>\n"; 388 } 389 390 outputReplacementsXML(Replaces); 391 outs() << "</replacements>\n"; 392 } 393 394 class ClangFormatDiagConsumer : public DiagnosticConsumer { 395 virtual void anchor() {} 396 397 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 398 const Diagnostic &Info) override { 399 400 SmallVector<char, 16> vec; 401 Info.FormatDiagnostic(vec); 402 errs() << "clang-format error:" << vec << "\n"; 403 } 404 }; 405 406 // Returns true on error. 407 static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) { 408 const bool IsSTDIN = FileName == "-"; 409 if (!OutputXML && Inplace && IsSTDIN) { 410 errs() << "error: cannot use -i when reading from stdin.\n"; 411 return false; 412 } 413 // On Windows, overwriting a file with an open file mapping doesn't work, 414 // so read the whole file into memory when formatting in-place. 415 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 416 !OutputXML && Inplace 417 ? MemoryBuffer::getFileAsStream(FileName) 418 : MemoryBuffer::getFileOrSTDIN(FileName, /*IsText=*/true); 419 if (std::error_code EC = CodeOrErr.getError()) { 420 errs() << EC.message() << "\n"; 421 return true; 422 } 423 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get()); 424 if (Code->getBufferSize() == 0) 425 return false; // Empty files are formatted correctly. 426 427 StringRef BufStr = Code->getBuffer(); 428 429 const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr); 430 431 if (InvalidBOM) { 432 errs() << "error: encoding with unsupported byte order mark \"" 433 << InvalidBOM << "\" detected"; 434 if (!IsSTDIN) 435 errs() << " in file '" << FileName << "'"; 436 errs() << ".\n"; 437 return true; 438 } 439 440 std::vector<tooling::Range> Ranges; 441 if (fillRanges(Code.get(), Ranges)) 442 return true; 443 StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName; 444 if (AssumedFileName.empty()) { 445 llvm::errs() << "error: empty filenames are not allowed\n"; 446 return true; 447 } 448 449 llvm::Expected<FormatStyle> FormatStyle = 450 getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(), 451 nullptr, WNoErrorList.isSet(WNoError::Unknown)); 452 if (!FormatStyle) { 453 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 454 return true; 455 } 456 457 StringRef QualifierAlignmentOrder = QualifierAlignment; 458 459 FormatStyle->QualifierAlignment = 460 StringSwitch<FormatStyle::QualifierAlignmentStyle>( 461 QualifierAlignmentOrder.lower()) 462 .Case("right", FormatStyle::QAS_Right) 463 .Case("left", FormatStyle::QAS_Left) 464 .Default(FormatStyle->QualifierAlignment); 465 466 if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) { 467 FormatStyle->QualifierOrder = {"const", "volatile", "type"}; 468 } else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) { 469 FormatStyle->QualifierOrder = {"type", "const", "volatile"}; 470 } else if (QualifierAlignmentOrder.contains("type")) { 471 FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom; 472 SmallVector<StringRef> Qualifiers; 473 QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1, 474 /*KeepEmpty=*/false); 475 FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()}; 476 } 477 478 if (SortIncludes.getNumOccurrences() != 0) { 479 if (SortIncludes) 480 FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive; 481 else 482 FormatStyle->SortIncludes = FormatStyle::SI_Never; 483 } 484 unsigned CursorPosition = Cursor; 485 Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges, 486 AssumedFileName, &CursorPosition); 487 488 // To format JSON insert a variable to trick the code into thinking its 489 // JavaScript. 490 if (FormatStyle->isJson() && !FormatStyle->DisableFormat) { 491 auto Err = Replaces.add(tooling::Replacement( 492 tooling::Replacement(AssumedFileName, 0, 0, "x = "))); 493 if (Err) 494 llvm::errs() << "Bad Json variable insertion\n"; 495 } 496 497 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); 498 if (!ChangedCode) { 499 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; 500 return true; 501 } 502 // Get new affected ranges after sorting `#includes`. 503 Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges); 504 FormattingAttemptStatus Status; 505 Replacements FormatChanges = 506 reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status); 507 Replaces = Replaces.merge(FormatChanges); 508 if (OutputXML || DryRun) { 509 if (DryRun) 510 return emitReplacementWarnings(Replaces, AssumedFileName, Code); 511 else 512 outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition); 513 } else { 514 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 515 new llvm::vfs::InMemoryFileSystem); 516 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 517 518 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions()); 519 ClangFormatDiagConsumer IgnoreDiagnostics; 520 DiagnosticsEngine Diagnostics( 521 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, 522 &IgnoreDiagnostics, false); 523 SourceManager Sources(Diagnostics, Files); 524 FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files, 525 InMemoryFileSystem.get()); 526 Rewriter Rewrite(Sources, LangOptions()); 527 tooling::applyAllReplacements(Replaces, Rewrite); 528 if (Inplace) { 529 if (Rewrite.overwriteChangedFiles()) 530 return true; 531 } else { 532 if (Cursor.getNumOccurrences() != 0) { 533 outs() << "{ \"Cursor\": " 534 << FormatChanges.getShiftedCodePosition(CursorPosition) 535 << ", \"IncompleteFormat\": " 536 << (Status.FormatComplete ? "false" : "true"); 537 if (!Status.FormatComplete) 538 outs() << ", \"Line\": " << Status.Line; 539 outs() << " }\n"; 540 } 541 Rewrite.getEditBuffer(ID).write(outs()); 542 } 543 } 544 return ErrorOnIncompleteFormat && !Status.FormatComplete; 545 } 546 547 } // namespace format 548 } // namespace clang 549 550 static void PrintVersion(raw_ostream &OS) { 551 OS << clang::getClangToolFullVersion("clang-format") << '\n'; 552 } 553 554 // Dump the configuration. 555 static int dumpConfig() { 556 std::unique_ptr<llvm::MemoryBuffer> Code; 557 // We can't read the code to detect the language if there's no file name. 558 if (!FileNames.empty()) { 559 // Read in the code in case the filename alone isn't enough to detect the 560 // language. 561 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 562 MemoryBuffer::getFileOrSTDIN(FileNames[0], /*IsText=*/true); 563 if (std::error_code EC = CodeOrErr.getError()) { 564 llvm::errs() << EC.message() << "\n"; 565 return 1; 566 } 567 Code = std::move(CodeOrErr.get()); 568 } 569 llvm::Expected<clang::format::FormatStyle> FormatStyle = 570 clang::format::getStyle(Style, 571 FileNames.empty() || FileNames[0] == "-" 572 ? AssumeFileName 573 : FileNames[0], 574 FallbackStyle, Code ? Code->getBuffer() : ""); 575 if (!FormatStyle) { 576 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 577 return 1; 578 } 579 std::string Config = clang::format::configurationAsText(*FormatStyle); 580 outs() << Config << "\n"; 581 return 0; 582 } 583 584 using String = SmallString<128>; 585 static String IgnoreDir; // Directory of .clang-format-ignore file. 586 static String PrevDir; // Directory of previous `FilePath`. 587 static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file. 588 589 // Check whether `FilePath` is ignored according to the nearest 590 // .clang-format-ignore file based on the rules below: 591 // - A blank line is skipped. 592 // - Leading and trailing spaces of a line are trimmed. 593 // - A line starting with a hash (`#`) is a comment. 594 // - A non-comment line is a single pattern. 595 // - The slash (`/`) is used as the directory separator. 596 // - A pattern is relative to the directory of the .clang-format-ignore file (or 597 // the root directory if the pattern starts with a slash). 598 // - A pattern is negated if it starts with a bang (`!`). 599 static bool isIgnored(StringRef FilePath) { 600 using namespace llvm::sys::fs; 601 if (!is_regular_file(FilePath)) 602 return false; 603 604 String Path; 605 String AbsPath{FilePath}; 606 607 using namespace llvm::sys::path; 608 make_absolute(AbsPath); 609 remove_dots(AbsPath, /*remove_dot_dot=*/true); 610 611 if (StringRef Dir{parent_path(AbsPath)}; PrevDir != Dir) { 612 PrevDir = Dir; 613 614 for (;;) { 615 Path = Dir; 616 append(Path, ".clang-format-ignore"); 617 if (is_regular_file(Path)) 618 break; 619 Dir = parent_path(Dir); 620 if (Dir.empty()) 621 return false; 622 } 623 624 IgnoreDir = convert_to_slash(Dir); 625 626 std::ifstream IgnoreFile{Path.c_str()}; 627 if (!IgnoreFile.good()) 628 return false; 629 630 Patterns.clear(); 631 632 for (std::string Line; std::getline(IgnoreFile, Line);) { 633 if (const auto Pattern{StringRef{Line}.trim()}; 634 // Skip empty and comment lines. 635 !Pattern.empty() && Pattern[0] != '#') { 636 Patterns.push_back(Pattern); 637 } 638 } 639 } 640 641 if (IgnoreDir.empty()) 642 return false; 643 644 const auto Pathname{convert_to_slash(AbsPath)}; 645 for (const auto &Pat : Patterns) { 646 const bool IsNegated = Pat[0] == '!'; 647 StringRef Pattern{Pat}; 648 if (IsNegated) 649 Pattern = Pattern.drop_front(); 650 651 if (Pattern.empty()) 652 continue; 653 654 Pattern = Pattern.ltrim(); 655 656 // `Pattern` is relative to `IgnoreDir` unless it starts with a slash. 657 // This doesn't support patterns containing drive names (e.g. `C:`). 658 if (Pattern[0] != '/') { 659 Path = IgnoreDir; 660 append(Path, Style::posix, Pattern); 661 remove_dots(Path, /*remove_dot_dot=*/true, Style::posix); 662 Pattern = Path; 663 } 664 665 if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated) 666 return true; 667 } 668 669 return false; 670 } 671 672 int main(int argc, const char **argv) { 673 llvm::InitLLVM X(argc, argv); 674 675 cl::HideUnrelatedOptions(ClangFormatCategory); 676 677 cl::SetVersionPrinter(PrintVersion); 678 cl::ParseCommandLineOptions( 679 argc, argv, 680 "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# " 681 "code.\n\n" 682 "If no arguments are specified, it formats the code from standard input\n" 683 "and writes the result to the standard output.\n" 684 "If <file>s are given, it reformats the files. If -i is specified\n" 685 "together with <file>s, the files are edited in-place. Otherwise, the\n" 686 "result is written to the standard output.\n"); 687 688 if (Help) { 689 cl::PrintHelpMessage(); 690 return 0; 691 } 692 693 if (DumpConfig) 694 return dumpConfig(); 695 696 if (!Files.empty()) { 697 std::ifstream ExternalFileOfFiles{std::string(Files)}; 698 std::string Line; 699 unsigned LineNo = 1; 700 while (std::getline(ExternalFileOfFiles, Line)) { 701 FileNames.push_back(Line); 702 LineNo++; 703 } 704 errs() << "Clang-formating " << LineNo << " files\n"; 705 } 706 707 if (FileNames.empty()) 708 return clang::format::format("-", FailOnIncompleteFormat); 709 710 if (FileNames.size() > 1 && 711 (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) { 712 errs() << "error: -offset, -length and -lines can only be used for " 713 "single file.\n"; 714 return 1; 715 } 716 717 unsigned FileNo = 1; 718 bool Error = false; 719 for (const auto &FileName : FileNames) { 720 if (isIgnored(FileName)) 721 continue; 722 if (Verbose) { 723 errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] " 724 << FileName << "\n"; 725 } 726 Error |= clang::format::format(FileName, FailOnIncompleteFormat); 727 } 728 return Error ? 1 : 0; 729 } 730