xref: /netbsd-src/external/apache2/llvm/dist/clang/tools/clang-format/ClangFormat.cpp (revision 76c7fc5f6b13ed0b1508e6b313e88e59977ed78e)
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/Support/CommandLine.h"
23 #include "llvm/Support/FileSystem.h"
24 #include "llvm/Support/InitLLVM.h"
25 #include "llvm/Support/Process.h"
26 
27 using namespace llvm;
28 using clang::tooling::Replacements;
29 
30 static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
31 
32 // Mark all our options with this category, everything else (except for -version
33 // and -help) will be hidden.
34 static cl::OptionCategory ClangFormatCategory("Clang-format options");
35 
36 static cl::list<unsigned>
37     Offsets("offset",
38             cl::desc("Format a range starting at this byte offset.\n"
39                      "Multiple ranges can be formatted by specifying\n"
40                      "several -offset and -length pairs.\n"
41                      "Can only be used with one input file."),
42             cl::cat(ClangFormatCategory));
43 static cl::list<unsigned>
44     Lengths("length",
45             cl::desc("Format a range of this length (in bytes).\n"
46                      "Multiple ranges can be formatted by specifying\n"
47                      "several -offset and -length pairs.\n"
48                      "When only a single -offset is specified without\n"
49                      "-length, clang-format will format up to the end\n"
50                      "of the file.\n"
51                      "Can only be used with one input file."),
52             cl::cat(ClangFormatCategory));
53 static cl::list<std::string>
54     LineRanges("lines",
55                cl::desc("<start line>:<end line> - format a range of\n"
56                         "lines (both 1-based).\n"
57                         "Multiple ranges can be formatted by specifying\n"
58                         "several -lines arguments.\n"
59                         "Can't be used with -offset and -length.\n"
60                         "Can only be used with one input file."),
61                cl::cat(ClangFormatCategory));
62 static cl::opt<std::string>
63     Style("style", cl::desc(clang::format::StyleOptionHelpDescription),
64           cl::init(clang::format::DefaultFormatStyle),
65           cl::cat(ClangFormatCategory));
66 static cl::opt<std::string>
67     FallbackStyle("fallback-style",
68                   cl::desc("The name of the predefined style used as a\n"
69                            "fallback in case clang-format is invoked with\n"
70                            "-style=file, but can not find the .clang-format\n"
71                            "file to use.\n"
72                            "Use -fallback-style=none to skip formatting."),
73                   cl::init(clang::format::DefaultFallbackStyle),
74                   cl::cat(ClangFormatCategory));
75 
76 static cl::opt<std::string> AssumeFileName(
77     "assume-filename",
78     cl::desc("When reading from stdin, clang-format assumes this\n"
79              "filename to look for a style config file (with\n"
80              "-style=file) and to determine the language."),
81     cl::init("<stdin>"), cl::cat(ClangFormatCategory));
82 
83 static cl::opt<bool> Inplace("i",
84                              cl::desc("Inplace edit <file>s, if specified."),
85                              cl::cat(ClangFormatCategory));
86 
87 static cl::opt<bool> OutputXML("output-replacements-xml",
88                                cl::desc("Output replacements as XML."),
89                                cl::cat(ClangFormatCategory));
90 static cl::opt<bool>
91     DumpConfig("dump-config",
92                cl::desc("Dump configuration options to stdout and exit.\n"
93                         "Can be used with -style option."),
94                cl::cat(ClangFormatCategory));
95 static cl::opt<unsigned>
96     Cursor("cursor",
97            cl::desc("The position of the cursor when invoking\n"
98                     "clang-format from an editor integration"),
99            cl::init(0), cl::cat(ClangFormatCategory));
100 
101 static cl::opt<bool> SortIncludes(
102     "sort-includes",
103     cl::desc("If set, overrides the include sorting behavior determined by the "
104              "SortIncludes style flag"),
105     cl::cat(ClangFormatCategory));
106 
107 static cl::opt<bool>
108     Verbose("verbose", cl::desc("If set, shows the list of processed files"),
109             cl::cat(ClangFormatCategory));
110 
111 // Use --dry-run to match other LLVM tools when you mean do it but don't
112 // actually do it
113 static cl::opt<bool>
114     DryRun("dry-run",
115            cl::desc("If set, do not actually make the formatting changes"),
116            cl::cat(ClangFormatCategory));
117 
118 // Use -n as a common command as an alias for --dry-run. (git and make use -n)
119 static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"),
120                              cl::cat(ClangFormatCategory), cl::aliasopt(DryRun),
121                              cl::NotHidden);
122 
123 // Emulate being able to turn on/off the warning.
124 static cl::opt<bool>
125     WarnFormat("Wclang-format-violations",
126                cl::desc("Warnings about individual formatting changes needed. "
127                         "Used only with --dry-run or -n"),
128                cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden);
129 
130 static cl::opt<bool>
131     NoWarnFormat("Wno-clang-format-violations",
132                  cl::desc("Do not warn about individual formatting changes "
133                           "needed. Used only with --dry-run or -n"),
134                  cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden);
135 
136 static cl::opt<unsigned> ErrorLimit(
137     "ferror-limit",
138     cl::desc("Set the maximum number of clang-format errors to emit before "
139              "stopping (0 = no limit). Used only with --dry-run or -n"),
140     cl::init(0), cl::cat(ClangFormatCategory));
141 
142 static cl::opt<bool>
143     WarningsAsErrors("Werror",
144                      cl::desc("If set, changes formatting warnings to errors"),
145                      cl::cat(ClangFormatCategory));
146 
147 static cl::opt<bool>
148     ShowColors("fcolor-diagnostics",
149                cl::desc("If set, and on a color-capable terminal controls "
150                         "whether or not to print diagnostics in color"),
151                cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden);
152 
153 static cl::opt<bool>
154     NoShowColors("fno-color-diagnostics",
155                  cl::desc("If set, and on a color-capable terminal controls "
156                           "whether or not to print diagnostics in color"),
157                  cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden);
158 
159 static cl::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"),
160                                        cl::cat(ClangFormatCategory));
161 
162 namespace clang {
163 namespace format {
164 
165 static FileID createInMemoryFile(StringRef FileName, MemoryBuffer *Source,
166                                  SourceManager &Sources, FileManager &Files,
167                                  llvm::vfs::InMemoryFileSystem *MemFS) {
168   MemFS->addFileNoOwn(FileName, 0, Source);
169   auto File = Files.getFile(FileName);
170   return Sources.createFileID(File ? *File : nullptr, SourceLocation(),
171                               SrcMgr::C_User);
172 }
173 
174 // Parses <start line>:<end line> input to a pair of line numbers.
175 // Returns true on error.
176 static bool parseLineRange(StringRef Input, unsigned &FromLine,
177                            unsigned &ToLine) {
178   std::pair<StringRef, StringRef> LineRange = Input.split(':');
179   return LineRange.first.getAsInteger(0, FromLine) ||
180          LineRange.second.getAsInteger(0, ToLine);
181 }
182 
183 static bool fillRanges(MemoryBuffer *Code,
184                        std::vector<tooling::Range> &Ranges) {
185   IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
186       new llvm::vfs::InMemoryFileSystem);
187   FileManager Files(FileSystemOptions(), InMemoryFileSystem);
188   DiagnosticsEngine Diagnostics(
189       IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
190       new DiagnosticOptions);
191   SourceManager Sources(Diagnostics, Files);
192   FileID ID = createInMemoryFile("<irrelevant>", Code, Sources, Files,
193                                  InMemoryFileSystem.get());
194   if (!LineRanges.empty()) {
195     if (!Offsets.empty() || !Lengths.empty()) {
196       errs() << "error: cannot use -lines with -offset/-length\n";
197       return true;
198     }
199 
200     for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) {
201       unsigned FromLine, ToLine;
202       if (parseLineRange(LineRanges[i], FromLine, ToLine)) {
203         errs() << "error: invalid <start line>:<end line> pair\n";
204         return true;
205       }
206       if (FromLine > ToLine) {
207         errs() << "error: start line should be less than end line\n";
208         return true;
209       }
210       SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1);
211       SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX);
212       if (Start.isInvalid() || End.isInvalid())
213         return true;
214       unsigned Offset = Sources.getFileOffset(Start);
215       unsigned Length = Sources.getFileOffset(End) - Offset;
216       Ranges.push_back(tooling::Range(Offset, Length));
217     }
218     return false;
219   }
220 
221   if (Offsets.empty())
222     Offsets.push_back(0);
223   if (Offsets.size() != Lengths.size() &&
224       !(Offsets.size() == 1 && Lengths.empty())) {
225     errs() << "error: number of -offset and -length arguments must match.\n";
226     return true;
227   }
228   for (unsigned i = 0, e = Offsets.size(); i != e; ++i) {
229     if (Offsets[i] >= Code->getBufferSize()) {
230       errs() << "error: offset " << Offsets[i] << " is outside the file\n";
231       return true;
232     }
233     SourceLocation Start =
234         Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]);
235     SourceLocation End;
236     if (i < Lengths.size()) {
237       if (Offsets[i] + Lengths[i] > Code->getBufferSize()) {
238         errs() << "error: invalid length " << Lengths[i]
239                << ", offset + length (" << Offsets[i] + Lengths[i]
240                << ") is outside the file.\n";
241         return true;
242       }
243       End = Start.getLocWithOffset(Lengths[i]);
244     } else {
245       End = Sources.getLocForEndOfFile(ID);
246     }
247     unsigned Offset = Sources.getFileOffset(Start);
248     unsigned Length = Sources.getFileOffset(End) - Offset;
249     Ranges.push_back(tooling::Range(Offset, Length));
250   }
251   return false;
252 }
253 
254 static void outputReplacementXML(StringRef Text) {
255   // FIXME: When we sort includes, we need to make sure the stream is correct
256   // utf-8.
257   size_t From = 0;
258   size_t Index;
259   while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) {
260     outs() << Text.substr(From, Index - From);
261     switch (Text[Index]) {
262     case '\n':
263       outs() << "&#10;";
264       break;
265     case '\r':
266       outs() << "&#13;";
267       break;
268     case '<':
269       outs() << "&lt;";
270       break;
271     case '&':
272       outs() << "&amp;";
273       break;
274     default:
275       llvm_unreachable("Unexpected character encountered!");
276     }
277     From = Index + 1;
278   }
279   outs() << Text.substr(From);
280 }
281 
282 static void outputReplacementsXML(const Replacements &Replaces) {
283   for (const auto &R : Replaces) {
284     outs() << "<replacement "
285            << "offset='" << R.getOffset() << "' "
286            << "length='" << R.getLength() << "'>";
287     outputReplacementXML(R.getReplacementText());
288     outs() << "</replacement>\n";
289   }
290 }
291 
292 static bool
293 emitReplacementWarnings(const Replacements &Replaces, StringRef AssumedFileName,
294                         const std::unique_ptr<llvm::MemoryBuffer> &Code) {
295   if (Replaces.empty()) {
296     return false;
297   }
298 
299   IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
300   DiagOpts->ShowColors = (ShowColors && !NoShowColors);
301 
302   IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
303   IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
304       new DiagnosticsEngine(DiagID, &*DiagOpts));
305 
306   IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
307       new llvm::vfs::InMemoryFileSystem);
308   FileManager Files(FileSystemOptions(), InMemoryFileSystem);
309   SourceManager Sources(*Diags, Files);
310   FileID FileID = createInMemoryFile(AssumedFileName, Code.get(), Sources,
311                                      Files, InMemoryFileSystem.get());
312 
313   FileManager &FileMgr = Sources.getFileManager();
314   llvm::ErrorOr<const FileEntry *> FileEntryPtr =
315       FileMgr.getFile(AssumedFileName);
316 
317   unsigned Errors = 0;
318   if (WarnFormat && !NoWarnFormat) {
319     for (const auto &R : Replaces) {
320       PresumedLoc PLoc = Sources.getPresumedLoc(
321           Sources.getLocForStartOfFile(FileID).getLocWithOffset(R.getOffset()));
322 
323       SourceLocation LineBegin =
324           Sources.translateFileLineCol(FileEntryPtr.get(), PLoc.getLine(), 1);
325       SourceLocation NextLineBegin = Sources.translateFileLineCol(
326           FileEntryPtr.get(), PLoc.getLine() + 1, 1);
327 
328       const char *StartBuf = Sources.getCharacterData(LineBegin);
329       const char *EndBuf = Sources.getCharacterData(NextLineBegin);
330 
331       StringRef Line(StartBuf, (EndBuf - StartBuf) - 1);
332 
333       SMDiagnostic Diags(
334           llvm::SourceMgr(), SMLoc(), AssumedFileName, PLoc.getLine(),
335           PLoc.getColumn(),
336           WarningsAsErrors ? SourceMgr::DiagKind::DK_Error
337                            : SourceMgr::DiagKind::DK_Warning,
338           "code should be clang-formatted [-Wclang-format-violations]", Line,
339           ArrayRef<std::pair<unsigned, unsigned>>());
340 
341       Diags.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors));
342       Errors++;
343       if (ErrorLimit && Errors >= ErrorLimit)
344         break;
345     }
346   }
347   return WarningsAsErrors;
348 }
349 
350 static void outputXML(const Replacements &Replaces,
351                       const Replacements &FormatChanges,
352                       const FormattingAttemptStatus &Status,
353                       const cl::opt<unsigned> &Cursor,
354                       unsigned CursorPosition) {
355   outs() << "<?xml version='1.0'?>\n<replacements "
356             "xml:space='preserve' incomplete_format='"
357          << (Status.FormatComplete ? "false" : "true") << "'";
358   if (!Status.FormatComplete)
359     outs() << " line='" << Status.Line << "'";
360   outs() << ">\n";
361   if (Cursor.getNumOccurrences() != 0)
362     outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition)
363            << "</cursor>\n";
364 
365   outputReplacementsXML(Replaces);
366   outs() << "</replacements>\n";
367 }
368 
369 // Returns true on error.
370 static bool format(StringRef FileName) {
371   if (!OutputXML && Inplace && FileName == "-") {
372     errs() << "error: cannot use -i when reading from stdin.\n";
373     return false;
374   }
375   // On Windows, overwriting a file with an open file mapping doesn't work,
376   // so read the whole file into memory when formatting in-place.
377   ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
378       !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName)
379                             : MemoryBuffer::getFileOrSTDIN(FileName);
380   if (std::error_code EC = CodeOrErr.getError()) {
381     errs() << EC.message() << "\n";
382     return true;
383   }
384   std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
385   if (Code->getBufferSize() == 0)
386     return false; // Empty files are formatted correctly.
387 
388   StringRef BufStr = Code->getBuffer();
389 
390   const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr);
391 
392   if (InvalidBOM) {
393     errs() << "error: encoding with unsupported byte order mark \""
394            << InvalidBOM << "\" detected";
395     if (FileName != "-")
396       errs() << " in file '" << FileName << "'";
397     errs() << ".\n";
398     return true;
399   }
400 
401   std::vector<tooling::Range> Ranges;
402   if (fillRanges(Code.get(), Ranges))
403     return true;
404   StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName;
405 
406   llvm::Expected<FormatStyle> FormatStyle =
407       getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer());
408   if (!FormatStyle) {
409     llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
410     return true;
411   }
412 
413   if (SortIncludes.getNumOccurrences() != 0)
414     FormatStyle->SortIncludes = SortIncludes;
415   unsigned CursorPosition = Cursor;
416   Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
417                                        AssumedFileName, &CursorPosition);
418   auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
419   if (!ChangedCode) {
420     llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
421     return true;
422   }
423   // Get new affected ranges after sorting `#includes`.
424   Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges);
425   FormattingAttemptStatus Status;
426   Replacements FormatChanges =
427       reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status);
428   Replaces = Replaces.merge(FormatChanges);
429   if (OutputXML || DryRun) {
430     if (DryRun) {
431       return emitReplacementWarnings(Replaces, AssumedFileName, Code);
432     } else {
433       outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition);
434     }
435   } else {
436     IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
437         new llvm::vfs::InMemoryFileSystem);
438     FileManager Files(FileSystemOptions(), InMemoryFileSystem);
439     DiagnosticsEngine Diagnostics(
440         IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
441         new DiagnosticOptions);
442     SourceManager Sources(Diagnostics, Files);
443     FileID ID = createInMemoryFile(AssumedFileName, Code.get(), Sources, Files,
444                                    InMemoryFileSystem.get());
445     Rewriter Rewrite(Sources, LangOptions());
446     tooling::applyAllReplacements(Replaces, Rewrite);
447     if (Inplace) {
448       if (Rewrite.overwriteChangedFiles())
449         return true;
450     } else {
451       if (Cursor.getNumOccurrences() != 0) {
452         outs() << "{ \"Cursor\": "
453                << FormatChanges.getShiftedCodePosition(CursorPosition)
454                << ", \"IncompleteFormat\": "
455                << (Status.FormatComplete ? "false" : "true");
456         if (!Status.FormatComplete)
457           outs() << ", \"Line\": " << Status.Line;
458         outs() << " }\n";
459       }
460       Rewrite.getEditBuffer(ID).write(outs());
461     }
462   }
463   return false;
464 }
465 
466 } // namespace format
467 } // namespace clang
468 
469 static void PrintVersion(raw_ostream &OS) {
470   OS << clang::getClangToolFullVersion("clang-format") << '\n';
471 }
472 
473 // Dump the configuration.
474 static int dumpConfig() {
475   StringRef FileName;
476   std::unique_ptr<llvm::MemoryBuffer> Code;
477   if (FileNames.empty()) {
478     // We can't read the code to detect the language if there's no
479     // file name, so leave Code empty here.
480     FileName = AssumeFileName;
481   } else {
482     // Read in the code in case the filename alone isn't enough to
483     // detect the language.
484     ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
485         MemoryBuffer::getFileOrSTDIN(FileNames[0]);
486     if (std::error_code EC = CodeOrErr.getError()) {
487       llvm::errs() << EC.message() << "\n";
488       return 1;
489     }
490     FileName = (FileNames[0] == "-") ? AssumeFileName : FileNames[0];
491     Code = std::move(CodeOrErr.get());
492   }
493   llvm::Expected<clang::format::FormatStyle> FormatStyle =
494       clang::format::getStyle(Style, FileName, FallbackStyle,
495                               Code ? Code->getBuffer() : "");
496   if (!FormatStyle) {
497     llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
498     return 1;
499   }
500   std::string Config = clang::format::configurationAsText(*FormatStyle);
501   outs() << Config << "\n";
502   return 0;
503 }
504 
505 int main(int argc, const char **argv) {
506   llvm::InitLLVM X(argc, argv);
507 
508   cl::HideUnrelatedOptions(ClangFormatCategory);
509 
510   cl::SetVersionPrinter(PrintVersion);
511   cl::ParseCommandLineOptions(
512       argc, argv,
513       "A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.\n\n"
514       "If no arguments are specified, it formats the code from standard input\n"
515       "and writes the result to the standard output.\n"
516       "If <file>s are given, it reformats the files. If -i is specified\n"
517       "together with <file>s, the files are edited in-place. Otherwise, the\n"
518       "result is written to the standard output.\n");
519 
520   if (Help) {
521     cl::PrintHelpMessage();
522     return 0;
523   }
524 
525   if (DumpConfig) {
526     return dumpConfig();
527   }
528 
529   bool Error = false;
530   if (FileNames.empty()) {
531     Error = clang::format::format("-");
532     return Error ? 1 : 0;
533   }
534   if (FileNames.size() != 1 &&
535       (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) {
536     errs() << "error: -offset, -length and -lines can only be used for "
537               "single file.\n";
538     return 1;
539   }
540   for (const auto &FileName : FileNames) {
541     if (Verbose)
542       errs() << "Formatting " << FileName << "\n";
543     Error |= clang::format::format(FileName);
544   }
545   return Error ? 1 : 0;
546 }
547