[clang-format] Do not treat C# attribute targets as labels
authorJonathan Coe <jbcoe@google.com>
Wed, 5 Feb 2020 16:51:31 +0000 (16:51 +0000)
committerJonathan Coe <jbcoe@google.com>
Wed, 5 Feb 2020 17:30:24 +0000 (17:30 +0000)
Summary: Merge '[', 'target' , ':' into a single token for C# attributes to
prevent the target from being seen as a label.

Reviewers: MyDeveloperDay, krasimir

Reviewed By: krasimir

Tags: #clang-format

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

clang/lib/Format/FormatTokenLexer.cpp
clang/lib/Format/FormatTokenLexer.h
clang/unittests/Format/FormatTestCSharp.cpp

index 9865095..e76d745 100644 (file)
@@ -76,6 +76,8 @@ void FormatTokenLexer::tryMergePreviousTokens() {
     return;
 
   if (Style.isCSharp()) {
+    if (tryMergeCSharpAttributeAndTarget())
+      return;
     if (tryMergeCSharpKeywordVariables())
       return;
     if (tryMergeCSharpStringLiteral())
@@ -275,6 +277,41 @@ bool FormatTokenLexer::tryMergeCSharpStringLiteral() {
   return true;
 }
 
+// Valid C# attribute targets:
+// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/#attribute-targets
+const llvm::StringSet<> FormatTokenLexer::CSharpAttributeTargets = {
+    "assembly", "module",   "field",  "event", "method",
+    "param",    "property", "return", "type",
+};
+
+bool FormatTokenLexer::tryMergeCSharpAttributeAndTarget() {
+  // Treat '[assembly:' and '[field:' as tokens in their own right.
+  if (Tokens.size() < 3)
+    return false;
+
+  auto &SquareBracket = *(Tokens.end() - 3);
+  auto &Target = *(Tokens.end() - 2);
+  auto &Colon = *(Tokens.end() - 1);
+
+  if (!SquareBracket->Tok.is(tok::l_square))
+    return false;
+
+  if (CSharpAttributeTargets.find(Target->TokenText) ==
+      CSharpAttributeTargets.end())
+    return false;
+
+  if (!Colon->Tok.is(tok::colon))
+    return false;
+
+  SquareBracket->TokenText =
+      StringRef(SquareBracket->TokenText.begin(),
+                Colon->TokenText.end() - SquareBracket->TokenText.begin());
+  SquareBracket->ColumnWidth += (Target->ColumnWidth + Colon->ColumnWidth);
+  Tokens.erase(Tokens.end() - 2);
+  Tokens.erase(Tokens.end() - 1);
+  return true;
+}
+
 bool FormatTokenLexer::tryMergeCSharpDoubleQuestion() {
   if (Tokens.size() < 2)
     return false;
index be13ac8..4fffb36 100644 (file)
@@ -21,6 +21,7 @@
 #include "clang/Basic/SourceManager.h"
 #include "clang/Format/Format.h"
 #include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/StringSet.h"
 #include "llvm/Support/Regex.h"
 
 #include <stack>
@@ -54,6 +55,7 @@ private:
   bool tryMergeCSharpNullConditionals();
   bool tryMergeCSharpDoubleQuestion();
   bool tryTransformCSharpForEach();
+  bool tryMergeCSharpAttributeAndTarget();
 
   bool tryMergeTokens(ArrayRef<tok::TokenKind> Kinds, TokenType NewType);
 
@@ -115,6 +117,9 @@ private:
   llvm::Regex MacroBlockBeginRegex;
   llvm::Regex MacroBlockEndRegex;
 
+  // Targets that may appear inside a C# attribute.
+  static const llvm::StringSet<> CSharpAttributeTargets;
+
   void readRawToken(FormatToken &Tok);
 
   void resetLexer(unsigned Offset);
index 86a44d0..dba76b5 100644 (file)
@@ -233,6 +233,15 @@ TEST_F(FormatTestCSharp, Attributes) {
       "[DllImport(\"Hello\", EntryPoint = \"hello_world\")]\n"
       "// The const char* returned by hello_world must not be deleted.\n"
       "private static extern IntPtr HelloFromCpp();)");
+
+  //  Unwrappable lines go on a line of their own.
+  // 'target:' is not treated as a label.
+  // Modify Style to enforce a column limit.
+  FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp);
+  Style.ColumnLimit = 10;
+  verifyFormat(R"([assembly:InternalsVisibleTo(
+    "SomeAssembly, PublicKey=SomePublicKeyThatExceedsTheColumnLimit")])",
+               Style);
 }
 
 TEST_F(FormatTestCSharp, CSharpUsing) {