Commit ca1fd460 authored by Jonathan Coe's avatar Jonathan Coe
Browse files

[clang-format] Do not treat C# attribute targets as labels

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
parent 9f507bfd
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -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;
+5 −0
Original line number Diff line number Diff line
@@ -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);
+9 −0
Original line number Diff line number Diff line
@@ -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) {