xref: /freebsd/contrib/llvm-project/clang/tools/clang-format/ClangFormat.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
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: .js .mjs .cjs .ts\n"
91              "  Json: .json .ipynb\n"
92              "  Objective-C: .m .mm\n"
93              "  Proto: .proto .protodevel\n"
94              "  TableGen: .td\n"
95              "  TextProto: .txtpb .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 static cl::opt<bool> FailOnIncompleteFormat(
209     "fail-on-incomplete-format",
210     cl::desc("If set, fail with exit code 1 on incomplete format."),
211     cl::init(false), cl::cat(ClangFormatCategory));
212 
213 static cl::opt<bool> ListIgnored("list-ignored",
214                                  cl::desc("List ignored files."),
215                                  cl::cat(ClangFormatCategory), cl::Hidden);
216 
217 namespace clang {
218 namespace format {
219 
createInMemoryFile(StringRef FileName,MemoryBufferRef Source,SourceManager & Sources,FileManager & Files,llvm::vfs::InMemoryFileSystem * MemFS)220 static FileID createInMemoryFile(StringRef FileName, MemoryBufferRef Source,
221                                  SourceManager &Sources, FileManager &Files,
222                                  llvm::vfs::InMemoryFileSystem *MemFS) {
223   MemFS->addFileNoOwn(FileName, 0, Source);
224   auto File = Files.getOptionalFileRef(FileName);
225   assert(File && "File not added to MemFS?");
226   return Sources.createFileID(*File, SourceLocation(), SrcMgr::C_User);
227 }
228 
229 // Parses <start line>:<end line> input to a pair of line numbers.
230 // Returns true on error.
parseLineRange(StringRef Input,unsigned & FromLine,unsigned & ToLine)231 static bool parseLineRange(StringRef Input, unsigned &FromLine,
232                            unsigned &ToLine) {
233   std::pair<StringRef, StringRef> LineRange = Input.split(':');
234   return LineRange.first.getAsInteger(0, FromLine) ||
235          LineRange.second.getAsInteger(0, ToLine);
236 }
237 
fillRanges(MemoryBuffer * Code,std::vector<tooling::Range> & Ranges)238 static bool fillRanges(MemoryBuffer *Code,
239                        std::vector<tooling::Range> &Ranges) {
240   IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
241       new llvm::vfs::InMemoryFileSystem);
242   FileManager Files(FileSystemOptions(), InMemoryFileSystem);
243   DiagnosticOptions DiagOpts;
244   DiagnosticsEngine Diagnostics(
245       IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), DiagOpts);
246   SourceManager Sources(Diagnostics, Files);
247   const auto ID = createInMemoryFile("<irrelevant>", *Code, Sources, Files,
248                                      InMemoryFileSystem.get());
249   if (!LineRanges.empty()) {
250     if (!Offsets.empty() || !Lengths.empty()) {
251       errs() << "error: cannot use -lines with -offset/-length\n";
252       return true;
253     }
254 
255     for (const auto &LineRange : LineRanges) {
256       unsigned FromLine, ToLine;
257       if (parseLineRange(LineRange, FromLine, ToLine)) {
258         errs() << "error: invalid <start line>:<end line> pair\n";
259         return true;
260       }
261       if (FromLine < 1) {
262         errs() << "error: start line should be at least 1\n";
263         return true;
264       }
265       if (FromLine > ToLine) {
266         errs() << "error: start line should not exceed end line\n";
267         return true;
268       }
269       const auto Start = Sources.translateLineCol(ID, FromLine, 1);
270       const auto End = Sources.translateLineCol(ID, ToLine, UINT_MAX);
271       if (Start.isInvalid() || End.isInvalid())
272         return true;
273       const auto Offset = Sources.getFileOffset(Start);
274       const auto Length = Sources.getFileOffset(End) - Offset;
275       Ranges.push_back(tooling::Range(Offset, Length));
276     }
277     return false;
278   }
279 
280   if (Offsets.empty())
281     Offsets.push_back(0);
282   const bool EmptyLengths = Lengths.empty();
283   unsigned Length = 0;
284   if (Offsets.size() == 1 && EmptyLengths) {
285     Length = Sources.getFileOffset(Sources.getLocForEndOfFile(ID)) - Offsets[0];
286   } else if (Offsets.size() != Lengths.size()) {
287     errs() << "error: number of -offset and -length arguments must match.\n";
288     return true;
289   }
290   for (unsigned I = 0, E = Offsets.size(), CodeSize = Code->getBufferSize();
291        I < E; ++I) {
292     const auto Offset = Offsets[I];
293     if (Offset >= CodeSize) {
294       errs() << "error: offset " << Offset << " is outside the file\n";
295       return true;
296     }
297     if (!EmptyLengths)
298       Length = Lengths[I];
299     if (Offset + Length > CodeSize) {
300       errs() << "error: invalid length " << Length << ", offset + length ("
301              << Offset + Length << ") is outside the file.\n";
302       return true;
303     }
304     Ranges.push_back(tooling::Range(Offset, Length));
305   }
306   return false;
307 }
308 
outputReplacementXML(StringRef Text)309 static void outputReplacementXML(StringRef Text) {
310   // FIXME: When we sort includes, we need to make sure the stream is correct
311   // utf-8.
312   size_t From = 0;
313   size_t Index;
314   while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) {
315     outs() << Text.substr(From, Index - From);
316     switch (Text[Index]) {
317     case '\n':
318       outs() << "&#10;";
319       break;
320     case '\r':
321       outs() << "&#13;";
322       break;
323     case '<':
324       outs() << "&lt;";
325       break;
326     case '&':
327       outs() << "&amp;";
328       break;
329     default:
330       llvm_unreachable("Unexpected character encountered!");
331     }
332     From = Index + 1;
333   }
334   outs() << Text.substr(From);
335 }
336 
outputReplacementsXML(const Replacements & Replaces)337 static void outputReplacementsXML(const Replacements &Replaces) {
338   for (const auto &R : Replaces) {
339     outs() << "<replacement "
340            << "offset='" << R.getOffset() << "' "
341            << "length='" << R.getLength() << "'>";
342     outputReplacementXML(R.getReplacementText());
343     outs() << "</replacement>\n";
344   }
345 }
346 
347 static bool
emitReplacementWarnings(const Replacements & Replaces,StringRef AssumedFileName,const std::unique_ptr<llvm::MemoryBuffer> & Code)348 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName,
349                         const std::unique_ptr<llvm::MemoryBuffer> &Code) {
350   unsigned Errors = 0;
351   if (WarnFormat && !NoWarnFormat) {
352     SourceMgr Mgr;
353     const char *StartBuf = Code->getBufferStart();
354 
355     Mgr.AddNewSourceBuffer(
356         MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc());
357     for (const auto &R : Replaces) {
358       SMDiagnostic Diag = Mgr.GetMessage(
359           SMLoc::getFromPointer(StartBuf + R.getOffset()),
360           WarningsAsErrors ? SourceMgr::DiagKind::DK_Error
361                            : SourceMgr::DiagKind::DK_Warning,
362           "code should be clang-formatted [-Wclang-format-violations]");
363 
364       Diag.print(nullptr, llvm::errs(), ShowColors && !NoShowColors);
365       if (ErrorLimit && ++Errors >= ErrorLimit)
366         break;
367     }
368   }
369   return WarningsAsErrors;
370 }
371 
outputXML(const Replacements & Replaces,const Replacements & FormatChanges,const FormattingAttemptStatus & Status,const cl::opt<unsigned> & Cursor,unsigned CursorPosition)372 static void outputXML(const Replacements &Replaces,
373                       const Replacements &FormatChanges,
374                       const FormattingAttemptStatus &Status,
375                       const cl::opt<unsigned> &Cursor,
376                       unsigned CursorPosition) {
377   outs() << "<?xml version='1.0'?>\n<replacements "
378             "xml:space='preserve' incomplete_format='"
379          << (Status.FormatComplete ? "false" : "true") << "'";
380   if (!Status.FormatComplete)
381     outs() << " line='" << Status.Line << "'";
382   outs() << ">\n";
383   if (Cursor.getNumOccurrences() != 0) {
384     outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition)
385            << "</cursor>\n";
386   }
387 
388   outputReplacementsXML(Replaces);
389   outs() << "</replacements>\n";
390 }
391 
392 class ClangFormatDiagConsumer : public DiagnosticConsumer {
anchor()393   virtual void anchor() {}
394 
HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,const Diagnostic & Info)395   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
396                         const Diagnostic &Info) override {
397 
398     SmallVector<char, 16> vec;
399     Info.FormatDiagnostic(vec);
400     errs() << "clang-format error:" << vec << "\n";
401   }
402 };
403 
404 // Returns true on error.
format(StringRef FileName,bool ErrorOnIncompleteFormat=false)405 static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) {
406   const bool IsSTDIN = FileName == "-";
407   if (!OutputXML && Inplace && IsSTDIN) {
408     errs() << "error: cannot use -i when reading from stdin.\n";
409     return true;
410   }
411   // On Windows, overwriting a file with an open file mapping doesn't work,
412   // so read the whole file into memory when formatting in-place.
413   ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
414       !OutputXML && Inplace
415           ? MemoryBuffer::getFileAsStream(FileName)
416           : MemoryBuffer::getFileOrSTDIN(FileName, /*IsText=*/true);
417   if (std::error_code EC = CodeOrErr.getError()) {
418     errs() << FileName << ": " << EC.message() << "\n";
419     return true;
420   }
421   std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
422   if (Code->getBufferSize() == 0)
423     return false; // Empty files are formatted correctly.
424 
425   StringRef BufStr = Code->getBuffer();
426 
427   const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr);
428 
429   if (InvalidBOM) {
430     errs() << "error: encoding with unsupported byte order mark \""
431            << InvalidBOM << "\" detected";
432     if (!IsSTDIN)
433       errs() << " in file '" << FileName << "'";
434     errs() << ".\n";
435     return true;
436   }
437 
438   std::vector<tooling::Range> Ranges;
439   if (fillRanges(Code.get(), Ranges))
440     return true;
441   StringRef AssumedFileName = IsSTDIN ? AssumeFileName : FileName;
442   if (AssumedFileName.empty()) {
443     llvm::errs() << "error: empty filenames are not allowed\n";
444     return true;
445   }
446 
447   Expected<FormatStyle> FormatStyle =
448       getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(),
449                nullptr, WNoErrorList.isSet(WNoError::Unknown));
450   if (!FormatStyle) {
451     llvm::errs() << toString(FormatStyle.takeError()) << "\n";
452     return true;
453   }
454 
455   StringRef QualifierAlignmentOrder = QualifierAlignment;
456 
457   FormatStyle->QualifierAlignment =
458       StringSwitch<FormatStyle::QualifierAlignmentStyle>(
459           QualifierAlignmentOrder.lower())
460           .Case("right", FormatStyle::QAS_Right)
461           .Case("left", FormatStyle::QAS_Left)
462           .Default(FormatStyle->QualifierAlignment);
463 
464   if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Left) {
465     FormatStyle->QualifierOrder = {"const", "volatile", "type"};
466   } else if (FormatStyle->QualifierAlignment == FormatStyle::QAS_Right) {
467     FormatStyle->QualifierOrder = {"type", "const", "volatile"};
468   } else if (QualifierAlignmentOrder.contains("type")) {
469     FormatStyle->QualifierAlignment = FormatStyle::QAS_Custom;
470     SmallVector<StringRef> Qualifiers;
471     QualifierAlignmentOrder.split(Qualifiers, " ", /*MaxSplit=*/-1,
472                                   /*KeepEmpty=*/false);
473     FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()};
474   }
475 
476   if (SortIncludes.getNumOccurrences() != 0) {
477     FormatStyle->SortIncludes = {};
478     if (SortIncludes)
479       FormatStyle->SortIncludes.Enabled = true;
480   }
481   unsigned CursorPosition = Cursor;
482   Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
483                                        AssumedFileName, &CursorPosition);
484 
485   const bool IsJson = FormatStyle->isJson();
486 
487   // To format JSON insert a variable to trick the code into thinking its
488   // JavaScript.
489   if (IsJson && !FormatStyle->DisableFormat) {
490     auto Err =
491         Replaces.add(tooling::Replacement(AssumedFileName, 0, 0, "x = "));
492     if (Err)
493       llvm::errs() << "Bad Json variable insertion\n";
494   }
495 
496   auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
497   if (!ChangedCode) {
498     llvm::errs() << toString(ChangedCode.takeError()) << "\n";
499     return true;
500   }
501   // Get new affected ranges after sorting `#includes`.
502   Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges);
503   FormattingAttemptStatus Status;
504   Replacements FormatChanges =
505       reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status);
506   Replaces = Replaces.merge(FormatChanges);
507   if (DryRun) {
508     return Replaces.size() > (IsJson ? 1u : 0u) &&
509            emitReplacementWarnings(Replaces, AssumedFileName, Code);
510   }
511   if (OutputXML) {
512     outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition);
513   } else {
514     IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
515         new llvm::vfs::InMemoryFileSystem);
516     FileManager Files(FileSystemOptions(), InMemoryFileSystem);
517 
518     DiagnosticOptions DiagOpts;
519     ClangFormatDiagConsumer IgnoreDiagnostics;
520     DiagnosticsEngine Diagnostics(
521         IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), DiagOpts,
522         &IgnoreDiagnostics, false);
523     SourceManager Sources(Diagnostics, Files);
524     FileID ID = createInMemoryFile(AssumedFileName, *Code, Sources, Files,
525                                    InMemoryFileSystem.get());
526     Rewriter Rewrite(Sources, LangOptions());
527     tooling::applyAllReplacements(Replaces, Rewrite);
528     if (Inplace) {
529       if (Rewrite.overwriteChangedFiles())
530         return true;
531     } else {
532       if (Cursor.getNumOccurrences() != 0) {
533         outs() << "{ \"Cursor\": "
534                << FormatChanges.getShiftedCodePosition(CursorPosition)
535                << ", \"IncompleteFormat\": "
536                << (Status.FormatComplete ? "false" : "true");
537         if (!Status.FormatComplete)
538           outs() << ", \"Line\": " << Status.Line;
539         outs() << " }\n";
540       }
541       Rewrite.getEditBuffer(ID).write(outs());
542     }
543   }
544   return ErrorOnIncompleteFormat && !Status.FormatComplete;
545 }
546 
547 } // namespace format
548 } // namespace clang
549 
PrintVersion(raw_ostream & OS)550 static void PrintVersion(raw_ostream &OS) {
551   OS << clang::getClangToolFullVersion("clang-format") << '\n';
552 }
553 
554 // Dump the configuration.
dumpConfig()555 static int dumpConfig() {
556   std::unique_ptr<llvm::MemoryBuffer> Code;
557   // We can't read the code to detect the language if there's no file name.
558   if (!FileNames.empty()) {
559     // Read in the code in case the filename alone isn't enough to detect the
560     // language.
561     ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
562         MemoryBuffer::getFileOrSTDIN(FileNames[0], /*IsText=*/true);
563     if (std::error_code EC = CodeOrErr.getError()) {
564       llvm::errs() << EC.message() << "\n";
565       return 1;
566     }
567     Code = std::move(CodeOrErr.get());
568   }
569   Expected<clang::format::FormatStyle> FormatStyle = clang::format::getStyle(
570       Style,
571       FileNames.empty() || FileNames[0] == "-" ? AssumeFileName : FileNames[0],
572       FallbackStyle, Code ? Code->getBuffer() : "");
573   if (!FormatStyle) {
574     llvm::errs() << toString(FormatStyle.takeError()) << "\n";
575     return 1;
576   }
577   std::string Config = clang::format::configurationAsText(*FormatStyle);
578   outs() << Config << "\n";
579   return 0;
580 }
581 
582 using String = SmallString<128>;
583 static String IgnoreDir;             // Directory of .clang-format-ignore file.
584 static String PrevDir;               // Directory of previous `FilePath`.
585 static SmallVector<String> Patterns; // Patterns in .clang-format-ignore file.
586 
587 // Check whether `FilePath` is ignored according to the nearest
588 // .clang-format-ignore file based on the rules below:
589 // - A blank line is skipped.
590 // - Leading and trailing spaces of a line are trimmed.
591 // - A line starting with a hash (`#`) is a comment.
592 // - A non-comment line is a single pattern.
593 // - The slash (`/`) is used as the directory separator.
594 // - A pattern is relative to the directory of the .clang-format-ignore file (or
595 //   the root directory if the pattern starts with a slash).
596 // - A pattern is negated if it starts with a bang (`!`).
isIgnored(StringRef FilePath)597 static bool isIgnored(StringRef FilePath) {
598   using namespace llvm::sys::fs;
599   if (!is_regular_file(FilePath))
600     return false;
601 
602   String Path;
603   String AbsPath{FilePath};
604 
605   using namespace llvm::sys::path;
606   make_absolute(AbsPath);
607   remove_dots(AbsPath, /*remove_dot_dot=*/true);
608 
609   if (StringRef Dir{parent_path(AbsPath)}; PrevDir != Dir) {
610     PrevDir = Dir;
611 
612     for (;;) {
613       Path = Dir;
614       append(Path, ".clang-format-ignore");
615       if (is_regular_file(Path))
616         break;
617       Dir = parent_path(Dir);
618       if (Dir.empty())
619         return false;
620     }
621 
622     IgnoreDir = convert_to_slash(Dir);
623 
624     std::ifstream IgnoreFile{Path.c_str()};
625     if (!IgnoreFile.good())
626       return false;
627 
628     Patterns.clear();
629 
630     for (std::string Line; std::getline(IgnoreFile, Line);) {
631       if (const auto Pattern{StringRef{Line}.trim()};
632           // Skip empty and comment lines.
633           !Pattern.empty() && Pattern[0] != '#') {
634         Patterns.push_back(Pattern);
635       }
636     }
637   }
638 
639   if (IgnoreDir.empty())
640     return false;
641 
642   const auto Pathname{convert_to_slash(AbsPath)};
643   for (const auto &Pat : Patterns) {
644     const bool IsNegated = Pat[0] == '!';
645     StringRef Pattern{Pat};
646     if (IsNegated)
647       Pattern = Pattern.drop_front();
648 
649     if (Pattern.empty())
650       continue;
651 
652     Pattern = Pattern.ltrim();
653 
654     // `Pattern` is relative to `IgnoreDir` unless it starts with a slash.
655     // This doesn't support patterns containing drive names (e.g. `C:`).
656     if (Pattern[0] != '/') {
657       Path = IgnoreDir;
658       append(Path, Style::posix, Pattern);
659       remove_dots(Path, /*remove_dot_dot=*/true, Style::posix);
660       Pattern = Path;
661     }
662 
663     if (clang::format::matchFilePath(Pattern, Pathname) == !IsNegated)
664       return true;
665   }
666 
667   return false;
668 }
669 
main(int argc,const char ** argv)670 int main(int argc, const char **argv) {
671   InitLLVM X(argc, argv);
672 
673   cl::HideUnrelatedOptions(ClangFormatCategory);
674 
675   cl::SetVersionPrinter(PrintVersion);
676   cl::ParseCommandLineOptions(
677       argc, argv,
678       "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
679       "code.\n\n"
680       "If no arguments are specified, it formats the code from standard input\n"
681       "and writes the result to the standard output.\n"
682       "If <file>s are given, it reformats the files. If -i is specified\n"
683       "together with <file>s, the files are edited in-place. Otherwise, the\n"
684       "result is written to the standard output.\n");
685 
686   if (Help) {
687     cl::PrintHelpMessage();
688     return 0;
689   }
690 
691   if (DumpConfig)
692     return dumpConfig();
693 
694   if (!Files.empty()) {
695     std::ifstream ExternalFileOfFiles{std::string(Files)};
696     std::string Line;
697     unsigned LineNo = 1;
698     while (std::getline(ExternalFileOfFiles, Line)) {
699       FileNames.push_back(Line);
700       LineNo++;
701     }
702     errs() << "Clang-formatting " << LineNo << " files\n";
703   }
704 
705   if (FileNames.empty()) {
706     if (isIgnored(AssumeFileName))
707       return 0;
708     return clang::format::format("-", FailOnIncompleteFormat);
709   }
710 
711   if (FileNames.size() > 1 &&
712       (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) {
713     errs() << "error: -offset, -length and -lines can only be used for "
714               "single file.\n";
715     return 1;
716   }
717 
718   unsigned FileNo = 1;
719   bool Error = false;
720   for (const auto &FileName : FileNames) {
721     const bool Ignored = isIgnored(FileName);
722     if (ListIgnored) {
723       if (Ignored)
724         outs() << FileName << '\n';
725       continue;
726     }
727     if (Ignored)
728       continue;
729     if (Verbose) {
730       errs() << "Formatting [" << FileNo++ << "/" << FileNames.size() << "] "
731              << FileName << "\n";
732     }
733     Error |= clang::format::format(FileName, FailOnIncompleteFormat);
734   }
735   return Error ? 1 : 0;
736 }
737