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