Index: docs/ClangFormat.rst =================================================================== --- docs/ClangFormat.rst +++ docs/ClangFormat.rst @@ -11,12 +11,12 @@ =============== :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. Index: docs/ClangFormatStyleOptions.rst =================================================================== --- docs/ClangFormatStyleOptions.rst +++ docs/ClangFormatStyleOptions.rst @@ -68,6 +68,10 @@ 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 @@ -1496,6 +1500,9 @@ * ``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. Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -158,7 +158,7 @@ ------------ -- ... +- Add language support for clang-formatting C# files libclang -------- Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -354,38 +354,38 @@ /// Different ways to break after the template declaration. enum BreakTemplateDeclarationsStyle { - /// Do not force break before declaration. - /// ``PenaltyBreakTemplateDeclaration`` is taken into account. - /// \code - /// template T foo() { - /// } - /// template T foo(int aaaaaaaaaaaaaaaaaaaaa, - /// int bbbbbbbbbbbbbbbbbbbbb) { - /// } - /// \endcode - BTDS_No, - /// Force break after template declaration only when the following - /// declaration spans multiple lines. - /// \code - /// template T foo() { - /// } - /// template - /// T foo(int aaaaaaaaaaaaaaaaaaaaa, - /// int bbbbbbbbbbbbbbbbbbbbb) { - /// } - /// \endcode - BTDS_MultiLine, - /// Always break after template declaration. - /// \code - /// template - /// T foo() { - /// } - /// template - /// T foo(int aaaaaaaaaaaaaaaaaaaaa, - /// int bbbbbbbbbbbbbbbbbbbbb) { - /// } - /// \endcode - BTDS_Yes + /// Do not force break before declaration. + /// ``PenaltyBreakTemplateDeclaration`` is taken into account. + /// \code + /// template T foo() { + /// } + /// template T foo(int aaaaaaaaaaaaaaaaaaaaa, + /// int bbbbbbbbbbbbbbbbbbbbb) { + /// } + /// \endcode + BTDS_No, + /// Force break after template declaration only when the following + /// declaration spans multiple lines. + /// \code + /// template T foo() { + /// } + /// template + /// T foo(int aaaaaaaaaaaaaaaaaaaaa, + /// int bbbbbbbbbbbbbbbbbbbbb) { + /// } + /// \endcode + BTDS_MultiLine, + /// Always break after template declaration. + /// \code + /// template + /// T foo() { + /// } + /// template + /// T foo(int aaaaaaaaaaaaaaaaaaaaa, + /// int bbbbbbbbbbbbbbbbbbbbb) { + /// } + /// \endcode + BTDS_Yes }; /// The template declaration breaking style to use. @@ -1219,6 +1219,8 @@ 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. @@ -1235,6 +1237,7 @@ 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; @@ -2050,6 +2053,8 @@ 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: @@ -2071,6 +2076,6 @@ namespace std { template <> struct is_error_code_enum : std::true_type {}; -} +} // end namespace std #endif // LLVM_CLANG_FORMAT_FORMAT_H Index: lib/Format/ContinuationIndenter.cpp =================================================================== --- lib/Format/ContinuationIndenter.cpp +++ lib/Format/ContinuationIndenter.cpp @@ -1716,6 +1716,7 @@ // it requires strings to be merged using "+" which we don't support. if (Style.Language == FormatStyle::LK_Java || Style.Language == FormatStyle::LK_JavaScript || + Style.isCSharp() || !Style.BreakStringLiterals || !AllowBreak) return nullptr; Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -61,6 +61,7 @@ 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); } }; @@ -2306,6 +2307,8 @@ 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; } Index: lib/Format/FormatToken.h =================================================================== --- lib/Format/FormatToken.h +++ lib/Format/FormatToken.h @@ -723,6 +723,8 @@ kw_slots = &IdentTable.get("slots"); kw_qslots = &IdentTable.get("Q_SLOTS"); + kw_internal = &IdentTable.get("internal"); + // Keep this at the end of the constructor to make sure everything here is // already initialized. JsExtraKeywords = std::unordered_set( @@ -796,6 +798,9 @@ IdentifierInfo *kw_slots; IdentifierInfo *kw_qslots; + // C# keywords + IdentifierInfo *kw_internal; + /// 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 { Index: lib/Format/TokenAnnotator.cpp =================================================================== --- lib/Format/TokenAnnotator.cpp +++ lib/Format/TokenAnnotator.cpp @@ -298,6 +298,8 @@ 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,36 @@ 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; + } + + // 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_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 +430,9 @@ bool IsCpp11AttributeSpecifier = isCpp11AttributeSpecifier(*Left) || Contexts.back().InCpp11AttributeSpecifier; + bool IsCSharp11AttributeSpecifier = isCSharpAttributeSpecifier(*Left) || + Contexts.back().InCSharpAttributeSpecifier; + bool InsideInlineASM = Line.startsWith(tok::kw_asm); bool StartsObjCMethodExpr = !InsideInlineASM && !CppArrayTemplates && Style.isCpp() && @@ -475,6 +510,8 @@ // 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; @@ -489,11 +526,14 @@ 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 && @@ -1190,6 +1230,7 @@ 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 @@ -2967,6 +3008,11 @@ if (Left.is(TT_ObjCBlockLBrace) && !Style.AllowShortBlocksOnASingleLine) return true; + 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; if ((Style.Language == FormatStyle::LK_Java || Style.Language == FormatStyle::LK_JavaScript) && Left.is(TT_LeadingJavaAnnotation) && Index: lib/Format/UnwrappedLineFormatter.cpp =================================================================== --- lib/Format/UnwrappedLineFormatter.cpp +++ lib/Format/UnwrappedLineFormatter.cpp @@ -94,7 +94,8 @@ /// 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() || Index: lib/Format/UnwrappedLineParser.cpp =================================================================== --- lib/Format/UnwrappedLineParser.cpp +++ lib/Format/UnwrappedLineParser.cpp @@ -999,7 +999,8 @@ 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(); @@ -1213,9 +1214,10 @@ // 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(); Index: tools/clang-format/ClangFormat.cpp =================================================================== --- tools/clang-format/ClangFormat.cpp +++ tools/clang-format/ClangFormat.cpp @@ -345,7 +345,7 @@ 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 s are given, it reformats the files. If -i is specified\n" Index: unittests/Format/CMakeLists.txt =================================================================== --- unittests/Format/CMakeLists.txt +++ unittests/Format/CMakeLists.txt @@ -6,6 +6,7 @@ CleanupTest.cpp FormatTest.cpp FormatTestComments.cpp + FormatTestCSharp.cpp FormatTestJS.cpp FormatTestJava.cpp FormatTestObjC.cpp Index: unittests/Format/FormatTestCSharp.cpp =================================================================== --- /dev/null +++ unittests/Format/FormatTestCSharp.cpp @@ -0,0 +1,100 @@ +//===- 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 Ranges(1, tooling::Range(Offset, Length)); + tooling::Replacements Replaces = reformat(Style, Code, Ranges); + auto Result = applyAllReplacements(Code, Replaces); + EXPECT_TRUE(static_cast(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, Attributes) { + verifyFormat("[STAThread]\n" + "static void\n" + "Main(string[] args) {}"); + + 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 {}"); +} + +} // namespace format +} // end namespace clang