• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===-- clang-format/ClangFormat.cpp - Clang format tool ------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 ///
10 /// \file
11 /// \brief This file implements a clang-format tool that automatically formats
12 /// (fragments of) C++ code.
13 ///
14 //===----------------------------------------------------------------------===//
15 
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/StringMap.h"
24 #include "llvm/Support/CommandLine.h"
25 #include "llvm/Support/Debug.h"
26 #include "llvm/Support/FileSystem.h"
27 #include "llvm/Support/Signals.h"
28 
29 using namespace llvm;
30 using clang::tooling::Replacements;
31 
32 static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
33 
34 // Mark all our options with this category, everything else (except for -version
35 // and -help) will be hidden.
36 static cl::OptionCategory ClangFormatCategory("Clang-format options");
37 
38 static cl::list<unsigned>
39     Offsets("offset",
40             cl::desc("Format a range starting at this byte offset.\n"
41                      "Multiple ranges can be formatted by specifying\n"
42                      "several -offset and -length pairs.\n"
43                      "Can only be used with one input file."),
44             cl::cat(ClangFormatCategory));
45 static cl::list<unsigned>
46     Lengths("length",
47             cl::desc("Format a range of this length (in bytes).\n"
48                      "Multiple ranges can be formatted by specifying\n"
49                      "several -offset and -length pairs.\n"
50                      "When only a single -offset is specified without\n"
51                      "-length, clang-format will format up to the end\n"
52                      "of the file.\n"
53                      "Can only be used with one input file."),
54             cl::cat(ClangFormatCategory));
55 static cl::list<std::string>
56 LineRanges("lines", cl::desc("<start line>:<end line> - format a range of\n"
57                              "lines (both 1-based).\n"
58                              "Multiple ranges can be formatted by specifying\n"
59                              "several -lines arguments.\n"
60                              "Can't be used with -offset and -length.\n"
61                              "Can only be used with one input file."),
62            cl::cat(ClangFormatCategory));
63 static cl::opt<std::string>
64     Style("style",
65           cl::desc(clang::format::StyleOptionHelpDescription),
66           cl::init("file"), cl::cat(ClangFormatCategory));
67 static cl::opt<std::string>
68 FallbackStyle("fallback-style",
69               cl::desc("The name of the predefined style used as a\n"
70                        "fallback in case clang-format is invoked with\n"
71                        "-style=file, but can not find the .clang-format\n"
72                        "file to use.\n"
73                        "Use -fallback-style=none to skip formatting."),
74               cl::init("LLVM"), cl::cat(ClangFormatCategory));
75 
76 static cl::opt<std::string>
77 AssumeFileName("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::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"),
108                                        cl::cat(ClangFormatCategory));
109 
110 namespace clang {
111 namespace format {
112 
createInMemoryFile(StringRef FileName,MemoryBuffer * Source,SourceManager & Sources,FileManager & Files,vfs::InMemoryFileSystem * MemFS)113 static FileID createInMemoryFile(StringRef FileName, MemoryBuffer *Source,
114                                  SourceManager &Sources, FileManager &Files,
115                                  vfs::InMemoryFileSystem *MemFS) {
116   MemFS->addFileNoOwn(FileName, 0, Source);
117   return Sources.createFileID(Files.getFile(FileName), SourceLocation(),
118                               SrcMgr::C_User);
119 }
120 
121 // Parses <start line>:<end line> input to a pair of line numbers.
122 // Returns true on error.
parseLineRange(StringRef Input,unsigned & FromLine,unsigned & ToLine)123 static bool parseLineRange(StringRef Input, unsigned &FromLine,
124                            unsigned &ToLine) {
125   std::pair<StringRef, StringRef> LineRange = Input.split(':');
126   return LineRange.first.getAsInteger(0, FromLine) ||
127          LineRange.second.getAsInteger(0, ToLine);
128 }
129 
fillRanges(MemoryBuffer * Code,std::vector<tooling::Range> & Ranges)130 static bool fillRanges(MemoryBuffer *Code,
131                        std::vector<tooling::Range> &Ranges) {
132   IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem(
133       new vfs::InMemoryFileSystem);
134   FileManager Files(FileSystemOptions(), InMemoryFileSystem);
135   DiagnosticsEngine Diagnostics(
136       IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
137       new DiagnosticOptions);
138   SourceManager Sources(Diagnostics, Files);
139   FileID ID = createInMemoryFile("<irrelevant>", Code, Sources, Files,
140                                  InMemoryFileSystem.get());
141   if (!LineRanges.empty()) {
142     if (!Offsets.empty() || !Lengths.empty()) {
143       errs() << "error: cannot use -lines with -offset/-length\n";
144       return true;
145     }
146 
147     for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) {
148       unsigned FromLine, ToLine;
149       if (parseLineRange(LineRanges[i], FromLine, ToLine)) {
150         errs() << "error: invalid <start line>:<end line> pair\n";
151         return true;
152       }
153       if (FromLine > ToLine) {
154         errs() << "error: start line should be less than end line\n";
155         return true;
156       }
157       SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1);
158       SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX);
159       if (Start.isInvalid() || End.isInvalid())
160         return true;
161       unsigned Offset = Sources.getFileOffset(Start);
162       unsigned Length = Sources.getFileOffset(End) - Offset;
163       Ranges.push_back(tooling::Range(Offset, Length));
164     }
165     return false;
166   }
167 
168   if (Offsets.empty())
169     Offsets.push_back(0);
170   if (Offsets.size() != Lengths.size() &&
171       !(Offsets.size() == 1 && Lengths.empty())) {
172     errs() << "error: number of -offset and -length arguments must match.\n";
173     return true;
174   }
175   for (unsigned i = 0, e = Offsets.size(); i != e; ++i) {
176     if (Offsets[i] >= Code->getBufferSize()) {
177       errs() << "error: offset " << Offsets[i] << " is outside the file\n";
178       return true;
179     }
180     SourceLocation Start =
181         Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]);
182     SourceLocation End;
183     if (i < Lengths.size()) {
184       if (Offsets[i] + Lengths[i] > Code->getBufferSize()) {
185         errs() << "error: invalid length " << Lengths[i]
186                << ", offset + length (" << Offsets[i] + Lengths[i]
187                << ") is outside the file.\n";
188         return true;
189       }
190       End = Start.getLocWithOffset(Lengths[i]);
191     } else {
192       End = Sources.getLocForEndOfFile(ID);
193     }
194     unsigned Offset = Sources.getFileOffset(Start);
195     unsigned Length = Sources.getFileOffset(End) - Offset;
196     Ranges.push_back(tooling::Range(Offset, Length));
197   }
198   return false;
199 }
200 
outputReplacementXML(StringRef Text)201 static void outputReplacementXML(StringRef Text) {
202   // FIXME: When we sort includes, we need to make sure the stream is correct
203   // utf-8.
204   size_t From = 0;
205   size_t Index;
206   while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) {
207     outs() << Text.substr(From, Index - From);
208     switch (Text[Index]) {
209     case '\n':
210       outs() << "&#10;";
211       break;
212     case '\r':
213       outs() << "&#13;";
214       break;
215     case '<':
216       outs() << "&lt;";
217       break;
218     case '&':
219       outs() << "&amp;";
220       break;
221     default:
222       llvm_unreachable("Unexpected character encountered!");
223     }
224     From = Index + 1;
225   }
226   outs() << Text.substr(From);
227 }
228 
outputReplacementsXML(const Replacements & Replaces)229 static void outputReplacementsXML(const Replacements &Replaces) {
230   for (const auto &R : Replaces) {
231     outs() << "<replacement "
232            << "offset='" << R.getOffset() << "' "
233            << "length='" << R.getLength() << "'>";
234     outputReplacementXML(R.getReplacementText());
235     outs() << "</replacement>\n";
236   }
237 }
238 
239 // Returns true on error.
format(StringRef FileName)240 static bool format(StringRef FileName) {
241   ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
242       MemoryBuffer::getFileOrSTDIN(FileName);
243   if (std::error_code EC = CodeOrErr.getError()) {
244     errs() << EC.message() << "\n";
245     return true;
246   }
247   std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
248   if (Code->getBufferSize() == 0)
249     return false; // Empty files are formatted correctly.
250   std::vector<tooling::Range> Ranges;
251   if (fillRanges(Code.get(), Ranges))
252     return true;
253   StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName;
254   FormatStyle FormatStyle = getStyle(Style, AssumedFileName, FallbackStyle);
255   if (SortIncludes.getNumOccurrences() != 0)
256     FormatStyle.SortIncludes = SortIncludes;
257   unsigned CursorPosition = Cursor;
258   Replacements Replaces = sortIncludes(FormatStyle, Code->getBuffer(), Ranges,
259                                        AssumedFileName, &CursorPosition);
260   auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
261   if (!ChangedCode) {
262     llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
263     return true;
264   }
265   for (const auto &R : Replaces)
266     Ranges.push_back({R.getOffset(), R.getLength()});
267 
268   bool IncompleteFormat = false;
269   Replacements FormatChanges = reformat(FormatStyle, *ChangedCode, Ranges,
270                                         AssumedFileName, &IncompleteFormat);
271   Replaces = tooling::mergeReplacements(Replaces, FormatChanges);
272   if (OutputXML) {
273     outs() << "<?xml version='1.0'?>\n<replacements "
274               "xml:space='preserve' incomplete_format='"
275            << (IncompleteFormat ? "true" : "false") << "'>\n";
276     if (Cursor.getNumOccurrences() != 0)
277       outs() << "<cursor>"
278              << tooling::shiftedCodePosition(FormatChanges, CursorPosition)
279              << "</cursor>\n";
280 
281     outputReplacementsXML(Replaces);
282     outs() << "</replacements>\n";
283   } else {
284     IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem(
285         new vfs::InMemoryFileSystem);
286     FileManager Files(FileSystemOptions(), InMemoryFileSystem);
287     DiagnosticsEngine Diagnostics(
288         IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
289         new DiagnosticOptions);
290     SourceManager Sources(Diagnostics, Files);
291     FileID ID = createInMemoryFile(AssumedFileName, Code.get(), Sources, Files,
292                                    InMemoryFileSystem.get());
293     Rewriter Rewrite(Sources, LangOptions());
294     tooling::applyAllReplacements(Replaces, Rewrite);
295     if (Inplace) {
296       if (FileName == "-")
297         errs() << "error: cannot use -i when reading from stdin.\n";
298       else if (Rewrite.overwriteChangedFiles())
299         return true;
300     } else {
301       if (Cursor.getNumOccurrences() != 0)
302         outs() << "{ \"Cursor\": "
303                << tooling::shiftedCodePosition(FormatChanges, CursorPosition)
304                << ", \"IncompleteFormat\": "
305                << (IncompleteFormat ? "true" : "false") << " }\n";
306       Rewrite.getEditBuffer(ID).write(outs());
307     }
308   }
309   return false;
310 }
311 
312 }  // namespace format
313 }  // namespace clang
314 
PrintVersion()315 static void PrintVersion() {
316   raw_ostream &OS = outs();
317   OS << clang::getClangToolFullVersion("clang-format") << '\n';
318 }
319 
main(int argc,const char ** argv)320 int main(int argc, const char **argv) {
321   llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
322 
323   cl::HideUnrelatedOptions(ClangFormatCategory);
324 
325   cl::SetVersionPrinter(PrintVersion);
326   cl::ParseCommandLineOptions(
327       argc, argv,
328       "A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf code.\n\n"
329       "If no arguments are specified, it formats the code from standard input\n"
330       "and writes the result to the standard output.\n"
331       "If <file>s are given, it reformats the files. If -i is specified\n"
332       "together with <file>s, the files are edited in-place. Otherwise, the\n"
333       "result is written to the standard output.\n");
334 
335   if (Help)
336     cl::PrintHelpMessage();
337 
338   if (DumpConfig) {
339     std::string Config =
340         clang::format::configurationAsText(clang::format::getStyle(
341             Style, FileNames.empty() ? AssumeFileName : FileNames[0],
342             FallbackStyle));
343     outs() << Config << "\n";
344     return 0;
345   }
346 
347   bool Error = false;
348   switch (FileNames.size()) {
349   case 0:
350     Error = clang::format::format("-");
351     break;
352   case 1:
353     Error = clang::format::format(FileNames[0]);
354     break;
355   default:
356     if (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty()) {
357       errs() << "error: -offset, -length and -lines can only be used for "
358                 "single file.\n";
359       return 1;
360     }
361     for (unsigned i = 0; i < FileNames.size(); ++i)
362       Error |= clang::format::format(FileNames[i]);
363     break;
364   }
365   return Error ? 1 : 0;
366 }
367 
368