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/Format/Format.h"
21 #include "clang/Lex/Lexer.h"
22 #include "clang/Rewrite/Core/Rewriter.h"
23 #include "llvm/Support/Debug.h"
24 #include "llvm/Support/FileSystem.h"
25 #include "llvm/Support/Signals.h"
26 #include "llvm/ADT/StringMap.h"
27 
28 using namespace llvm;
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 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", cl::desc("<start line>:<end line> - format a range of\n"
55                              "lines (both 1-based).\n"
56                              "Multiple ranges can be formatted by specifying\n"
57                              "several -lines arguments.\n"
58                              "Can't be used with -offset and -length.\n"
59                              "Can only be used with one input file."),
60            cl::cat(ClangFormatCategory));
61 static cl::opt<std::string>
62     Style("style",
63           cl::desc(clang::format::StyleOptionHelpDescription),
64           cl::init("file"), cl::cat(ClangFormatCategory));
65 
66 static cl::opt<std::string>
67 AssumeFilename("assume-filename",
68                cl::desc("When reading from stdin, clang-format assumes this\n"
69                         "filename to look for a style config file (with\n"
70                         "-style=file)."),
71                cl::cat(ClangFormatCategory));
72 
73 static cl::opt<bool> Inplace("i",
74                              cl::desc("Inplace edit <file>s, if specified."),
75                              cl::cat(ClangFormatCategory));
76 
77 static cl::opt<bool> OutputXML("output-replacements-xml",
78                                cl::desc("Output replacements as XML."),
79                                cl::cat(ClangFormatCategory));
80 static cl::opt<bool>
81     DumpConfig("dump-config",
82                cl::desc("Dump configuration options to stdout and exit.\n"
83                         "Can be used with -style option."),
84                cl::cat(ClangFormatCategory));
85 static cl::opt<unsigned>
86     Cursor("cursor",
87            cl::desc("The position of the cursor when invoking\n"
88                     "clang-format from an editor integration"),
89            cl::init(0), cl::cat(ClangFormatCategory));
90 
91 static cl::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"),
92                                        cl::cat(ClangFormatCategory));
93 
94 namespace clang {
95 namespace format {
96 
97 static FileID createInMemoryFile(StringRef FileName, const MemoryBuffer *Source,
98                                  SourceManager &Sources, FileManager &Files) {
99   const FileEntry *Entry = Files.getVirtualFile(FileName == "-" ? "<stdin>" :
100                                                     FileName,
101                                                 Source->getBufferSize(), 0);
102   Sources.overrideFileContents(Entry, Source, true);
103   return Sources.createFileID(Entry, SourceLocation(), SrcMgr::C_User);
104 }
105 
106 // Parses <start line>:<end line> input to a pair of line numbers.
107 // Returns true on error.
108 static bool parseLineRange(StringRef Input, unsigned &FromLine,
109                            unsigned &ToLine) {
110   std::pair<StringRef, StringRef> LineRange = Input.split(':');
111   return LineRange.first.getAsInteger(0, FromLine) ||
112          LineRange.second.getAsInteger(0, ToLine);
113 }
114 
115 static bool fillRanges(SourceManager &Sources, FileID ID,
116                        const MemoryBuffer *Code,
117                        std::vector<CharSourceRange> &Ranges) {
118   if (!LineRanges.empty()) {
119     if (!Offsets.empty() || !Lengths.empty()) {
120       llvm::errs() << "error: cannot use -lines with -offset/-length\n";
121       return true;
122     }
123 
124     for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) {
125       unsigned FromLine, ToLine;
126       if (parseLineRange(LineRanges[i], FromLine, ToLine)) {
127         llvm::errs() << "error: invalid <start line>:<end line> pair\n";
128         return true;
129       }
130       if (FromLine > ToLine) {
131         llvm::errs() << "error: start line should be less than end line\n";
132         return true;
133       }
134       SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1);
135       SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX);
136       if (Start.isInvalid() || End.isInvalid())
137         return true;
138       Ranges.push_back(CharSourceRange::getCharRange(Start, End));
139     }
140     return false;
141   }
142 
143   if (Offsets.empty())
144     Offsets.push_back(0);
145   if (Offsets.size() != Lengths.size() &&
146       !(Offsets.size() == 1 && Lengths.empty())) {
147     llvm::errs()
148         << "error: number of -offset and -length arguments must match.\n";
149     return true;
150   }
151   for (unsigned i = 0, e = Offsets.size(); i != e; ++i) {
152     if (Offsets[i] >= Code->getBufferSize()) {
153       llvm::errs() << "error: offset " << Offsets[i]
154                    << " is outside the file\n";
155       return true;
156     }
157     SourceLocation Start =
158         Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]);
159     SourceLocation End;
160     if (i < Lengths.size()) {
161       if (Offsets[i] + Lengths[i] > Code->getBufferSize()) {
162         llvm::errs() << "error: invalid length " << Lengths[i]
163                      << ", offset + length (" << Offsets[i] + Lengths[i]
164                      << ") is outside the file.\n";
165         return true;
166       }
167       End = Start.getLocWithOffset(Lengths[i]);
168     } else {
169       End = Sources.getLocForEndOfFile(ID);
170     }
171     Ranges.push_back(CharSourceRange::getCharRange(Start, End));
172   }
173   return false;
174 }
175 
176 // Returns true on error.
177 static bool format(StringRef FileName) {
178   FileManager Files((FileSystemOptions()));
179   DiagnosticsEngine Diagnostics(
180       IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
181       new DiagnosticOptions);
182   SourceManager Sources(Diagnostics, Files);
183   OwningPtr<MemoryBuffer> Code;
184   if (error_code ec = MemoryBuffer::getFileOrSTDIN(FileName, Code)) {
185     llvm::errs() << ec.message() << "\n";
186     return true;
187   }
188   if (Code->getBufferSize() == 0)
189     return false; // Empty files are formatted correctly.
190   FileID ID = createInMemoryFile(FileName, Code.get(), Sources, Files);
191   std::vector<CharSourceRange> Ranges;
192   if (fillRanges(Sources, ID, Code.get(), Ranges))
193     return true;
194 
195   FormatStyle FormatStyle =
196       getStyle(Style, (FileName == "-") ? AssumeFilename : FileName);
197   Lexer Lex(ID, Sources.getBuffer(ID), Sources,
198             getFormattingLangOpts(FormatStyle.Standard));
199   tooling::Replacements Replaces = reformat(FormatStyle, Lex, Sources, Ranges);
200   if (OutputXML) {
201     llvm::outs()
202         << "<?xml version='1.0'?>\n<replacements xml:space='preserve'>\n";
203     for (tooling::Replacements::const_iterator I = Replaces.begin(),
204                                                E = Replaces.end();
205          I != E; ++I) {
206       llvm::outs() << "<replacement "
207                    << "offset='" << I->getOffset() << "' "
208                    << "length='" << I->getLength() << "'>"
209                    << I->getReplacementText() << "</replacement>\n";
210     }
211     llvm::outs() << "</replacements>\n";
212   } else {
213     Rewriter Rewrite(Sources, LangOptions());
214     tooling::applyAllReplacements(Replaces, Rewrite);
215     if (Inplace) {
216       if (Rewrite.overwriteChangedFiles())
217         return true;
218     } else {
219       if (Cursor.getNumOccurrences() != 0)
220         outs() << "{ \"Cursor\": " << tooling::shiftedCodePosition(
221                                           Replaces, Cursor) << " }\n";
222       Rewrite.getEditBuffer(ID).write(outs());
223     }
224   }
225   return false;
226 }
227 
228 }  // namespace format
229 }  // namespace clang
230 
231 int main(int argc, const char **argv) {
232   llvm::sys::PrintStackTraceOnErrorSignal();
233 
234   // Hide unrelated options.
235   StringMap<cl::Option*> Options;
236   cl::getRegisteredOptions(Options);
237   for (StringMap<cl::Option *>::iterator I = Options.begin(), E = Options.end();
238        I != E; ++I) {
239     if (I->second->Category != &ClangFormatCategory && I->first() != "help" &&
240         I->first() != "version")
241       I->second->setHiddenFlag(cl::ReallyHidden);
242   }
243 
244   cl::ParseCommandLineOptions(
245       argc, argv,
246       "A tool to format C/C++/Obj-C code.\n\n"
247       "If no arguments are specified, it formats the code from standard input\n"
248       "and writes the result to the standard output.\n"
249       "If <file>s are given, it reformats the files. If -i is specified\n"
250       "together with <file>s, the files are edited in-place. Otherwise, the\n"
251       "result is written to the standard output.\n");
252 
253   if (Help)
254     cl::PrintHelpMessage();
255 
256   if (DumpConfig) {
257     std::string Config =
258         clang::format::configurationAsText(clang::format::getStyle(
259             Style, FileNames.empty() ? AssumeFilename : FileNames[0]));
260     llvm::outs() << Config << "\n";
261     return 0;
262   }
263 
264   bool Error = false;
265   switch (FileNames.size()) {
266   case 0:
267     Error = clang::format::format("-");
268     break;
269   case 1:
270     Error = clang::format::format(FileNames[0]);
271     break;
272   default:
273     if (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty()) {
274       llvm::errs() << "error: -offset, -length and -lines can only be used for "
275                       "single file.\n";
276       return 1;
277     }
278     for (unsigned i = 0; i < FileNames.size(); ++i)
279       Error |= clang::format::format(FileNames[i]);
280     break;
281   }
282   return Error ? 1 : 0;
283 }
284