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 "clang/Basic/Diagnostic.h"
16 #include "clang/Basic/DiagnosticOptions.h"
17 #include "clang/Basic/FileManager.h"
18 #include "clang/Basic/SourceManager.h"
19 #include "clang/Basic/Version.h"
20 #include "clang/Format/Format.h"
21 #include "clang/Rewrite/Core/Rewriter.h"
22 #include "llvm/ADT/StringSwitch.h"
23 #include "llvm/Support/CommandLine.h"
24 #include "llvm/Support/FileSystem.h"
25 #include "llvm/Support/InitLLVM.h"
26 #include "llvm/Support/Process.h"
27 #include <fstream>
28 
29 using namespace llvm;
30 using clang::tooling::Replacements;
31 
32 static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
33 
34 // Mark all our options with this category, everything else (except for -version
35 // and -help) will be hidden.
36 static cl::OptionCategory ClangFormatCategory("Clang-format options");
37 
38 static cl::list<unsigned>
39     Offsets("offset",
40             cl::desc("Format a range starting at this byte offset.\n"
41                      "Multiple ranges can be formatted by specifying\n"
42                      "several -offset and -length pairs.\n"
43                      "Can only be used with one input file."),
44             cl::cat(ClangFormatCategory));
45 static cl::list<unsigned>
46     Lengths("length",
47             cl::desc("Format a range of this length (in bytes).\n"
48                      "Multiple ranges can be formatted by specifying\n"
49                      "several -offset and -length pairs.\n"
50                      "When only a single -offset is specified without\n"
51                      "-length, clang-format will format up to the end\n"
52                      "of the file.\n"
53                      "Can only be used with one input file."),
54             cl::cat(ClangFormatCategory));
55 static cl::list<std::string>
56     LineRanges("lines",
57                cl::desc("<start line>:<end line> - format a range of\n"
58                         "lines (both 1-based).\n"
59                         "Multiple ranges can be formatted by specifying\n"
60                         "several -lines arguments.\n"
61                         "Can't be used with -offset and -length.\n"
62                         "Can only be used with one input file."),
63                cl::cat(ClangFormatCategory));
64 static cl::opt<std::string>
65     Style("style", cl::desc(clang::format::StyleOptionHelpDescription),
66           cl::init(clang::format::DefaultFormatStyle),
67           cl::cat(ClangFormatCategory));
68 static cl::opt<std::string>
69     FallbackStyle("fallback-style",
70                   cl::desc("The name of the predefined style used as a\n"
71                            "fallback in case clang-format is invoked with\n"
72                            "-style=file, but can not find the .clang-format\n"
73                            "file to use. Defaults to 'LLVM'.\n"
74                            "Use -fallback-style=none to skip formatting."),
75                   cl::init(clang::format::DefaultFallbackStyle),
76                   cl::cat(ClangFormatCategory));
77 
78 static cl::opt<std::string> AssumeFileName(
79     "assume-filename",
80     cl::desc("Set filename used to determine the language and to find\n"
81              ".clang-format file.\n"
82              "Only used when reading from stdin.\n"
83              "If this is not passed, the .clang-format file is searched\n"
84              "relative to the current working directory when reading stdin.\n"
85              "Unrecognized filenames are treated as C++.\n"
86              "supported:\n"
87              "  CSharp: .cs\n"
88              "  Java: .java\n"
89              "  JavaScript: .mjs .js .ts\n"
90              "  Json: .json\n"
91              "  Objective-C: .m .mm\n"
92              "  Proto: .proto .protodevel\n"
93              "  TableGen: .td\n"
94              "  TextProto: .textpb .pb.txt .textproto .asciipb\n"
95              "  Verilog: .sv .svh .v .vh"),
96     cl::init("<stdin>"), cl::cat(ClangFormatCategory));
97 
98 static cl::opt<bool> Inplace("i",
99                              cl::desc("Inplace edit <file>s, if specified."),
100                              cl::cat(ClangFormatCategory));
101 
102 static cl::opt<bool> OutputXML("output-replacements-xml",
103                                cl::desc("Output replacements as XML."),
104                                cl::cat(ClangFormatCategory));
105 static cl::opt<bool>
106     DumpConfig("dump-config",
107                cl::desc("Dump configuration options to stdout and exit.\n"
108                         "Can be used with -style option."),
109                cl::cat(ClangFormatCategory));
110 static cl::opt<unsigned>
111     Cursor("cursor",
112            cl::desc("The position of the cursor when invoking\n"
113                     "clang-format from an editor integration"),
114            cl::init(0), cl::cat(ClangFormatCategory));
115 
116 static cl::opt<bool>
117     SortIncludes("sort-includes",
118                  cl::desc("If set, overrides the include sorting behavior\n"
119                           "determined by the SortIncludes style flag"),
120                  cl::cat(ClangFormatCategory));
121 
122 static cl::opt<std::string> QualifierAlignment(
123     "qualifier-alignment",
124     cl::desc("If set, overrides the qualifier alignment style\n"
125              "determined by the QualifierAlignment style flag"),
126     cl::init(""), cl::cat(ClangFormatCategory));
127 
128 static cl::opt<std::string> Files(
129     "files",
130     cl::desc("A file containing a list of files to process, one per line."),
131     cl::value_desc("filename"), cl::init(""), cl::cat(ClangFormatCategory));
132 
133 static cl::opt<bool>
134     Verbose("verbose", cl::desc("If set, shows the list of processed files"),
135             cl::cat(ClangFormatCategory));
136 
137 // Use --dry-run to match other LLVM tools when you mean do it but don't
138 // actually do it
139 static cl::opt<bool>
140     DryRun("dry-run",
141            cl::desc("If set, do not actually make the formatting changes"),
142            cl::cat(ClangFormatCategory));
143 
144 // Use -n as a common command as an alias for --dry-run. (git and make use -n)
145 static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"),
146                              cl::cat(ClangFormatCategory), cl::aliasopt(DryRun),
147                              cl::NotHidden);
148 
149 // Emulate being able to turn on/off the warning.
150 static cl::opt<bool>
151     WarnFormat("Wclang-format-violations",
152                cl::desc("Warnings about individual formatting changes needed. "
153                         "Used only with --dry-run or -n"),
154                cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden);
155 
156 static cl::opt<bool>
157     NoWarnFormat("Wno-clang-format-violations",
158                  cl::desc("Do not warn about individual formatting changes "
159                           "needed. Used only with --dry-run or -n"),
160                  cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden);
161 
162 static cl::opt<unsigned> ErrorLimit(
163     "ferror-limit",
164     cl::desc("Set the maximum number of clang-format errors to emit\n"
165              "before stopping (0 = no limit).\n"
166              "Used only with --dry-run or -n"),
167     cl::init(0), cl::cat(ClangFormatCategory));
168 
169 static cl::opt<bool>
170     WarningsAsErrors("Werror",
171                      cl::desc("If set, changes formatting warnings to errors"),
172                      cl::cat(ClangFormatCategory));
173 
174 namespace {
175 enum class WNoError { Unknown };
176 }
177 
178 static cl::bits<WNoError> WNoErrorList(
179     "Wno-error",
180     cl::desc("If set don't error out on the specified warning type."),
181     cl::values(
182         clEnumValN(WNoError::Unknown, "unknown",
183                    "If set, unknown format options are only warned about.\n"
184                    "This can be used to enable formatting, even if the\n"
185                    "configuration contains unknown (newer) options.\n"
186                    "Use with caution, as this might lead to dramatically\n"
187                    "differing format depending on an option being\n"
188                    "supported or not.")),
189     cl::cat(ClangFormatCategory));
190 
191 static cl::opt<bool>
192     ShowColors("fcolor-diagnostics",
193                cl::desc("If set, and on a color-capable terminal controls "
194                         "whether or not to print diagnostics in color"),
195                cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden);
196 
197 static cl::opt<bool>
198     NoShowColors("fno-color-diagnostics",
199                  cl::desc("If set, and on a color-capable terminal controls "
200                           "whether or not to print diagnostics in color"),
201                  cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden);
202 
203 static cl::list<std::string> FileNames(cl::Positional,
204                                        cl::desc("[@<file>] [<file> ...]"),
205                                        cl::cat(ClangFormatCategory));
206 
207 namespace clang {
208 namespace format {
209 
210 static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source,
211                                  SourceManager &Sources, FileManager &Files,
212                                  llvm::vfs::InMemoryFileSystem *MemFS) {
213   MemFS->addFileNoOwn(FileName, 0, Source);
214   auto File = Files.getOptionalFileRef(FileName);
215   assert(File && "File not added to MemFS?");
216   return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User);
217 }
218 
219 // Parses <start line>:<end line> input to a pair of line numbers.
220 // Returns true on error.
221 static bool parseLineRange(StringRef Input, unsigned &FromLine,
222                            unsigned &ToLine) {
223   std::pair<StringRef, StringRef> LineRange = Input.split(':');
224   return LineRange.first.getAsInteger(0, FromLine) ||
225          LineRange.second.getAsInteger(0, ToLine);
226 }
227 
228 static bool fillRanges(MemoryBuffer *Code,
229                        std::vector<tooling::Range> &Ranges) {
230   IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
231       new llvm::vfs::InMemoryFileSystem);
232   FileManager Files(FileSystemOptions(), InMemoryFileSystem);
233   DiagnosticsEngine Diagnostics(
234       IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
235       new DiagnosticOptions);
236   SourceManager Sources(Diagnostics, Files);
237   FileID ID = createInMemoryFile("<irrelevant>", *Code, Sources, Files,
238                                  InMemoryFileSystem.get());
239   if (!LineRanges.empty()) {
240     if (!Offsets.empty() || !Lengths.empty()) {
241       errs() << "error: cannot use -lines with -offset/-length\n";
242       return true;
243     }
244 
245     for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) {
246       unsigned FromLine, ToLine;
247       if (parseLineRange(LineRanges[i], FromLine, ToLine)) {
248         errs() << "error: invalid <start line>:<end line> pair\n";
249         return true;
250       }
251       if (FromLine < 1) {
252         errs() << "error: start line should be at least 1\n";
253         return true;
254       }
255       if (FromLine > ToLine) {
256         errs() << "error: start line should not exceed end line\n";
257         return true;
258       }
259       SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1);
260       SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX);
261       if (Start.isInvalid() || End.isInvalid())
262         return true;
263       unsigned Offset = Sources.getFileOffset(Start);
264       unsigned Length = Sources.getFileOffset(End) - Offset;
265       Ranges.push_back(tooling::Range(Offset, Length));
266     }
267     return false;
268   }
269 
270   if (Offsets.empty())
271     Offsets.push_back(0);
272   if (Offsets.size() != Lengths.size() &&
273       !(Offsets.size() == 1 && Lengths.empty())) {
274     errs() << "error: number of -offset and -length arguments must match.\n";
275     return true;
276   }
277   for (unsigned i = 0, e = Offsets.size(); i != e; ++i) {
278     if (Offsets[i] >= Code->getBufferSize()) {
279       errs() << "error: offset " << Offsets[i] << " is outside the file\n";
280       return true;
281     }
282     SourceLocation Start =
283         Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]);
284     SourceLocation End;
285     if (i < Lengths.size()) {
286       if (Offsets[i] + Lengths[i] > Code->getBufferSize()) {
287         errs() << "error: invalid length " << Lengths[i]
288                << ", offset + length (" << Offsets[i] + Lengths[i]
289                << ") is outside the file.\n";
290         return true;
291       }
292       End = Start.getLocWithOffset(Lengths[i]);
293     } else {
294       End = Sources.getLocForEndOfFile(ID);
295     }
296     unsigned Offset = Sources.getFileOffset(Start);
297     unsigned Length = Sources.getFileOffset(End) - Offset;
298     Ranges.push_back(tooling::Range(Offset, Length));
299   }
300   return false;
301 }
302 
303 static void outputReplacementXML(StringRef Text) {
304   // FIXME: When we sort includes, we need to make sure the stream is correct
305   // utf-8.
306   size_t From = 0;
307   size_t Index;
308   while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) {
309     outs() << Text.substr(From, Index - From);
310     switch (Text[Index]) {
311     case '\n':
312       outs() << "&#10;";
313       break;
314     case '\r':
315       outs() << "&#13;";
316       break;
317     case '<':
318       outs() << "&lt;";
319       break;
320     case '&':
321       outs() << "&amp;";
322       break;
323     default:
324       llvm_unreachable("Unexpected character encountered!");
325     }
326     From = Index + 1;
327   }
328   outs() << Text.substr(From);
329 }
330 
331 static void outputReplacementsXML(const Replacements &Replaces) {
332   for (const auto &R : Replaces) {
333     outs() << "<replacement " << "offset='" << R.getOffset() << "' "
334            << "length='" << R.getLength() << "'>";
335     outputReplacementXML(R.getReplacementText());
336     outs() << "</replacement>\n";
337   }
338 }
339 
340 static bool
341 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName,
342                         const std::unique_ptr<llvm::MemoryBuffer> &Code) {
343   if (Replaces.empty())
344     return false;
345 
346   unsigned Errors = 0;
347   if (WarnFormat && !NoWarnFormat) {
348     llvm::SourceMgr Mgr;
349     const char *StartBuf = Code->getBufferStart();
350 
351     Mgr.AddNewSourceBuffer(
352         MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc());
353     for (const auto &R : Replaces) {
354       SMDiagnostic Diag = Mgr.GetMessage(
355           SMLoc::getFromPointer(StartBuf + R.getOffset()),
356           WarningsAsErrors ? SourceMgr::DiagKind::DK_Error
357                            : SourceMgr::DiagKind::DK_Warning,
358           "code should be clang-formatted [-Wclang-format-violations]");
359 
360       Diag.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors));
361       if (ErrorLimit && ++Errors >= ErrorLimit)
362         break;
363     }
364   }
365   return WarningsAsErrors;
366 }
367 
368 static void outputXML(const Replacements &Replaces,
369                       const Replacements &FormatChanges,
370                       const FormattingAttemptStatus &Status,
371                       const cl::opt<unsigned> &Cursor,
372                       unsigned CursorPosition) {
373   outs() << "<?xml version='1.0'?>\n<replacements "
374             "xml:space='preserve' incomplete_format='"
375          << (Status.FormatComplete ? "false" : "true") << "'";
376   if (!Status.FormatComplete)
377     outs() << " line='" << Status.Line << "'";
378   outs() << ">\n";
379   if (Cursor.getNumOccurrences() != 0) {
380     outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition)
381            << "</cursor>\n";
382   }
383 
384   outputReplacementsXML(Replaces);
385   outs() << "</replacements>\n";
386 }
387 
388 class ClangFormatDiagConsumer : public DiagnosticConsumer {
389   virtual void anchor() {}
390 
391   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
392                         const Diagnostic &Info) override {
393 
394     SmallVector<char, 16> vec;
395     Info.FormatDiagnostic(vec);
396     errs() << "clang-format error:" << vec << "\n";
397   }
398 };
399 
400 // Returns true on error.
401 static bool format(StringRef FileName, bool IsSTDIN) {
402   if (!OutputXML && Inplace && IsSTDIN) {
403     errs() << "error: cannot use -i when reading from stdin.\n";
404     return false;
405   }
406   // On Windows, overwriting a file with an open file mapping doesn't work,
407   // so read the whole file into memory when formatting in-place.
408   ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
409       !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName)
410                             : MemoryBuffer::getFileOrSTDIN(FileName);
411   if (std::error_code EC = CodeOrErr.getError()) {
412     errs() << EC.message() << "\n";
413     return true;
414   }
415   std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
416   if (Code->getBufferSize() == 0)
417     return false; // Empty files are formatted correctly.
418 
419   StringRef BufStr = Code->getBuffer();
420 
421   const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr);
422 
423   if (InvalidBOM) {
424     errs() << "error: encoding with unsupported byte order mark \""
425            << InvalidBOM << "\" detected";
426     if (!IsSTDIN)
427       errs() << " in file '" << FileName << "'";
428     errs() << ".\n";
429     return true;
430   }
431 
432   std::vector<tooling::Range> Ranges;
433   if (fillRanges(Code.get(), Ranges))
434     return true;
435   StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName;
436   if (AssumedFileName.empty()) {
437     llvm::errs() << "error: empty filenames are not allowed\n";
438     return true;
439   }
440 
441   llvm::Expected<FormatStyle> FormatStyle =
442       getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(),
443                nullptr, WNoErrorList.isSet(WNoError::Unknown));
444   if (!FormatStyle) {
445     llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
446     return true;
447   }
448 
449   StringRef QualifierAlignmentOrder = QualifierAlignment;
450 
451   FormatStyle->QualifierAlignment =
452       StringSwitch<FormatStyle::QualifierAlignmentStyle>(
453           QualifierAlignmentOrder.lower())
454           .Case("right", FormatStyle::QAS_Right)
455           .Case("left", FormatStyle::QAS_Left)
456           .Default(FormatStyle->QualifierAlignment);
457 
458   if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) {
459     FormatStyle->QualifierOrder = {"const", "volatile", "type"};
460   } else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) {
461     FormatStyle->QualifierOrder = {"type", "const", "volatile"};
462   } else if (QualifierAlignmentOrder.contains("type")) {
463     FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom;
464     SmallVector<StringRef> Qualifiers;
465     QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1,
466                                   /*KeepEmpty=*/false);
467     FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()};
468   }
469 
470   if (SortIncludes.getNumOccurrences() != 0) {
471     if (SortIncludes)
472       FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive;
473     else
474       FormatStyle->SortIncludes = FormatStyle::SI_Never;
475   }
476   unsigned CursorPosition = Cursor;
477   Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
478                                        AssumedFileName, &CursorPosition);
479 
480   // To format JSON insert a variable to trick the code into thinking its
481   // JavaScript.
482   if (FormatStyle->isJson() && !FormatStyle->DisableFormat) {
483     auto Err = Replaces.add(tooling::Replacement(
484         tooling::Replacement(AssumedFileName, 0, 0, "x = ")));
485     if (Err)
486       llvm::errs() << "Bad Json variable insertion\n";
487   }
488 
489   auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
490   if (!ChangedCode) {
491     llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
492     return true;
493   }
494   // Get new affected ranges after sorting `#includes`.
495   Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges);
496   FormattingAttemptStatus Status;
497   Replacements FormatChanges =
498       reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status);
499   Replaces = Replaces.merge(FormatChanges);
500   if (OutputXML || DryRun) {
501     if (DryRun)
502       return emitReplacementWarnings(Replaces, AssumedFileName, Code);
503     else
504       outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition);
505   } else {
506     IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
507         new llvm::vfs::InMemoryFileSystem);
508     FileManager Files(FileSystemOptions(), InMemoryFileSystem);
509 
510     IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
511     ClangFormatDiagConsumer IgnoreDiagnostics;
512     DiagnosticsEngine Diagnostics(
513         IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts,
514         &IgnoreDiagnostics, false);
515     SourceManager Sources(Diagnostics, Files);
516     FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files,
517                                    InMemoryFileSystem.get());
518     Rewriter Rewrite(Sources, LangOptions());
519     tooling::applyAllReplacements(Replaces, Rewrite);
520     if (Inplace) {
521       if (Rewrite.overwriteChangedFiles())
522         return true;
523     } else {
524       if (Cursor.getNumOccurrences() != 0) {
525         outs() << "{ \"Cursor\": "
526                << FormatChanges.getShiftedCodePosition(CursorPosition)
527                << ", \"IncompleteFormat\": "
528                << (Status.FormatComplete ? "false" : "true");
529         if (!Status.FormatComplete)
530           outs() << ", \"Line\": " << Status.Line;
531         outs() << " }\n";
532       }
533       Rewrite.getEditBuffer(ID).write(outs());
534     }
535   }
536   return false;
537 }
538 
539 } // namespace format
540 } // namespace clang
541 
542 static void PrintVersion(raw_ostream &OS) {
543   OS << clang::getClangToolFullVersion("clang-format") << '\n';
544 }
545 
546 // Dump the configuration.
547 static int dumpConfig(bool IsSTDIN) {
548   std::unique_ptr<llvm::MemoryBuffer> Code;
549   // We can't read the code to detect the language if there's no file name.
550   if (!IsSTDIN) {
551     // Read in the code in case the filename alone isn't enough to detect the
552     // language.
553     ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
554         MemoryBuffer::getFileOrSTDIN(FileNames[0]);
555     if (std::error_code EC = CodeOrErr.getError()) {
556       llvm::errs() << EC.message() << "\n";
557       return 1;
558     }
559     Code = std::move(CodeOrErr.get());
560   }
561   llvm::Expected<clang::format::FormatStyle> FormatStyle =
562       clang::format::getStyle(Style, IsSTDIN ? AssumeFileName : FileNames[0],
563                               FallbackStyle, Code ? Code->getBuffer() : "");
564   if (!FormatStyle) {
565     llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
566     return 1;
567   }
568   std::string Config = clang::format::configurationAsText(*FormatStyle);
569   outs() << Config << "\n";
570   return 0;
571 }
572 
573 int main(int argc, const char **argv) {
574   llvm::InitLLVM X(argc, argv);
575 
576   cl::HideUnrelatedOptions(ClangFormatCategory);
577 
578   cl::SetVersionPrinter(PrintVersion);
579   cl::ParseCommandLineOptions(
580       argc, argv,
581       "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
582       "code.\n\n"
583       "If no arguments are specified, it formats the code from standard input\n"
584       "and writes the result to the standard output.\n"
585       "If <file>s are given, it reformats the files. If -i is specified\n"
586       "together with <file>s, the files are edited in-place. Otherwise, the\n"
587       "result is written to the standard output.\n");
588 
589   if (Help) {
590     cl::PrintHelpMessage();
591     return 0;
592   }
593 
594   if (FileNames.empty())
595     FileNames.push_back("-");
596 
597   if (DumpConfig)
598     return dumpConfig(FileNames[0] == "-");
599 
600   if (!Files.empty()) {
601     std::ifstream ExternalFileOfFiles{std::string(Files)};
602     std::string Line;
603     unsigned LineNo = 1;
604     while (std::getline(ExternalFileOfFiles, Line)) {
605       FileNames.push_back(Line);
606       LineNo++;
607     }
608     errs() << "Clang-formating " << LineNo << " files\n";
609   }
610 
611   if (FileNames.size() != 1 &&
612       (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) {
613     errs() << "error: -offset, -length and -lines can only be used for "
614               "single file.\n";
615     return 1;
616   }
617 
618   unsigned FileNo = 1;
619   bool Error = false;
620   for (const auto &FileName : FileNames) {
621     if (Verbose) {
622       errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
623              << FileName << "\n";
624     }
625     Error |= clang::format::format(FileName, FileName == "-");
626   }
627   return Error ? 1 : 0;
628 }
629