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, bool IsSTDIN) { 403 if (!OutputXML && Inplace && IsSTDIN) { 404 errs() << "error: cannot use -i when reading from stdin.\n"; 405 return false; 406 } 407 // On Windows, overwriting a file with an open file mapping doesn't work, 408 // so read the whole file into memory when formatting in-place. 409 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 410 !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName) 411 : MemoryBuffer::getFileOrSTDIN(FileName); 412 if (std::error_code EC = CodeOrErr.getError()) { 413 errs() << EC.message() << "\n"; 414 return true; 415 } 416 std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get()); 417 if (Code->getBufferSize() == 0) 418 return false; // Empty files are formatted correctly. 419 420 StringRef BufStr = Code->getBuffer(); 421 422 const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr); 423 424 if (InvalidBOM) { 425 errs() << "error: encoding with unsupported byte order mark \"" 426 << InvalidBOM << "\" detected"; 427 if (!IsSTDIN) 428 errs() << " in file '" << FileName << "'"; 429 errs() << ".\n"; 430 return true; 431 } 432 433 std::vector<tooling::Range> Ranges; 434 if (fillRanges(Code.get(), Ranges)) 435 return true; 436 StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName; 437 if (AssumedFileName.empty()) { 438 llvm::errs() << "error: empty filenames are not allowed\n"; 439 return true; 440 } 441 442 llvm::Expected<FormatStyle> FormatStyle = 443 getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(), 444 nullptr, WNoErrorList.isSet(WNoError::Unknown)); 445 if (!FormatStyle) { 446 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 447 return true; 448 } 449 450 StringRef QualifierAlignmentOrder = QualifierAlignment; 451 452 FormatStyle->QualifierAlignment = 453 StringSwitch<FormatStyle::QualifierAlignmentStyle>( 454 QualifierAlignmentOrder.lower()) 455 .Case("right", FormatStyle::QAS_Right) 456 .Case("left", FormatStyle::QAS_Left) 457 .Default(FormatStyle->QualifierAlignment); 458 459 if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) { 460 FormatStyle->QualifierOrder = {"const", "volatile", "type"}; 461 } else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) { 462 FormatStyle->QualifierOrder = {"type", "const", "volatile"}; 463 } else if (QualifierAlignmentOrder.contains("type")) { 464 FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom; 465 SmallVector<StringRef> Qualifiers; 466 QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1, 467 /*KeepEmpty=*/false); 468 FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()}; 469 } 470 471 if (SortIncludes.getNumOccurrences() != 0) { 472 if (SortIncludes) 473 FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive; 474 else 475 FormatStyle->SortIncludes = FormatStyle::SI_Never; 476 } 477 unsigned CursorPosition = Cursor; 478 Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges, 479 AssumedFileName, &CursorPosition); 480 481 // To format JSON insert a variable to trick the code into thinking its 482 // JavaScript. 483 if (FormatStyle->isJson() && !FormatStyle->DisableFormat) { 484 auto Err = Replaces.add(tooling::Replacement( 485 tooling::Replacement(AssumedFileName, 0, 0, "x = "))); 486 if (Err) 487 llvm::errs() << "Bad Json variable insertion\n"; 488 } 489 490 auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); 491 if (!ChangedCode) { 492 llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; 493 return true; 494 } 495 // Get new affected ranges after sorting `#includes`. 496 Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges); 497 FormattingAttemptStatus Status; 498 Replacements FormatChanges = 499 reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status); 500 Replaces = Replaces.merge(FormatChanges); 501 if (OutputXML || DryRun) { 502 if (DryRun) 503 return emitReplacementWarnings(Replaces, AssumedFileName, Code); 504 else 505 outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition); 506 } else { 507 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( 508 new llvm::vfs::InMemoryFileSystem); 509 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 510 511 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions()); 512 ClangFormatDiagConsumer IgnoreDiagnostics; 513 DiagnosticsEngine Diagnostics( 514 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts, 515 &IgnoreDiagnostics, false); 516 SourceManager Sources(Diagnostics, Files); 517 FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files, 518 InMemoryFileSystem.get()); 519 Rewriter Rewrite(Sources, LangOptions()); 520 tooling::applyAllReplacements(Replaces, Rewrite); 521 if (Inplace) { 522 if (Rewrite.overwriteChangedFiles()) 523 return true; 524 } else { 525 if (Cursor.getNumOccurrences() != 0) { 526 outs() << "{ \"Cursor\": " 527 << FormatChanges.getShiftedCodePosition(CursorPosition) 528 << ", \"IncompleteFormat\": " 529 << (Status.FormatComplete ? "false" : "true"); 530 if (!Status.FormatComplete) 531 outs() << ", \"Line\": " << Status.Line; 532 outs() << " }\n"; 533 } 534 Rewrite.getEditBuffer(ID).write(outs()); 535 } 536 } 537 return false; 538 } 539 540 } // namespace format 541 } // namespace clang 542 543 static void PrintVersion(raw_ostream &OS) { 544 OS << clang::getClangToolFullVersion("clang-format") << '\n'; 545 } 546 547 // Dump the configuration. 548 static int dumpConfig(bool IsSTDIN) { 549 std::unique_ptr<llvm::MemoryBuffer> Code; 550 // We can't read the code to detect the language if there's no file name. 551 if (!IsSTDIN) { 552 // Read in the code in case the filename alone isn't enough to detect the 553 // language. 554 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = 555 MemoryBuffer::getFileOrSTDIN(FileNames[0]); 556 if (std::error_code EC = CodeOrErr.getError()) { 557 llvm::errs() << EC.message() << "\n"; 558 return 1; 559 } 560 Code = std::move(CodeOrErr.get()); 561 } 562 llvm::Expected<clang::format::FormatStyle> FormatStyle = 563 clang::format::getStyle(Style, IsSTDIN ? AssumeFileName : FileNames[0], 564 FallbackStyle, Code ? Code->getBuffer() : ""); 565 if (!FormatStyle) { 566 llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; 567 return 1; 568 } 569 std::string Config = clang::format::configurationAsText(*FormatStyle); 570 outs() << Config << "\n"; 571 return 0; 572 } 573 574 // Check whether `FilePath` is ignored according to the nearest 575 // .clang-format-ignore file based on the rules below: 576 // - A blank line is skipped. 577 // - Leading and trailing spaces of a line are trimmed. 578 // - A line starting with a hash (`#`) is a comment. 579 // - A non-comment line is a single pattern. 580 // - The slash (`/`) is used as the directory separator. 581 // - A pattern is relative to the directory of the .clang-format-ignore file (or 582 // the root directory if the pattern starts with a slash). 583 // - A pattern is negated if it starts with a bang (`!`). 584 static bool isIgnored(StringRef FilePath) { 585 using namespace llvm::sys::fs; 586 if (!is_regular_file(FilePath)) 587 return false; 588 589 using namespace llvm::sys::path; 590 SmallString<128> Path, AbsPath{FilePath}; 591 592 make_absolute(AbsPath); 593 remove_dots(AbsPath, /*remove_dot_dot=*/true); 594 595 StringRef IgnoreDir{AbsPath}; 596 do { 597 IgnoreDir = parent_path(IgnoreDir); 598 if (IgnoreDir.empty()) 599 return false; 600 601 Path = IgnoreDir; 602 append(Path, ".clang-format-ignore"); 603 } while (!is_regular_file(Path)); 604 605 std::ifstream IgnoreFile{Path.c_str()}; 606 if (!IgnoreFile.good()) 607 return false; 608 609 const auto Pathname = convert_to_slash(AbsPath); 610 for (std::string Line; std::getline(IgnoreFile, Line);) { 611 auto Pattern = StringRef(Line).trim(); 612 if (Pattern.empty() || Pattern[0] == '#') 613 continue; 614 615 const bool IsNegated = Pattern[0] == '!'; 616 if (IsNegated) 617 Pattern = Pattern.drop_front(); 618 619 if (Pattern.empty()) 620 continue; 621 622 Pattern = Pattern.ltrim(); 623 if (Pattern[0] != '/') { 624 Path = convert_to_slash(IgnoreDir); 625 append(Path, Style::posix, Pattern); 626 remove_dots(Path, /*remove_dot_dot=*/true, Style::posix); 627 Pattern = Path.str(); 628 } 629 630 if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated) 631 return true; 632 } 633 634 return false; 635 } 636 637 int main(int argc, const char **argv) { 638 llvm::InitLLVM X(argc, argv); 639 640 cl::HideUnrelatedOptions(ClangFormatCategory); 641 642 cl::SetVersionPrinter(PrintVersion); 643 cl::ParseCommandLineOptions( 644 argc, argv, 645 "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# " 646 "code.\n\n" 647 "If no arguments are specified, it formats the code from standard input\n" 648 "and writes the result to the standard output.\n" 649 "If <file>s are given, it reformats the files. If -i is specified\n" 650 "together with <file>s, the files are edited in-place. Otherwise, the\n" 651 "result is written to the standard output.\n"); 652 653 if (Help) { 654 cl::PrintHelpMessage(); 655 return 0; 656 } 657 658 if (FileNames.empty()) 659 FileNames.push_back("-"); 660 661 if (DumpConfig) 662 return dumpConfig(FileNames[0] == "-"); 663 664 if (!Files.empty()) { 665 std::ifstream ExternalFileOfFiles{std::string(Files)}; 666 std::string Line; 667 unsigned LineNo = 1; 668 while (std::getline(ExternalFileOfFiles, Line)) { 669 FileNames.push_back(Line); 670 LineNo++; 671 } 672 errs() << "Clang-formating " << LineNo << " files\n"; 673 } 674 675 if (FileNames.size() != 1 && 676 (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) { 677 errs() << "error: -offset, -length and -lines can only be used for " 678 "single file.\n"; 679 return 1; 680 } 681 682 unsigned FileNo = 1; 683 bool Error = false; 684 for (const auto &FileName : FileNames) { 685 const bool IsSTDIN = FileName == "-"; 686 if (!IsSTDIN && isIgnored(FileName)) 687 continue; 688 if (Verbose) { 689 errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] " 690 << FileName << "\n"; 691 } 692 Error |= clang::format::format(FileName, IsSTDIN); 693 } 694 return Error ? 1 : 0; 695 } 696