summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--clang/docs/ClangFormat.rst4
-rw-r--r--clang/docs/ClangFormatStyleOptions.rst14
-rw-r--r--clang/docs/ReleaseNotes.rst3
-rw-r--r--clang/include/clang/Format/Format.h5
-rw-r--r--clang/lib/Format/ContinuationIndenter.cpp12
-rw-r--r--clang/lib/Format/Format.cpp35
-rw-r--r--clang/lib/Format/FormatToken.h136
-rw-r--r--clang/lib/Format/FormatTokenLexer.cpp109
-rw-r--r--clang/lib/Format/FormatTokenLexer.h4
-rw-r--r--clang/lib/Format/TokenAnnotator.cpp57
-rw-r--r--clang/lib/Format/UnwrappedLineFormatter.cpp6
-rw-r--r--clang/lib/Format/UnwrappedLineParser.cpp6
-rw-r--r--clang/tools/clang-format/ClangFormat.cpp2
-rw-r--r--clang/unittests/Format/CMakeLists.txt1
-rw-r--r--clang/unittests/Format/FormatTestCSharp.cpp184
15 files changed, 559 insertions, 19 deletions
diff --git a/clang/docs/ClangFormat.rst b/clang/docs/ClangFormat.rst
index f2228c57505..4a448c07af5 100644
--- a/clang/docs/ClangFormat.rst
+++ b/clang/docs/ClangFormat.rst
@@ -11,12 +11,12 @@ Standalone Tool
===============
:program:`clang-format` is located in `clang/tools/clang-format` and can be used
-to format C/C++/Java/JavaScript/Objective-C/Protobuf code.
+to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.
.. code-block:: console
$ clang-format -help
- OVERVIEW: A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf code.
+ OVERVIEW: A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.
If no arguments are specified, it formats the code from standard input
and writes the result to the standard output.
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 7cd11c7aa75..1a783fd4306 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -7,8 +7,8 @@ supported by :doc:`LibFormat` and :doc:`ClangFormat`.
When using :program:`clang-format` command line utility or
``clang::format::reformat(...)`` functions from code, one can either use one of
-the predefined styles (LLVM, Google, Chromium, Mozilla, WebKit) or create a
-custom style by configuring specific style options.
+the predefined styles (LLVM, Google, Chromium, Mozilla, WebKit, Microsoft) or
+create a custom style by configuring specific style options.
Configuring Style with clang-format
@@ -68,6 +68,10 @@ An example of a configuration file for multiple languages:
Language: Proto
# Don't format .proto files.
DisableFormat: true
+ ---
+ Language: CSharp
+ # Use 100 columns for C#.
+ ColumnLimit: 100
...
An easy way to get a valid ``.clang-format`` file containing all configuration
@@ -144,6 +148,9 @@ the configuration (without a prefix: ``Auto``).
* ``WebKit``
A style complying with `WebKit's style guide
<https://www.webkit.org/coding/coding-style.html>`_
+ * ``Microsoft``
+ A style complying with `Microsoft's style guide
+ <https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference?view=vs-2017>`_
.. START_FORMAT_STYLE_OPTIONS
@@ -1552,6 +1559,9 @@ the configuration (without a prefix: ``Auto``).
* ``LK_Cpp`` (in configuration: ``Cpp``)
Should be used for C, C++.
+ * ``LK_CSharp`` (in configuration: ``CSharp``)
+ Should be used for C#.
+
* ``LK_Java`` (in configuration: ``Java``)
Should be used for Java.
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 2f5c82e352a..7664a2c878e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -165,7 +165,8 @@ release of Clang. Users of the build system should adjust accordingly.
AST Matchers
------------
-- ...
+- Add language support for clang-formatting C# files
+- Add Microsoft coding style to encapsulate default C# formatting style
clang-format
------------
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 13e36ea7cff..34a511f6543 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -1258,6 +1258,8 @@ struct FormatStyle {
LK_None,
/// Should be used for C, C++.
LK_Cpp,
+ /// Should be used for C#.
+ LK_CSharp,
/// Should be used for Java.
LK_Java,
/// Should be used for JavaScript.
@@ -1274,6 +1276,7 @@ struct FormatStyle {
LK_TextProto
};
bool isCpp() const { return Language == LK_Cpp || Language == LK_ObjC; }
+ bool isCSharp() const { return Language == LK_CSharp; }
/// Language, this format style is targeted at.
LanguageKind Language;
@@ -2090,6 +2093,8 @@ inline StringRef getLanguageName(FormatStyle::LanguageKind Language) {
switch (Language) {
case FormatStyle::LK_Cpp:
return "C++";
+ case FormatStyle::LK_CSharp:
+ return "CSharp";
case FormatStyle::LK_ObjC:
return "Objective-C";
case FormatStyle::LK_Java:
diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp
index a82c8ce5b57..a2a13d4d465 100644
--- a/clang/lib/Format/ContinuationIndenter.cpp
+++ b/clang/lib/Format/ContinuationIndenter.cpp
@@ -418,7 +418,8 @@ bool ContinuationIndenter::mustBreak(const LineState &State) {
if (Style.AlwaysBreakBeforeMultilineStrings &&
(NewLineColumn == State.FirstIndent + Style.ContinuationIndentWidth ||
Previous.is(tok::comma) || Current.NestingLevel < 2) &&
- !Previous.isOneOf(tok::kw_return, tok::lessless, tok::at) &&
+ !Previous.isOneOf(tok::kw_return, tok::lessless, tok::at,
+ Keywords.kw_dollar) &&
!Previous.isOneOf(TT_InlineASMColon, TT_ConditionalExpr) &&
nextIsMultilineString(State))
return true;
@@ -1158,6 +1159,8 @@ unsigned ContinuationIndenter::moveStateToNextToken(LineState &State,
if (Current.is(TT_ObjCStringLiteral) && State.StartOfStringLiteral == 0)
State.StartOfStringLiteral = State.Column + 1;
+ if (Current.is(TT_CSharpStringLiteral) && State.StartOfStringLiteral == 0)
+ State.StartOfStringLiteral = State.Column + 1;
else if (Current.isStringLiteral() && State.StartOfStringLiteral == 0)
State.StartOfStringLiteral = State.Column;
else if (!Current.isOneOf(tok::comment, tok::identifier, tok::hash) &&
@@ -1711,10 +1714,11 @@ ContinuationIndenter::createBreakableToken(const FormatToken &Current,
LineState &State, bool AllowBreak) {
unsigned StartColumn = State.Column - Current.ColumnWidth;
if (Current.isStringLiteral()) {
- // FIXME: String literal breaking is currently disabled for Java and JS, as
- // it requires strings to be merged using "+" which we don't support.
+ // FIXME: String literal breaking is currently disabled for C#,Java and
+ // JavaScript, as it requires strings to be merged using "+" which we
+ // don't support.
if (Style.Language == FormatStyle::LK_Java ||
- Style.Language == FormatStyle::LK_JavaScript ||
+ Style.Language == FormatStyle::LK_JavaScript || Style.isCSharp() ||
!Style.BreakStringLiterals || !AllowBreak)
return nullptr;
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 5e8a2497fea..5fe118a7161 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -61,6 +61,7 @@ template <> struct ScalarEnumerationTraits<FormatStyle::LanguageKind> {
IO.enumCase(Value, "Proto", FormatStyle::LK_Proto);
IO.enumCase(Value, "TableGen", FormatStyle::LK_TableGen);
IO.enumCase(Value, "TextProto", FormatStyle::LK_TextProto);
+ IO.enumCase(Value, "CSharp", FormatStyle::LK_CSharp);
}
};
@@ -287,8 +288,8 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("Language", Style.Language);
if (IO.outputting()) {
- StringRef StylesArray[] = {"LLVM", "Google", "Chromium",
- "Mozilla", "WebKit", "GNU"};
+ StringRef StylesArray[] = {"LLVM", "Google", "Chromium", "Mozilla",
+ "WebKit", "GNU", "Microsoft"};
ArrayRef<StringRef> Styles(StylesArray);
for (size_t i = 0, e = Styles.size(); i < e; ++i) {
StringRef StyleName(Styles[i]);
@@ -951,6 +952,32 @@ FormatStyle getGNUStyle() {
return Style;
}
+FormatStyle getMicrosoftStyle(FormatStyle::LanguageKind Language) {
+ FormatStyle Style = getLLVMStyle();
+ Style.ColumnLimit = 120;
+ Style.TabWidth = 4;
+ Style.IndentWidth = 4;
+ Style.UseTab = FormatStyle::UT_Never;
+ Style.BreakBeforeBraces = FormatStyle::BS_Custom;
+ Style.BraceWrapping.AfterClass = true;
+ Style.BraceWrapping.AfterControlStatement = true;
+ Style.BraceWrapping.AfterEnum = true;
+ Style.BraceWrapping.AfterFunction = true;
+ Style.BraceWrapping.AfterNamespace = true;
+ Style.BraceWrapping.AfterObjCDeclaration = true;
+ Style.BraceWrapping.AfterStruct = true;
+ Style.BraceWrapping.AfterExternBlock = true;
+ Style.BraceWrapping.BeforeCatch = true;
+ Style.BraceWrapping.BeforeElse = true;
+ Style.PenaltyReturnTypeOnItsOwnLine = 1000;
+ Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None;
+ Style.AllowShortBlocksOnASingleLine = false;
+ Style.AllowShortCaseLabelsOnASingleLine = false;
+ Style.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
+ Style.AllowShortLoopsOnASingleLine = false;
+ return Style;
+}
+
FormatStyle getNoStyle() {
FormatStyle NoStyle = getLLVMStyle();
NoStyle.DisableFormat = true;
@@ -973,6 +1000,8 @@ bool getPredefinedStyle(StringRef Name, FormatStyle::LanguageKind Language,
*Style = getWebKitStyle();
} else if (Name.equals_lower("gnu")) {
*Style = getGNUStyle();
+ } else if (Name.equals_lower("microsoft")) {
+ *Style = getMicrosoftStyle(Language);
} else if (Name.equals_lower("none")) {
*Style = getNoStyle();
} else {
@@ -2323,6 +2352,8 @@ static FormatStyle::LanguageKind getLanguageByFileName(StringRef FileName) {
return FormatStyle::LK_TextProto;
if (FileName.endswith_lower(".td"))
return FormatStyle::LK_TableGen;
+ if (FileName.endswith_lower(".cs"))
+ return FormatStyle::LK_CSharp;
return FormatStyle::LK_Cpp;
}
diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index a5daf8aed46..461e0704a3f 100644
--- a/clang/lib/Format/FormatToken.h
+++ b/clang/lib/Format/FormatToken.h
@@ -96,6 +96,8 @@ namespace format {
TYPE(TrailingReturnArrow) \
TYPE(TrailingUnaryOperator) \
TYPE(UnaryOperator) \
+ TYPE(CSharpStringLiteral) \
+ TYPE(CSharpNullCoalescing) \
TYPE(Unknown)
enum TokenType {
@@ -723,7 +725,36 @@ struct AdditionalKeywords {
kw_slots = &IdentTable.get("slots");
kw_qslots = &IdentTable.get("Q_SLOTS");
- // Keep this at the end of the constructor to make sure everything here is
+ // C# keywords
+ kw_dollar = &IdentTable.get("dollar");
+ kw_base = &IdentTable.get("base");
+ kw_byte = &IdentTable.get("byte");
+ kw_checked = &IdentTable.get("checked");
+ kw_decimal = &IdentTable.get("decimal");
+ kw_delegate = &IdentTable.get("delegate");
+ kw_event = &IdentTable.get("event");
+ kw_fixed = &IdentTable.get("fixed");
+ kw_foreach = &IdentTable.get("foreach");
+ kw_implicit = &IdentTable.get("implicit");
+ kw_internal = &IdentTable.get("internal");
+ kw_lock = &IdentTable.get("lock");
+ kw_null = &IdentTable.get("null");
+ kw_object = &IdentTable.get("object");
+ kw_out = &IdentTable.get("out");
+ kw_params = &IdentTable.get("params");
+ kw_ref = &IdentTable.get("ref");
+ kw_string = &IdentTable.get("string");
+ kw_stackalloc = &IdentTable.get("stackalloc");
+ kw_sbyte = &IdentTable.get("sbyte");
+ kw_sealed = &IdentTable.get("sealed");
+ kw_uint = &IdentTable.get("uint");
+ kw_ulong = &IdentTable.get("ulong");
+ kw_unchecked = &IdentTable.get("unchecked");
+ kw_unsafe = &IdentTable.get("unsafe");
+ kw_ushort = &IdentTable.get("ushort");
+
+ // Keep this at the end of the constructor to make sure everything here
+ // is
// already initialized.
JsExtraKeywords = std::unordered_set<IdentifierInfo *>(
{kw_as, kw_async, kw_await, kw_declare, kw_finally, kw_from,
@@ -731,6 +762,19 @@ struct AdditionalKeywords {
kw_set, kw_type, kw_typeof, kw_var, kw_yield,
// Keywords from the Java section.
kw_abstract, kw_extends, kw_implements, kw_instanceof, kw_interface});
+
+ CSharpExtraKeywords = std::unordered_set<IdentifierInfo *>(
+ {kw_base, kw_byte, kw_checked, kw_decimal, kw_delegate, kw_event,
+ kw_fixed, kw_foreach, kw_implicit, kw_in, kw_interface, kw_internal,
+ kw_is, kw_lock, kw_null, kw_object, kw_out, kw_override, kw_params,
+ kw_readonly, kw_ref, kw_string, kw_stackalloc, kw_sbyte, kw_sealed,
+ kw_uint, kw_ulong, kw_unchecked, kw_unsafe, kw_ushort,
+ // Keywords from the JavaScript section.
+ kw_as, kw_async, kw_await, kw_declare, kw_finally, kw_from,
+ kw_function, kw_get, kw_import, kw_is, kw_let, kw_module, kw_readonly,
+ kw_set, kw_type, kw_typeof, kw_var, kw_yield,
+ // Keywords from the Java section.
+ kw_abstract, kw_extends, kw_implements, kw_instanceof, kw_interface});
}
// Context sensitive keywords.
@@ -796,6 +840,37 @@ struct AdditionalKeywords {
IdentifierInfo *kw_slots;
IdentifierInfo *kw_qslots;
+ // C# keywords
+ IdentifierInfo *kw_dollar;
+ IdentifierInfo *kw_base;
+ IdentifierInfo *kw_byte;
+ IdentifierInfo *kw_checked;
+ IdentifierInfo *kw_decimal;
+ IdentifierInfo *kw_delegate;
+ IdentifierInfo *kw_event;
+ IdentifierInfo *kw_fixed;
+ IdentifierInfo *kw_foreach;
+ IdentifierInfo *kw_implicit;
+ IdentifierInfo *kw_internal;
+
+ IdentifierInfo *kw_lock;
+ IdentifierInfo *kw_null;
+ IdentifierInfo *kw_object;
+ IdentifierInfo *kw_out;
+
+ IdentifierInfo *kw_params;
+
+ IdentifierInfo *kw_ref;
+ IdentifierInfo *kw_string;
+ IdentifierInfo *kw_stackalloc;
+ IdentifierInfo *kw_sbyte;
+ IdentifierInfo *kw_sealed;
+ IdentifierInfo *kw_uint;
+ IdentifierInfo *kw_ulong;
+ IdentifierInfo *kw_unchecked;
+ IdentifierInfo *kw_unsafe;
+ IdentifierInfo *kw_ushort;
+
/// Returns \c true if \p Tok is a true JavaScript identifier, returns
/// \c false if it is a keyword or a pseudo keyword.
bool IsJavaScriptIdentifier(const FormatToken &Tok) const {
@@ -804,9 +879,68 @@ struct AdditionalKeywords {
JsExtraKeywords.end();
}
+ /// Returns \c true if \p Tok is a C# keyword, returns
+ /// \c false if it is a anything else.
+ bool isCSharpKeyword(const FormatToken &Tok) const {
+ switch (Tok.Tok.getKind()) {
+ case tok::kw_bool:
+ case tok::kw_break:
+ case tok::kw_case:
+ case tok::kw_catch:
+ case tok::kw_char:
+ case tok::kw_class:
+ case tok::kw_const:
+ case tok::kw_continue:
+ case tok::kw_default:
+ case tok::kw_do:
+ case tok::kw_double:
+ case tok::kw_else:
+ case tok::kw_enum:
+ case tok::kw_explicit:
+ case tok::kw_extern:
+ case tok::kw_false:
+ case tok::kw_float:
+ case tok::kw_for:
+ case tok::kw_goto:
+ case tok::kw_if:
+ case tok::kw_int:
+ case tok::kw_long:
+ case tok::kw_namespace:
+ case tok::kw_new:
+ case tok::kw_operator:
+ case tok::kw_private:
+ case tok::kw_protected:
+ case tok::kw_public:
+ case tok::kw_return:
+ case tok::kw_short:
+ case tok::kw_sizeof:
+ case tok::kw_static:
+ case tok::kw_struct:
+ case tok::kw_switch:
+ case tok::kw_this:
+ case tok::kw_throw:
+ case tok::kw_true:
+ case tok::kw_try:
+ case tok::kw_typeof:
+ case tok::kw_using:
+ case tok::kw_virtual:
+ case tok::kw_void:
+ case tok::kw_volatile:
+ case tok::kw_while:
+ return true;
+ default:
+ return Tok.is(tok::identifier) &&
+ CSharpExtraKeywords.find(Tok.Tok.getIdentifierInfo()) ==
+ CSharpExtraKeywords.end();
+ }
+ }
+
private:
/// The JavaScript keywords beyond the C++ keyword set.
std::unordered_set<IdentifierInfo *> JsExtraKeywords;
+
+ /// The C# keywords beyond the C++ keyword set
+ std::unordered_set<IdentifierInfo *> CSharpExtraKeywords;
};
} // namespace format
diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp
index da755e36c15..3a1dcef7d43 100644
--- a/clang/lib/Format/FormatTokenLexer.cpp
+++ b/clang/lib/Format/FormatTokenLexer.cpp
@@ -66,6 +66,21 @@ void FormatTokenLexer::tryMergePreviousTokens() {
return;
if (tryMergeLessLess())
return;
+
+ if (Style.isCSharp()) {
+ if (tryMergeCSharpKeywordVariables())
+ return;
+ if (tryMergeCSharpVerbatimStringLiteral())
+ return;
+ if (tryMergeCSharpDoubleQuestion())
+ return;
+ if (tryMergeCSharpNullConditionals())
+ return;
+ static const tok::TokenKind JSRightArrow[] = {tok::equal, tok::greater};
+ if (tryMergeTokens(JSRightArrow, TT_JsFatArrow))
+ return;
+ }
+
if (tryMergeNSStringLiteral())
return;
@@ -142,6 +157,100 @@ bool FormatTokenLexer::tryMergeJSPrivateIdentifier() {
return true;
}
+// Search for verbatim or interpolated string literals @"ABC" or
+// $"aaaaa{abc}aaaaa" i and mark the token as TT_CSharpStringLiteral, and to
+// prevent splitting of @, $ and ".
+bool FormatTokenLexer::tryMergeCSharpVerbatimStringLiteral() {
+ if (Tokens.size() < 2)
+ return false;
+ auto &At = *(Tokens.end() - 2);
+ auto &String = *(Tokens.end() - 1);
+
+ // Look for $"aaaaaa" @"aaaaaa".
+ if (!(At->is(tok::at) || At->TokenText == "$") ||
+ !String->is(tok::string_literal))
+ return false;
+
+ if (Tokens.size() >= 2 && At->is(tok::at)) {
+ auto &Dollar = *(Tokens.end() - 3);
+ if (Dollar->TokenText == "$") {
+ // This looks like $@"aaaaa" so we need to combine all 3 tokens.
+ Dollar->Tok.setKind(tok::string_literal);
+ Dollar->TokenText =
+ StringRef(Dollar->TokenText.begin(),
+ String->TokenText.end() - Dollar->TokenText.begin());
+ Dollar->ColumnWidth += (At->ColumnWidth + String->ColumnWidth);
+ Dollar->Type = TT_CSharpStringLiteral;
+ Tokens.erase(Tokens.end() - 2);
+ Tokens.erase(Tokens.end() - 1);
+ return true;
+ }
+ }
+
+ // Convert back into just a string_literal.
+ At->Tok.setKind(tok::string_literal);
+ At->TokenText = StringRef(At->TokenText.begin(),
+ String->TokenText.end() - At->TokenText.begin());
+ At->ColumnWidth += String->ColumnWidth;
+ At->Type = TT_CSharpStringLiteral;
+ Tokens.erase(Tokens.end() - 1);
+ return true;
+}
+
+bool FormatTokenLexer::tryMergeCSharpDoubleQuestion() {
+ if (Tokens.size() < 2)
+ return false;
+ auto &FirstQuestion = *(Tokens.end() - 2);
+ auto &SecondQuestion = *(Tokens.end() - 1);
+ if (!FirstQuestion->is(tok::question) || !SecondQuestion->is(tok::question))
+ return false;
+ FirstQuestion->Tok.setKind(tok::question);
+ FirstQuestion->TokenText = StringRef(FirstQuestion->TokenText.begin(),
+ SecondQuestion->TokenText.end() -
+ FirstQuestion->TokenText.begin());
+ FirstQuestion->ColumnWidth += SecondQuestion->ColumnWidth;
+ FirstQuestion->Type = TT_CSharpNullCoalescing;
+ Tokens.erase(Tokens.end() - 1);
+ return true;
+}
+
+bool FormatTokenLexer::tryMergeCSharpKeywordVariables() {
+ if (Tokens.size() < 2)
+ return false;
+ auto &At = *(Tokens.end() - 2);
+ auto &Keyword = *(Tokens.end() - 1);
+ if (!At->is(tok::at))
+ return false;
+ if (!Keywords.isCSharpKeyword(*Keyword))
+ return false;
+
+ At->Tok.setKind(tok::identifier);
+ At->TokenText = StringRef(At->TokenText.begin(),
+ Keyword->TokenText.end() - At->TokenText.begin());
+ At->ColumnWidth += Keyword->ColumnWidth;
+ At->Type = Keyword->Type;
+ Tokens.erase(Tokens.end() - 1);
+ return true;
+}
+
+// In C# merge the Identifier and the ? together e.g. arg?.
+bool FormatTokenLexer::tryMergeCSharpNullConditionals() {
+ if (Tokens.size() < 2)
+ return false;
+ auto &Identifier = *(Tokens.end() - 2);
+ auto &Question = *(Tokens.end() - 1);
+ if (!Identifier->isOneOf(tok::r_square, tok::identifier) ||
+ !Question->is(tok::question))
+ return false;
+ Identifier->TokenText =
+ StringRef(Identifier->TokenText.begin(),
+ Question->TokenText.end() - Identifier->TokenText.begin());
+ Identifier->ColumnWidth += Question->ColumnWidth;
+ Identifier->Type = Identifier->Type;
+ Tokens.erase(Tokens.end() - 1);
+ return true;
+}
+
bool FormatTokenLexer::tryMergeLessLess() {
// Merge X,less,less,Y into X,lessless,Y unless X or Y is less.
if (Tokens.size() < 3)
diff --git a/clang/lib/Format/FormatTokenLexer.h b/clang/lib/Format/FormatTokenLexer.h
index a9646261461..1e096fc5020 100644
--- a/clang/lib/Format/FormatTokenLexer.h
+++ b/clang/lib/Format/FormatTokenLexer.h
@@ -49,6 +49,10 @@ private:
bool tryMergeLessLess();
bool tryMergeNSStringLiteral();
bool tryMergeJSPrivateIdentifier();
+ bool tryMergeCSharpVerbatimStringLiteral();
+ bool tryMergeCSharpKeywordVariables();
+ bool tryMergeCSharpNullConditionals();
+ bool tryMergeCSharpDoubleQuestion();
bool tryMergeTokens(ArrayRef<tok::TokenKind> Kinds, TokenType NewType);
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index eeaceb82f9f..ccf5e51576d 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -298,6 +298,8 @@ private:
CurrentToken->Type = TT_JavaAnnotation;
if (Left->Previous && Left->Previous->is(TT_LeadingJavaAnnotation))
CurrentToken->Type = TT_LeadingJavaAnnotation;
+ if (Left->Previous && Left->Previous->is(TT_AttributeSquare))
+ CurrentToken->Type = TT_AttributeSquare;
if (!HasMultipleLines)
Left->PackingKind = PPK_Inconclusive;
@@ -348,6 +350,40 @@ private:
return false;
}
+ bool isCSharpAttributeSpecifier(const FormatToken &Tok) {
+ if (!Style.isCSharp())
+ return false;
+
+ const FormatToken *AttrTok = Tok.Next;
+ if (!AttrTok)
+ return false;
+
+ // Just an empty declaration e.g. string [].
+ if (AttrTok->is(tok::r_square))
+ return false;
+
+ // Move along the tokens inbetween the '[' and ']' e.g. [STAThread].
+ while (AttrTok && AttrTok->isNot(tok::r_square)) {
+ AttrTok = AttrTok->Next;
+ }
+
+ if (!AttrTok)
+ return false;
+
+ // Move past the end of ']'.
+ AttrTok = AttrTok->Next;
+ if (!AttrTok)
+ return false;
+
+ // Limit this to being an access modifier that follows.
+ if (AttrTok->isOneOf(tok::kw_public, tok::kw_private, tok::kw_protected,
+ tok::kw_class, tok::kw_static, tok::l_square,
+ Keywords.kw_internal)) {
+ return true;
+ }
+ return false;
+ }
+
bool isCpp11AttributeSpecifier(const FormatToken &Tok) {
if (!Style.isCpp() || !Tok.startsSequence(tok::l_square, tok::l_square))
return false;
@@ -398,6 +434,11 @@ private:
bool IsCpp11AttributeSpecifier = isCpp11AttributeSpecifier(*Left) ||
Contexts.back().InCpp11AttributeSpecifier;
+ // Treat C# Attributes [STAThread] much like C++ attributes [[...]].
+ bool IsCSharp11AttributeSpecifier =
+ isCSharpAttributeSpecifier(*Left) ||
+ Contexts.back().InCSharpAttributeSpecifier;
+
bool InsideInlineASM = Line.startsWith(tok::kw_asm);
bool StartsObjCMethodExpr =
!InsideInlineASM && !CppArrayTemplates && Style.isCpp() &&
@@ -478,6 +519,8 @@ private:
// Should only be relevant to JavaScript:
tok::kw_default)) {
Left->Type = TT_ArrayInitializerLSquare;
+ } else if (IsCSharp11AttributeSpecifier) {
+ Left->Type = TT_AttributeSquare;
} else {
BindingIncrease = 10;
Left->Type = TT_ArraySubscriptLSquare;
@@ -492,11 +535,14 @@ private:
Contexts.back().ColonIsObjCMethodExpr = StartsObjCMethodExpr;
Contexts.back().InCpp11AttributeSpecifier = IsCpp11AttributeSpecifier;
+ Contexts.back().InCSharpAttributeSpecifier = IsCSharp11AttributeSpecifier;
while (CurrentToken) {
if (CurrentToken->is(tok::r_square)) {
if (IsCpp11AttributeSpecifier)
CurrentToken->Type = TT_AttributeSquare;
+ if (IsCSharp11AttributeSpecifier)
+ CurrentToken->Type = TT_AttributeSquare;
else if (((CurrentToken->Next &&
CurrentToken->Next->is(tok::l_paren)) ||
(CurrentToken->Previous &&
@@ -1193,6 +1239,7 @@ private:
bool CaretFound = false;
bool IsForEachMacro = false;
bool InCpp11AttributeSpecifier = false;
+ bool InCSharpAttributeSpecifier = false;
};
/// Puts a new \c Context onto the stack \c Contexts for the lifetime
@@ -2630,7 +2677,7 @@ bool TokenAnnotator::spaceRequiredBefore(const AnnotatedLine &Line,
// and "%d %d"
if (Left.is(tok::numeric_constant) && Right.is(tok::percent))
return Right.WhitespaceRange.getEnd() != Right.WhitespaceRange.getBegin();
- } else if (Style.Language == FormatStyle::LK_JavaScript) {
+ } else if (Style.Language == FormatStyle::LK_JavaScript || Style.isCSharp()) {
if (Left.is(TT_JsFatArrow))
return true;
// for await ( ...
@@ -2977,6 +3024,14 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
if (Left.is(TT_ObjCBlockLBrace) && !Style.AllowShortBlocksOnASingleLine)
return true;
+ // Put multiple C# attributes on a new line.
+ if (Style.isCSharp() &&
+ ((Left.is(TT_AttributeSquare) && Left.is(tok::r_square)) ||
+ (Left.is(tok::r_square) && Right.is(TT_AttributeSquare) &&
+ Right.is(tok::l_square))))
+ return true;
+
+ // Put multiple Java annotation on a new line.
if ((Style.Language == FormatStyle::LK_Java ||
Style.Language == FormatStyle::LK_JavaScript) &&
Left.is(TT_LeadingJavaAnnotation) &&
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 5d8142a1cfb..4e633c203c1 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -94,7 +94,7 @@ private:
/// characters to the left from their level.
int getIndentOffset(const FormatToken &RootToken) {
if (Style.Language == FormatStyle::LK_Java ||
- Style.Language == FormatStyle::LK_JavaScript)
+ Style.Language == FormatStyle::LK_JavaScript || Style.isCSharp())
return 0;
if (RootToken.isAccessSpecifier(false) ||
RootToken.isObjCAccessSpecifier() ||
@@ -1073,7 +1073,9 @@ unsigned UnwrappedLineFormatter::format(
TheLine.Last->TotalLength + Indent <= ColumnLimit ||
(TheLine.Type == LT_ImportStatement &&
(Style.Language != FormatStyle::LK_JavaScript ||
- !Style.JavaScriptWrapImports));
+ !Style.JavaScriptWrapImports)) ||
+ (Style.isCSharp() &&
+ TheLine.InPPDirective); // don't split #regions in C#
if (Style.ColumnLimit == 0)
NoColumnLimitLineFormatter(Indenter, Whitespaces, Style, this)
.formatLine(TheLine, NextStartColumn + Indent,
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 4bd71aa26d5..814e8143e17 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -1000,7 +1000,7 @@ void UnwrappedLineParser::parseStructuralElement() {
case tok::kw_protected:
case tok::kw_private:
if (Style.Language == FormatStyle::LK_Java ||
- Style.Language == FormatStyle::LK_JavaScript)
+ Style.Language == FormatStyle::LK_JavaScript || Style.isCSharp())
nextToken();
else
parseAccessSpecifier();
@@ -1214,9 +1214,9 @@ void UnwrappedLineParser::parseStructuralElement() {
// parseRecord falls through and does not yet add an unwrapped line as a
// record declaration or definition can start a structural element.
parseRecord();
- // This does not apply for Java and JavaScript.
+ // This does not apply for Java, JavaScript and C#.
if (Style.Language == FormatStyle::LK_Java ||
- Style.Language == FormatStyle::LK_JavaScript) {
+ Style.Language == FormatStyle::LK_JavaScript || Style.isCSharp()) {
if (FormatTok->is(tok::semi))
nextToken();
addUnwrappedLine();
diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp
index 9e0ce71d7f1..655a8d8e872 100644
--- a/clang/tools/clang-format/ClangFormat.cpp
+++ b/clang/tools/clang-format/ClangFormat.cpp
@@ -345,7 +345,7 @@ int main(int argc, const char **argv) {
cl::SetVersionPrinter(PrintVersion);
cl::ParseCommandLineOptions(
argc, argv,
- "A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf code.\n\n"
+ "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 <file>s are given, it reformats the files. If -i is specified\n"
diff --git a/clang/unittests/Format/CMakeLists.txt b/clang/unittests/Format/CMakeLists.txt
index 015c25ee6b9..bf02de97052 100644
--- a/clang/unittests/Format/CMakeLists.txt
+++ b/clang/unittests/Format/CMakeLists.txt
@@ -6,6 +6,7 @@ add_clang_unittest(FormatTests
CleanupTest.cpp
FormatTest.cpp
FormatTestComments.cpp
+ FormatTestCSharp.cpp
FormatTestJS.cpp
FormatTestJava.cpp
FormatTestObjC.cpp
diff --git a/clang/unittests/Format/FormatTestCSharp.cpp b/clang/unittests/Format/FormatTestCSharp.cpp
new file mode 100644
index 00000000000..801adb28bd9
--- /dev/null
+++ b/clang/unittests/Format/FormatTestCSharp.cpp
@@ -0,0 +1,184 @@
+//===- unittest/Format/FormatTestCSharp.cpp - Formatting tests for CSharp -===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "FormatTestUtils.h"
+#include "clang/Format/Format.h"
+#include "llvm/Support/Debug.h"
+#include "gtest/gtest.h"
+
+#define DEBUG_TYPE "format-test"
+
+namespace clang {
+namespace format {
+
+class FormatTestCSharp : public ::testing::Test {
+protected:
+ static std::string format(llvm::StringRef Code, unsigned Offset,
+ unsigned Length, const FormatStyle &Style) {
+ LLVM_DEBUG(llvm::errs() << "---\n");
+ LLVM_DEBUG(llvm::errs() << Code << "\n\n");
+ std::vector<tooling::Range> Ranges(1, tooling::Range(Offset, Length));
+ tooling::Replacements Replaces = reformat(Style, Code, Ranges);
+ auto Result = applyAllReplacements(Code, Replaces);
+ EXPECT_TRUE(static_cast<bool>(Result));
+ LLVM_DEBUG(llvm::errs() << "\n" << *Result << "\n\n");
+ return *Result;
+ }
+
+ static std::string
+ format(llvm::StringRef Code,
+ const FormatStyle &Style = getGoogleStyle(FormatStyle::LK_CSharp)) {
+ return format(Code, 0, Code.size(), Style);
+ }
+
+ static FormatStyle getStyleWithColumns(unsigned ColumnLimit) {
+ FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp);
+ Style.ColumnLimit = ColumnLimit;
+ return Style;
+ }
+
+ static void verifyFormat(
+ llvm::StringRef Code,
+ const FormatStyle &Style = getGoogleStyle(FormatStyle::LK_CSharp)) {
+ EXPECT_EQ(Code.str(), format(Code, Style)) << "Expected code is not stable";
+ EXPECT_EQ(Code.str(), format(test::messUp(Code), Style));
+ }
+};
+
+TEST_F(FormatTestCSharp, CSharpClass) {
+ verifyFormat("public class SomeClass {\n"
+ " void f() {}\n"
+ " int g() { return 0; }\n"
+ " void h() {\n"
+ " while (true) f();\n"
+ " for (;;) f();\n"
+ " if (true) f();\n"
+ " }\n"
+ "}");
+}
+
+TEST_F(FormatTestCSharp, AccessModifiers) {
+ verifyFormat("public String toString() {}");
+ verifyFormat("private String toString() {}");
+ verifyFormat("protected String toString() {}");
+ verifyFormat("internal String toString() {}");
+
+ verifyFormat("public override String toString() {}");
+ verifyFormat("private override String toString() {}");
+ verifyFormat("protected override String toString() {}");
+ verifyFormat("internal override String toString() {}");
+
+ verifyFormat("internal static String toString() {}");
+}
+
+TEST_F(FormatTestCSharp, NoStringLiteralBreaks) {
+ verifyFormat("foo("
+ "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaa\");");
+}
+
+TEST_F(FormatTestCSharp, CSharpVerbatiumStringLiterals) {
+ verifyFormat("foo(@\"aaaaaaaa\\abc\\aaaa\");");
+ // @"ABC\" + ToString("B") - handle embedded \ in literal string at
+ // the end
+ //
+ /*
+ * After removal of Lexer change we are currently not able
+ * To handle these cases
+ verifyFormat("string s = @\"ABC\\\" + ToString(\"B\");");
+ verifyFormat("string s = @\"ABC\"\"DEF\"\"GHI\"");
+ verifyFormat("string s = @\"ABC\"\"DEF\"\"\"");
+ verifyFormat("string s = @\"ABC\"\"DEF\"\"\" + abc");
+ */
+}
+
+TEST_F(FormatTestCSharp, CSharpInterpolatedStringLiterals) {
+ verifyFormat("foo($\"aaaaaaaa{aaa}aaaa\");");
+ verifyFormat("foo($\"aaaa{A}\");");
+ verifyFormat(
+ "foo($\"aaaa{A}"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");");
+ verifyFormat("Name = $\"{firstName} {lastName}\";");
+
+ // $"ABC\" + ToString("B") - handle embedded \ in literal string at
+ // the end
+ verifyFormat("string s = $\"A{abc}BC\" + ToString(\"B\");");
+ verifyFormat("$\"{domain}\\\\{user}\"");
+ verifyFormat(
+ "var verbatimInterpolated = $@\"C:\\Users\\{userName}\\Documents\\\";");
+}
+
+TEST_F(FormatTestCSharp, CSharpFatArrows) {
+ verifyFormat("Task serverTask = Task.Run(async() => {");
+ verifyFormat("public override string ToString() => \"{Name}\\{Age}\";");
+}
+
+TEST_F(FormatTestCSharp, CSharpNullConditional) {
+ verifyFormat(
+ "public Person(string firstName, string lastName, int? age=null)");
+
+ verifyFormat("switch(args?.Length)");
+
+ verifyFormat("public static void Main(string[] args) { string dirPath "
+ "= args?[0]; }");
+}
+
+TEST_F(FormatTestCSharp, Attributes) {
+ verifyFormat("[STAThread]\n"
+ "static void\n"
+ "Main(string[] args) {}");
+
+ verifyFormat("[TestMethod]\n"
+ "private class Test {}");
+
+ verifyFormat("[TestMethod]\n"
+ "protected class Test {}");
+
+ verifyFormat("[TestMethod]\n"
+ "internal class Test {}");
+
+ verifyFormat("[TestMethod]\n"
+ "class Test {}");
+
+ verifyFormat("[TestMethod]\n"
+ "[DeploymentItem(\"Test.txt\")]\n"
+ "public class Test {}");
+
+ verifyFormat("[System.AttributeUsage(System.AttributeTargets.Method)]\n"
+ "[System.Runtime.InteropServices.ComVisible(true)]\n"
+ "public sealed class STAThreadAttribute : Attribute {}");
+
+ verifyFormat("[Verb(\"start\", HelpText = \"Starts the server listening on "
+ "provided port\")]\n"
+ "class Test {}");
+
+ verifyFormat("[TestMethod]\n"
+ "public string Host {\n set;\n get;\n}");
+
+ verifyFormat("[TestMethod(\"start\", HelpText = \"Starts the server "
+ "listening on provided host\")]\n"
+ "public string Host {\n set;\n get;\n}");
+}
+
+TEST_F(FormatTestCSharp, CSharpRegions) {
+ verifyFormat("#region aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaa "
+ "aaaaaaaaaaaaaaa long region");
+}
+
+TEST_F(FormatTestCSharp, CSharpKeyWordEscaping) {
+ verifyFormat("public enum var { none, @string, bool, @enum }");
+}
+
+TEST_F(FormatTestCSharp, CSharpNullCoalescing) {
+ verifyFormat("var test = ABC ?? DEF");
+ verifyFormat("string myname = name ?? \"ABC\";");
+ verifyFormat("return _name ?? \"DEF\";");
+}
+
+} // namespace format
+} // end namespace clang
OpenPOWER on IntegriCloud