Skip to content

Commit 146d685

Browse files
committed
clang-format: [JS] detect C++ keywords.
Summary: C++ defines a number of keywords that are regular identifiers in JavaScript, e.g. `concept`: const concept = 1; // legit JS This change expands the existing `IsJavaScriptIdentifier(Tok)` function to return false for C++ keywords that aren't keywords in JS. Reviewers: krasimir Subscribers: jfb, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D77311
1 parent 0718e3a commit 146d685

File tree

3 files changed

+118
-17
lines changed

3 files changed

+118
-17
lines changed

clang/lib/Format/FormatToken.h

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -910,9 +910,64 @@ struct AdditionalKeywords {
910910
/// Returns \c true if \p Tok is a true JavaScript identifier, returns
911911
/// \c false if it is a keyword or a pseudo keyword.
912912
bool IsJavaScriptIdentifier(const FormatToken &Tok) const {
913-
return Tok.is(tok::identifier) &&
914-
JsExtraKeywords.find(Tok.Tok.getIdentifierInfo()) ==
915-
JsExtraKeywords.end();
913+
// Based on the list of JavaScript & TypeScript keywords here:
914+
// https://github.com/microsoft/TypeScript/blob/master/src/compiler/scanner.ts#L74
915+
switch (Tok.Tok.getKind()) {
916+
case tok::kw_break:
917+
case tok::kw_case:
918+
case tok::kw_catch:
919+
case tok::kw_class:
920+
case tok::kw_continue:
921+
case tok::kw_const:
922+
case tok::kw_default:
923+
case tok::kw_delete:
924+
case tok::kw_do:
925+
case tok::kw_else:
926+
case tok::kw_enum:
927+
case tok::kw_export:
928+
case tok::kw_false:
929+
case tok::kw_for:
930+
case tok::kw_if:
931+
case tok::kw_import:
932+
case tok::kw_module:
933+
case tok::kw_new:
934+
case tok::kw_private:
935+
case tok::kw_protected:
936+
case tok::kw_public:
937+
case tok::kw_return:
938+
case tok::kw_static:
939+
case tok::kw_switch:
940+
case tok::kw_this:
941+
case tok::kw_throw:
942+
case tok::kw_true:
943+
case tok::kw_try:
944+
case tok::kw_typeof:
945+
case tok::kw_void:
946+
case tok::kw_while:
947+
// These are JS keywords that are lexed by LLVM/clang as keywords.
948+
return false;
949+
case tok::identifier:
950+
// For identifiers, make sure they are true identifiers, excluding the
951+
// JavaScript pseudo-keywords (not lexed by LLVM/clang as keywords).
952+
return JsExtraKeywords.find(Tok.Tok.getIdentifierInfo()) ==
953+
JsExtraKeywords.end();
954+
default:
955+
// Other keywords are handled in the switch below, to avoid problems due
956+
// to duplicate case labels when using the #include trick.
957+
break;
958+
}
959+
960+
switch (Tok.Tok.getKind()) {
961+
// Handle C++ keywords not included above: these are all JS identifiers.
962+
#define KEYWORD(X, Y) case tok::kw_##X:
963+
#include "clang/Basic/TokenKinds.def"
964+
// #undef KEYWORD is not needed -- it's #undef-ed at the end of
965+
// TokenKinds.def
966+
return true;
967+
default:
968+
// All other tokens (punctuation etc) are not JS identifiers.
969+
return false;
970+
}
916971
}
917972

918973
/// Returns \c true if \p Tok is a C# keyword, returns

clang/lib/Format/TokenAnnotator.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,9 +1522,9 @@ class AnnotatingParser {
15221522
if (Style.Language == FormatStyle::LK_JavaScript) {
15231523
if (Current.is(tok::exclaim)) {
15241524
if (Current.Previous &&
1525-
(Current.Previous->isOneOf(tok::identifier, tok::kw_namespace,
1526-
tok::r_paren, tok::r_square,
1527-
tok::r_brace) ||
1525+
(Keywords.IsJavaScriptIdentifier(*Current.Previous) ||
1526+
Current.Previous->isOneOf(tok::kw_namespace, tok::r_paren,
1527+
tok::r_square, tok::r_brace) ||
15281528
Current.Previous->Tok.isLiteral())) {
15291529
Current.Type = TT_JsNonNullAssertion;
15301530
return;
@@ -3070,10 +3070,8 @@ bool TokenAnnotator::spaceRequiredBefore(const AnnotatedLine &Line,
30703070
(Right.is(TT_TemplateString) && Right.TokenText.startswith("}")))
30713071
return false;
30723072
// In tagged template literals ("html`bar baz`"), there is no space between
3073-
// the tag identifier and the template string. getIdentifierInfo makes sure
3074-
// that the identifier is not a pseudo keyword like `yield`, either.
3075-
if (Left.is(tok::identifier) && Keywords.IsJavaScriptIdentifier(Left) &&
3076-
Right.is(TT_TemplateString))
3073+
// the tag identifier and the template string.
3074+
if (Keywords.IsJavaScriptIdentifier(Left) && Right.is(TT_TemplateString))
30773075
return false;
30783076
if (Right.is(tok::star) &&
30793077
Left.isOneOf(Keywords.kw_function, Keywords.kw_yield))

clang/unittests/Format/FormatTestJS.cpp

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -386,13 +386,6 @@ TEST_F(FormatTestJS, ReservedWordsParenthesized) {
386386
"return (x);\n");
387387
}
388388

389-
TEST_F(FormatTestJS, CppKeywords) {
390-
// Make sure we don't mess stuff up because of C++ keywords.
391-
verifyFormat("return operator && (aa);");
392-
// .. or QT ones.
393-
verifyFormat("slots: Slot[];");
394-
}
395-
396389
TEST_F(FormatTestJS, ES6DestructuringAssignment) {
397390
verifyFormat("var [a, b, c] = [1, 2, 3];");
398391
verifyFormat("const [a, b, c] = [1, 2, 3];");
@@ -2366,6 +2359,61 @@ TEST_F(FormatTestJS, NonNullAssertionOperator) {
23662359
verifyFormat("return !!x;\n");
23672360
}
23682361

2362+
TEST_F(FormatTestJS, CppKeywords) {
2363+
// Make sure we don't mess stuff up because of C++ keywords.
2364+
verifyFormat("return operator && (aa);");
2365+
// .. or QT ones.
2366+
verifyFormat("const slots: Slot[];");
2367+
// use the "!" assertion operator to validate that clang-format understands
2368+
// these C++ keywords aren't keywords in JS/TS.
2369+
verifyFormat("auto!;");
2370+
verifyFormat("char!;");
2371+
verifyFormat("concept!;");
2372+
verifyFormat("double!;");
2373+
verifyFormat("extern!;");
2374+
verifyFormat("float!;");
2375+
verifyFormat("inline!;");
2376+
verifyFormat("int!;");
2377+
verifyFormat("long!;");
2378+
verifyFormat("register!;");
2379+
verifyFormat("restrict!;");
2380+
verifyFormat("sizeof!;");
2381+
verifyFormat("struct!;");
2382+
verifyFormat("typedef!;");
2383+
verifyFormat("union!;");
2384+
verifyFormat("unsigned!;");
2385+
verifyFormat("volatile!;");
2386+
verifyFormat("_Alignas!;");
2387+
verifyFormat("_Alignof!;");
2388+
verifyFormat("_Atomic!;");
2389+
verifyFormat("_Bool!;");
2390+
verifyFormat("_Complex!;");
2391+
verifyFormat("_Generic!;");
2392+
verifyFormat("_Imaginary!;");
2393+
verifyFormat("_Noreturn!;");
2394+
verifyFormat("_Static_assert!;");
2395+
verifyFormat("_Thread_local!;");
2396+
verifyFormat("__func__!;");
2397+
verifyFormat("__objc_yes!;");
2398+
verifyFormat("__objc_no!;");
2399+
verifyFormat("asm!;");
2400+
verifyFormat("bool!;");
2401+
verifyFormat("const_cast!;");
2402+
verifyFormat("dynamic_cast!;");
2403+
verifyFormat("explicit!;");
2404+
verifyFormat("friend!;");
2405+
verifyFormat("mutable!;");
2406+
verifyFormat("operator!;");
2407+
verifyFormat("reinterpret_cast!;");
2408+
verifyFormat("static_cast!;");
2409+
verifyFormat("template!;");
2410+
verifyFormat("typename!;");
2411+
verifyFormat("typeid!;");
2412+
verifyFormat("using!;");
2413+
verifyFormat("virtual!;");
2414+
verifyFormat("wchar_t!;");
2415+
}
2416+
23692417
TEST_F(FormatTestJS, NullPropagatingOperator) {
23702418
verifyFormat("let x = foo?.bar?.baz();\n");
23712419
verifyFormat("let x = foo?.(foo);\n");

0 commit comments

Comments
 (0)