xref: /freebsd/contrib/llvm-project/clang/tools/clang-format/ClangFormat.cpp (revision 5ffd83dbcc34f10e07f6d3e968ae6365869615f4)
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("Override filename used to determine the language.\n"
79              "When reading from stdin, clang-format assumes this\n"
80              "filename 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   unsigned Errors = 0;
299   if (WarnFormat && !NoWarnFormat) {
300     llvm::SourceMgr Mgr;
301     const char *StartBuf = Code->getBufferStart();
302 
303     Mgr.AddNewSourceBuffer(
304         MemoryBuffer::getMemBuffer(StartBuf, AssumedFileName), SMLoc());
305     for (const auto &R : Replaces) {
306       SMDiagnostic Diag = Mgr.GetMessage(
307           SMLoc::getFromPointer(StartBuf + R.getOffset()),
308           WarningsAsErrors ? SourceMgr::DiagKind::DK_Error
309                            : SourceMgr::DiagKind::DK_Warning,
310           "code should be clang-formatted [-Wclang-format-violations]");
311 
312       Diag.print(nullptr, llvm::errs(), (ShowColors && !NoShowColors));
313       if (ErrorLimit && ++Errors >= ErrorLimit)
314         break;
315     }
316   }
317   return WarningsAsErrors;
318 }
319 
320 static void outputXML(const Replacements &Replaces,
321                       const Replacements &FormatChanges,
322                       const FormattingAttemptStatus &Status,
323                       const cl::opt<unsigned> &Cursor,
324                       unsigned CursorPosition) {
325   outs() << "<?xml version='1.0'?>\n<replacements "
326             "xml:space='preserve' incomplete_format='"
327          << (Status.FormatComplete ? "false" : "true") << "'";
328   if (!Status.FormatComplete)
329     outs() << " line='" << Status.Line << "'";
330   outs() << ">\n";
331   if (Cursor.getNumOccurrences() != 0)
332     outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition)
333            << "</cursor>\n";
334 
335   outputReplacementsXML(Replaces);
336   outs() << "</replacements>\n";
337 }
338 
339 // Returns true on error.
340 static bool format(StringRef FileName) {
341   if (!OutputXML && Inplace && FileName == "-") {
342     errs() << "error: cannot use -i when reading from stdin.\n";
343     return false;
344   }
345   // On Windows, overwriting a file with an open file mapping doesn't work,
346   // so read the whole file into memory when formatting in-place.
347   ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
348       !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName)
349                             : MemoryBuffer::getFileOrSTDIN(FileName);
350   if (std::error_code EC = CodeOrErr.getError()) {
351     errs() << EC.message() << "\n";
352     return true;
353   }
354   std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
355   if (Code->getBufferSize() == 0)
356     return false; // Empty files are formatted correctly.
357 
358   StringRef BufStr = Code->getBuffer();
359 
360   const char *InvalidBOM = SrcMgr::ContentCache::getInvalidBOM(BufStr);
361 
362   if (InvalidBOM) {
363     errs() << "error: encoding with unsupported byte order mark \""
364            << InvalidBOM << "\" detected";
365     if (FileName != "-")
366       errs() << " in file '" << FileName << "'";
367     errs() << ".\n";
368     return true;
369   }
370 
371   std::vector<tooling::Range> Ranges;
372   if (fillRanges(Code.get(), Ranges))
373     return true;
374   StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName;
375   if (AssumedFileName.empty()) {
376     llvm::errs() << "error: empty filenames are not allowed\n";
377     return true;
378   }
379 
380   llvm::Expected<FormatStyle> FormatStyle =
381       getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer());
382   if (!FormatStyle) {
383     llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
384     return true;
385   }
386 
387   if (SortIncludes.getNumOccurrences() != 0)
388     FormatStyle->SortIncludes = SortIncludes;
389   unsigned CursorPosition = Cursor;
390   Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
391                                        AssumedFileName, &CursorPosition);
392   auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
393   if (!ChangedCode) {
394     llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
395     return true;
396   }
397   // Get new affected ranges after sorting `#includes`.
398   Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges);
399   FormattingAttemptStatus Status;
400   Replacements FormatChanges =
401       reformat(*FormatStyle, *ChangedCode, Ranges, AssumedFileName, &Status);
402   Replaces = Replaces.merge(FormatChanges);
403   if (OutputXML || DryRun) {
404     if (DryRun) {
405       return emitReplacementWarnings(Replaces, AssumedFileName, Code);
406     } else {
407       outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition);
408     }
409   } else {
410     IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
411         new llvm::vfs::InMemoryFileSystem);
412     FileManager Files(FileSystemOptions(), InMemoryFileSystem);
413     DiagnosticsEngine Diagnostics(
414         IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
415         new DiagnosticOptions);
416     SourceManager Sources(Diagnostics, Files);
417     FileID ID = createInMemoryFile(AssumedFileName, Code.get(), Sources, Files,
418                                    InMemoryFileSystem.get());
419     Rewriter Rewrite(Sources, LangOptions());
420     tooling::applyAllReplacements(Replaces, Rewrite);
421     if (Inplace) {
422       if (Rewrite.overwriteChangedFiles())
423         return true;
424     } else {
425       if (Cursor.getNumOccurrences() != 0) {
426         outs() << "{ \"Cursor\": "
427                << FormatChanges.getShiftedCodePosition(CursorPosition)
428                << ", \"IncompleteFormat\": "
429                << (Status.FormatComplete ? "false" : "true");
430         if (!Status.FormatComplete)
431           outs() << ", \"Line\": " << Status.Line;
432         outs() << " }\n";
433       }
434       Rewrite.getEditBuffer(ID).write(outs());
435     }
436   }
437   return false;
438 }
439 
440 } // namespace format
441 } // namespace clang
442 
443 static void PrintVersion(raw_ostream &OS) {
444   OS << clang::getClangToolFullVersion("clang-format") << '\n';
445 }
446 
447 // Dump the configuration.
448 static int dumpConfig() {
449   StringRef FileName;
450   std::unique_ptr<llvm::MemoryBuffer> Code;
451   if (FileNames.empty()) {
452     // We can't read the code to detect the language if there's no
453     // file name, so leave Code empty here.
454     FileName = AssumeFileName;
455   } else {
456     // Read in the code in case the filename alone isn't enough to
457     // detect the language.
458     ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
459         MemoryBuffer::getFileOrSTDIN(FileNames[0]);
460     if (std::error_code EC = CodeOrErr.getError()) {
461       llvm::errs() << EC.message() << "\n";
462       return 1;
463     }
464     FileName = (FileNames[0] == "-") ? AssumeFileName : FileNames[0];
465     Code = std::move(CodeOrErr.get());
466   }
467   llvm::Expected<clang::format::FormatStyle> FormatStyle =
468       clang::format::getStyle(Style, FileName, FallbackStyle,
469                               Code ? Code->getBuffer() : "");
470   if (!FormatStyle) {
471     llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
472     return 1;
473   }
474   std::string Config = clang::format::configurationAsText(*FormatStyle);
475   outs() << Config << "\n";
476   return 0;
477 }
478 
479 int main(int argc, const char **argv) {
480   llvm::InitLLVM X(argc, argv);
481 
482   cl::HideUnrelatedOptions(ClangFormatCategory);
483 
484   cl::SetVersionPrinter(PrintVersion);
485   cl::ParseCommandLineOptions(
486       argc, argv,
487       "A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.\n\n"
488       "If no arguments are specified, it formats the code from standard input\n"
489       "and writes the result to the standard output.\n"
490       "If <file>s are given, it reformats the files. If -i is specified\n"
491       "together with <file>s, the files are edited in-place. Otherwise, the\n"
492       "result is written to the standard output.\n");
493 
494   if (Help) {
495     cl::PrintHelpMessage();
496     return 0;
497   }
498 
499   if (DumpConfig) {
500     return dumpConfig();
501   }
502 
503   bool Error = false;
504   if (FileNames.empty()) {
505     Error = clang::format::format("-");
506     return Error ? 1 : 0;
507   }
508   if (FileNames.size() != 1 &&
509       (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) {
510     errs() << "error: -offset, -length and -lines can only be used for "
511               "single file.\n";
512     return 1;
513   }
514   for (const auto &FileName : FileNames) {
515     if (Verbose)
516       errs() << "Formatting " << FileName << "\n";
517     Error |= clang::format::format(FileName);
518   }
519   return Error ? 1 : 0;
520 }
521