diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -68,6 +68,9 @@ TYPE(JsTypeColon) \ TYPE(JsTypeOperator) \ TYPE(JsTypeOptionalQuestion) \ + TYPE(JsAndAndEqual) \ + TYPE(JsPipePipeEqual) \ + TYPE(JsNullishCoalescingEqual) \ TYPE(LambdaArrow) \ TYPE(LambdaLBrace) \ TYPE(LambdaLSquare) \ diff --git a/clang/lib/Format/FormatTokenLexer.cpp b/clang/lib/Format/FormatTokenLexer.cpp --- a/clang/lib/Format/FormatTokenLexer.cpp +++ b/clang/lib/Format/FormatTokenLexer.cpp @@ -121,6 +121,10 @@ tok::period}; static const tok::TokenKind JSNullishOperator[] = {tok::question, tok::question}; + static const tok::TokenKind JSNullishEqual[] = {tok::question, + tok::question, tok::equal}; + static const tok::TokenKind JSPipePipeEqual[] = {tok::pipepipe, tok::equal}; + static const tok::TokenKind JSAndAndEqual[] = {tok::ampamp, tok::equal}; // FIXME: Investigate what token type gives the correct operator priority. if (tryMergeTokens(JSIdentity, TT_BinaryOperator)) @@ -148,6 +152,13 @@ Tokens.back()->Tok.setKind(tok::period); return; } + if (tryMergeTokens(JSAndAndEqual, TT_JsAndAndEqual) || + tryMergeTokens(JSPipePipeEqual, TT_JsPipePipeEqual) || + tryMergeTokens(JSNullishEqual, TT_JsNullishCoalescingEqual)) { + // Treat like the "=" assignment operator. + Tokens.back()->Tok.setKind(tok::equal); + return; + } if (tryMergeJSPrivateIdentifier()) return; } diff --git a/clang/unittests/Format/FormatTestJS.cpp b/clang/unittests/Format/FormatTestJS.cpp --- a/clang/unittests/Format/FormatTestJS.cpp +++ b/clang/unittests/Format/FormatTestJS.cpp @@ -2424,6 +2424,15 @@ getGoogleJSStyleWithColumns(40)); } +TEST_F(FormatTestJS, AssignmentOperators) { + verifyFormat("a &&= b;\n"); + verifyFormat("a ||= b;\n"); + // NB: need to split ? ?= to avoid it being interpreted by C++ as a trigraph + // for #. + verifyFormat("a ?" + "?= b;\n"); +} + TEST_F(FormatTestJS, Conditional) { verifyFormat("y = x ? 1 : 2;"); verifyFormat("x ? 1 : 2;");