207 lines
7.2 KiB
Python
207 lines
7.2 KiB
Python
""" Tests for the JS parser. """
|
|
|
|
import unittest
|
|
import jsparagus.lexer
|
|
from js_parser.parser import parse_Script, JSParser
|
|
from js_parser.lexer import JSLexer
|
|
|
|
|
|
class ESTestCase(unittest.TestCase):
|
|
def parse(self, s):
|
|
if isinstance(s, list):
|
|
f = JSLexer(JSParser())
|
|
for chunk in s:
|
|
f.write(chunk)
|
|
return f.close()
|
|
else:
|
|
return parse_Script(s)
|
|
|
|
def assert_parses(self, s):
|
|
self.parse(s)
|
|
|
|
def assert_incomplete(self, s):
|
|
"""Assert that s fails to parse with UnexpectedEndError.
|
|
|
|
(This should be the case if `s` is a prefix of a valid Script.)
|
|
"""
|
|
self.assertRaises(jsparagus.lexer.UnexpectedEndError,
|
|
lambda: parse_Script(s))
|
|
|
|
def assert_syntax_error(self, s):
|
|
"""Assert that s fails to parse."""
|
|
with self.assertRaises(jsparagus.lexer.SyntaxError):
|
|
parse_Script(s)
|
|
|
|
def assert_can_close_after(self, s):
|
|
parser = JSParser()
|
|
lexer = JSLexer(parser)
|
|
if isinstance(s, list):
|
|
for chunk in s:
|
|
lexer.write(chunk)
|
|
else:
|
|
lexer.write(s)
|
|
self.assertTrue(lexer.can_close())
|
|
|
|
# === Tests!
|
|
|
|
def test_asi_at_end(self):
|
|
self.assert_parses("3 + 4")
|
|
self.assert_syntax_error("3 4")
|
|
self.assert_incomplete("3 +")
|
|
self.assert_incomplete("{")
|
|
self.assert_incomplete("{;")
|
|
|
|
def test_asi_at_block_end(self):
|
|
self.assert_parses("{ doCrimes() }")
|
|
self.assert_parses("function f() { ok }")
|
|
|
|
def test_asi_after_line_terminator(self):
|
|
self.assert_parses('''\
|
|
switch (value) {
|
|
case 1: break
|
|
case 2: console.log('2');
|
|
}
|
|
''')
|
|
self.assert_syntax_error(
|
|
"switch (value) { case 1: break case 2: console.log('2'); }")
|
|
|
|
def test_asi_after_no_line_terminator_here(self):
|
|
self.assert_parses('''\
|
|
function f() {
|
|
return
|
|
x;
|
|
}
|
|
''')
|
|
|
|
def test_asi_suppressed(self):
|
|
# The specification says ASI does not happen in the production
|
|
# EmptyStatement : `;`.
|
|
self.assert_syntax_error("if (true)")
|
|
self.assert_syntax_error("{ for (;;) }")
|
|
|
|
# ASI does not happen in for(;;) loops.
|
|
self.assert_syntax_error("for ( \n ; ) {}")
|
|
self.assert_syntax_error("for ( ; \n ) {}")
|
|
self.assert_syntax_error("for ( \n \n ) {}")
|
|
self.assert_syntax_error("for (var i = 0 \n i < 9; i++) {}")
|
|
self.assert_syntax_error("for (var i = 0; i < 9 \n i++) {}")
|
|
self.assert_syntax_error("for (i = 0 \n i < 9; i++) {}")
|
|
self.assert_syntax_error("for (i = 0; i < 9 \n i++) {}")
|
|
self.assert_syntax_error("for (let i = 0 \n i < 9; i++) {}")
|
|
|
|
# ASI is suppressed in the production ClassElement[Yield, Await] : `;`
|
|
# to prevent an infinite loop of ASI. lol
|
|
self.assert_syntax_error("class Fail { \n +1; }")
|
|
|
|
def test_if_else(self):
|
|
self.assert_parses("if (x) f();")
|
|
self.assert_incomplete("if (x)")
|
|
self.assert_parses("if (x) f(); else g();")
|
|
self.assert_incomplete("if (x) f(); else")
|
|
self.assert_parses("if (x) if (y) g(); else h();")
|
|
self.assert_parses("if (x) if (y) g(); else h(); else j();")
|
|
|
|
def test_lexer_decimal(self):
|
|
self.assert_parses("0.")
|
|
self.assert_parses(".5")
|
|
self.assert_syntax_error(".")
|
|
|
|
def test_arrow(self):
|
|
self.assert_parses("x => x")
|
|
self.assert_parses("f = x => x;")
|
|
self.assert_parses("(x, y) => [y, x]")
|
|
self.assert_parses("f = (x, y) => {}")
|
|
self.assert_syntax_error("(x, y) => {x: x, y: y}")
|
|
|
|
def test_invalid_character(self):
|
|
self.assert_syntax_error("\0")
|
|
self.assert_syntax_error("—x;")
|
|
self.assert_syntax_error("const ONE_THIRD = 1 ÷ 3;")
|
|
|
|
def test_regexp(self):
|
|
self.assert_parses(r"/\w/")
|
|
self.assert_parses("/[A-Z]/")
|
|
self.assert_parses("/[//]/")
|
|
self.assert_parses("/a*a/")
|
|
self.assert_parses("/**//x*/")
|
|
self.assert_parses("{} /x/")
|
|
self.assert_parses("of / 2")
|
|
|
|
def test_incomplete_comments(self):
|
|
self.assert_syntax_error("/*")
|
|
self.assert_syntax_error("/* hello world")
|
|
self.assert_syntax_error("/* hello world *")
|
|
self.assert_parses(["/* hello\n", " world */"])
|
|
self.assert_parses(["// oawfeoiawj", "ioawefoawjie"])
|
|
self.assert_parses(["// oawfeoiawj", "ioawefoawjie\n ok();"])
|
|
self.assert_parses(["// oawfeoiawj", "ioawefoawjie", "jiowaeawojefiw"])
|
|
self.assert_parses(["// oawfeoiawj", "ioawefoawjie", "jiowaeawojefiw\n ok();"])
|
|
|
|
def test_awkward_chunks(self):
|
|
self.assert_parses(["let", "ter.head = 1;"])
|
|
self.assert_parses(["let", " x = 1;"])
|
|
|
|
# `list()` here explodes the string into a list of one-character strings.
|
|
self.assert_parses(list("function f() { ok(); }"))
|
|
|
|
self.assertEqual(
|
|
self.parse(["/xyzzy/", "g;"]),
|
|
('script',
|
|
('script_body',
|
|
('statement_list_single',
|
|
('expression_statement',
|
|
('regexp_literal', '/xyzzy/g'))))))
|
|
|
|
self.assertEqual(
|
|
self.parse(['x/', '=2;']),
|
|
('script',
|
|
('script_body',
|
|
('statement_list_single',
|
|
('expression_statement',
|
|
('compound_assignment_expr',
|
|
('identifier_expr', ('identifier_reference', 'x')),
|
|
('box_assign_op', ('div_assign_op', '/=')),
|
|
('numeric_literal', '2')))))))
|
|
|
|
def test_can_close(self):
|
|
self.assert_can_close_after([])
|
|
self.assert_can_close_after("")
|
|
self.assert_can_close_after("2 + 2;\n")
|
|
self.assert_can_close_after("// seems ok\n")
|
|
|
|
def test_can_close_with_asi(self):
|
|
self.assert_can_close_after("2 + 2\n")
|
|
|
|
def test_conditional_keywords(self):
|
|
# property names
|
|
self.assert_parses("let obj = {if: 3, function: 4};")
|
|
self.assert_parses("assert(obj.if == 3);")
|
|
|
|
# method names
|
|
self.assert_parses("""
|
|
class C {
|
|
if() {}
|
|
function() {}
|
|
}
|
|
""")
|
|
|
|
self.assert_parses("var let = [new Date];") # let as identifier
|
|
self.assert_parses("let v = let;") # let as keyword, then identifier
|
|
# Next line would fail because the multitoken `let [` lookahead isn't implemented yet.
|
|
# self.assert_parses("let.length;") # `let .` -> ExpressionStatement
|
|
self.assert_syntax_error("let[0].getYear();") # `let [` -> LexicalDeclaration
|
|
|
|
self.assert_parses("""
|
|
var of = [1, 2, 3];
|
|
for (of of of) console.log(of); // logs 1, 2, 3
|
|
""")
|
|
self.assert_parses("var of, let, private, target;")
|
|
self.assert_parses("class X { get y() {} }")
|
|
self.assert_parses("async: { break async; }")
|
|
self.assert_parses("var get = { get get() {}, set get(v) {}, set: 3 };")
|
|
self.assert_parses("for (async of => {};;) {}")
|
|
# self.assert_parses("for (async of []) {}") # would fail
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|