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 @@ -960,6 +960,7 @@ kw_let = &IdentTable.get("let"); kw_module = &IdentTable.get("module"); kw_readonly = &IdentTable.get("readonly"); + kw_satisfies = &IdentTable.get("satisfies"); kw_set = &IdentTable.get("set"); kw_type = &IdentTable.get("type"); kw_typeof = &IdentTable.get("typeof"); @@ -1170,7 +1171,8 @@ JsExtraKeywords = std::unordered_set( {kw_as, kw_async, kw_await, kw_declare, kw_finally, kw_from, kw_function, kw_get, kw_import, kw_is, kw_let, kw_module, kw_override, - kw_readonly, kw_set, kw_type, kw_typeof, kw_var, kw_yield, + kw_readonly, kw_satisfies, kw_set, kw_type, kw_typeof, kw_var, + kw_yield, // Keywords from the Java section. kw_abstract, kw_extends, kw_implements, kw_instanceof, kw_interface}); @@ -1349,6 +1351,7 @@ IdentifierInfo *kw_let; IdentifierInfo *kw_module; IdentifierInfo *kw_readonly; + IdentifierInfo *kw_satisfies; IdentifierInfo *kw_set; IdentifierInfo *kw_type; IdentifierInfo *kw_typeof; diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -1882,7 +1882,8 @@ } } if (Current.Next && - Current.Next->isOneOf(TT_BinaryOperator, Keywords.kw_as)) { + Current.Next->isOneOf(TT_BinaryOperator, Keywords.kw_as, + Keywords.kw_satisfies)) { Current.setType(TT_NonNullAssertion); return; } @@ -2074,7 +2075,7 @@ return false; if (Tok.Previous->isOneOf(TT_LeadingJavaAnnotation, Keywords.kw_instanceof, - Keywords.kw_as)) { + Keywords.kw_as, Keywords.kw_satisfies)) { return false; } if (Style.isJavaScript() && Tok.Previous->is(Keywords.kw_in)) @@ -2725,7 +2726,8 @@ return prec::Relational; } if (Style.isJavaScript() && - Current->isOneOf(Keywords.kw_in, Keywords.kw_as)) { + Current->isOneOf(Keywords.kw_in, Keywords.kw_as, + Keywords.kw_satisfies)) { return prec::Relational; } if (Current->is(TT_BinaryOperator) || Current->is(tok::comma)) @@ -4268,11 +4270,12 @@ (!Left.Previous || !Left.Previous->is(tok::period))) { return true; } - if (Left.isOneOf(tok::kw_for, Keywords.kw_as) && Left.Previous && - Left.Previous->is(tok::period) && Right.is(tok::l_paren)) { + if (Left.isOneOf(tok::kw_for, Keywords.kw_as, Keywords.kw_satisfies) && + Left.Previous && Left.Previous->is(tok::period) && + Right.is(tok::l_paren)) { return false; } - if (Left.is(Keywords.kw_as) && + if (Left.isOneOf(Keywords.kw_as, Keywords.kw_satisfies) && Right.isOneOf(tok::l_square, tok::l_brace, tok::l_paren)) { return true; } @@ -4303,8 +4306,8 @@ if (Right.is(TT_NonNullAssertion)) return false; if (Left.is(TT_NonNullAssertion) && - Right.isOneOf(Keywords.kw_as, Keywords.kw_in)) { - return true; // "x! as string", "x! in y" + Right.isOneOf(Keywords.kw_as, Keywords.kw_satisfies, Keywords.kw_in)) { + return true; // "x! as string", "x! satisfies string", "x! in y" } } else if (Style.Language == FormatStyle::LK_Java) { if (Left.is(tok::r_square) && Right.is(tok::l_brace)) @@ -5108,15 +5111,16 @@ return Style.BreakBeforeBinaryOperators == FormatStyle::BOS_None; if (Right.is(Keywords.kw_in)) return Style.BreakBeforeBinaryOperators != FormatStyle::BOS_None; - if (Right.is(Keywords.kw_as)) - return false; // must not break before as in 'x as type' casts + if (Right.isOneOf(Keywords.kw_as, Keywords.kw_satisfies)) + return false; // must not break before as or satisfies in 'x as type' + // casts if (Right.isOneOf(Keywords.kw_extends, Keywords.kw_infer)) { // extends and infer can appear as keywords in conditional types: // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#conditional-types // do not break before them, as the expressions are subject to ASI. return false; } - if (Left.is(Keywords.kw_as)) + if (Left.isOneOf(Keywords.kw_as, Keywords.kw_satisfies)) return true; if (Left.is(TT_NonNullAssertion)) return true; diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -556,10 +556,11 @@ // must also be part of it. ProbablyBracedList = LBraceStack.back()->is(TT_BracedListLBrace); - ProbablyBracedList = ProbablyBracedList || + ProbablyBracedList = + ProbablyBracedList || (Style.isJavaScript() && - NextTok->isOneOf(Keywords.kw_of, Keywords.kw_in, - Keywords.kw_as)); + NextTok->isOneOf(Keywords.kw_of, Keywords.kw_in, Keywords.kw_as, + Keywords.kw_satisfies)); ProbablyBracedList = ProbablyBracedList || (Style.isCpp() && NextTok->is(tok::l_paren)); @@ -1213,7 +1214,8 @@ Keywords.kw_let, Keywords.kw_var, tok::kw_const, Keywords.kw_abstract, Keywords.kw_extends, Keywords.kw_implements, Keywords.kw_instanceof, Keywords.kw_interface, - Keywords.kw_override, Keywords.kw_throws, Keywords.kw_from)); + Keywords.kw_override, Keywords.kw_satisfies, Keywords.kw_throws, + Keywords.kw_from)); } static bool mustBeJSIdentOrValue(const AdditionalKeywords &Keywords, 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 @@ -2182,6 +2182,21 @@ getGoogleJSStyleWithColumns(40)); } +TEST_F(FormatTestJS, SatisfiesSyntax) { + verifyFormat("let x = foo satisfies Type;"); + verifyFormat("let x = (a + b) satisfies\n" + " LongTypeIsLong;", + getGoogleJSStyleWithColumns(30)); + verifyFormat("let x = [{x: 1} satisfies Type];"); + verifyFormat("x = x satisfies [A, B];"); + verifyFormat("x = x satisfies {a: string};"); + verifyFormat("x = x satisfies (string);"); + verifyFormat("x = x! satisfies (string);"); + verifyFormat("let x = something.someFunction() satisfies\n" + " LongTypeIsLong;", + getGoogleJSStyleWithColumns(50)); +} + TEST_F(FormatTestJS, TypeArguments) { verifyFormat("class X {}"); verifyFormat("new X();");