From: Manuel Klimek Date: Thu, 21 May 2015 12:23:34 +0000 (+0000) Subject: clang-format: [JS] Better support for fat arrows. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=384ba28ba1769530821c2b4ed2016b32d9447bec;p=clang clang-format: [JS] Better support for fat arrows. 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 --- diff --git a/lib/Format/Format.cpp b/lib/Format/Format.cpp index 98146471af..10c68f9da6 100644 --- a/lib/Format/Format.cpp +++ b/lib/Format/Format.cpp @@ -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 Kinds) { + bool tryMergeTokens(ArrayRef 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; } diff --git a/lib/Format/FormatToken.h b/lib/Format/FormatToken.h index 9325d0d52e..ec0fdf4aa8 100644 --- a/lib/Format/FormatToken.h +++ b/lib/Format/FormatToken.h @@ -51,6 +51,7 @@ enum TokenType { TT_InlineASMBrace, TT_InlineASMColon, TT_JavaAnnotation, + TT_JsFatArrow, TT_JsTypeColon, TT_JsTypeOptionalQuestion, TT_LambdaArrow, diff --git a/lib/Format/UnwrappedLineParser.cpp b/lib/Format/UnwrappedLineParser.cpp index 5b1a448567..939528fbff 100644 --- a/lib/Format/UnwrappedLineParser.cpp +++ b/lib/Format/UnwrappedLineParser.cpp @@ -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: diff --git a/lib/Format/UnwrappedLineParser.h b/lib/Format/UnwrappedLineParser.h index c2fa029576..6a6e56fea0 100644 --- a/lib/Format/UnwrappedLineParser.h +++ b/lib/Format/UnwrappedLineParser.h @@ -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(); diff --git a/unittests/Format/FormatTestJS.cpp b/unittests/Format/FormatTestJS.cpp index 41ffbedea7..1481ef0f28 100644 --- a/unittests/Format/FormatTestJS.cpp +++ b/unittests/Format/FormatTestJS.cpp @@ -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>;"); + verifyFormat("var x = {y: function(): z { return 1; }};"); + verifyFormat("var x = {y: function(): {a: number} { return 1; }};"); } TEST_F(FormatTestJS, ClassDeclarations) {