Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -2089,6 +2089,31 @@ unsigned Line = 0; }; +enum class ExtraFormattingOptions { + None = 0x0, + KeepLineBreaksForNonEmptyLines = 0x1 +}; + +inline ExtraFormattingOptions operator|(ExtraFormattingOptions lhs, + ExtraFormattingOptions rhs) { + using T = std::underlying_type_t; + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +inline ExtraFormattingOptions operator&(ExtraFormattingOptions lhs, + ExtraFormattingOptions rhs) { + using T = std::underlying_type_t; + return static_cast(static_cast(lhs) & + static_cast(rhs)); +} + +inline bool alwaysKeepLineBreaks(ExtraFormattingOptions FormattingOptions) { + return (FormattingOptions & + ExtraFormattingOptions::KeepLineBreaksForNonEmptyLines) != + ExtraFormattingOptions::None; +} + /// Reformats the given \p Ranges in \p Code. /// /// Each range is extended on either end to its next bigger logic unit, i.e. @@ -2113,6 +2138,12 @@ StringRef FileName, bool *IncompleteFormat); +tooling::Replacements reformat(const FormatStyle &Style, + ExtraFormattingOptions FormattingOptions, + StringRef Code, ArrayRef Ranges, + StringRef FileName = "", + FormattingAttemptStatus *Status = nullptr); + /// Clean up any erroneous/redundant code in the given \p Ranges in \p /// Code. /// Index: lib/Format/ContinuationIndenter.cpp =================================================================== --- lib/Format/ContinuationIndenter.cpp +++ lib/Format/ContinuationIndenter.cpp @@ -1563,6 +1563,7 @@ std::pair Fixes = internal::reformat( RawStringStyle, RawText, {tooling::Range(0, RawText.size())}, FirstStartColumn, NextStartColumn, LastStartColumn, "", + ExtraFormattingOptions::None, /*Status=*/nullptr); auto NewCode = applyAllReplacements(RawText, Fixes.first); Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -1239,8 +1239,9 @@ class Formatter : public TokenAnalyzer { public: Formatter(const Environment &Env, const FormatStyle &Style, + ExtraFormattingOptions FormattingOptions, FormattingAttemptStatus *Status) - : TokenAnalyzer(Env, Style), Status(Status) {} + : TokenAnalyzer(Env, Style, FormattingOptions), Status(Status) {} std::pair analyze(TokenAnnotator &Annotator, @@ -1263,7 +1264,7 @@ unsigned Penalty = UnwrappedLineFormatter(&Indenter, &Whitespaces, Style, Tokens.getKeywords(), Env.getSourceManager(), - Status) + FormattingOptions, Status) .format(AnnotatedLines, /*DryRun=*/false, /*AdditionalIndent=*/0, /*FixBadIndentation=*/false, @@ -2248,6 +2249,7 @@ reformat(const FormatStyle &Style, StringRef Code, ArrayRef Ranges, unsigned FirstStartColumn, unsigned NextStartColumn, unsigned LastStartColumn, StringRef FileName, + ExtraFormattingOptions FormattingOptions, FormattingAttemptStatus *Status) { FormatStyle Expanded = expandPresets(Style); if (Expanded.DisableFormat) @@ -2281,7 +2283,7 @@ }); Passes.emplace_back([&](const Environment &Env) { - return Formatter(Env, Expanded, Status).process(); + return Formatter(Env, Expanded, FormattingOptions, Status).process(); }); auto Env = @@ -2318,7 +2320,21 @@ return internal::reformat(Style, Code, Ranges, /*FirstStartColumn=*/0, /*NextStartColumn=*/0, - /*LastStartColumn=*/0, FileName, Status) + /*LastStartColumn=*/0, FileName, + ExtraFormattingOptions::None, Status) + .first; +} + +tooling::Replacements reformat(const FormatStyle &Style, + ExtraFormattingOptions FormattingOptions, + StringRef Code, ArrayRef Ranges, + StringRef FileName, + FormattingAttemptStatus *Status) { + return internal::reformat(Style, Code, Ranges, + /*FirstStartColumn=*/0, + /*NextStartColumn=*/0, + /*LastStartColumn=*/0, FileName, FormattingOptions, + Status) .first; } Index: lib/Format/FormatInternal.h =================================================================== --- lib/Format/FormatInternal.h +++ lib/Format/FormatInternal.h @@ -73,6 +73,7 @@ reformat(const FormatStyle &Style, StringRef Code, ArrayRef Ranges, unsigned FirstStartColumn, unsigned NextStartColumn, unsigned LastStartColumn, StringRef FileName, + ExtraFormattingOptions FormattingOptions, FormattingAttemptStatus *Status); } // namespace internal Index: lib/Format/TokenAnalyzer.h =================================================================== --- lib/Format/TokenAnalyzer.h +++ lib/Format/TokenAnalyzer.h @@ -83,7 +83,9 @@ class TokenAnalyzer : public UnwrappedLineConsumer { public: - TokenAnalyzer(const Environment &Env, const FormatStyle &Style); + TokenAnalyzer( + const Environment &Env, const FormatStyle &Style, + ExtraFormattingOptions FormattingOptions = ExtraFormattingOptions::None); std::pair process(); @@ -104,6 +106,7 @@ AffectedRangeManager AffectedRangeMgr; SmallVector, 2> UnwrappedLines; encoding::Encoding Encoding; + ExtraFormattingOptions FormattingOptions; }; } // end namespace format Index: lib/Format/TokenAnalyzer.cpp =================================================================== --- lib/Format/TokenAnalyzer.cpp +++ lib/Format/TokenAnalyzer.cpp @@ -48,12 +48,14 @@ } } -TokenAnalyzer::TokenAnalyzer(const Environment &Env, const FormatStyle &Style) +TokenAnalyzer::TokenAnalyzer(const Environment &Env, const FormatStyle &Style, + ExtraFormattingOptions FormattingOptions) : Style(Style), Env(Env), AffectedRangeMgr(Env.getSourceManager(), Env.getCharRanges()), UnwrappedLines(1), Encoding(encoding::detectEncoding( - Env.getSourceManager().getBufferData(Env.getFileID()))) { + Env.getSourceManager().getBufferData(Env.getFileID()))), + FormattingOptions(FormattingOptions) { LLVM_DEBUG( llvm::dbgs() << "File encoding: " << (Encoding == encoding::Encoding_UTF8 ? "UTF8" : "unknown") @@ -68,7 +70,8 @@ Env.getFirstStartColumn(), Style, Encoding); UnwrappedLineParser Parser(Style, Tokens.getKeywords(), - Env.getFirstStartColumn(), Tokens.lex(), *this); + Env.getFirstStartColumn(), Tokens.lex(), *this, + FormattingOptions); Parser.parse(); assert(UnwrappedLines.rbegin()->empty()); unsigned Penalty = 0; Index: lib/Format/UnwrappedLineFormatter.h =================================================================== --- lib/Format/UnwrappedLineFormatter.h +++ lib/Format/UnwrappedLineFormatter.h @@ -32,9 +32,11 @@ const FormatStyle &Style, const AdditionalKeywords &Keywords, const SourceManager &SourceMgr, + ExtraFormattingOptions FormattingOptions, FormattingAttemptStatus *Status) : Indenter(Indenter), Whitespaces(Whitespaces), Style(Style), - Keywords(Keywords), SourceMgr(SourceMgr), Status(Status) {} + Keywords(Keywords), SourceMgr(SourceMgr), + FormattingOptions(FormattingOptions), Status(Status) {} /// Format the current block and return the penalty. unsigned format(const SmallVectorImpl &Lines, @@ -67,6 +69,7 @@ const FormatStyle &Style; const AdditionalKeywords &Keywords; const SourceManager &SourceMgr; + ExtraFormattingOptions FormattingOptions; FormattingAttemptStatus *Status; }; } // end namespace format Index: lib/Format/UnwrappedLineFormatter.cpp =================================================================== --- lib/Format/UnwrappedLineFormatter.cpp +++ lib/Format/UnwrappedLineFormatter.cpp @@ -682,11 +682,12 @@ /// Base class for classes that format one \c AnnotatedLine. class LineFormatter { public: - LineFormatter(ContinuationIndenter *Indenter, WhitespaceManager *Whitespaces, - const FormatStyle &Style, - UnwrappedLineFormatter *BlockFormatter) + LineFormatter( + ContinuationIndenter *Indenter, WhitespaceManager *Whitespaces, + const FormatStyle &Style, UnwrappedLineFormatter *BlockFormatter, + ExtraFormattingOptions FormattingOptions = ExtraFormattingOptions::None) : Indenter(Indenter), Whitespaces(Whitespaces), Style(Style), - BlockFormatter(BlockFormatter) {} + BlockFormatter(BlockFormatter), FormattingOptions(FormattingOptions) {} virtual ~LineFormatter() {} /// Formats an \c AnnotatedLine and returns the penalty. @@ -726,7 +727,8 @@ // assert so that we can simply call this function for all tokens. return true; - if (NewLine) { + if (NewLine || (Previous.Children[0]->First->MustBreakBefore && + alwaysKeepLineBreaks(FormattingOptions))) { int AdditionalIndent = State.Stack.back().Indent - Previous.Children[0]->Level * Style.IndentWidth; @@ -776,6 +778,7 @@ WhitespaceManager *Whitespaces; const FormatStyle &Style; UnwrappedLineFormatter *BlockFormatter; + ExtraFormattingOptions FormattingOptions; }; /// Formatter that keeps the existing line breaks. @@ -784,8 +787,10 @@ NoColumnLimitLineFormatter(ContinuationIndenter *Indenter, WhitespaceManager *Whitespaces, const FormatStyle &Style, - UnwrappedLineFormatter *BlockFormatter) - : LineFormatter(Indenter, Whitespaces, Style, BlockFormatter) {} + UnwrappedLineFormatter *BlockFormatter, + ExtraFormattingOptions FormattingOptions) + : LineFormatter(Indenter, Whitespaces, Style, BlockFormatter, + FormattingOptions) {} /// Formats the line, simply keeping all of the input's line breaking /// decisions. @@ -821,7 +826,8 @@ LineState State = Indenter->getInitialState(FirstIndent, FirstStartColumn, &Line, DryRun); while (State.NextToken) { - formatChildren(State, /*Newline=*/false, DryRun, Penalty); + bool Newline = false; + formatChildren(State, Newline, DryRun, Penalty); Indenter->addTokenToState( State, /*Newline=*/State.NextToken->MustBreakBefore, DryRun); } @@ -1076,8 +1082,9 @@ !Style.JavaScriptWrapImports)) || (Style.isCSharp() && TheLine.InPPDirective); // don't split #regions in C# - if (Style.ColumnLimit == 0) - NoColumnLimitLineFormatter(Indenter, Whitespaces, Style, this) + if (Style.ColumnLimit == 0 || alwaysKeepLineBreaks(FormattingOptions)) + NoColumnLimitLineFormatter(Indenter, Whitespaces, Style, this, + FormattingOptions) .formatLine(TheLine, NextStartColumn + Indent, FirstLine ? FirstStartColumn : 0, DryRun); else if (FitsIntoOneLine) Index: lib/Format/UnwrappedLineParser.h =================================================================== --- lib/Format/UnwrappedLineParser.h +++ lib/Format/UnwrappedLineParser.h @@ -77,7 +77,8 @@ UnwrappedLineParser(const FormatStyle &Style, const AdditionalKeywords &Keywords, unsigned FirstStartColumn, ArrayRef Tokens, - UnwrappedLineConsumer &Callback); + UnwrappedLineConsumer &Callback, + ExtraFormattingOptions FormattingOptions); void parse(); @@ -274,6 +275,8 @@ // does not start at the beginning of the file. unsigned FirstStartColumn; + ExtraFormattingOptions FormattingOptions; + friend class ScopedLineState; friend class CompoundStatementIndenter; }; Index: lib/Format/UnwrappedLineParser.cpp =================================================================== --- lib/Format/UnwrappedLineParser.cpp +++ lib/Format/UnwrappedLineParser.cpp @@ -222,11 +222,10 @@ } // end anonymous namespace -UnwrappedLineParser::UnwrappedLineParser(const FormatStyle &Style, - const AdditionalKeywords &Keywords, - unsigned FirstStartColumn, - ArrayRef Tokens, - UnwrappedLineConsumer &Callback) +UnwrappedLineParser::UnwrappedLineParser( + const FormatStyle &Style, const AdditionalKeywords &Keywords, + unsigned FirstStartColumn, ArrayRef Tokens, + UnwrappedLineConsumer &Callback, ExtraFormattingOptions FormattingOptions) : Line(new UnwrappedLine), MustBreakBeforeNextToken(false), CurrentLines(&Lines), Style(Style), Keywords(Keywords), CommentPragmasRegex(Style.CommentPragmas), Tokens(nullptr), @@ -234,7 +233,8 @@ IncludeGuard(Style.IndentPPDirectives == FormatStyle::PPDIS_None ? IG_Rejected : IG_Inited), - IncludeGuardToken(nullptr), FirstStartColumn(FirstStartColumn) {} + IncludeGuardToken(nullptr), FirstStartColumn(FirstStartColumn), + FormattingOptions(FormattingOptions) {} void UnwrappedLineParser::reset() { PPBranchLevel = -1; @@ -2608,6 +2608,8 @@ else readTokenWithJavaScriptASI(); FormatTok->Previous = Previous; + if (FormatTok->NewlinesBefore && alwaysKeepLineBreaks(FormattingOptions)) + FormatTok->MustBreakBefore = true; } void UnwrappedLineParser::distributeComments( Index: tools/CMakeLists.txt =================================================================== --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -4,6 +4,7 @@ add_clang_subdirectory(driver) add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) +add_clang_subdirectory(clang-format-ide) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) add_clang_subdirectory(clang-import-test) Index: tools/clang-format-ide/CMakeLists.txt =================================================================== --- tools/clang-format-ide/CMakeLists.txt +++ tools/clang-format-ide/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_tool(clang-format-ide + ClangFormatIDE.cpp + ) + +set(CLANG_FORMAT_LIB_DEPS + clangBasic + clangFormat + clangRewrite + clangToolingCore + ) + +target_link_libraries(clang-format-ide + PRIVATE + ${CLANG_FORMAT_LIB_DEPS} + ) Index: tools/clang-format-ide/ClangFormatIDE.cpp =================================================================== --- tools/clang-format-ide/ClangFormatIDE.cpp +++ tools/clang-format-ide/ClangFormatIDE.cpp @@ -0,0 +1,102 @@ +//===-- clang-format/ClangFormat.cpp - Clang format tool ------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file implements a clang-format tool that automatically formats +/// (fragments of) C++ code. +/// +//===----------------------------------------------------------------------===// + +#include "../clang-format/ClangFormat.h" + +static cl::opt + KeepLineBreaks("keep-line-breaks", + cl::desc("If set, keeps all existing line breaks"), + cl::cat(ClangFormatCategory)); + +static void PrintVersion(raw_ostream &OS) { + OS << clang::getClangToolFullVersion("clang-format-ide") << '\n'; +} + +int main(int argc, const char **argv) { + llvm::InitLLVM X(argc, argv); + + cl::HideUnrelatedOptions(ClangFormatCategory); + + cl::SetVersionPrinter(PrintVersion); + cl::ParseCommandLineOptions( + argc, argv, + "A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/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" + "If s are given, it reformats the files. If -i is specified\n" + "together with s, the files are edited in-place. Otherwise, the\n" + "result is written to the standard output.\n"); + + if (Help) { + cl::PrintHelpMessage(); + return 0; + } + + if (DumpConfig) { + StringRef FileName; + std::unique_ptr Code; + if (FileNames.empty()) { + // We can't read the code to detect the language if there's no + // file name, so leave Code empty here. + FileName = AssumeFileName; + } else { + // Read in the code in case the filename alone isn't enough to + // detect the language. + ErrorOr> CodeOrErr = + MemoryBuffer::getFileOrSTDIN(FileNames[0]); + if (std::error_code EC = CodeOrErr.getError()) { + llvm::errs() << EC.message() << "\n"; + return 1; + } + FileName = (FileNames[0] == "-") ? AssumeFileName : FileNames[0]; + Code = std::move(CodeOrErr.get()); + } + llvm::Expected FormatStyle = + clang::format::getStyle(Style, FileName, FallbackStyle, + Code ? Code->getBuffer() : ""); + if (!FormatStyle) { + llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; + return 1; + } + std::string Config = clang::format::configurationAsText(*FormatStyle); + outs() << Config << "\n"; + return 0; + } + + clang::format::ExtraFormattingOptions FormattingOptions = + clang::format::ExtraFormattingOptions::None; + if (KeepLineBreaks.getNumOccurrences() != 0) { + FormattingOptions = + FormattingOptions | + clang::format::ExtraFormattingOptions::KeepLineBreaksForNonEmptyLines; + } + + bool Error = false; + if (FileNames.empty()) { + Error = clang::format::format("-", FormattingOptions); + return Error ? 1 : 0; + } + if (FileNames.size() != 1 && + (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty())) { + errs() << "error: -offset, -length and -lines can only be used for " + "single file.\n"; + return 1; + } + for (const auto &FileName : FileNames) { + if (Verbose) + errs() << "Formatting " << FileName << "\n"; + Error |= clang::format::format(FileName, FormattingOptions); + } + return Error ? 1 : 0; +} Index: tools/clang-format/ClangFormat.h =================================================================== --- tools/clang-format/ClangFormat.h +++ tools/clang-format/ClangFormat.h @@ -0,0 +1,339 @@ +//===- ClangFormat.h - Main formatting functions for clang-format ----------==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifdef LLVM_CLANG_FORMAT_H +#error ClangFormat.h can only be included once +#else +#define LLVM_CLANG_FORMAT_H + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Version.h" +#include "clang/Format/Format.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/Process.h" + +using namespace llvm; +using clang::tooling::Replacements; + +static cl::opt 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. +static cl::OptionCategory ClangFormatCategory("Clang-format options"); + +static cl::list + 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 + 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)); +static cl::list + LineRanges("lines", + cl::desc(": - 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 + Style("style", cl::desc(clang::format::StyleOptionHelpDescription), + cl::init(clang::format::DefaultFormatStyle), + cl::cat(ClangFormatCategory)); +static cl::opt + FallbackStyle("fallback-style", + cl::desc("The name of the predefined style used as a\n" + "fallback in case clang-format is invoked with\n" + "-style=file, but can not find the .clang-format\n" + "file to use.\n" + "Use -fallback-style=none to skip formatting."), + cl::init(clang::format::DefaultFallbackStyle), + cl::cat(ClangFormatCategory)); + +static cl::opt 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) and to determine the language."), + cl::init(""), cl::cat(ClangFormatCategory)); + +static cl::opt Inplace("i", + cl::desc("Inplace edit s, if specified."), + cl::cat(ClangFormatCategory)); + +static cl::opt OutputXML("output-replacements-xml", + cl::desc("Output replacements as XML."), + cl::cat(ClangFormatCategory)); +static cl::opt + 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 + Cursor("cursor", + cl::desc("The position of the cursor when invoking\n" + "clang-format from an editor integration"), + cl::init(0), cl::cat(ClangFormatCategory)); + +static cl::opt SortIncludes( + "sort-includes", + cl::desc("If set, overrides the include sorting behavior determined by the " + "SortIncludes style flag"), + cl::cat(ClangFormatCategory)); + +static cl::opt + Verbose("verbose", cl::desc("If set, shows the list of processed files"), + cl::cat(ClangFormatCategory)); + +static cl::list FileNames(cl::Positional, cl::desc("[ ...]"), + cl::cat(ClangFormatCategory)); + +namespace clang { +namespace format { + +static FileID createInMemoryFile(StringRef FileName, MemoryBuffer *Source, + SourceManager &Sources, FileManager &Files, + llvm::vfs::InMemoryFileSystem *MemFS) { + MemFS->addFileNoOwn(FileName, 0, Source); + return Sources.createFileID(Files.getFile(FileName), SourceLocation(), + SrcMgr::C_User); +} + +// Parses : input to a pair of line numbers. +// Returns true on error. +static bool parseLineRange(StringRef Input, unsigned &FromLine, + unsigned &ToLine) { + std::pair LineRange = Input.split(':'); + return LineRange.first.getAsInteger(0, FromLine) || + LineRange.second.getAsInteger(0, ToLine); +} + +static bool fillRanges(MemoryBuffer *Code, + std::vector &Ranges) { + IntrusiveRefCntPtr InMemoryFileSystem( + new llvm::vfs::InMemoryFileSystem); + FileManager Files(FileSystemOptions(), InMemoryFileSystem); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs), + new DiagnosticOptions); + SourceManager Sources(Diagnostics, Files); + FileID ID = createInMemoryFile("", Code, Sources, Files, + InMemoryFileSystem.get()); + if (!LineRanges.empty()) { + if (!Offsets.empty() || !Lengths.empty()) { + 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)) { + errs() << "error: invalid : pair\n"; + return true; + } + if (FromLine > ToLine) { + 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; + unsigned Offset = Sources.getFileOffset(Start); + unsigned Length = Sources.getFileOffset(End) - Offset; + Ranges.push_back(tooling::Range(Offset, Length)); + } + return false; + } + + if (Offsets.empty()) + Offsets.push_back(0); + if (Offsets.size() != Lengths.size() && + !(Offsets.size() == 1 && Lengths.empty())) { + 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()) { + 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()) { + 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); + } + unsigned Offset = Sources.getFileOffset(Start); + unsigned Length = Sources.getFileOffset(End) - Offset; + Ranges.push_back(tooling::Range(Offset, Length)); + } + return false; +} + +static void outputReplacementXML(StringRef Text) { + // FIXME: When we sort includes, we need to make sure the stream is correct + // utf-8. + size_t From = 0; + size_t Index; + while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) { + outs() << Text.substr(From, Index - From); + switch (Text[Index]) { + case '\n': + outs() << " "; + break; + case '\r': + outs() << " "; + break; + case '<': + outs() << "<"; + break; + case '&': + outs() << "&"; + break; + default: + llvm_unreachable("Unexpected character encountered!"); + } + From = Index + 1; + } + outs() << Text.substr(From); +} + +static void outputReplacementsXML(const Replacements &Replaces) { + for (const auto &R : Replaces) { + outs() << ""; + outputReplacementXML(R.getReplacementText()); + outs() << "\n"; + } +} + +// Returns true on error. +static bool format( + StringRef FileName, + ExtraFormattingOptions FormattingOptions = ExtraFormattingOptions::None) { + if (!OutputXML && Inplace && FileName == "-") { + errs() << "error: cannot use -i when reading from stdin.\n"; + return false; + } + // On Windows, overwriting a file with an open file mapping doesn't work, + // so read the whole file into memory when formatting in-place. + ErrorOr> CodeOrErr = + !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName) + : MemoryBuffer::getFileOrSTDIN(FileName); + if (std::error_code EC = CodeOrErr.getError()) { + errs() << EC.message() << "\n"; + return true; + } + std::unique_ptr Code = std::move(CodeOrErr.get()); + if (Code->getBufferSize() == 0) + return false; // Empty files are formatted correctly. + std::vector Ranges; + if (fillRanges(Code.get(), Ranges)) + return true; + StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName; + + llvm::Expected FormatStyle = + getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer()); + if (!FormatStyle) { + llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; + return true; + } + + if (SortIncludes.getNumOccurrences() != 0) + FormatStyle->SortIncludes = SortIncludes; + unsigned CursorPosition = Cursor; + Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges, + AssumedFileName, &CursorPosition); + auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); + if (!ChangedCode) { + llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; + return true; + } + // Get new affected ranges after sorting `#includes`. + Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges); + FormattingAttemptStatus Status; + Replacements FormatChanges = + reformat(*FormatStyle, FormattingOptions, *ChangedCode, Ranges, + AssumedFileName, &Status); + Replaces = Replaces.merge(FormatChanges); + if (OutputXML) { + outs() << "\n\n"; + if (Cursor.getNumOccurrences() != 0) + outs() << "" + << FormatChanges.getShiftedCodePosition(CursorPosition) + << "\n"; + + outputReplacementsXML(Replaces); + outs() << "\n"; + } else { + IntrusiveRefCntPtr InMemoryFileSystem( + new llvm::vfs::InMemoryFileSystem); + FileManager Files(FileSystemOptions(), InMemoryFileSystem); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs), + new DiagnosticOptions); + SourceManager Sources(Diagnostics, Files); + FileID ID = createInMemoryFile(AssumedFileName, Code.get(), Sources, Files, + InMemoryFileSystem.get()); + Rewriter Rewrite(Sources, LangOptions()); + tooling::applyAllReplacements(Replaces, Rewrite); + if (Inplace) { + if (Rewrite.overwriteChangedFiles()) + return true; + } else { + if (Cursor.getNumOccurrences() != 0) { + outs() << "{ \"Cursor\": " + << FormatChanges.getShiftedCodePosition(CursorPosition) + << ", \"IncompleteFormat\": " + << (Status.FormatComplete ? "false" : "true"); + if (!Status.FormatComplete) + outs() << ", \"Line\": " << Status.Line; + outs() << " }\n"; + } + Rewrite.getEditBuffer(ID).write(outs()); + } + } + return false; +} + +} // namespace format +} // namespace clang + +#endif Index: tools/clang-format/ClangFormat.cpp =================================================================== --- tools/clang-format/ClangFormat.cpp +++ tools/clang-format/ClangFormat.cpp @@ -12,326 +12,7 @@ /// //===----------------------------------------------------------------------===// -#include "clang/Basic/Diagnostic.h" -#include "clang/Basic/DiagnosticOptions.h" -#include "clang/Basic/FileManager.h" -#include "clang/Basic/SourceManager.h" -#include "clang/Basic/Version.h" -#include "clang/Format/Format.h" -#include "clang/Rewrite/Core/Rewriter.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/Process.h" - -using namespace llvm; -using clang::tooling::Replacements; - -static cl::opt 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. -static cl::OptionCategory ClangFormatCategory("Clang-format options"); - -static cl::list - 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 - 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)); -static cl::list -LineRanges("lines", cl::desc(": - 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 - Style("style", cl::desc(clang::format::StyleOptionHelpDescription), - cl::init(clang::format::DefaultFormatStyle), - cl::cat(ClangFormatCategory)); -static cl::opt - FallbackStyle("fallback-style", - cl::desc("The name of the predefined style used as a\n" - "fallback in case clang-format is invoked with\n" - "-style=file, but can not find the .clang-format\n" - "file to use.\n" - "Use -fallback-style=none to skip formatting."), - cl::init(clang::format::DefaultFallbackStyle), - cl::cat(ClangFormatCategory)); - -static cl::opt -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) and to determine the language."), - cl::init(""), cl::cat(ClangFormatCategory)); - -static cl::opt Inplace("i", - cl::desc("Inplace edit s, if specified."), - cl::cat(ClangFormatCategory)); - -static cl::opt OutputXML("output-replacements-xml", - cl::desc("Output replacements as XML."), - cl::cat(ClangFormatCategory)); -static cl::opt - 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 - Cursor("cursor", - cl::desc("The position of the cursor when invoking\n" - "clang-format from an editor integration"), - cl::init(0), cl::cat(ClangFormatCategory)); - -static cl::opt SortIncludes( - "sort-includes", - cl::desc("If set, overrides the include sorting behavior determined by the " - "SortIncludes style flag"), - cl::cat(ClangFormatCategory)); - -static cl::opt - Verbose("verbose", cl::desc("If set, shows the list of processed files"), - cl::cat(ClangFormatCategory)); - -static cl::list FileNames(cl::Positional, cl::desc("[ ...]"), - cl::cat(ClangFormatCategory)); - -namespace clang { -namespace format { - -static FileID createInMemoryFile(StringRef FileName, MemoryBuffer *Source, - SourceManager &Sources, FileManager &Files, - llvm::vfs::InMemoryFileSystem *MemFS) { - MemFS->addFileNoOwn(FileName, 0, Source); - return Sources.createFileID(Files.getFile(FileName), SourceLocation(), - SrcMgr::C_User); -} - -// Parses : input to a pair of line numbers. -// Returns true on error. -static bool parseLineRange(StringRef Input, unsigned &FromLine, - unsigned &ToLine) { - std::pair LineRange = Input.split(':'); - return LineRange.first.getAsInteger(0, FromLine) || - LineRange.second.getAsInteger(0, ToLine); -} - -static bool fillRanges(MemoryBuffer *Code, - std::vector &Ranges) { - IntrusiveRefCntPtr InMemoryFileSystem( - new llvm::vfs::InMemoryFileSystem); - FileManager Files(FileSystemOptions(), InMemoryFileSystem); - DiagnosticsEngine Diagnostics( - IntrusiveRefCntPtr(new DiagnosticIDs), - new DiagnosticOptions); - SourceManager Sources(Diagnostics, Files); - FileID ID = createInMemoryFile("", Code, Sources, Files, - InMemoryFileSystem.get()); - if (!LineRanges.empty()) { - if (!Offsets.empty() || !Lengths.empty()) { - 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)) { - errs() << "error: invalid : pair\n"; - return true; - } - if (FromLine > ToLine) { - 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; - unsigned Offset = Sources.getFileOffset(Start); - unsigned Length = Sources.getFileOffset(End) - Offset; - Ranges.push_back(tooling::Range(Offset, Length)); - } - return false; - } - - if (Offsets.empty()) - Offsets.push_back(0); - if (Offsets.size() != Lengths.size() && - !(Offsets.size() == 1 && Lengths.empty())) { - 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()) { - 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()) { - 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); - } - unsigned Offset = Sources.getFileOffset(Start); - unsigned Length = Sources.getFileOffset(End) - Offset; - Ranges.push_back(tooling::Range(Offset, Length)); - } - return false; -} - -static void outputReplacementXML(StringRef Text) { - // FIXME: When we sort includes, we need to make sure the stream is correct - // utf-8. - size_t From = 0; - size_t Index; - while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) { - outs() << Text.substr(From, Index - From); - switch (Text[Index]) { - case '\n': - outs() << " "; - break; - case '\r': - outs() << " "; - break; - case '<': - outs() << "<"; - break; - case '&': - outs() << "&"; - break; - default: - llvm_unreachable("Unexpected character encountered!"); - } - From = Index + 1; - } - outs() << Text.substr(From); -} - -static void outputReplacementsXML(const Replacements &Replaces) { - for (const auto &R : Replaces) { - outs() << ""; - outputReplacementXML(R.getReplacementText()); - outs() << "\n"; - } -} - -// Returns true on error. -static bool format(StringRef FileName) { - if (!OutputXML && Inplace && FileName == "-") { - errs() << "error: cannot use -i when reading from stdin.\n"; - return false; - } - // On Windows, overwriting a file with an open file mapping doesn't work, - // so read the whole file into memory when formatting in-place. - ErrorOr> CodeOrErr = - !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName) : - MemoryBuffer::getFileOrSTDIN(FileName); - if (std::error_code EC = CodeOrErr.getError()) { - errs() << EC.message() << "\n"; - return true; - } - std::unique_ptr Code = std::move(CodeOrErr.get()); - if (Code->getBufferSize() == 0) - return false; // Empty files are formatted correctly. - std::vector Ranges; - if (fillRanges(Code.get(), Ranges)) - return true; - StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName; - - llvm::Expected FormatStyle = - getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer()); - if (!FormatStyle) { - llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n"; - return true; - } - - if (SortIncludes.getNumOccurrences() != 0) - FormatStyle->SortIncludes = SortIncludes; - unsigned CursorPosition = Cursor; - Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges, - AssumedFileName, &CursorPosition); - auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces); - if (!ChangedCode) { - llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n"; - return true; - } - // Get new affected ranges after sorting `#includes`. - Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges); - FormattingAttemptStatus Status; - Replacements FormatChanges = reformat(*FormatStyle, *ChangedCode, Ranges, - AssumedFileName, &Status); - Replaces = Replaces.merge(FormatChanges); - if (OutputXML) { - outs() << "\n\n"; - if (Cursor.getNumOccurrences() != 0) - outs() << "" - << FormatChanges.getShiftedCodePosition(CursorPosition) - << "\n"; - - outputReplacementsXML(Replaces); - outs() << "\n"; - } else { - IntrusiveRefCntPtr InMemoryFileSystem( - new llvm::vfs::InMemoryFileSystem); - FileManager Files(FileSystemOptions(), InMemoryFileSystem); - DiagnosticsEngine Diagnostics( - IntrusiveRefCntPtr(new DiagnosticIDs), - new DiagnosticOptions); - SourceManager Sources(Diagnostics, Files); - FileID ID = createInMemoryFile(AssumedFileName, Code.get(), Sources, Files, - InMemoryFileSystem.get()); - Rewriter Rewrite(Sources, LangOptions()); - tooling::applyAllReplacements(Replaces, Rewrite); - if (Inplace) { - if (Rewrite.overwriteChangedFiles()) - return true; - } else { - if (Cursor.getNumOccurrences() != 0) { - outs() << "{ \"Cursor\": " - << FormatChanges.getShiftedCodePosition(CursorPosition) - << ", \"IncompleteFormat\": " - << (Status.FormatComplete ? "false" : "true"); - if (!Status.FormatComplete) - outs() << ", \"Line\": " << Status.Line; - outs() << " }\n"; - } - Rewrite.getEditBuffer(ID).write(outs()); - } - } - return false; -} - -} // namespace format -} // namespace clang +#include "ClangFormat.h" static void PrintVersion(raw_ostream &OS) { OS << clang::getClangToolFullVersion("clang-format") << '\n'; Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp +++ unittests/Format/FormatTest.cpp @@ -35,15 +35,16 @@ SC_DoNotCheck }; - std::string format(llvm::StringRef Code, - const FormatStyle &Style = getLLVMStyle(), - StatusCheck CheckComplete = SC_ExpectComplete) { + std::string format( + llvm::StringRef Code, const FormatStyle &Style = getLLVMStyle(), + StatusCheck CheckComplete = SC_ExpectComplete, + ExtraFormattingOptions FormattingOptions = ExtraFormattingOptions::None) { LLVM_DEBUG(llvm::errs() << "---\n"); LLVM_DEBUG(llvm::errs() << Code << "\n\n"); std::vector Ranges(1, tooling::Range(0, Code.size())); FormattingAttemptStatus Status; tooling::Replacements Replaces = - reformat(Style, Code, Ranges, "", &Status); + reformat(Style, FormattingOptions, Code, Ranges, "", &Status); if (CheckComplete != SC_DoNotCheck) { bool ExpectedCompleteFormat = CheckComplete == SC_ExpectComplete; EXPECT_EQ(ExpectedCompleteFormat, Status.FormatComplete) @@ -395,6 +396,25 @@ Style)); } +TEST_F(FormatTest, KeepsLineBreaks) { + FormatStyle Style = getLLVMStyle(); + EXPECT_EQ("if (a\n" + " && b) {\n" + "}", + format("if (a\n" + " && b) {\n" + "}", + Style, SC_ExpectComplete, + ExtraFormattingOptions::KeepLineBreaksForNonEmptyLines)); + + EXPECT_EQ("[]() {\n" + " foo(); }", + format("[]() {\n" + "foo(); }", + Style, SC_ExpectComplete, + ExtraFormattingOptions::KeepLineBreaksForNonEmptyLines)); +} + TEST_F(FormatTest, RecognizesBinaryOperatorKeywords) { verifyFormat("x = (a) and (b);"); verifyFormat("x = (a) or (b);");