Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -650,15 +650,14 @@ 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 @@ return true; } - bool tryMergeTokens(ArrayRef Kinds) { + bool tryMergeTokens(ArrayRef Kinds, TokenType NewType) { if (Tokens.size() < Kinds.size()) return false; @@ -709,6 +708,7 @@ First[0]->TokenText = StringRef(First[0]->TokenText.data(), First[0]->TokenText.size() + AddLength); First[0]->ColumnWidth += AddLength; + First[0]->Type = NewType; return true; } Index: lib/Format/FormatToken.h =================================================================== --- lib/Format/FormatToken.h +++ lib/Format/FormatToken.h @@ -51,6 +51,7 @@ TT_InlineASMBrace, TT_InlineASMColon, TT_JavaAnnotation, + TT_JsFatArrow, TT_JsTypeColon, TT_JsTypeOptionalQuestion, TT_LambdaArrow, Index: lib/Format/UnwrappedLineParser.h =================================================================== --- lib/Format/UnwrappedLineParser.h +++ lib/Format/UnwrappedLineParser.h @@ -107,6 +107,11 @@ 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: lib/Format/UnwrappedLineParser.cpp =================================================================== --- lib/Format/UnwrappedLineParser.cpp +++ lib/Format/UnwrappedLineParser.cpp @@ -882,6 +882,17 @@ 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 @@ 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 @@ // 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: unittests/Format/FormatTestJS.cpp =================================================================== --- unittests/Format/FormatTestJS.cpp +++ unittests/Format/FormatTestJS.cpp @@ -98,6 +98,7 @@ } TEST_F(FormatTestJS, ContainerLiterals) { + verifyFormat("var x = {y: function(a) { return a; }};"); verifyFormat("return {\n" " link: function() {\n" " f(); //\n" @@ -142,6 +143,10 @@ 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 @@ " .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, 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>;"); + verifyFormat("var x = {y: function(): z { return 1; }};"); + verifyFormat("var x = {y: function(): {a: number} { return 1; }};"); } TEST_F(FormatTestJS, ClassDeclarations) {