mirror of
https://github.com/robertkrimen/otto
synced 2025-10-19 19:55:30 +08:00
This patch implements source map support in the parser, the runtime, the script record, and the stack trace printing. The library used to parse and use the source maps is gopkg.in/sourcemap.v1. Unlike earlier versions of this patch, the consumer of otto does not need parse the source map on their own - it's now handled similarly to parsing JavaScript content. To use a source map, the consumer must explicitly parse their source into a `Script` object with `Otto.CompileWithSourceMap`. The script record returned from that call will carry source map information with it, and all location-related functions should reflect the original source positions.
1024 lines
26 KiB
Go
1024 lines
26 KiB
Go
package parser
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/robertkrimen/otto/ast"
|
|
"github.com/robertkrimen/otto/file"
|
|
"github.com/robertkrimen/otto/underscore"
|
|
)
|
|
|
|
func firstErr(err error) error {
|
|
switch err := err.(type) {
|
|
case ErrorList:
|
|
return err[0]
|
|
}
|
|
return err
|
|
}
|
|
|
|
var matchBeforeAfterSeparator = regexp.MustCompile(`(?m)^[ \t]*---$`)
|
|
|
|
func testParse(src string) (parser *_parser, program *ast.Program, err error) {
|
|
return testParseWithMode(src, 0)
|
|
}
|
|
|
|
func testParseWithMode(src string, mode Mode) (parser *_parser, program *ast.Program, err error) {
|
|
defer func() {
|
|
if tmp := recover(); tmp != nil {
|
|
switch tmp := tmp.(type) {
|
|
case string:
|
|
if strings.HasPrefix(tmp, "SyntaxError:") {
|
|
parser = nil
|
|
program = nil
|
|
err = errors.New(tmp)
|
|
return
|
|
}
|
|
}
|
|
panic(tmp)
|
|
}
|
|
}()
|
|
parser = _newParser("", src, 1, nil)
|
|
parser.mode = mode
|
|
program, err = parser.parse()
|
|
return
|
|
}
|
|
|
|
func TestParseFile(t *testing.T) {
|
|
tt(t, func() {
|
|
_, err := ParseFile(nil, "", `/abc/`, 0)
|
|
is(err, nil)
|
|
|
|
_, err = ParseFile(nil, "", `/(?!def)abc/`, IgnoreRegExpErrors)
|
|
is(err, nil)
|
|
|
|
_, err = ParseFile(nil, "", `/(?!def)abc/`, 0)
|
|
is(err, "(anonymous): Line 1:1 Invalid regular expression: re2: Invalid (?!) <lookahead>")
|
|
|
|
_, err = ParseFile(nil, "", `/(?!def)abc/; return`, IgnoreRegExpErrors)
|
|
is(err, "(anonymous): Line 1:15 Illegal return statement")
|
|
|
|
_, err = ParseFile(nil, "/make-sure-file-path-is-returned-not-anonymous", `a..`, 0)
|
|
is(err, "/make-sure-file-path-is-returned-not-anonymous: Line 1:3 Unexpected token .")
|
|
})
|
|
}
|
|
|
|
func TestParseFunction(t *testing.T) {
|
|
tt(t, func() {
|
|
test := func(prm, bdy string, expect interface{}) *ast.FunctionLiteral {
|
|
function, err := ParseFunction(prm, bdy)
|
|
is(firstErr(err), expect)
|
|
return function
|
|
}
|
|
|
|
test("a, b,c,d", "", nil)
|
|
|
|
test("a, b;,c,d", "", "(anonymous): Line 1:15 Unexpected token ;")
|
|
|
|
test("this", "", "(anonymous): Line 1:11 Unexpected token this")
|
|
|
|
test("a, b, c, null", "", "(anonymous): Line 1:20 Unexpected token null")
|
|
|
|
test("a, b,c,d", "return;", nil)
|
|
|
|
test("a, b,c,d", "break;", "(anonymous): Line 2:1 Illegal break statement")
|
|
|
|
test("a, b,c,d", "{}", nil)
|
|
})
|
|
}
|
|
|
|
func TestParserErr(t *testing.T) {
|
|
tt(t, func() {
|
|
test := func(input string, expect interface{}) (*ast.Program, *_parser) {
|
|
parser := _newParser("", input, 1, nil)
|
|
program, err := parser.parse()
|
|
is(firstErr(err), expect)
|
|
return program, parser
|
|
}
|
|
|
|
program, parser := test("", nil)
|
|
|
|
program, parser = test(`
|
|
var abc;
|
|
break; do {
|
|
} while(true);
|
|
`, "(anonymous): Line 3:9 Illegal break statement")
|
|
{
|
|
stmt := program.Body[1].(*ast.BadStatement)
|
|
is(parser.position(stmt.From).Column, 9)
|
|
is(parser.position(stmt.To).Column, 16)
|
|
is(parser.slice(stmt.From, stmt.To), "break; ")
|
|
}
|
|
|
|
test("{", "(anonymous): Line 1:2 Unexpected end of input")
|
|
|
|
test("}", "(anonymous): Line 1:1 Unexpected token }")
|
|
|
|
test("3ea", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("3in", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("3in []", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("3e", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("3e+", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("3e-", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("3x", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("3x0", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("0x", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("09", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("018", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("01.0", "(anonymous): Line 1:3 Unexpected number")
|
|
|
|
test("01a", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("0x3in[]", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("\"Hello\nWorld\"", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("\u203f = 10", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("x\\", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("x\\\\", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("x\\u005c", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("x\\u002a", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("x\\\\u002a", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("/\n", "(anonymous): Line 1:1 Invalid regular expression: missing /")
|
|
|
|
test("var x = /(s/g", "(anonymous): Line 1:9 Invalid regular expression: Unterminated group")
|
|
|
|
test("0 = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
|
|
|
|
test("func() = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
|
|
|
|
test("(1 + 1) = 2", "(anonymous): Line 1:2 Invalid left-hand side in assignment")
|
|
|
|
test("1++", "(anonymous): Line 1:2 Invalid left-hand side in assignment")
|
|
|
|
test("1--", "(anonymous): Line 1:2 Invalid left-hand side in assignment")
|
|
|
|
test("--1", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
|
|
|
|
test("for((1 + 1) in abc) def();", "(anonymous): Line 1:1 Invalid left-hand side in for-in")
|
|
|
|
test("[", "(anonymous): Line 1:2 Unexpected end of input")
|
|
|
|
test("[,", "(anonymous): Line 1:3 Unexpected end of input")
|
|
|
|
test("1 + {", "(anonymous): Line 1:6 Unexpected end of input")
|
|
|
|
test("1 + { abc:abc", "(anonymous): Line 1:14 Unexpected end of input")
|
|
|
|
test("1 + { abc:abc,", "(anonymous): Line 1:15 Unexpected end of input")
|
|
|
|
test("var abc = /\n/", "(anonymous): Line 1:11 Invalid regular expression: missing /")
|
|
|
|
test("var abc = \"\n", "(anonymous): Line 1:11 Unexpected token ILLEGAL")
|
|
|
|
test("var if = 0", "(anonymous): Line 1:5 Unexpected token if")
|
|
|
|
test("abc + 0 = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
|
|
|
|
test("+abc = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
|
|
|
|
test("1 + (", "(anonymous): Line 1:6 Unexpected end of input")
|
|
|
|
test("\n\n\n{", "(anonymous): Line 4:2 Unexpected end of input")
|
|
|
|
test("\n/* Some multiline\ncomment */\n)", "(anonymous): Line 4:1 Unexpected token )")
|
|
|
|
// TODO
|
|
//{ set 1 }
|
|
//{ get 2 }
|
|
//({ set: s(if) { } })
|
|
//({ set s(.) { } })
|
|
//({ set: s() { } })
|
|
//({ set: s(a, b) { } })
|
|
//({ get: g(d) { } })
|
|
//({ get i() { }, i: 42 })
|
|
//({ i: 42, get i() { } })
|
|
//({ set i(x) { }, i: 42 })
|
|
//({ i: 42, set i(x) { } })
|
|
//({ get i() { }, get i() { } })
|
|
//({ set i(x) { }, set i(x) { } })
|
|
|
|
test("function abc(if) {}", "(anonymous): Line 1:14 Unexpected token if")
|
|
|
|
test("function abc(true) {}", "(anonymous): Line 1:14 Unexpected token true")
|
|
|
|
test("function abc(false) {}", "(anonymous): Line 1:14 Unexpected token false")
|
|
|
|
test("function abc(null) {}", "(anonymous): Line 1:14 Unexpected token null")
|
|
|
|
test("function null() {}", "(anonymous): Line 1:10 Unexpected token null")
|
|
|
|
test("function true() {}", "(anonymous): Line 1:10 Unexpected token true")
|
|
|
|
test("function false() {}", "(anonymous): Line 1:10 Unexpected token false")
|
|
|
|
test("function if() {}", "(anonymous): Line 1:10 Unexpected token if")
|
|
|
|
test("a b;", "(anonymous): Line 1:3 Unexpected identifier")
|
|
|
|
test("if.a", "(anonymous): Line 1:3 Unexpected token .")
|
|
|
|
test("a if", "(anonymous): Line 1:3 Unexpected token if")
|
|
|
|
test("a class", "(anonymous): Line 1:3 Unexpected reserved word")
|
|
|
|
test("break\n", "(anonymous): Line 1:1 Illegal break statement")
|
|
|
|
test("break 1;", "(anonymous): Line 1:7 Unexpected number")
|
|
|
|
test("for (;;) { break 1; }", "(anonymous): Line 1:18 Unexpected number")
|
|
|
|
test("continue\n", "(anonymous): Line 1:1 Illegal continue statement")
|
|
|
|
test("continue 1;", "(anonymous): Line 1:10 Unexpected number")
|
|
|
|
test("for (;;) { continue 1; }", "(anonymous): Line 1:21 Unexpected number")
|
|
|
|
test("throw", "(anonymous): Line 1:1 Unexpected end of input")
|
|
|
|
test("throw;", "(anonymous): Line 1:6 Unexpected token ;")
|
|
|
|
test("throw \n", "(anonymous): Line 1:1 Unexpected end of input")
|
|
|
|
test("for (var abc, def in {});", "(anonymous): Line 1:19 Unexpected token in")
|
|
|
|
test("for ((abc in {});;);", nil)
|
|
|
|
test("for ((abc in {}));", "(anonymous): Line 1:17 Unexpected token )")
|
|
|
|
test("for (+abc in {});", "(anonymous): Line 1:1 Invalid left-hand side in for-in")
|
|
|
|
test("if (false)", "(anonymous): Line 1:11 Unexpected end of input")
|
|
|
|
test("if (false) abc(); else", "(anonymous): Line 1:23 Unexpected end of input")
|
|
|
|
test("do", "(anonymous): Line 1:3 Unexpected end of input")
|
|
|
|
test("while (false)", "(anonymous): Line 1:14 Unexpected end of input")
|
|
|
|
test("for (;;)", "(anonymous): Line 1:9 Unexpected end of input")
|
|
|
|
test("with (abc)", "(anonymous): Line 1:11 Unexpected end of input")
|
|
|
|
test("try {}", "(anonymous): Line 1:1 Missing catch or finally after try")
|
|
|
|
test("try {} catch {}", "(anonymous): Line 1:14 Unexpected token {")
|
|
|
|
test("try {} catch () {}", "(anonymous): Line 1:15 Unexpected token )")
|
|
|
|
test("\u203f = 1", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
// TODO
|
|
// const x = 12, y;
|
|
// const x, y = 12;
|
|
// const x;
|
|
// if(true) let a = 1;
|
|
// if(true) const a = 1;
|
|
|
|
test(`new abc()."def"`, "(anonymous): Line 1:11 Unexpected string")
|
|
|
|
test("/*", "(anonymous): Line 1:3 Unexpected end of input")
|
|
|
|
test("/**", "(anonymous): Line 1:4 Unexpected end of input")
|
|
|
|
test("/*\n\n\n", "(anonymous): Line 4:1 Unexpected end of input")
|
|
|
|
test("/*\n\n\n*", "(anonymous): Line 4:2 Unexpected end of input")
|
|
|
|
test("/*abc", "(anonymous): Line 1:6 Unexpected end of input")
|
|
|
|
test("/*abc *", "(anonymous): Line 1:9 Unexpected end of input")
|
|
|
|
test("\n]", "(anonymous): Line 2:1 Unexpected token ]")
|
|
|
|
test("\r\n]", "(anonymous): Line 2:1 Unexpected token ]")
|
|
|
|
test("\n\r]", "(anonymous): Line 3:1 Unexpected token ]")
|
|
|
|
test("//\r\n]", "(anonymous): Line 2:1 Unexpected token ]")
|
|
|
|
test("//\n\r]", "(anonymous): Line 3:1 Unexpected token ]")
|
|
|
|
test("/abc\\\n/", "(anonymous): Line 1:1 Invalid regular expression: missing /")
|
|
|
|
test("//\r \n]", "(anonymous): Line 3:1 Unexpected token ]")
|
|
|
|
test("/*\r\n*/]", "(anonymous): Line 2:3 Unexpected token ]")
|
|
|
|
test("/*\r \n*/]", "(anonymous): Line 3:3 Unexpected token ]")
|
|
|
|
test("\\\\", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("\\u005c", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("\\abc", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("\\u0000", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("\\u200c = []", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("\\u200D = []", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test(`"\`, "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test(`"\u`, "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("return", "(anonymous): Line 1:1 Illegal return statement")
|
|
|
|
test("continue", "(anonymous): Line 1:1 Illegal continue statement")
|
|
|
|
test("break", "(anonymous): Line 1:1 Illegal break statement")
|
|
|
|
test("switch (abc) { default: continue; }", "(anonymous): Line 1:25 Illegal continue statement")
|
|
|
|
test("do { abc } *", "(anonymous): Line 1:12 Unexpected token *")
|
|
|
|
test("while (true) { break abc; }", "(anonymous): Line 1:16 Undefined label 'abc'")
|
|
|
|
test("while (true) { continue abc; }", "(anonymous): Line 1:16 Undefined label 'abc'")
|
|
|
|
test("abc: while (true) { (function(){ break abc; }); }", "(anonymous): Line 1:34 Undefined label 'abc'")
|
|
|
|
test("abc: while (true) { (function(){ abc: break abc; }); }", nil)
|
|
|
|
test("abc: while (true) { (function(){ continue abc; }); }", "(anonymous): Line 1:34 Undefined label 'abc'")
|
|
|
|
test(`abc: if (0) break abc; else {}`, nil)
|
|
|
|
test(`abc: if (0) { break abc; } else {}`, nil)
|
|
|
|
test(`abc: if (0) { break abc } else {}`, nil)
|
|
|
|
test("abc: while (true) { abc: while (true) {} }", "(anonymous): Line 1:21 Label 'abc' already exists")
|
|
|
|
if false {
|
|
// TODO When strict mode is implemented
|
|
test("(function () { 'use strict'; delete abc; }())", "")
|
|
}
|
|
|
|
test("_: _: while (true) {]", "(anonymous): Line 1:4 Label '_' already exists")
|
|
|
|
test("_:\n_:\nwhile (true) {]", "(anonymous): Line 2:1 Label '_' already exists")
|
|
|
|
test("_:\n _:\nwhile (true) {]", "(anonymous): Line 2:4 Label '_' already exists")
|
|
|
|
test("/Xyzzy(?!Nothing happens)/",
|
|
"(anonymous): Line 1:1 Invalid regular expression: re2: Invalid (?!) <lookahead>")
|
|
|
|
test("function(){}", "(anonymous): Line 1:9 Unexpected token (")
|
|
|
|
test("\n/*/", "(anonymous): Line 2:4 Unexpected end of input")
|
|
|
|
test("/*/.source", "(anonymous): Line 1:11 Unexpected end of input")
|
|
|
|
test("/\\1/.source", "(anonymous): Line 1:1 Invalid regular expression: re2: Invalid \\1 <backreference>")
|
|
|
|
test("var class", "(anonymous): Line 1:5 Unexpected reserved word")
|
|
|
|
test("var if", "(anonymous): Line 1:5 Unexpected token if")
|
|
|
|
test("object Object", "(anonymous): Line 1:8 Unexpected identifier")
|
|
|
|
test("[object Object]", "(anonymous): Line 1:9 Unexpected identifier")
|
|
|
|
test("\\u0xyz", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test(`for (var abc, def in {}) {}`, "(anonymous): Line 1:19 Unexpected token in")
|
|
|
|
test(`for (abc, def in {}) {}`, "(anonymous): Line 1:1 Invalid left-hand side in for-in")
|
|
|
|
test(`for (var abc=def, ghi=("abc" in {}); true;) {}`, nil)
|
|
|
|
{
|
|
// Semicolon insertion
|
|
|
|
test("this\nif (1);", nil)
|
|
|
|
test("while (1) { break\nif (1); }", nil)
|
|
|
|
test("throw\nif (1);", "(anonymous): Line 1:1 Illegal newline after throw")
|
|
|
|
test("(function(){ return\nif (1); })", nil)
|
|
|
|
test("while (1) { continue\nif (1); }", nil)
|
|
|
|
test("debugger\nif (1);", nil)
|
|
}
|
|
|
|
{ // Reserved words
|
|
|
|
test("class", "(anonymous): Line 1:1 Unexpected reserved word")
|
|
test("abc.class = 1", nil)
|
|
test("var class;", "(anonymous): Line 1:5 Unexpected reserved word")
|
|
|
|
test("const", "(anonymous): Line 1:1 Unexpected reserved word")
|
|
test("abc.const = 1", nil)
|
|
test("var const;", "(anonymous): Line 1:5 Unexpected reserved word")
|
|
|
|
test("enum", "(anonymous): Line 1:1 Unexpected reserved word")
|
|
test("abc.enum = 1", nil)
|
|
test("var enum;", "(anonymous): Line 1:5 Unexpected reserved word")
|
|
|
|
test("export", "(anonymous): Line 1:1 Unexpected reserved word")
|
|
test("abc.export = 1", nil)
|
|
test("var export;", "(anonymous): Line 1:5 Unexpected reserved word")
|
|
|
|
test("extends", "(anonymous): Line 1:1 Unexpected reserved word")
|
|
test("abc.extends = 1", nil)
|
|
test("var extends;", "(anonymous): Line 1:5 Unexpected reserved word")
|
|
|
|
test("import", "(anonymous): Line 1:1 Unexpected reserved word")
|
|
test("abc.import = 1", nil)
|
|
test("var import;", "(anonymous): Line 1:5 Unexpected reserved word")
|
|
|
|
test("super", "(anonymous): Line 1:1 Unexpected reserved word")
|
|
test("abc.super = 1", nil)
|
|
test("var super;", "(anonymous): Line 1:5 Unexpected reserved word")
|
|
}
|
|
|
|
{ // Reserved words (strict)
|
|
|
|
test(`implements`, nil)
|
|
test(`abc.implements = 1`, nil)
|
|
test(`var implements;`, nil)
|
|
|
|
test(`interface`, nil)
|
|
test(`abc.interface = 1`, nil)
|
|
test(`var interface;`, nil)
|
|
|
|
test(`let`, nil)
|
|
test(`abc.let = 1`, nil)
|
|
test(`var let;`, nil)
|
|
|
|
test(`package`, nil)
|
|
test(`abc.package = 1`, nil)
|
|
test(`var package;`, nil)
|
|
|
|
test(`private`, nil)
|
|
test(`abc.private = 1`, nil)
|
|
test(`var private;`, nil)
|
|
|
|
test(`protected`, nil)
|
|
test(`abc.protected = 1`, nil)
|
|
test(`var protected;`, nil)
|
|
|
|
test(`public`, nil)
|
|
test(`abc.public = 1`, nil)
|
|
test(`var public;`, nil)
|
|
|
|
test(`static`, nil)
|
|
test(`abc.static = 1`, nil)
|
|
test(`var static;`, nil)
|
|
|
|
test(`yield`, nil)
|
|
test(`abc.yield = 1`, nil)
|
|
test(`var yield;`, nil)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParser(t *testing.T) {
|
|
tt(t, func() {
|
|
test := func(source string, chk interface{}) *ast.Program {
|
|
_, program, err := testParse(source)
|
|
is(firstErr(err), chk)
|
|
return program
|
|
}
|
|
|
|
test(`
|
|
abc
|
|
--
|
|
[]
|
|
`, "(anonymous): Line 3:13 Invalid left-hand side in assignment")
|
|
|
|
test(`
|
|
abc--
|
|
[]
|
|
`, nil)
|
|
|
|
test("1\n[]\n", "(anonymous): Line 2:2 Unexpected token ]")
|
|
|
|
test(`
|
|
function abc() {
|
|
}
|
|
abc()
|
|
`, nil)
|
|
|
|
program := test("", nil)
|
|
|
|
test("//", nil)
|
|
|
|
test("/* */", nil)
|
|
|
|
test("/** **/", nil)
|
|
|
|
test("/*****/", nil)
|
|
|
|
test("/*", "(anonymous): Line 1:3 Unexpected end of input")
|
|
|
|
test("#", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("/**/#", "(anonymous): Line 1:5 Unexpected token ILLEGAL")
|
|
|
|
test("new +", "(anonymous): Line 1:5 Unexpected token +")
|
|
|
|
program = test(";", nil)
|
|
is(len(program.Body), 1)
|
|
is(program.Body[0].(*ast.EmptyStatement).Semicolon, file.Idx(1))
|
|
|
|
program = test(";;", nil)
|
|
is(len(program.Body), 2)
|
|
is(program.Body[0].(*ast.EmptyStatement).Semicolon, file.Idx(1))
|
|
is(program.Body[1].(*ast.EmptyStatement).Semicolon, file.Idx(2))
|
|
|
|
program = test("1.2", nil)
|
|
is(len(program.Body), 1)
|
|
is(program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.NumberLiteral).Literal, "1.2")
|
|
|
|
program = test("/* */1.2", nil)
|
|
is(len(program.Body), 1)
|
|
is(program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.NumberLiteral).Literal, "1.2")
|
|
|
|
program = test("\n", nil)
|
|
is(len(program.Body), 0)
|
|
|
|
test(`
|
|
if (0) {
|
|
abc = 0
|
|
}
|
|
else abc = 0
|
|
`, nil)
|
|
|
|
test("if (0) abc = 0 else abc = 0", "(anonymous): Line 1:16 Unexpected token else")
|
|
|
|
test(`
|
|
if (0) {
|
|
abc = 0
|
|
} else abc = 0
|
|
`, nil)
|
|
|
|
test(`
|
|
if (0) {
|
|
abc = 1
|
|
} else {
|
|
}
|
|
`, nil)
|
|
|
|
test(`
|
|
do {
|
|
} while (true)
|
|
`, nil)
|
|
|
|
test(`
|
|
try {
|
|
} finally {
|
|
}
|
|
`, nil)
|
|
|
|
test(`
|
|
try {
|
|
} catch (abc) {
|
|
} finally {
|
|
}
|
|
`, nil)
|
|
|
|
test(`
|
|
try {
|
|
}
|
|
catch (abc) {
|
|
}
|
|
finally {
|
|
}
|
|
`, nil)
|
|
|
|
test(`try {} catch (abc) {} finally {}`, nil)
|
|
|
|
test(`
|
|
do {
|
|
do {
|
|
} while (0)
|
|
} while (0)
|
|
`, nil)
|
|
|
|
test(`
|
|
(function(){
|
|
try {
|
|
if (
|
|
1
|
|
) {
|
|
return 1
|
|
}
|
|
return 0
|
|
} finally {
|
|
}
|
|
})()
|
|
`, nil)
|
|
|
|
test("abc = ''\ndef", nil)
|
|
|
|
test("abc = 1\ndef", nil)
|
|
|
|
test("abc = Math\ndef", nil)
|
|
|
|
test(`"\'"`, nil)
|
|
|
|
test(`
|
|
abc = function(){
|
|
}
|
|
abc = 0
|
|
`, nil)
|
|
|
|
test("abc.null = 0", nil)
|
|
|
|
test("0x41", nil)
|
|
|
|
test(`"\d"`, nil)
|
|
|
|
test(`(function(){return this})`, nil)
|
|
|
|
test(`
|
|
Object.defineProperty(Array.prototype, "0", {
|
|
value: 100,
|
|
writable: false,
|
|
configurable: true
|
|
});
|
|
abc = [101];
|
|
abc.hasOwnProperty("0") && abc[0] === 101;
|
|
`, nil)
|
|
|
|
test(`new abc()`, nil)
|
|
test(`new {}`, nil)
|
|
|
|
test(`
|
|
limit = 4
|
|
result = 0
|
|
while (limit) {
|
|
limit = limit - 1
|
|
if (limit) {
|
|
}
|
|
else {
|
|
break
|
|
}
|
|
result = result + 1
|
|
}
|
|
`, nil)
|
|
|
|
test(`
|
|
while (0) {
|
|
if (0) {
|
|
continue
|
|
}
|
|
}
|
|
`, nil)
|
|
|
|
test("var \u0061\u0062\u0063 = 0", nil)
|
|
|
|
// 7_3_1
|
|
test("var test7_3_1\nabc = 66;", nil)
|
|
test("var test7_3_1\u2028abc = 66;", nil)
|
|
|
|
// 7_3_3
|
|
test("//\u2028 =;", "(anonymous): Line 2:2 Unexpected token =")
|
|
|
|
// 7_3_10
|
|
test("var abc = \u2029;", "(anonymous): Line 2:1 Unexpected token ;")
|
|
test("var abc = \\u2029;", "(anonymous): Line 1:11 Unexpected token ILLEGAL")
|
|
test("var \\u0061\\u0062\\u0063 = 0;", nil)
|
|
|
|
test("'", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
test("'\nstr\ning\n'", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
|
|
|
|
// S7.6_A4.3_T1
|
|
test(`var $\u0030 = 0;`, nil)
|
|
|
|
// S7.6.1.1_A1.1
|
|
test(`switch = 1`, "(anonymous): Line 1:8 Unexpected token =")
|
|
|
|
// S7.8.3_A2.1_T1
|
|
test(`.0 === 0.0`, nil)
|
|
|
|
// 7.8.5-1
|
|
test("var regExp = /\\\rn/;", "(anonymous): Line 1:14 Invalid regular expression: missing /")
|
|
|
|
// S7.8.5_A1.1_T2
|
|
test("var regExp = /=/;", nil)
|
|
|
|
// S7.8.5_A1.2_T1
|
|
test("/*/", "(anonymous): Line 1:4 Unexpected end of input")
|
|
|
|
// Sbp_7.9_A9_T3
|
|
test(`
|
|
do {
|
|
;
|
|
} while (false) true
|
|
`, nil)
|
|
|
|
// S7.9_A10_T10
|
|
test(`
|
|
{a:1
|
|
} 3
|
|
`, nil)
|
|
|
|
test(`
|
|
abc
|
|
++def
|
|
`, nil)
|
|
|
|
// S7.9_A5.2_T1
|
|
test(`
|
|
for(false;false
|
|
) {
|
|
break;
|
|
}
|
|
`, "(anonymous): Line 3:13 Unexpected token )")
|
|
|
|
// S7.9_A9_T8
|
|
test(`
|
|
do {};
|
|
while (false)
|
|
`, "(anonymous): Line 2:18 Unexpected token ;")
|
|
|
|
// S8.4_A5
|
|
test(`
|
|
"x\0y"
|
|
`, nil)
|
|
|
|
// S9.3.1_A6_T1
|
|
test(`
|
|
10e10000
|
|
`, nil)
|
|
|
|
// 10.4.2-1-5
|
|
test(`
|
|
"abc\
|
|
def"
|
|
`, nil)
|
|
|
|
test("'\\\n'", nil)
|
|
|
|
test("'\\\r\n'", nil)
|
|
|
|
//// 11.13.1-1-1
|
|
test("42 = 42;", "(anonymous): Line 1:1 Invalid left-hand side in assignment")
|
|
|
|
// S11.13.2_A4.2_T1.3
|
|
test(`
|
|
abc /= "1"
|
|
`, nil)
|
|
|
|
// 12.1-1
|
|
test(`
|
|
try{};catch(){}
|
|
`, "(anonymous): Line 2:13 Missing catch or finally after try")
|
|
|
|
// 12.1-3
|
|
test(`
|
|
try{};finally{}
|
|
`, "(anonymous): Line 2:13 Missing catch or finally after try")
|
|
|
|
// S12.6.3_A11.1_T3
|
|
test(`
|
|
while (true) {
|
|
break abc;
|
|
}
|
|
`, "(anonymous): Line 3:17 Undefined label 'abc'")
|
|
|
|
// S15.3_A2_T1
|
|
test(`var x / = 1;`, "(anonymous): Line 1:7 Unexpected token /")
|
|
|
|
test(`
|
|
function abc() {
|
|
if (0)
|
|
return;
|
|
else {
|
|
}
|
|
}
|
|
`, nil)
|
|
|
|
test("//\u2028 var =;", "(anonymous): Line 2:6 Unexpected token =")
|
|
|
|
test(`
|
|
throw
|
|
{}
|
|
`, "(anonymous): Line 2:13 Illegal newline after throw")
|
|
|
|
// S7.6.1.1_A1.11
|
|
test(`
|
|
function = 1
|
|
`, "(anonymous): Line 2:22 Unexpected token =")
|
|
|
|
// S7.8.3_A1.2_T1
|
|
test(`0e1`, nil)
|
|
|
|
test("abc = 1; abc\n++", "(anonymous): Line 2:3 Unexpected end of input")
|
|
|
|
// ---
|
|
|
|
test("({ get abc() {} })", nil)
|
|
|
|
test(`for (abc.def in {}) {}`, nil)
|
|
|
|
test(`while (true) { break }`, nil)
|
|
|
|
test(`while (true) { continue }`, nil)
|
|
|
|
test(`abc=/^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?)|(.{0,2}\/{1}))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/,def=/^(?:(\w+:)\/{2})|(.{0,2}\/{1})?([/.]*?(?:[^?]+)?\/?)?$/`, nil)
|
|
|
|
test(`(function() { try {} catch (err) {} finally {} return })`, nil)
|
|
|
|
test(`0xde0b6b3a7640080.toFixed(0)`, nil)
|
|
|
|
test(`/[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/`, nil)
|
|
|
|
test(`/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]/`, nil)
|
|
|
|
test("var abc = 1;\ufeff", nil)
|
|
|
|
test("\ufeff/* var abc = 1; */", nil)
|
|
|
|
test(`if (-0x8000000000000000<=abc&&abc<=0x8000000000000000) {}`, nil)
|
|
|
|
test(`(function(){debugger;return this;})`, nil)
|
|
|
|
test(`
|
|
|
|
`, nil)
|
|
|
|
test(`
|
|
var abc = ""
|
|
debugger
|
|
`, nil)
|
|
|
|
test(`
|
|
var abc = /\[\]$/
|
|
debugger
|
|
`, nil)
|
|
|
|
test(`
|
|
var abc = 1 /
|
|
2
|
|
debugger
|
|
`, nil)
|
|
})
|
|
}
|
|
|
|
func Test_parseStringLiteral(t *testing.T) {
|
|
tt(t, func() {
|
|
test := func(have, want string) {
|
|
have, err := parseStringLiteral(have)
|
|
is(err, nil)
|
|
is(have, want)
|
|
}
|
|
|
|
test("", "")
|
|
|
|
test("1(\\\\d+)", "1(\\d+)")
|
|
|
|
test("\\u2029", "\u2029")
|
|
|
|
test("abc\\uFFFFabc", "abc\uFFFFabc")
|
|
|
|
test("[First line \\\nSecond line \\\n Third line\\\n. ]",
|
|
"[First line Second line Third line. ]")
|
|
|
|
test("\\u007a\\x79\\u000a\\x78", "zy\nx")
|
|
|
|
// S7.8.4_A4.2_T3
|
|
test("\\a", "a")
|
|
test("\u0410", "\u0410")
|
|
|
|
// S7.8.4_A5.1_T1
|
|
test("\\0", "\u0000")
|
|
|
|
// S8.4_A5
|
|
test("\u0000", "\u0000")
|
|
|
|
// 15.5.4.20
|
|
test("'abc'\\\n'def'", "'abc''def'")
|
|
|
|
// 15.5.4.20-4-1
|
|
test("'abc'\\\r\n'def'", "'abc''def'")
|
|
|
|
// Octal
|
|
test("\\0", "\000")
|
|
test("\\00", "\000")
|
|
test("\\000", "\000")
|
|
test("\\09", "\0009")
|
|
test("\\009", "\0009")
|
|
test("\\0009", "\0009")
|
|
test("\\1", "\001")
|
|
test("\\01", "\001")
|
|
test("\\001", "\001")
|
|
test("\\0011", "\0011")
|
|
test("\\1abc", "\001abc")
|
|
|
|
test("\\\u4e16", "\u4e16")
|
|
|
|
// err
|
|
test = func(have, want string) {
|
|
have, err := parseStringLiteral(have)
|
|
is(err.Error(), want)
|
|
is(have, "")
|
|
}
|
|
|
|
test(`\u`, `invalid escape: \u: len("") != 4`)
|
|
test(`\u0`, `invalid escape: \u: len("0") != 4`)
|
|
test(`\u00`, `invalid escape: \u: len("00") != 4`)
|
|
test(`\u000`, `invalid escape: \u: len("000") != 4`)
|
|
|
|
test(`\x`, `invalid escape: \x: len("") != 2`)
|
|
test(`\x0`, `invalid escape: \x: len("0") != 2`)
|
|
test(`\x0`, `invalid escape: \x: len("0") != 2`)
|
|
})
|
|
}
|
|
|
|
func Test_parseNumberLiteral(t *testing.T) {
|
|
tt(t, func() {
|
|
test := func(input string, expect interface{}) {
|
|
result, err := parseNumberLiteral(input)
|
|
is(err, nil)
|
|
is(result, expect)
|
|
}
|
|
|
|
test("0", 0)
|
|
|
|
test("0x8000000000000000", float64(9.223372036854776e+18))
|
|
})
|
|
}
|
|
|
|
func TestPosition(t *testing.T) {
|
|
tt(t, func() {
|
|
parser := _newParser("", "// Lorem ipsum", 1, nil)
|
|
|
|
// Out of range, idx0 (error condition)
|
|
is(parser.slice(0, 1), "")
|
|
is(parser.slice(0, 10), "")
|
|
|
|
// Out of range, idx1 (error condition)
|
|
is(parser.slice(1, 128), "")
|
|
|
|
is(parser.str[0:0], "")
|
|
is(parser.slice(1, 1), "")
|
|
|
|
is(parser.str[0:1], "/")
|
|
is(parser.slice(1, 2), "/")
|
|
|
|
is(parser.str[0:14], "// Lorem ipsum")
|
|
is(parser.slice(1, 15), "// Lorem ipsum")
|
|
|
|
parser = _newParser("", "(function(){ return 0; })", 1, nil)
|
|
program, err := parser.parse()
|
|
is(err, nil)
|
|
|
|
var node ast.Node
|
|
node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral)
|
|
is(node.Idx0(), file.Idx(2))
|
|
is(node.Idx1(), file.Idx(25))
|
|
is(parser.slice(node.Idx0(), node.Idx1()), "function(){ return 0; }")
|
|
is(parser.slice(node.Idx0(), node.Idx1()+1), "function(){ return 0; })")
|
|
is(parser.slice(node.Idx0(), node.Idx1()+2), "")
|
|
is(node.(*ast.FunctionLiteral).Source, "function(){ return 0; }")
|
|
|
|
node = program
|
|
is(node.Idx0(), file.Idx(2))
|
|
is(node.Idx1(), file.Idx(25))
|
|
is(parser.slice(node.Idx0(), node.Idx1()), "function(){ return 0; }")
|
|
|
|
parser = _newParser("", "(function(){ return abc; })", 1, nil)
|
|
program, err = parser.parse()
|
|
is(err, nil)
|
|
node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral)
|
|
is(node.(*ast.FunctionLiteral).Source, "function(){ return abc; }")
|
|
})
|
|
}
|
|
|
|
func BenchmarkParser(b *testing.B) {
|
|
src := underscore.Source()
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
parser := _newParser("", src, 1, nil)
|
|
parser.parse()
|
|
}
|
|
}
|