xref: /freebsd/contrib/llvm-project/clang/tools/clang-format/ClangFormat.cpp (revision 1db9f3b21e39176dd5b67cf8ac378633b172463e)
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   // 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