| // 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 |