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 " 340 << "offset='" << R.getOffset() << "' " 341 << "length='" << R.getLength() << "'>"; 342 outputReplacementXML(R.getReplacementText()); 343 outs() << "</replacement>\n"; 344 } 345 } 346 347 static bool 348 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName, 349 const std::unique_ptr<llvm::MemoryBuffer> &Code) { 350 if (Replaces.empty()) 351 return false; 352 353 unsigned Errors = 0; 354 if (WarnFormat && !NoWarnFormat) { 355 SourceMgr Mgr; 356 const char *StartBuf = Code->getBufferStart(); 357 358 Mgr.AddNewSourceBuffer( 359 MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc()); 360 for (const auto &R : Replaces) { 361 SMDiagnostic Diag = Mgr.GetMessage( 362 SMLoc::getFromPointer(StartBuf + R.getOffset()), 363 WarningsAsErrors ? SourceMgr::DiagKind::DK_Error 364 : SourceMgr::DiagKind::DK_Warning, 365 "code should be clang-formatted [-Wclang-format-violations]"); 366 367 Diag.print(nullptr, llvm::errs(), ShowColors && !NoShowColors); 368 if (ErrorLimit && ++Errors >= ErrorLimit) 369 break; 370 } 371 } 372 return WarningsAsErrors; 373 } 374 375 static void outputXML(const Replacements &Replaces, 376 const Replacements &FormatChanges, 377 const FormattingAttemptStatus &Status, 378 const cl::opt<unsigned> &Cursor, 379 unsigned CursorPosition) { 380 outs() << "<?xml version='1.0'?>\n<replacements " 381 "xml:space='preserve' incomplete_format='" 382 << (Status.FormatComplete ? "false" : "true") << "'"; 383 if (!Status.FormatComplete) 384 outs() << " line='" << Status.Line << "'"; 385 outs() << ">\n"; 386 if (Cursor.getNumOccurrences() != 0) { 387 outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition) 388 << "</cursor>\n"; 389 } 390 391 outputReplacementsXML(Replaces); 392 outs() << "</replacements>\n"; 393 } 394 395 class ClangFormatDiagConsumer : public DiagnosticConsumer { 396 virtual void anchor() {} 397 398 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 399 const Diagnostic &Info) override { 400 401 SmallVector<char, 16> vec; 402 Info.FormatDiagnostic(vec); 403 errs() << "clang-format error:" << vec << "\n"; 404 } 405 }; 406 407 // Returns true on error. 408 static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) { 409 const bool IsSTDIN = FileName == "-"; 410 if (!OutputXML && Inplace && IsSTDIN) { 411 errs() << "error: cannot use -i when reading from stdin.\n"; 412 return false; 413 } 414 // On Windows, overwriting a file with an open file mapping doesn't work, 415 // so read the whole file into memory when formatting in-place. 416 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 417 !OutputXML && Inplace 418 ? MemoryBuffer::getFileAsStream(FileName) 419 : MemoryBuffer::getFileOrSTDIN(FileName, /*IsText=*/true); 420 if (std::error_code EC = CodeOrErr.getError()) { 421 errs() << EC.message() << "\n"; 422 return true; 423 } 424 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get()); 425 if (Code->getBufferSize() == 0) 426 return false; // Empty files are formatted correctly. 427 428 StringRef BufStr = Code->getBuffer(); 429 430 const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr); 431 432 if (InvalidBOM) { 433 errs() << "error: encoding with unsupported byte order mark \"" 434 << InvalidBOM << "\" detected"; 435 if (!IsSTDIN) 436 errs() << " in file '" << FileName << "'"; 437 errs() << ".\n"; 438 return true; 439 } 440 441 std::vector<tooling::Range> Ranges; 442 if (fillRanges(Code.get(), Ranges)) 443 return true; 444 StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName; 445 if (AssumedFileName.empty()) { 446 llvm::errs() << "error: empty filenames are not allowed\n"; 447 return true; 448 } 449 450 Expected<FormatStyle> FormatStyle = 451 getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(), 452 nullptr, WNoErrorList.isSet(WNoError::Unknown)); 453 if (!FormatStyle) { 454 llvm::errs() << toString(FormatStyle.takeError()) << "\n"; 455 return true; 456 } 457 458 StringRef QualifierAlignmentOrder = QualifierAlignment; 459 460 FormatStyle->QualifierAlignment = 461 StringSwitch<FormatStyle::QualifierAlignmentStyle>( 462 QualifierAlignmentOrder.lower()) 463 .Case("right", FormatStyle::QAS_Right) 464 .Case("left", FormatStyle::QAS_Left) 465 .Default(FormatStyle->QualifierAlignment); 466 467 if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) { 468 FormatStyle->QualifierOrder = {"const", "volatile", "type"}; 469 } else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) { 470 FormatStyle->QualifierOrder = {"type", "const", "volatile"}; 471 } else if (QualifierAlignmentOrder.contains("type")) { 472 FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom; 473 SmallVector<StringRef> Qualifiers; 474 QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1, 475 /*KeepEmpty=*/false); 476 FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()}; 477 } 478 479 if (SortIncludes.getNumOccurrences() != 0) { 480 if (SortIncludes) 481 FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive; 482 else 483 FormatStyle->SortIncludes = FormatStyle::SI_Never; 484 } 485 unsigned CursorPosition = Cursor; 486 Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges, 487 AssumedFileName, &CursorPosition); 488 489 // To format JSON insert a variable to trick the code into thinking its 490 // JavaScript. 491 if (FormatStyle->isJson() && !FormatStyle->DisableFormat) { 492 auto Err = Replaces.add(tooling::Replacement( 493 tooling::Replacement(AssumedFileName, 0, 0, "x = "))); 494 if (Err) 495 llvm::errs() << "Bad Json variable insertion\n"; 496 } 497 498 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); 499 if (!ChangedCode) { 500 llvm::errs() << toString(ChangedCode.takeError()) << "\n"; 501 return true; 502 } 503 // Get new affected ranges after sorting `#includes`. 504 Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges); 505 FormattingAttemptStatus Status; 506 Replacements FormatChanges = 507 reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status); 508 Replaces = Replaces.merge(FormatChanges); 509 if (OutputXML || DryRun) { 510 if (DryRun) 511 return emitReplacementWarnings(Replaces, AssumedFileName, Code); 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 Expected<clang::format::FormatStyle> FormatStyle = clang::format::getStyle( 570 Style, 571 FileNames.empty() || FileNames[0] == "-" ? AssumeFileName : FileNames[0], 572 FallbackStyle, Code ? Code->getBuffer() : ""); 573 if (!FormatStyle) { 574 llvm::errs() << toString(FormatStyle.takeError()) << "\n"; 575 return 1; 576 } 577 std::string Config = clang::format::configurationAsText(*FormatStyle); 578 outs() << Config << "\n"; 579 return 0; 580 } 581 582 using String = SmallString<128>; 583 static String IgnoreDir; // Directory of .clang-format-ignore file. 584 static String PrevDir; // Directory of previous `FilePath`. 585 static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file. 586 587 // Check whether `FilePath` is ignored according to the nearest 588 // .clang-format-ignore file based on the rules below: 589 // - A blank line is skipped. 590 // - Leading and trailing spaces of a line are trimmed. 591 // - A line starting with a hash (`#`) is a comment. 592 // - A non-comment line is a single pattern. 593 // - The slash (`/`) is used as the directory separator. 594 // - A pattern is relative to the directory of the .clang-format-ignore file (or 595 // the root directory if the pattern starts with a slash). 596 // - A pattern is negated if it starts with a bang (`!`). 597 static bool isIgnored(StringRef FilePath) { 598 using namespace llvm::sys::fs; 599 if (!is_regular_file(FilePath)) 600 return false; 601 602 String Path; 603 String AbsPath{FilePath}; 604 605 using namespace llvm::sys::path; 606 make_absolute(AbsPath); 607 remove_dots(AbsPath, /*remove_dot_dot=*/true); 608 609 if (StringRef Dir{parent_path(AbsPath)}; PrevDir != Dir) { 610 PrevDir = Dir; 611 612 for (;;) { 613 Path = Dir; 614 append(Path, ".clang-format-ignore"); 615 if (is_regular_file(Path)) 616 break; 617 Dir = parent_path(Dir); 618 if (Dir.empty()) 619 return false; 620 } 621 622 IgnoreDir = convert_to_slash(Dir); 623 624 std::ifstream IgnoreFile{Path.c_str()}; 625 if (!IgnoreFile.good()) 626 return false; 627 628 Patterns.clear(); 629 630 for (std::string Line; std::getline(IgnoreFile, Line);) { 631 if (const auto Pattern{StringRef{Line}.trim()}; 632 // Skip empty and comment lines. 633 !Pattern.empty() && Pattern[0] != '#') { 634 Patterns.push_back(Pattern); 635 } 636 } 637 } 638 639 if (IgnoreDir.empty()) 640 return false; 641 642 const auto Pathname{convert_to_slash(AbsPath)}; 643 for (const auto &Pat : Patterns) { 644 const bool IsNegated = Pat[0] == '!'; 645 StringRef Pattern{Pat}; 646 if (IsNegated) 647 Pattern = Pattern.drop_front(); 648 649 if (Pattern.empty()) 650 continue; 651 652 Pattern = Pattern.ltrim(); 653 654 // `Pattern` is relative to `IgnoreDir` unless it starts with a slash. 655 // This doesn't support patterns containing drive names (e.g. `C:`). 656 if (Pattern[0] != '/') { 657 Path = IgnoreDir; 658 append(Path, Style::posix, Pattern); 659 remove_dots(Path, /*remove_dot_dot=*/true, Style::posix); 660 Pattern = Path; 661 } 662 663 if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated) 664 return true; 665 } 666 667 return false; 668 } 669 670 int main(int argc, const char **argv) { 671 InitLLVM X(argc, argv); 672 673 cl::HideUnrelatedOptions(ClangFormatCategory); 674 675 cl::SetVersionPrinter(PrintVersion); 676 cl::ParseCommandLineOptions( 677 argc, argv, 678 "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# " 679 "code.\n\n" 680 "If no arguments are specified, it formats the code from standard input\n" 681 "and writes the result to the standard output.\n" 682 "If <file>s are given, it reformats the files. If -i is specified\n" 683 "together with <file>s, the files are edited in-place. Otherwise, the\n" 684 "result is written to the standard output.\n"); 685 686 if (Help) { 687 cl::PrintHelpMessage(); 688 return 0; 689 } 690 691 if (DumpConfig) 692 return dumpConfig(); 693 694 if (!Files.empty()) { 695 std::ifstream ExternalFileOfFiles{std::string(Files)}; 696 std::string Line; 697 unsigned LineNo = 1; 698 while (std::getline(ExternalFileOfFiles, Line)) { 699 FileNames.push_back(Line); 700 LineNo++; 701 } 702 errs() << "Clang-formating " << LineNo << " files\n"; 703 } 704 705 if (FileNames.empty()) 706 return clang::format::format("-", FailOnIncompleteFormat); 707 708 if (FileNames.size() > 1 && 709 (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) { 710 errs() << "error: -offset, -length and -lines can only be used for " 711 "single file.\n"; 712 return 1; 713 } 714 715 unsigned FileNo = 1; 716 bool Error = false; 717 for (const auto &FileName : FileNames) { 718 if (isIgnored(FileName)) 719 continue; 720 if (Verbose) { 721 errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] " 722 << FileName << "\n"; 723 } 724 Error |= clang::format::format(FileName, FailOnIncompleteFormat); 725 } 726 return Error ? 1 : 0; 727 } 728