2024-09-20 16:50:35 +08:00
package parser
import (
"errors"
"math/big"
"regexp"
"strings"
"testing"
2024-09-24 13:34:32 +08:00
"apigo.cc/ai/ai/goja/ast"
"apigo.cc/ai/ai/goja/file"
"apigo.cc/ai/ai/goja/token"
"apigo.cc/ai/ai/goja/unistring"
2024-09-20 16:50:35 +08:00
)
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 ) {
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 )
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/; return ` , IgnoreRegExpErrors )
is ( err , "(anonymous): Line 1:15 Illegal return statement" )
} )
}
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 )
program , err := parser . parse ( )
is ( firstErr ( err ) , expect )
return 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; " )
}
s := string ( [ ] byte { 0x22 , 0x25 , 0x21 , 0x63 , 0x28 , 0x73 , 0x74 , 0x72 , 0x69 , 0x6e , 0x67 , 0x3d , 0x25 , 0x63 , 0x25 , 0x9c , 0x29 , 0x25 , 0x21 , 0x5c , 0x28 , 0x73 , 0x74 , 0x72 , 0x69 , 0x6e , 0x67 , 0x3d , 0x5c , 0xe2 , 0x80 , 0xa9 , 0x29 , 0x78 , 0x39 , 0x63 , 0x22 } )
test ( s , ` (anonymous): Line 1:16 Invalid UTF-8 character ` )
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 ( ".0.9" , "(anonymous): Line 1:3 Unexpected number" )
test ( "0o3e1" , "(anonymous): Line 1:1 Unexpected token ILLEGAL" )
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 ( "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 or for-of" )
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 )" )
test ( "+1 ** 2" , "(anonymous): Line 1:4 Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence" )
test ( "typeof 1 ** 2" , "(anonymous): Line 1:10 Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence" )
// 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 token class" )
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 or for-of" )
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: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" )
test ( ` if(0) { do { } while(0) } else { do { } while(0) } ` , nil )
test ( ` if(0) do { } while(0); else do { } while(0) ` , nil )
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 ( "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 ( "var class" , "(anonymous): Line 1:5 Unexpected token class" )
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 or for-of" )
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:6 Unexpected end of input" )
test ( "abc.class = 1" , nil )
test ( "var class;" , "(anonymous): Line 1:5 Unexpected token class" )
test ( "const" , "(anonymous): Line 1:6 Unexpected end of input" )
test ( "abc.const = 1" , nil )
test ( "var const;" , "(anonymous): Line 1:5 Unexpected token const" )
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 token extends" )
test ( "abc.extends = 1" , nil )
test ( "var extends;" , "(anonymous): Line 1:5 Unexpected token extends" )
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 'super' keyword unexpected here" )
test ( "abc.super = 1" , nil )
test ( "var super;" , "(anonymous): Line 1:5 Unexpected token super" )
test ( `
obj = {
aaa : 1
bbb : "string"
} ; ` , "(anonymous): Line 4:6 Unexpected identifier" )
test ( "{}" , nil )
test ( "{a: 1}" , nil )
test ( "{a: 1,}" , "(anonymous): Line 1:7 Unexpected token }" )
test ( "{a: 1, b: 2}" , "(anonymous): Line 1:9 Unexpected token :" )
test ( "{a: 1, b: 2,}" , "(anonymous): Line 1:9 Unexpected token :" )
test ( ` let f = () => new import(''); ` , "(anonymous): Line 1:19 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 )
}
test ( ` 0, { get a(param = null) { } }; ` , "(anonymous): Line 1:11 Getter must not have any formal parameters." )
test ( ` let { f( ` , "(anonymous): Line 1:7 Unexpected end of input" )
test ( "`" , "(anonymous): Line 1:2 Unexpected end of input" )
test ( " `" , "(anonymous): Line 1:3 Unexpected end of input" )
test ( "` " , "(anonymous): Line 1:3 Unexpected end of input" )
test ( ` var { ..( ` , "(anonymous): Line 1:7 Unexpected token ILLEGAL" )
test ( ` var { get..( ` , "(anonymous): Line 1:10 Unexpected token ILLEGAL" )
test ( ` var { set..( ` , "(anonymous): Line 1:10 Unexpected token ILLEGAL" )
test ( ` (0 ?? 0 || true) ` , "(anonymous): Line 1:9 Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses" )
test ( ` (a || b ?? c) ` , "(anonymous): Line 1:9 Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses" )
test ( ` 2 ?? 2 && 3 + 3 ` , "(anonymous): Line 1:3 Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses" )
test ( `
class C {
st \ u0061tic m ( ) { }
}
` , "(anonymous): Line 3:25 Unexpected identifier" )
} )
}
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 ( ` new (() => { }); ` , nil )
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 )
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 ( "try {} catch {}" , 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" )
test ( "s &^= 42;" , "(anonymous): Line 1:4 Unexpected token ^=" )
// 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 )
test ( "'ё\\\u2029'" , nil )
test ( ` [a, b] = [1, 2] ` , nil )
test ( ` ( { "a b": { }} = { }) ` , nil )
test ( ` ref = ( a , b = 39 , ) = > {
} ; ` , nil )
test ( ` (a,) => { } ` , nil )
test ( ` 2 ?? (2 && 3) + 3 ` , nil )
test ( ` (2 ?? 2) && 3 + 3 ` , nil )
program = test ( ` a ?? b ?? c ` , nil )
is ( len ( program . Body ) , 1 )
is ( program . Body [ 0 ] . ( * ast . ExpressionStatement ) . Expression . ( * ast . BinaryExpression ) . Right . ( * ast . Identifier ) . Name , "c" )
program = test ( `
class C {
a
b
# c
m ( ) {
return this . # c ;
}
}
` , nil )
is ( len ( program . Body ) , 1 )
{
program := test ( ` (-2)**53 ` , nil )
st := program . Body [ 0 ] . ( * ast . ExpressionStatement ) . Expression . ( * ast . BinaryExpression )
is ( st . Operator , token . EXPONENT )
left := st . Left . ( * ast . UnaryExpression )
is ( left . Operator , token . MINUS )
op1 := left . Operand . ( * ast . NumberLiteral )
is ( op1 . Literal , "2" )
right := st . Right . ( * ast . NumberLiteral )
is ( right . Literal , "53" )
}
} )
}
func TestParseDestruct ( t * testing . T ) {
parser := newParser ( "" , ` ( { a: (a.b), ...spread,} = { }) ` )
prg , err := parser . parse ( )
if err != nil {
t . Fatal ( err )
}
_ = prg
}
func Test_parseStringLiteral ( t * testing . T ) {
tt ( t , func ( ) {
test := func ( have string , want unistring . String ) {
parser := newParser ( "" , have )
parser . read ( )
parser . read ( )
_ , res , err := parser . scanString ( 0 , true )
is ( err , "" )
is ( res , want )
}
test ( ` "" ` , "" )
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 string , want unistring . String ) {
parser := newParser ( "" , have )
parser . read ( )
parser . read ( )
_ , res , err := parser . scanString ( 0 , true )
is ( err , want )
is ( res , "" )
}
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 ` )
} )
}
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 ) )
test ( "1n" , big . NewInt ( 1 ) )
test ( "-1n" , big . NewInt ( - 1 ) )
test ( "0x23n" , big . NewInt ( 35 ) )
test ( "0xabcdef01n" , big . NewInt ( 2882400001 ) )
var n big . Int
n . SetString ( "0xabcdef0123456789abcdef0123" , 0 )
test ( "0xabcdef0123456789abcdef0123n" , & n )
} )
}
func TestPosition ( t * testing . T ) {
tt ( t , func ( ) {
parser := newParser ( "" , "// Lorem ipsum" )
// 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; })" )
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; })" )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . ExpressionStatement ) . Expression . ( * ast . FunctionLiteral )
is ( node . ( * ast . FunctionLiteral ) . Source , "function(){ return abc; }" )
node = program . Body [ 0 ] . ( * ast . ExpressionStatement ) . Expression . ( * ast . FunctionLiteral ) . Body . List [ 0 ] . ( * ast . ReturnStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "return abc" )
parser = newParser ( "" , "(function(){ return; })" )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . ExpressionStatement ) . Expression . ( * ast . FunctionLiteral ) . Body . List [ 0 ] . ( * ast . ReturnStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "return" )
parser = newParser ( "" , "true ? 1 : 2" )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . ExpressionStatement ) . Expression . ( * ast . ConditionalExpression )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "true ? 1 : 2" )
parser = newParser ( "" , "a++" )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . ExpressionStatement ) . Expression . ( * ast . UnaryExpression )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "a++" )
parser = newParser ( "" , "++a" )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . ExpressionStatement ) . Expression . ( * ast . UnaryExpression )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "++a" )
parser = newParser ( "" , "xyz: for (i = 0; i < 10; i++) { if (i == 5) continue xyz; }" )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . LabelledStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "xyz: for (i = 0; i < 10; i++) { if (i == 5) continue xyz; }" )
node = program . Body [ 0 ] . ( * ast . LabelledStatement ) . Statement . ( * ast . ForStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "for (i = 0; i < 10; i++) { if (i == 5) continue xyz; }" )
block := program . Body [ 0 ] . ( * ast . LabelledStatement ) . Statement . ( * ast . ForStatement ) . Body . ( * ast . BlockStatement )
node = block . List [ 0 ] . ( * ast . IfStatement ) . Consequent . ( * ast . BranchStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "continue xyz" )
parser = newParser ( "" , "for (p in o) { break; }" )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . ForInStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "for (p in o) { break; }" )
node = program . Body [ 0 ] . ( * ast . ForInStatement ) . Body . ( * ast . BlockStatement ) . List [ 0 ] . ( * ast . BranchStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "break" )
parser = newParser ( "" , "while (i < 10) { i++; }" )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . WhileStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "while (i < 10) { i++; }" )
parser = newParser ( "" , "do { i++; } while (i < 10 )" )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . DoWhileStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "do { i++; } while (i < 10 )" )
parser = newParser ( "" , "with (1) {}" )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . WithStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , "with (1) {}" )
parser = newParser ( "" , ` switch ( a ) {
case 1 : x -- ;
case 2 :
default : x ++ ;
} ` )
program , err = parser . parse ( )
is ( err , nil )
node = program . Body [ 0 ] . ( * ast . SwitchStatement )
is ( parser . slice ( node . Idx0 ( ) , node . Idx1 ( ) ) , ` switch ( a ) {
case 1 : x -- ;
case 2 :
default : x ++ ;
} ` )
} )
}
func TestExtractSourceMapLine ( t * testing . T ) {
tt ( t , func ( ) {
is ( extractSourceMapLine ( "" ) , "" )
is ( extractSourceMapLine ( "\n" ) , "" )
is ( extractSourceMapLine ( " " ) , "" )
is ( extractSourceMapLine ( "1\n2\n3\n4\n" ) , "" )
src := ` "use strict" ;
var x = { } ;
//# sourceMappingURL=delme.js.map`
modSrc := ` (function(exports, require, module) { ` + src + `
} ) `
is ( extractSourceMapLine ( modSrc ) , "//# sourceMappingURL=delme.js.map" )
is ( extractSourceMapLine ( modSrc + "\n\n\n\n" ) , "//# sourceMappingURL=delme.js.map" )
} )
}
func TestSourceMapOptions ( t * testing . T ) {
tt ( t , func ( ) {
count := 0
requestedPath := ""
loader := func ( p string ) ( [ ] byte , error ) {
count ++
requestedPath = p
return nil , nil
}
src := ` "use strict" ;
var x = { } ;
//# sourceMappingURL=delme.js.map`
_ , err := ParseFile ( nil , "delme.js" , src , 0 , WithSourceMapLoader ( loader ) )
is ( err , nil )
is ( count , 1 )
is ( requestedPath , "delme.js.map" )
count = 0
_ , err = ParseFile ( nil , "" , src , 0 , WithSourceMapLoader ( loader ) )
is ( err , nil )
is ( count , 1 )
is ( requestedPath , "delme.js.map" )
count = 0
_ , err = ParseFile ( nil , "delme.js" , src , 0 , WithDisableSourceMaps )
is ( err , nil )
is ( count , 0 )
_ , err = ParseFile ( nil , "/home/user/src/delme.js" , src , 0 , WithSourceMapLoader ( loader ) )
is ( err , nil )
is ( count , 1 )
is ( requestedPath , "/home/user/src/delme.js.map" )
count = 0
_ , err = ParseFile ( nil , "https://site.com/delme.js" , src , 0 , WithSourceMapLoader ( loader ) )
is ( err , nil )
is ( count , 1 )
is ( requestedPath , "https://site.com/delme.js.map" )
} )
}
func TestParseTemplateCharacters ( t * testing . T ) {
parser := newParser ( "" , "`test\\\r\\\n${a}`" )
parser . next ( )
if parser . token != token . BACKTICK {
t . Fatalf ( "Token: %s" , parser . token )
}
checkParseTemplateChars := func ( expectedLiteral string , expectedParsed unistring . String , expectedFinished , expectParseErr , expectErr bool ) {
literal , parsed , finished , parseErr , err := parser . parseTemplateCharacters ( )
if err != "" != expectErr {
t . Fatal ( err )
}
if literal != expectedLiteral {
t . Fatalf ( "Literal: %q" , literal )
}
if parsed != expectedParsed {
t . Fatalf ( "Parsed: %q" , parsed )
}
if finished != expectedFinished {
t . Fatal ( finished )
}
if parseErr != "" != expectParseErr {
t . Fatalf ( "parseErr: %v" , parseErr )
}
}
checkParseTemplateChars ( "test\\\n\\\n" , "test" , false , false , false )
parser . next ( )
parser . expect ( token . IDENTIFIER )
if len ( parser . errors ) > 0 {
t . Fatal ( parser . errors )
}
if parser . token != token . RIGHT_BRACE {
t . Fatal ( "Expected }" )
}
if len ( parser . errors ) > 0 {
t . Fatal ( parser . errors )
}
checkParseTemplateChars ( "" , "" , true , false , false )
if parser . chr != - 1 {
t . Fatal ( "Expected EOF" )
}
}
func TestParseTemplateLiteral ( t * testing . T ) {
parser := newParser ( "" , "f()\n`test${a}`" )
prg , err := parser . parse ( )
if err != nil {
t . Fatal ( err )
}
if st , ok := prg . Body [ 0 ] . ( * ast . ExpressionStatement ) ; ok {
if expr , ok := st . Expression . ( * ast . TemplateLiteral ) ; ok {
if expr . Tag == nil {
t . Fatal ( "tag is nil" )
}
if idx0 := expr . Tag . Idx0 ( ) ; idx0 != 1 {
t . Fatalf ( "Tag.Idx0(): %d" , idx0 )
}
if expr . OpenQuote != 5 {
t . Fatalf ( "OpenQuote: %d" , expr . OpenQuote )
}
if expr . CloseQuote != 14 {
t . Fatalf ( "CloseQuote: %d" , expr . CloseQuote )
}
if l := len ( expr . Elements ) ; l != 2 {
t . Fatalf ( "len elements: %d" , l )
}
if l := len ( expr . Expressions ) ; l != 1 {
t . Fatalf ( "len expressions: %d" , l )
}
} else {
t . Fatal ( st )
}
} else {
t . Fatal ( prg . Body [ 0 ] )
}
}
func TestParseTemplateLiteralWithTail ( t * testing . T ) {
parser := newParser ( "" , "f()\n`test${a}tail` " )
prg , err := parser . parse ( )
if err != nil {
t . Fatal ( err )
}
if st , ok := prg . Body [ 0 ] . ( * ast . ExpressionStatement ) ; ok {
if expr , ok := st . Expression . ( * ast . TemplateLiteral ) ; ok {
if expr . CloseQuote != 18 {
t . Fatalf ( "CloseQuote: %d" , expr . CloseQuote )
}
} else {
t . Fatal ( st )
}
} else {
t . Fatal ( prg . Body [ 0 ] )
}
}