[clang-format] Format raw string literals

Summary:
This patch adds raw string literal formatting.

Reviewers: djasper, klimek

Reviewed By: klimek

Subscribers: klimek, mgorny

Differential Revision: https://reviews.llvm.org/D35943

llvm-svn: 316903
This commit is contained in:
Krasimir Georgiev 2017-10-30 14:01:50 +00:00
parent 5cde1ccb29
commit 9ad83fe7f6
25 changed files with 1266 additions and 107 deletions

View File

@ -1327,6 +1327,41 @@ struct FormatStyle {
/// \brief Pointer and reference alignment style.
PointerAlignmentStyle PointerAlignment;
/// See documentation of ``RawStringFormats``.
struct RawStringFormat {
/// \brief The delimiter that this raw string format matches.
std::string Delimiter;
/// \brief The language of this raw string.
LanguageKind Language;
/// \brief The style name on which this raw string format is based on.
/// If not specified, the raw string format is based on the style that this
/// format is based on.
std::string BasedOnStyle;
bool operator==(const RawStringFormat &Other) const {
return Delimiter == Other.Delimiter && Language == Other.Language &&
BasedOnStyle == Other.BasedOnStyle;
}
};
/// \brief Raw string delimiters denoting that the raw string contents are
/// code in a particular language and can be reformatted.
///
/// A raw string with a matching delimiter will be reformatted assuming the
/// specified language based on a predefined style given by 'BasedOnStyle'.
/// If 'BasedOnStyle' is not found, the formatting is based on llvm style.
///
/// To configure this in the .clang-format file, use:
/// \code{.yaml}
/// RawStringFormats:
/// - Delimiter: 'pb'
/// Language: TextProto
/// BasedOnStyle: llvm
/// - Delimiter: 'proto'
/// Language: TextProto
/// BasedOnStyle: google
/// \endcode
std::vector<RawStringFormat> RawStringFormats;
/// \brief If ``true``, clang-format will attempt to re-flow comments.
/// \code
/// false:
@ -1592,6 +1627,7 @@ struct FormatStyle {
PenaltyExcessCharacter == R.PenaltyExcessCharacter &&
PenaltyReturnTypeOnItsOwnLine == R.PenaltyReturnTypeOnItsOwnLine &&
PointerAlignment == R.PointerAlignment &&
RawStringFormats == R.RawStringFormats &&
SpaceAfterCStyleCast == R.SpaceAfterCStyleCast &&
SpaceAfterTemplateKeyword == R.SpaceAfterTemplateKeyword &&
SpaceBeforeAssignmentOperators == R.SpaceBeforeAssignmentOperators &&

View File

@ -14,6 +14,7 @@
#include "ContinuationIndenter.h"
#include "BreakableToken.h"
#include "FormatInternal.h"
#include "WhitespaceManager.h"
#include "clang/Basic/OperatorPrecedence.h"
#include "clang/Basic/SourceManager.h"
@ -76,6 +77,53 @@ static bool opensProtoMessageField(const FormatToken &LessTok,
(LessTok.Previous && LessTok.Previous->is(tok::equal))));
}
// Returns the delimiter of a raw string literal, or None if TokenText is not
// the text of a raw string literal. The delimiter could be the empty string.
// For example, the delimiter of R"deli(cont)deli" is deli.
static llvm::Optional<StringRef> getRawStringDelimiter(StringRef TokenText) {
if (TokenText.size() < 5 // The smallest raw string possible is 'R"()"'.
|| !TokenText.startswith("R\"") || !TokenText.endswith("\""))
return None;
// A raw string starts with 'R"<delimiter>(' and delimiter is ascii and has
// size at most 16 by the standard, so the first '(' must be among the first
// 19 bytes.
size_t LParenPos = TokenText.substr(0, 19).find_first_of('(');
if (LParenPos == StringRef::npos)
return None;
StringRef Delimiter = TokenText.substr(2, LParenPos - 2);
// Check that the string ends in ')Delimiter"'.
size_t RParenPos = TokenText.size() - Delimiter.size() - 2;
if (TokenText[RParenPos] != ')')
return None;
if (!TokenText.substr(RParenPos + 1).startswith(Delimiter))
return None;
return Delimiter;
}
RawStringFormatStyleManager::RawStringFormatStyleManager(
const FormatStyle &CodeStyle) {
for (const auto &RawStringFormat : CodeStyle.RawStringFormats) {
FormatStyle Style;
if (!getPredefinedStyle(RawStringFormat.BasedOnStyle,
RawStringFormat.Language, &Style)) {
Style = getLLVMStyle();
Style.Language = RawStringFormat.Language;
}
Style.ColumnLimit = CodeStyle.ColumnLimit;
DelimiterStyle.insert({RawStringFormat.Delimiter, Style});
}
}
llvm::Optional<FormatStyle>
RawStringFormatStyleManager::get(StringRef Delimiter) const {
auto It = DelimiterStyle.find(Delimiter);
if (It == DelimiterStyle.end())
return None;
return It->second;
}
ContinuationIndenter::ContinuationIndenter(const FormatStyle &Style,
const AdditionalKeywords &Keywords,
const SourceManager &SourceMgr,
@ -85,14 +133,18 @@ ContinuationIndenter::ContinuationIndenter(const FormatStyle &Style,
: Style(Style), Keywords(Keywords), SourceMgr(SourceMgr),
Whitespaces(Whitespaces), Encoding(Encoding),
BinPackInconclusiveFunctions(BinPackInconclusiveFunctions),
CommentPragmasRegex(Style.CommentPragmas) {}
CommentPragmasRegex(Style.CommentPragmas), RawStringFormats(Style) {}
LineState ContinuationIndenter::getInitialState(unsigned FirstIndent,
unsigned FirstStartColumn,
const AnnotatedLine *Line,
bool DryRun) {
LineState State;
State.FirstIndent = FirstIndent;
State.Column = FirstIndent;
if (FirstStartColumn && Line->First->NewlinesBefore == 0)
State.Column = FirstStartColumn;
else
State.Column = FirstIndent;
// With preprocessor directive indentation, the line starts on column 0
// since it's indented after the hash, but FirstIndent is set to the
// preprocessor indent.
@ -1216,6 +1268,89 @@ void ContinuationIndenter::moveStateToNewBlock(LineState &State) {
State.Stack.back().BreakBeforeParameter = true;
}
static unsigned getLastLineEndColumn(StringRef Text, unsigned StartColumn,
unsigned TabWidth,
encoding::Encoding Encoding) {
size_t LastNewlinePos = Text.find_last_of("\n");
if (LastNewlinePos == StringRef::npos) {
return StartColumn +
encoding::columnWidthWithTabs(Text, StartColumn, TabWidth, Encoding);
} else {
return encoding::columnWidthWithTabs(Text.substr(LastNewlinePos),
/*StartColumn=*/0, TabWidth, Encoding);
}
}
unsigned ContinuationIndenter::reformatRawStringLiteral(
const FormatToken &Current, unsigned StartColumn, LineState &State,
StringRef Delimiter, const FormatStyle &RawStringStyle, bool DryRun) {
// The text of a raw string is between the leading 'R"delimiter(' and the
// trailing 'delimiter)"'.
unsigned PrefixSize = 3 + Delimiter.size();
unsigned SuffixSize = 2 + Delimiter.size();
// The first start column is the column the raw text starts.
unsigned FirstStartColumn = StartColumn + PrefixSize;
// The next start column is the intended indentation a line break inside
// the raw string at level 0. It is determined by the following rules:
// - if the content starts on newline, it is one level more than the current
// indent, and
// - if the content does not start on a newline, it is the first start
// column.
// These rules have the advantage that the formatted content both does not
// violate the rectangle rule and visually flows within the surrounding
// source.
bool ContentStartsOnNewline = Current.TokenText[PrefixSize] == '\n';
unsigned NextStartColumn = ContentStartsOnNewline
? State.Stack.back().Indent + Style.IndentWidth
: FirstStartColumn;
// The last start column is the column the raw string suffix starts if it is
// put on a newline.
// The last start column is the intended indentation of the raw string postfix
// if it is put on a newline. It is determined by the following rules:
// - if the raw string prefix starts on a newline, it is the column where
// that raw string prefix starts, and
// - if the raw string prefix does not start on a newline, it is the current
// indent.
unsigned LastStartColumn = Current.NewlinesBefore
? FirstStartColumn - PrefixSize
: State.Stack.back().Indent;
std::string RawText =
Current.TokenText.substr(PrefixSize).drop_back(SuffixSize);
std::pair<tooling::Replacements, unsigned> Fixes = internal::reformat(
RawStringStyle, RawText, {tooling::Range(0, RawText.size())},
FirstStartColumn, NextStartColumn, LastStartColumn, "<stdin>",
/*FormattingAttemptStatus=*/nullptr);
auto NewCode = applyAllReplacements(RawText, Fixes.first);
tooling::Replacements NoFixes;
if (!NewCode) {
State.Column += Current.ColumnWidth;
return 0;
}
if (!DryRun) {
SourceLocation OriginLoc =
Current.Tok.getLocation().getLocWithOffset(PrefixSize);
for (const tooling::Replacement &Fix : Fixes.first) {
auto Err = Whitespaces.addReplacement(tooling::Replacement(
SourceMgr, OriginLoc.getLocWithOffset(Fix.getOffset()),
Fix.getLength(), Fix.getReplacementText()));
if (Err) {
llvm::errs() << "Failed to reformat raw string: "
<< llvm::toString(std::move(Err)) << "\n";
}
}
}
unsigned RawLastLineEndColumn = getLastLineEndColumn(
*NewCode, FirstStartColumn, Style.TabWidth, Encoding);
State.Column = RawLastLineEndColumn + SuffixSize;
return Fixes.second;
}
unsigned ContinuationIndenter::addMultilineToken(const FormatToken &Current,
LineState &State) {
if (!Current.IsMultiline)
@ -1238,9 +1373,18 @@ unsigned ContinuationIndenter::addMultilineToken(const FormatToken &Current,
unsigned ContinuationIndenter::breakProtrudingToken(const FormatToken &Current,
LineState &State,
bool DryRun) {
// Don't break multi-line tokens other than block comments. Instead, just
// update the state.
if (Current.isNot(TT_BlockComment) && Current.IsMultiline)
// Compute the raw string style to use in case this is a raw string literal
// that can be reformatted.
llvm::Optional<StringRef> Delimiter = None;
llvm::Optional<FormatStyle> RawStringStyle = None;
if (Current.isStringLiteral())
Delimiter = getRawStringDelimiter(Current.TokenText);
if (Delimiter)
RawStringStyle = RawStringFormats.get(*Delimiter);
// Don't break multi-line tokens other than block comments and raw string
// literals. Instead, just update the state.
if (Current.isNot(TT_BlockComment) && !RawStringStyle && Current.IsMultiline)
return addMultilineToken(Current, State);
// Don't break implicit string literals or import statements.
@ -1275,6 +1419,11 @@ unsigned ContinuationIndenter::breakProtrudingToken(const FormatToken &Current,
if (Current.IsUnterminatedLiteral)
return 0;
if (RawStringStyle) {
RawStringStyle->ColumnLimit = ColumnLimit;
return reformatRawStringLiteral(Current, StartColumn, State, *Delimiter,
*RawStringStyle, DryRun);
}
StringRef Text = Current.TokenText;
StringRef Prefix;
StringRef Postfix;

View File

@ -20,6 +20,8 @@
#include "FormatToken.h"
#include "clang/Format/Format.h"
#include "llvm/Support/Regex.h"
#include <map>
#include <tuple>
namespace clang {
class SourceManager;
@ -30,8 +32,17 @@ class AnnotatedLine;
struct FormatToken;
struct LineState;
struct ParenState;
struct RawStringFormatStyleManager;
class WhitespaceManager;
struct RawStringFormatStyleManager {
llvm::StringMap<FormatStyle> DelimiterStyle;
RawStringFormatStyleManager(const FormatStyle &CodeStyle);
llvm::Optional<FormatStyle> get(StringRef Delimiter) const;
};
class ContinuationIndenter {
public:
/// \brief Constructs a \c ContinuationIndenter to format \p Line starting in
@ -44,9 +55,11 @@ public:
bool BinPackInconclusiveFunctions);
/// \brief Get the initial state, i.e. the state after placing \p Line's
/// first token at \p FirstIndent.
LineState getInitialState(unsigned FirstIndent, const AnnotatedLine *Line,
bool DryRun);
/// first token at \p FirstIndent. When reformatting a fragment of code, as in
/// the case of formatting inside raw string literals, \p FirstStartColumn is
/// the column at which the state of the parent formatter is.
LineState getInitialState(unsigned FirstIndent, unsigned FirstStartColumn,
const AnnotatedLine *Line, bool DryRun);
// FIXME: canBreak and mustBreak aren't strictly indentation-related. Find a
// better home.
@ -88,15 +101,24 @@ private:
/// \brief Update 'State' with the next token opening a nested block.
void moveStateToNewBlock(LineState &State);
/// \brief Reformats a raw string literal.
///
/// \returns An extra penalty induced by reformatting the token.
unsigned reformatRawStringLiteral(const FormatToken &Current,
unsigned StartColumn, LineState &State,
StringRef Delimiter,
const FormatStyle &RawStringStyle,
bool DryRun);
/// \brief If the current token sticks out over the end of the line, break
/// it if possible.
///
/// \returns An extra penalty if a token was broken, otherwise 0.
///
/// The returned penalty will cover the cost of the additional line breaks and
/// column limit violation in all lines except for the last one. The penalty
/// for the column limit violation in the last line (and in single line
/// tokens) is handled in \c addNextStateToQueue.
/// The returned penalty will cover the cost of the additional line breaks
/// and column limit violation in all lines except for the last one. The
/// penalty for the column limit violation in the last line (and in single
/// line tokens) is handled in \c addNextStateToQueue.
unsigned breakProtrudingToken(const FormatToken &Current, LineState &State,
bool DryRun);
@ -143,6 +165,7 @@ private:
encoding::Encoding Encoding;
bool BinPackInconclusiveFunctions;
llvm::Regex CommentPragmasRegex;
const RawStringFormatStyleManager RawStringFormats;
};
struct ParenState {

View File

@ -16,6 +16,7 @@
#include "clang/Format/Format.h"
#include "AffectedRangeManager.h"
#include "ContinuationIndenter.h"
#include "FormatInternal.h"
#include "FormatTokenLexer.h"
#include "NamespaceEndCommentsFixer.h"
#include "SortJavaScriptImports.h"
@ -44,7 +45,8 @@
using clang::format::FormatStyle;
LLVM_YAML_IS_SEQUENCE_VECTOR(clang::format::FormatStyle::IncludeCategory)
LLVM_YAML_IS_SEQUENCE_VECTOR(clang::format::FormatStyle::IncludeCategory);
LLVM_YAML_IS_SEQUENCE_VECTOR(clang::format::FormatStyle::RawStringFormat);
namespace llvm {
namespace yaml {
@ -389,6 +391,7 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("PenaltyReturnTypeOnItsOwnLine",
Style.PenaltyReturnTypeOnItsOwnLine);
IO.mapOptional("PointerAlignment", Style.PointerAlignment);
IO.mapOptional("RawStringFormats", Style.RawStringFormats);
IO.mapOptional("ReflowComments", Style.ReflowComments);
IO.mapOptional("SortIncludes", Style.SortIncludes);
IO.mapOptional("SortUsingDeclarations", Style.SortUsingDeclarations);
@ -441,6 +444,14 @@ template <> struct MappingTraits<FormatStyle::IncludeCategory> {
}
};
template <> struct MappingTraits<FormatStyle::RawStringFormat> {
static void mapping(IO &IO, FormatStyle::RawStringFormat &Format) {
IO.mapOptional("Delimiter", Format.Delimiter);
IO.mapOptional("Language", Format.Language);
IO.mapOptional("BasedOnStyle", Format.BasedOnStyle);
}
};
// Allows to read vector<FormatStyle> while keeping default values.
// IO.getContext() should contain a pointer to the FormatStyle structure, that
// will be used to get default values for missing keys.
@ -620,6 +631,7 @@ FormatStyle getLLVMStyle() {
LLVMStyle.SpacesBeforeTrailingComments = 1;
LLVMStyle.Standard = FormatStyle::LS_Cpp11;
LLVMStyle.UseTab = FormatStyle::UT_Never;
LLVMStyle.RawStringFormats = {{"pb", FormatStyle::LK_TextProto, "google"}};
LLVMStyle.ReflowComments = true;
LLVMStyle.SpacesInParentheses = false;
LLVMStyle.SpacesInSquareBrackets = false;
@ -895,7 +907,7 @@ public:
JavaScriptRequoter(const Environment &Env, const FormatStyle &Style)
: TokenAnalyzer(Env, Style) {}
tooling::Replacements
std::pair<tooling::Replacements, unsigned>
analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) override {
@ -903,7 +915,7 @@ public:
AnnotatedLines.end());
tooling::Replacements Result;
requoteJSStringLiteral(AnnotatedLines, Result);
return Result;
return {Result, 0};
}
private:
@ -984,7 +996,7 @@ public:
FormattingAttemptStatus *Status)
: TokenAnalyzer(Env, Style), Status(Status) {}
tooling::Replacements
std::pair<tooling::Replacements, unsigned>
analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) override {
@ -1003,13 +1015,20 @@ public:
ContinuationIndenter Indenter(Style, Tokens.getKeywords(),
Env.getSourceManager(), Whitespaces, Encoding,
BinPackInconclusiveFunctions);
UnwrappedLineFormatter(&Indenter, &Whitespaces, Style, Tokens.getKeywords(),
Env.getSourceManager(), Status)
.format(AnnotatedLines);
unsigned Penalty =
UnwrappedLineFormatter(&Indenter, &Whitespaces, Style,
Tokens.getKeywords(), Env.getSourceManager(),
Status)
.format(AnnotatedLines, /*DryRun=*/false,
/*AdditionalIndent=*/0,
/*FixBadIndentation=*/false,
/*FirstStartColumn=*/Env.getFirstStartColumn(),
/*NextStartColumn=*/Env.getNextStartColumn(),
/*LastStartColumn=*/Env.getLastStartColumn());
for (const auto &R : Whitespaces.generateReplacements())
if (Result.add(R))
return Result;
return Result;
return {Result, 0};
return {Result, Penalty};
}
private:
@ -1097,7 +1116,7 @@ public:
DeletedTokens(FormatTokenLess(Env.getSourceManager())) {}
// FIXME: eliminate unused parameters.
tooling::Replacements
std::pair<tooling::Replacements, unsigned>
analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) override {
@ -1125,7 +1144,7 @@ public:
}
}
return generateFixes();
return {generateFixes(), 0};
}
private:
@ -1906,19 +1925,22 @@ cleanupAroundReplacements(StringRef Code, const tooling::Replacements &Replaces,
return processReplacements(Cleanup, Code, NewReplaces, Style);
}
tooling::Replacements reformat(const FormatStyle &Style, StringRef Code,
ArrayRef<tooling::Range> Ranges,
StringRef FileName,
FormattingAttemptStatus *Status) {
namespace internal {
std::pair<tooling::Replacements, unsigned>
reformat(const FormatStyle &Style, StringRef Code,
ArrayRef<tooling::Range> Ranges, unsigned FirstStartColumn,
unsigned NextStartColumn, unsigned LastStartColumn, StringRef FileName,
FormattingAttemptStatus *Status) {
FormatStyle Expanded = expandPresets(Style);
if (Expanded.DisableFormat)
return tooling::Replacements();
return {tooling::Replacements(), 0};
if (isLikelyXml(Code))
return tooling::Replacements();
return {tooling::Replacements(), 0};
if (Expanded.Language == FormatStyle::LK_JavaScript && isMpegTS(Code))
return tooling::Replacements();
return {tooling::Replacements(), 0};
typedef std::function<tooling::Replacements(const Environment &)>
typedef std::function<std::pair<tooling::Replacements, unsigned>(
const Environment &)>
AnalyzerPass;
SmallVector<AnalyzerPass, 4> Passes;
@ -1944,26 +1966,42 @@ tooling::Replacements reformat(const FormatStyle &Style, StringRef Code,
return Formatter(Env, Expanded, Status).process();
});
std::unique_ptr<Environment> Env =
Environment::CreateVirtualEnvironment(Code, FileName, Ranges);
std::unique_ptr<Environment> Env = Environment::CreateVirtualEnvironment(
Code, FileName, Ranges, FirstStartColumn, NextStartColumn,
LastStartColumn);
llvm::Optional<std::string> CurrentCode = None;
tooling::Replacements Fixes;
unsigned Penalty = 0;
for (size_t I = 0, E = Passes.size(); I < E; ++I) {
tooling::Replacements PassFixes = Passes[I](*Env);
std::pair<tooling::Replacements, unsigned> PassFixes = Passes[I](*Env);
auto NewCode = applyAllReplacements(
CurrentCode ? StringRef(*CurrentCode) : Code, PassFixes);
CurrentCode ? StringRef(*CurrentCode) : Code, PassFixes.first);
if (NewCode) {
Fixes = Fixes.merge(PassFixes);
Fixes = Fixes.merge(PassFixes.first);
Penalty += PassFixes.second;
if (I + 1 < E) {
CurrentCode = std::move(*NewCode);
Env = Environment::CreateVirtualEnvironment(
*CurrentCode, FileName,
tooling::calculateRangesAfterReplacements(Fixes, Ranges));
tooling::calculateRangesAfterReplacements(Fixes, Ranges),
FirstStartColumn, NextStartColumn, LastStartColumn);
}
}
}
return Fixes;
return {Fixes, Penalty};
}
} // namespace internal
tooling::Replacements reformat(const FormatStyle &Style, StringRef Code,
ArrayRef<tooling::Range> Ranges,
StringRef FileName,
FormattingAttemptStatus *Status) {
return internal::reformat(Style, Code, Ranges,
/*FirstStartColumn=*/0,
/*NextStartColumn=*/0,
/*LastStartColumn=*/0, FileName, Status)
.first;
}
tooling::Replacements cleanup(const FormatStyle &Style, StringRef Code,
@ -1975,7 +2013,7 @@ tooling::Replacements cleanup(const FormatStyle &Style, StringRef Code,
std::unique_ptr<Environment> Env =
Environment::CreateVirtualEnvironment(Code, FileName, Ranges);
Cleaner Clean(*Env, Style);
return Clean.process();
return Clean.process().first;
}
tooling::Replacements reformat(const FormatStyle &Style, StringRef Code,
@ -1995,7 +2033,7 @@ tooling::Replacements fixNamespaceEndComments(const FormatStyle &Style,
std::unique_ptr<Environment> Env =
Environment::CreateVirtualEnvironment(Code, FileName, Ranges);
NamespaceEndCommentsFixer Fix(*Env, Style);
return Fix.process();
return Fix.process().first;
}
tooling::Replacements sortUsingDeclarations(const FormatStyle &Style,
@ -2005,7 +2043,7 @@ tooling::Replacements sortUsingDeclarations(const FormatStyle &Style,
std::unique_ptr<Environment> Env =
Environment::CreateVirtualEnvironment(Code, FileName, Ranges);
UsingDeclarationsSorter Sorter(*Env, Style);
return Sorter.process();
return Sorter.process().first;
}
LangOptions getFormattingLangOpts(const FormatStyle &Style) {

View File

@ -0,0 +1,79 @@
//===--- FormatInternal.h - Format C++ code ---------------------*- C++ -*-===//
//
// 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 declares Format APIs to be used internally by the
/// formatting library implementation.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_LIB_FORMAT_FORMATINTERNAL_H
#define LLVM_CLANG_LIB_FORMAT_FORMATINTERNAL_H
namespace clang {
namespace format {
namespace internal {
/// \brief Reformats the given \p Ranges in the code fragment \p Code.
///
/// A fragment of code could conceptually be surrounded by other code that might
/// constrain how that fragment is laid out.
/// For example, consider the fragment of code between 'R"(' and ')"',
/// exclusive, in the following code:
///
/// void outer(int x) {
/// string inner = R"(name: data
/// ^ FirstStartColumn
/// value: {
/// x: 1
/// ^ NextStartColumn
/// }
/// )";
/// ^ LastStartColumn
/// }
///
/// The outer code can influence the inner fragment as follows:
/// * \p FirstStartColumn specifies the column at which \p Code starts.
/// * \p NextStartColumn specifies the additional indent dictated by the
/// surrounding code. It is applied to the rest of the lines of \p Code.
/// * \p LastStartColumn specifies the column at which the last line of
/// \p Code should end, in case the last line is an empty line.
///
/// In the case where the last line of the fragment contains content,
/// the fragment ends at the end of that content and \p LastStartColumn is
/// not taken into account, for example in:
///
/// void block() {
/// string inner = R"(name: value)";
/// }
///
/// Each range is extended on either end to its next bigger logic unit, i.e.
/// everything that might influence its formatting or might be influenced by its
/// formatting.
///
/// Returns a pair P, where:
/// * P.first are the ``Replacements`` necessary to make all \p Ranges comply
/// with \p Style.
/// * P.second is the penalty induced by formatting the fragment \p Code.
/// If the formatting of the fragment doesn't have a notion of penalty,
/// returns 0.
///
/// If ``Status`` is non-null, its value will be populated with the status of
/// this formatting attempt. See \c FormattingAttemptStatus.
std::pair<tooling::Replacements, unsigned>
reformat(const FormatStyle &Style, StringRef Code,
ArrayRef<tooling::Range> Ranges, unsigned FirstStartColumn,
unsigned NextStartColumn, unsigned LastStartColumn, StringRef FileName,
FormattingAttemptStatus *Status);
} // namespace internal
} // namespace format
} // namespace clang
#endif

View File

@ -24,10 +24,10 @@ namespace clang {
namespace format {
FormatTokenLexer::FormatTokenLexer(const SourceManager &SourceMgr, FileID ID,
const FormatStyle &Style,
unsigned Column, const FormatStyle &Style,
encoding::Encoding Encoding)
: FormatTok(nullptr), IsFirstToken(true), StateStack({LexerState::NORMAL}),
Column(0), TrailingWhitespace(0), SourceMgr(SourceMgr), ID(ID),
Column(Column), TrailingWhitespace(0), SourceMgr(SourceMgr), ID(ID),
Style(Style), IdentTable(getFormattingLangOpts(Style)),
Keywords(IdentTable), Encoding(Encoding), FirstInLineIndex(0),
FormattingDisabled(false), MacroBlockBeginRegex(Style.MacroBlockBegin),

View File

@ -36,7 +36,7 @@ enum LexerState {
class FormatTokenLexer {
public:
FormatTokenLexer(const SourceManager &SourceMgr, FileID ID,
FormatTokenLexer(const SourceManager &SourceMgr, FileID ID, unsigned Column,
const FormatStyle &Style, encoding::Encoding Encoding);
ArrayRef<FormatToken *> lex();

View File

@ -137,7 +137,7 @@ NamespaceEndCommentsFixer::NamespaceEndCommentsFixer(const Environment &Env,
const FormatStyle &Style)
: TokenAnalyzer(Env, Style) {}
tooling::Replacements NamespaceEndCommentsFixer::analyze(
std::pair<tooling::Replacements, unsigned> NamespaceEndCommentsFixer::analyze(
TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) {
const SourceManager &SourceMgr = Env.getSourceManager();
@ -206,7 +206,7 @@ tooling::Replacements NamespaceEndCommentsFixer::analyze(
}
StartLineIndex = SIZE_MAX;
}
return Fixes;
return {Fixes, 0};
}
} // namespace format

View File

@ -25,7 +25,7 @@ class NamespaceEndCommentsFixer : public TokenAnalyzer {
public:
NamespaceEndCommentsFixer(const Environment &Env, const FormatStyle &Style);
tooling::Replacements
std::pair<tooling::Replacements, unsigned>
analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) override;

View File

@ -123,7 +123,7 @@ public:
: TokenAnalyzer(Env, Style),
FileContents(Env.getSourceManager().getBufferData(Env.getFileID())) {}
tooling::Replacements
std::pair<tooling::Replacements, unsigned>
analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) override {
@ -138,7 +138,7 @@ public:
parseModuleReferences(Keywords, AnnotatedLines);
if (References.empty())
return Result;
return {Result, 0};
SmallVector<unsigned, 16> Indices;
for (unsigned i = 0, e = References.size(); i != e; ++i)
@ -168,7 +168,7 @@ public:
}
if (ReferencesInOrder && SymbolsInOrder)
return Result;
return {Result, 0};
SourceRange InsertionPoint = References[0].Range;
InsertionPoint.setEnd(References[References.size() - 1].Range.getEnd());
@ -202,7 +202,7 @@ public:
assert(false);
}
return Result;
return {Result, 0};
}
private:
@ -449,7 +449,7 @@ tooling::Replacements sortJavaScriptImports(const FormatStyle &Style,
std::unique_ptr<Environment> Env =
Environment::CreateVirtualEnvironment(Code, FileName, Ranges);
JavaScriptImportSorter Sorter(*Env, Style);
return Sorter.process();
return Sorter.process().first;
}
} // end namespace format

View File

@ -38,7 +38,10 @@ namespace format {
// Code.
std::unique_ptr<Environment>
Environment::CreateVirtualEnvironment(StringRef Code, StringRef FileName,
ArrayRef<tooling::Range> Ranges) {
ArrayRef<tooling::Range> Ranges,
unsigned FirstStartColumn,
unsigned NextStartColumn,
unsigned LastStartColumn) {
// This is referenced by `FileMgr` and will be released by `FileMgr` when it
// is deleted.
IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem(
@ -70,9 +73,9 @@ Environment::CreateVirtualEnvironment(StringRef Code, StringRef FileName,
SourceLocation End = Start.getLocWithOffset(Range.getLength());
CharRanges.push_back(CharSourceRange::getCharRange(Start, End));
}
return llvm::make_unique<Environment>(ID, std::move(FileMgr),
std::move(VirtualSM),
std::move(Diagnostics), CharRanges);
return llvm::make_unique<Environment>(
ID, std::move(FileMgr), std::move(VirtualSM), std::move(Diagnostics),
CharRanges, FirstStartColumn, NextStartColumn, LastStartColumn);
}
TokenAnalyzer::TokenAnalyzer(const Environment &Env, const FormatStyle &Style)
@ -89,14 +92,16 @@ TokenAnalyzer::TokenAnalyzer(const Environment &Env, const FormatStyle &Style)
<< "\n");
}
tooling::Replacements TokenAnalyzer::process() {
std::pair<tooling::Replacements, unsigned> TokenAnalyzer::process() {
tooling::Replacements Result;
FormatTokenLexer Tokens(Env.getSourceManager(), Env.getFileID(), Style,
Encoding);
FormatTokenLexer Tokens(Env.getSourceManager(), Env.getFileID(),
Env.getFirstStartColumn(), Style, Encoding);
UnwrappedLineParser Parser(Style, Tokens.getKeywords(), Tokens.lex(), *this);
UnwrappedLineParser Parser(Style, Tokens.getKeywords(),
Env.getFirstStartColumn(), Tokens.lex(), *this);
Parser.parse();
assert(UnwrappedLines.rbegin()->empty());
unsigned Penalty = 0;
for (unsigned Run = 0, RunE = UnwrappedLines.size(); Run + 1 != RunE; ++Run) {
DEBUG(llvm::dbgs() << "Run " << Run << "...\n");
SmallVector<AnnotatedLine *, 16> AnnotatedLines;
@ -107,13 +112,13 @@ tooling::Replacements TokenAnalyzer::process() {
Annotator.annotate(*AnnotatedLines.back());
}
tooling::Replacements RunResult =
std::pair<tooling::Replacements, unsigned> RunResult =
analyze(Annotator, AnnotatedLines, Tokens);
DEBUG({
llvm::dbgs() << "Replacements for run " << Run << ":\n";
for (tooling::Replacements::const_iterator I = RunResult.begin(),
E = RunResult.end();
for (tooling::Replacements::const_iterator I = RunResult.first.begin(),
E = RunResult.first.end();
I != E; ++I) {
llvm::dbgs() << I->toString() << "\n";
}
@ -121,17 +126,19 @@ tooling::Replacements TokenAnalyzer::process() {
for (unsigned i = 0, e = AnnotatedLines.size(); i != e; ++i) {
delete AnnotatedLines[i];
}
for (const auto &R : RunResult) {
Penalty += RunResult.second;
for (const auto &R : RunResult.first) {
auto Err = Result.add(R);
// FIXME: better error handling here. For now, simply return an empty
// Replacements to indicate failure.
if (Err) {
llvm::errs() << llvm::toString(std::move(Err)) << "\n";
return tooling::Replacements();
return {tooling::Replacements(), 0};
}
}
}
return Result;
return {Result, Penalty};
}
void TokenAnalyzer::consumeUnwrappedLine(const UnwrappedLine &TheLine) {

View File

@ -37,21 +37,37 @@ namespace format {
class Environment {
public:
Environment(SourceManager &SM, FileID ID, ArrayRef<CharSourceRange> Ranges)
: ID(ID), CharRanges(Ranges.begin(), Ranges.end()), SM(SM) {}
: ID(ID), CharRanges(Ranges.begin(), Ranges.end()), SM(SM),
FirstStartColumn(0),
NextStartColumn(0),
LastStartColumn(0) {}
Environment(FileID ID, std::unique_ptr<FileManager> FileMgr,
std::unique_ptr<SourceManager> VirtualSM,
std::unique_ptr<DiagnosticsEngine> Diagnostics,
const std::vector<CharSourceRange> &CharRanges)
const std::vector<CharSourceRange> &CharRanges,
unsigned FirstStartColumn,
unsigned NextStartColumn,
unsigned LastStartColumn)
: ID(ID), CharRanges(CharRanges.begin(), CharRanges.end()),
SM(*VirtualSM), FileMgr(std::move(FileMgr)),
SM(*VirtualSM),
FirstStartColumn(FirstStartColumn),
NextStartColumn(NextStartColumn),
LastStartColumn(LastStartColumn),
FileMgr(std::move(FileMgr)),
VirtualSM(std::move(VirtualSM)), Diagnostics(std::move(Diagnostics)) {}
// This sets up an virtual file system with file \p FileName containing \p
// Code.
// This sets up an virtual file system with file \p FileName containing the
// fragment \p Code. Assumes that \p Code starts at \p FirstStartColumn,
// that the next lines of \p Code should start at \p NextStartColumn, and
// that \p Code should end at \p LastStartColumn if it ends in newline.
// See also the documentation of clang::format::internal::reformat.
static std::unique_ptr<Environment>
CreateVirtualEnvironment(StringRef Code, StringRef FileName,
ArrayRef<tooling::Range> Ranges);
ArrayRef<tooling::Range> Ranges,
unsigned FirstStartColumn = 0,
unsigned NextStartColumn = 0,
unsigned LastStartColumn = 0);
FileID getFileID() const { return ID; }
@ -59,10 +75,25 @@ public:
const SourceManager &getSourceManager() const { return SM; }
// Returns the column at which the fragment of code managed by this
// environment starts.
unsigned getFirstStartColumn() const { return FirstStartColumn; }
// Returns the column at which subsequent lines of the fragment of code
// managed by this environment should start.
unsigned getNextStartColumn() const { return NextStartColumn; }
// Returns the column at which the fragment of code managed by this
// environment should end if it ends in a newline.
unsigned getLastStartColumn() const { return LastStartColumn; }
private:
FileID ID;
SmallVector<CharSourceRange, 8> CharRanges;
SourceManager &SM;
unsigned FirstStartColumn;
unsigned NextStartColumn;
unsigned LastStartColumn;
// The order of these fields are important - they should be in the same order
// as they are created in `CreateVirtualEnvironment` so that they can be
@ -76,10 +107,10 @@ class TokenAnalyzer : public UnwrappedLineConsumer {
public:
TokenAnalyzer(const Environment &Env, const FormatStyle &Style);
tooling::Replacements process();
std::pair<tooling::Replacements, unsigned> process();
protected:
virtual tooling::Replacements
virtual std::pair<tooling::Replacements, unsigned>
analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) = 0;

View File

@ -1891,7 +1891,8 @@ void TokenAnnotator::calculateFormattingInformation(AnnotatedLine &Line) {
}
Line.First->TotalLength =
Line.First->IsMultiline ? Style.ColumnLimit : Line.First->ColumnWidth;
Line.First->IsMultiline ? Style.ColumnLimit
: Line.FirstStartColumn + Line.First->ColumnWidth;
FormatToken *Current = Line.First->Next;
bool InFunctionDecl = Line.MightBeFunctionDecl;
while (Current) {

View File

@ -43,7 +43,8 @@ public:
InPPDirective(Line.InPPDirective),
MustBeDeclaration(Line.MustBeDeclaration), MightBeFunctionDecl(false),
IsMultiVariableDeclStmt(false), Affected(false),
LeadingEmptyLinesAffected(false), ChildrenAffected(false) {
LeadingEmptyLinesAffected(false), ChildrenAffected(false),
FirstStartColumn(Line.FirstStartColumn) {
assert(!Line.Tokens.empty());
// Calculate Next and Previous for all tokens. Note that we must overwrite
@ -127,6 +128,8 @@ public:
/// \c True if one of this line's children intersects with an input range.
bool ChildrenAffected;
unsigned FirstStartColumn;
private:
// Disallow copying.
AnnotatedLine(const AnnotatedLine &) = delete;

View File

@ -659,7 +659,9 @@ public:
/// \brief Formats an \c AnnotatedLine and returns the penalty.
///
/// If \p DryRun is \c false, directly applies the changes.
virtual unsigned formatLine(const AnnotatedLine &Line, unsigned FirstIndent,
virtual unsigned formatLine(const AnnotatedLine &Line,
unsigned FirstIndent,
unsigned FirstStartColumn,
bool DryRun) = 0;
protected:
@ -730,7 +732,8 @@ protected:
*Child->First, /*Newlines=*/0, /*Spaces=*/1,
/*StartOfTokenColumn=*/State.Column, State.Line->InPPDirective);
}
Penalty += formatLine(*Child, State.Column + 1, DryRun);
Penalty +=
formatLine(*Child, State.Column + 1, /*FirstStartColumn=*/0, DryRun);
State.Column += 1 + Child->Last->TotalLength;
return true;
@ -756,10 +759,10 @@ public:
/// \brief Formats the line, simply keeping all of the input's line breaking
/// decisions.
unsigned formatLine(const AnnotatedLine &Line, unsigned FirstIndent,
bool DryRun) override {
unsigned FirstStartColumn, bool DryRun) override {
assert(!DryRun);
LineState State =
Indenter->getInitialState(FirstIndent, &Line, /*DryRun=*/false);
LineState State = Indenter->getInitialState(FirstIndent, FirstStartColumn,
&Line, /*DryRun=*/false);
while (State.NextToken) {
bool Newline =
Indenter->mustBreak(State) ||
@ -782,9 +785,10 @@ public:
/// \brief Puts all tokens into a single line.
unsigned formatLine(const AnnotatedLine &Line, unsigned FirstIndent,
bool DryRun) override {
unsigned FirstStartColumn, bool DryRun) override {
unsigned Penalty = 0;
LineState State = Indenter->getInitialState(FirstIndent, &Line, DryRun);
LineState State =
Indenter->getInitialState(FirstIndent, FirstStartColumn, &Line, DryRun);
while (State.NextToken) {
formatChildren(State, /*Newline=*/false, DryRun, Penalty);
Indenter->addTokenToState(
@ -806,8 +810,9 @@ public:
/// \brief Formats the line by finding the best line breaks with line lengths
/// below the column limit.
unsigned formatLine(const AnnotatedLine &Line, unsigned FirstIndent,
bool DryRun) override {
LineState State = Indenter->getInitialState(FirstIndent, &Line, DryRun);
unsigned FirstStartColumn, bool DryRun) override {
LineState State =
Indenter->getInitialState(FirstIndent, FirstStartColumn, &Line, DryRun);
// If the ObjC method declaration does not fit on a line, we should format
// it with one arg per line.
@ -974,7 +979,10 @@ private:
unsigned
UnwrappedLineFormatter::format(const SmallVectorImpl<AnnotatedLine *> &Lines,
bool DryRun, int AdditionalIndent,
bool FixBadIndentation) {
bool FixBadIndentation,
unsigned FirstStartColumn,
unsigned NextStartColumn,
unsigned LastStartColumn) {
LineJoiner Joiner(Style, Keywords, Lines);
// Try to look up already computed penalty in DryRun-mode.
@ -994,9 +1002,10 @@ UnwrappedLineFormatter::format(const SmallVectorImpl<AnnotatedLine *> &Lines,
// The minimum level of consecutive lines that have been formatted.
unsigned RangeMinLevel = UINT_MAX;
bool FirstLine = true;
for (const AnnotatedLine *Line =
Joiner.getNextMergedLine(DryRun, IndentTracker);
Line; Line = NextLine) {
Line; Line = NextLine, FirstLine = false) {
const AnnotatedLine &TheLine = *Line;
unsigned Indent = IndentTracker.getIndent();
@ -1020,8 +1029,12 @@ UnwrappedLineFormatter::format(const SmallVectorImpl<AnnotatedLine *> &Lines,
}
if (ShouldFormat && TheLine.Type != LT_Invalid) {
if (!DryRun)
formatFirstToken(TheLine, PreviousLine, Indent);
if (!DryRun) {
bool LastLine = Line->First->is(tok::eof);
formatFirstToken(TheLine, PreviousLine,
Indent,
LastLine ? LastStartColumn : NextStartColumn + Indent);
}
NextLine = Joiner.getNextMergedLine(DryRun, IndentTracker);
unsigned ColumnLimit = getColumnLimit(TheLine.InPPDirective, NextLine);
@ -1030,16 +1043,18 @@ UnwrappedLineFormatter::format(const SmallVectorImpl<AnnotatedLine *> &Lines,
(TheLine.Type == LT_ImportStatement &&
(Style.Language != FormatStyle::LK_JavaScript ||
!Style.JavaScriptWrapImports));
if (Style.ColumnLimit == 0)
NoColumnLimitLineFormatter(Indenter, Whitespaces, Style, this)
.formatLine(TheLine, Indent, DryRun);
.formatLine(TheLine, NextStartColumn + Indent,
FirstLine ? FirstStartColumn : 0, DryRun);
else if (FitsIntoOneLine)
Penalty += NoLineBreakFormatter(Indenter, Whitespaces, Style, this)
.formatLine(TheLine, Indent, DryRun);
.formatLine(TheLine, NextStartColumn + Indent,
FirstLine ? FirstStartColumn : 0, DryRun);
else
Penalty += OptimizingLineFormatter(Indenter, Whitespaces, Style, this)
.formatLine(TheLine, Indent, DryRun);
.formatLine(TheLine, NextStartColumn + Indent,
FirstLine ? FirstStartColumn : 0, DryRun);
RangeMinLevel = std::min(RangeMinLevel, TheLine.Level);
} else {
// If no token in the current line is affected, we still need to format
@ -1062,6 +1077,7 @@ UnwrappedLineFormatter::format(const SmallVectorImpl<AnnotatedLine *> &Lines,
// Format the first token.
if (ReformatLeadingWhitespace)
formatFirstToken(TheLine, PreviousLine,
TheLine.First->OriginalColumn,
TheLine.First->OriginalColumn);
else
Whitespaces->addUntouchableToken(*TheLine.First,
@ -1084,12 +1100,14 @@ UnwrappedLineFormatter::format(const SmallVectorImpl<AnnotatedLine *> &Lines,
void UnwrappedLineFormatter::formatFirstToken(const AnnotatedLine &Line,
const AnnotatedLine *PreviousLine,
unsigned Indent) {
unsigned Indent,
unsigned NewlineIndent) {
FormatToken &RootToken = *Line.First;
if (RootToken.is(tok::eof)) {
unsigned Newlines = std::min(RootToken.NewlinesBefore, 1u);
Whitespaces->replaceWhitespace(RootToken, Newlines, /*Spaces=*/0,
/*StartOfTokenColumn=*/0);
unsigned TokenIndent = Newlines ? NewlineIndent : 0;
Whitespaces->replaceWhitespace(RootToken, Newlines, TokenIndent,
TokenIndent);
return;
}
unsigned Newlines =
@ -1104,10 +1122,6 @@ void UnwrappedLineFormatter::formatFirstToken(const AnnotatedLine &Line,
if (RootToken.IsFirst && !RootToken.HasUnescapedNewline)
Newlines = 0;
// Preprocessor directives get indented after the hash, if indented.
if (Line.Type == LT_PreprocessorDirective || Line.Type == LT_ImportStatement)
Indent = 0;
// Remove empty lines after "{".
if (!Style.KeepEmptyLinesAtTheStartOfBlocks && PreviousLine &&
PreviousLine->Last->is(tok::l_brace) &&
@ -1125,6 +1139,13 @@ void UnwrappedLineFormatter::formatFirstToken(const AnnotatedLine &Line,
(!PreviousLine->InPPDirective || !RootToken.HasUnescapedNewline))
Newlines = std::min(1u, Newlines);
if (Newlines)
Indent = NewlineIndent;
// Preprocessor directives get indented after the hash, if indented.
if (Line.Type == LT_PreprocessorDirective || Line.Type == LT_ImportStatement)
Indent = 0;
Whitespaces->replaceWhitespace(RootToken, Newlines, Indent, Indent,
Line.InPPDirective &&
!RootToken.HasUnescapedNewline);

View File

@ -40,13 +40,17 @@ public:
/// \brief Format the current block and return the penalty.
unsigned format(const SmallVectorImpl<AnnotatedLine *> &Lines,
bool DryRun = false, int AdditionalIndent = 0,
bool FixBadIndentation = false);
bool FixBadIndentation = false,
unsigned FirstStartColumn = 0,
unsigned NextStartColumn = 0,
unsigned LastStartColumn = 0);
private:
/// \brief Add a new line and the required indent before the first Token
/// of the \c UnwrappedLine if there was no structural parsing error.
void formatFirstToken(const AnnotatedLine &Line,
const AnnotatedLine *PreviousLine, unsigned Indent);
const AnnotatedLine *PreviousLine, unsigned Indent,
unsigned NewlineIndent);
/// \brief Returns the column limit for a line, taking into account whether we
/// need an escaped newline due to a continued preprocessor directive.

View File

@ -225,6 +225,7 @@ private:
UnwrappedLineParser::UnwrappedLineParser(const FormatStyle &Style,
const AdditionalKeywords &Keywords,
unsigned FirstStartColumn,
ArrayRef<FormatToken *> Tokens,
UnwrappedLineConsumer &Callback)
: Line(new UnwrappedLine), MustBreakBeforeNextToken(false),
@ -232,7 +233,7 @@ UnwrappedLineParser::UnwrappedLineParser(const FormatStyle &Style,
CommentPragmasRegex(Style.CommentPragmas), Tokens(nullptr),
Callback(Callback), AllTokens(Tokens), PPBranchLevel(-1),
IfNdefCondition(nullptr), FoundIncludeGuardStart(false),
IncludeGuardRejected(false) {}
IncludeGuardRejected(false), FirstStartColumn(FirstStartColumn) {}
void UnwrappedLineParser::reset() {
PPBranchLevel = -1;
@ -247,10 +248,12 @@ void UnwrappedLineParser::reset() {
CurrentLines = &Lines;
DeclarationScopeStack.clear();
PPStack.clear();
Line->FirstStartColumn = FirstStartColumn;
}
void UnwrappedLineParser::parse() {
IndexedTokenSource TokenSource(AllTokens);
Line->FirstStartColumn = FirstStartColumn;
do {
DEBUG(llvm::dbgs() << "----\n");
reset();
@ -2193,7 +2196,8 @@ void UnwrappedLineParser::parseJavaScriptEs6ImportExport() {
LLVM_ATTRIBUTE_UNUSED static void printDebugInfo(const UnwrappedLine &Line,
StringRef Prefix = "") {
llvm::dbgs() << Prefix << "Line(" << Line.Level << ")"
llvm::dbgs() << Prefix << "Line(" << Line.Level
<< ", FSC=" << Line.FirstStartColumn << ")"
<< (Line.InPPDirective ? " MACRO" : "") << ": ";
for (std::list<UnwrappedLineNode>::const_iterator I = Line.Tokens.begin(),
E = Line.Tokens.end();
@ -2226,6 +2230,7 @@ void UnwrappedLineParser::addUnwrappedLine() {
CurrentLines->push_back(std::move(*Line));
Line->Tokens.clear();
Line->MatchingOpeningBlockLineIndex = UnwrappedLine::kInvalidIndex;
Line->FirstStartColumn = 0;
if (CurrentLines == &Lines && !PreprocessorDirectives.empty()) {
CurrentLines->append(
std::make_move_iterator(PreprocessorDirectives.begin()),

View File

@ -56,6 +56,8 @@ struct UnwrappedLine {
size_t MatchingOpeningBlockLineIndex;
static const size_t kInvalidIndex = -1;
unsigned FirstStartColumn = 0;
};
class UnwrappedLineConsumer {
@ -71,6 +73,7 @@ class UnwrappedLineParser {
public:
UnwrappedLineParser(const FormatStyle &Style,
const AdditionalKeywords &Keywords,
unsigned FirstStartColumn,
ArrayRef<FormatToken *> Tokens,
UnwrappedLineConsumer &Callback);
@ -249,6 +252,10 @@ private:
FormatToken *IfNdefCondition;
bool FoundIncludeGuardStart;
bool IncludeGuardRejected;
// Contains the first start column where the source begins. This is zero for
// normal source code and may be nonzero when formatting a code fragment that
// does not start at the beginning of the file.
unsigned FirstStartColumn;
friend class ScopedLineState;
friend class CompoundStatementIndenter;

View File

@ -124,7 +124,7 @@ UsingDeclarationsSorter::UsingDeclarationsSorter(const Environment &Env,
const FormatStyle &Style)
: TokenAnalyzer(Env, Style) {}
tooling::Replacements UsingDeclarationsSorter::analyze(
std::pair<tooling::Replacements, unsigned> UsingDeclarationsSorter::analyze(
TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) {
const SourceManager &SourceMgr = Env.getSourceManager();
@ -149,7 +149,7 @@ tooling::Replacements UsingDeclarationsSorter::analyze(
UsingDeclarations.push_back(UsingDeclaration(AnnotatedLines[I], Label));
}
endUsingDeclarationBlock(&UsingDeclarations, SourceMgr, &Fixes);
return Fixes;
return {Fixes, 0};
}
} // namespace format

View File

@ -25,7 +25,7 @@ class UsingDeclarationsSorter : public TokenAnalyzer {
public:
UsingDeclarationsSorter(const Environment &Env, const FormatStyle &Style);
tooling::Replacements
std::pair<tooling::Replacements, unsigned>
analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) override;

View File

@ -67,6 +67,11 @@ void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
/*IsInsideToken=*/false));
}
llvm::Error
WhitespaceManager::addReplacement(const tooling::Replacement &Replacement) {
return Replaces.add(Replacement);
}
void WhitespaceManager::replaceWhitespaceInToken(
const FormatToken &Tok, unsigned Offset, unsigned ReplaceChars,
StringRef PreviousPostfix, StringRef CurrentPrefix, bool InPPDirective,

View File

@ -57,6 +57,8 @@ public:
/// was not called.
void addUntouchableToken(const FormatToken &Tok, bool InPPDirective);
llvm::Error addReplacement(const tooling::Replacement &Replacement);
/// \brief Inserts or replaces whitespace in the middle of a token.
///
/// Inserts \p PreviousPostfix, \p Newlines, \p Spaces and \p CurrentPrefix

View File

@ -10,6 +10,7 @@ add_clang_unittest(FormatTests
FormatTestJava.cpp
FormatTestObjC.cpp
FormatTestProto.cpp
FormatTestRawStrings.cpp
FormatTestSelective.cpp
FormatTestTextProto.cpp
NamespaceEndCommentsFixerTest.cpp

View File

@ -10254,6 +10254,20 @@ TEST_F(FormatTest, ParsesConfiguration) {
" Priority: 1",
IncludeCategories, ExpectedCategories);
CHECK_PARSE("IncludeIsMainRegex: 'abc$'", IncludeIsMainRegex, "abc$");
Style.RawStringFormats.clear();
std::vector<FormatStyle::RawStringFormat> ExpectedRawStringFormats = {
{"pb", FormatStyle::LK_TextProto, "llvm"},
{"cpp", FormatStyle::LK_Cpp, "google"}};
CHECK_PARSE("RawStringFormats:\n"
" - Delimiter: 'pb'\n"
" Language: TextProto\n"
" BasedOnStyle: llvm\n"
" - Delimiter: 'cpp'\n"
" Language: Cpp\n"
" BasedOnStyle: google",
RawStringFormats, ExpectedRawStringFormats);
}
TEST_F(FormatTest, ParsesConfigurationWithLanguages) {

View File

@ -0,0 +1,733 @@
//===- unittest/Format/FormatTestRawStrings.cpp - Formatting unit tests ---===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/Format/Format.h"
#include "../Tooling/ReplacementTest.h"
#include "FormatTestUtils.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/MemoryBuffer.h"
#include "gtest/gtest.h"
#define DEBUG_TYPE "format-test"
using clang::tooling::ReplacementTest;
using clang::tooling::toReplacements;
namespace clang {
namespace format {
namespace {
class FormatTestRawStrings : public ::testing::Test {
protected:
enum StatusCheck { SC_ExpectComplete, SC_ExpectIncomplete, SC_DoNotCheck };
std::string format(llvm::StringRef Code,
const FormatStyle &Style = getLLVMStyle(),
StatusCheck CheckComplete = SC_ExpectComplete) {
DEBUG(llvm::errs() << "---\n");
DEBUG(llvm::errs() << Code << "\n\n");
std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size()));
FormattingAttemptStatus Status;
tooling::Replacements Replaces =
reformat(Style, Code, Ranges, "<stdin>", &Status);
if (CheckComplete != SC_DoNotCheck) {
bool ExpectedCompleteFormat = CheckComplete == SC_ExpectComplete;
EXPECT_EQ(ExpectedCompleteFormat, Status.FormatComplete)
<< Code << "\n\n";
}
ReplacementCount = Replaces.size();
auto Result = applyAllReplacements(Code, Replaces);
EXPECT_TRUE(static_cast<bool>(Result));
DEBUG(llvm::errs() << "\n" << *Result << "\n\n");
return *Result;
}
FormatStyle getStyleWithColumns(FormatStyle Style, unsigned ColumnLimit) {
Style.ColumnLimit = ColumnLimit;
return Style;
}
FormatStyle getLLVMStyleWithColumns(unsigned ColumnLimit) {
return getStyleWithColumns(getLLVMStyle(), ColumnLimit);
}
int ReplacementCount;
FormatStyle getRawStringPbStyleWithColumns(unsigned ColumnLimit) {
FormatStyle Style = getLLVMStyle();
Style.ColumnLimit = ColumnLimit;
Style.RawStringFormats = {{/*Delimiter=*/"pb",
/*Kind=*/FormatStyle::LK_TextProto,
/*BasedOnStyle=*/"google"}};
return Style;
}
FormatStyle getRawStringLLVMCppStyleBasedOn(std::string BasedOnStyle) {
FormatStyle Style = getLLVMStyle();
Style.RawStringFormats = {{/*Delimiter=*/"cpp",
/*Kind=*/FormatStyle::LK_Cpp, BasedOnStyle}};
return Style;
}
FormatStyle getRawStringGoogleCppStyleBasedOn(std::string BasedOnStyle) {
FormatStyle Style = getGoogleStyle(FormatStyle::LK_Cpp);
Style.RawStringFormats = {{/*Delimiter=*/"cpp",
/*Kind=*/FormatStyle::LK_Cpp, BasedOnStyle}};
return Style;
}
// Gcc 4.8 doesn't support raw string literals in macros, which breaks some
// build bots. We use this function instead.
void expect_eq(const std::string Expected, const std::string Actual) {
EXPECT_EQ(Expected, Actual);
}
};
TEST_F(FormatTestRawStrings, ReformatsAccordingToBaseStyle) {
// llvm style puts '*' on the right.
// google style puts '*' on the left.
// Use the llvm style if the raw string style has no BasedOnStyle.
expect_eq(R"test(int *i = R"cpp(int *p = nullptr;)cpp")test",
format(R"test(int * i = R"cpp(int * p = nullptr;)cpp")test",
getRawStringLLVMCppStyleBasedOn("")));
// Use the google style if the raw string style has BasedOnStyle=google.
expect_eq(R"test(int *i = R"cpp(int* p = nullptr;)cpp")test",
format(R"test(int * i = R"cpp(int * p = nullptr;)cpp")test",
getRawStringLLVMCppStyleBasedOn("google")));
// Use the llvm style if the raw string style has no BasedOnStyle=llvm.
expect_eq(R"test(int* i = R"cpp(int *p = nullptr;)cpp")test",
format(R"test(int * i = R"cpp(int * p = nullptr;)cpp")test",
getRawStringGoogleCppStyleBasedOn("llvm")));
}
TEST_F(FormatTestRawStrings, MatchesDelimitersCaseSensitively) {
// Don't touch the 'PB' raw string, format the 'pb' raw string.
expect_eq(R"test(
s = R"PB(item:1)PB";
t = R"pb(item: 1)pb";)test",
format(R"test(
s = R"PB(item:1)PB";
t = R"pb(item:1)pb";)test",
getRawStringPbStyleWithColumns(40)));
FormatStyle MixedStyle = getLLVMStyle();
MixedStyle.RawStringFormats = {
{/*Delimiter=*/"cpp", /*Kind=*/FormatStyle::LK_Cpp,
/*BasedOnStyle=*/"llvm"},
{/*Delimiter=*/"CPP", /*Kind=*/FormatStyle::LK_Cpp,
/*BasedOnStyle=*/"google"}};
// Format the 'cpp' raw string with '*' on the right.
// Format the 'CPP' raw string with '*' on the left.
// Do not format the 'Cpp' raw string.
// Do not format non-raw strings.
expect_eq(R"test(
a = R"cpp(int *i = 0;)cpp";
b = R"CPP(int* j = 0;)CPP";
c = R"Cpp(int * k = 0;)Cpp";
d = R"cpp(int * k = 0;)Cpp";)test",
format(R"test(
a = R"cpp(int * i = 0;)cpp";
b = R"CPP(int * j = 0;)CPP";
c = R"Cpp(int * k = 0;)Cpp";
d = R"cpp(int * k = 0;)Cpp";)test",
MixedStyle));
}
TEST_F(FormatTestRawStrings, ReformatsShortRawStringsOnSingleLine) {
expect_eq(
R"test(P p = TP(R"pb()pb");)test",
format(
R"test(P p = TP(R"pb( )pb");)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(
R"test(P p = TP(R"pb(item_1: 1)pb");)test",
format(
R"test(P p = TP(R"pb(item_1:1)pb");)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(
R"test(P p = TP(R"pb(item_1: 1)pb");)test",
format(
R"test(P p = TP(R"pb( item_1 : 1 )pb");)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(
R"test(P p = TP(R"pb(item_1: 1 item_2: 2)pb");)test",
format(
R"test(P p = TP(R"pb(item_1:1 item_2:2)pb");)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(
R"test(P p = TP(R"pb(item_1 <1> item_2: {2})pb");)test",
format(
R"test(P p = TP(R"pb(item_1<1> item_2:{2})pb");)test",
getRawStringPbStyleWithColumns(40)));
// Merge two short lines into one.
expect_eq(R"test(
std::string s = R"pb(
item_1: 1 item_2: 2
)pb";
)test",
format(R"test(
std::string s = R"pb(
item_1:1
item_2:2
)pb";
)test",
getRawStringPbStyleWithColumns(40)));
}
TEST_F(FormatTestRawStrings, BreaksRawStringsExceedingColumnLimit) {
expect_eq(R"test(
P p = TPPPPPPPPPPPPPPP(
R"pb(item_1: 1, item_2: 2)pb");)test",
format(R"test(
P p = TPPPPPPPPPPPPPPP(R"pb(item_1: 1, item_2: 2)pb");)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
P p =
TPPPPPPPPPPPPPPP(
R"pb(item_1: 1,
item_2: 2,
item_3: 3)pb");)test",
format(R"test(
P p = TPPPPPPPPPPPPPPP(R"pb(item_1: 1, item_2: 2, item_3: 3)pb");)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
P p = TP(R"pb(item_1 <1>
item_2: <2>
item_3 {})pb");)test",
format(R"test(
P p = TP(R"pb(item_1<1> item_2:<2> item_3{ })pb");)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(
R"test(
P p = TP(R"pb(item_1: 1,
item_2: 2,
item_3: 3,
item_4: 4)pb");)test",
format(
R"test(
P p = TP(R"pb(item_1: 1, item_2: 2, item_3: 3, item_4: 4)pb");)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
P p = TPPPPPPPPPPPPPPP(
R"pb(item_1 <1>,
item_2: {2},
item_3: <3>,
item_4: {4})pb");)test",
format(R"test(
P p = TPPPPPPPPPPPPPPP(R"pb(item_1<1>, item_2: {2}, item_3: <3>, item_4:{4})pb");)test",
getRawStringPbStyleWithColumns(40)));
// Breaks before a short raw string exceeding the column limit.
expect_eq(R"test(
FFFFFFFFFFFFFFFFFFFFFFFFFFF(
R"pb(key: 1)pb");
P p = TPPPPPPPPPPPPPPPPPPPP(
R"pb(key: 2)pb");
auto TPPPPPPPPPPPPPPPPPPPP =
R"pb(key: 3)pb";
P p = TPPPPPPPPPPPPPPPPPPPP(
R"pb(i: 1, j: 2)pb");
int f(string s) {
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF(
R"pb(key: 1)pb");
P p = TPPPPPPPPPPPPPPPPPPPP(
R"pb(key: 2)pb");
auto TPPPPPPPPPPPPPPPPPPPP =
R"pb(key: 3)pb";
if (s.empty())
P p = TPPPPPPPPPPPPPPPPPPPP(
R"pb(i: 1, j: 2)pb");
}
)test",
format(R"test(
FFFFFFFFFFFFFFFFFFFFFFFFFFF(R"pb(key:1)pb");
P p = TPPPPPPPPPPPPPPPPPPPP(R"pb(key:2)pb");
auto TPPPPPPPPPPPPPPPPPPPP = R"pb(key:3)pb";
P p = TPPPPPPPPPPPPPPPPPPPP(R"pb(i: 1, j:2)pb");
int f(string s) {
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF(R"pb(key:1)pb");
P p = TPPPPPPPPPPPPPPPPPPPP(R"pb(key:2)pb");
auto TPPPPPPPPPPPPPPPPPPPP = R"pb(key:3)pb";
if (s.empty())
P p = TPPPPPPPPPPPPPPPPPPPP(R"pb(i: 1, j:2)pb");
}
)test",
getRawStringPbStyleWithColumns(40)));
}
TEST_F(FormatTestRawStrings, FormatsRawStringArguments) {
expect_eq(R"test(
P p = TP(R"pb(key {1})pb", param_2);)test",
format(R"test(
P p = TP(R"pb(key{1})pb",param_2);)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
PPPPPPPPPPPPP(R"pb(keykeyk)pb",
param_2);)test",
format(R"test(
PPPPPPPPPPPPP(R"pb(keykeyk)pb", param_2);)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
P p =
TP(R"pb(item: {i: 1, s: 's'}
item: {i: 2, s: 't'})pb");)test",
format(R"test(
P p = TP(R"pb(item: {i: 1, s: 's'} item: {i: 2, s: 't'})pb");)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
FFFFFFFFFFFFFFFFFFF(
R"pb(key: "value")pb",
R"pb(key2: "value")pb");)test",
format(R"test(
FFFFFFFFFFFFFFFFFFF(R"pb(key: "value")pb", R"pb(key2: "value")pb");)test",
getRawStringPbStyleWithColumns(40)));
// Formats the first out of two arguments.
expect_eq(R"test(
FFFFFFFF(R"pb(key: 1)pb", argument2);
struct S {
const s =
f(R"pb(key: 1)pb", argument2);
void f() {
if (gol)
return g(R"pb(key: 1)pb",
132789237);
return g(R"pb(key: 1)pb", "172893");
}
};)test",
format(R"test(
FFFFFFFF(R"pb(key:1)pb", argument2);
struct S {
const s = f(R"pb(key:1)pb", argument2);
void f() {
if (gol)
return g(R"pb(key:1)pb", 132789237);
return g(R"pb(key:1)pb", "172893");
}
};)test",
getRawStringPbStyleWithColumns(40)));
// Formats the second out of two arguments.
expect_eq(R"test(
FFFFFFFF(argument1, R"pb(key: 2)pb");
struct S {
const s =
f(argument1, R"pb(key: 2)pb");
void f() {
if (gol)
return g(12784137,
R"pb(key: 2)pb");
return g(17283122, R"pb(key: 2)pb");
}
};)test",
format(R"test(
FFFFFFFF(argument1, R"pb(key:2)pb");
struct S {
const s = f(argument1, R"pb(key:2)pb");
void f() {
if (gol)
return g(12784137, R"pb(key:2)pb");
return g(17283122, R"pb(key:2)pb");
}
};)test",
getRawStringPbStyleWithColumns(40)));
// Formats two short raw string arguments.
expect_eq(R"test(
FFFFF(R"pb(key: 1)pb", R"pb(key: 2)pb");)test",
format(R"test(
FFFFF(R"pb(key:1)pb", R"pb(key:2)pb");)test",
getRawStringPbStyleWithColumns(40)));
// TODO(krasimir): The original source code fits on one line, so the
// non-optimizing formatter is chosen. But after the formatting in protos is
// made, the code doesn't fit on one line anymore and further formatting
// splits it.
//
// Should we disable raw string formatting for the non-optimizing formatter?
expect_eq(R"test(
FFFFFFF(R"pb(key: 1)pb", R"pb(key: 2)pb");)test",
format(R"test(
FFFFFFF(R"pb(key:1)pb", R"pb(key:2)pb");)test",
getRawStringPbStyleWithColumns(40)));
// Formats two short raw string arguments, puts second on newline.
expect_eq(R"test(
FFFFFFFF(R"pb(key: 1)pb",
R"pb(key: 2)pb");)test",
format(R"test(
FFFFFFFF(R"pb(key:1)pb", R"pb(key:2)pb");)test",
getRawStringPbStyleWithColumns(40)));
// Formats both arguments.
expect_eq(R"test(
FFFFFFFF(R"pb(key: 1)pb",
R"pb(key: 2)pb");
struct S {
const s = f(R"pb(key: 1)pb",
R"pb(key: 2)pb");
void f() {
if (gol)
return g(R"pb(key: 1)pb",
R"pb(key: 2)pb");
return g(R"pb(k1)pb", R"pb(k2)pb");
}
};)test",
format(R"test(
FFFFFFFF(R"pb(key:1)pb", R"pb(key:2)pb");
struct S {
const s = f(R"pb(key:1)pb", R"pb(key:2)pb");
void f() {
if (gol)
return g(R"pb(key:1)pb", R"pb(key:2)pb");
return g(R"pb( k1 )pb", R"pb( k2 )pb");
}
};)test",
getRawStringPbStyleWithColumns(40)));
}
TEST_F(FormatTestRawStrings, RawStringStartingWithNewlines) {
expect_eq(R"test(
std::string s = R"pb(
item_1: 1
)pb";
)test",
format(R"test(
std::string s = R"pb(
item_1:1
)pb";
)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
std::string s = R"pb(
item_1: 1
)pb";
)test",
format(R"test(
std::string s = R"pb(
item_1:1
)pb";
)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
std::string s = R"pb(
item_1: 1
)pb";
)test",
format(R"test(
std::string s = R"pb(
item_1:1
)pb";
)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
std::string s = R"pb(
item_1: 1,
item_2: 2
)pb";
)test",
format(R"test(
std::string s = R"pb(
item_1:1, item_2:2
)pb";
)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
std::string s = R"pb(
book {
title: "Alice's Adventures"
author: "Lewis Caroll"
}
book {
title: "Peter Pan"
author: "J. M. Barrie"
}
)pb";
)test",
format(R"test(
std::string s = R"pb(
book { title: "Alice's Adventures" author: "Lewis Caroll" }
book { title: "Peter Pan" author: "J. M. Barrie" }
)pb";
)test",
getRawStringPbStyleWithColumns(40)));
}
TEST_F(FormatTestRawStrings, BreaksBeforeRawStrings) {
expect_eq(R"test(
ASSERT_TRUE(
ParseFromString(R"pb(item_1: 1)pb"),
ptr);)test",
format(R"test(
ASSERT_TRUE(ParseFromString(R"pb(item_1: 1)pb"), ptr);)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
ASSERT_TRUE(toolong::ParseFromString(
R"pb(item_1: 1)pb"),
ptr);)test",
format(R"test(
ASSERT_TRUE(toolong::ParseFromString(R"pb(item_1: 1)pb"), ptr);)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
ASSERT_TRUE(ParseFromString(
R"pb(item_1: 1,
item_2: 2)pb"),
ptr);)test",
format(R"test(
ASSERT_TRUE(ParseFromString(R"pb(item_1: 1, item_2: 2)pb"), ptr);)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
ASSERT_TRUE(
ParseFromString(
R"pb(item_1: 1 item_2: 2)pb"),
ptr);)test",
format(R"test(
ASSERT_TRUE(ParseFromString(R"pb(item_1: 1 item_2: 2)pb"), ptr);)test",
getRawStringPbStyleWithColumns(40)));
}
TEST_F(FormatTestRawStrings, RawStringsInOperands) {
// Formats the raw string first operand of a binary operator expression.
expect_eq(R"test(auto S = R"pb(item_1: 1)pb" + rest;)test",
format(R"test(auto S = R"pb(item_1:1)pb" + rest;)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S = R"pb(item_1: 1, item_2: 2)pb" +
rest;)test",
format(R"test(
auto S = R"pb(item_1:1,item_2:2)pb"+rest;)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S =
R"pb(item_1: 1 item_2: 2)pb" + rest;)test",
format(R"test(
auto S = R"pb(item_1:1 item_2:2)pb"+rest;)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S = R"pb(item_1: 1,
item_2: 2,
item_3: 3)pb" + rest;)test",
format(R"test(
auto S = R"pb(item_1:1,item_2:2,item_3:3)pb"+rest;)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S = R"pb(item_1: 1,
item_2: 2,
item_3: 3)pb" +
longlongrest;)test",
format(R"test(
auto S = R"pb(item_1:1,item_2:2,item_3:3)pb"+longlongrest;)test",
getRawStringPbStyleWithColumns(40)));
// Formats the raw string second operand of a binary operator expression.
expect_eq(R"test(auto S = first + R"pb(item_1: 1)pb";)test",
format(R"test(auto S = first + R"pb(item_1:1)pb";)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S = first + R"pb(item_1: 1,
item_2: 2)pb";)test",
format(R"test(
auto S = first+R"pb(item_1:1,item_2:2)pb";)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S = first + R"pb(item_1: 1
item_2: 2)pb";)test",
format(R"test(
auto S = first+R"pb(item_1:1 item_2:2)pb";)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S = R"pb(item_1: 1,
item_2: 2,
item_3: 3)pb" + rest;)test",
format(R"test(
auto S = R"pb(item_1:1,item_2:2,item_3:3)pb"+rest;)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S = R"pb(item_1: 1,
item_2: 2,
item_3: 3)pb" +
longlongrest;)test",
format(R"test(
auto S = R"pb(item_1:1,item_2:2,item_3:3)pb"+longlongrest;)test",
getRawStringPbStyleWithColumns(40)));
// Formats the raw string operands in expressions.
expect_eq(R"test(
auto S = R"pb(item_1: 1)pb" +
R"pb(item_2: 2)pb";
)test",
format(R"test(
auto S=R"pb(item_1:1)pb"+R"pb(item_2:2)pb";
)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S = R"pb(item_1: 1)pb" +
R"pb(item_2: 2)pb" +
R"pb(item_3: 3)pb";
)test",
format(R"test(
auto S=R"pb(item_1:1)pb"+R"pb(item_2:2)pb"+R"pb(item_3:3)pb";
)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S = (count < 3)
? R"pb(item_1: 1)pb"
: R"pb(item_2: 2)pb";
)test",
format(R"test(
auto S=(count<3)?R"pb(item_1:1)pb":R"pb(item_2:2)pb";
)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S =
(count < 3)
? R"pb(item_1: 1, item_2: 2)pb"
: R"pb(item_3: 3)pb";
)test",
format(R"test(
auto S=(count<3)?R"pb(item_1:1,item_2:2)pb":R"pb(item_3:3)pb";
)test",
getRawStringPbStyleWithColumns(40)));
expect_eq(R"test(
auto S =
(count < 3)
? R"pb(item_1: 1)pb"
: R"pb(item_2: 2, item_3: 3)pb";
)test",
format(R"test(
auto S=(count<3)?R"pb(item_1:1)pb":R"pb(item_2:2,item_3:3)pb";
)test",
getRawStringPbStyleWithColumns(40)));
}
TEST_F(FormatTestRawStrings, PrefixAndSuffixAlignment) {
// Keep the suffix at the end of line if not on newline.
expect_eq(R"test(
int s() {
auto S = PTP(
R"pb(
item_1: 1,
item_2: 2)pb");
})test",
format(R"test(
int s() {
auto S = PTP(
R"pb(
item_1: 1,
item_2: 2)pb");
})test",
getRawStringPbStyleWithColumns(20)));
// Align the suffix with the surrounding FirstIndent if the prefix is not on
// a line of its own.
expect_eq(R"test(
int s() {
auto S = PTP(
R"pb(
item_1: 1,
item_2: 2
)pb");
})test",
format(R"test(
int s() {
auto S = PTP(R"pb(
item_1: 1,
item_2: 2
)pb");
})test",
getRawStringPbStyleWithColumns(20)));
// Align the prefix with the suffix if both the prefix and suffix are on a
// line of their own.
expect_eq(R"test(
int s() {
auto S = PTP(
R"pb(
item_1: 1,
item_2: 2,
)pb");
})test",
format(R"test(
int s() {
auto S = PTP(
R"pb(
item_1: 1,
item_2: 2,
)pb");
})test",
getRawStringPbStyleWithColumns(20)));
}
TEST_F(FormatTestRawStrings, EstimatesPenalty) {
// The penalty for characters exceeding the column limit in the raw string
// forces 'hh' to be put on a newline.
expect_eq(R"test(
ff(gggggg,
hh(R"pb(key {
i1: k1
i2: k2
})pb"));
)test",
format(R"test(
ff(gggggg, hh(R"pb(key {
i1: k1
i2: k2
})pb"));
)test",
getRawStringPbStyleWithColumns(20)));
}
TEST_F(FormatTestRawStrings, DontFormatNonRawStrings) {
expect_eq(R"test(a = R"pb(key:value)";)test",
format(R"test(a = R"pb(key:value)";)test",
getRawStringPbStyleWithColumns(20)));
}
} // end namespace
} // end namespace format
} // end namespace clang