From 9326f919225688c87e75fca90f496d7f326e942d Mon Sep 17 00:00:00 2001 From: Daniel Jasper Date: Tue, 5 May 2015 08:40:32 +0000 Subject: [PATCH] clang-format: [JS] support optional methods. Optional methods use ? tokens like this: interface X { y?(): z; } It seems easiest to detect and disambiguate these from ternary expressions by checking if the code is in a declaration context. Turns out that that didn't quite work properly for interfaces in Java and JS, and for JS file root contexts. Patch by Martin Probst, thank you. llvm-svn: 236488 --- clang/lib/Format/TokenAnnotator.cpp | 52 +++++++++++++++++++++++--------- clang/lib/Format/UnwrappedLineParser.cpp | 24 ++++++++++----- clang/unittests/Format/FormatTestJS.cpp | 4 +++ 3 files changed, 58 insertions(+), 22 deletions(-) diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index aa415a5..4519357 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -422,11 +422,12 @@ private: return false; // Colons from ?: are handled in parseConditional(). if (Style.Language == FormatStyle::LK_JavaScript) { - if (Contexts.back().ColonIsForRangeExpr || - (Contexts.size() == 1 && + if (Contexts.back().ColonIsForRangeExpr || // colon in for loop + (Contexts.size() == 1 && // switch/case labels !Line.First->isOneOf(tok::kw_enum, tok::kw_case)) || - Contexts.back().ContextKind == tok::l_paren || - Contexts.back().ContextKind == tok::l_square) { + Contexts.back().ContextKind == tok::l_paren || // function params + Contexts.back().ContextKind == tok::l_square || // array type + Line.MustBeDeclaration) { // method/property declaration Tok->Type = TT_JsTypeColon; break; } @@ -533,14 +534,20 @@ private: break; case tok::question: if (Style.Language == FormatStyle::LK_JavaScript && Tok->Next && - Tok->Next->isOneOf(tok::colon, tok::semi, tok::r_paren, + Tok->Next->isOneOf(tok::semi, tok::colon, tok::r_paren, tok::r_brace)) { - // Question marks before semicolons, colons, commas, etc. indicate - // optional types (fields, parameters), e.g. - // `function(x?: string, y?) {...}` or `class X {y?;}` + // Question marks before semicolons, colons, etc. indicate optional + // types (fields, parameters), e.g. + // function(x?: string, y?) {...} + // class X { y?; } Tok->Type = TT_JsTypeOptionalQuestion; break; } + // Declarations cannot be conditional expressions, this can only be part + // of a type declaration. + if (Line.MustBeDeclaration && + Style.Language == FormatStyle::LK_JavaScript) + break; parseConditional(); break; case tok::kw_template: @@ -872,7 +879,14 @@ private: } else if (Current.isOneOf(tok::exclaim, tok::tilde)) { Current.Type = TT_UnaryOperator; } else if (Current.is(tok::question)) { - Current.Type = TT_ConditionalExpr; + if (Style.Language == FormatStyle::LK_JavaScript && + Line.MustBeDeclaration) { + // In JavaScript, `interface X { foo?(): bar; }` is an optional method + // on the interface, not a ternary expression. + Current.Type = TT_JsTypeOptionalQuestion; + } else { + Current.Type = TT_ConditionalExpr; + } } else if (Current.isBinaryOperator() && (!Current.Previous || Current.Previous->isNot(tok::l_square))) { Current.Type = TT_BinaryOperator; @@ -1854,12 +1868,20 @@ bool TokenAnnotator::spaceRequiredBefore(const AnnotatedLine &Line, return Right.is(tok::coloncolon); if (Right.is(TT_OverloadedOperatorLParen)) return false; - if (Right.is(tok::colon)) - return !Line.First->isOneOf(tok::kw_case, tok::kw_default) && - Right.getNextNonComment() && Right.isNot(TT_ObjCMethodExpr) && - !Left.is(tok::question) && - !(Right.is(TT_InlineASMColon) && Left.is(tok::coloncolon)) && - (Right.isNot(TT_DictLiteral) || Style.SpacesInContainerLiterals); + if (Right.is(tok::colon)) { + if (Line.First->isOneOf(tok::kw_case, tok::kw_default) || + !Right.getNextNonComment()) + return false; + if (Right.is(TT_ObjCMethodExpr)) + return false; + if (Left.is(tok::question)) + return false; + if (Right.is(TT_InlineASMColon) && Left.is(tok::coloncolon)) + return false; + if (Right.is(TT_DictLiteral)) + return Style.SpacesInContainerLiterals; + return true; + } if (Left.is(TT_UnaryOperator)) return Right.is(TT_BinaryOperator); if (Left.is(TT_CastRParen)) diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 46e59f9..a1fe9ce 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -262,9 +262,12 @@ bool UnwrappedLineParser::parse() { } void UnwrappedLineParser::parseFile() { - ScopedDeclarationState DeclarationState( - *Line, DeclarationScopeStack, - /*MustBeDeclaration=*/!Line->InPPDirective); + // The top-level context in a file always has declarations, except for pre- + // processor directives and JavaScript files. + bool MustBeDeclaration = + !Line->InPPDirective && Style.Language != FormatStyle::LK_JavaScript; + ScopedDeclarationState DeclarationState(*Line, DeclarationScopeStack, + MustBeDeclaration); parseLevel(/*HasOpeningBrace=*/false); // Make sure to format the remaining tokens. flushComments(true); @@ -837,14 +840,21 @@ void UnwrappedLineParser::parseStructuralElement() { parseTryCatch(); return; case tok::identifier: { - StringRef Text = FormatTok->TokenText; // Parse function literal unless 'function' is the first token in a line // in which case this should be treated as a free-standing function. - if (Style.Language == FormatStyle::LK_JavaScript && Text == "function" && - Line->Tokens.size() > 0) { + if (Style.Language == FormatStyle::LK_JavaScript && + FormatTok->is(Keywords.kw_function) && Line->Tokens.size() > 0) { tryToParseJSFunction(); break; } + if ((Style.Language == FormatStyle::LK_JavaScript || + Style.Language == FormatStyle::LK_Java) && + FormatTok->is(Keywords.kw_interface)) { + parseRecord(); + break; + } + + StringRef Text = FormatTok->TokenText; nextToken(); if (Line->Tokens.size() == 1 && // JS doesn't have macros, and within classes colons indicate fields, @@ -1577,7 +1587,7 @@ void UnwrappedLineParser::parseRecord() { // We fall through to parsing a structural element afterwards, so // class A {} n, m; // will end up in one unwrapped line. - // This does not apply for Java. + // This does not apply for Java and JavaScript. if (Style.Language == FormatStyle::LK_Java || Style.Language == FormatStyle::LK_JavaScript) addUnwrappedLine(); diff --git a/clang/unittests/Format/FormatTestJS.cpp b/clang/unittests/Format/FormatTestJS.cpp index 0c36dce..43fc4b4 100644 --- a/clang/unittests/Format/FormatTestJS.cpp +++ b/clang/unittests/Format/FormatTestJS.cpp @@ -709,6 +709,10 @@ TEST_F(FormatTestJS, OptionalTypes) { " y?: z;\n" " z?;\n" "}"); + verifyFormat("interface X {\n" + " y?(): z;\n" + "}"); + verifyFormat("x ? 1 : 2;"); } TEST_F(FormatTestJS, IndexSignature) { -- 2.7.4