Newer
Older
//===-- clang-format/ClangFormat.cpp - Clang format tool ------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file implements a clang-format tool that automatically formats
/// (fragments of) C++ code.
///
//===----------------------------------------------------------------------===//
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "clang/Lex/Lexer.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Signals.h"
#include "llvm/ADT/StringMap.h"
using namespace llvm;
static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
// Mark all our options with this category, everything else (except for -version
// and -help) will be hidden.
cl::OptionCategory ClangFormatCategory("Clang-format options");
static cl::list<unsigned>
Offsets("offset",
cl::desc("Format a range starting at this byte offset.\n"
"Multiple ranges can be formatted by specifying\n"
"several -offset and -length pairs.\n"
"Can only be used with one input file."),
cl::cat(ClangFormatCategory));
static cl::list<unsigned>
Lengths("length",
cl::desc("Format a range of this length (in bytes).\n"
"Multiple ranges can be formatted by specifying\n"
"several -offset and -length pairs.\n"
"When only a single -offset is specified without\n"
"-length, clang-format will format up to the end\n"
"of the file.\n"
"Can only be used with one input file."),
cl::cat(ClangFormatCategory));
Alexander Kornienko
committed
static cl::list<std::string>
LineRanges("lines", cl::desc("<start line>:<end line> - format a range of\n"
"lines (both 1-based).\n"
"Multiple ranges can be formatted by specifying\n"
"several -lines arguments.\n"
"Can't be used with -offset and -length.\n"
"Can only be used with one input file."),
cl::cat(ClangFormatCategory));
static cl::opt<std::string>
Style("style",
cl::desc(clang::format::StyleOptionHelpDescription),
cl::init("file"), cl::cat(ClangFormatCategory));
static cl::opt<std::string>
AssumeFilename("assume-filename",
cl::desc("When reading from stdin, clang-format assumes this\n"
"filename to look for a style config file (with\n"
"-style=file)."),
cl::cat(ClangFormatCategory));
cl::desc("Inplace edit <file>s, if specified."),
cl::cat(ClangFormatCategory));
static cl::opt<bool> OutputXML("output-replacements-xml",
cl::desc("Output replacements as XML."),
cl::cat(ClangFormatCategory));
static cl::opt<bool>
DumpConfig("dump-config",
cl::desc("Dump configuration options to stdout and exit.\n"
"Can be used with -style option."),
cl::cat(ClangFormatCategory));
static cl::opt<unsigned>
Cursor("cursor",
Alexander Kornienko
committed
cl::desc("The position of the cursor when invoking\n"
"clang-format from an editor integration"),
cl::init(0), cl::cat(ClangFormatCategory));
static cl::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"),
cl::cat(ClangFormatCategory));
namespace clang {
namespace format {
static FileID createInMemoryFile(StringRef FileName, const MemoryBuffer *Source,
SourceManager &Sources, FileManager &Files) {
const FileEntry *Entry = Files.getVirtualFile(FileName == "-" ? "<stdin>" :
FileName,
Source->getBufferSize(), 0);
Sources.overrideFileContents(Entry, Source, true);
return Sources.createFileID(Entry, SourceLocation(), SrcMgr::C_User);
}
Alexander Kornienko
committed
// Parses <start line>:<end line> input to a pair of line numbers.
// Returns true on error.
Alexander Kornienko
committed
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
static bool parseLineRange(StringRef Input, unsigned &FromLine,
unsigned &ToLine) {
std::pair<StringRef, StringRef> LineRange = Input.split(':');
return LineRange.first.getAsInteger(0, FromLine) ||
LineRange.second.getAsInteger(0, ToLine);
}
static bool fillRanges(SourceManager &Sources, FileID ID,
const MemoryBuffer *Code,
std::vector<CharSourceRange> &Ranges) {
if (!LineRanges.empty()) {
if (!Offsets.empty() || !Lengths.empty()) {
llvm::errs() << "error: cannot use -lines with -offset/-length\n";
return true;
}
for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) {
unsigned FromLine, ToLine;
if (parseLineRange(LineRanges[i], FromLine, ToLine)) {
llvm::errs() << "error: invalid <start line>:<end line> pair\n";
return true;
}
if (FromLine > ToLine) {
llvm::errs() << "error: start line should be less than end line\n";
return true;
}
SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1);
SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX);
if (Start.isInvalid() || End.isInvalid())
return true;
Ranges.push_back(CharSourceRange::getCharRange(Start, End));
}
return false;
Alexander Kornienko
committed
if (Offsets.empty())
Offsets.push_back(0);
if (Offsets.size() != Lengths.size() &&
!(Offsets.size() == 1 && Lengths.empty())) {
llvm::errs()
<< "error: number of -offset and -length arguments must match.\n";
return true;
for (unsigned i = 0, e = Offsets.size(); i != e; ++i) {
if (Offsets[i] >= Code->getBufferSize()) {
llvm::errs() << "error: offset " << Offsets[i]
<< " is outside the file\n";
return true;
}
SourceLocation Start =
Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]);
SourceLocation End;
if (i < Lengths.size()) {
if (Offsets[i] + Lengths[i] > Code->getBufferSize()) {
llvm::errs() << "error: invalid length " << Lengths[i]
<< ", offset + length (" << Offsets[i] + Lengths[i]
<< ") is outside the file.\n";
return true;
}
End = Start.getLocWithOffset(Lengths[i]);
} else {
End = Sources.getLocForEndOfFile(ID);
}
Ranges.push_back(CharSourceRange::getCharRange(Start, End));
}
Alexander Kornienko
committed
return false;
}
// Returns true on error.
static bool format(StringRef FileName) {
Alexander Kornienko
committed
FileManager Files((FileSystemOptions()));
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
new DiagnosticOptions);
SourceManager Sources(Diagnostics, Files);
OwningPtr<MemoryBuffer> Code;
if (error_code ec = MemoryBuffer::getFileOrSTDIN(FileName, Code)) {
llvm::errs() << ec.message() << "\n";
return true;
}
if (Code->getBufferSize() == 0)
return false; // Empty files are formatted correctly.
Alexander Kornienko
committed
FileID ID = createInMemoryFile(FileName, Code.get(), Sources, Files);
std::vector<CharSourceRange> Ranges;
if (fillRanges(Sources, ID, Code.get(), Ranges))
return true;
FormatStyle FormatStyle =
getStyle(Style, (FileName == "-") ? AssumeFilename : FileName);
Lexer Lex(ID, Sources.getBuffer(ID), Sources,
getFormattingLangOpts(FormatStyle.Standard));
tooling::Replacements Replaces = reformat(FormatStyle, Lex, Sources, Ranges);
llvm::outs()
<< "<?xml version='1.0'?>\n<replacements xml:space='preserve'>\n";
for (tooling::Replacements::const_iterator I = Replaces.begin(),
E = Replaces.end();
I != E; ++I) {
llvm::outs() << "<replacement "
<< "offset='" << I->getOffset() << "' "
<< "length='" << I->getLength() << "'>"
<< I->getReplacementText() << "</replacement>\n";
}
llvm::outs() << "</replacements>\n";
} else {
Rewriter Rewrite(Sources, LangOptions());
tooling::applyAllReplacements(Replaces, Rewrite);
if (Inplace) {
if (Cursor.getNumOccurrences() != 0)
outs() << "{ \"Cursor\": " << tooling::shiftedCodePosition(
Replaces, Cursor) << " }\n";
Rewrite.getEditBuffer(ID).write(outs());
}
}
}
} // namespace format
} // namespace clang
int main(int argc, const char **argv) {
llvm::sys::PrintStackTraceOnErrorSignal();
// Hide unrelated options.
StringMap<cl::Option*> Options;
cl::getRegisteredOptions(Options);
for (StringMap<cl::Option *>::iterator I = Options.begin(), E = Options.end();
I != E; ++I) {
if (I->second->Category != &ClangFormatCategory && I->first() != "help" &&
I->first() != "version")
I->second->setHiddenFlag(cl::ReallyHidden);
}
cl::ParseCommandLineOptions(
argc, argv,
"A tool to format C/C++/Obj-C code.\n\n"
"If no arguments are specified, it formats the code from standard input\n"
"and writes the result to the standard output.\n"
Alexander Kornienko
committed
"If <file>s are given, it reformats the files. If -i is specified\n"
"together with <file>s, the files are edited in-place. Otherwise, the\n"
"result is written to the standard output.\n");
std::string Config =
clang::format::configurationAsText(clang::format::getStyle(
Style, FileNames.empty() ? AssumeFilename : FileNames[0]));
llvm::outs() << Config << "\n";
return 0;
}
bool Error = false;
switch (FileNames.size()) {
case 0:
Error = clang::format::format("-");
break;
case 1:
Error = clang::format::format(FileNames[0]);
break;
default:
Alexander Kornienko
committed
if (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty()) {
llvm::errs() << "error: -offset, -length and -lines can only be used for "
"single file.\n";
return 1;
}
for (unsigned i = 0; i < FileNames.size(); ++i)
Error |= clang::format::format(FileNames[i]);
break;
}
return Error ? 1 : 0;