xref: /llvm-project/clang/tools/clang-format/ClangFormat.cpp (revision d813af73f70f6b2fd41621d640cc35e595b9da4c)
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() << "&#10;";
314       break;
315     case '\r':
316       outs() << "&#13;";
317       break;
318     case '<':
319       outs() << "&lt;";
320       break;
321     case '&':
322       outs() << "&amp;";
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 
551   // `FileNames` must have at least "-" in it even if no file was specified.
552   assert(!FileNames.empty());
553 
554   // Read in the code in case the filename alone isn't enough to detect the
555   // language.
556   ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
557       MemoryBuffer::getFileOrSTDIN(FileNames[0]);
558   if (std::error_code EC = CodeOrErr.getError()) {
559     llvm::errs() << EC.message() << "\n";
560     return 1;
561   }
562   Code = std::move(CodeOrErr.get());
563 
564   llvm::Expected<clang::format::FormatStyle> FormatStyle =
565       clang::format::getStyle(Style, IsSTDIN ? AssumeFileName : FileNames[0],
566                               FallbackStyle, Code ? Code->getBuffer() : "");
567   if (!FormatStyle) {
568     llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
569     return 1;
570   }
571   std::string Config = clang::format::configurationAsText(*FormatStyle);
572   outs() << Config << "\n";
573   return 0;
574 }
575 
576 using String = SmallString<128>;
577 static String IgnoreDir;             // Directory of .clang-format-ignore file.
578 static String PrevDir;               // Directory of previous `FilePath`.
579 static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file.
580 
581 // Check whether `FilePath` is ignored according to the nearest
582 // .clang-format-ignore file based on the rules below:
583 // - A blank line is skipped.
584 // - Leading and trailing spaces of a line are trimmed.
585 // - A line starting with a hash (`#`) is a comment.
586 // - A non-comment line is a single pattern.
587 // - The slash (`/`) is used as the directory separator.
588 // - A pattern is relative to the directory of the .clang-format-ignore file (or
589 //   the root directory if the pattern starts with a slash).
590 // - A pattern is negated if it starts with a bang (`!`).
591 static bool isIgnored(StringRef FilePath) {
592   using namespace llvm::sys::fs;
593   if (!is_regular_file(FilePath))
594     return false;
595 
596   String Path;
597   String AbsPath{FilePath};
598 
599   using namespace llvm::sys::path;
600   make_absolute(AbsPath);
601   remove_dots(AbsPath, /*remove_dot_dot=*/true);
602 
603   if (StringRef Dir{parent_path(AbsPath)}; PrevDir != Dir) {
604     PrevDir = Dir;
605 
606     for (;;) {
607       Path = Dir;
608       append(Path, ".clang-format-ignore");
609       if (is_regular_file(Path))
610         break;
611       Dir = parent_path(Dir);
612       if (Dir.empty())
613         return false;
614     }
615 
616     IgnoreDir = convert_to_slash(Dir);
617 
618     std::ifstream IgnoreFile{Path.c_str()};
619     if (!IgnoreFile.good())
620       return false;
621 
622     Patterns.clear();
623 
624     for (std::string Line; std::getline(IgnoreFile, Line);) {
625       if (const auto Pattern{StringRef{Line}.trim()};
626           // Skip empty and comment lines.
627           !Pattern.empty() && Pattern[0] != '#') {
628         Patterns.push_back(Pattern);
629       }
630     }
631   }
632 
633   if (IgnoreDir.empty())
634     return false;
635 
636   const auto Pathname{convert_to_slash(AbsPath)};
637   for (const auto &Pat : Patterns) {
638     const bool IsNegated = Pat[0] == '!';
639     StringRef Pattern{Pat};
640     if (IsNegated)
641       Pattern = Pattern.drop_front();
642 
643     if (Pattern.empty())
644       continue;
645 
646     Pattern = Pattern.ltrim();
647 
648     // `Pattern` is relative to `IgnoreDir` unless it starts with a slash.
649     // This doesn't support patterns containing drive names (e.g. `C:`).
650     if (Pattern[0] != '/') {
651       Path = IgnoreDir;
652       append(Path, Style::posix, Pattern);
653       remove_dots(Path, /*remove_dot_dot=*/true, Style::posix);
654       Pattern = Path;
655     }
656 
657     if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated)
658       return true;
659   }
660 
661   return false;
662 }
663 
664 int main(int argc, const char **argv) {
665   llvm::InitLLVM X(argc, argv);
666 
667   cl::HideUnrelatedOptions(ClangFormatCategory);
668 
669   cl::SetVersionPrinter(PrintVersion);
670   cl::ParseCommandLineOptions(
671       argc, argv,
672       "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
673       "code.\n\n"
674       "If no arguments are specified, it formats the code from standard input\n"
675       "and writes the result to the standard output.\n"
676       "If <file>s are given, it reformats the files. If -i is specified\n"
677       "together with <file>s, the files are edited in-place. Otherwise, the\n"
678       "result is written to the standard output.\n");
679 
680   if (Help) {
681     cl::PrintHelpMessage();
682     return 0;
683   }
684 
685   if (FileNames.empty())
686     FileNames.push_back("-");
687 
688   if (DumpConfig)
689     return dumpConfig(FileNames[0] == "-");
690 
691   if (!Files.empty()) {
692     std::ifstream ExternalFileOfFiles{std::string(Files)};
693     std::string Line;
694     unsigned LineNo = 1;
695     while (std::getline(ExternalFileOfFiles, Line)) {
696       FileNames.push_back(Line);
697       LineNo++;
698     }
699     errs() << "Clang-formating " << LineNo << " files\n";
700   }
701 
702   if (FileNames.size() != 1 &&
703       (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) {
704     errs() << "error: -offset, -length and -lines can only be used for "
705               "single file.\n";
706     return 1;
707   }
708 
709   unsigned FileNo = 1;
710   bool Error = false;
711   for (const auto &FileName : FileNames) {
712     const bool IsSTDIN = FileName == "-";
713     if (!IsSTDIN && isIgnored(FileName))
714       continue;
715     if (Verbose) {
716       errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
717              << FileName << "\n";
718     }
719     Error |= clang::format::format(FileName, IsSTDIN);
720   }
721   return Error ? 1 : 0;
722 }
723