unittests/Parser/JSLexerTest.cpp (980 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "hermes/Parser/JSLexer.h"
#include "DiagContext.h"
#include "llvh/ADT/APFloat.h"
#include "llvh/ADT/APInt.h"
#include "llvh/ADT/SmallString.h"
#include "gtest/gtest.h"
using namespace hermes;
using namespace hermes::parser;
using llvh::APFloat;
using llvh::APInt;
namespace {
TEST(JSLexerTest, PunctuatorTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
static const char puncts[] =
#define PUNCTUATOR(name, str) str " "
#include "hermes/Parser/TokenKinds.def"
;
JSLexer lex(puncts, sm, alloc);
#define PUNCTUATOR(name, str) \
ASSERT_EQ(lex.advance(JSLexer::AllowDiv)->getKind(), TokenKind::name);
#include "hermes/Parser/TokenKinds.def"
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
#ifndef NDEBUG
#define PUNCTUATOR(name, str) ASSERT_TRUE(isPunctuatorDbg(TokenKind::name));
#include "hermes/Parser/TokenKinds.def"
#endif
// "/=" and "/" require context or they could be interpreted as a regexp
// literal
}
TEST(JSLexerTest, PunctuatorDivTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
JSLexer lex("a / b /= c", sm, alloc);
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::slash, lex.advance(JSLexer::AllowDiv)->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::slashequal, lex.advance(JSLexer::AllowDiv)->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, WhiteSpaceTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
JSLexer lex("{ ; \n} \n \n ;", sm, alloc);
ASSERT_EQ(TokenKind::l_brace, lex.advance()->getKind());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::r_brace, lex.advance()->getKind());
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
}
TEST(JSLexerTest, UnicodeWhiteSpaceTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
// Source with unicode whitespace characters.
JSLexer lex("{\xe2\x80\x80;\xe2\x80\x8a \n} \xe2\x81\x9f\n \n ;", sm, alloc);
// lexer behavior should be the same as in WhiteSpaceTest
ASSERT_EQ(TokenKind::l_brace, lex.advance()->getKind());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::r_brace, lex.advance()->getKind());
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(0, diag.getErrCountClear());
}
TEST(JSLexerTest, CommentTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex(
"; /* foo */ { /* bar \n"
" ***** */ } // hello\n"
" /* comment */ ;\n"
" /* not closed",
sm,
alloc);
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::l_brace, lex.advance()->getKind());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::r_brace, lex.advance()->getKind());
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear()); // comment not closed
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
}
TEST(JSLexerTest, HashbangTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex(
"#! hashbang comment\n"
";\n"
"#! // not a hashbang comment\n",
sm,
alloc);
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::exclaim, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
}
TEST(JSLexerTest, NumberTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
#define _GEN_TESTS(flt, dec) \
flt(1235) flt(1234567890123) flt(0) dec(0x10) flt(1.2) dec(055) flt(.1) \
flt(1.) flt(1e2) flt(5e+3) flt(4e-3) flt(.1e-3) flt(12.34e+5)
#define _MK_STR(num) " " #num
// Bitwise comparison of a floating numeric literal against a
// APFloat(test_input)
#define _FLT(num) \
{ \
APFloat fval(APFloat::IEEEdouble(), #num); \
llvh::SmallString<16> actual, expected; \
\
const Token *tok = lex.advance(); \
ASSERT_EQ(TokenKind::numeric_literal, tok->getKind()); \
\
fval.toString(expected); \
APFloat(tok->getNumericLiteral()).toString(actual); \
EXPECT_STREQ(expected.c_str(), actual.c_str()); \
EXPECT_TRUE(APFloat(tok->getNumericLiteral()).bitwiseIsEqual(fval)); \
}
// Bitwise comparison of a integer numeric literal against
// APFloat(APInt(test_input))
#define _DEC(num) \
{ \
APInt ival; \
APFloat fval(APFloat::IEEEdouble()); \
llvh::SmallString<16> actual, expected; \
\
const Token *tok = lex.advance(); \
ASSERT_EQ(TokenKind::numeric_literal, tok->getKind()); \
\
StringRef(#num).getAsInteger(0, ival); \
fval.convertFromAPInt(ival, false, APFloat::rmNearestTiesToEven); \
fval.toString(expected); \
APFloat(tok->getNumericLiteral()).toString(actual); \
EXPECT_STREQ(expected.c_str(), actual.c_str()); \
EXPECT_TRUE(APFloat(tok->getNumericLiteral()).bitwiseIsEqual(fval)); \
}
JSLexer lex(_GEN_TESTS(_MK_STR, _MK_STR), sm, alloc, nullptr, false);
_GEN_TESTS(_FLT, _DEC);
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
#undef _DEC
#undef _FLT
#undef _MK_STR
#undef _GEN_TESTS
}
TEST(JSLexerTest, NumericSeparatorTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex(
" 1_2 12"
" 0x1_2 0x12"
" 0xdead_beef 0xdeadbeef"
" 0b1_1 0b11"
" 0o1_1 0o11"
" 123_456_789 123456789"
" 12_345e1_2 12345e12"
" 1_1.1_2 1_1.12",
sm,
alloc);
const Token *tok = lex.advance();
while (tok->getKind() != TokenKind::eof) {
ASSERT_EQ(TokenKind::numeric_literal, tok->getKind());
double withSep = tok->getNumericLiteral();
tok = lex.advance();
ASSERT_EQ(TokenKind::numeric_literal, tok->getKind());
double noSep = tok->getNumericLiteral();
ASSERT_EQ(withSep, noSep);
tok = lex.advance();
}
}
#define LEX_EXPECT_BIGINT(s, lex) \
ASSERT_EQ(TokenKind::bigint_literal, lex.advance()->getKind()); \
EXPECT_STREQ(s, lex.getCurToken()->getBigIntLiteral()->c_str())
TEST(JSLexerTest, BigIntTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
{
JSLexer lex(
" 0n"
" 1n"
" 1000n"
" 1928371289378129381212398n"
" 0xdeadbeefn"
" 0b10101100101n",
sm,
alloc);
LEX_EXPECT_BIGINT("0n", lex);
LEX_EXPECT_BIGINT("1n", lex);
LEX_EXPECT_BIGINT("1000n", lex);
LEX_EXPECT_BIGINT("1928371289378129381212398n", lex);
LEX_EXPECT_BIGINT("0xdeadbeefn", lex);
LEX_EXPECT_BIGINT("0b10101100101n", lex);
}
{
JSLexer lex("09n", sm, alloc);
lex.advance();
ASSERT_EQ(1, diag.getErrCountClear());
}
{
JSLexer lex("1.1n", sm, alloc);
lex.advance();
ASSERT_EQ(1, diag.getErrCountClear());
}
{
JSLexer lex("1e2n", sm, alloc);
lex.advance();
ASSERT_EQ(1, diag.getErrCountClear());
}
}
TEST(JSLexerTest, BadNumbersTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex(
"123hhhh; 123e ; .4.5 ; 0_7 1__23 0b_11 123_ 1._2 12e_3", sm, alloc);
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
lex.setStrictMode(false);
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(2, diag.getErrCountClear());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, BigIntegerTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex(
" 0xFFFFFFFFFFFFFFFF"
" 99999999999999999999",
sm,
alloc);
// Test more then 52 bits of integer
{
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
llvh::SmallString<32> actual;
APFloat(lex.getCurToken()->getNumericLiteral()).toString(actual, 20);
// We need dtoa.c for the correct string representation
// EXPECT_STREQ("18446744073709552000", actual.c_str());
EXPECT_STREQ("18446744073709551616", actual.c_str());
}
{
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
llvh::SmallString<32> actual;
APFloat(lex.getCurToken()->getNumericLiteral()).toString(actual, 30, 30);
EXPECT_STREQ("100000000000000000000", actual.c_str());
}
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, ZeroRadixTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex(" 0x", sm, alloc);
// Test a malformed hex number.
{
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
}
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, OctalLiteralTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
{
JSLexer lex("01 010 09 019 0o11 0O11", sm, alloc);
auto tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 1.0);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 8.0);
ASSERT_EQ(diag.getWarnCountClear(), 0);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 9.0);
ASSERT_EQ(diag.getWarnCountClear(), 1);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 19.0);
ASSERT_EQ(diag.getWarnCountClear(), 1);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 9.0);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 9.0);
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
{
JSLexer lex("08.1_1 07.11 07.9 08.9", sm, alloc);
lex.setStrictMode(false);
auto tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 8.11);
ASSERT_EQ(diag.getWarnCountClear(), 1);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(diag.getErrCountClear(), 1);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(diag.getErrCountClear(), 1);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 8.9);
ASSERT_EQ(diag.getWarnCountClear(), 1);
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
{
JSLexer lex("08.1_1 07.11 07.9 08.9", sm, alloc);
lex.setStrictMode(true);
auto tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(diag.getErrCountClear(), 1);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(diag.getErrCountClear(), 1);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(diag.getErrCountClear(), 1);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(diag.getErrCountClear(), 1);
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
}
#if HERMES_PARSE_FLOW
TEST(JSLexerTest, FlowOctalLiteralTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex("01", sm, alloc);
auto tok = lex.advance(JSLexer::GrammarContext::Type);
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 1.0);
ASSERT_EQ(1, diag.getErrCountClear());
}
#endif
TEST(JSLexerTest, BinaryLiteralTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex("0b1 0B1 0b101", sm, alloc);
auto tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 1.0);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 1.0);
tok = lex.advance();
ASSERT_EQ(tok->getKind(), TokenKind::numeric_literal);
ASSERT_EQ(tok->getNumericLiteral(), 5.0);
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
#define LEX_EXPECT_IDENT(s, lex) \
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind()); \
EXPECT_STREQ(s, lex.getCurToken()->getIdentifier()->c_str())
TEST(JSLexerTest, SimpleIdentifierTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
JSLexer lex("true foo bar foo", sm, alloc);
ASSERT_EQ(TokenKind::rw_true, lex.advance()->getKind());
LEX_EXPECT_IDENT("foo", lex);
UniqueString *foo = lex.getCurToken()->getIdentifier();
LEX_EXPECT_IDENT("bar", lex);
LEX_EXPECT_IDENT("foo", lex);
ASSERT_EQ(foo, lex.getCurToken()->getIdentifier());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, IdentifierTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
JSLexer lex(
" _foo$123"
" $123"
" a\\u0061"
" \\u0061\\u0061"
" \\u0061a",
sm,
alloc);
LEX_EXPECT_IDENT("_foo$123", lex);
LEX_EXPECT_IDENT("$123", lex);
LEX_EXPECT_IDENT("aa", lex);
UniqueString *aa = lex.getCurToken()->getIdentifier();
LEX_EXPECT_IDENT("aa", lex);
ASSERT_EQ(aa, lex.getCurToken()->getIdentifier());
LEX_EXPECT_IDENT("aa", lex);
ASSERT_EQ(aa, lex.getCurToken()->getIdentifier());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, PrivateIdentifierTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex(
" #foo"
" # foo"
" #64",
sm,
alloc);
ASSERT_EQ(TokenKind::private_identifier, lex.advance()->getKind());
EXPECT_EQ("foo", lex.getCurToken()->getPrivateIdentifier()->str());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
EXPECT_EQ(1, diag.getErrCountClear());
EXPECT_EQ("foo", lex.getCurToken()->getIdentifier()->str());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
EXPECT_EQ(1, diag.getErrCountClear());
EXPECT_EQ(64, lex.getCurToken()->getNumericLiteral());
}
TEST(JSLexerTest, StringTest1) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex(
"'aa' \"bb\" 'open1\n"
"\"open2",
sm,
alloc);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ("aa", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ("bb", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
EXPECT_STREQ("open1", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
EXPECT_STREQ("open2", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_TRUE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
}
TEST(JSLexerTest, StringLineParaSepTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
// Test that Unicode line and paragraph separatot are valid in a string
// (since ES10).
JSLexer lex(
"'\xe2\x80\xa8' "
"'\xe2\x80\xa9' "
"'\\\xe2\x80\xa8' "
"'\\\xe2\x80\xa9' ",
sm,
alloc);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ("\xe2\x80\xa8", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ("\xe2\x80\xa9", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ("", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ("", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, StringTest2) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex(
"'a\\u0061\x62\143' "
"'\\w\\'\\\"\\b\\f\\n\\r\\t\\v\\\na' "
"'\\x1g' "
"'\\u123g'",
sm,
alloc);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ("aabc", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ(
"w'\"\b\f\n\r\t\va", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, StringOctalTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
{
// non-strict mode.
JSLexer lex("'\\0' '\\000' '\\05'", sm, alloc, nullptr, false);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
{
// strict mode.
JSLexer lex("'\\0' '\\000' '\\05'", sm, alloc, nullptr, true);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
EXPECT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
EXPECT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
}
TEST(JSLexerTest, UnicodeEscapeTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
{
JSLexer lex("'\\u0f3b' '\\u{0f3b}' '\\u{0062}'", sm, alloc);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ(
"\xe0\xbc\xbb", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ(
"\xe0\xbc\xbb", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
EXPECT_STREQ("\x62", lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_EQ(0, diag.getErrCountClear());
}
{
JSLexer lex("'\\u{ffffffff}'", sm, alloc);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
}
{
JSLexer lex("'\\u{}'", sm, alloc);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
}
}
TEST(JSLexerTest, RegexpSmoke) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
JSLexer lex("; /aa/bc", sm, alloc);
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_EQ(
TokenKind::regexp_literal, lex.advance(JSLexer::AllowRegExp)->getKind());
EXPECT_STREQ("aa", lex.getCurToken()->getRegExpLiteral()->getBody()->c_str());
EXPECT_STREQ(
"bc", lex.getCurToken()->getRegExpLiteral()->getFlags()->c_str());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, RegexpSmoke2) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
JSLexer lex("; /(\\w+)\\s(\\w+)/g", sm, alloc);
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_EQ(
TokenKind::regexp_literal, lex.advance(JSLexer::AllowRegExp)->getKind());
EXPECT_STREQ(
"(\\w+)\\s(\\w+)",
lex.getCurToken()->getRegExpLiteral()->getBody()->c_str());
EXPECT_STREQ("g", lex.getCurToken()->getRegExpLiteral()->getFlags()->c_str());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
// Make sure that we *allow* invalid UTF-16 surrogate pairs.
TEST(JSLexerTest, UTF16BadSurrogatePairs) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext ctx(sm);
JSLexer lex("' \\udc01 \\ud805 '", sm, alloc);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
EXPECT_STREQ(
"\x20\xed\xb0\x81\x20\xed\xa0\x85\x20",
lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_EQ(0, ctx.getErrCountClear());
}
// Make sure '/' can be used in a regexp class
TEST(JSLexerTest, classInRegexp) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext ctx(sm);
JSLexer lex("/[a/]/", sm, alloc);
ASSERT_EQ(TokenKind::regexp_literal, lex.advance()->getKind());
EXPECT_STREQ(
"[a/]", lex.getCurToken()->getRegExpLiteral()->getBody()->c_str());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_EQ(0, ctx.getErrCountClear());
}
// UTF-8-encoded codepoints which are larger than 0xFFFF should be normalized
// into a UTF-8 encoded surrogate pair.
TEST(JSLexerTest, normalizeUTF8) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext ctx(sm);
JSLexer lex("'\xf0\x90\x80\x81'", sm, alloc);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
EXPECT_STREQ(
"\xed\xa0\x80\xed\xb0\x81",
lex.getCurToken()->getStringLiteral()->c_str());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, templateLiterals) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext ctx(sm);
{
JSLexer lex("`abc` `\\x41` `\\u0041` `\\\xe2\x80\xa8`", sm, alloc);
ASSERT_EQ(TokenKind::no_substitution_template, lex.advance()->getKind());
EXPECT_STREQ("abc", lex.getCurToken()->getTemplateValue()->c_str());
EXPECT_STREQ("abc", lex.getCurToken()->getTemplateRawValue()->c_str());
ASSERT_EQ(TokenKind::no_substitution_template, lex.advance()->getKind());
EXPECT_STREQ("\x41", lex.getCurToken()->getTemplateValue()->c_str());
EXPECT_STREQ("\\x41", lex.getCurToken()->getTemplateRawValue()->c_str());
ASSERT_EQ(TokenKind::no_substitution_template, lex.advance()->getKind());
EXPECT_STREQ("\u0041", lex.getCurToken()->getTemplateValue()->c_str());
EXPECT_STREQ("\\u0041", lex.getCurToken()->getTemplateRawValue()->c_str());
ASSERT_EQ(TokenKind::no_substitution_template, lex.advance()->getKind());
EXPECT_STREQ("", lex.getCurToken()->getTemplateValue()->c_str());
EXPECT_STREQ(
"\\\xe2\x80\xa8", lex.getCurToken()->getTemplateRawValue()->c_str());
}
{
JSLexer lex("`abc${x}def${y}ghi`", sm, alloc);
ASSERT_EQ(TokenKind::template_head, lex.advance()->getKind());
EXPECT_STREQ("abc", lex.getCurToken()->getTemplateValue()->c_str());
EXPECT_STREQ("abc", lex.getCurToken()->getTemplateRawValue()->c_str());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
EXPECT_STREQ("x", lex.getCurToken()->getIdentifier()->c_str());
ASSERT_EQ(TokenKind::r_brace, lex.advance()->getKind());
lex.rescanRBraceInTemplateLiteral();
ASSERT_EQ(TokenKind::template_middle, lex.getCurToken()->getKind());
EXPECT_STREQ("def", lex.getCurToken()->getTemplateValue()->c_str());
EXPECT_STREQ("def", lex.getCurToken()->getTemplateRawValue()->c_str());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
EXPECT_STREQ("y", lex.getCurToken()->getIdentifier()->c_str());
ASSERT_EQ(TokenKind::r_brace, lex.advance()->getKind());
lex.rescanRBraceInTemplateLiteral();
ASSERT_EQ(TokenKind::template_tail, lex.getCurToken()->getKind());
EXPECT_STREQ("ghi", lex.getCurToken()->getTemplateValue()->c_str());
EXPECT_STREQ("ghi", lex.getCurToken()->getTemplateRawValue()->c_str());
}
{
JSLexer lex("`\\0`", sm, alloc);
ASSERT_EQ(TokenKind::no_substitution_template, lex.advance()->getKind());
EXPECT_STREQ("\0", lex.getCurToken()->getTemplateValue()->c_str());
EXPECT_STREQ("\\0", lex.getCurToken()->getTemplateRawValue()->c_str());
}
{
JSLexer lex("`\r\n \n \r`", sm, alloc);
ASSERT_EQ(TokenKind::no_substitution_template, lex.advance()->getKind());
EXPECT_STREQ("\n \n \n", lex.getCurToken()->getTemplateValue()->c_str());
EXPECT_STREQ("\n \n \n", lex.getCurToken()->getTemplateRawValue()->c_str());
}
}
TEST(JSLexerTest, reservedTokens) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext ctx(sm);
static const char str[] =
"implements private public "
"interface package protected static "
"yield";
// Ensure we recognize the words in strict mode.
{
JSLexer lex(str, sm, alloc, nullptr, true);
ASSERT_EQ(TokenKind::rw_implements, lex.advance()->getKind());
ASSERT_EQ(TokenKind::rw_private, lex.advance()->getKind());
ASSERT_EQ(TokenKind::rw_public, lex.advance()->getKind());
ASSERT_EQ(TokenKind::rw_interface, lex.advance()->getKind());
ASSERT_EQ(TokenKind::rw_package, lex.advance()->getKind());
ASSERT_EQ(TokenKind::rw_protected, lex.advance()->getKind());
ASSERT_EQ(TokenKind::rw_static, lex.advance()->getKind());
ASSERT_EQ(TokenKind::rw_yield, lex.advance()->getKind());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_EQ(0, ctx.getErrCountClear());
}
// Ensure we don't recognize the words in strict mode.
{
JSLexer lex(str, sm, alloc, nullptr, false);
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_EQ(0, ctx.getErrCountClear());
}
}
TEST(JSLexerTest, SourceMappingUrl) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext ctx(sm);
// Ensure it works at the end of a file.
{
static const char str[] =
"var x = 1;"
"//# sourceMappingURL=localhost:8000/this_is_the_url.map";
JSLexer lex(str, sm, alloc);
ASSERT_EQ(TokenKind::rw_var, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::equal, lex.advance()->getKind());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
lex.advance();
ASSERT_EQ("localhost:8000/this_is_the_url.map", sm.getSourceMappingUrl(1));
}
// Ensure it works in the middle of a file.
{
static const char str[] =
"var x = 1;\n"
"//# sourceMappingURL=second-map.map\n"
"var y = 2;";
JSLexer lex(str, sm, alloc);
ASSERT_EQ(TokenKind::rw_var, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::equal, lex.advance()->getKind());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
lex.advance();
ASSERT_EQ("second-map.map", sm.getSourceMappingUrl(2));
}
// Ensure it doesn't read invalid source map comments.
{
static const char str[] =
"var x = 1;\n"
"// sourceMappingURL=localhost:8000/this_is_the_url.map\n"
"//# sourceMappingURL =localhost:8000/this_is_the_url.map\n"
"//#sourceMappingURL=localhost:8000/this_is_the_url.map\n"
"//# sourceMappingURL=\nlocalhost:8000/this_is_the_url.map\n";
JSLexer lex(str, sm, alloc);
ASSERT_EQ(TokenKind::rw_var, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::equal, lex.advance()->getKind());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
lex.advance();
lex.advance();
lex.advance();
lex.advance();
ASSERT_TRUE(sm.getSourceMappingUrl(3).empty());
}
// Ensure it overwrites the first URL with another one.
{
static const char str[] =
"var x = 1;\n"
"//# sourceMappingURL=url1\n"
"//# sourceMappingURL=url2\n";
JSLexer lex(str, sm, alloc);
ASSERT_EQ(TokenKind::rw_var, lex.advance()->getKind());
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(TokenKind::equal, lex.advance()->getKind());
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
lex.advance();
lex.advance();
ASSERT_EQ("url2", sm.getSourceMappingUrl(4));
}
}
TEST(JSLexerTest, LookaheadNewlineTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
// Test the lookahead function which will not revert to the current
// token after lookahead if an optional expected token is provided.
JSLexer lex("function\n(", sm, alloc);
ASSERT_EQ(TokenKind::rw_function, lex.advance()->getKind());
{
// Revert since there is no expected token
// Expect None returned since there is a newline before the next token
auto optNext = lex.lookahead1(llvh::None);
ASSERT_FALSE(optNext.hasValue());
}
ASSERT_EQ(TokenKind::rw_function, lex.getCurToken()->getKind());
ASSERT_EQ(TokenKind::l_paren, lex.advance()->getKind());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, LookaheadTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
// Test the lookahead function which will not revert to the current
// token after lookahead if an optional expected token is provided.
JSLexer lex("function( foo,", sm, alloc);
ASSERT_EQ(TokenKind::rw_function, lex.advance()->getKind());
{
// Without the expected token, always revert.
auto optNext = lex.lookahead1(llvh::None);
ASSERT_TRUE(optNext.hasValue());
EXPECT_EQ(TokenKind::l_paren, optNext.getValue());
}
ASSERT_EQ(TokenKind::rw_function, lex.getCurToken()->getKind());
ASSERT_EQ(TokenKind::l_paren, lex.advance()->getKind());
// With the expected token, revert iff it doesn't match.
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
{
auto optNext = lex.lookahead1(TokenKind::plus);
ASSERT_TRUE(optNext.hasValue());
EXPECT_EQ(TokenKind::comma, optNext.getValue());
// Revert to original token.
ASSERT_EQ(TokenKind::identifier, lex.getCurToken()->getKind());
}
{
auto optNext = lex.lookahead1(TokenKind::comma);
ASSERT_TRUE(optNext.hasValue());
EXPECT_EQ(TokenKind::comma, optNext.getValue());
// Match with expected, keep the lookahead token.
ASSERT_EQ(TokenKind::comma, lex.getCurToken()->getKind());
}
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, RegressConsumeBadHexTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
// Test a hex escape where the two following characters are ('5' & ~32).
// This catches a bug where we were or-ing 32 before checking whether the
// character is a digit.
JSLexer lex("'\\x\x15\x15'", sm, alloc);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(1, diag.getErrCountClear());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_FALSE(lex.isNewLineBeforeCurrentToken());
ASSERT_EQ(0, diag.getErrCountClear());
}
TEST(JSLexerTest, ConsumeBadBracedCodePoint) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
// Test an invalid curly brace escape without a terminating curly brace
// This catches an out of bounds read where we hit the error limit and
// curCharPtr_ was set to eof, but JSLexer::consumeBracedCodePoint continued
// to operate on curCharPtr_
// We configure a low error limit to reach the interesting code path faster
// then we use invalid characters in a non terminated curly brace code point
// to trigger JSLexer:error
sm.setErrorLimit(1);
JSLexer lex("'\\u{12XXXXXXXXXXX'", sm, alloc);
ASSERT_EQ(TokenKind::string_literal, lex.advance()->getKind());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
}
TEST(JSLexerTest, AtSignTest) {
{
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
sm.setErrorLimit(10);
JSLexer lex("`${{}@", sm, alloc);
ASSERT_EQ(TokenKind::template_head, lex.advance()->getKind());
ASSERT_EQ(TokenKind::l_brace, lex.advance()->getKind());
ASSERT_EQ(TokenKind::r_brace, lex.advance()->getKind());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
EXPECT_EQ(1, sm.getErrorCount());
}
{
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
sm.setErrorLimit(1);
JSLexer lex("`${{}@", sm, alloc);
ASSERT_EQ(TokenKind::template_head, lex.advance()->getKind());
ASSERT_EQ(TokenKind::l_brace, lex.advance()->getKind());
ASSERT_EQ(TokenKind::r_brace, lex.advance()->getKind());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
EXPECT_EQ(1, sm.getErrorCount());
}
}
TEST(JSLexerTest, JSXTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
JSLexer lex("abc def{xyz<qwerty", sm, alloc);
ASSERT_EQ(TokenKind::jsx_text, lex.advanceInJSXChild()->getKind());
EXPECT_STREQ("abc def", lex.getCurToken()->getJSXTextRaw()->c_str());
ASSERT_EQ(TokenKind::l_brace, lex.advanceInJSXChild()->getKind());
ASSERT_EQ(TokenKind::jsx_text, lex.advanceInJSXChild()->getKind());
EXPECT_STREQ("xyz", lex.getCurToken()->getJSXTextRaw()->c_str());
ASSERT_EQ(TokenKind::less, lex.advanceInJSXChild()->getKind());
ASSERT_EQ(TokenKind::jsx_text, lex.advanceInJSXChild()->getKind());
EXPECT_STREQ("qwerty", lex.getCurToken()->getJSXTextRaw()->c_str());
}
TEST(JSLexerTest, StoreCommentsTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
{
JSLexer lex("// hello\n;\n// world", sm, alloc, nullptr, true, false);
lex.setStoreComments(true);
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_EQ(2, lex.getStoredComments().size());
EXPECT_EQ(StoredComment::Kind::Line, lex.getStoredComments()[0].getKind());
EXPECT_EQ(" hello", lex.getStoredComments()[0].getString());
EXPECT_EQ(StoredComment::Kind::Line, lex.getStoredComments()[1].getKind());
EXPECT_EQ(" world", lex.getStoredComments()[1].getString());
}
{
JSLexer lex("/* hello */;/*world*/", sm, alloc, nullptr, true, false);
lex.setStoreComments(true);
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_EQ(2, lex.getStoredComments().size());
EXPECT_EQ(StoredComment::Kind::Block, lex.getStoredComments()[0].getKind());
EXPECT_EQ(" hello ", lex.getStoredComments()[0].getString());
EXPECT_EQ(StoredComment::Kind::Block, lex.getStoredComments()[1].getKind());
EXPECT_EQ("world", lex.getStoredComments()[1].getString());
}
{
JSLexer lex("#! hello world\n;", sm, alloc, nullptr, true, false);
lex.setStoreComments(true);
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_EQ(1, lex.getStoredComments().size());
EXPECT_EQ(
StoredComment::Kind::Hashbang, lex.getStoredComments()[0].getKind());
EXPECT_EQ(" hello world", lex.getStoredComments()[0].getString());
}
{
JSLexer lex("/**/;//\n", sm, alloc, nullptr, true, false);
lex.setStoreComments(true);
ASSERT_EQ(TokenKind::semi, lex.advance()->getKind());
ASSERT_EQ(TokenKind::eof, lex.advance()->getKind());
ASSERT_EQ(2, lex.getStoredComments().size());
EXPECT_EQ(StoredComment::Kind::Block, lex.getStoredComments()[0].getKind());
EXPECT_EQ("", lex.getStoredComments()[0].getString());
EXPECT_EQ(StoredComment::Kind::Line, lex.getStoredComments()[1].getKind());
EXPECT_EQ("", lex.getStoredComments()[1].getString());
}
{
JSLexer lex("/*one*/ < /*two*/ >", sm, alloc, nullptr, true, false);
lex.setStoreComments(true);
// Create save point between two comments
ASSERT_EQ(TokenKind::less, lex.advance()->getKind());
JSLexer::SavePoint savePoint{&lex};
ASSERT_EQ(TokenKind::greater, lex.advance()->getKind());
ASSERT_EQ(2, lex.getStoredComments().size());
EXPECT_EQ(StoredComment::Kind::Block, lex.getStoredComments()[0].getKind());
EXPECT_EQ("one", lex.getStoredComments()[0].getString());
EXPECT_EQ(StoredComment::Kind::Block, lex.getStoredComments()[1].getKind());
EXPECT_EQ("two", lex.getStoredComments()[1].getString());
// After restoring, comment after save point is removed from comment storage
savePoint.restore();
ASSERT_EQ(1, lex.getStoredComments().size());
EXPECT_EQ(StoredComment::Kind::Block, lex.getStoredComments()[0].getKind());
EXPECT_EQ("one", lex.getStoredComments()[0].getString());
}
{
JSLexer lex("/*one*/ A /*two*/ >", sm, alloc, nullptr, true, false);
lex.setStoreComments(true);
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(1, lex.getStoredComments().size());
EXPECT_EQ("one", lex.getStoredComments()[0].getString());
// Lookahead does not store comments
lex.lookahead1(TokenKind::semi);
ASSERT_EQ(1, lex.getStoredComments().size());
EXPECT_EQ("one", lex.getStoredComments()[0].getString());
ASSERT_EQ(TokenKind::greater, lex.advance()->getKind());
ASSERT_EQ(2, lex.getStoredComments().size());
}
}
TEST(JSLexerTest, PrevTokenEndLocTest) {
JSLexer::Allocator alloc;
SourceErrorManager sm;
DiagContext diag(sm);
{
JSLexer lex("var x = 1", sm, alloc);
ASSERT_EQ(TokenKind::rw_var, lex.advance()->getKind());
SMLoc varEndLoc = lex.getCurToken()->getEndLoc();
ASSERT_EQ(TokenKind::identifier, lex.advance()->getKind());
ASSERT_EQ(varEndLoc, lex.getPrevTokenEndLoc());
SMLoc idEndLoc = lex.getCurToken()->getEndLoc();
// Create save point at identifier
JSLexer::SavePoint savePoint{&lex};
ASSERT_EQ(TokenKind::equal, lex.advance()->getKind());
ASSERT_EQ(idEndLoc, lex.getPrevTokenEndLoc());
SMLoc equalEndLoc = lex.getCurToken()->getEndLoc();
ASSERT_EQ(TokenKind::numeric_literal, lex.advance()->getKind());
ASSERT_EQ(equalEndLoc, lex.getPrevTokenEndLoc());
// Restoring to identifier. Last token is now the var keyword.
savePoint.restore();
ASSERT_EQ(varEndLoc, lex.getPrevTokenEndLoc());
}
}
} // namespace