From: Daniel Jasper Date: Wed, 21 May 2014 12:51:23 +0000 (+0000) Subject: clang-format: [JS] Support different function literal style. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c890573f2c2e042f1c585f925fc9a4edd1123662;p=clang clang-format: [JS] Support different function literal style. Before: goog.array.forEach(array, function() { doSomething(); doSomething(); }, this); After: goog.array.forEach(array, function() { doSomething(); doSomething(); }, this); git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@209291 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Format/ContinuationIndenter.cpp b/lib/Format/ContinuationIndenter.cpp index 63bb5e9918..389047945c 100644 --- a/lib/Format/ContinuationIndenter.cpp +++ b/lib/Format/ContinuationIndenter.cpp @@ -420,9 +420,16 @@ unsigned ContinuationIndenter::addTokenOnNewLine(LineState &State, // Any break on this level means that the parent level has been broken // and we need to avoid bin packing there. - for (unsigned i = 0, e = State.Stack.size() - 1; i != e; ++i) { - State.Stack[i].BreakBeforeParameter = true; + bool JavaScriptFormat = Style.Language == FormatStyle::LK_JavaScript && + Current.is(tok::r_brace) && + State.Stack.size() > 1 && + State.Stack[State.Stack.size() - 2].JSFunctionInlined; + if (!JavaScriptFormat) { + for (unsigned i = 0, e = State.Stack.size() - 1; i != e; ++i) { + State.Stack[i].BreakBeforeParameter = true; + } } + if (PreviousNonComment && !PreviousNonComment->isOneOf(tok::comma, tok::semi) && PreviousNonComment->Type != TT_TemplateCloser && @@ -465,6 +472,9 @@ unsigned ContinuationIndenter::getNewLineColumn(const LineState &State) { return Current.NestingLevel == 0 ? State.FirstIndent : State.Stack.back().Indent; if (Current.isOneOf(tok::r_brace, tok::r_square)) { + if (State.Stack.size() > 1 && + State.Stack[State.Stack.size() - 2].JSFunctionInlined) + return State.FirstIndent; if (Current.closesBlockTypeList(Style) || (Current.MatchingParen && Current.MatchingParen->BlockKind == BK_BracedInit)) @@ -600,6 +610,27 @@ unsigned ContinuationIndenter::moveStateToNextToken(LineState &State, // Insert scopes created by fake parenthesis. const FormatToken *Previous = Current.getPreviousNonComment(); + + // Add special behavior to support a format commonly used for JavaScript + // closures: + // SomeFunction(function() { + // foo(); + // bar(); + // }, a, b, c); + if (Style.Language == FormatStyle::LK_JavaScript) { + if (Current.isNot(tok::comment) && Previous && Previous->is(tok::l_brace) && + State.Stack.size() > 1) { + if (State.Stack[State.Stack.size() - 2].JSFunctionInlined && Newline) { + for (unsigned i = 0, e = State.Stack.size() - 1; i != e; ++i) { + State.Stack[i].NoLineBreak = true; + } + } + State.Stack[State.Stack.size() - 2].JSFunctionInlined = false; + } + if (Current.TokenText == "function") + State.Stack.back().JSFunctionInlined = !Newline; + } + // Don't add extra indentation for the first fake parenthesis after // 'return', assignments or opening <({[. The indentation for these cases // is special cased. diff --git a/lib/Format/ContinuationIndenter.h b/lib/Format/ContinuationIndenter.h index d81bf645aa..dc3e3086fd 100644 --- a/lib/Format/ContinuationIndenter.h +++ b/lib/Format/ContinuationIndenter.h @@ -139,7 +139,8 @@ struct ParenState { StartOfFunctionCall(0), StartOfArraySubscripts(0), NestedNameSpecifierContinuation(0), CallContinuation(0), VariablePos(0), ContainsLineBreak(false), ContainsUnwrappedBuilder(0), - AlignColons(true), ObjCSelectorNameFound(false), LambdasFound(0) {} + AlignColons(true), ObjCSelectorNameFound(false), LambdasFound(0), + JSFunctionInlined(false) {} /// \brief The position to which a specific parenthesis level needs to be /// indented. @@ -240,6 +241,10 @@ struct ParenState { /// the same token. unsigned LambdasFound; + // \brief The previous JavaScript 'function' keyword is not wrapped to a new + // line. + bool JSFunctionInlined; + bool operator<(const ParenState &Other) const { if (Indent != Other.Indent) return Indent < Other.Indent; @@ -273,6 +278,8 @@ struct ParenState { return ContainsLineBreak < Other.ContainsLineBreak; if (ContainsUnwrappedBuilder != Other.ContainsUnwrappedBuilder) return ContainsUnwrappedBuilder < Other.ContainsUnwrappedBuilder; + if (JSFunctionInlined != Other.JSFunctionInlined) + return JSFunctionInlined < Other.JSFunctionInlined; return false; } }; diff --git a/lib/Format/Format.cpp b/lib/Format/Format.cpp index ae8c75ff6d..891a7188bb 100644 --- a/lib/Format/Format.cpp +++ b/lib/Format/Format.cpp @@ -1146,8 +1146,13 @@ private: return true; if (NewLine) { - int AdditionalIndent = State.Stack.back().Indent - - Previous.Children[0]->Level * Style.IndentWidth; + int AdditionalIndent = 0; + if (State.Stack.size() < 2 || + !State.Stack[State.Stack.size() - 2].JSFunctionInlined) { + AdditionalIndent = State.Stack.back().Indent - + Previous.Children[0]->Level * Style.IndentWidth; + } + Penalty += format(Previous.Children, DryRun, AdditionalIndent, /*FixBadIndentation=*/true); return true; diff --git a/unittests/Format/FormatTestJS.cpp b/unittests/Format/FormatTestJS.cpp index 657f10874e..257c247765 100644 --- a/unittests/Format/FormatTestJS.cpp +++ b/unittests/Format/FormatTestJS.cpp @@ -120,6 +120,18 @@ TEST_F(FormatTestJS, Closures) { "};"); EXPECT_EQ("abc = xyz ? function() { return 1; } : function() { return -1; };", format("abc=xyz?function(){return 1;}:function(){return -1;};")); + + verifyFormat("var closure = goog.bind(\n" + " function() { // comment\n" + " foo();\n" + " bar();\n" + " },\n" + " this, arg1IsReallyLongAndNeeedsLineBreaks,\n" + " arg3IsReallyLongAndNeeedsLineBreaks);"); + verifyFormat("var closure = goog.bind(function() { // comment\n" + " foo();\n" + " bar();\n" + "}, this);"); } TEST_F(FormatTestJS, ReturnStatements) {