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