]> granicus.if.org Git - clang/commitdiff
clang-format: [JS] Better support for fat arrows.
authorManuel Klimek <klimek@google.com>
Thu, 21 May 2015 12:23:34 +0000 (12:23 +0000)
committerManuel Klimek <klimek@google.com>
Thu, 21 May 2015 12:23:34 +0000 (12:23 +0000)
Assigns a token type (TT_JsFatArrow) to => tokens, and uses that to
more easily recognize and format fat arrow functions.
Improves function parsing to better recognize formal parameter
lists and return type declarations.
Recognizes arrow functions and parse function bodies as child blocks.

Patch by Martin Probst.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@237895 91177308-0d34-0410-b5e6-96231b3b80d8

lib/Format/Format.cpp
lib/Format/FormatToken.h
lib/Format/UnwrappedLineParser.cpp
lib/Format/UnwrappedLineParser.h
unittests/Format/FormatTestJS.cpp

index 98146471af38718a3b0ed94c3d7fb7ad56d1d046..10c68f9da617474c9481dfb498550f7412fad098 100644 (file)
@@ -650,15 +650,14 @@ private:
       static const tok::TokenKind JSShiftEqual[] = {tok::greater, tok::greater,
                                                     tok::greaterequal};
       static const tok::TokenKind JSRightArrow[] = {tok::equal, tok::greater};
-      // FIXME: We probably need to change token type to mimic operator with the
-      // correct priority.
-      if (tryMergeTokens(JSIdentity))
+      // FIXME: Investigate what token type gives the correct operator priority.
+      if (tryMergeTokens(JSIdentity, TT_BinaryOperator))
         return;
-      if (tryMergeTokens(JSNotIdentity))
+      if (tryMergeTokens(JSNotIdentity, TT_BinaryOperator))
         return;
-      if (tryMergeTokens(JSShiftEqual))
+      if (tryMergeTokens(JSShiftEqual, TT_BinaryOperator))
         return;
-      if (tryMergeTokens(JSRightArrow))
+      if (tryMergeTokens(JSRightArrow, TT_JsFatArrow))
         return;
     }
   }
@@ -689,7 +688,7 @@ private:
     return true;
   }
 
-  bool tryMergeTokens(ArrayRef<tok::TokenKind> Kinds) {
+  bool tryMergeTokens(ArrayRef<tok::TokenKind> Kinds, TokenType NewType) {
     if (Tokens.size() < Kinds.size())
       return false;
 
@@ -709,6 +708,7 @@ private:
     First[0]->TokenText = StringRef(First[0]->TokenText.data(),
                                     First[0]->TokenText.size() + AddLength);
     First[0]->ColumnWidth += AddLength;
+    First[0]->Type = NewType;
     return true;
   }
 
index 9325d0d52e5a6234bc36a7095bb13ccb9b6d79ac..ec0fdf4aa813b04bf78d50c123a8d1b753de21c6 100644 (file)
@@ -51,6 +51,7 @@ enum TokenType {
   TT_InlineASMBrace,
   TT_InlineASMColon,
   TT_JavaAnnotation,
+  TT_JsFatArrow,
   TT_JsTypeColon,
   TT_JsTypeOptionalQuestion,
   TT_LambdaArrow,
index 5b1a448567363a0a47d9715583b99aeb3b3d6fe1..939528fbffe559e868d44b61caa8b0e60bbef78d 100644 (file)
@@ -882,6 +882,17 @@ void UnwrappedLineParser::parseStructuralElement() {
       break;
     }
     case tok::equal:
+      // Fat arrows (=>) have tok::TokenKind tok::equal but TokenType
+      // TT_JsFatArrow. The always start an expression or a child block if
+      // followed by a curly.
+      if (FormatTok->is(TT_JsFatArrow)) {
+        nextToken();
+        if (FormatTok->is(tok::l_brace)) {
+          parseChildBlock();
+        }
+        break;
+      }
+
       nextToken();
       if (FormatTok->Tok.is(tok::l_brace)) {
         parseBracedList();
@@ -1006,18 +1017,44 @@ void UnwrappedLineParser::tryToParseJSFunction() {
 
   if (FormatTok->isNot(tok::l_paren))
     return;
-  nextToken();
-  while (FormatTok->isNot(tok::l_brace)) {
-    // Err on the side of caution in order to avoid consuming the full file in
-    // case of incomplete code.
-    if (!FormatTok->isOneOf(tok::identifier, tok::comma, tok::r_paren,
-                            tok::comment))
-      return;
+
+  // Parse formal parameter list.
+  parseBalanced(tok::l_paren, tok::r_paren);
+
+  if (FormatTok->is(tok::colon)) {
+    // Parse a type definition.
     nextToken();
+
+    // Eat the type declaration. For braced inline object types, balance braces,
+    // otherwise just parse until finding an l_brace for the function body.
+    if (FormatTok->is(tok::l_brace)) {
+      parseBalanced(tok::l_brace, tok::r_brace);
+    } else {
+      while(FormatTok->isNot(tok::l_brace) && !eof()) {
+        nextToken();
+      }
+    }
   }
+
   parseChildBlock();
 }
 
+void UnwrappedLineParser::parseBalanced(tok::TokenKind OpenKind,
+                                        tok::TokenKind CloseKind) {
+  assert(FormatTok->is(OpenKind));
+  nextToken();
+  int Depth = 1;
+  while (Depth > 0 && !eof()) {
+    // Parse the formal parameter list.
+    if (FormatTok->is(OpenKind)) {
+      ++Depth;
+    } else if (FormatTok->is(CloseKind)) {
+      --Depth;
+    }
+    nextToken();
+  }
+}
+
 bool UnwrappedLineParser::tryToParseBracedList() {
   if (FormatTok->BlockKind == BK_Unknown)
     calculateBraceTypes();
@@ -1035,10 +1072,19 @@ bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons) {
   // FIXME: Once we have an expression parser in the UnwrappedLineParser,
   // replace this by using parseAssigmentExpression() inside.
   do {
-    if (Style.Language == FormatStyle::LK_JavaScript &&
-        FormatTok->is(Keywords.kw_function)) {
-      tryToParseJSFunction();
-      continue;
+    if (Style.Language == FormatStyle::LK_JavaScript) {
+      if (FormatTok->is(Keywords.kw_function)) {
+        tryToParseJSFunction();
+        continue;
+      } else if (FormatTok->is(TT_JsFatArrow)) {
+        nextToken();
+        // Fat arrows can be followed by simple expressions or by child blocks
+        // in curly braces.
+        if (FormatTok->is(tok::l_brace)){
+          parseChildBlock();
+          continue;
+        }
+      }
     }
     switch (FormatTok->Tok.getKind()) {
     case tok::caret:
index c2fa02957685a760aae8f3710dd065aa24f5ccbd..6a6e56fea0280ab18fa9779ab14d5a7ef1057317 100644 (file)
@@ -107,6 +107,11 @@ private:
   bool tryToParseLambda();
   bool tryToParseLambdaIntroducer();
   void tryToParseJSFunction();
+  /// \brief Parses tokens until encountering the CloseKind token, but balances
+  /// tokens when encountering more OpenKind tokens. Useful for e.g. parsing a
+  /// curly brace delimited block that can contain nested blocks.
+  /// The parser must be positioned on a token of OpenKind.
+  void parseBalanced(tok::TokenKind OpenKind, tok::TokenKind CloseKind);
   void addUnwrappedLine();
   bool eof() const;
   void nextToken();
index 41ffbedea74e204c4b1124aaa357ba7dacce0b9a..1481ef0f285c5590349b2098e448cc447ca83ba4 100644 (file)
@@ -98,6 +98,7 @@ TEST_F(FormatTestJS, ES6DestructuringAssignment) {
 }
 
 TEST_F(FormatTestJS, ContainerLiterals) {
+  verifyFormat("var x = {y: function(a) { return a; }};");
   verifyFormat("return {\n"
                "  link: function() {\n"
                "    f();  //\n"
@@ -142,6 +143,10 @@ TEST_F(FormatTestJS, ContainerLiterals) {
   verifyFormat("X = {\n  a: 123\n};");
   verifyFormat("X.Y = {\n  a: 123\n};");
   verifyFormat("x = foo && {a: 123};");
+
+  // Arrow functions in object literals.
+  verifyFormat("var x = {y: (a) => { return a; }};");
+  verifyFormat("var x = {y: (a) => a};");
 }
 
 TEST_F(FormatTestJS, MethodsInObjectLiterals) {
@@ -419,13 +424,28 @@ TEST_F(FormatTestJS, MultipleFunctionLiterals) {
                "    .thenCatch(function(error) { body(); });");
 }
 
+TEST_F(FormatTestJS, ArrowFunctions) {
+  verifyFormat("var x = (a) => {\n"
+               "  return a;\n"
+               "};");
+  verifyFormat("var x = (a) => {\n"
+               "  function y() { return 42; }\n"
+               "  return a;\n"
+               "};");
+  verifyFormat("var x = (a: type): {some: type} => {\n"
+               "  return a;\n"
+               "};");
+  verifyFormat("var x = (a) => a;");
+  verifyFormat("var x = (a) => a;");
+}
+
 TEST_F(FormatTestJS, ReturnStatements) {
   verifyFormat("function() {\n"
                "  return [hello, world];\n"
                "}");
 }
 
-TEST_F(FormatTestJS, ClosureStyleComments) {
+TEST_F(FormatTestJS, ClosureStyleCasts) {
   verifyFormat("var x = /** @type {foo} */ (bar);");
 }
 
@@ -536,11 +556,14 @@ TEST_F(FormatTestJS, RegexLiteralExamples) {
 TEST_F(FormatTestJS, TypeAnnotations) {
   verifyFormat("var x: string;");
   verifyFormat("function x(): string {\n  return 'x';\n}");
+  verifyFormat("function x(): {x: string} {\n  return {x: 'x'};\n}");
   verifyFormat("function x(y: string): string {\n  return 'x';\n}");
   verifyFormat("for (var y: string in x) {\n  x();\n}");
   verifyFormat("((a: string, b: number): string => a + b);");
   verifyFormat("var x: (y: number) => string;");
   verifyFormat("var x: P<string, (a: number) => string>;");
+  verifyFormat("var x = {y: function(): z { return 1; }};");
+  verifyFormat("var x = {y: function(): {a: number} { return 1; }};");
 }
 
 TEST_F(FormatTestJS, ClassDeclarations) {