Commit a94c91ca authored by Toon Verwaest's avatar Toon Verwaest Committed by Commit Bot

[parser] Fix escaped contextual keyword handling

Escaped contextual keywords are simply valid identifiers if they do not occur
in the context where they are a keyword. Escape sequences of the form \uNNNN
or \u{NNNNNN} must be consumed as part of the identifier.

If such escaped contextual keywords do occur in a context where they are a
keyword, they are a syntax error. In that case we manually check locally
whether they are escaped.

Bug: v8:6543, v8:6541

Change-Id: I7e1557963883e722310b9078d7d7636ec94aa603
Reviewed-on: https://chromium-review.googlesource.com/c/1473293Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Commit-Queue: Toon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59628}
parent 5d1d0795
......@@ -49,14 +49,14 @@ struct PerfectKeywordHashTableEntry {
Token::Value value;
};
enum {
TOTAL_KEYWORDS = 47,
TOTAL_KEYWORDS = 49,
MIN_WORD_LENGTH = 2,
MAX_WORD_LENGTH = 10,
MIN_HASH_VALUE = 2,
MAX_HASH_VALUE = 51
MAX_HASH_VALUE = 55
};
/* maximum key range = 50, duplicates = 0 */
/* maximum key range = 54, duplicates = 0 */
class PerfectKeywordHash {
private:
......@@ -70,22 +70,22 @@ inline unsigned int PerfectKeywordHash::Hash(const char* str, int len) {
DCHECK_LT(str[1], 128);
DCHECK_LT(str[0], 128);
static const unsigned char asso_values[128] = {
52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
52, 8, 2, 6, 0, 0, 9, 52, 21, 0, 52, 52, 36, 40, 0, 3,
6, 52, 17, 13, 16, 16, 38, 25, 6, 26, 52, 52, 52, 52, 52, 52};
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 8, 0, 6, 0, 0, 9, 9, 9, 0, 56, 56, 34, 41, 0, 3,
6, 56, 19, 10, 13, 16, 39, 26, 37, 36, 56, 56, 56, 56, 56, 56};
return len + asso_values[static_cast<unsigned char>(str[1])] +
asso_values[static_cast<unsigned char>(str[0])];
}
static const unsigned char kPerfectKeywordLengthTable[64] = {
0, 0, 2, 3, 4, 2, 6, 7, 8, 9, 10, 2, 6, 7, 5, 3, 7, 8, 4, 5, 4, 7,
5, 6, 5, 0, 5, 0, 6, 4, 7, 5, 9, 8, 5, 6, 3, 4, 5, 3, 4, 4, 5, 0,
6, 4, 6, 5, 6, 3, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
0, 0, 2, 3, 4, 2, 6, 7, 8, 9, 10, 2, 3, 3, 5, 3, 7, 8, 4, 5, 4, 7,
5, 5, 5, 6, 4, 5, 6, 6, 4, 5, 7, 8, 9, 3, 4, 3, 4, 5, 5, 5, 6, 6,
7, 5, 4, 6, 0, 0, 3, 10, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0};
static const struct PerfectKeywordHashTableEntry kPerfectKeywordHashTable[64] =
{{"", Token::IDENTIFIER},
......@@ -100,8 +100,8 @@ static const struct PerfectKeywordHashTableEntry kPerfectKeywordHashTable[64] =
{"interface", Token::FUTURE_STRICT_RESERVED_WORD},
{"instanceof", Token::INSTANCEOF},
{"if", Token::IF},
{"export", Token::EXPORT},
{"extends", Token::EXTENDS},
{"get", Token::GET},
{"set", Token::SET},
{"const", Token::CONST},
{"for", Token::FOR},
{"finally", Token::FINALLY},
......@@ -111,39 +111,39 @@ static const struct PerfectKeywordHashTableEntry kPerfectKeywordHashTable[64] =
{"null", Token::NULL_LITERAL},
{"package", Token::FUTURE_STRICT_RESERVED_WORD},
{"false", Token::FALSE_LITERAL},
{"return", Token::RETURN},
{"break", Token::BREAK},
{"", Token::IDENTIFIER},
{"async", Token::ASYNC},
{"", Token::IDENTIFIER},
{"break", Token::BREAK},
{"return", Token::RETURN},
{"this", Token::THIS},
{"throw", Token::THROW},
{"public", Token::FUTURE_STRICT_RESERVED_WORD},
{"static", Token::STATIC},
{"with", Token::WITH},
{"super", Token::SUPER},
{"private", Token::FUTURE_STRICT_RESERVED_WORD},
{"yield", Token::YIELD},
{"protected", Token::FUTURE_STRICT_RESERVED_WORD},
{"function", Token::FUNCTION},
{"super", Token::SUPER},
{"static", Token::STATIC},
{"protected", Token::FUTURE_STRICT_RESERVED_WORD},
{"try", Token::TRY},
{"true", Token::TRUE_LITERAL},
{"await", Token::AWAIT},
{"let", Token::LET},
{"else", Token::ELSE},
{"this", Token::THIS},
{"throw", Token::THROW},
{"", Token::IDENTIFIER},
{"await", Token::AWAIT},
{"while", Token::WHILE},
{"yield", Token::YIELD},
{"switch", Token::SWITCH},
{"export", Token::EXPORT},
{"extends", Token::EXTENDS},
{"class", Token::CLASS},
{"void", Token::VOID},
{"import", Token::IMPORT},
{"class", Token::CLASS},
{"typeof", Token::TYPEOF},
{"", Token::IDENTIFIER},
{"", Token::IDENTIFIER},
{"var", Token::VAR},
{"implements", Token::FUTURE_STRICT_RESERVED_WORD},
{"while", Token::WHILE},
{"", Token::IDENTIFIER},
{"", Token::IDENTIFIER},
{"", Token::IDENTIFIER},
{"", Token::IDENTIFIER},
{"typeof", Token::TYPEOF},
{"", Token::IDENTIFIER},
{"", Token::IDENTIFIER},
{"", Token::IDENTIFIER},
......
......@@ -35,6 +35,7 @@ false, Token::FALSE_LITERAL
finally, Token::FINALLY
for, Token::FOR
function, Token::FUNCTION
get, Token::GET
if, Token::IF
implements, Token::FUTURE_STRICT_RESERVED_WORD
import, Token::IMPORT
......@@ -49,6 +50,7 @@ private, Token::FUTURE_STRICT_RESERVED_WORD
protected, Token::FUTURE_STRICT_RESERVED_WORD
public, Token::FUTURE_STRICT_RESERVED_WORD
return, Token::RETURN
set, Token::SET
static, Token::STATIC
super, Token::SUPER
switch, Token::SWITCH
......
......@@ -797,6 +797,7 @@ class ParserBase {
bool PeekContextualKeyword(const AstRawString* name) {
return peek() == Token::IDENTIFIER &&
!scanner()->next_literal_contains_escapes() &&
scanner()->NextSymbol(ast_value_factory()) == name;
}
......@@ -808,14 +809,21 @@ class ParserBase {
return false;
}
void ExpectMetaProperty(const AstRawString* property_name,
const char* full_name, int pos);
void ExpectContextualKeyword(const AstRawString* name) {
void ExpectContextualKeyword(const AstRawString* name,
const char* fullname = nullptr, int pos = -1) {
Expect(Token::IDENTIFIER);
if (V8_UNLIKELY(scanner()->CurrentSymbol(ast_value_factory()) != name)) {
ReportUnexpectedToken(scanner()->current_token());
}
if (V8_UNLIKELY(scanner()->literal_contains_escapes())) {
const char* full = fullname == nullptr
? reinterpret_cast<const char*>(name->raw_data())
: fullname;
int start = pos == -1 ? position() : pos;
impl()->ReportMessageAt(Scanner::Location(start, end_position()),
MessageTemplate::kInvalidEscapedMetaProperty,
full);
}
}
bool CheckInOrOf(ForEachStatement::VisitMode* visit_mode) {
......@@ -1472,7 +1480,6 @@ template <typename Impl>
typename ParserBase<Impl>::IdentifierT
ParserBase<Impl>::ParseAndClassifyIdentifier(Token::Value next) {
DCHECK_EQ(scanner()->current_token(), next);
STATIC_ASSERT(Token::IDENTIFIER + 1 == Token::ASYNC);
if (V8_LIKELY(IsInRange(next, Token::IDENTIFIER, Token::ASYNC))) {
IdentifierT name = impl()->GetSymbol();
if (V8_UNLIKELY(impl()->IsArguments(name) &&
......@@ -1648,7 +1655,8 @@ ParserBase<Impl>::ParsePrimaryExpression() {
FunctionKind kind = FunctionKind::kArrowFunction;
if (V8_UNLIKELY(token == Token::ASYNC &&
!scanner()->HasLineTerminatorBeforeNext())) {
!scanner()->HasLineTerminatorBeforeNext() &&
!scanner()->literal_contains_escapes())) {
// async function ...
if (peek() == Token::FUNCTION) return ParseAsyncFunctionLiteral();
......@@ -1929,6 +1937,9 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseProperty(
impl()->PushLiteralName(prop_info->name);
return factory()->NewStringLiteral(prop_info->name, position());
}
if (V8_UNLIKELY(scanner()->literal_contains_escapes())) {
impl()->ReportUnexpectedToken(Token::ESCAPED_KEYWORD);
}
prop_info->function_flags = ParseFunctionFlag::kIsAsync;
prop_info->kind = ParsePropertyKind::kMethod;
}
......@@ -1939,21 +1950,21 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseProperty(
}
if (prop_info->kind == ParsePropertyKind::kNotSet &&
Check(Token::IDENTIFIER)) {
IdentifierT symbol = impl()->GetSymbol();
if (!prop_info->ParsePropertyKindFromToken(peek())) {
if (impl()->IdentifierEquals(symbol, ast_value_factory()->get_string())) {
prop_info->kind = ParsePropertyKind::kAccessorGetter;
} else if (impl()->IdentifierEquals(symbol,
ast_value_factory()->set_string())) {
prop_info->kind = ParsePropertyKind::kAccessorSetter;
}
}
if (!IsAccessor(prop_info->kind)) {
prop_info->name = symbol;
IsInRange(peek(), Token::GET, Token::SET)) {
Token::Value token = Next();
if (prop_info->ParsePropertyKindFromToken(peek())) {
prop_info->name = impl()->GetSymbol();
impl()->PushLiteralName(prop_info->name);
return factory()->NewStringLiteral(prop_info->name, position());
}
if (V8_UNLIKELY(scanner()->literal_contains_escapes())) {
impl()->ReportUnexpectedToken(Token::ESCAPED_KEYWORD);
}
if (token == Token::GET) {
prop_info->kind = ParsePropertyKind::kAccessorGetter;
} else if (token == Token::SET) {
prop_info->kind = ParsePropertyKind::kAccessorSetter;
}
}
int pos = peek_position();
......@@ -2682,6 +2693,9 @@ ParserBase<Impl>::ParseYieldExpression() {
expression_scope()->RecordParameterInitializerError(
scanner()->peek_location(), MessageTemplate::kYieldInParameter);
Consume(Token::YIELD);
if (V8_UNLIKELY(scanner()->literal_contains_escapes())) {
impl()->ReportUnexpectedToken(Token::ESCAPED_KEYWORD);
}
CheckStackOverflow();
......@@ -2906,6 +2920,9 @@ ParserBase<Impl>::ParseAwaitExpression() {
MessageTemplate::kAwaitExpressionFormalParameter);
int await_pos = peek_position();
Consume(Token::AWAIT);
if (V8_UNLIKELY(scanner()->literal_contains_escapes())) {
impl()->ReportUnexpectedToken(Token::ESCAPED_KEYWORD);
}
CheckStackOverflow();
......@@ -2987,7 +3004,8 @@ ParserBase<Impl>::ParseLeftHandSideContinuation(ExpressionT result) {
if (V8_UNLIKELY(peek() == Token::LPAREN && impl()->IsIdentifier(result) &&
scanner()->current_token() == Token::ASYNC &&
!scanner()->HasLineTerminatorBeforeNext())) {
!scanner()->HasLineTerminatorBeforeNext() &&
!scanner()->literal_contains_escapes())) {
DCHECK(impl()->IsAsync(impl()->AsIdentifier(result)));
int pos = position();
......@@ -3249,8 +3267,9 @@ ParserBase<Impl>::ParseImportExpressions() {
Consume(Token::IMPORT);
int pos = position();
if (allow_harmony_import_meta() && peek() == Token::PERIOD) {
ExpectMetaProperty(ast_value_factory()->meta_string(), "import.meta", pos);
if (allow_harmony_import_meta() && Check(Token::PERIOD)) {
ExpectContextualKeyword(ast_value_factory()->meta_string(), "import.meta",
pos);
if (!parsing_module_) {
impl()->ReportMessageAt(scanner()->location(),
MessageTemplate::kImportMetaOutsideModule);
......@@ -3303,23 +3322,13 @@ typename ParserBase<Impl>::ExpressionT ParserBase<Impl>::ParseSuperExpression(
return impl()->FailureExpression();
}
template <typename Impl>
void ParserBase<Impl>::ExpectMetaProperty(const AstRawString* property_name,
const char* full_name, int pos) {
Consume(Token::PERIOD);
ExpectContextualKeyword(property_name);
if (V8_UNLIKELY(scanner()->literal_contains_escapes())) {
impl()->ReportMessageAt(Scanner::Location(pos, end_position()),
MessageTemplate::kInvalidEscapedMetaProperty,
full_name);
}
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseNewTargetExpression() {
int pos = position();
ExpectMetaProperty(ast_value_factory()->target_string(), "new.target", pos);
Consume(Token::PERIOD);
ExpectContextualKeyword(ast_value_factory()->target_string(), "new.target",
pos);
if (!GetReceiverScope()->is_function_scope()) {
impl()->ReportMessageAt(scanner()->location(),
......@@ -3801,6 +3810,9 @@ ParserBase<Impl>::ParseAsyncFunctionDeclaration(
// async [no LineTerminator here] function BindingIdentifier[Await]
// ( FormalParameters[Await] ) { AsyncFunctionBody }
DCHECK_EQ(scanner()->current_token(), Token::ASYNC);
if (V8_UNLIKELY(scanner()->literal_contains_escapes())) {
impl()->ReportUnexpectedToken(Token::ESCAPED_KEYWORD);
}
int pos = position();
DCHECK(!scanner()->HasLineTerminatorBeforeNext());
Consume(Token::FUNCTION);
......@@ -3978,6 +3990,8 @@ bool ParserBase<Impl>::IsNextLetKeyword() {
// tokens.
case Token::YIELD:
case Token::AWAIT:
case Token::GET:
case Token::SET:
case Token::ASYNC:
return true;
case Token::FUTURE_STRICT_RESERVED_WORD:
......@@ -4258,6 +4272,9 @@ ParserBase<Impl>::ParseAsyncFunctionLiteral() {
// async [no LineTerminator here] function BindingIdentifier[Await]
// ( FormalParameters[Await] ) { AsyncFunctionBody }
DCHECK_EQ(scanner()->current_token(), Token::ASYNC);
if (V8_UNLIKELY(scanner()->literal_contains_escapes())) {
impl()->ReportUnexpectedToken(Token::ESCAPED_KEYWORD);
}
int pos = position();
Consume(Token::FUNCTION);
IdentifierT name = impl()->NullIdentifier();
......
......@@ -42,6 +42,8 @@ namespace internal {
KEYWORD("finally", Token::FINALLY) \
KEYWORD("for", Token::FOR) \
KEYWORD("function", Token::FUNCTION) \
KEYWORD_GROUP('g') \
KEYWORD("get", Token::GET) \
KEYWORD_GROUP('i') \
KEYWORD("if", Token::IF) \
KEYWORD("implements", Token::FUTURE_STRICT_RESERVED_WORD) \
......@@ -62,6 +64,7 @@ namespace internal {
KEYWORD_GROUP('r') \
KEYWORD("return", Token::RETURN) \
KEYWORD_GROUP('s') \
KEYWORD("set", Token::SET) \
KEYWORD("static", Token::STATIC) \
KEYWORD("super", Token::SUPER) \
KEYWORD("switch", Token::SWITCH) \
......
......@@ -1007,16 +1007,17 @@ Token::Value Scanner::ScanIdentifierOrKeywordInnerSlow(bool escaped,
Vector<const uint8_t> chars = next().literal_chars.one_byte_literal();
Token::Value token =
KeywordOrIdentifierToken(chars.start(), chars.length());
/* TODO(adamk): YIELD should be handled specially. */
if (IsInRange(token, Token::IDENTIFIER, Token::YIELD)) return token;
if (token == Token::FUTURE_STRICT_RESERVED_WORD) {
if (escaped) return Token::ESCAPED_STRICT_RESERVED_WORD;
return token;
}
if (token == Token::IDENTIFIER) return token;
if (!escaped) return token;
if (token == Token::LET || token == Token::STATIC) {
STATIC_ASSERT(Token::LET + 1 == Token::STATIC);
if (IsInRange(token, Token::LET, Token::STATIC)) {
return Token::ESCAPED_STRICT_RESERVED_WORD;
}
return Token::ESCAPED_KEYWORD;
......
......@@ -316,6 +316,10 @@ class Scanner {
return LiteralContainsEscapes(current());
}
bool next_literal_contains_escapes() const {
return LiteralContainsEscapes(next());
}
const AstRawString* CurrentSymbol(AstValueFactory* ast_value_factory) const;
const AstRawString* NextSymbol(AstValueFactory* ast_value_factory) const;
......@@ -517,7 +521,6 @@ class Scanner {
bool CanAccessLiteral() const {
return token == Token::PRIVATE_NAME || token == Token::ILLEGAL ||
token == Token::UNINITIALIZED || token == Token::REGEXP_LITERAL ||
token == Token::ESCAPED_KEYWORD ||
IsInRange(token, Token::NUMBER, Token::STRING) ||
(Token::IsAnyIdentifier(token) && !Token::IsKeyword(token)) ||
IsInRange(token, Token::TEMPLATE_SPAN, Token::TEMPLATE_TAIL);
......
......@@ -34,8 +34,7 @@ const int8_t Token::precedence_[2][NUM_TOKENS] = {{TOKEN_LIST(T1, T1)},
#undef T2
#undef T1
#define KT(a, b, c) \
IsPropertyNameBits::encode(Token::IsAnyIdentifier(a) || a == ESCAPED_KEYWORD),
#define KT(a, b, c) IsPropertyNameBits::encode(Token::IsAnyIdentifier(a)),
#define KK(a, b, c) \
IsKeywordBits::encode(true) | IsPropertyNameBits::encode(true),
const uint8_t Token::token_flags[] = {TOKEN_LIST(KT, KK)};
......
......@@ -171,6 +171,8 @@ namespace internal {
/* BEGIN AnyIdentifier */ \
/* Identifiers (not keywords or future reserved words). */ \
T(IDENTIFIER, nullptr, 0) \
K(GET, "get", 0) \
K(SET, "set", 0) \
K(ASYNC, "async", 0) \
/* `await` is a reserved word in module code only */ \
K(AWAIT, "await", 0) \
......
......@@ -92,6 +92,8 @@ TEST(AutoSemicolonToken) {
bool TokenIsAnyIdentifier(Token::Value token) {
switch (token) {
case Token::IDENTIFIER:
case Token::GET:
case Token::SET:
case Token::ASYNC:
case Token::AWAIT:
case Token::YIELD:
......@@ -116,6 +118,8 @@ bool TokenIsCallable(Token::Value token) {
switch (token) {
case Token::SUPER:
case Token::IDENTIFIER:
case Token::GET:
case Token::SET:
case Token::ASYNC:
case Token::AWAIT:
case Token::YIELD:
......@@ -140,6 +144,8 @@ bool TokenIsValidIdentifier(Token::Value token, LanguageMode language_mode,
bool is_generator, bool disallow_await) {
switch (token) {
case Token::IDENTIFIER:
case Token::GET:
case Token::SET:
case Token::ASYNC:
return true;
case Token::YIELD:
......@@ -840,19 +846,9 @@ TEST(StreamScanner) {
std::unique_ptr<i::Utf16CharacterStream> stream1(
i::ScannerStream::ForTesting(str1));
i::Token::Value expectations1[] = {
i::Token::LBRACE,
i::Token::IDENTIFIER,
i::Token::IDENTIFIER,
i::Token::FOR,
i::Token::COLON,
i::Token::MUL,
i::Token::DIV,
i::Token::LT,
i::Token::SUB,
i::Token::IDENTIFIER,
i::Token::EOS,
i::Token::ILLEGAL
};
i::Token::LBRACE, i::Token::IDENTIFIER, i::Token::GET, i::Token::FOR,
i::Token::COLON, i::Token::MUL, i::Token::DIV, i::Token::LT,
i::Token::SUB, i::Token::IDENTIFIER, i::Token::EOS, i::Token::ILLEGAL};
TestStreamScanner(stream1.get(), expectations1, 0, 0);
const char* str2 = "case default const {THIS\nPART\nSKIPPED} do";
......@@ -9379,11 +9375,10 @@ TEST(EscapedKeywords) {
"class C { st\\u0061tic *bar() {} }",
"class C { st\\u0061tic get bar() {} }",
"class C { st\\u0061tic set bar() {} }",
// TODO(adamk): These should not be errors in sloppy mode.
"(y\\u0069eld);",
"var y\\u0069eld = 1;",
"var { y\\u0069eld } = {};",
"(async ()=>{\\u0061wait 100})()",
"({\\u0067et get(){}})",
"({\\u0073et set(){}})",
"(async ()=>{var \\u0061wait = 100})()",
nullptr
};
// clang-format on
......@@ -9397,6 +9392,9 @@ TEST(EscapedKeywords) {
"var l\\u0065t = 1;",
"l\\u0065t = 1;",
"(l\\u0065t === 1);",
"(y\\u0069eld);",
"var y\\u0069eld = 1;",
"var { y\\u0069eld } = {};",
nullptr
};
// clang-format on
......
......@@ -591,24 +591,6 @@
# https://bugs.chromium.org/p/v8/issues/detail?id=6538
# https://bugs.chromium.org/p/v8/issues/detail?id=6541
'language/export/escaped-as-export-specifier': [FAIL],
'language/export/escaped-from': [FAIL],
'language/expressions/object/method-definition/escaped-get': [FAIL],
'language/expressions/object/method-definition/escaped-set': [FAIL],
'language/import/escaped-as-import-specifier': [FAIL],
'language/import/escaped-as-namespace-import': [FAIL],
'language/import/escaped-from': [FAIL],
'language/statements/for-await-of/escaped-of': [FAIL],
'language/statements/for-of/escaped-of': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=6543
'language/statements/labeled/value-await-non-module-escaped': [FAIL],
'language/statements/labeled/value-yield-non-strict-escaped': [FAIL],
'language/expressions/async-arrow-function/escaped-async-line-terminator': [FAIL],
'language/expressions/class/class-name-ident-await-escaped': [FAIL],
'language/statements/class/class-name-ident-await-escaped': [FAIL],
############################ INVALID TESTS #############################
# Test makes unjustified assumptions about the number of calls to SortCompare.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment