blob: cdd493fda556ea3b43b611612f7fa66337de4d04 [file] [log] [blame]
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "pagespeed/kernel/js/js_tokenizer.h"
#include <utility>
#include "pagespeed/kernel/base/google_message_handler.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/stdio_file_system.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/js/js_keywords.h"
using pagespeed::JsKeywords;
using pagespeed::js::JsTokenizer;
using pagespeed::js::JsTokenizerPatterns;
namespace {
const char kTestRootDir[] = "/pagespeed/kernel/js/testdata/third_party/";
class JsTokenizerTest : public testing::Test {
protected:
void BeginTokenizing(StringPiece input) {
tokenizer_.reset(new JsTokenizer(&patterns_, input));
}
void ExpectParseStack(StringPiece expected_parse_stack) {
EXPECT_STREQ(expected_parse_stack, tokenizer_->ParseStackForTest());
}
void ExpectToken(JsKeywords::Type expected_type,
StringPiece expected_token) {
StringPiece actual_token;
const JsKeywords::Type actual_type = tokenizer_->NextToken(&actual_token);
EXPECT_EQ(make_pair(expected_type, expected_token),
make_pair(actual_type, actual_token));
EXPECT_FALSE(tokenizer_->has_error());
}
void ExpectToken(JsKeywords::Type expected_type, StringPiece expected_token,
StringPiece expected_parse_stack) {
ExpectToken(expected_type, expected_token);
ExpectParseStack(expected_parse_stack);
}
void ExpectEndOfInput() {
ExpectToken(JsKeywords::kEndOfInput, "");
ExpectParseStack("");
}
void ExpectError(StringPiece expected_token) {
StringPiece actual_token;
const JsKeywords::Type actual_type = tokenizer_->NextToken(&actual_token);
EXPECT_EQ(make_pair(JsKeywords::kError, expected_token),
make_pair(actual_type, actual_token));
EXPECT_TRUE(tokenizer_->has_error());
}
void ExpectTokenizeFileSuccessfully(StringPiece filename) {
// Read in the JavaScript file.
GoogleString original;
{
net_instaweb::StdioFileSystem file_system;
const GoogleString filepath = net_instaweb::StrCat(
net_instaweb::GTestSrcDir(), kTestRootDir, filename);
net_instaweb::GoogleMessageHandler message_handler;
ASSERT_TRUE(file_system.ReadFile(
filepath.c_str(), &original, &message_handler));
}
// Tokenize the JavaScript, appending each token onto the output string.
// There should be no tokenizer errors.
GoogleString output;
{
output.reserve(original.size());
JsTokenizer tokenizer(&patterns_, original);
StringPiece token;
while (tokenizer.NextToken(&token) !=
JsKeywords::kEndOfInput) {
ASSERT_FALSE(tokenizer.has_error())
<< "Error at: " << token.substr(0, 150)
<< "\nWith stack: " << tokenizer.ParseStackForTest();
token.AppendToString(&output);
}
}
// The concatenation of all tokens should exactly reproduce the input.
EXPECT_STREQ(original, output);
}
private:
JsTokenizerPatterns patterns_;
net_instaweb::scoped_ptr<JsTokenizer> tokenizer_;
};
TEST_F(JsTokenizerTest, EmptyInput) {
BeginTokenizing("");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Blocks) {
BeginTokenizing("if (foo){\n"
"}else if(bar){\n"
"}else baz;");
ExpectParseStack("Start");
ExpectToken(JsKeywords::kIf, "if", "Start BkKwd");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "(", "Start BkKwd (");
ExpectToken(JsKeywords::kIdentifier, "foo", "Start BkKwd ( Expr");
ExpectToken(JsKeywords::kOperator, ")", "Start BkHdr");
ExpectToken(JsKeywords::kOperator, "{", "Start BkHdr {");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kOperator, "}", "Start");
ExpectToken(JsKeywords::kElse, "else", "Start BkHdr");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIf, "if", "Start BkHdr BkKwd");
ExpectToken(JsKeywords::kOperator, "(", "Start BkHdr BkKwd (");
ExpectToken(JsKeywords::kIdentifier, "bar", "Start BkHdr BkKwd ( Expr");
ExpectToken(JsKeywords::kOperator, ")", "Start BkHdr");
ExpectToken(JsKeywords::kOperator, "{", "Start BkHdr {");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kOperator, "}", "Start");
ExpectToken(JsKeywords::kElse, "else", "Start BkHdr");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "baz", "Start BkHdr Expr");
ExpectToken(JsKeywords::kOperator, ";", "Start");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Functions) {
BeginTokenizing("function foo(){return 1}\n"
"bar=function(){return 2};\n"
"(function(){window=5})();");
ExpectParseStack("Start");
ExpectToken(JsKeywords::kFunction, "function");
ExpectParseStack("Start BkKwd");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectParseStack("Start BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start BkHdr");
ExpectToken(JsKeywords::kOperator, "{");
ExpectParseStack("Start BkHdr {");
ExpectToken(JsKeywords::kReturn, "return");
ExpectParseStack("Start BkHdr { RetTh");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kNumber, "1");
ExpectParseStack("Start BkHdr { RetTh Expr");
ExpectToken(JsKeywords::kOperator, "}");
ExpectParseStack("Start");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "bar");
ExpectParseStack("Start Expr");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kFunction, "function");
ExpectParseStack("Start Expr Oper BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start Expr Oper BkHdr");
ExpectToken(JsKeywords::kOperator, "{");
ExpectParseStack("Start Expr Oper BkHdr {");
ExpectToken(JsKeywords::kReturn, "return");
ExpectParseStack("Start Expr Oper BkHdr { RetTh");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kNumber, "2");
ExpectParseStack("Start Expr Oper BkHdr { RetTh Expr");
ExpectToken(JsKeywords::kOperator, "}");
ExpectParseStack("Start Expr");
ExpectToken(JsKeywords::kOperator, ";");
ExpectParseStack("Start");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kOperator, "(");
ExpectParseStack("Start (");
ExpectToken(JsKeywords::kFunction, "function");
ExpectParseStack("Start ( BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectParseStack("Start ( BkKwd (");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start ( BkHdr");
ExpectToken(JsKeywords::kOperator, "{");
ExpectParseStack("Start ( BkHdr {");
ExpectToken(JsKeywords::kIdentifier, "window");
ExpectParseStack("Start ( BkHdr { Expr");
ExpectToken(JsKeywords::kOperator, "=");
ExpectParseStack("Start ( BkHdr { Expr Oper");
ExpectToken(JsKeywords::kNumber, "5");
ExpectParseStack("Start ( BkHdr { Expr");
ExpectToken(JsKeywords::kOperator, "}");
ExpectParseStack("Start ( Expr");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start Expr");
ExpectToken(JsKeywords::kOperator, "(");
ExpectParseStack("Start Expr (");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start Expr");
ExpectToken(JsKeywords::kOperator, ";");
ExpectParseStack("Start");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, ForLoop) {
BeginTokenizing("loop:for(var x=0;x<5;++x){\n"
" break loop;\n"
"}");
ExpectToken(JsKeywords::kIdentifier, "loop");
ExpectToken(JsKeywords::kOperator, ":");
ExpectToken(JsKeywords::kFor, "for");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kVar, "var");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kNumber, "0");
ExpectParseStack("Start BkKwd ( Other Expr");
ExpectToken(JsKeywords::kOperator, ";");
ExpectParseStack("Start BkKwd (");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "<");
ExpectToken(JsKeywords::kNumber, "5");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kOperator, "++");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectParseStack("Start BkKwd ( Expr");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start BkHdr");
ExpectToken(JsKeywords::kOperator, "{");
ExpectParseStack("Start BkHdr {");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kBreak, "break");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "loop");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kOperator, "}");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Keywords) {
// A piece of code that contains every keyword at least once.
BeginTokenizing("(function(){\n"
" var\n"
" x=typeof null\n"
" const y=void false\n"
" delete x\n"
" if(this instanceof String){\n"
" debugger\n"
" for(z in this)\n"
" continue\n"
" do break;while(true)\n"
" switch(y){\n"
" case 0:\n"
" default:\n"
" try{\n"
" with(this){\n"
" throw new Object()\n"
" }\n"
" }catch(e){\n"
" return\n"
" }finally{}\n"
" }\n"
" }else return\n"
"})");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kFunction, "function");
ExpectParseStack("Start ( BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kOperator, ")");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectParseStack("Start ( BkHdr {");
ExpectToken(JsKeywords::kVar, "var");
ExpectParseStack("Start ( BkHdr { Other");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kTypeof, "typeof");
ExpectParseStack("Start ( BkHdr { Other Expr Oper");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kNull, "null");
ExpectParseStack("Start ( BkHdr { Other Expr");
ExpectToken(JsKeywords::kSemiInsert, "\n ");
ExpectParseStack("Start ( BkHdr {");
ExpectToken(JsKeywords::kConst, "const");
ExpectParseStack("Start ( BkHdr { Other");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "y");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kVoid, "void");
ExpectParseStack("Start ( BkHdr { Other Expr Oper");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kFalse, "false");
ExpectParseStack("Start ( BkHdr { Other Expr");
ExpectToken(JsKeywords::kSemiInsert, "\n ");
ExpectParseStack("Start ( BkHdr {");
ExpectToken(JsKeywords::kDelete, "delete");
ExpectParseStack("Start ( BkHdr { Oper");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kSemiInsert, "\n ");
ExpectParseStack("Start ( BkHdr {");
ExpectToken(JsKeywords::kIf, "if");
ExpectParseStack("Start ( BkHdr { BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kThis, "this");
ExpectParseStack("Start ( BkHdr { BkKwd ( Expr");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kInstanceof, "instanceof");
ExpectParseStack("Start ( BkHdr { BkKwd ( Expr Oper");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "String");
ExpectToken(JsKeywords::kOperator, ")");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectParseStack("Start ( BkHdr { BkHdr {");
ExpectToken(JsKeywords::kDebugger, "debugger");
ExpectParseStack("Start ( BkHdr { BkHdr { Jump");
ExpectToken(JsKeywords::kSemiInsert, "\n ");
ExpectParseStack("Start ( BkHdr { BkHdr {");
ExpectToken(JsKeywords::kFor, "for");
ExpectParseStack("Start ( BkHdr { BkHdr { BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kIdentifier, "z");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIn, "in");
ExpectParseStack("Start ( BkHdr { BkHdr { BkKwd ( Expr Oper");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kThis, "this");
ExpectParseStack("Start ( BkHdr { BkHdr { BkKwd ( Expr");
ExpectToken(JsKeywords::kOperator, ")");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr");
ExpectToken(JsKeywords::kContinue, "continue");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr Jump");
ExpectToken(JsKeywords::kSemiInsert, "\n ");
ExpectParseStack("Start ( BkHdr { BkHdr {");
ExpectToken(JsKeywords::kDo, "do");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kBreak, "break");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr Jump");
ExpectToken(JsKeywords::kOperator, ";");
ExpectParseStack("Start ( BkHdr { BkHdr {");
ExpectToken(JsKeywords::kWhile, "while");
ExpectParseStack("Start ( BkHdr { BkHdr { BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kTrue, "true");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kSwitch, "switch");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kIdentifier, "y");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr");
ExpectToken(JsKeywords::kOperator, "{");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr {");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kCase, "case");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { Oper");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kNumber, "0");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { Expr");
ExpectToken(JsKeywords::kOperator, ":");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr {");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kDefault, "default");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { Other");
ExpectToken(JsKeywords::kOperator, ":");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr {");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kTry, "try");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkHdr");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkHdr {");
ExpectToken(JsKeywords::kWith, "with");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkHdr { BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kThis, "this");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkHdr { BkKwd ( Expr");
ExpectToken(JsKeywords::kOperator, ")");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkHdr { BkHdr {");
ExpectToken(JsKeywords::kThrow, "throw");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkHdr { BkHdr { RetTh");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kNew, "new");
ExpectParseStack(
"Start ( BkHdr { BkHdr { BkHdr { BkHdr { BkHdr { RetTh Oper");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "Object");
ExpectParseStack(
"Start ( BkHdr { BkHdr { BkHdr { BkHdr { BkHdr { RetTh Expr");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kOperator, ")");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectParseStack(
"Start ( BkHdr { BkHdr { BkHdr { BkHdr { BkHdr { RetTh Expr");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kOperator, "}");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr {");
ExpectToken(JsKeywords::kCatch, "catch");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kIdentifier, "e");
ExpectToken(JsKeywords::kOperator, ")");
ExpectToken(JsKeywords::kOperator, "{");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkHdr {");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kReturn, "return");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkHdr { RetTh");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kOperator, "}");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr {");
ExpectToken(JsKeywords::kFinally, "finally");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkHdr");
ExpectToken(JsKeywords::kOperator, "{");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr { BkHdr {");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectParseStack("Start ( BkHdr { BkHdr { BkHdr {");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kOperator, "}");
ExpectParseStack("Start ( BkHdr {");
ExpectToken(JsKeywords::kElse, "else");
ExpectParseStack("Start ( BkHdr { BkHdr");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kReturn, "return");
ExpectParseStack("Start ( BkHdr { BkHdr RetTh");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kOperator, "}");
ExpectParseStack("Start ( Expr");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start Expr");
ExpectEndOfInput();
ExpectParseStack("");
}
TEST_F(JsTokenizerTest, StrictModeReservedWords) {
// These names are reserved words in strict mode, but otherwise they're legal
// identifiers.
// TODO(mdsteele): At some point we may want to implement strict mode error
// checking, at which point we should add a test with "use strict".
BeginTokenizing("var implements,interface,let,package\n"
" ,private,protected,public,static,yield;");
ExpectToken(JsKeywords::kVar, "var", "Start Other");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "implements", "Start Other Expr");
ExpectToken(JsKeywords::kOperator, ",", "Start Other");
ExpectToken(JsKeywords::kIdentifier, "interface");
ExpectToken(JsKeywords::kOperator, ",");
ExpectToken(JsKeywords::kIdentifier, "let");
ExpectToken(JsKeywords::kOperator, ",");
ExpectToken(JsKeywords::kIdentifier, "package");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kOperator, ",");
ExpectToken(JsKeywords::kIdentifier, "private");
ExpectToken(JsKeywords::kOperator, ",");
ExpectToken(JsKeywords::kIdentifier, "protected");
ExpectToken(JsKeywords::kOperator, ",");
ExpectToken(JsKeywords::kIdentifier, "public");
ExpectToken(JsKeywords::kOperator, ",");
ExpectToken(JsKeywords::kIdentifier, "static");
ExpectToken(JsKeywords::kOperator, ",");
ExpectToken(JsKeywords::kIdentifier, "yield");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, ReservedWordsAsFieldNames1) {
// Reserved words may be used as identifiers in certain contexts, such as
// field access.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Reserved_word_usage
BeginTokenizing("document.true=true;");
ExpectToken(JsKeywords::kIdentifier, "document");
ExpectToken(JsKeywords::kOperator, ".");
ExpectToken(JsKeywords::kIdentifier, "true");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kTrue, "true");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, ReservedWordsAsFieldNames2) {
// Reserved words may be used as identifiers in certain contexts, such as
// object literal property names.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Reserved_word_usage
BeginTokenizing("x={if:false,class:3};");
ExpectToken(JsKeywords::kIdentifier, "x", "Start Expr");
ExpectToken(JsKeywords::kOperator, "=", "Start Expr Oper");
ExpectToken(JsKeywords::kOperator, "{", "Start Expr Oper {");
ExpectToken(JsKeywords::kIdentifier, "if", "Start Expr Oper { Expr");
ExpectToken(JsKeywords::kOperator, ":", "Start Expr Oper { Oper");
ExpectToken(JsKeywords::kFalse, "false", "Start Expr Oper { Expr");
ExpectToken(JsKeywords::kOperator, ",", "Start Expr Oper {");
ExpectToken(JsKeywords::kIdentifier, "class", "Start Expr Oper { Expr");
ExpectToken(JsKeywords::kOperator, ":", "Start Expr Oper { Oper");
ExpectToken(JsKeywords::kNumber, "3", "Start Expr Oper { Expr");
ExpectToken(JsKeywords::kOperator, "}", "Start Expr");
ExpectToken(JsKeywords::kOperator, ";", "Start");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, ReservedWordsAsLabelNames) {
// Reserved words may NOT be used as label identifiers. We should
// distinguish this case from the object literal case above.
BeginTokenizing("{if:false,class:3};");
ExpectToken(JsKeywords::kOperator, "{", "Start {");
ExpectToken(JsKeywords::kIf, "if", "Start { BkKwd");
ExpectError(":false,class:3};");
}
TEST_F(JsTokenizerTest, Comments) {
BeginTokenizing("x=1; // hello\n"
"y=/* world */2;\n"
"z<!--sgml\n" // <!-- is a line comment, but
"foo-->bar;\n" // --> is a line comment only at
" --> sgml\n"); // the start of the line.
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kNumber, "1");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kComment, "// hello");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "y");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kComment, "/* world */");
ExpectToken(JsKeywords::kNumber, "2");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "z");
ExpectToken(JsKeywords::kComment, "<!--sgml");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectToken(JsKeywords::kOperator, "--");
ExpectToken(JsKeywords::kOperator, ">");
ExpectToken(JsKeywords::kIdentifier, "bar");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kComment, "--> sgml");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, LineComments) {
BeginTokenizing("1//foo\r"
"+2//bar\xE2\x80\xA8" // U+2028 LINE SEPARATOR"
"+3//baz\xE2\x80\xA9" // U+2029 PARAGRAPH SEPARATOR
"4//quux\xE2\x81\x9F" // U+205F MEDIUM MATHEMATICAL SPACE
"+5//hello, world!\n"
"6");
ExpectToken(JsKeywords::kNumber, "1");
ExpectToken(JsKeywords::kComment, "//foo");
ExpectToken(JsKeywords::kLineSeparator, "\r");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kNumber, "2");
ExpectToken(JsKeywords::kComment, "//bar");
ExpectToken(JsKeywords::kLineSeparator, "\xE2\x80\xA8");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kNumber, "3");
ExpectToken(JsKeywords::kComment, "//baz");
ExpectToken(JsKeywords::kSemiInsert, "\xE2\x80\xA9");
ExpectToken(JsKeywords::kNumber, "4");
ExpectToken(JsKeywords::kComment, "//quux\xE2\x81\x9F+5//hello, world!");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kNumber, "6");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, SgmlComments) {
BeginTokenizing("4+<!--foo\n"
"3+\n"
"/*foo*/ --> foo\n"
"x --> foo\n");
ExpectToken(JsKeywords::kNumber, "4");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kComment, "<!--foo");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kNumber, "3");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kComment, "/*foo*/");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kComment, "--> foo");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "--");
ExpectToken(JsKeywords::kOperator, ">");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Whitespace) {
BeginTokenizing("\xEF\xBB\xBF" // U+FEFF BYTE ORDER MARK
"a\f=\t1\v+ 2\r"
"5\xC2\xA0" // U+00A0 NO-BREAK SPACE
"\xE2\x81\x9F" // U+205F MEDIUM MATHEMATICAL SPACE
"+\xE1\x9A\x80" // U+1680 OGHAM SPACE MARK
"7\xE2\x80\xA9" // U+2029 PARAGRAPH SEPARATOR
";\xE2\x80\xA8"); // U+2028 LINE SEPARATOR
ExpectToken(JsKeywords::kWhitespace, "\xEF\xBB\xBF");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kWhitespace, "\f");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kWhitespace, "\t");
ExpectToken(JsKeywords::kNumber, "1");
ExpectToken(JsKeywords::kWhitespace, "\v");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kNumber, "2");
ExpectToken(JsKeywords::kSemiInsert, "\r");
ExpectToken(JsKeywords::kNumber, "5");
ExpectToken(JsKeywords::kWhitespace, "\xC2\xA0\xE2\x81\x9F");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kWhitespace, "\xE1\x9A\x80");
ExpectToken(JsKeywords::kNumber, "7");
ExpectToken(JsKeywords::kLineSeparator, "\xE2\x80\xA9");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\xE2\x80\xA8");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, NumericLiterals) {
BeginTokenizing("var foo =\n .85 - 0191.e+3+0171.toString();\n"
"1..property");
ExpectToken(JsKeywords::kVar, "var");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kLineSeparator, "\n ");
ExpectToken(JsKeywords::kNumber, ".85");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "-");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kNumber, "0191.e+3");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kNumber, "0171");
ExpectToken(JsKeywords::kOperator, ".");
ExpectToken(JsKeywords::kIdentifier, "toString");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kOperator, ")");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kNumber, "1.");
ExpectToken(JsKeywords::kOperator, ".");
ExpectToken(JsKeywords::kIdentifier, "property");
ExpectEndOfInput();
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, RegexLiterals) {
BeginTokenizing("foo=/quux/;\n"
"bar=/quux/ig;\n"
"baz=a/quux/ig;");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kRegex, "/quux/");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "bar");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kRegex, "/quux/ig");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "baz");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kIdentifier, "quux");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kIdentifier, "ig");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, RegexVsSlash) {
BeginTokenizing("if(a*(b+c)/d<e)/d<e/.exec('\\'');\n"
"else/x/.exec(\"\");");
ExpectParseStack("Start");
ExpectToken(JsKeywords::kIf, "if");
ExpectParseStack("Start BkKwd");
ExpectToken(JsKeywords::kOperator, "(");
ExpectParseStack("Start BkKwd (");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectParseStack("Start BkKwd ( Expr");
ExpectToken(JsKeywords::kOperator, "*");
ExpectParseStack("Start BkKwd ( Expr Oper");
ExpectToken(JsKeywords::kOperator, "(");
ExpectParseStack("Start BkKwd ( Expr Oper (");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectParseStack("Start BkKwd ( Expr Oper ( Expr");
ExpectToken(JsKeywords::kOperator, "+");
ExpectParseStack("Start BkKwd ( Expr Oper ( Expr Oper");
ExpectToken(JsKeywords::kIdentifier, "c");
ExpectParseStack("Start BkKwd ( Expr Oper ( Expr");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start BkKwd ( Expr");
ExpectToken(JsKeywords::kOperator, "/");
ExpectParseStack("Start BkKwd ( Expr Oper");
ExpectToken(JsKeywords::kIdentifier, "d");
ExpectParseStack("Start BkKwd ( Expr");
ExpectToken(JsKeywords::kOperator, "<");
ExpectParseStack("Start BkKwd ( Expr Oper");
ExpectToken(JsKeywords::kIdentifier, "e");
ExpectParseStack("Start BkKwd ( Expr");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start BkHdr");
ExpectToken(JsKeywords::kRegex, "/d<e/");
ExpectParseStack("Start BkHdr Expr");
ExpectToken(JsKeywords::kOperator, ".");
ExpectParseStack("Start BkHdr Expr .");
ExpectToken(JsKeywords::kIdentifier, "exec");
ExpectParseStack("Start BkHdr Expr");
ExpectToken(JsKeywords::kOperator, "(");
ExpectParseStack("Start BkHdr Expr (");
ExpectToken(JsKeywords::kStringLiteral, "'\\''");
ExpectParseStack("Start BkHdr Expr ( Expr");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start BkHdr Expr");
ExpectToken(JsKeywords::kOperator, ";");
ExpectParseStack("Start");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kElse, "else");
ExpectParseStack("Start BkHdr");
ExpectToken(JsKeywords::kRegex, "/x/");
ExpectParseStack("Start BkHdr Expr");
ExpectToken(JsKeywords::kOperator, ".");
ExpectParseStack("Start BkHdr Expr .");
ExpectToken(JsKeywords::kIdentifier, "exec");
ExpectParseStack("Start BkHdr Expr");
ExpectToken(JsKeywords::kOperator, "(");
ExpectParseStack("Start BkHdr Expr (");
ExpectToken(JsKeywords::kStringLiteral, "\"\"");
ExpectParseStack("Start BkHdr Expr ( Expr");
ExpectToken(JsKeywords::kOperator, ")");
ExpectParseStack("Start BkHdr Expr");
ExpectToken(JsKeywords::kOperator, ";");
ExpectParseStack("Start");
ExpectEndOfInput();
ExpectParseStack("");
}
TEST_F(JsTokenizerTest, Operators1) {
BeginTokenizing("foo /= bar+++baz;\n"
"a=b==c&&d===e;\n"
"a>>>=b>=c?d>>_:$;");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "/=");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "bar");
ExpectToken(JsKeywords::kOperator, "++");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kIdentifier, "baz");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectToken(JsKeywords::kOperator, "==");
ExpectToken(JsKeywords::kIdentifier, "c");
ExpectToken(JsKeywords::kOperator, "&&");
ExpectToken(JsKeywords::kIdentifier, "d");
ExpectToken(JsKeywords::kOperator, "===");
ExpectToken(JsKeywords::kIdentifier, "e");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, ">>>=");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectToken(JsKeywords::kOperator, ">=");
ExpectToken(JsKeywords::kIdentifier, "c");
ExpectToken(JsKeywords::kOperator, "?");
ExpectToken(JsKeywords::kIdentifier, "d");
ExpectToken(JsKeywords::kOperator, ">>");
ExpectToken(JsKeywords::kIdentifier, "_");
ExpectToken(JsKeywords::kOperator, ":");
ExpectToken(JsKeywords::kIdentifier, "$");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Operators2) {
BeginTokenizing("a+++b\n"
"a+ ++b\n"
"a+ +b\n"
"a---b\n"
"a-++b\n"
"!!b\n");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "++");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "++");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "--");
ExpectToken(JsKeywords::kOperator, "-");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "-");
ExpectToken(JsKeywords::kOperator, "++");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kOperator, "!");
ExpectToken(JsKeywords::kOperator, "!");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Colons1) {
// Each of the three lines below contains the substring ":{}/x/i". However,
// in the first two lines that's a label colon, followed by an empty block,
// followed by a regex literal, while in the third line it's an ternary
// operator, followed by an empty object literal, followed by division.
BeginTokenizing("switch(x){default:{}/x/i}\n"
"foobar:{}/x/i;\n"
"a?b:{}/x/i");
ExpectToken(JsKeywords::kSwitch, "switch");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, ")");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kDefault, "default");
ExpectParseStack("Start BkHdr { Other");
ExpectToken(JsKeywords::kOperator, ":");
ExpectParseStack("Start BkHdr {");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kRegex, "/x/i");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "foobar");
ExpectParseStack("Start Expr");
ExpectToken(JsKeywords::kOperator, ":");
ExpectParseStack("Start");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kRegex, "/x/i");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "?");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectParseStack("Start Expr ? Expr");
ExpectToken(JsKeywords::kOperator, ":");
ExpectParseStack("Start Expr Oper");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kIdentifier, "i");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Colons2) {
// Each of the three lines below contains the substring "{foo:{}/x/i}". In
// the first line the outer braces are a block, so the inside is a label,
// followed by an empty block, followed by a regex literal. in the second
// line the outer braces are an object literal, so the inside is an object
// property equal to an empty object literal divided by some other values.
// In the third line the outer braces are again a block (even though they're
// part of an expression), so the inside is again a label/block/regex.
BeginTokenizing("{foo:{}/x/i}\n"
"y={foo:{}/x/i}\n"
"z=function(){foo:{}/x/i}");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectToken(JsKeywords::kOperator, ":");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kRegex, "/x/i");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "y");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectToken(JsKeywords::kOperator, ":");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kIdentifier, "i");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kIdentifier, "z");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kFunction, "function");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kOperator, ")");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectToken(JsKeywords::kOperator, ":");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kRegex, "/x/i");
ExpectToken(JsKeywords::kOperator, "}");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, ObjectLiteralsArray) {
BeginTokenizing("[{a:42},{a:32}]");
ExpectToken(JsKeywords::kOperator, "[", "Start [");
ExpectToken(JsKeywords::kOperator, "{", "Start [ {");
ExpectToken(JsKeywords::kIdentifier, "a", "Start [ { Expr");
ExpectToken(JsKeywords::kOperator, ":", "Start [ { Oper");
ExpectToken(JsKeywords::kNumber, "42", "Start [ { Expr");
ExpectToken(JsKeywords::kOperator, "}", "Start [ Expr");
ExpectToken(JsKeywords::kOperator, ",", "Start [");
ExpectToken(JsKeywords::kOperator, "{", "Start [ {");
ExpectToken(JsKeywords::kIdentifier, "a", "Start [ { Expr");
ExpectToken(JsKeywords::kOperator, ":", "Start [ { Oper");
ExpectToken(JsKeywords::kNumber, "32", "Start [ { Expr");
ExpectToken(JsKeywords::kOperator, "}", "Start [ Expr");
ExpectToken(JsKeywords::kOperator, "]", "Start Expr");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, TrailingCommas) {
BeginTokenizing("x={a:1,}\n"
"y=[,,]");
ExpectToken(JsKeywords::kIdentifier, "x", "Start Expr");
ExpectToken(JsKeywords::kOperator, "=", "Start Expr Oper");
ExpectToken(JsKeywords::kOperator, "{", "Start Expr Oper {");
ExpectToken(JsKeywords::kIdentifier, "a", "Start Expr Oper { Expr");
ExpectToken(JsKeywords::kOperator, ":", "Start Expr Oper { Oper");
ExpectToken(JsKeywords::kNumber, "1", "Start Expr Oper { Expr");
ExpectToken(JsKeywords::kOperator, ",", "Start Expr Oper {");
ExpectToken(JsKeywords::kOperator, "}", "Start Expr");
ExpectToken(JsKeywords::kSemiInsert, "\n", "Start");
ExpectToken(JsKeywords::kIdentifier, "y", "Start Expr");
ExpectToken(JsKeywords::kOperator, "=", "Start Expr Oper");
ExpectToken(JsKeywords::kOperator, "[", "Start Expr Oper [");
ExpectToken(JsKeywords::kOperator, ",", "Start Expr Oper [");
ExpectToken(JsKeywords::kOperator, ",", "Start Expr Oper [");
ExpectToken(JsKeywords::kOperator, "]", "Start Expr");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, CommaOperator) {
BeginTokenizing("x=(y++,y*5)");
ExpectToken(JsKeywords::kIdentifier, "x", "Start Expr");
ExpectToken(JsKeywords::kOperator, "=", "Start Expr Oper");
ExpectToken(JsKeywords::kOperator, "(", "Start Expr Oper (");
ExpectToken(JsKeywords::kIdentifier, "y", "Start Expr Oper ( Expr");
ExpectToken(JsKeywords::kOperator, "++", "Start Expr Oper ( Expr");
ExpectToken(JsKeywords::kOperator, ",", "Start Expr Oper ( Expr Oper");
ExpectToken(JsKeywords::kIdentifier, "y", "Start Expr Oper ( Expr");
ExpectToken(JsKeywords::kOperator, "*", "Start Expr Oper ( Expr Oper");
ExpectToken(JsKeywords::kNumber, "5", "Start Expr Oper ( Expr");
ExpectToken(JsKeywords::kOperator, ")", "Start Expr");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, ObjectLiteralRegexLiteral) {
// On the first line, this looks like it should be an object literal divided
// by x divided by i, but nope, that's a block with a labelled expression
// statement, followed by a regex literal. The second line, on the other
// hand, _is_ an object literal, followed by division.
BeginTokenizing("{foo:1} / x/i;\n"
"x={foo:1} / x/i;");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectToken(JsKeywords::kOperator, ":");
ExpectToken(JsKeywords::kNumber, "1");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kRegex, "/ x/i");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kOperator, "{");
ExpectToken(JsKeywords::kIdentifier, "foo");
ExpectToken(JsKeywords::kOperator, ":");
ExpectToken(JsKeywords::kNumber, "1");
ExpectToken(JsKeywords::kOperator, "}");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kIdentifier, "i");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, EmptyBlockRegexLiteral) {
BeginTokenizing("if(true){{}/foo/}");
ExpectToken(JsKeywords::kIf, "if", "Start BkKwd");
ExpectToken(JsKeywords::kOperator, "(", "Start BkKwd (");
ExpectToken(JsKeywords::kTrue, "true", "Start BkKwd ( Expr");
ExpectToken(JsKeywords::kOperator, ")", "Start BkHdr");
ExpectToken(JsKeywords::kOperator, "{", "Start BkHdr {");
ExpectToken(JsKeywords::kOperator, "{", "Start BkHdr { {");
ExpectToken(JsKeywords::kOperator, "}", "Start BkHdr {");
ExpectToken(JsKeywords::kRegex, "/foo/", "Start BkHdr { Expr");
ExpectToken(JsKeywords::kOperator, "}", "Start");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, TrickyRegexLiteral) {
BeginTokenizing("var x=a[0] / b /i;\n"
"var y=a[0]+/ b /i;");
ExpectToken(JsKeywords::kVar, "var");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "[");
ExpectToken(JsKeywords::kNumber, "0");
ExpectToken(JsKeywords::kOperator, "]");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kIdentifier, "i");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kVar, "var");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "y");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "[");
ExpectToken(JsKeywords::kNumber, "0");
ExpectToken(JsKeywords::kOperator, "]");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kRegex, "/ b /i");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, RegexLiteralsWithBrackets) {
BeginTokenizing("/http:\\/\\/[^/]+\\// / /z[\\]/ ]/");
// The / in [^/] doesn't end the regex.
ExpectToken(JsKeywords::kRegex, "/http:\\/\\/[^/]+\\//");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "/");
ExpectToken(JsKeywords::kWhitespace, " ");
// The first ] is escaped and doesn't close the [, so the following / doesn't
// close the regex.
ExpectToken(JsKeywords::kRegex, "/z[\\]/ ]/");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, ReturnRegex) {
// Make sure we understand that this is not division; "return" is not an
// identifier!
BeginTokenizing("return / x /g;\n"
"return/#.+/.test(\n'#24' );");
ExpectToken(JsKeywords::kReturn, "return");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kRegex, "/ x /g");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kReturn, "return");
ExpectToken(JsKeywords::kRegex, "/#.+/");
ExpectToken(JsKeywords::kOperator, ".");
ExpectToken(JsKeywords::kIdentifier, "test");
ExpectToken(JsKeywords::kOperator, "(");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kStringLiteral, "'#24'");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, ")");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, ReturnRegex2) {
// Make sure we understand that this is not division; "return" is not an
// identifier!
BeginTokenizing("return / x /g;");
ExpectToken(JsKeywords::kReturn, "return");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kRegex, "/ x /g");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, ThrowRegex) {
// Make sure we understand that this is not division; "throw" is not an
// identifier! (And yes, in JS you're allowed to throw a regex.)
BeginTokenizing("throw / x /g;");
ExpectToken(JsKeywords::kThrow, "throw");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kRegex, "/ x /g");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, UnicodeRegexFlags) {
// The Look Of Disapproval emoticon is probably not a semantically valid
// regex flag, but it is lexically valid, so we should be able to tokenize
// it.
BeginTokenizing("/\xE2\x98\x83/\xE0\xB2\xA0_\xE0\xB2\xA0\xE2\x80\xA9;");
ExpectToken(JsKeywords::kRegex, "/\xE2\x98\x83/\xE0\xB2\xA0_\xE0\xB2\xA0");
ExpectToken(JsKeywords::kLineSeparator, "\xE2\x80\xA9");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, SemicolonInsertion1) {
BeginTokenizing("3\n" // Semicolon is not inserted here.
"// foo\n"
"--> foo\n"
"-5\n" // Semicolon is inserted here.
"// bar\n"
"6");
ExpectToken(JsKeywords::kNumber, "3");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kComment, "// foo");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kComment, "--> foo");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kOperator, "-");
ExpectToken(JsKeywords::kNumber, "5");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kComment, "// bar");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kNumber, "6");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, SemicolonInsertion2) {
BeginTokenizing("w\n" // Semicolon is inserted here...
"++\n" // ...but not here.
"x\n" // Semicolon inserted here again.
"y++\n" // And here again.
"z");
ExpectToken(JsKeywords::kIdentifier, "w", "Start Expr");
ExpectToken(JsKeywords::kSemiInsert, "\n", "Start");
ExpectToken(JsKeywords::kOperator, "++", "Start Oper");
ExpectToken(JsKeywords::kLineSeparator, "\n", "Start Oper");
ExpectToken(JsKeywords::kIdentifier, "x", "Start Expr");
ExpectToken(JsKeywords::kSemiInsert, "\n", "Start");
ExpectToken(JsKeywords::kIdentifier, "y", "Start Expr");
ExpectToken(JsKeywords::kOperator, "++", "Start Expr");
ExpectToken(JsKeywords::kSemiInsert, "\n", "Start");
ExpectToken(JsKeywords::kIdentifier, "z", "Start Expr");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, SemicolonInsertion3) {
BeginTokenizing("x\nin\xE1\x9A\x80y;\n" // U+1680 OGHAM SPACE MARK
"x\nin\\u0063\ny;\n"
"x\ninstanceof\ny;\n"
"x\ninstanceof\xE0\xB2\xA0_\n" // U+0CA0 KANNADA LETTER TTHA
"y;");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIn, "in");
ExpectToken(JsKeywords::kWhitespace, "\xE1\x9A\x80");
ExpectToken(JsKeywords::kIdentifier, "y");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kIdentifier, "in\\u0063");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kIdentifier, "y");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kInstanceof, "instanceof");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "y");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kIdentifier, "instanceof\xE0\xB2\xA0_");
ExpectToken(JsKeywords::kSemiInsert, "\n");
ExpectToken(JsKeywords::kIdentifier, "y");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, SemicolonInsertion4) {
BeginTokenizing("x={}\n"
"{debugger}");
ExpectToken(JsKeywords::kIdentifier, "x", "Start Expr");
ExpectToken(JsKeywords::kOperator, "=", "Start Expr Oper");
ExpectToken(JsKeywords::kOperator, "{", "Start Expr Oper {");
ExpectToken(JsKeywords::kOperator, "}", "Start Expr");
ExpectToken(JsKeywords::kSemiInsert, "\n", "Start");
ExpectToken(JsKeywords::kOperator, "{", "Start {");
ExpectToken(JsKeywords::kDebugger, "debugger", "Start { Jump");
ExpectToken(JsKeywords::kOperator, "}", "Start");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, UnclosedBlockComment) {
BeginTokenizing("debugger; /* foo");
ExpectToken(JsKeywords::kDebugger, "debugger");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectError("/* foo");
}
TEST_F(JsTokenizerTest, UnclosedRegexLiteral) {
BeginTokenizing("var bar=/quux;");
ExpectParseStack("Start");
ExpectToken(JsKeywords::kVar, "var", "Start Other");
ExpectToken(JsKeywords::kWhitespace, " ", "Start Other");
ExpectToken(JsKeywords::kIdentifier, "bar", "Start Other Expr");
ExpectToken(JsKeywords::kOperator, "=", "Start Other Expr Oper");
ExpectError("/quux;");
ExpectParseStack("Start Other Expr Oper");
}
TEST_F(JsTokenizerTest, LinebreakInRegex) {
// Regexes cannot contain linebreaks.
BeginTokenizing("x=/foo\nquux/;");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "=");
ExpectError("/foo\nquux/;");
// They can contain Unicode characters, but not Unicode linebreaks.
BeginTokenizing("x=/foo\xE2\x98\x83quux/+" // U+2603 SNOWMAN
"/foo\xE2\x80\xA9quux/;"); // U+2029 PARAGRAPH SEPARATOR
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kRegex, "/foo\xE2\x98\x83quux/");
ExpectToken(JsKeywords::kOperator, "+");
ExpectError("/foo\xE2\x80\xA9quux/;");
// Unlike in strings, newlines in regexes cannot be escaped by backslashes.
BeginTokenizing("x=/foo\\\nquux/;");
ExpectToken(JsKeywords::kIdentifier, "x");
ExpectToken(JsKeywords::kOperator, "=");
ExpectError("/foo\\\nquux/;");
}
TEST_F(JsTokenizerTest, LinebreakInStringLiteral) {
// Strings can contain linebreaks only if escaped by a backslash.
BeginTokenizing("'foo\\\nquux'+"
// A CRLF counts as one linebreak.
"\"foo\\\r\nquux\"+"
// Apparently, so does LFCR, for some reason.
"\"foo\\\n\rquux\"+"
// But two LFs are two linebreaks!
"'foo\\\n\nquux';");
ExpectToken(JsKeywords::kStringLiteral, "'foo\\\nquux'");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kStringLiteral, "\"foo\\\r\nquux\"");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kStringLiteral, "\"foo\\\n\rquux\"");
ExpectToken(JsKeywords::kOperator, "+");
ExpectError("'foo\\\n\nquux';");
// Strings can contain Unicode characters.
BeginTokenizing("'foo\xE2\x98\x83quux'+" // U+2603 SNOWMAN
// Unicode linebreaks are allowed if backslash-escaped.
"'foo\\\xE2\x80\xA8quux'+" // U+2028 LINE SEPARATOR
// But it's an error if the Unicode linebreak isn't escaped!
"'foo\xE2\x80\xA8quux';"); // U+2028 LINE SEPARATOR
ExpectToken(JsKeywords::kStringLiteral, "'foo\xE2\x98\x83quux'");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kStringLiteral, "'foo\\\xE2\x80\xA8quux'");
ExpectToken(JsKeywords::kOperator, "+");
ExpectError("'foo\xE2\x80\xA8quux';");
}
TEST_F(JsTokenizerTest, EscapedQuotesInStringLiteral1) {
BeginTokenizing("'foo\\\\\\'bar';");
ExpectToken(JsKeywords::kStringLiteral, "'foo\\\\\\'bar'");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, EscapedQuotesInStringLiteral2) {
BeginTokenizing("\"baz\"+'foo\\\\\\'ba\"r';");
ExpectToken(JsKeywords::kStringLiteral, "\"baz\"");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kStringLiteral, "'foo\\\\\\'ba\"r'");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, EscapedQuotesInStringLiteral3) {
BeginTokenizing("'b\\\\az'+'foo\\'bar';");
ExpectToken(JsKeywords::kStringLiteral, "'b\\\\az'");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kStringLiteral, "'foo\\'bar'");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, EscapedQuotesInStringLiteral4) {
BeginTokenizing("'b\\'az'+'foobar';");
ExpectToken(JsKeywords::kStringLiteral, "'b\\'az'");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kStringLiteral, "'foobar'");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, EscapedQuotesInStringLiteral5) {
BeginTokenizing("\"f\xFFoo\\\\\\\"bar\";");
ExpectToken(JsKeywords::kStringLiteral, "\"f\xFFoo\\\\\\\"bar\"");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, UnclosedStringLiteral) {
BeginTokenizing("bar='quux;");
ExpectToken(JsKeywords::kIdentifier, "bar");
ExpectToken(JsKeywords::kOperator, "=");
ExpectError("'quux;");
}
TEST_F(JsTokenizerTest, UnmatchedCloseParen) {
BeginTokenizing("bar='quux');");
ExpectToken(JsKeywords::kIdentifier, "bar");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kStringLiteral, "'quux'");
ExpectError(");");
}
TEST_F(JsTokenizerTest, BogusInputCharacter) {
BeginTokenizing("var #foo;");
ExpectToken(JsKeywords::kVar, "var");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectError("#foo;");
}
TEST_F(JsTokenizerTest, BackslashesInIdentifier) {
BeginTokenizing("a\\u03c0b");
ExpectToken(JsKeywords::kIdentifier, "a\\u03c0b");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, BackslashesInString) {
BeginTokenizing("\"a\\\"b\"");
ExpectToken(JsKeywords::kStringLiteral, "\"a\\\"b\"");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, CombinePluses) {
BeginTokenizing("a+++b");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "++");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, CombinePluses2) {
BeginTokenizing("a+ ++b");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "++");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, CombinePlusesSpace) {
BeginTokenizing("a+ +b");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, CombineMinuses) {
BeginTokenizing("a---b");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "--");
ExpectToken(JsKeywords::kOperator, "-");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, CombineMixed) {
BeginTokenizing("a--+b");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "--");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, CombineMixed2) {
BeginTokenizing("a-++b");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, "-");
ExpectToken(JsKeywords::kOperator, "++");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, CombineBangs) {
BeginTokenizing("!!b");
ExpectToken(JsKeywords::kOperator, "!");
ExpectToken(JsKeywords::kOperator, "!");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, NumbersAndDotsAndIdentifiersAndKeywords) {
BeginTokenizing("return a.b+5.3");
ExpectToken(JsKeywords::kReturn, "return");
ExpectToken(JsKeywords::kWhitespace, " ");
ExpectToken(JsKeywords::kIdentifier, "a");
ExpectToken(JsKeywords::kOperator, ".");
ExpectToken(JsKeywords::kIdentifier, "b");
ExpectToken(JsKeywords::kOperator, "+");
ExpectToken(JsKeywords::kNumber, "5.3");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, NumberProperty) {
BeginTokenizing("1..property");
ExpectToken(JsKeywords::kNumber, "1.");
ExpectToken(JsKeywords::kOperator, ".");
ExpectToken(JsKeywords::kIdentifier, "property");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, LineCommentAtEndOfInput) {
BeginTokenizing("hello//world");
ExpectToken(JsKeywords::kIdentifier, "hello");
ExpectToken(JsKeywords::kComment, "//world");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Latin1BlockComment) {
// Try to tokenize input that is Latin-1 encoded. This is not valid UTF-8,
// but we should be able to proceed gracefully (in most cases) if the
// non-ascii characters only ever appear in string literals and comments.
BeginTokenizing("/* qu\xE9 pasa */\n");
ExpectToken(JsKeywords::kComment, "/* qu\xE9 pasa */");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Latin1LineComment) {
// Try to tokenize input that is Latin-1 encoded. This is not valid UTF-8,
// but we should be able to proceed gracefully (in most cases) if the
// non-ascii characters only ever appear in string literals and comments.
BeginTokenizing("// qu\xE9 pasa\n");
ExpectToken(JsKeywords::kComment, "// qu\xE9 pasa");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Latin1StringLiteral) {
// Try to tokenize input that is Latin-1 encoded. This is not valid UTF-8,
// but we should be able to proceed gracefully (in most cases) if the
// non-ascii characters only ever appear in string literals and comments.
BeginTokenizing("\"qu\xE9 pasa\"\n");
ExpectToken(JsKeywords::kStringLiteral, "\"qu\xE9 pasa\"");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectEndOfInput();
// An example with more complicated escaping:
BeginTokenizing("'\xAA\\'\xBB\\\r\n\xCC'\n");
ExpectToken(JsKeywords::kStringLiteral, "'\xAA\\'\xBB\\\r\n\xCC'");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, Latin1Input) {
// Try to tokenize input that is Latin-1 encoded. This is not valid UTF-8,
// but we should be able to proceed gracefully (in most cases) if the
// non-ascii characters only ever appear in string literals and comments.
BeginTokenizing("str='Qu\xE9 pasa';// 'qu\xE9' means 'what'\n"
"cents=/* 73\xA2 is $0.73 */73;");
ExpectToken(JsKeywords::kIdentifier, "str");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kStringLiteral, "'Qu\xE9 pasa'");
ExpectToken(JsKeywords::kOperator, ";");
ExpectToken(JsKeywords::kComment, "// 'qu\xE9' means 'what'");
ExpectToken(JsKeywords::kLineSeparator, "\n");
ExpectToken(JsKeywords::kIdentifier, "cents");
ExpectToken(JsKeywords::kOperator, "=");
ExpectToken(JsKeywords::kComment, "/* 73\xA2 is $0.73 */");
ExpectToken(JsKeywords::kNumber, "73");
ExpectToken(JsKeywords::kOperator, ";");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, JsonHeuristic) {
// Sometimes we put JSON data through the JavaScript tokenizer. Most JSON
// will parse just fine, but JSON object literals, without parse context to
// mark them as expressions (rather than code blocks) will generally not
// parse as JavaScript code. Therefore, the tokenizer has a heuristic to
// recognize input consisting of a JSON object literal and alter the parse
// state to handle it.
BeginTokenizing("{\"foo\":{\"bar\":1},\"baz\":2}");
// At first, we assume this is JS code, as usual:
ExpectToken(JsKeywords::kOperator, "{", "Start {");
ExpectToken(JsKeywords::kStringLiteral, "\"foo\"", "Start { Expr");
// Once we see the colon, we know this is actually a JSON object literal
// rather than a code block (a string literal followed by a colon isn't valid
// syntax at start-of-statement). Adding a synthetic Oper parse state in
// front of the { state allows us to treat this as an object literal rather
// than a code block. From here we can proceed as normal.
ExpectToken(JsKeywords::kOperator, ":", "Start Oper { Oper");
ExpectToken(JsKeywords::kOperator, "{", "Start Oper { Oper {");
ExpectToken(JsKeywords::kStringLiteral, "\"bar\"");
ExpectToken(JsKeywords::kOperator, ":", "Start Oper { Oper { Oper");
ExpectToken(JsKeywords::kNumber, "1", "Start Oper { Oper { Expr");
ExpectToken(JsKeywords::kOperator, "}", "Start Oper { Expr");
ExpectToken(JsKeywords::kOperator, ",", "Start Oper {");
ExpectToken(JsKeywords::kStringLiteral, "\"baz\"");
ExpectToken(JsKeywords::kOperator, ":", "Start Oper { Oper");
ExpectToken(JsKeywords::kNumber, "2", "Start Oper { Expr");
ExpectToken(JsKeywords::kOperator, "}", "Start Expr");
ExpectEndOfInput();
}
TEST_F(JsTokenizerTest, TokenizeAngular) {
ExpectTokenizeFileSuccessfully("angular.original");
}
TEST_F(JsTokenizerTest, TokenizeJQuery) {
ExpectTokenizeFileSuccessfully("jquery.original");
}
TEST_F(JsTokenizerTest, TokenizePrototype) {
ExpectTokenizeFileSuccessfully("prototype.original");
}
} // namespace