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: .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 namespace clang { 209 namespace format { 210 211 static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source, 212 SourceManager &Sources, FileManager &Files, 213 llvm::vfs::InMemoryFileSystem *MemFS) { 214 MemFS->addFileNoOwn(FileName, 0, Source); 215 auto File = Files.getOptionalFileRef(FileName); 216 assert(File && "File not added to MemFS?"); 217 return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User); 218 } 219 220 // Parses <start line>:<end line> input to a pair of line numbers. 221 // Returns true on error. 222 static bool parseLineRange(StringRef Input, unsigned &FromLine, 223 unsigned &ToLine) { 224 std::pair<StringRef, StringRef> LineRange = Input.split(':'); 225 return LineRange.first.getAsInteger(0, FromLine) || 226 LineRange.second.getAsInteger(0, ToLine); 227 } 228 229 static bool fillRanges(MemoryBuffer *Code, 230 std::vector<tooling::Range> &Ranges) { 231 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 232 new llvm::vfs::InMemoryFileSystem); 233 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 234 DiagnosticsEngine Diagnostics( 235 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 236 new DiagnosticOptions); 237 SourceManager Sources(Diagnostics, Files); 238 FileID ID = createInMemoryFile("<irrelevant>", *Code, Sources, Files, 239 InMemoryFileSystem.get()); 240 if (!LineRanges.empty()) { 241 if (!Offsets.empty() || !Lengths.empty()) { 242 errs() << "error: cannot use -lines with -offset/-length\n"; 243 return true; 244 } 245 246 for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) { 247 unsigned FromLine, ToLine; 248 if (parseLineRange(LineRanges[i], FromLine, ToLine)) { 249 errs() << "error: invalid <start line>:<end line> pair\n"; 250 return true; 251 } 252 if (FromLine < 1) { 253 errs() << "error: start line should be at least 1\n"; 254 return true; 255 } 256 if (FromLine > ToLine) { 257 errs() << "error: start line should not exceed end line\n"; 258 return true; 259 } 260 SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1); 261 SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX); 262 if (Start.isInvalid() || End.isInvalid()) 263 return true; 264 unsigned Offset = Sources.getFileOffset(Start); 265 unsigned Length = Sources.getFileOffset(End) - Offset; 266 Ranges.push_back(tooling::Range(Offset, Length)); 267 } 268 return false; 269 } 270 271 if (Offsets.empty()) 272 Offsets.push_back(0); 273 if (Offsets.size() != Lengths.size() && 274 !(Offsets.size() == 1 && Lengths.empty())) { 275 errs() << "error: number of -offset and -length arguments must match.\n"; 276 return true; 277 } 278 for (unsigned i = 0, e = Offsets.size(); i != e; ++i) { 279 if (Offsets[i] >= Code->getBufferSize()) { 280 errs() << "error: offset " << Offsets[i] << " is outside the file\n"; 281 return true; 282 } 283 SourceLocation Start = 284 Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]); 285 SourceLocation End; 286 if (i < Lengths.size()) { 287 if (Offsets[i] + Lengths[i] > Code->getBufferSize()) { 288 errs() << "error: invalid length " << Lengths[i] 289 << ", offset + length (" << Offsets[i] + Lengths[i] 290 << ") is outside the file.\n"; 291 return true; 292 } 293 End = Start.getLocWithOffset(Lengths[i]); 294 } else { 295 End = Sources.getLocForEndOfFile(ID); 296 } 297 unsigned Offset = Sources.getFileOffset(Start); 298 unsigned Length = Sources.getFileOffset(End) - Offset; 299 Ranges.push_back(tooling::Range(Offset, Length)); 300 } 301 return false; 302 } 303 304 static void outputReplacementXML(StringRef Text) { 305 // FIXME: When we sort includes, we need to make sure the stream is correct 306 // utf-8. 307 size_t From = 0; 308 size_t Index; 309 while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) { 310 outs() << Text.substr(From, Index - From); 311 switch (Text[Index]) { 312 case '\n': 313 outs() << " "; 314 break; 315 case '\r': 316 outs() << " "; 317 break; 318 case '<': 319 outs() << "<"; 320 break; 321 case '&': 322 outs() << "&"; 323 break; 324 default: 325 llvm_unreachable("Unexpected character encountered!"); 326 } 327 From = Index + 1; 328 } 329 outs() << Text.substr(From); 330 } 331 332 static void outputReplacementsXML(const Replacements &Replaces) { 333 for (const auto &R : Replaces) { 334 outs() << "<replacement " << "offset='" << R.getOffset() << "' " 335 << "length='" << R.getLength() << "'>"; 336 outputReplacementXML(R.getReplacementText()); 337 outs() << "</replacement>\n"; 338 } 339 } 340 341 static bool 342 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName, 343 const std::unique_ptr<llvm::MemoryBuffer> &Code) { 344 if (Replaces.empty()) 345 return false; 346 347 unsigned Errors = 0; 348 if (WarnFormat && !NoWarnFormat) { 349 llvm::SourceMgr Mgr; 350 const char *StartBuf = Code->getBufferStart(); 351 352 Mgr.AddNewSourceBuffer( 353 MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc()); 354 for (const auto &R : Replaces) { 355 SMDiagnostic Diag = Mgr.GetMessage( 356 SMLoc::getFromPointer(StartBuf + R.getOffset()), 357 WarningsAsErrors ? SourceMgr::DiagKind::DK_Error 358 : SourceMgr::DiagKind::DK_Warning, 359 "code should be clang-formatted [-Wclang-format-violations]"); 360 361 Diag.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors)); 362 if (ErrorLimit && ++Errors >= ErrorLimit) 363 break; 364 } 365 } 366 return WarningsAsErrors; 367 } 368 369 static void outputXML(const Replacements &Replaces, 370 const Replacements &FormatChanges, 371 const FormattingAttemptStatus &Status, 372 const cl::opt<unsigned> &Cursor, 373 unsigned CursorPosition) { 374 outs() << "<?xml version='1.0'?>\n<replacements " 375 "xml:space='preserve' incomplete_format='" 376 << (Status.FormatComplete ? "false" : "true") << "'"; 377 if (!Status.FormatComplete) 378 outs() << " line='" << Status.Line << "'"; 379 outs() << ">\n"; 380 if (Cursor.getNumOccurrences() != 0) { 381 outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition) 382 << "</cursor>\n"; 383 } 384 385 outputReplacementsXML(Replaces); 386 outs() << "</replacements>\n"; 387 } 388 389 class ClangFormatDiagConsumer : public DiagnosticConsumer { 390 virtual void anchor() {} 391 392 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 393 const Diagnostic &Info) override { 394 395 SmallVector<char, 16> vec; 396 Info.FormatDiagnostic(vec); 397 errs() << "clang-format error:" << vec << "\n"; 398 } 399 }; 400 401 // Returns true on error. 402 static bool format(StringRef FileName) { 403 const bool IsSTDIN = FileName == "-"; 404 if (!OutputXML && Inplace && IsSTDIN) { 405 errs() << "error: cannot use -i when reading from stdin.\n"; 406 return false; 407 } 408 // On Windows, overwriting a file with an open file mapping doesn't work, 409 // so read the whole file into memory when formatting in-place. 410 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 411 !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName) 412 : MemoryBuffer::getFileOrSTDIN(FileName); 413 if (std::error_code EC = CodeOrErr.getError()) { 414 errs() << EC.message() << "\n"; 415 return true; 416 } 417 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get()); 418 if (Code->getBufferSize() == 0) 419 return false; // Empty files are formatted correctly. 420 421 StringRef BufStr = Code->getBuffer(); 422 423 const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr); 424 425 if (InvalidBOM) { 426 errs() << "error: encoding with unsupported byte order mark \"" 427 << InvalidBOM << "\" detected"; 428 if (!IsSTDIN) 429 errs() << " in file '" << FileName << "'"; 430 errs() << ".\n"; 431 return true; 432 } 433 434 std::vector<tooling::Range> Ranges; 435 if (fillRanges(Code.get(), Ranges)) 436 return true; 437 StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName; 438 if (AssumedFileName.empty()) { 439 llvm::errs() << "error: empty filenames are not allowed\n"; 440 return true; 441 } 442 443 llvm::Expected<FormatStyle> FormatStyle = 444 getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(), 445 nullptr, WNoErrorList.isSet(WNoError::Unknown)); 446 if (!FormatStyle) { 447 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 448 return true; 449 } 450 451 StringRef QualifierAlignmentOrder = QualifierAlignment; 452 453 FormatStyle->QualifierAlignment = 454 StringSwitch<FormatStyle::QualifierAlignmentStyle>( 455 QualifierAlignmentOrder.lower()) 456 .Case("right", FormatStyle::QAS_Right) 457 .Case("left", FormatStyle::QAS_Left) 458 .Default(FormatStyle->QualifierAlignment); 459 460 if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) { 461 FormatStyle->QualifierOrder = {"const", "volatile", "type"}; 462 } else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) { 463 FormatStyle->QualifierOrder = {"type", "const", "volatile"}; 464 } else if (QualifierAlignmentOrder.contains("type")) { 465 FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom; 466 SmallVector<StringRef> Qualifiers; 467 QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1, 468 /*KeepEmpty=*/false); 469 FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()}; 470 } 471 472 if (SortIncludes.getNumOccurrences() != 0) { 473 if (SortIncludes) 474 FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive; 475 else 476 FormatStyle->SortIncludes = FormatStyle::SI_Never; 477 } 478 unsigned CursorPosition = Cursor; 479 Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges, 480 AssumedFileName, &CursorPosition); 481 482 // To format JSON insert a variable to trick the code into thinking its 483 // JavaScript. 484 if (FormatStyle->isJson() && !FormatStyle->DisableFormat) { 485 auto Err = Replaces.add(tooling::Replacement( 486 tooling::Replacement(AssumedFileName, 0, 0, "x = "))); 487 if (Err) 488 llvm::errs() << "Bad Json variable insertion\n"; 489 } 490 491 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); 492 if (!ChangedCode) { 493 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; 494 return true; 495 } 496 // Get new affected ranges after sorting `#includes`. 497 Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges); 498 FormattingAttemptStatus Status; 499 Replacements FormatChanges = 500 reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status); 501 Replaces = Replaces.merge(FormatChanges); 502 if (OutputXML || DryRun) { 503 if (DryRun) 504 return emitReplacementWarnings(Replaces, AssumedFileName, Code); 505 else 506 outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition); 507 } else { 508 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 509 new llvm::vfs::InMemoryFileSystem); 510 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 511 512 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions()); 513 ClangFormatDiagConsumer IgnoreDiagnostics; 514 DiagnosticsEngine Diagnostics( 515 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, 516 &IgnoreDiagnostics, false); 517 SourceManager Sources(Diagnostics, Files); 518 FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files, 519 InMemoryFileSystem.get()); 520 Rewriter Rewrite(Sources, LangOptions()); 521 tooling::applyAllReplacements(Replaces, Rewrite); 522 if (Inplace) { 523 if (Rewrite.overwriteChangedFiles()) 524 return true; 525 } else { 526 if (Cursor.getNumOccurrences() != 0) { 527 outs() << "{ \"Cursor\": " 528 << FormatChanges.getShiftedCodePosition(CursorPosition) 529 << ", \"IncompleteFormat\": " 530 << (Status.FormatComplete ? "false" : "true"); 531 if (!Status.FormatComplete) 532 outs() << ", \"Line\": " << Status.Line; 533 outs() << " }\n"; 534 } 535 Rewrite.getEditBuffer(ID).write(outs()); 536 } 537 } 538 return false; 539 } 540 541 } // namespace format 542 } // namespace clang 543 544 static void PrintVersion(raw_ostream &OS) { 545 OS << clang::getClangToolFullVersion("clang-format") << '\n'; 546 } 547 548 // Dump the configuration. 549 static int dumpConfig() { 550 std::unique_ptr<llvm::MemoryBuffer> Code; 551 // We can't read the code to detect the language if there's no file name. 552 if (!FileNames.empty()) { 553 // Read in the code in case the filename alone isn't enough to detect the 554 // language. 555 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 556 MemoryBuffer::getFileOrSTDIN(FileNames[0]); 557 if (std::error_code EC = CodeOrErr.getError()) { 558 llvm::errs() << EC.message() << "\n"; 559 return 1; 560 } 561 Code = std::move(CodeOrErr.get()); 562 } 563 llvm::Expected<clang::format::FormatStyle> FormatStyle = 564 clang::format::getStyle(Style, 565 FileNames.empty() || FileNames[0] == "-" 566 ? AssumeFileName 567 : FileNames[0], 568 FallbackStyle, Code ? Code->getBuffer() : ""); 569 if (!FormatStyle) { 570 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 571 return 1; 572 } 573 std::string Config = clang::format::configurationAsText(*FormatStyle); 574 outs() << Config << "\n"; 575 return 0; 576 } 577 578 using String = SmallString<128>; 579 static String IgnoreDir; // Directory of .clang-format-ignore file. 580 static String PrevDir; // Directory of previous `FilePath`. 581 static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file. 582 583 // Check whether `FilePath` is ignored according to the nearest 584 // .clang-format-ignore file based on the rules below: 585 // - A blank line is skipped. 586 // - Leading and trailing spaces of a line are trimmed. 587 // - A line starting with a hash (`#`) is a comment. 588 // - A non-comment line is a single pattern. 589 // - The slash (`/`) is used as the directory separator. 590 // - A pattern is relative to the directory of the .clang-format-ignore file (or 591 // the root directory if the pattern starts with a slash). 592 // - A pattern is negated if it starts with a bang (`!`). 593 static bool isIgnored(StringRef FilePath) { 594 using namespace llvm::sys::fs; 595 if (!is_regular_file(FilePath)) 596 return false; 597 598 String Path; 599 String AbsPath{FilePath}; 600 601 using namespace llvm::sys::path; 602 make_absolute(AbsPath); 603 remove_dots(AbsPath, /*remove_dot_dot=*/true); 604 605 if (StringRef Dir{parent_path(AbsPath)}; PrevDir != Dir) { 606 PrevDir = Dir; 607 608 for (;;) { 609 Path = Dir; 610 append(Path, ".clang-format-ignore"); 611 if (is_regular_file(Path)) 612 break; 613 Dir = parent_path(Dir); 614 if (Dir.empty()) 615 return false; 616 } 617 618 IgnoreDir = convert_to_slash(Dir); 619 620 std::ifstream IgnoreFile{Path.c_str()}; 621 if (!IgnoreFile.good()) 622 return false; 623 624 Patterns.clear(); 625 626 for (std::string Line; std::getline(IgnoreFile, Line);) { 627 if (const auto Pattern{StringRef{Line}.trim()}; 628 // Skip empty and comment lines. 629 !Pattern.empty() && Pattern[0] != '#') { 630 Patterns.push_back(Pattern); 631 } 632 } 633 } 634 635 if (IgnoreDir.empty()) 636 return false; 637 638 const auto Pathname{convert_to_slash(AbsPath)}; 639 for (const auto &Pat : Patterns) { 640 const bool IsNegated = Pat[0] == '!'; 641 StringRef Pattern{Pat}; 642 if (IsNegated) 643 Pattern = Pattern.drop_front(); 644 645 if (Pattern.empty()) 646 continue; 647 648 Pattern = Pattern.ltrim(); 649 650 // `Pattern` is relative to `IgnoreDir` unless it starts with a slash. 651 // This doesn't support patterns containing drive names (e.g. `C:`). 652 if (Pattern[0] != '/') { 653 Path = IgnoreDir; 654 append(Path, Style::posix, Pattern); 655 remove_dots(Path, /*remove_dot_dot=*/true, Style::posix); 656 Pattern = Path; 657 } 658 659 if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated) 660 return true; 661 } 662 663 return false; 664 } 665 666 int main(int argc, const char **argv) { 667 llvm::InitLLVM X(argc, argv); 668 669 cl::HideUnrelatedOptions(ClangFormatCategory); 670 671 cl::SetVersionPrinter(PrintVersion); 672 cl::ParseCommandLineOptions( 673 argc, argv, 674 "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# " 675 "code.\n\n" 676 "If no arguments are specified, it formats the code from standard input\n" 677 "and writes the result to the standard output.\n" 678 "If <file>s are given, it reformats the files. If -i is specified\n" 679 "together with <file>s, the files are edited in-place. Otherwise, the\n" 680 "result is written to the standard output.\n"); 681 682 if (Help) { 683 cl::PrintHelpMessage(); 684 return 0; 685 } 686 687 if (DumpConfig) 688 return dumpConfig(); 689 690 if (!Files.empty()) { 691 std::ifstream ExternalFileOfFiles{std::string(Files)}; 692 std::string Line; 693 unsigned LineNo = 1; 694 while (std::getline(ExternalFileOfFiles, Line)) { 695 FileNames.push_back(Line); 696 LineNo++; 697 } 698 errs() << "Clang-formating " << LineNo << " files\n"; 699 } 700 701 if (FileNames.empty()) 702 return clang::format::format("-"); 703 704 if (FileNames.size() > 1 && 705 (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) { 706 errs() << "error: -offset, -length and -lines can only be used for " 707 "single file.\n"; 708 return 1; 709 } 710 711 unsigned FileNo = 1; 712 bool Error = false; 713 for (const auto &FileName : FileNames) { 714 if (isIgnored(FileName)) 715 continue; 716 if (Verbose) { 717 errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] " 718 << FileName << "\n"; 719 } 720 Error |= clang::format::format(FileName); 721 } 722 return Error ? 1 : 0; 723 } 724