Revision control
Copy as Markdown
Other Tools
""" 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()