xref: /llvm-project/clang/tools/clang-format/ClangFormat.cpp (revision fa94cbb70f2363ec487d033c99c681953d5e095c)
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 
27 using namespace llvm;
28 
29 static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
30 
31 static cl::list<unsigned>
32 Offsets("offset", cl::desc("Format a range starting at this byte offset. Can "
33                            "only be used with one input file."));
34 static cl::list<unsigned>
35 Lengths("length", cl::desc("Format a range of this length (in bytes). "
36                            "When it's not specified, end of file is used. "
37                            "Can only be used with one input file."));
38 static cl::opt<std::string> Style(
39     "style",
40     cl::desc(
41         "Coding style, currently supports: LLVM, Google, Chromium, Mozilla. "
42         "Use '-style file' to load style configuration from .clang-format file "
43         "located in one of the parent directories of the source file (or "
44         "current directory for stdin)."),
45     cl::init("LLVM"));
46 static cl::opt<bool> Inplace("i",
47                              cl::desc("Inplace edit <file>s, if specified."));
48 
49 static cl::opt<bool> OutputXML(
50     "output-replacements-xml", cl::desc("Output replacements as XML."));
51 static cl::opt<bool>
52     DumpConfig("dump-config",
53                cl::desc("Dump configuration options to stdout and exit. Can be used with -style option."));
54 
55 static cl::list<std::string> FileNames(cl::Positional,
56                                        cl::desc("[<file> ...]"));
57 
58 namespace clang {
59 namespace format {
60 
61 static FileID createInMemoryFile(StringRef FileName, const MemoryBuffer *Source,
62                                  SourceManager &Sources, FileManager &Files) {
63   const FileEntry *Entry = Files.getVirtualFile(FileName == "-" ? "<stdin>" :
64                                                     FileName,
65                                                 Source->getBufferSize(), 0);
66   Sources.overrideFileContents(Entry, Source, true);
67   return Sources.createFileID(Entry, SourceLocation(), SrcMgr::C_User);
68 }
69 
70 FormatStyle getStyle(StringRef StyleName, StringRef FileName) {
71   if (!StyleName.equals_lower("file"))
72     return getPredefinedStyle(StyleName);
73 
74   SmallString<128> Path(FileName);
75   llvm::sys::fs::make_absolute(Path);
76   for (StringRef Directory = llvm::sys::path::parent_path(Path);
77        !Directory.empty();
78        Directory = llvm::sys::path::parent_path(Directory)) {
79     SmallString<128> ConfigFile(Directory);
80     llvm::sys::path::append(ConfigFile, ".clang-format");
81     DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
82     bool IsFile = false;
83     // Ignore errors from is_regular_file: we only need to know if we can read
84     // the file or not.
85     llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile);
86     if (IsFile) {
87       OwningPtr<MemoryBuffer> Text;
88       if (error_code ec = MemoryBuffer::getFile(ConfigFile, Text)) {
89         llvm::errs() << ec.message() << "\n";
90         continue;
91       }
92       FormatStyle Style;
93       if (error_code ec = parseConfiguration(Text->getBuffer(), &Style)) {
94         llvm::errs() << "Error reading " << ConfigFile << ": " << ec.message()
95                      << "\n";
96         continue;
97       }
98       DEBUG(llvm::dbgs() << "Using configuration file " << ConfigFile << "\n");
99       return Style;
100     }
101   }
102   llvm::errs() << "Can't find usable .clang-format, using LLVM style\n";
103   return getLLVMStyle();
104 }
105 
106 // Returns true on error.
107 static bool format(std::string FileName) {
108   FileManager Files((FileSystemOptions()));
109   DiagnosticsEngine Diagnostics(
110       IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
111       new DiagnosticOptions);
112   SourceManager Sources(Diagnostics, Files);
113   OwningPtr<MemoryBuffer> Code;
114   if (error_code ec = MemoryBuffer::getFileOrSTDIN(FileName, Code)) {
115     llvm::errs() << ec.message() << "\n";
116     return true;
117   }
118   FileID ID = createInMemoryFile(FileName, Code.get(), Sources, Files);
119   Lexer Lex(ID, Sources.getBuffer(ID), Sources, getFormattingLangOpts());
120   if (Offsets.empty())
121     Offsets.push_back(0);
122   if (Offsets.size() != Lengths.size() &&
123       !(Offsets.size() == 1 && Lengths.empty())) {
124     llvm::errs()
125         << "error: number of -offset and -length arguments must match.\n";
126     return true;
127   }
128   std::vector<CharSourceRange> Ranges;
129   for (unsigned i = 0, e = Offsets.size(); i != e; ++i) {
130     if (Offsets[i] >= Code->getBufferSize()) {
131       llvm::errs() << "error: offset " << Offsets[i]
132                    << " is outside the file\n";
133       return true;
134     }
135     SourceLocation Start =
136         Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]);
137     SourceLocation End;
138     if (i < Lengths.size()) {
139       if (Offsets[i] + Lengths[i] > Code->getBufferSize()) {
140         llvm::errs() << "error: invalid length " << Lengths[i]
141                      << ", offset + length (" << Offsets[i] + Lengths[i]
142                      << ") is outside the file.\n";
143         return true;
144       }
145       End = Start.getLocWithOffset(Lengths[i]);
146     } else {
147       End = Sources.getLocForEndOfFile(ID);
148     }
149     Ranges.push_back(CharSourceRange::getCharRange(Start, End));
150   }
151   tooling::Replacements Replaces =
152       reformat(getStyle(Style, FileName), Lex, Sources, Ranges);
153   if (OutputXML) {
154     llvm::outs()
155         << "<?xml version='1.0'?>\n<replacements xml:space='preserve'>\n";
156     for (tooling::Replacements::const_iterator I = Replaces.begin(),
157                                                E = Replaces.end();
158          I != E; ++I) {
159       llvm::outs() << "<replacement "
160                    << "offset='" << I->getOffset() << "' "
161                    << "length='" << I->getLength() << "'>"
162                    << I->getReplacementText() << "</replacement>\n";
163     }
164     llvm::outs() << "</replacements>\n";
165   } else {
166     Rewriter Rewrite(Sources, LangOptions());
167     tooling::applyAllReplacements(Replaces, Rewrite);
168     if (Inplace) {
169       if (Replaces.size() == 0)
170         return false; // Nothing changed, don't touch the file.
171 
172       std::string ErrorInfo;
173       llvm::raw_fd_ostream FileStream(FileName.c_str(), ErrorInfo,
174                                       llvm::raw_fd_ostream::F_Binary);
175       if (!ErrorInfo.empty()) {
176         llvm::errs() << "Error while writing file: " << ErrorInfo << "\n";
177         return true;
178       }
179       Rewrite.getEditBuffer(ID).write(FileStream);
180       FileStream.flush();
181     } else {
182       Rewrite.getEditBuffer(ID).write(outs());
183     }
184   }
185   return false;
186 }
187 
188 }  // namespace format
189 }  // namespace clang
190 
191 int main(int argc, const char **argv) {
192   llvm::sys::PrintStackTraceOnErrorSignal();
193   cl::ParseCommandLineOptions(
194       argc, argv,
195       "A tool to format C/C++/Obj-C code.\n\n"
196       "If no arguments are specified, it formats the code from standard input\n"
197       "and writes the result to the standard output.\n"
198       "If <file>s are given, it reformats the files. If -i is specified \n"
199       "together with <file>s, the files are edited in-place. Otherwise, the \n"
200       "result is written to the standard output.\n");
201 
202   if (Help)
203     cl::PrintHelpMessage();
204 
205   if (DumpConfig) {
206     std::string Config = clang::format::configurationAsText(
207         clang::format::getStyle(Style, FileNames.empty() ? "-" : FileNames[0]));
208     llvm::outs() << Config << "\n";
209     return 0;
210   }
211 
212   bool Error = false;
213   switch (FileNames.size()) {
214   case 0:
215     Error = clang::format::format("-");
216     break;
217   case 1:
218     Error = clang::format::format(FileNames[0]);
219     break;
220   default:
221     if (!Offsets.empty() || !Lengths.empty()) {
222       llvm::errs() << "error: \"-offset\" and \"-length\" can only be used for "
223                       "single file.\n";
224       return 1;
225     }
226     for (unsigned i = 0; i < FileNames.size(); ++i)
227       Error |= clang::format::format(FileNames[i]);
228     break;
229   }
230   return Error ? 1 : 0;
231 }
232