use std::cell::Cell;
use std::collections::HashMap;
use std::rc::Rc;
use hermes_ast::context::GCLock;
use hermes_ast::node::Node;
use hermes_support::location::{SMLoc, SMRange};
use crate::lexer::{GrammarContext, JSLexer};
use crate::token_kinds::TokenKind;
mod classes;
mod expressions;
mod flow;
mod functions;
mod jsx;
mod modules;
mod pre_lazy;
mod statements;
mod ts;
pub use pre_lazy::ParserPass;
use pre_lazy::PreParsedBufferInfo;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[allow(dead_code)] pub(super) enum AllowImportExport {
Yes,
No,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum IsConstructorCall {
No,
Yes,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[allow(dead_code)]
pub(super) enum IsClassHeritageArgument {
No,
Yes,
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
pub struct Param(u32);
pub const PARAM_IN: Param = Param(1 << 0);
pub const PARAM_RETURN: Param = Param(1 << 1);
pub const PARAM_DEFAULT: Param = Param(1 << 2);
pub const PARAM_TAGGED: Param = Param(1 << 3);
impl Param {
pub fn plus(self, b: Param) -> Param {
Param(self.0 | b.0)
}
pub fn minus(self, b: Param) -> Param {
Param(self.0 & !b.0)
}
pub fn has(self, p: Param) -> bool {
(self.0 & p.0) != 0
}
pub fn has_all(self, p: Param) -> bool {
(self.0 & p.0) == p.0
}
pub fn get(self, p: Param) -> Param {
Param(self.0 & p.0)
}
}
const MAX_RECURSION_DEPTH: u32 =
if cfg!(debug_assertions) { 128 } else { 1024 };
pub(super) struct RecursionGuard(Rc<Cell<u32>>);
impl Drop for RecursionGuard {
fn drop(&mut self) {
self.0.set(self.0.get() - 1);
}
}
pub(super) struct ParamFlagGuard {
cell: Rc<Cell<bool>>,
old: bool,
}
impl Drop for ParamFlagGuard {
fn drop(&mut self) {
self.cell.set(self.old);
}
}
pub(super) struct JsxDepthGuard {
cell: Rc<Cell<u32>>,
old: u32,
}
impl Drop for JsxDepthGuard {
fn drop(&mut self) {
self.cell.set(self.old);
}
}
pub struct JSParserImpl<'gc, 'ast, 'ctx, 'a> {
gc: &'gc GCLock<'ast, 'ctx>,
pub(super) lexer: JSLexer<'a>,
recursion_depth: Rc<Cell<u32>>,
pub(super) param_yield: Rc<Cell<bool>>,
pub(super) param_await: Rc<Cell<bool>>,
pub(super) use_static_builtin: bool,
pub(super) allow_anon_function_type: Rc<Cell<bool>>,
pub(super) allow_conditional_type: Rc<Cell<bool>>,
pub(super) jsx_depth: Rc<Cell<u32>>,
pub(super) pass: ParserPass,
pub(super) pre_parsed: PreParsedBufferInfo,
pub(super) is_arrow_function: Rc<Cell<bool>>,
pub(super) contains_arrow_functions: Rc<Cell<bool>>,
pub(super) may_contain_arrow_functions_using_arguments: Rc<Cell<bool>>,
pub(super) seen_directives: Vec<Vec<u8>>,
}
impl<'gc, 'ast, 'ctx, 'a> JSParserImpl<'gc, 'ast, 'ctx, 'a> {
pub fn new(gc: &'gc GCLock<'ast, 'ctx>, mut lexer: JSLexer<'a>) -> Self {
lexer.set_strict_mode(gc.ctx().strict_mode());
lexer.advance(GrammarContext::AllowRegExp);
JSParserImpl {
gc,
lexer,
recursion_depth: Rc::new(Cell::new(0)),
param_yield: Rc::new(Cell::new(false)),
param_await: Rc::new(Cell::new(false)),
use_static_builtin: false,
allow_anon_function_type: Rc::new(Cell::new(false)),
allow_conditional_type: Rc::new(Cell::new(false)),
jsx_depth: Rc::new(Cell::new(0)),
pass: ParserPass::FullParse,
pre_parsed: PreParsedBufferInfo {
function_info: HashMap::new(),
},
is_arrow_function: Rc::new(Cell::new(false)),
contains_arrow_functions: Rc::new(Cell::new(false)),
may_contain_arrow_functions_using_arguments: Rc::new(Cell::new(false)),
seen_directives: Vec::new(),
}
}
pub fn new_with_pass(
gc: &'gc GCLock<'ast, 'ctx>,
lexer: JSLexer<'a>,
pass: ParserPass,
) -> Self {
let mut p = Self::new(gc, lexer);
p.pass = pass;
p
}
pub fn get_use_static_builtin(&self) -> bool {
self.use_static_builtin
}
pub fn take_pre_parsed(&mut self) -> PreParsedBufferInfo {
std::mem::replace(
&mut self.pre_parsed,
PreParsedBufferInfo {
function_info: HashMap::new(),
},
)
}
pub fn set_pre_parsed(&mut self, t: PreParsedBufferInfo) {
self.pre_parsed = t;
}
pub fn set_strict_mode(&mut self, strict: bool) {
self.lexer.set_strict_mode(strict);
}
pub(super) fn parse_flow(&self) -> bool {
self.gc.ctx().parse_flow()
}
pub(super) fn parse_flow_ambiguous(&self) -> bool {
self.gc.ctx().parse_flow_ambiguous()
}
pub(super) fn parse_flow_component_syntax(&self) -> bool {
self.gc.ctx().parse_flow_component_syntax()
}
pub(super) fn parse_flow_records(&self) -> bool {
self.gc.ctx().parse_flow_records()
}
pub(super) fn parse_flow_match(&self) -> bool {
self.gc.ctx().parse_flow_match()
}
pub(super) fn parse_ts(&self) -> bool {
self.gc.ctx().parse_ts()
}
pub(super) fn parse_jsx(&self) -> bool {
self.gc.ctx().parse_jsx()
}
pub(super) fn parse_types(&self) -> bool {
self.parse_flow() || self.parse_ts()
}
pub(in crate::js) fn parse_type_annotation(
&mut self,
wrapped_start: Option<SMLoc>,
allow_anon_function_type: flow::AllowAnonFunctionType,
) -> Option<&'gc Node<'gc>> {
debug_assert!(self.parse_flow() || self.parse_ts());
if self.parse_flow() {
return self.parse_type_annotation_flow(
wrapped_start,
allow_anon_function_type,
);
}
self.parse_type_annotation_ts(wrapped_start)
}
pub(in crate::js) fn parse_return_type_annotation(
&mut self,
wrapped_start: Option<SMLoc>,
allow_anon_function_type: flow::AllowAnonFunctionType,
) -> Option<&'gc Node<'gc>> {
debug_assert!(self.parse_flow() || self.parse_ts());
if self.parse_flow() {
return self.parse_return_type_annotation_flow(
wrapped_start,
allow_anon_function_type,
);
}
self.parse_type_annotation_ts(wrapped_start)
}
pub(in crate::js) fn parse_type_arguments(
&mut self,
) -> Option<&'gc Node<'gc>> {
debug_assert!(self.parse_flow() || self.parse_ts());
if self.parse_flow() {
return self
.parse_type_args_flow(crate::lexer::GrammarContext::Type);
}
self.parse_ts_type_arguments()
}
#[inline]
pub(super) fn cur_kind(&self) -> TokenKind {
self.lexer.token().kind()
}
#[inline]
pub(super) fn cur_range(&self) -> SMRange {
self.lexer.token().source_range()
}
#[inline]
pub(super) fn cur_start(&self) -> SMLoc {
self.lexer.token().start_loc()
}
#[inline]
pub(super) fn check(&self, kind: TokenKind) -> bool {
self.cur_kind() == kind
}
#[inline]
pub(super) fn check2(&self, k1: TokenKind, k2: TokenKind) -> bool {
let k = self.cur_kind();
k == k1 || k == k2
}
#[inline]
pub(super) fn check_n3(
&self,
k1: TokenKind,
k2: TokenKind,
k3: TokenKind,
) -> bool {
let k = self.cur_kind();
k == k1 || k == k2 || k == k3
}
#[inline]
pub(super) fn check_n4(
&self,
k1: TokenKind,
k2: TokenKind,
k3: TokenKind,
k4: TokenKind,
) -> bool {
let k = self.cur_kind();
k == k1 || k == k2 || k == k3 || k == k4
}
pub(super) fn advance(&mut self, grammar_context: GrammarContext) -> SMRange {
let prev = self.cur_range();
self.lexer.advance(grammar_context);
prev
}
pub(super) fn check_and_eat(
&mut self,
kind: TokenKind,
grammar_context: GrammarContext,
) -> bool {
if self.check(kind) {
self.advance(grammar_context);
true
} else {
false
}
}
pub(super) fn error_at(&mut self, range: SMRange, msg: &str) {
self.lexer.get_source_mgr_mut().error_at(
range.start,
Some(range),
msg,
hermes_support::diag::Subsystem::Parser,
);
}
pub(super) fn error_cur(&mut self, msg: &str) {
let range = self.cur_range();
self.error_at(range, msg);
}
pub(super) fn error_at_loc(&mut self, loc: SMLoc, msg: &str) {
self.lexer.get_source_mgr_mut().error_at(
loc,
None,
msg,
hermes_support::diag::Subsystem::Parser,
);
}
pub(super) fn need(&mut self, kind: TokenKind, where_: &str) -> bool {
if self.check(kind) {
return true;
}
let msg = format!(
"'{}' expected{}",
crate::token_kinds::token_kind_str(kind),
where_
);
self.error_expected_msg(&msg, None, None);
false
}
pub(super) fn error_expected_msg(
&mut self,
msg: &str,
what: Option<&str>,
what_loc: Option<SMLoc>,
) {
let err_loc = self.cur_start();
let range = match what_loc {
Some(w) => {
let sm = self.lexer.get_source_mgr();
if sm
.find_coords(w)
.is_same_source_line_as(&sm.find_coords(err_loc))
{
Some(sm.combine_into_range(w, err_loc))
} else {
None
}
}
None => None,
};
let same_line = range.is_some();
self.lexer.get_source_mgr_mut().error_at(
err_loc,
range,
msg,
hermes_support::diag::Subsystem::Parser,
);
if !same_line {
if let (Some(what), Some(w)) = (what, what_loc) {
self.lexer.get_source_mgr_mut().note_at(
w,
None,
what,
hermes_support::diag::Subsystem::Parser,
);
}
}
}
pub(super) fn need_at(
&mut self,
kind: TokenKind,
where_: &str,
what: Option<&str>,
what_loc: SMLoc,
) -> bool {
if self.check(kind) {
return true;
}
let msg = format!(
"'{}' expected{}",
crate::token_kinds::token_kind_str(kind),
where_
);
self.error_expected_msg(&msg, what, Some(what_loc));
false
}
pub(super) fn error_expected2(
&mut self,
k1: TokenKind,
k2: TokenKind,
where_: &str,
what: Option<&str>,
what_loc: SMLoc,
) {
let msg = format!(
"'{}' or '{}' expected{}",
crate::token_kinds::token_kind_str(k1),
crate::token_kinds::token_kind_str(k2),
where_
);
self.error_expected_msg(&msg, what, Some(what_loc));
}
pub(super) fn error_expected3(
&mut self,
k1: TokenKind,
k2: TokenKind,
k3: TokenKind,
where_: &str,
what: Option<&str>,
what_loc: SMLoc,
) {
let msg = format!(
"'{}', '{}' or '{}' expected{}",
crate::token_kinds::token_kind_str(k1),
crate::token_kinds::token_kind_str(k2),
crate::token_kinds::token_kind_str(k3),
where_
);
self.error_expected_msg(&msg, what, Some(what_loc));
}
#[allow(clippy::too_many_arguments)]
pub(super) fn error_expected4(
&mut self,
k1: TokenKind,
k2: TokenKind,
k3: TokenKind,
k4: TokenKind,
where_: &str,
what: Option<&str>,
what_loc: SMLoc,
) {
let msg = format!(
"'{}', '{}', '{}' or '{}' expected{}",
crate::token_kinds::token_kind_str(k1),
crate::token_kinds::token_kind_str(k2),
crate::token_kinds::token_kind_str(k3),
crate::token_kinds::token_kind_str(k4),
where_
);
self.error_expected_msg(&msg, what, Some(what_loc));
}
pub(super) fn eat_at(
&mut self,
kind: TokenKind,
grammar_context: GrammarContext,
where_: &str,
what: Option<&str>,
what_loc: SMLoc,
) -> bool {
if self.need_at(kind, where_, what, what_loc) {
self.advance(grammar_context);
true
} else {
false
}
}
pub(super) fn source_bytes(&self, start: SMLoc, end: SMLoc) -> &[u8] {
let buf_start = self.lexer.get_buffer_start();
let buf = self.lexer.buffer_bytes();
&buf[(start.offset - buf_start) as usize
..(end.offset - buf_start) as usize]
}
pub(super) fn source_bytes_atom(
&self,
start: SMLoc,
end: SMLoc,
) -> hermes_atom_table::AtomBytes {
self.lexer.get_string_literal(self.source_bytes(start, end))
}
pub(super) fn check_recursion(&mut self) -> Option<RecursionGuard> {
let depth = self.recursion_depth.get() + 1;
if depth >= MAX_RECURSION_DEPTH {
let loc = self.cur_start();
self.error_at_loc(
loc,
"Too many nested expressions/statements/declarations",
);
return None;
}
self.recursion_depth.set(depth);
Some(RecursionGuard(Rc::clone(&self.recursion_depth)))
}
pub(super) fn save_param_yield(&self, new_val: bool) -> ParamFlagGuard {
let old = self.param_yield.get();
self.param_yield.set(new_val);
ParamFlagGuard {
cell: Rc::clone(&self.param_yield),
old,
}
}
pub(super) fn save_param_await(&self, new_val: bool) -> ParamFlagGuard {
let old = self.param_await.get();
self.param_await.set(new_val);
ParamFlagGuard {
cell: Rc::clone(&self.param_await),
old,
}
}
pub(super) fn save_allow_anon_function_type(
&self,
new_val: bool,
) -> ParamFlagGuard {
let old = self.allow_anon_function_type.get();
self.allow_anon_function_type.set(new_val);
ParamFlagGuard {
cell: Rc::clone(&self.allow_anon_function_type),
old,
}
}
pub(super) fn save_allow_conditional_type(
&self,
new_val: bool,
) -> ParamFlagGuard {
let old = self.allow_conditional_type.get();
self.allow_conditional_type.set(new_val);
ParamFlagGuard {
cell: Rc::clone(&self.allow_conditional_type),
old,
}
}
pub(super) fn save_jsx_depth(&self, new_val: u32) -> JsxDepthGuard {
let old = self.jsx_depth.get();
self.jsx_depth.set(new_val);
JsxDepthGuard {
cell: Rc::clone(&self.jsx_depth),
old,
}
}
pub(super) fn dummy_range(&self) -> SMRange {
let loc = self.cur_start();
SMRange {
start: loc,
end: loc,
}
}
pub(super) fn invalid_range(&self) -> SMRange {
let loc = self.cur_start();
SMRange {
start: SMLoc {
source: loc.source,
offset: 1,
},
end: SMLoc {
source: loc.source,
offset: 0,
},
}
}
pub(super) fn set_location(
&self,
start: SMLoc,
end: SMLoc,
node: Node<'gc>,
) -> &'gc Node<'gc> {
let allocated = self.gc.alloc(node);
let md = allocated.metadata();
md.range.set(SMRange { start, end });
md.debug_loc.set(start);
allocated
}
pub(super) fn set_location_d(
&self,
start: SMLoc,
end: SMLoc,
debug: SMLoc,
node: Node<'gc>,
) -> &'gc Node<'gc> {
let allocated = self.gc.alloc(node);
let md = allocated.metadata();
md.range.set(SMRange { start, end });
md.debug_loc.set(debug);
allocated
}
pub fn parse(&mut self) -> Option<&'gc Node<'gc>> {
let res = self.parse_program()?;
if self.lexer.get_source_mgr().error_count() != 0 {
return None;
}
Some(res)
}
fn parse_program(&mut self) -> Option<&'gc Node<'gc>> {
use hermes_ast::node::Program;
use hermes_ast::node_child::{NodeList, NodeMetadata};
let start = self.cur_start();
let mut stmts: Vec<&'gc Node<'gc>> = Vec::new();
if !self.parse_statement_list(
Param::default(),
[TokenKind::eof],
true,
AllowImportExport::Yes,
&mut stmts,
) {
return None;
}
let end = if stmts.is_empty() {
start
} else {
stmts.last().unwrap().metadata().range.get().end
};
let body = NodeList::from_iter(self.gc, stmts);
let program = Node::Program(Program::new(
NodeMetadata::new(SMRange { start, end }),
body,
));
Some(self.set_location(start, end, program))
}
#[cfg(test)]
pub(crate) fn cur_kind_pub(&self) -> TokenKind {
self.cur_kind()
}
#[cfg(test)]
pub(crate) fn error_count_pub(&self) -> u32 {
self.lexer.get_source_mgr().error_count()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parser_constructs_and_sees_first_token() {
use hermes_ast::context::Context;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b" /* hi */ ");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let parser = JSParserImpl::new(&gc, lexer);
assert_eq!(
parser.cur_kind_pub(),
crate::token_kinds::TokenKind::eof
);
}
#[test]
fn parses_empty_program() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"/* only trivia */\n");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("empty program parses");
match program {
Node::Program(p) => assert!(p.body.is_empty(), "empty source -> empty body"),
other => panic!("expected Program, got {:?}", other.kind()),
}
assert_eq!(parser.error_count_pub(), 0);
}
#[test]
fn parses_numeric_literal_stmt() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"42;\n");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("42; parses");
assert_eq!(parser.error_count_pub(), 0);
if let Node::Program(p) = program {
assert_eq!(p.body.iter().count(), 1);
let stmt = p.body.iter().next().unwrap();
if let Node::ExpressionStatement(es) = stmt {
if let Node::NumericLiteral(nl) = es.expression {
assert_eq!(nl.value.get(), 42.0);
} else {
panic!("expected NumericLiteral");
}
} else {
panic!("expected ExpressionStatement");
}
} else {
panic!("expected Program");
}
}
#[test]
fn parse_returns_none_on_recoverable_error() {
use hermes_ast::context::Context;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id =
sm.add_buffer_bytes("input", b"\"use strict\"; var x = 010;\n");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse();
assert!(parser.error_count_pub() > 0, "octal literal must error");
assert!(
program.is_none(),
"parse() must return None once the source has errors, even \
though parseProgram() itself recovered and built a tree"
);
}
#[test]
fn parses_empty_statement() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b";;;\n");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect(";;; parses");
assert_eq!(parser.error_count_pub(), 0);
if let Node::Program(p) = program {
assert_eq!(p.body.iter().count(), 3);
for stmt in p.body {
assert!(
matches!(stmt, Node::EmptyStatement(_)),
"expected EmptyStatement"
);
}
} else {
panic!("expected Program");
}
}
#[test]
fn if_statement_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"if(x);");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("if statement parses in P2.4");
assert_eq!(parser.error_count_pub(), 0, "zero errors");
let Node::Program(p) = program else {
panic!("expected Program")
};
let stmt = p.body.iter().next().expect("one statement");
assert!(
matches!(stmt, Node::IfStatement(_)),
"expected IfStatement, got {:?}",
stmt.kind()
);
}
#[test]
fn function_expression_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(&gc, &mut sm, atoms, b"(function(){});");
assert!(
matches!(expr, Node::FunctionExpression(_)),
"expected FunctionExpression, got {:?}",
expr.kind()
);
}
fn assert_parse_errors(src: &[u8], why: &str) {
use hermes_ast::context::Context;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", src);
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
assert!(parser.parse().is_none(), "{why}");
assert!(parser.error_count_pub() >= 1, "{why}: expected an error");
}
fn assert_parse_has_errors_impl(src: &[u8], why: &str, parse_flow: bool) {
use hermes_ast::context::Context;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", src);
let mut ctx = Context::new();
ctx.set_parse_flow(parse_flow);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let _ = parser.parse();
assert!(parser.error_count_pub() >= 1, "{why}: expected an error");
}
fn assert_parse_has_errors(src: &[u8], why: &str) {
assert_parse_has_errors_impl(src, why, false);
}
fn parse_one_stmt<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::Node<'gc> {
flow_parse_stmt_at(gc, sm, src, 0)
}
#[test]
fn function_declaration_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"function f(){}");
assert!(
matches!(stmt, Node::FunctionDeclaration(_)),
"expected FunctionDeclaration, got {:?}",
stmt.kind()
);
}
#[test]
fn generator_declaration_has_generator_flag() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"function* h(){}");
if let Node::FunctionDeclaration(fd) = stmt {
assert!(fd.generator.get(), "generator flag is true");
assert!(!fd.r#async.get(), "async flag is false");
} else {
panic!("expected FunctionDeclaration");
}
}
#[test]
fn async_declaration_has_async_flag() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"async function k(){}");
if let Node::FunctionDeclaration(fd) = stmt {
assert!(fd.r#async.get(), "async flag is true");
assert!(!fd.generator.get(), "generator flag is false");
} else {
panic!("expected FunctionDeclaration");
}
}
#[test]
fn function_params_identifier_and_rest() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"function f(a, ...r){}");
if let Node::FunctionDeclaration(fd) = stmt {
let params: Vec<_> = fd.params.iter().collect();
assert_eq!(params.len(), 2);
assert!(
matches!(params[0], Node::Identifier(_)),
"first param is Identifier"
);
assert!(
matches!(params[1], Node::RestElement(_)),
"second param is RestElement"
);
} else {
panic!("expected FunctionDeclaration");
}
}
#[test]
fn function_params_object_and_array_patterns() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"function g({x},[y]){}");
if let Node::FunctionDeclaration(fd) = stmt {
let params: Vec<_> = fd.params.iter().collect();
assert_eq!(params.len(), 2);
assert!(
matches!(params[0], Node::ObjectPattern(_)),
"first param is ObjectPattern"
);
assert!(
matches!(params[1], Node::ArrayPattern(_)),
"second param is ArrayPattern"
);
} else {
panic!("expected FunctionDeclaration");
}
}
#[test]
fn await_in_async_body_parses() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(
parse_snippet(&mut sm, b"async function f(){ await x; }"),
"await in async body must parse cleanly"
);
}
#[test]
fn yield_in_generator_parses() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(
parse_snippet(&mut sm, b"function* g(){ yield 1; }"),
"yield in generator body must parse cleanly"
);
}
#[test]
fn class_declaration_with_heritage() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"class A extends B {}");
let Node::ClassDeclaration(cd) = stmt else {
panic!("expected ClassDeclaration, got {:?}", stmt.kind());
};
let sup = cd.super_class.expect("superClass present");
match sup {
Node::Identifier(id) => {
let bytes = gc.ctx().atom_table.bytes(id.name.get());
assert_eq!(bytes, b"B");
}
other => panic!("expected Identifier superClass, got {:?}", other.kind()),
}
}
fn parse_one_class_member<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
member_src: &str,
) -> &'gc hermes_ast::node::Node<'gc> {
use hermes_ast::node::Node;
let src = format!("class A {{ {member_src} }}");
let stmt = parse_one_stmt(gc, sm, src.as_bytes());
let Node::ClassDeclaration(cd) = stmt else {
panic!("expected ClassDeclaration, got {:?}", stmt.kind());
};
let Node::ClassBody(cb) = cd.body else {
panic!("expected ClassBody");
};
cb.body.iter().next().expect("one class member")
}
#[test]
fn class_method_kind_method() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let member = parse_one_class_member(&gc, &mut sm, "m(){}");
let Node::MethodDefinition(md) = member else {
panic!("expected MethodDefinition, got {:?}", member.kind());
};
let kind = gc.ctx().atom_table.bytes(md.kind.get());
assert_eq!(kind, b"method");
assert!(!md.r#static.get(), "not static");
}
#[test]
fn class_method_kind_constructor() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let member = parse_one_class_member(&gc, &mut sm, "constructor(){}");
let Node::MethodDefinition(md) = member else {
panic!("expected MethodDefinition, got {:?}", member.kind());
};
let kind = gc.ctx().atom_table.bytes(md.kind.get());
assert_eq!(kind, b"constructor");
}
#[test]
fn class_method_kind_get() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let member = parse_one_class_member(&gc, &mut sm, "get x(){}");
let Node::MethodDefinition(md) = member else {
panic!("expected MethodDefinition, got {:?}", member.kind());
};
let kind = gc.ctx().atom_table.bytes(md.kind.get());
assert_eq!(kind, b"get");
}
#[test]
fn class_method_static() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let member = parse_one_class_member(&gc, &mut sm, "static s(){}");
let Node::MethodDefinition(md) = member else {
panic!("expected MethodDefinition, got {:?}", member.kind());
};
assert!(md.r#static.get(), "static flag set");
}
#[test]
fn class_private_method_key_is_private_name() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let member = parse_one_class_member(&gc, &mut sm, "#p(){}");
let Node::MethodDefinition(md) = member else {
panic!("expected MethodDefinition, got {:?}", member.kind());
};
assert!(
matches!(md.key, Node::PrivateName(_)),
"method key is PrivateName, got {:?}",
md.key.kind()
);
}
#[test]
fn class_field_with_value() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let member = parse_one_class_member(&gc, &mut sm, "x = 1;");
let Node::ClassProperty(cp) = member else {
panic!("expected ClassProperty, got {:?}", member.kind());
};
assert!(cp.value.is_some(), "field has a value");
}
#[test]
fn class_private_field() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let member = parse_one_class_member(&gc, &mut sm, "#f;");
assert!(
matches!(member, Node::ClassPrivateProperty(_)),
"expected ClassPrivateProperty, got {:?}",
member.kind()
);
}
#[test]
fn class_static_block() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let member = parse_one_class_member(&gc, &mut sm, "static { }");
assert!(
matches!(member, Node::StaticBlock(_)),
"expected StaticBlock, got {:?}",
member.kind()
);
}
#[test]
fn class_expression_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(&gc, &mut sm, atoms, b"(class {});");
assert!(
matches!(expr, Node::ClassExpression(_)),
"expected ClassExpression, got {:?}",
expr.kind()
);
}
#[test]
fn class_declaration_with_decorator() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"@dec class A {}");
let Node::ClassDeclaration(cd) = stmt else {
panic!("expected ClassDeclaration, got {:?}", stmt.kind());
};
let decorators: Vec<_> = cd.decorators.iter().collect();
assert_eq!(decorators.len(), 1, "one decorator");
assert!(
matches!(decorators[0], Node::Decorator(_)),
"expected Decorator node"
);
}
#[test]
fn class_strict_mode_does_not_leak() {
use hermes_ast::context::Context;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"class A {}\nwith(x) y;");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
assert!(
parser.parse().is_some(),
"with-statement after class must parse (sloppy mode restored)"
);
assert_eq!(
parser.error_count_pub(),
0,
"no errors: class strict mode must not leak to enclosing sloppy code"
);
}
#[test]
fn import_call_no_options() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(&gc, &mut sm, atoms, b"import('m');");
if let Node::ImportExpression(ie) = expr {
assert!(
matches!(ie.source, Node::StringLiteral(_)),
"source should be a StringLiteral, got {:?}",
ie.source.kind()
);
assert!(ie.options.is_none(), "options should be None");
} else {
panic!("expected ImportExpression, got {:?}", expr.kind());
}
}
#[test]
fn import_call_with_options() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(&gc, &mut sm, atoms, b"import('m', {});");
if let Node::ImportExpression(ie) = expr {
assert!(
matches!(ie.options, Some(Node::ObjectExpression(_))),
"options should be Some(ObjectExpression), got {:?}",
ie.options.map(|o| o.kind())
);
} else {
panic!("expected ImportExpression, got {:?}", expr.kind());
}
}
#[test]
fn import_meta_property() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(&gc, &mut sm, atoms, b"import.meta;");
if let Node::MetaProperty(mp) = expr {
if let Node::Identifier(meta) = mp.meta {
assert_eq!(
gc.ctx().atom_table.bytes(meta.name.get()),
b"import",
"meta identifier name should be `import`"
);
} else {
panic!("meta should be an Identifier");
}
if let Node::Identifier(prop) = mp.property {
assert_eq!(
gc.ctx().atom_table.bytes(prop.name.get()),
b"meta",
"property identifier name should be `meta`"
);
} else {
panic!("property should be an Identifier");
}
} else {
panic!("expected MetaProperty, got {:?}", expr.kind());
}
}
#[test]
fn import_meta_bad_form_errors() {
assert_parse_errors(b"import.foo;", "'meta' expected after import.");
}
#[test]
fn import_meta_escaped_meta_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr =
parse_expr_from(&gc, &mut sm, atoms, b"import.m\\u0065ta;");
if let Node::MetaProperty(mp) = expr {
if let Node::Identifier(prop) = mp.property {
assert_eq!(
gc.ctx().atom_table.bytes(prop.name.get()),
b"meta",
"escaped `m\\u0065ta` should intern to `meta`"
);
} else {
panic!("property should be an Identifier");
}
} else {
panic!("expected MetaProperty, got {:?}", expr.kind());
}
}
fn ident_bytes<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
node: &hermes_ast::node::Node<'gc>,
) -> Vec<u8> {
if let hermes_ast::node::Node::Identifier(id) = node {
gc.ctx().atom_table.bytes(id.name.get()).to_vec()
} else {
panic!("expected Identifier, got {:?}", node.kind());
}
}
#[test]
fn import_default_specifier_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"import x from 'm';");
if let Node::ImportDeclaration(decl) = stmt {
assert_eq!(decl.specifiers.iter().count(), 1);
let spec = decl.specifiers.iter().next().unwrap();
if let Node::ImportDefaultSpecifier(ds) = spec {
assert_eq!(ident_bytes(&gc, ds.local), b"x");
} else {
panic!("expected ImportDefaultSpecifier, got {:?}", spec.kind());
}
if let Node::StringLiteral(sl) = decl.source {
assert_eq!(gc.ctx().atom_table.bytes(sl.value.get()), b"m");
} else {
panic!("source should be a StringLiteral");
}
assert_eq!(
gc.ctx().atom_table.bytes(decl.import_kind.get()),
b"value"
);
} else {
panic!("expected ImportDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn import_named_specifier_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"import {b as c} from 'm';");
if let Node::ImportDeclaration(decl) = stmt {
assert_eq!(decl.specifiers.iter().count(), 1);
let spec = decl.specifiers.iter().next().unwrap();
if let Node::ImportSpecifier(is) = spec {
assert_eq!(ident_bytes(&gc, is.imported), b"b");
assert_eq!(ident_bytes(&gc, is.local), b"c");
} else {
panic!("expected ImportSpecifier, got {:?}", spec.kind());
}
} else {
panic!("expected ImportDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn import_namespace_specifier_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"import * as ns from 'm';");
if let Node::ImportDeclaration(decl) = stmt {
assert_eq!(decl.specifiers.iter().count(), 1);
let spec = decl.specifiers.iter().next().unwrap();
if let Node::ImportNamespaceSpecifier(ns) = spec {
assert_eq!(ident_bytes(&gc, ns.local), b"ns");
} else {
panic!(
"expected ImportNamespaceSpecifier, got {:?}",
spec.kind()
);
}
} else {
panic!("expected ImportDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn import_default_plus_named_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt =
parse_one_stmt(&gc, &mut sm, b"import d, {a, b} from 'm';");
if let Node::ImportDeclaration(decl) = stmt {
let specs: Vec<_> = decl.specifiers.iter().collect();
assert_eq!(specs.len(), 3);
assert!(matches!(specs[0], Node::ImportDefaultSpecifier(_)));
assert!(matches!(specs[1], Node::ImportSpecifier(_)));
assert!(matches!(specs[2], Node::ImportSpecifier(_)));
} else {
panic!("expected ImportDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn import_default_plus_namespace_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt =
parse_one_stmt(&gc, &mut sm, b"import d, * as ns from 'm';");
if let Node::ImportDeclaration(decl) = stmt {
let specs: Vec<_> = decl.specifiers.iter().collect();
assert_eq!(specs.len(), 2);
assert!(matches!(specs[0], Node::ImportDefaultSpecifier(_)));
assert!(matches!(specs[1], Node::ImportNamespaceSpecifier(_)));
} else {
panic!("expected ImportDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn import_bare_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"import 'm';");
if let Node::ImportDeclaration(decl) = stmt {
assert_eq!(decl.specifiers.iter().count(), 0);
assert_eq!(decl.attributes.iter().count(), 0);
if let Node::StringLiteral(sl) = decl.source {
assert_eq!(gc.ctx().atom_table.bytes(sl.value.get()), b"m");
} else {
panic!("source should be a StringLiteral");
}
} else {
panic!("expected ImportDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn import_attribute_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(
&gc,
&mut sm,
b"import x from 'm' with { type: 'json' };",
);
if let Node::ImportDeclaration(decl) = stmt {
assert_eq!(decl.attributes.iter().count(), 1);
let attr = decl.attributes.iter().next().unwrap();
if let Node::ImportAttribute(ia) = attr {
assert_eq!(ident_bytes(&gc, ia.key), b"type");
if let Node::StringLiteral(sl) = ia.value {
assert_eq!(
gc.ctx().atom_table.bytes(sl.value.get()),
b"json"
);
} else {
panic!("attribute value should be a StringLiteral");
}
} else {
panic!("expected ImportAttribute, got {:?}", attr.kind());
}
} else {
panic!("expected ImportDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn import_duplicate_named_errors() {
assert_parse_has_errors(
b"import {a, a} from 'm';",
"duplicate named import is a Duplicate entry error",
);
}
#[test]
fn import_in_block_errors() {
assert_parse_has_errors(
b"{ import x from 'm'; }",
"import inside a block must be at top level of module",
);
}
#[test]
fn export_named_specifier_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let buf_id = sm.add_buffer_bytes("input", b"var a;\nexport {a as b};");
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("parse succeeded");
assert_eq!(parser.error_count_pub(), 0, "zero errors");
let Node::Program(p) = program else {
panic!("expected Program")
};
let stmt = p.body.iter().nth(1).expect("has second statement");
if let Node::ExportNamedDeclaration(decl) = stmt {
assert!(decl.declaration.is_none(), "declaration None");
assert!(decl.source.is_none(), "source None");
assert_eq!(
gc.ctx().atom_table.bytes(decl.export_kind.get()),
b"value"
);
assert_eq!(decl.specifiers.iter().count(), 1);
let spec = decl.specifiers.iter().next().unwrap();
if let Node::ExportSpecifier(es) = spec {
assert_eq!(ident_bytes(&gc, es.exported), b"b");
assert_eq!(ident_bytes(&gc, es.local), b"a");
} else {
panic!("expected ExportSpecifier, got {:?}", spec.kind());
}
} else {
panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn export_named_from_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"export {a} from 'm';");
if let Node::ExportNamedDeclaration(decl) = stmt {
if let Some(Node::StringLiteral(sl)) = decl.source {
assert_eq!(gc.ctx().atom_table.bytes(sl.value.get()), b"m");
} else {
panic!("source should be a StringLiteral");
}
} else {
panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn export_all_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"export * from 'm';");
if let Node::ExportAllDeclaration(decl) = stmt {
if let Node::StringLiteral(sl) = decl.source {
assert_eq!(gc.ctx().atom_table.bytes(sl.value.get()), b"m");
} else {
panic!("source should be a StringLiteral");
}
} else {
panic!("expected ExportAllDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn export_namespace_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"export * as ns from 'm';");
if let Node::ExportNamedDeclaration(decl) = stmt {
assert_eq!(decl.specifiers.iter().count(), 1);
let spec = decl.specifiers.iter().next().unwrap();
if let Node::ExportNamespaceSpecifier(ns) = spec {
assert_eq!(ident_bytes(&gc, ns.exported), b"ns");
} else {
panic!("expected ExportNamespaceSpecifier, got {:?}", spec.kind());
}
} else {
panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn export_default_expr_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"export default 1;");
if let Node::ExportDefaultDeclaration(decl) = stmt {
assert!(
matches!(decl.declaration, Node::NumericLiteral(_)),
"declaration should be a NumericLiteral, got {:?}",
decl.declaration.kind()
);
} else {
panic!("expected ExportDefaultDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn export_default_function_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"export default function(){}");
if let Node::ExportDefaultDeclaration(decl) = stmt {
if let Node::FunctionDeclaration(fd) = decl.declaration {
assert!(fd.id.is_none(), "default function has no id");
} else {
panic!(
"declaration should be a FunctionDeclaration, got {:?}",
decl.declaration.kind()
);
}
} else {
panic!("expected ExportDefaultDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn export_var_declaration_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"export var x = 1;");
if let Node::ExportNamedDeclaration(decl) = stmt {
assert!(
matches!(decl.declaration, Some(Node::VariableDeclaration(_))),
"declaration should be a VariableDeclaration, got {:?}",
decl.declaration.map(|d| d.kind())
);
} else {
panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn export_function_declaration_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"export function f(){}");
if let Node::ExportNamedDeclaration(decl) = stmt {
assert!(
matches!(decl.declaration, Some(Node::FunctionDeclaration(_))),
"declaration should be a FunctionDeclaration, got {:?}",
decl.declaration.map(|d| d.kind())
);
} else {
panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind());
}
}
#[test]
fn export_in_block_errors() {
assert_parse_has_errors(
b"{ export var x = 1; }",
"export inside a block must be at top level of module",
);
}
fn assert_flow_export_kind(src: &[u8], kind: &[u8]) {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = flow_parse_stmt_at(&gc, &mut sm, src, 0);
let Node::ExportNamedDeclaration(decl) = stmt else {
panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind())
};
assert_eq!(
gc.ctx().atom_table.bytes(decl.export_kind.get()),
kind,
"exportKind for {:?}",
String::from_utf8_lossy(src)
);
}
#[test]
fn flow_export_type_alias_kind_is_type() {
assert_flow_export_kind(b"export type A = number;", b"type");
}
#[test]
fn flow_export_opaque_type_kind_is_type() {
assert_flow_export_kind(b"export opaque type B = string;", b"type");
}
#[test]
fn flow_export_interface_kind_is_type() {
assert_flow_export_kind(b"export interface I { x: number }", b"type");
}
#[test]
fn flow_export_value_kinds_stay_value() {
assert_flow_export_kind(b"export var x = 1;", b"value");
assert_flow_export_kind(b"export function f(){}", b"value");
}
#[test]
fn export_type_without_flow_errors() {
assert_parse_has_errors(
b"export type A = 1;",
"export type without Flow is not a declaration",
);
}
#[test]
fn flow_export_type_clause_and_star_have_type_kind() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
assert_flow_export_kind(b"export type {x};", b"type");
assert_flow_export_kind(b"export type {x} from 'm';", b"type");
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt =
flow_parse_stmt_at(&gc, &mut sm, b"export type * from 'm';", 0);
let Node::ExportAllDeclaration(decl) = stmt else {
panic!("expected ExportAllDeclaration, got {:?}", stmt.kind())
};
assert_eq!(
gc.ctx().atom_table.bytes(decl.export_kind.get()),
b"type"
);
}
#[test]
fn array_literal_parses() {
use hermes_ast::context::Context;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"[1];");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
assert!(
parser.parse().is_some(),
"array literal should parse successfully in P1.7"
);
assert_eq!(parser.error_count_pub(), 0);
}
#[test]
fn parses_sequence_expression() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"1, 2, 3;");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("1, 2, 3; parses");
assert_eq!(parser.error_count_pub(), 0);
if let Node::Program(p) = program {
assert_eq!(p.body.iter().count(), 1);
let stmt = p.body.iter().next().unwrap();
if let Node::ExpressionStatement(es) = stmt {
assert!(
matches!(es.expression, Node::SequenceExpression(_)),
"expected SequenceExpression"
);
} else {
panic!("expected ExpressionStatement");
}
}
}
#[test]
fn use_strict_directive_sets_strict_mode() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"\"use strict\"; 1;");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("\"use strict\"; 1; parses");
assert_eq!(parser.error_count_pub(), 0);
if let Node::Program(p) = program {
assert_eq!(p.body.iter().count(), 2);
}
assert!(parser.lexer.is_strict_mode());
}
fn parse_expr_from<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
atoms: &hermes_atom_table::AtomTable,
src: &[u8],
) -> &'gc hermes_ast::node::Node<'gc> {
let buf_id = sm.add_buffer_bytes("input", src);
let lexer = crate::lexer::JSLexer::new(
buf_id,
sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(gc, lexer);
let program = parser.parse().expect("parse succeeded");
assert_eq!(parser.error_count_pub(), 0, "zero errors");
if let hermes_ast::node::Node::Program(p) = program {
let stmt = p.body.iter().next().expect("has statement");
if let hermes_ast::node::Node::ExpressionStatement(es) = stmt {
return es.expression;
}
}
panic!("expected ExpressionStatement");
}
#[test]
fn parses_simple_assignment() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(&gc, &mut sm, atoms, b"a = b;");
match expr {
Node::AssignmentExpression(n) => {
let op_bytes = gc.ctx().atom_table.bytes(n.operator.get());
assert_eq!(op_bytes, b"=", "operator is =");
assert!(
matches!(n.left, Node::Identifier(_)),
"left is Identifier"
);
assert!(
matches!(n.right, Node::Identifier(_)),
"right is Identifier"
);
}
other => panic!("expected AssignmentExpression, got {:?}", other.kind()),
}
}
#[test]
fn parses_compound_assignment_plus() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(&gc, &mut sm, atoms, b"a += 1;");
match expr {
Node::AssignmentExpression(n) => {
let op_bytes = gc.ctx().atom_table.bytes(n.operator.get());
assert_eq!(op_bytes, b"+=", "operator is +=");
}
other => panic!("expected AssignmentExpression, got {:?}", other.kind()),
}
}
#[test]
fn parses_right_assoc_chain() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(&gc, &mut sm, atoms, b"a = b = c;");
match expr {
Node::AssignmentExpression(outer) => {
assert!(
matches!(outer.left, Node::Identifier(_)),
"outer.left is Identifier(a)"
);
match outer.right {
Node::AssignmentExpression(inner) => {
let inner_left = match inner.left {
Node::Identifier(id) => id,
other => panic!(
"expected Identifier(b), got {:?}",
other.kind()
),
};
let b_bytes = gc.ctx().atom_table.bytes(inner_left.name.get());
assert_eq!(b_bytes, b"b", "inner.left is b");
assert!(
matches!(inner.right, Node::Identifier(_)),
"inner.right is Identifier(c)"
);
}
other => panic!(
"outer.right must be AssignmentExpression(b=c), got {:?}",
other.kind()
),
}
}
other => panic!("expected AssignmentExpression, got {:?}", other.kind()),
}
}
#[test]
fn assignment_not_confused_with_equality() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(&gc, &mut sm, atoms, b"a == b;");
assert!(
matches!(expr, Node::BinaryExpression(_)),
"== produces BinaryExpression, not AssignmentExpression"
);
}
#[test]
fn arrow_expr_parses_after_p33() {
use hermes_ast::context::Context;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"a => b;");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
assert!(parser.parse().is_some(), "arrow should parse in P3.3");
assert_eq!(parser.error_count_pub(), 0, "no errors");
}
fn parse_snippet(sm: &mut hermes_support::manager::SourceErrorManager, src: &[u8]) -> bool {
use hermes_ast::context::Context;
let buf_id = sm.add_buffer_bytes("input", src);
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let result = parser.parse();
result.is_some() && parser.error_count_pub() == 0
}
#[test]
fn object_literal_empty_parses() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(parse_snippet(&mut sm, b"({});"), "empty object literal");
}
#[test]
fn object_literal_keyed_parses() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(parse_snippet(&mut sm, b"({a: 1, b: 2});"), "keyed properties");
}
#[test]
fn object_literal_shorthand_parses() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(parse_snippet(&mut sm, b"({a, b});"), "shorthand properties");
}
#[test]
fn object_literal_computed_parses() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(parse_snippet(&mut sm, b"({[x]: 1});"), "computed key");
}
#[test]
fn object_literal_spread_parses() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(parse_snippet(&mut sm, b"({...a});"), "spread element");
}
#[test]
fn object_literal_string_and_number_keys_parse() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(
parse_snippet(&mut sm, b"({\"s\": 1, 0: 2});"),
"string and number keys"
);
}
#[test]
fn object_literal_get_set_as_data_property() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(
parse_snippet(&mut sm, b"({get: 1, set: 2});"),
"get/set as data properties"
);
assert!(
parse_snippet(&mut sm, b"({get, set});"),
"get/set shorthand"
);
}
#[test]
fn object_literal_async_as_data_property() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(
parse_snippet(&mut sm, b"({async: 1});"),
"async as data property"
);
assert!(
parse_snippet(&mut sm, b"({async});"),
"async shorthand"
);
}
#[test]
fn object_literal_cover_initializer_parses() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(
parse_snippet(&mut sm, b"({a=1});"),
"CoverInitializedName must parse"
);
}
fn parse_single_property<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::Property<'gc> {
use hermes_ast::node::Node;
let expr = parse_expr_ok(gc, sm, src);
let Node::ObjectExpression(obj) = expr else {
panic!("expected ObjectExpression, got {:?}", expr.kind());
};
let props: Vec<_> = obj.properties.iter().collect();
assert_eq!(props.len(), 1, "expected exactly one property");
match props[0] {
Node::Property(p) => p,
other => panic!("expected Property, got {:?}", other.kind()),
}
}
#[test]
fn object_getter_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let p = parse_single_property(&gc, &mut sm, b"({get x() { return 1; }});");
assert_eq!(gc.ctx().atom_table.bytes(p.kind.get()), b"get");
assert!(!p.method.get(), "getter is not a method");
assert!(!p.computed.get());
let Node::FunctionExpression(f) = p.value else {
panic!("getter value must be FunctionExpression");
};
assert_eq!(f.params.iter().count(), 0, "getter has no params");
assert!(!f.generator.get());
assert!(!f.r#async.get());
}
#[test]
fn object_setter_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let p = parse_single_property(&gc, &mut sm, b"({set x(v) {}});");
assert_eq!(gc.ctx().atom_table.bytes(p.kind.get()), b"set");
assert!(!p.method.get(), "setter is not a method");
let Node::FunctionExpression(f) = p.value else {
panic!("setter value must be FunctionExpression");
};
assert_eq!(f.params.iter().count(), 1, "setter has one param");
}
#[test]
fn object_method_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let p = parse_single_property(&gc, &mut sm, b"({m() {}});");
assert_eq!(gc.ctx().atom_table.bytes(p.kind.get()), b"init");
assert!(p.method.get(), "plain method has method=true");
assert!(!p.shorthand.get());
let Node::FunctionExpression(f) = p.value else {
panic!("method value must be FunctionExpression");
};
assert!(!f.generator.get());
assert!(!f.r#async.get());
}
#[test]
fn object_generator_method_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let p = parse_single_property(&gc, &mut sm, b"({*g() {}});");
assert!(p.method.get());
let Node::FunctionExpression(f) = p.value else {
panic!("generator method value must be FunctionExpression");
};
assert!(f.generator.get(), "generator==true");
assert!(!f.r#async.get());
}
#[test]
fn object_async_method_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let p = parse_single_property(&gc, &mut sm, b"({async a() {}});");
assert!(p.method.get());
let Node::FunctionExpression(f) = p.value else {
panic!("async method value must be FunctionExpression");
};
assert!(f.r#async.get(), "async==true");
assert!(!f.generator.get());
}
#[test]
fn object_async_generator_method_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let p = parse_single_property(&gc, &mut sm, b"({async *ag() {}});");
assert!(p.method.get());
let Node::FunctionExpression(f) = p.value else {
panic!("async generator method value must be FunctionExpression");
};
assert!(f.r#async.get(), "async==true");
assert!(f.generator.get(), "generator==true");
}
#[test]
fn object_computed_method_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let p = parse_single_property(&gc, &mut sm, b"({[k]() {}});");
assert!(p.computed.get(), "computed key");
assert!(p.method.get());
assert!(matches!(p.value, Node::FunctionExpression(_)));
}
#[test]
fn object_string_and_numeric_methods_parse() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
assert!(
parse_snippet(&mut sm, b"({'s'() {}, 0() {}});"),
"string- and numeric-keyed methods"
);
}
fn parse_expr_ok<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::Node<'gc> {
let buf_id = sm.add_buffer_bytes("input", src);
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(gc, lexer);
let program = parser.parse().expect("parse succeeded");
assert_eq!(parser.error_count_pub(), 0, "zero errors");
if let hermes_ast::node::Node::Program(p) = program {
let stmt = p.body.iter().next().expect("has statement");
if let hermes_ast::node::Node::ExpressionStatement(es) = stmt {
return es.expression;
}
}
panic!("expected ExpressionStatement");
}
#[test]
fn array_destructure_simple() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let expr = parse_expr_ok(&gc, &mut sm, b"[a] = b;");
match expr {
Node::AssignmentExpression(asn) => {
let op = gc.ctx().atom_table.bytes(asn.operator.get());
assert_eq!(op, b"=");
assert!(
matches!(asn.left, Node::ArrayPattern(_)),
"left is ArrayPattern, got {:?}",
asn.left.kind()
);
}
other => panic!("expected AssignmentExpression, got {:?}", other.kind()),
}
}
#[test]
fn array_destructure_with_rest() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let expr = parse_expr_ok(&gc, &mut sm, b"[a, ...b] = c;");
if let Node::AssignmentExpression(asn) = expr {
if let Node::ArrayPattern(ap) = asn.left {
let elems: Vec<_> = ap.elements.iter().collect();
assert_eq!(elems.len(), 2);
assert!(matches!(elems[0], Node::Identifier(_)));
assert!(matches!(elems[1], Node::RestElement(_)));
} else {
panic!("left must be ArrayPattern");
}
} else {
panic!("expected AssignmentExpression");
}
}
#[test]
fn array_destructure_with_hole() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let expr = parse_expr_ok(&gc, &mut sm, b"[a, , b] = c;");
if let Node::AssignmentExpression(asn) = expr {
if let Node::ArrayPattern(ap) = asn.left {
let elems: Vec<_> = ap.elements.iter().collect();
assert_eq!(elems.len(), 3);
assert!(matches!(elems[0], Node::Identifier(_)));
assert!(matches!(elems[1], Node::Empty(_)));
assert!(matches!(elems[2], Node::Identifier(_)));
} else {
panic!("left must be ArrayPattern");
}
} else {
panic!("expected AssignmentExpression");
}
}
#[test]
fn array_destructure_with_default() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let expr = parse_expr_ok(&gc, &mut sm, b"[a = 1, b] = c;");
if let Node::AssignmentExpression(asn) = expr {
if let Node::ArrayPattern(ap) = asn.left {
let elems: Vec<_> = ap.elements.iter().collect();
assert_eq!(elems.len(), 2);
assert!(
matches!(elems[0], Node::AssignmentPattern(_)),
"first element is AssignmentPattern"
);
assert!(matches!(elems[1], Node::Identifier(_)));
} else {
panic!("left must be ArrayPattern");
}
} else {
panic!("expected AssignmentExpression");
}
}
#[test]
fn object_destructure_shorthand() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let expr = parse_expr_ok(&gc, &mut sm, b"({a} = b);");
if let Node::AssignmentExpression(asn) = expr {
let op = gc.ctx().atom_table.bytes(asn.operator.get());
assert_eq!(op, b"=");
assert!(
matches!(asn.left, Node::ObjectPattern(_)),
"left is ObjectPattern, got {:?}",
asn.left.kind()
);
} else {
panic!("expected AssignmentExpression");
}
}
#[test]
fn object_destructure_cover_initializer() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let expr = parse_expr_ok(&gc, &mut sm, b"({a = 1} = b);");
if let Node::AssignmentExpression(asn) = expr {
if let Node::ObjectPattern(op) = asn.left {
let props: Vec<_> = op.properties.iter().collect();
assert_eq!(props.len(), 1);
if let Node::Property(p) = props[0] {
assert!(
matches!(p.value, Node::AssignmentPattern(_)),
"property value is AssignmentPattern"
);
} else {
panic!("expected Property");
}
} else {
panic!("left must be ObjectPattern");
}
} else {
panic!("expected AssignmentExpression");
}
}
#[test]
fn object_destructure_with_rest() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let expr = parse_expr_ok(&gc, &mut sm, b"({...r} = o);");
if let Node::AssignmentExpression(asn) = expr {
if let Node::ObjectPattern(op) = asn.left {
let props: Vec<_> = op.properties.iter().collect();
assert_eq!(props.len(), 1);
assert!(
matches!(props[0], Node::RestElement(_)),
"property is RestElement"
);
} else {
panic!("left must be ObjectPattern");
}
} else {
panic!("expected AssignmentExpression");
}
}
#[test]
fn nested_array_object_destructure() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let expr = parse_expr_ok(&gc, &mut sm, b"[{a}, [b]] = c;");
if let Node::AssignmentExpression(asn) = expr {
if let Node::ArrayPattern(ap) = asn.left {
let elems: Vec<_> = ap.elements.iter().collect();
assert_eq!(elems.len(), 2);
assert!(matches!(elems[0], Node::ObjectPattern(_)));
assert!(matches!(elems[1], Node::ArrayPattern(_)));
} else {
panic!("left must be ArrayPattern");
}
} else {
panic!("expected AssignmentExpression");
}
}
#[test]
fn return_outside_function_reports_error_but_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"return x;\n");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse_program().expect("return still parses");
assert!(
parser.error_count_pub() >= 1,
"top-level return reports an error"
);
if let Node::Program(p) = program {
let stmt = p.body.iter().next().expect("has statement");
assert!(
matches!(stmt, Node::ReturnStatement(_)),
"still produces a ReturnStatement"
);
} else {
panic!("expected Program");
}
}
#[test]
fn throw_newline_before_argument_fails() {
use hermes_ast::context::Context;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"throw\nx;\n");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
assert!(
parser.parse().is_none(),
"throw with newline before argument fails"
);
assert!(parser.error_count_pub() >= 1);
}
#[test]
fn labelled_statement_parses() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"foo: x;\n");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("labelled statement parses");
assert_eq!(parser.error_count_pub(), 0, "zero errors");
if let Node::Program(p) = program {
let stmt = p.body.iter().next().expect("has statement");
if let Node::LabeledStatement(ls) = stmt {
if let Node::Identifier(id) = ls.label {
assert_eq!(gc.ctx().atom_table.bytes(id.name.get()), b"foo");
} else {
panic!("label must be an Identifier");
}
assert!(
matches!(ls.body, Node::ExpressionStatement(_)),
"body is an ExpressionStatement"
);
} else {
panic!("expected LabeledStatement");
}
} else {
panic!("expected Program");
}
}
#[test]
fn binding_array_pattern_basic() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"[a, , ...b]");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let pat = parser
.parse_binding_pattern_for_test()
.expect("array binding pattern parses");
assert_eq!(parser.error_count_pub(), 0, "no errors");
let ap = match pat {
Node::ArrayPattern(ap) => ap,
other => panic!("expected ArrayPattern, got {:?}", other.kind()),
};
let elems: Vec<&Node> = ap.elements.iter().collect();
assert_eq!(elems.len(), 3, "three elements");
assert!(
matches!(elems[0], Node::Identifier(_)),
"elem0 = Identifier(a)"
);
assert!(matches!(elems[1], Node::Empty(_)), "elem1 = Empty hole");
match elems[2] {
Node::RestElement(r) => {
assert!(
matches!(r.argument, Node::Identifier(_)),
"rest arg = Identifier(b)"
);
}
other => panic!("elem2 should be RestElement, got {:?}", other.kind()),
}
}
#[test]
fn binding_array_pattern_default_initializer() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"[a = 1]");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let pat = parser
.parse_binding_pattern_for_test()
.expect("array binding pattern with default parses");
assert_eq!(parser.error_count_pub(), 0, "no errors");
let ap = match pat {
Node::ArrayPattern(ap) => ap,
other => panic!("expected ArrayPattern, got {:?}", other.kind()),
};
let elems: Vec<&Node> = ap.elements.iter().collect();
assert_eq!(elems.len(), 1, "one element");
match elems[0] {
Node::AssignmentPattern(asn) => {
assert!(
matches!(asn.left, Node::Identifier(_)),
"left = Identifier(a)"
);
assert!(
matches!(asn.right, Node::NumericLiteral(_)),
"right = NumericLiteral(1)"
);
}
other => {
panic!("elem0 should be AssignmentPattern, got {:?}", other.kind())
}
}
}
#[test]
fn binding_object_pattern_basic() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"{a, b: c, d = 1, ...r}");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let pat = parser
.parse_binding_pattern_for_test()
.expect("object binding pattern parses");
assert_eq!(parser.error_count_pub(), 0, "no errors");
let op = match pat {
Node::ObjectPattern(op) => op,
other => panic!("expected ObjectPattern, got {:?}", other.kind()),
};
let props: Vec<&Node> = op.properties.iter().collect();
assert_eq!(props.len(), 4, "four properties");
match props[0] {
Node::Property(p) => {
assert!(p.shorthand.get(), "a is shorthand");
assert!(!p.computed.get(), "a not computed");
assert!(matches!(p.key, Node::Identifier(_)), "key = a");
assert!(matches!(p.value, Node::Identifier(_)), "value = a");
}
other => panic!("prop0 should be Property, got {:?}", other.kind()),
}
match props[1] {
Node::Property(p) => {
assert!(!p.shorthand.get(), "b:c not shorthand");
assert!(matches!(p.key, Node::Identifier(_)), "key = b");
assert!(matches!(p.value, Node::Identifier(_)), "value = c");
}
other => panic!("prop1 should be Property, got {:?}", other.kind()),
}
match props[2] {
Node::Property(p) => {
assert!(p.shorthand.get(), "d = 1 is shorthand");
match p.value {
Node::AssignmentPattern(asn) => {
assert!(
matches!(asn.left, Node::Identifier(_)),
"left = d"
);
assert!(
matches!(asn.right, Node::NumericLiteral(_)),
"right = 1"
);
}
other => panic!(
"prop2 value should be AssignmentPattern, got {:?}",
other.kind()
),
}
}
other => panic!("prop2 should be Property, got {:?}", other.kind()),
}
match props[3] {
Node::RestElement(r) => {
assert!(
matches!(r.argument, Node::Identifier(_)),
"rest arg = r"
);
}
other => panic!("prop3 should be RestElement, got {:?}", other.kind()),
}
}
fn parse_with_collector<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
atoms: &hermes_atom_table::AtomTable,
src: &[u8],
) -> Option<&'gc hermes_ast::node::Node<'gc>> {
sm.set_handler(Box::new(hermes_support::diag::CollectingHandler::new()));
let buf_id = sm.add_buffer_bytes("input", src);
let lexer = crate::lexer::JSLexer::new(
buf_id,
sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(gc, lexer);
parser.parse()
}
#[test]
fn var_array_pattern_declaration() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"var [a] = b;");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("var [a] = b; parses");
assert_eq!(parser.error_count_pub(), 0);
let Node::Program(p) = program else {
panic!("expected Program")
};
let stmt = p.body.iter().next().expect("one statement");
let Node::VariableDeclaration(vd) = stmt else {
panic!("expected VariableDeclaration, got {:?}", stmt.kind())
};
assert_eq!(
gc.ctx().atom_table.bytes(vd.kind.get()),
b"var",
"kind should be 'var'"
);
let decl = vd.declarations.iter().next().expect("one declarator");
let Node::VariableDeclarator(d) = decl else {
panic!("expected VariableDeclarator")
};
assert!(
matches!(d.id, Node::ArrayPattern(_)),
"declarator id should be ArrayPattern, got {:?}",
d.id.kind()
);
assert!(d.init.is_some(), "declarator should have an initializer");
}
#[test]
fn const_without_initializer_errors() {
use hermes_ast::context::Context;
use hermes_support::diag::{CollectingHandler, DiagKind};
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _program = parse_with_collector(&gc, &mut sm, atoms, b"const x;");
let h = sm.handler_as::<CollectingHandler>().unwrap();
let errs: Vec<_> = h
.messages()
.iter()
.filter(|m| m.kind == DiagKind::Error)
.collect();
assert!(
errs.iter()
.any(|m| m.message == "missing initializer in const declaration"),
"expected 'missing initializer in const declaration', got {:?}",
errs.iter().map(|m| &m.message).collect::<Vec<_>>()
);
}
#[test]
fn destructuring_without_initializer_errors() {
use hermes_ast::context::Context;
use hermes_support::diag::{CollectingHandler, DiagKind};
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _program = parse_with_collector(&gc, &mut sm, atoms, b"var [a];");
let h = sm.handler_as::<CollectingHandler>().unwrap();
let errs: Vec<_> = h
.messages()
.iter()
.filter(|m| m.kind == DiagKind::Error)
.collect();
assert!(
errs.iter()
.any(|m| m.message == "destucturing declaration must be initialized"),
"expected 'destucturing declaration must be initialized', got {:?}",
errs.iter().map(|m| &m.message).collect::<Vec<_>>()
);
}
#[test]
fn let_declaration_kind() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"let x = 1;");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("let x = 1; parses");
assert_eq!(parser.error_count_pub(), 0);
let Node::Program(p) = program else {
panic!("expected Program")
};
let stmt = p.body.iter().next().expect("one statement");
let Node::VariableDeclaration(vd) = stmt else {
panic!("expected VariableDeclaration, got {:?}", stmt.kind())
};
assert_eq!(
gc.ctx().atom_table.bytes(vd.kind.get()),
b"let",
"kind should be 'let'"
);
}
#[test]
fn loose_let_is_expression_statement() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", b"let;\nlet x;");
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("let;\\nlet x; parses");
assert_eq!(parser.error_count_pub(), 0);
let Node::Program(p) = program else {
panic!("expected Program")
};
let mut it = p.body.iter();
let first = it.next().expect("first statement");
assert!(
matches!(first, Node::ExpressionStatement(_)),
"`let;` should be an ExpressionStatement, got {:?}",
first.kind()
);
let second = it.next().expect("second statement");
assert!(
matches!(second, Node::VariableDeclaration(_)),
"`let x;` should be a VariableDeclaration, got {:?}",
second.kind()
);
}
fn parse_first_stmt<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::Node<'gc> {
let buf_id = sm.add_buffer_bytes("input", src);
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(gc, lexer);
let program = parser.parse().expect("parse succeeded");
assert_eq!(parser.error_count_pub(), 0, "zero errors");
if let hermes_ast::node::Node::Program(p) = program {
return p.body.iter().next().expect("has statement");
}
panic!("expected Program");
}
#[test]
fn nested_block_statement() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"{{x;}}");
let Node::BlockStatement(outer) = stmt else {
panic!("expected BlockStatement, got {:?}", stmt.kind())
};
let inner = outer.body.iter().next().expect("one inner statement");
assert!(
matches!(inner, Node::BlockStatement(_)),
"inner statement should be BlockStatement, got {:?}",
inner.kind()
);
}
#[test]
fn if_with_else() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"if(a)b;else c;");
let Node::IfStatement(iff) = stmt else {
panic!("expected IfStatement, got {:?}", stmt.kind())
};
assert!(iff.alternate.is_some(), "alternate should be present");
}
#[test]
fn dangling_else_binds_to_inner_if() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"if(a)if(b)c;else d;");
let Node::IfStatement(outer) = stmt else {
panic!("expected IfStatement, got {:?}", stmt.kind())
};
assert!(
outer.alternate.is_none(),
"outer if should have no alternate"
);
let Node::IfStatement(inner) = outer.consequent else {
panic!(
"outer consequent should be IfStatement, got {:?}",
outer.consequent.kind()
)
};
assert!(
inner.alternate.is_some(),
"else should bind to the inner if"
);
}
#[test]
fn while_body_and_test_not_swapped() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"while(x)y;");
let Node::WhileStatement(w) = stmt else {
panic!("expected WhileStatement, got {:?}", stmt.kind())
};
let Node::Identifier(id) = w.test else {
panic!("test should be Identifier(x), got {:?}", w.test.kind())
};
assert_eq!(gc.ctx().atom_table.bytes(id.name.get()), b"x");
assert!(
matches!(w.body, Node::ExpressionStatement(_)),
"body should be ExpressionStatement, got {:?}",
w.body.kind()
);
}
#[test]
fn switch_duplicate_default_errors() {
use hermes_ast::context::Context;
use hermes_support::diag::{CollectingHandler, DiagKind};
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _program = parse_with_collector(
&gc,
&mut sm,
atoms,
b"switch(x){default:;default:;}",
);
let h = sm.handler_as::<CollectingHandler>().unwrap();
let errs: Vec<_> = h
.messages()
.iter()
.filter(|m| m.kind == DiagKind::Error)
.collect();
assert!(
errs.iter().any(|m| m.message
== "more than one 'default' clause in 'switch'"),
"expected duplicate-default error, got {:?}",
errs.iter().map(|m| &m.message).collect::<Vec<_>>()
);
}
#[test]
fn try_without_handler_errors() {
use hermes_ast::context::Context;
use hermes_support::diag::{CollectingHandler, DiagKind};
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _program = parse_with_collector(&gc, &mut sm, atoms, b"try{}");
let h = sm.handler_as::<CollectingHandler>().unwrap();
let errs: Vec<_> = h
.messages()
.iter()
.filter(|m| m.kind == DiagKind::Error)
.collect();
assert!(
errs.iter().any(|m| m
.message
.contains("'catch' or 'finally' expected")),
"expected catch/finally error, got {:?}",
errs.iter().map(|m| &m.message).collect::<Vec<_>>()
);
}
#[test]
fn for_in_basic() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"for(a in b)c;");
let Node::ForInStatement(f) = stmt else {
panic!("expected ForInStatement, got {:?}", stmt.kind())
};
let Node::Identifier(left) = f.left else {
panic!("left should be Identifier(a), got {:?}", f.left.kind())
};
assert_eq!(gc.ctx().atom_table.bytes(left.name.get()), b"a");
let Node::Identifier(right) = f.right else {
panic!("right should be Identifier(b), got {:?}", f.right.kind())
};
assert_eq!(gc.ctx().atom_table.bytes(right.name.get()), b"b");
}
#[test]
fn for_of_array_pattern_left() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"for([a] of b)c;");
let Node::ForOfStatement(f) = stmt else {
panic!("expected ForOfStatement, got {:?}", stmt.kind())
};
assert!(
matches!(f.left, Node::ArrayPattern(_)),
"left should be ArrayPattern, got {:?}",
f.left.kind()
);
assert!(!f.r#await.get(), "await should be false");
}
#[test]
fn for_in_multiple_bindings_errors() {
use hermes_ast::context::Context;
use hermes_support::diag::{CollectingHandler, DiagKind};
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _program =
parse_with_collector(&gc, &mut sm, atoms, b"for(var a, b in c);");
let h = sm.handler_as::<CollectingHandler>().unwrap();
let errs: Vec<_> = h
.messages()
.iter()
.filter(|m| m.kind == DiagKind::Error)
.collect();
assert!(
errs.iter().any(|m| m.message
== "Only one binding must be declared in a for-in/for-of loop"),
"expected single-binding error, got {:?}",
errs.iter().map(|m| &m.message).collect::<Vec<_>>()
);
}
#[test]
fn for_empty_head() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"for(;;);");
let Node::ForStatement(f) = stmt else {
panic!("expected ForStatement, got {:?}", stmt.kind())
};
assert!(f.init.is_none(), "init should be None");
assert!(f.test.is_none(), "test should be None");
assert!(f.update.is_none(), "update should be None");
assert!(
matches!(f.body, Node::EmptyStatement(_)),
"body should be EmptyStatement, got {:?}",
f.body.kind()
);
}
#[test]
fn for_c_style_var_init() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"for(var i=0;i<2;i++);");
let Node::ForStatement(f) = stmt else {
panic!("expected ForStatement, got {:?}", stmt.kind())
};
let init = f.init.expect("init should be Some");
assert!(
matches!(init, Node::VariableDeclaration(_)),
"init should be VariableDeclaration, got {:?}",
init.kind()
);
assert!(f.test.is_some(), "test should be Some");
assert!(f.update.is_some(), "update should be Some");
}
fn first_yield<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::YieldExpression<'gc> {
use hermes_ast::node::Node;
let decl = parse_first_stmt(gc, sm, src);
let Node::FunctionDeclaration(f) = decl else {
panic!("expected FunctionDeclaration, got {:?}", decl.kind())
};
let Node::BlockStatement(block) = f.body else {
panic!("expected BlockStatement body, got {:?}", f.body.kind())
};
let first = block.body.iter().next().expect("body has a statement");
let Node::ExpressionStatement(es) = first else {
panic!("expected ExpressionStatement, got {:?}", first.kind())
};
let Node::YieldExpression(y) = es.expression else {
panic!(
"expected YieldExpression, got {:?}",
es.expression.kind()
)
};
y
}
#[test]
fn yield_delegate() {
use hermes_ast::context::Context;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let y = first_yield(&gc, &mut sm, b"function* g(){ yield* a; }");
assert!(y.delegate.get(), "yield* should set delegate=true");
assert!(y.argument.is_some(), "yield* a has an argument");
}
#[test]
fn yield_no_argument() {
use hermes_ast::context::Context;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let y = first_yield(&gc, &mut sm, b"function* g(){ yield; }");
assert!(y.argument.is_none(), "bare yield has no argument");
assert!(!y.delegate.get(), "bare yield is not delegating");
}
#[test]
fn yield_with_argument() {
use hermes_ast::context::Context;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let y = first_yield(&gc, &mut sm, b"function* g(){ yield 1; }");
assert!(y.argument.is_some(), "yield 1 has an argument");
assert!(!y.delegate.get(), "yield 1 is not delegating");
}
fn first_arrow<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::ArrowFunctionExpression<'gc> {
use hermes_ast::node::Node;
let stmt = parse_first_stmt(gc, sm, src);
let Node::ExpressionStatement(es) = stmt else {
panic!("expected ExpressionStatement, got {:?}", stmt.kind())
};
let Node::ArrowFunctionExpression(a) = es.expression else {
panic!(
"expected ArrowFunctionExpression, got {:?}",
es.expression.kind()
)
};
a
}
#[test]
fn arrow_single_ident() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let a = first_arrow(&gc, &mut sm, b"a => a;");
assert!(a.expression.get(), "concise body is an expression");
assert!(!a.r#async.get(), "not async");
let params: Vec<_> = a.params.iter().collect();
assert_eq!(params.len(), 1, "one param");
assert!(
matches!(params[0], Node::Identifier(_)),
"param is Identifier, got {:?}",
params[0].kind()
);
}
#[test]
fn arrow_empty_params() {
use hermes_ast::context::Context;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let a = first_arrow(&gc, &mut sm, b"() => 0;");
assert_eq!(a.params.iter().count(), 0, "no params");
assert!(a.expression.get(), "concise body");
}
#[test]
fn arrow_rest_param() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let a = first_arrow(&gc, &mut sm, b"(a, ...b) => b;");
let params: Vec<_> = a.params.iter().collect();
assert_eq!(params.len(), 2, "two params");
assert!(
matches!(params[0], Node::Identifier(_)),
"first param Identifier, got {:?}",
params[0].kind()
);
assert!(
matches!(params[1], Node::RestElement(_)),
"second param RestElement, got {:?}",
params[1].kind()
);
}
#[test]
fn arrow_default_param() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let a = first_arrow(&gc, &mut sm, b"(a = 1) => a;");
let params: Vec<_> = a.params.iter().collect();
assert_eq!(params.len(), 1, "one param");
assert!(
matches!(params[0], Node::AssignmentPattern(_)),
"param AssignmentPattern, got {:?}",
params[0].kind()
);
}
#[test]
fn arrow_object_pattern_param() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let a = first_arrow(&gc, &mut sm, b"({x}) => x;");
let params: Vec<_> = a.params.iter().collect();
assert_eq!(params.len(), 1, "one param");
assert!(
matches!(params[0], Node::ObjectPattern(_)),
"param ObjectPattern, got {:?}",
params[0].kind()
);
}
#[test]
fn arrow_block_body() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let a = first_arrow(&gc, &mut sm, b"a => { return a; };");
assert!(!a.expression.get(), "block body is not an expression");
assert!(
matches!(a.body, Node::BlockStatement(_)),
"block body, got {:?}",
a.body.kind()
);
}
#[test]
fn arrow_async_single_ident() {
use hermes_ast::context::Context;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let a = first_arrow(&gc, &mut sm, b"async a => a;");
assert!(a.r#async.get(), "async arrow");
assert_eq!(a.params.iter().count(), 1, "one param");
}
#[test]
fn arrow_async_paren() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let a = first_arrow(&gc, &mut sm, b"async (a) => a;");
assert!(a.r#async.get(), "async arrow");
let params: Vec<_> = a.params.iter().collect();
assert_eq!(params.len(), 1, "one param");
assert!(
matches!(params[0], Node::Identifier(_)),
"param Identifier, got {:?}",
params[0].kind()
);
}
#[test]
fn paren_ident_not_arrow() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"(a);");
let Node::ExpressionStatement(es) = stmt else {
panic!("expected ExpressionStatement, got {:?}", stmt.kind())
};
assert!(
matches!(es.expression, Node::Identifier(_)),
"expression is Identifier, got {:?}",
es.expression.kind()
);
assert_eq!(
es.expression.metadata().parens.get(),
1,
"one paren recorded"
);
}
#[test]
fn paren_sequence_not_arrow() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"(a, b);");
let Node::ExpressionStatement(es) = stmt else {
panic!("expected ExpressionStatement, got {:?}", stmt.kind())
};
assert!(
matches!(es.expression, Node::SequenceExpression(_)),
"expression is SequenceExpression, got {:?}",
es.expression.kind()
);
}
#[test]
fn paren_trailing_comma_not_arrow() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_first_stmt(&gc, &mut sm, b"(a,);");
let Node::ExpressionStatement(es) = stmt else {
panic!("expected ExpressionStatement, got {:?}", stmt.kind())
};
let Node::SequenceExpression(seq) = es.expression else {
panic!(
"expected SequenceExpression, got {:?}",
es.expression.kind()
)
};
let elems: Vec<_> = seq.expressions.iter().collect();
assert_eq!(elems.len(), 2, "[a, CoverTrailingComma]");
assert!(
matches!(elems[1], Node::CoverTrailingComma(_)),
"last element CoverTrailingComma, got {:?}",
elems[1].kind()
);
}
#[cfg(test)]
fn return_arg_in_object_method<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::Node<'gc> {
use hermes_ast::node::Node;
let stmt = parse_first_stmt(gc, sm, src);
let Node::ExpressionStatement(es) = stmt else {
panic!("expected ExpressionStatement, got {:?}", stmt.kind())
};
let Node::ObjectExpression(obj) = es.expression else {
panic!("expected ObjectExpression, got {:?}", es.expression.kind())
};
let prop = obj.properties.iter().next().expect("has property");
let Node::Property(prop) = prop else {
panic!("expected Property, got {:?}", prop.kind())
};
let Node::FunctionExpression(func) = prop.value else {
panic!("expected FunctionExpression, got {:?}", prop.value.kind())
};
let Node::BlockStatement(block) = func.body else {
panic!("expected BlockStatement, got {:?}", func.body.kind())
};
let ret = block.body.iter().next().expect("has return statement");
let Node::ReturnStatement(ret) = ret else {
panic!("expected ReturnStatement, got {:?}", ret.kind())
};
ret.argument.expect("return has argument")
}
#[test]
fn super_member_dot() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let arg = return_arg_in_object_method(
&gc,
&mut sm,
b"({ m() { return super.x; } });",
);
let Node::MemberExpression(member) = arg else {
panic!("expected MemberExpression, got {:?}", arg.kind())
};
assert!(
matches!(member.object, Node::Super(_)),
"object is Super, got {:?}",
member.object.kind()
);
assert!(!member.computed.get(), "super.x is not computed");
}
#[test]
fn super_member_computed() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let arg = return_arg_in_object_method(
&gc,
&mut sm,
b"({ m() { return super['y']; } });",
);
let Node::MemberExpression(member) = arg else {
panic!("expected MemberExpression, got {:?}", arg.kind())
};
assert!(
matches!(member.object, Node::Super(_)),
"object is Super, got {:?}",
member.object.kind()
);
assert!(member.computed.get(), "super['y'] is computed");
}
fn assert_flow_parse_has_errors(src: &[u8], why: &str) {
assert_parse_has_errors_impl(src, why, true);
}
#[test]
fn flow_type_alias_number() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"type X = number;");
let Node::TypeAlias(alias) = stmt else {
panic!("expected TypeAlias, got {:?}", stmt.kind())
};
assert_eq!(ident_bytes(&gc, alias.id), b"X");
assert!(alias.type_parameters.is_none(), "no type parameters");
assert!(
matches!(alias.right, Node::NumberTypeAnnotation(_)),
"right is NumberTypeAnnotation, got {:?}",
alias.right.kind()
);
}
#[test]
fn flow_type_alias_string_literal() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"type X = 'hi';");
let Node::TypeAlias(alias) = stmt else {
panic!("expected TypeAlias, got {:?}", stmt.kind())
};
let Node::StringLiteralTypeAnnotation(lit) = alias.right else {
panic!(
"expected StringLiteralTypeAnnotation, got {:?}",
alias.right.kind()
)
};
assert_eq!(gc.ctx().atom_table.bytes(lit.value.get()), b"hi");
assert_eq!(gc.ctx().atom_table.bytes(lit.raw.get()), b"'hi'");
}
#[test]
fn flow_disabled_type_alias_is_plain_js() {
assert_parse_has_errors(
b"type X = number;",
"'type X' must not parse as a declaration without parse_flow",
);
}
#[test]
fn flow_disabled_type_is_plain_identifier() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"var type = 1;");
assert!(
matches!(stmt, Node::VariableDeclaration(_)),
"expected VariableDeclaration, got {:?}",
stmt.kind()
);
}
fn flow_enum<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> (&'gc hermes_ast::node::Node<'gc>, &'gc hermes_ast::node::Node<'gc>) {
let stmt = parse_one_stmt(gc, sm, src);
let hermes_ast::node::Node::EnumDeclaration(decl) = stmt else {
panic!("expected EnumDeclaration, got {:?}", stmt.kind())
};
(stmt, decl.body)
}
#[test]
fn flow_enum_empty() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let (_, body) = flow_enum(&gc, &mut sm, b"enum E {}");
let Node::EnumStringBody(b) = body else {
panic!("expected EnumStringBody, got {:?}", body.kind())
};
assert!(b.members.is_empty(), "no members");
assert!(!b.explicit_type.get(), "no explicit type");
assert!(!b.has_unknown_members.get(), "no unknown members");
assert_eq!(sm.error_count(), 0, "no errors");
}
#[test]
fn flow_enum_defaulted() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let (_, body) = flow_enum(&gc, &mut sm, b"enum E { A, B, C }");
let Node::EnumStringBody(b) = body else {
panic!("expected EnumStringBody, got {:?}", body.kind())
};
assert_eq!(b.members.iter().count(), 3, "three members");
for m in b.members.iter() {
assert!(
matches!(m, Node::EnumDefaultedMember(_)),
"member is defaulted, got {:?}",
m.kind()
);
}
assert_eq!(sm.error_count(), 0);
}
#[test]
fn flow_enum_number_typed() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let (_, body) =
flow_enum(&gc, &mut sm, b"enum N of number { A = 1, B = 2 }");
let Node::EnumNumberBody(b) = body else {
panic!("expected EnumNumberBody, got {:?}", body.kind())
};
assert!(b.explicit_type.get(), "explicit type");
assert_eq!(b.members.iter().count(), 2);
for m in b.members.iter() {
assert!(
matches!(m, Node::EnumNumberMember(_)),
"member is EnumNumberMember, got {:?}",
m.kind()
);
}
assert_eq!(sm.error_count(), 0);
}
#[test]
fn flow_enum_boolean_typed() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let (_, body) = flow_enum(
&gc,
&mut sm,
b"enum B of boolean { A = true, B = false }",
);
let Node::EnumBooleanBody(b) = body else {
panic!("expected EnumBooleanBody, got {:?}", body.kind())
};
assert!(b.explicit_type.get());
assert!(matches!(
b.members.iter().next().unwrap(),
Node::EnumBooleanMember(_)
));
assert_eq!(sm.error_count(), 0);
}
#[test]
fn flow_enum_symbol_body_has_no_explicit_type() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let (_, body) = flow_enum(&gc, &mut sm, b"enum Y of symbol { A, B }");
let Node::EnumSymbolBody(b) = body else {
panic!("expected EnumSymbolBody, got {:?}", body.kind())
};
assert_eq!(b.members.iter().count(), 2);
assert!(!b.has_unknown_members.get());
assert_eq!(sm.error_count(), 0);
}
#[test]
fn flow_enum_inexact() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let (_, body) =
flow_enum(&gc, &mut sm, b"enum E { A = 1, B = 2, ... }");
let Node::EnumNumberBody(b) = body else {
panic!("expected EnumNumberBody, got {:?}", body.kind())
};
assert!(b.has_unknown_members.get(), "has unknown members");
assert_eq!(b.members.iter().count(), 2, "two real members");
assert_eq!(sm.error_count(), 0);
}
#[test]
fn flow_enum_negative_member() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let (_, body) = flow_enum(&gc, &mut sm, b"enum E { A = -1, B = 2 }");
let Node::EnumNumberBody(b) = body else {
panic!("expected EnumNumberBody, got {:?}", body.kind())
};
let first = b.members.iter().next().unwrap();
let Node::EnumNumberMember(m) = first else {
panic!("expected EnumNumberMember, got {:?}", first.kind())
};
let Node::NumericLiteral(lit) = m.init else {
panic!("expected NumericLiteral, got {:?}", m.init.kind())
};
assert_eq!(lit.value.get(), -1.0, "negated literal");
assert_eq!(sm.error_count(), 0);
}
#[test]
fn flow_enum_kind_mismatch_errors() {
assert_flow_parse_has_errors(
b"enum N of number { A = 1, B = \"x\" }",
"string initializer in number enum must error",
);
}
#[test]
fn flow_enum_inconsistent_initializers_errors() {
assert_flow_parse_has_errors(
b"enum E { A = 1, B }",
"mixed initialized/defaulted members must error",
);
}
#[test]
fn flow_enum_defaulted_number_errors() {
assert_flow_parse_has_errors(
b"enum N of number { A, B }",
"number enums must use initializers",
);
}
#[test]
fn flow_disabled_enum_is_plain_identifier() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"var enum2 = 1;");
assert!(
matches!(stmt, Node::VariableDeclaration(_)),
"expected VariableDeclaration, got {:?}",
stmt.kind()
);
}
fn flow_ambiguous_expr<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::Node<'gc> {
let stmt = parse_one_stmt(gc, sm, src);
let hermes_ast::node::Node::ExpressionStatement(es) = stmt else {
panic!("expected ExpressionStatement, got {:?}", stmt.kind())
};
es.expression
}
#[test]
fn flow_as_expression() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_ambiguous(true);
let gc = ctx.lock();
let expr = flow_ambiguous_expr(&gc, &mut sm, b"x as number;");
let Node::AsExpression(as_expr) = expr else {
panic!("expected AsExpression, got {:?}", expr.kind())
};
assert_eq!(ident_bytes(&gc, as_expr.expression), b"x");
assert!(
matches!(as_expr.type_annotation, Node::NumberTypeAnnotation(_)),
"type is NumberTypeAnnotation, got {:?}",
as_expr.type_annotation.kind()
);
}
#[test]
fn flow_as_const_expression() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_ambiguous(true);
let gc = ctx.lock();
let expr = flow_ambiguous_expr(&gc, &mut sm, b"y as const;");
let Node::AsConstExpression(as_const) = expr else {
panic!("expected AsConstExpression, got {:?}", expr.kind())
};
assert_eq!(ident_bytes(&gc, as_const.expression), b"y");
}
#[test]
fn flow_call_type_args_vs_comparison() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
{
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_ambiguous(true);
let gc = ctx.lock();
let expr = flow_ambiguous_expr(&gc, &mut sm, b"f<T>();");
let Node::CallExpression(call) = expr else {
panic!("expected CallExpression, got {:?}", expr.kind())
};
assert!(
call.type_arguments.is_some(),
"f<T>() must keep type arguments"
);
}
{
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_ambiguous(true);
let gc = ctx.lock();
let expr = flow_ambiguous_expr(&gc, &mut sm, b"a < b;");
assert!(
matches!(expr, Node::BinaryExpression(_)),
"a < b must be a BinaryExpression, got {:?}",
expr.kind()
);
}
}
#[test]
fn flow_new_type_args() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
{
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_ambiguous(true);
let gc = ctx.lock();
let expr = flow_ambiguous_expr(&gc, &mut sm, b"new C<T>;");
let Node::NewExpression(new_expr) = expr else {
panic!("expected NewExpression, got {:?}", expr.kind())
};
assert!(
new_expr.type_arguments.is_some(),
"new C<T> must keep type arguments"
);
assert_eq!(new_expr.arguments.iter().count(), 0, "no args");
}
{
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_ambiguous(true);
let gc = ctx.lock();
let expr = flow_ambiguous_expr(&gc, &mut sm, b"new C<T>(x);");
let Node::NewExpression(new_expr) = expr else {
panic!("expected NewExpression, got {:?}", expr.kind())
};
assert!(new_expr.type_arguments.is_some(), "type args kept");
assert_eq!(new_expr.arguments.iter().count(), 1, "one arg");
}
}
#[test]
fn flow_optional_call_type_args() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_ambiguous(true);
let gc = ctx.lock();
let expr = flow_ambiguous_expr(&gc, &mut sm, b"obj?.foo<T>(x);");
let Node::OptionalCallExpression(call) = expr else {
panic!("expected OptionalCallExpression, got {:?}", expr.kind())
};
assert!(
call.type_arguments.is_some(),
"obj?.foo<T>(x) must keep type arguments"
);
assert_eq!(call.arguments.iter().count(), 1, "one argument");
}
#[test]
fn flow_ambiguous_off_keeps_comparison() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let expr = flow_ambiguous_expr(&gc, &mut sm, b"f < T > (g);");
assert!(
matches!(expr, Node::BinaryExpression(_)),
"without ambiguous flag, f<T>(g) is a comparison, got {:?}",
expr.kind()
);
}
fn flow_const_init<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::Node<'gc> {
let stmt = parse_one_stmt(gc, sm, src);
let hermes_ast::node::Node::VariableDeclaration(vd) = stmt else {
panic!("expected VariableDeclaration, got {:?}", stmt.kind())
};
let decl = vd.declarations.iter().next().expect("one declarator");
let hermes_ast::node::Node::VariableDeclarator(d) = decl else {
panic!("expected VariableDeclarator, got {:?}", decl.kind())
};
d.init.expect("declarator has an init")
}
#[test]
fn flow_typed_arrow_full() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init =
flow_const_init(&gc, &mut sm, b"const f = <T>(x: T): T => x;");
let Node::ArrowFunctionExpression(arrow) = init else {
panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
};
assert!(arrow.type_parameters.is_some(), "has type parameters");
assert!(arrow.return_type.is_some(), "has return type");
assert!(arrow.predicate.is_none(), "no predicate");
assert!(arrow.expression.get(), "concise (expression) body");
assert!(!arrow.r#async.get(), "not async");
}
#[test]
fn flow_typed_arrow_predicate() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init = flow_const_init(
&gc,
&mut sm,
b"const g = (x): x is number => true;",
);
let Node::ArrowFunctionExpression(arrow) = init else {
panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
};
assert!(arrow.type_parameters.is_none(), "no type parameters");
let rt = arrow.return_type.expect("predicate sets return_type");
let Node::TypeAnnotation(ta) = rt else {
panic!("return_type wraps a TypeAnnotation, got {:?}", rt.kind())
};
assert!(
matches!(ta.type_annotation, Node::TypePredicate(_)),
"return type is a TypePredicate, got {:?}",
ta.type_annotation.kind()
);
assert!(arrow.predicate.is_none(), "no %checks predicate");
}
#[test]
fn flow_typed_arrow_void_block() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init = flow_const_init(&gc, &mut sm, b"const e = (): void => {};");
let Node::ArrowFunctionExpression(arrow) = init else {
panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
};
assert!(arrow.return_type.is_some(), "has return type");
assert!(!arrow.expression.get(), "block body");
}
#[test]
fn flow_typed_async_arrow() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init =
flow_const_init(&gc, &mut sm, b"const f = async <T>(x: T): T => x;");
let Node::ArrowFunctionExpression(arrow) = init else {
panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
};
assert!(arrow.r#async.get(), "is async");
assert!(arrow.type_parameters.is_some(), "has type parameters");
assert!(arrow.return_type.is_some(), "has return type");
}
#[test]
fn flow_typed_async_arrow_no_generics() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init =
flow_const_init(&gc, &mut sm, b"const g = async (x: number) => x;");
let Node::ArrowFunctionExpression(arrow) = init else {
panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
};
assert!(arrow.r#async.get(), "is async");
assert!(arrow.type_parameters.is_none(), "no type parameters");
}
#[test]
fn flow_plain_async_arrow_still_works() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init = flow_const_init(&gc, &mut sm, b"const g = async (x) => x;");
let Node::ArrowFunctionExpression(arrow) = init else {
panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
};
assert!(arrow.r#async.get(), "is async");
assert!(arrow.type_parameters.is_none(), "no type parameters");
assert!(arrow.return_type.is_none(), "no return type");
}
#[test]
fn flow_async_as_identifier() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init = flow_const_init(&gc, &mut sm, b"const a = async;");
assert!(
matches!(init, Node::Identifier(_)),
"bare `async` is an Identifier, got {:?}",
init.kind()
);
}
#[test]
fn flow_type_cast() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init = flow_const_init(&gc, &mut sm, b"const a = (x: number);");
let Node::TypeCastExpression(cast) = init else {
panic!("expected TypeCastExpression, got {:?}", init.kind())
};
assert_eq!(ident_bytes(&gc, cast.expression), b"x");
assert!(
matches!(cast.type_annotation, Node::TypeAnnotation(_)),
"type is wrapped in a TypeAnnotation, got {:?}",
cast.type_annotation.kind()
);
}
#[test]
fn flow_type_cast_object() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init = flow_const_init(&gc, &mut sm, b"const b = ({p}: O);");
let Node::TypeCastExpression(cast) = init else {
panic!("expected TypeCastExpression, got {:?}", init.kind())
};
assert!(
matches!(cast.expression, Node::ObjectExpression(_)),
"expr is ObjectExpression, got {:?}",
cast.expression.kind()
);
}
#[test]
fn flow_conditional_consequent_cover() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init =
flow_const_init(&gc, &mut sm, b"const c = cond ? (a: T) => a : b;");
let Node::ConditionalExpression(cond) = init else {
panic!("expected ConditionalExpression, got {:?}", init.kind())
};
assert!(
matches!(cond.consequent, Node::ArrowFunctionExpression(_)),
"consequent is a typed arrow, got {:?}",
cond.consequent.kind()
);
assert_eq!(ident_bytes(&gc, cond.alternate), b"b");
}
#[test]
fn flow_typed_arrow_disambiguation_comparison() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init =
flow_const_init(&gc, &mut sm, b"const r = (a < b, c > (d));");
assert!(
matches!(init, Node::SequenceExpression(_)),
"comparison sequence, got {:?}",
init.kind()
);
}
#[test]
fn flow_typed_arrow_lt_not_arrow() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let init = flow_const_init(&gc, &mut sm, b"const r = a < b;");
assert!(
matches!(init, Node::BinaryExpression(_)),
"a < b is a comparison, got {:?}",
init.kind()
);
}
fn flow_alias_right<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::Node<'gc> {
let stmt = parse_one_stmt(gc, sm, src);
let hermes_ast::node::Node::TypeAlias(alias) = stmt else {
panic!("expected TypeAlias, got {:?}", stmt.kind())
};
alias.right
}
fn assert_generic_named<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
node: &hermes_ast::node::Node<'gc>,
name: &[u8],
) {
use hermes_ast::node::Node;
let Node::GenericTypeAnnotation(g) = node else {
panic!("expected GenericTypeAnnotation, got {:?}", node.kind())
};
assert!(g.type_parameters.is_none(), "no type args");
assert_eq!(ident_bytes(gc, g.id), name);
}
#[test]
fn flow_union_intersection_types() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = X | Y | Z;");
let Node::UnionTypeAnnotation(u) = ty else {
panic!("expected UnionTypeAnnotation, got {:?}", ty.kind())
};
let members: Vec<_> = u.types.iter().collect();
assert_eq!(members.len(), 3);
assert_generic_named(&gc, members[0], b"X");
assert_generic_named(&gc, members[2], b"Z");
let ty = flow_alias_right(&gc, &mut sm, b"type A = | X | Y;");
let Node::UnionTypeAnnotation(u) = ty else {
panic!("expected UnionTypeAnnotation, got {:?}", ty.kind())
};
assert_eq!(u.types.iter().count(), 2);
let ty = flow_alias_right(&gc, &mut sm, b"type A = | X;");
assert_generic_named(&gc, ty, b"X");
let ty = flow_alias_right(&gc, &mut sm, b"type A = X & Y | Z;");
let Node::UnionTypeAnnotation(u) = ty else {
panic!("expected UnionTypeAnnotation, got {:?}", ty.kind())
};
let members: Vec<_> = u.types.iter().collect();
assert_eq!(members.len(), 2);
let Node::IntersectionTypeAnnotation(i) = members[0] else {
panic!(
"expected IntersectionTypeAnnotation, got {:?}",
members[0].kind()
)
};
assert_eq!(i.types.iter().count(), 2);
assert_generic_named(&gc, members[1], b"Z");
}
#[test]
fn flow_nullable_nesting() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = ??X;");
let Node::NullableTypeAnnotation(outer) = ty else {
panic!("expected NullableTypeAnnotation, got {:?}", ty.kind())
};
let Node::NullableTypeAnnotation(inner) = outer.type_annotation else {
panic!(
"expected nested NullableTypeAnnotation, got {:?}",
outer.type_annotation.kind()
)
};
assert_generic_named(&gc, inner.type_annotation, b"X");
}
#[test]
fn flow_postfix_types() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = X[][];");
let Node::ArrayTypeAnnotation(outer) = ty else {
panic!("expected ArrayTypeAnnotation, got {:?}", ty.kind())
};
let Node::ArrayTypeAnnotation(inner) = outer.element_type else {
panic!(
"expected nested ArrayTypeAnnotation, got {:?}",
outer.element_type.kind()
)
};
assert_generic_named(&gc, inner.element_type, b"X");
let ty = flow_alias_right(&gc, &mut sm, b"type A = X[K];");
let Node::IndexedAccessType(idx) = ty else {
panic!("expected IndexedAccessType, got {:?}", ty.kind())
};
assert_generic_named(&gc, idx.object_type, b"X");
assert_generic_named(&gc, idx.index_type, b"K");
let ty = flow_alias_right(&gc, &mut sm, b"type A = X?.[K];");
let Node::OptionalIndexedAccessType(opt) = ty else {
panic!("expected OptionalIndexedAccessType, got {:?}", ty.kind())
};
assert!(opt.optional.get(), "?.[ access is optional");
let ty = flow_alias_right(&gc, &mut sm, b"type A = X?.[A][B];");
let Node::OptionalIndexedAccessType(outer) = ty else {
panic!("expected OptionalIndexedAccessType, got {:?}", ty.kind())
};
assert!(!outer.optional.get(), "[B] itself is not optional");
let Node::OptionalIndexedAccessType(inner) = outer.object_type else {
panic!(
"expected inner OptionalIndexedAccessType, got {:?}",
outer.object_type.kind()
)
};
assert!(inner.optional.get(), "?.[A] is optional");
}
#[test]
fn flow_generic_types() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = Foo.Bar.Baz;");
let Node::GenericTypeAnnotation(g) = ty else {
panic!("expected GenericTypeAnnotation, got {:?}", ty.kind())
};
assert!(g.type_parameters.is_none());
let Node::QualifiedTypeIdentifier(outer) = g.id else {
panic!("expected QualifiedTypeIdentifier, got {:?}", g.id.kind())
};
assert_eq!(ident_bytes(&gc, outer.id), b"Baz");
let Node::QualifiedTypeIdentifier(inner) = outer.qualification else {
panic!(
"expected inner QualifiedTypeIdentifier, got {:?}",
outer.qualification.kind()
)
};
assert_eq!(ident_bytes(&gc, inner.qualification), b"Foo");
assert_eq!(ident_bytes(&gc, inner.id), b"Bar");
let ty = flow_alias_right(&gc, &mut sm, b"type A = Foo<>;");
let Node::GenericTypeAnnotation(g) = ty else {
panic!("expected GenericTypeAnnotation, got {:?}", ty.kind())
};
let args = g.type_parameters.expect("has type args");
let Node::TypeParameterInstantiation(inst) = args else {
panic!("expected TypeParameterInstantiation, got {:?}", args.kind())
};
assert_eq!(inst.params.iter().count(), 0, "`Foo<>` has no args");
let ty = flow_alias_right(&gc, &mut sm, b"type A = Foo<X, Y>;");
let Node::GenericTypeAnnotation(g) = ty else {
panic!("expected GenericTypeAnnotation, got {:?}", ty.kind())
};
let Node::TypeParameterInstantiation(inst) =
g.type_parameters.expect("has type args")
else {
panic!("expected TypeParameterInstantiation")
};
assert_eq!(inst.params.iter().count(), 2);
}
#[test]
fn flow_nested_generic_type_args() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = Foo<Bar<Baz<U>>>;");
let mut node = ty;
for name in [&b"Foo"[..], b"Bar", b"Baz"] {
let Node::GenericTypeAnnotation(g) = node else {
panic!("expected GenericTypeAnnotation, got {:?}", node.kind())
};
assert_eq!(ident_bytes(&gc, g.id), name);
let Node::TypeParameterInstantiation(inst) =
g.type_parameters.expect("has type args")
else {
panic!("expected TypeParameterInstantiation")
};
assert_eq!(inst.params.iter().count(), 1, "one arg at each level");
node = inst.params.iter().next().unwrap();
}
assert_generic_named(&gc, node, b"U");
}
#[test]
fn flow_typeof_types() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = typeof x.y;");
let Node::TypeofTypeAnnotation(t) = ty else {
panic!("expected TypeofTypeAnnotation, got {:?}", ty.kind())
};
assert!(t.type_arguments.is_none());
let Node::QualifiedTypeofIdentifier(q) = t.argument else {
panic!(
"expected QualifiedTypeofIdentifier, got {:?}",
t.argument.kind()
)
};
assert_eq!(ident_bytes(&gc, q.qualification), b"x");
assert_eq!(ident_bytes(&gc, q.id), b"y");
let ty = flow_alias_right(&gc, &mut sm, b"type A = typeof (x);");
let Node::TypeofTypeAnnotation(t) = ty else {
panic!("expected TypeofTypeAnnotation, got {:?}", ty.kind())
};
assert!(matches!(t.argument, Node::Identifier(_)));
assert_eq!(t.argument.metadata().parens.get(), 1, "one paren recorded");
let ty = flow_alias_right(&gc, &mut sm, b"type A = typeof x<Y>;");
let Node::TypeofTypeAnnotation(t) = ty else {
panic!("expected TypeofTypeAnnotation, got {:?}", ty.kind())
};
let Node::TypeParameterInstantiation(inst) =
t.type_arguments.expect("has type args")
else {
panic!("expected TypeParameterInstantiation")
};
assert_eq!(inst.params.iter().count(), 1);
}
#[test]
fn flow_tuple_types() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = [X, Y];");
let Node::TupleTypeAnnotation(t) = ty else {
panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
};
assert!(!t.inexact.get());
let elems: Vec<_> = t.element_types.iter().collect();
assert_eq!(elems.len(), 2);
assert_generic_named(&gc, elems[0], b"X");
let ty = flow_alias_right(&gc, &mut sm, b"type A = [a: X, b?: Y];");
let Node::TupleTypeAnnotation(t) = ty else {
panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
};
let elems: Vec<_> = t.element_types.iter().collect();
assert_eq!(elems.len(), 2);
let Node::TupleTypeLabeledElement(first) = elems[0] else {
panic!(
"expected TupleTypeLabeledElement, got {:?}",
elems[0].kind()
)
};
assert_eq!(ident_bytes(&gc, first.label), b"a");
assert!(!first.optional.get());
assert!(first.variance.is_none());
let Node::TupleTypeLabeledElement(second) = elems[1] else {
panic!(
"expected TupleTypeLabeledElement, got {:?}",
elems[1].kind()
)
};
assert!(second.optional.get(), "b? is optional");
let ty = flow_alias_right(&gc, &mut sm, b"type A = [X, ...Y];");
let Node::TupleTypeAnnotation(t) = ty else {
panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
};
let elems: Vec<_> = t.element_types.iter().collect();
let Node::TupleTypeSpreadElement(spread) = elems[1] else {
panic!(
"expected TupleTypeSpreadElement, got {:?}",
elems[1].kind()
)
};
assert!(spread.label.is_none());
assert_generic_named(&gc, spread.type_annotation, b"Y");
let ty = flow_alias_right(&gc, &mut sm, b"type A = [...rest: Y];");
let Node::TupleTypeAnnotation(t) = ty else {
panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
};
let elems: Vec<_> = t.element_types.iter().collect();
let Node::TupleTypeSpreadElement(spread) = elems[0] else {
panic!(
"expected TupleTypeSpreadElement, got {:?}",
elems[0].kind()
)
};
assert_eq!(ident_bytes(&gc, spread.label.expect("labeled")), b"rest");
let ty = flow_alias_right(&gc, &mut sm, b"type A = [+a: X, -b: Y];");
let Node::TupleTypeAnnotation(t) = ty else {
panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
};
let elems: Vec<_> = t.element_types.iter().collect();
let Node::TupleTypeLabeledElement(first) = elems[0] else {
panic!("expected TupleTypeLabeledElement")
};
let Node::Variance(v) = first.variance.expect("has variance") else {
panic!("expected Variance")
};
assert_eq!(gc.ctx().atom_table.bytes(v.kind.get()), b"plus");
let Node::TupleTypeLabeledElement(second) = elems[1] else {
panic!("expected TupleTypeLabeledElement")
};
let Node::Variance(v) = second.variance.expect("has variance") else {
panic!("expected Variance")
};
assert_eq!(gc.ctx().atom_table.bytes(v.kind.get()), b"minus");
let ty = flow_alias_right(&gc, &mut sm, b"type A = [X, ...];");
let Node::TupleTypeAnnotation(t) = ty else {
panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
};
assert!(t.inexact.get(), "trailing ... makes the tuple inexact");
assert_eq!(t.element_types.iter().count(), 1);
let ty = flow_alias_right(&gc, &mut sm, b"type A = [];");
let Node::TupleTypeAnnotation(t) = ty else {
panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
};
assert_eq!(t.element_types.iter().count(), 0);
assert!(!t.inexact.get());
}
#[test]
fn flow_tuple_errors() {
use hermes_ast::context::Context;
use hermes_support::diag::{CollectingHandler, DiagKind};
use hermes_support::manager::SourceErrorManager;
{
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _ = parse_with_collector(&gc, &mut sm, atoms, b"type A = [X, ..., Y];");
let h = sm.handler_as::<CollectingHandler>().unwrap();
assert!(
h.messages().iter().any(|m| m.kind == DiagKind::Error
&& m.message
== "trailing commas after inexact tuple types are not allowed"),
"got {:?}",
h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
);
}
{
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _ = parse_with_collector(&gc, &mut sm, atoms, b"type A = [+X];");
let h = sm.handler_as::<CollectingHandler>().unwrap();
assert!(
h.messages().iter().any(|m| m.kind == DiagKind::Error
&& m.message
== "Variance can only be used with labeled tuple elements"),
"got {:?}",
h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
);
}
{
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _ = parse_with_collector(&gc, &mut sm, atoms, b"type A = [1: X];");
let h = sm.handler_as::<CollectingHandler>().unwrap();
assert!(
h.messages().iter().any(|m| m.kind == DiagKind::Error
&& m.message == "identifier expected"),
"got {:?}",
h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
);
}
}
#[test]
fn flow_keyof_type() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = keyof X;");
let Node::KeyofTypeAnnotation(k) = ty else {
panic!("expected KeyofTypeAnnotation, got {:?}", ty.kind())
};
assert_generic_named(&gc, k.argument, b"X");
}
#[test]
fn flow_conditional_type() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty =
flow_alias_right(&gc, &mut sm, b"type T = X extends Y ? A : B;");
let Node::ConditionalTypeAnnotation(c) = ty else {
panic!("expected ConditionalTypeAnnotation, got {:?}", ty.kind())
};
assert_generic_named(&gc, c.check_type, b"X");
assert_generic_named(&gc, c.extends_type, b"Y");
assert_generic_named(&gc, c.true_type, b"A");
assert_generic_named(&gc, c.false_type, b"B");
}
#[test]
fn flow_infer_type_bound_and_backtrack() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
fn infer_param<'gc>(node: &'gc Node<'gc>) -> &'gc hermes_ast::node::TypeParameter<'gc> {
let Node::InferTypeAnnotation(i) = node else {
panic!("expected InferTypeAnnotation, got {:?}", node.kind())
};
let Node::TypeParameter(p) = i.type_parameter else {
panic!(
"expected TypeParameter, got {:?}",
i.type_parameter.kind()
)
};
p
}
let ty = flow_alias_right(
&gc,
&mut sm,
b"type T = X extends infer U ? U : never;",
);
let Node::ConditionalTypeAnnotation(c) = ty else {
panic!("expected ConditionalTypeAnnotation, got {:?}", ty.kind())
};
let p = infer_param(c.extends_type);
assert_eq!(gc.ctx().atom_table.bytes(p.name.get()), b"U");
assert!(p.bound.is_none());
assert!(p.uses_extends_bound.get());
let ty = flow_alias_right(
&gc,
&mut sm,
b"type T = X extends infer U extends V ? U : never;",
);
let Node::ConditionalTypeAnnotation(c) = ty else {
panic!("expected ConditionalTypeAnnotation, got {:?}", ty.kind())
};
let p = infer_param(c.extends_type);
let bound = p.bound.expect("bound kept");
assert_generic_named(&gc, bound, b"V");
let ty = flow_alias_right(
&gc,
&mut sm,
b"type T = infer U extends V ? A : B;",
);
let Node::ConditionalTypeAnnotation(c) = ty else {
panic!("expected ConditionalTypeAnnotation, got {:?}", ty.kind())
};
let p = infer_param(c.check_type);
assert!(p.bound.is_none(), "bound backtracked away");
assert_generic_named(&gc, c.extends_type, b"V");
let ty =
flow_alias_right(&gc, &mut sm, b"type T = infer U extends V;");
let p = infer_param(ty);
assert!(p.bound.is_some(), "no `?` follows — bound kept");
}
#[test]
fn flow_negative_literal_types() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = -3;");
let Node::NumberLiteralTypeAnnotation(n) = ty else {
panic!(
"expected NumberLiteralTypeAnnotation, got {:?}",
ty.kind()
)
};
assert_eq!(n.value.get(), -3.0);
assert_eq!(gc.ctx().atom_table.bytes(n.raw.get()), b"-3");
let ty = flow_alias_right(&gc, &mut sm, b"type A = -2n;");
let Node::BigIntLiteralTypeAnnotation(b) = ty else {
panic!(
"expected BigIntLiteralTypeAnnotation, got {:?}",
ty.kind()
)
};
assert_eq!(gc.ctx().atom_table.bytes(b.raw.get()), b"-2n");
}
fn as_fta<'gc, 'n>(
node: &'n hermes_ast::node::Node<'gc>,
) -> &'n hermes_ast::node::FunctionTypeAnnotation<'gc> {
let hermes_ast::node::Node::FunctionTypeAnnotation(fta) = node else {
panic!("expected FunctionTypeAnnotation, got {:?}", node.kind())
};
fta
}
fn as_ftp<'gc, 'n>(
node: &'n hermes_ast::node::Node<'gc>,
) -> &'n hermes_ast::node::FunctionTypeParam<'gc> {
let hermes_ast::node::Node::FunctionTypeParam(ftp) = node else {
panic!("expected FunctionTypeParam, got {:?}", node.kind())
};
ftp
}
#[test]
fn flow_function_type_full_shape() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(
&gc,
&mut sm,
b"type A = <T>(this: X, a: B, c?: D, ...rest: E) => R;",
);
let fta = as_fta(ty);
let tp = fta.type_parameters.expect("has type params");
let Node::TypeParameterDeclaration(tpd) = tp else {
panic!("expected TypeParameterDeclaration, got {:?}", tp.kind())
};
assert_eq!(tpd.params.iter().count(), 1);
let this_param = as_ftp(fta.this.expect("has this constraint"));
assert!(this_param.name.is_none(), "this constraint has no name");
assert_generic_named(&gc, this_param.type_annotation, b"X");
let params: Vec<_> = fta.params.iter().collect();
assert_eq!(params.len(), 2);
let a = as_ftp(params[0]);
assert_eq!(ident_bytes(&gc, a.name.expect("a named")), b"a");
assert!(!a.optional.get());
let c = as_ftp(params[1]);
assert_eq!(ident_bytes(&gc, c.name.expect("c named")), b"c");
assert!(c.optional.get());
let rest = as_ftp(fta.rest.expect("has rest"));
assert_eq!(ident_bytes(&gc, rest.name.expect("rest named")), b"rest");
assert_generic_named(&gc, fta.return_type, b"R");
let ty = flow_alias_right(&gc, &mut sm, b"type C = (number) => string;");
let fta = as_fta(ty);
assert!(fta.this.is_none());
assert!(fta.rest.is_none());
assert!(fta.type_parameters.is_none());
let params: Vec<_> = fta.params.iter().collect();
assert_eq!(params.len(), 1);
let p = as_ftp(params[0]);
assert!(p.name.is_none(), "bare type param has no name");
assert!(matches!(p.type_annotation, Node::NumberTypeAnnotation(_)));
}
#[test]
fn flow_group_vs_function_type() {
use hermes_ast::context::Context;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = (X);");
assert_generic_named(&gc, ty, b"X");
assert_eq!(ty.metadata().parens.get(), 1, "group bumps parens");
let ty = flow_alias_right(&gc, &mut sm, b"type B = (x: X) => R;");
let fta = as_fta(ty);
let params: Vec<_> = fta.params.iter().collect();
assert_eq!(ident_bytes(&gc, as_ftp(params[0]).name.unwrap()), b"x");
let ty = flow_alias_right(&gc, &mut sm, b"type C = (X) => R;");
let fta = as_fta(ty);
assert!(as_ftp(fta.params.iter().next().unwrap()).name.is_none());
let ty = flow_alias_right(&gc, &mut sm, b"type D = () => R;");
assert!(as_fta(ty).params.is_empty());
}
#[test]
fn flow_anon_function_type() {
use hermes_ast::context::Context;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(&gc, &mut sm, b"type A = T => U => V;");
let outer = as_fta(ty);
let param = as_ftp(outer.params.iter().next().expect("one param"));
assert!(param.name.is_none());
assert_generic_named(&gc, param.type_annotation, b"T");
let inner = as_fta(outer.return_type);
assert_generic_named(&gc, inner.return_type, b"V");
}
#[test]
fn flow_object_type_properties() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let ty = flow_alias_right(
&gc,
&mut sm,
b"type A = { x: B, y?: C, m(): D, get g(): E, set s(v: F): void, +ro: G };",
);
let Node::ObjectTypeAnnotation(obj) = ty else {
panic!("expected ObjectTypeAnnotation, got {:?}", ty.kind())
};
assert!(!obj.exact.get());
assert!(!obj.inexact.get());
assert!(obj.indexers.is_empty());
assert!(obj.call_properties.is_empty());
assert!(obj.internal_slots.is_empty());
let props: Vec<_> = obj.properties.iter().collect();
assert_eq!(props.len(), 6);
let prop = |i: usize| -> &hermes_ast::node::ObjectTypeProperty<'_> {
let Node::ObjectTypeProperty(p) = props[i] else {
panic!("expected ObjectTypeProperty, got {:?}", props[i].kind())
};
p
};
let x = prop(0);
assert_eq!(ident_bytes(&gc, x.key), b"x");
assert!(!x.method.get() && !x.optional.get());
assert!(!x.r#static.get() && !x.proto.get());
assert!(x.variance.is_none());
assert_eq!(gc.ctx().atom_table.bytes(x.kind.get()), b"init");
let y = prop(1);
assert!(y.optional.get());
let m = prop(2);
assert!(m.method.get());
assert_generic_named(&gc, as_fta(m.value).return_type, b"D");
assert_eq!(gc.ctx().atom_table.bytes(m.kind.get()), b"init");
let g = prop(3);
assert!(!g.method.get());
assert_eq!(ident_bytes(&gc, g.key), b"g");
assert_eq!(gc.ctx().atom_table.bytes(g.kind.get()), b"get");
let s = prop(4);
assert_eq!(gc.ctx().atom_table.bytes(s.kind.get()), b"set");
assert_eq!(as_fta(s.value).params.iter().count(), 1);
let ro = prop(5);
let variance = ro.variance.expect("has variance");
let Node::Variance(v) = variance else {
panic!("expected Variance, got {:?}", variance.kind())
};
assert_eq!(gc.ctx().atom_table.bytes(v.kind.get()), b"plus");
}
#[test]
fn flow_object_type_member_families() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let obj_of = |sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8]| {
let ty = flow_alias_right(&gc, sm, src);
let Node::ObjectTypeAnnotation(obj) = ty else {
panic!("expected ObjectTypeAnnotation, got {:?}", ty.kind())
};
obj
};
let obj = obj_of(&mut sm, b"type A = { [k: string]: V };");
let idx = obj.indexers.iter().next().expect("one indexer");
let Node::ObjectTypeIndexer(idx) = idx else {
panic!("expected ObjectTypeIndexer, got {:?}", idx.kind())
};
assert_eq!(ident_bytes(&gc, idx.id.expect("has id")), b"k");
assert!(matches!(idx.key, Node::StringTypeAnnotation(_)));
assert!(!idx.r#static.get());
let obj = obj_of(&mut sm, b"type B = { [K]: V };");
let idx = obj.indexers.iter().next().expect("one indexer");
let Node::ObjectTypeIndexer(idx) = idx else {
panic!("expected ObjectTypeIndexer, got {:?}", idx.kind())
};
assert!(idx.id.is_none());
assert_generic_named(&gc, idx.key, b"K");
for (src, sigil) in [
(b"type C = { [K in T]: V };".as_slice(), None),
(b"type D = { [K in T]?: V };", Some(b"Optional".as_slice())),
(b"type E = { [K in T]+?: V };", Some(b"PlusOptional")),
(b"type F = { [K in T]-?: V };", Some(b"MinusOptional")),
] {
let obj = obj_of(&mut sm, src);
let prop = obj.properties.iter().next().expect("one property");
let Node::ObjectTypeMappedTypeProperty(mapped) = prop else {
panic!(
"expected ObjectTypeMappedTypeProperty, got {:?}",
prop.kind()
)
};
let Node::TypeParameter(key_tparam) = mapped.key_tparam else {
panic!("expected TypeParameter")
};
assert_eq!(
gc.ctx().atom_table.bytes(key_tparam.name.get()),
b"K"
);
assert_generic_named(&gc, mapped.source_type, b"T");
assert_generic_named(&gc, mapped.prop_type, b"V");
match sigil {
None => assert_eq!(
mapped.optional.get(),
hermes_atom_table::INVALID_ATOM_BYTES,
"no sigil dumps as null"
),
Some(s) => assert_eq!(
gc.ctx().atom_table.bytes(mapped.optional.get()),
s
),
}
}
let obj = obj_of(&mut sm, b"type G = { +[K in T]: V };");
let prop = obj.properties.iter().next().expect("one property");
let Node::ObjectTypeMappedTypeProperty(mapped) = prop else {
panic!("expected ObjectTypeMappedTypeProperty")
};
assert!(mapped.variance.is_some());
let obj =
obj_of(&mut sm, b"type H = { (x: A): R, [[slot]]: T, ...S };");
let call = obj.call_properties.iter().next().expect("one call");
let Node::ObjectTypeCallProperty(call) = call else {
panic!("expected ObjectTypeCallProperty, got {:?}", call.kind())
};
assert_eq!(as_fta(call.value).params.iter().count(), 1);
let slot = obj.internal_slots.iter().next().expect("one slot");
let Node::ObjectTypeInternalSlot(slot) = slot else {
panic!("expected ObjectTypeInternalSlot, got {:?}", slot.kind())
};
assert_eq!(ident_bytes(&gc, slot.id), b"slot");
assert!(!slot.method.get() && !slot.optional.get());
let spread = obj.properties.iter().next().expect("one spread");
let Node::ObjectTypeSpreadProperty(spread) = spread else {
panic!("expected ObjectTypeSpreadProperty, got {:?}", spread.kind())
};
assert_generic_named(&gc, spread.argument, b"S");
let obj = obj_of(&mut sm, b"type I = { [[m]](): R };");
let slot = obj.internal_slots.iter().next().expect("one slot");
let Node::ObjectTypeInternalSlot(slot) = slot else {
panic!("expected ObjectTypeInternalSlot")
};
assert!(slot.method.get());
let obj = obj_of(&mut sm, b"type J = {| a: T |};");
assert!(obj.exact.get());
let obj = obj_of(&mut sm, b"type K = { a: T, ... };");
assert!(obj.inexact.get());
let obj = obj_of(&mut sm, b"type L = { ... };");
assert!(obj.inexact.get());
assert!(obj.properties.is_empty());
}
#[test]
fn flow_object_type_modifier_name_fallbacks() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
for (src, name, method) in [
(b"type A = { static: T };".as_slice(), b"static".as_slice(), false),
(b"type B = { proto: T };", b"proto", false),
(b"type C = { static(): R };", b"static", true),
(b"type D = { readonly: T };", b"readonly", false),
] {
let ty = flow_alias_right(&gc, &mut sm, src);
let Node::ObjectTypeAnnotation(obj) = ty else {
panic!("expected ObjectTypeAnnotation, got {:?}", ty.kind())
};
let prop = obj.properties.iter().next().expect("one property");
let Node::ObjectTypeProperty(prop) = prop else {
panic!("expected ObjectTypeProperty, got {:?}", prop.kind())
};
assert_eq!(ident_bytes(&gc, prop.key), name);
assert_eq!(prop.method.get(), method);
assert!(!prop.r#static.get(), "the keyword was the name");
assert!(!prop.proto.get(), "the keyword was the name");
}
}
#[test]
fn flow_type_param_declarations() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let params_of = |sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8]| {
let stmt = parse_one_stmt(&gc, sm, src);
let Node::TypeAlias(alias) = stmt else {
panic!("expected TypeAlias, got {:?}", stmt.kind())
};
let tp = alias.type_parameters.expect("has type params");
let Node::TypeParameterDeclaration(tpd) = tp else {
panic!("expected TypeParameterDeclaration, got {:?}", tp.kind())
};
tpd.params.iter().collect::<Vec<_>>()
};
let tparam = |node: &'_ hermes_ast::node::Node<'_>| {
let Node::TypeParameter(p) = node else {
panic!("expected TypeParameter, got {:?}", node.kind())
};
let name = gc.ctx().atom_table.bytes(p.name.get()).to_vec();
let variance = p.variance.map(|v| {
let Node::Variance(v) = v else {
panic!("expected Variance, got {:?}", v.kind())
};
gc.ctx().atom_table.bytes(v.kind.get()).to_vec()
});
(name, variance)
};
let params = params_of(&mut sm, b"type A<T,> = T;");
assert_eq!(params.len(), 1);
assert_eq!(tparam(params[0]), (b"T".to_vec(), None));
let params = params_of(&mut sm, b"type B<const T> = T;");
let Node::TypeParameter(p) = params[0] else { unreachable!() };
assert!(p.r#const.get());
let params = params_of(&mut sm, b"type C<+T, -U> = [T, U];");
assert_eq!(tparam(params[0]), (b"T".to_vec(), Some(b"plus".to_vec())));
assert_eq!(tparam(params[1]), (b"U".to_vec(), Some(b"minus".to_vec())));
let params = params_of(&mut sm, b"type D<in T, out U> = [T, U];");
assert_eq!(tparam(params[0]), (b"T".to_vec(), Some(b"in".to_vec())));
assert_eq!(tparam(params[1]), (b"U".to_vec(), Some(b"out".to_vec())));
let params = params_of(&mut sm, b"type E<in> = X;");
assert_eq!(tparam(params[0]), (b"in".to_vec(), None));
let params = params_of(&mut sm, b"type F<out = X> = out;");
assert_eq!(tparam(params[0]), (b"out".to_vec(), None));
let Node::TypeParameter(p) = params[0] else { unreachable!() };
assert!(p.default.is_some(), "`= X` is the default");
let params = params_of(&mut sm, b"type G<T: number> = T;");
let Node::TypeParameter(p) = params[0] else { unreachable!() };
let bound = p.bound.expect("has bound");
let Node::TypeAnnotation(bound) = bound else {
panic!("expected TypeAnnotation, got {:?}", bound.kind())
};
assert!(matches!(
bound.type_annotation,
Node::NumberTypeAnnotation(_)
));
assert!(!p.uses_extends_bound.get());
let params = params_of(&mut sm, b"type H<T extends U> = T;");
let Node::TypeParameter(p) = params[0] else { unreachable!() };
assert!(p.bound.is_some());
assert!(p.uses_extends_bound.get());
let params = params_of(&mut sm, b"type I<T = string> = T;");
let Node::TypeParameter(p) = params[0] else { unreachable!() };
assert!(matches!(
p.default.expect("has default"),
Node::StringTypeAnnotation(_)
));
}
#[test]
fn flow_type_predicates() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let predicate_of = |sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8]| {
let ty = flow_alias_right(&gc, sm, src);
as_fta(ty).return_type
};
let ret = predicate_of(&mut sm, b"type A = (x: mixed) => x is number;");
let Node::TypePredicate(p) = ret else {
panic!("expected TypePredicate, got {:?}", ret.kind())
};
assert_eq!(ident_bytes(&gc, p.parameter_name), b"x");
assert!(matches!(
p.type_annotation.expect("has type"),
Node::NumberTypeAnnotation(_)
));
assert_eq!(p.kind.get(), hermes_atom_table::INVALID_ATOM_BYTES);
let ret =
predicate_of(&mut sm, b"type B = (x: mixed) => asserts x is T;");
let Node::TypePredicate(p) = ret else {
panic!("expected TypePredicate, got {:?}", ret.kind())
};
assert_eq!(gc.ctx().atom_table.bytes(p.kind.get()), b"asserts");
assert!(p.type_annotation.is_some());
let ret = predicate_of(&mut sm, b"type C = (x: mixed) => asserts x;");
let Node::TypePredicate(p) = ret else {
panic!("expected TypePredicate, got {:?}", ret.kind())
};
assert!(p.type_annotation.is_none());
let ret = predicate_of(&mut sm, b"type D = (x: mixed) => asserts;");
assert_generic_named(&gc, ret, b"asserts");
let ret =
predicate_of(&mut sm, b"type E = (x: mixed) => implies x is T;");
let Node::TypePredicate(p) = ret else {
panic!("expected TypePredicate, got {:?}", ret.kind())
};
assert_eq!(gc.ctx().atom_table.bytes(p.kind.get()), b"implies");
assert!(p.type_annotation.is_some());
}
#[test]
fn flow_checks_predicates() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let parse_predicate = |src: &[u8]| -> (&'static str, bool) {
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", src);
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
parser.advance(crate::lexer::GrammarContext::Type);
let pred =
parser.parse_predicate_flow().expect("predicate parses");
let kind = match pred {
Node::DeclaredPredicate(d) => {
assert!(
matches!(d.value, Node::Identifier(_)),
"the checks expression is parsed as a JS expression"
);
"declared"
}
Node::InferredPredicate(_) => "inferred",
other => panic!("unexpected predicate {:?}", other.kind()),
};
(kind, parser.error_count_pub() == 0)
};
assert_eq!(parse_predicate(b"x %checks(y)"), ("declared", true));
assert_eq!(parse_predicate(b"x %checks"), ("inferred", true));
}
#[test]
fn flow_p52_errors() {
use hermes_ast::context::Context;
use hermes_support::diag::{CollectingHandler, DiagKind};
use hermes_support::manager::SourceErrorManager;
let assert_error = |src: &[u8], expected: &str| {
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _ = parse_with_collector(&gc, &mut sm, atoms, src);
let h = sm.handler_as::<CollectingHandler>().unwrap();
assert!(
h.messages()
.iter()
.any(|m| m.kind == DiagKind::Error && m.message == expected),
"expected {:?}, got {:?}",
expected,
h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
);
};
assert_error(
b"type A = {| a: T, ... |};",
"Explicit inexact syntax cannot appear inside an explicit exact object type",
);
assert_error(
b"type A = { get x(a: B): T };",
"Getter must have 0 parameters",
);
assert_error(
b"type A = { set x(): void };",
"Setter must have 1 parameter",
);
assert_error(
b"type A = { get x(this: B): T };",
"Accessors must not have 'this' annotations",
);
assert_error(
b"type A = (this?: X) => Y;",
"'this' constraint may not be optional",
);
assert_error(
b"type A = (a: X, this: Y) => Z;",
"'this' constraint must be the first parameter",
);
assert_error(
b"type A = { +(): R };",
"call property must not specify variance",
);
assert_error(
b"type A = { +get x(): T };",
"accessor property must not specify variance",
);
assert_error(b"type A = { proto x: T };", "invalid 'proto' modifier");
assert_error(b"type A = { static x: T };", "invalid 'static' modifier");
assert_error(b"type A = { +[[s]]: T };", "Unexpected variance sigil");
assert_error(
b"type A = (x) => implies<T> x is U;",
"invalid return annotation. 'implies' type guard needs to be followed by identifier",
);
assert_error(
b"type A = (x) => implies x;",
"expecting 'is' after parameter of 'implies' type guard",
);
}
fn flow_parse_stmt_at<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
idx: usize,
) -> &'gc hermes_ast::node::Node<'gc> {
let buf_id = sm.add_buffer_bytes("input", src);
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(gc, lexer);
let program = parser.parse().expect("parse succeeded");
assert_eq!(parser.error_count_pub(), 0, "zero errors");
if let hermes_ast::node::Node::Program(p) = program {
return p.body.iter().nth(idx).expect("has enough statements");
}
panic!("expected Program");
}
#[test]
fn flow_opaque_type_shapes() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let check = |src: &[u8],
has_tp: bool,
has_lower: bool,
has_upper: bool,
has_super: bool| {
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, src);
let Node::OpaqueType(o) = stmt else {
panic!("expected OpaqueType, got {:?}", stmt.kind())
};
assert_eq!(o.type_parameters.is_some(), has_tp, "{src:?} tp");
assert_eq!(o.lower_bound.is_some(), has_lower, "{src:?} lower");
assert_eq!(o.upper_bound.is_some(), has_upper, "{src:?} upper");
assert_eq!(o.supertype.is_some(), has_super, "{src:?} super");
};
check(b"opaque type A = number;", false, false, false, false);
check(b"opaque type B<T> = T;", true, false, false, false);
check(b"opaque type C: number = 1;", false, false, false, true);
check(b"opaque type D super X = Y;", false, true, false, false);
check(b"opaque type E extends F = G;", false, false, true, false);
check(
b"opaque type H super X extends F = G;",
false,
true,
true,
false,
);
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"opaque type C: number = 1;");
let Node::OpaqueType(o) = stmt else {
panic!("expected OpaqueType, got {:?}", stmt.kind())
};
assert_eq!(ident_bytes(&gc, o.id), b"C");
assert!(
matches!(o.supertype, Some(Node::NumberTypeAnnotation(_))),
"supertype is NumberTypeAnnotation"
);
assert!(
matches!(o.impltype, Node::NumberLiteralTypeAnnotation(_)),
"impltype is NumberLiteralTypeAnnotation, got {:?}",
o.impltype.kind()
);
}
#[test]
fn flow_interface_declaration() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"interface I { x: number }");
let Node::InterfaceDeclaration(decl) = stmt else {
panic!("expected InterfaceDeclaration, got {:?}", stmt.kind())
};
assert_eq!(ident_bytes(&gc, decl.id), b"I");
assert!(decl.type_parameters.is_none(), "no type params");
assert!(decl.extends.is_empty(), "no extends");
let Node::ObjectTypeAnnotation(body) = decl.body else {
panic!("expected ObjectTypeAnnotation body")
};
assert_eq!(body.properties.iter().count(), 1, "one property");
let stmt = parse_one_stmt(
&gc,
&mut sm,
b"interface J<T> extends K, L<T> { m(): void }",
);
let Node::InterfaceDeclaration(decl) = stmt else {
panic!("expected InterfaceDeclaration, got {:?}", stmt.kind())
};
assert_eq!(ident_bytes(&gc, decl.id), b"J");
assert!(decl.type_parameters.is_some(), "has type params");
let extends: Vec<_> = decl.extends.iter().collect();
assert_eq!(extends.len(), 2, "two extends entries");
let Node::InterfaceExtends(e0) = extends[0] else {
panic!("expected InterfaceExtends, got {:?}", extends[0].kind())
};
assert_eq!(ident_bytes(&gc, e0.id), b"K");
assert!(e0.type_parameters.is_none(), "K has no type args");
let Node::InterfaceExtends(e1) = extends[1] else {
panic!("expected InterfaceExtends, got {:?}", extends[1].kind())
};
assert_eq!(ident_bytes(&gc, e1.id), b"L");
assert!(e1.type_parameters.is_some(), "L<T> keeps its type args");
let stmt = parse_one_stmt(&gc, &mut sm, b"interface E {}");
let Node::InterfaceDeclaration(decl) = stmt else {
panic!("expected InterfaceDeclaration, got {:?}", stmt.kind())
};
let Node::ObjectTypeAnnotation(body) = decl.body else {
panic!("expected ObjectTypeAnnotation body")
};
assert!(body.properties.is_empty(), "empty body");
}
#[test]
fn flow_interface_type_annotation() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let right =
flow_alias_right(&gc, &mut sm, b"type A = interface { x: number };");
let Node::InterfaceTypeAnnotation(ita) = right else {
panic!("expected InterfaceTypeAnnotation, got {:?}", right.kind())
};
assert!(ita.extends.is_empty(), "no extends");
assert!(
matches!(ita.body, Some(Node::ObjectTypeAnnotation(_))),
"body is ObjectTypeAnnotation"
);
let right = flow_alias_right(
&gc,
&mut sm,
b"type B = interface extends I { y: T };",
);
let Node::InterfaceTypeAnnotation(ita) = right else {
panic!("expected InterfaceTypeAnnotation, got {:?}", right.kind())
};
let extends: Vec<_> = ita.extends.iter().collect();
assert_eq!(extends.len(), 1, "one extends entry");
assert!(matches!(extends[0], Node::InterfaceExtends(_)));
let stmt = flow_parse_stmt_at(
&gc,
&mut sm,
b"'use strict'; type C = interface { x: number };",
1,
);
let Node::TypeAlias(alias) = stmt else {
panic!("expected TypeAlias, got {:?}", stmt.kind())
};
assert!(
matches!(alias.right, Node::InterfaceTypeAnnotation(_)),
"rw_interface arm builds InterfaceTypeAnnotation, got {:?}",
alias.right.kind()
);
let stmt = flow_parse_stmt_at(
&gc,
&mut sm,
b"'use strict'; interface S { x: number }",
1,
);
assert!(
matches!(stmt, Node::InterfaceDeclaration(_)),
"rw_interface declaration parses, got {:?}",
stmt.kind()
);
}
#[test]
fn flow_class_implements() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let parse_impl = |src: &[u8], expect_args: bool| {
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", src);
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let node = parser
.parse_class_implements_flow()
.expect("class implements parses");
let Node::ClassImplements(ci) = node else {
panic!("expected ClassImplements, got {:?}", node.kind())
};
assert_eq!(ident_bytes(&gc, ci.id), b"I");
assert_eq!(
ci.type_parameters.is_some(),
expect_args,
"{src:?} type args"
);
assert_eq!(parser.error_count_pub(), 0, "zero errors");
};
parse_impl(b"I", false);
parse_impl(b"I<T>", true);
}
#[test]
fn flow_p53_errors() {
use hermes_ast::context::Context;
use hermes_support::diag::{CollectingHandler, DiagKind};
use hermes_support::manager::SourceErrorManager;
let assert_error = |src: &[u8], expected: &str| {
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _ = parse_with_collector(&gc, &mut sm, atoms, src);
let h = sm.handler_as::<CollectingHandler>().unwrap();
assert!(
h.messages()
.iter()
.any(|m| m.kind == DiagKind::Error && m.message == expected),
"expected {:?}, got {:?}",
expected,
h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
);
};
assert_error(
b"interface I { ...T }",
"Spreading a type is only allowed inside an object type",
);
assert_error(b"opaque type X;", "'=' expected in type alias");
assert_error(
b"opaque interface I {}",
"invalid token in opaque type declaration",
);
}
#[test]
fn flow_function_signature() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(
&gc,
&mut sm,
b"function f<T>(x: T): T { return x; }",
);
let Node::FunctionDeclaration(f) = stmt else {
panic!("expected FunctionDeclaration, got {:?}", stmt.kind())
};
assert!(f.type_parameters.is_some(), "type params");
assert!(f.return_type.is_some(), "return type");
assert!(f.predicate.is_none(), "no predicate");
let param = f.params.iter().next().expect("one param");
let Node::Identifier(p) = param else {
panic!("expected Identifier param, got {:?}", param.kind())
};
assert!(p.type_annotation.is_some(), "param annotation");
assert!(!p.optional.get(), "param not optional");
}
#[test]
fn flow_function_predicates() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
{
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(
&gc,
&mut sm,
b"function p(x: mixed): boolean %checks { return !!x; }",
);
let Node::FunctionDeclaration(f) = stmt else {
panic!("expected FunctionDeclaration, got {:?}", stmt.kind())
};
assert!(f.return_type.is_some(), "return type");
assert!(
matches!(f.predicate, Some(Node::InferredPredicate(_))),
"inferred predicate"
);
}
{
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(
&gc,
&mut sm,
b"function q(x: mixed): %checks (x === 1) {}",
);
let Node::FunctionDeclaration(f) = stmt else {
panic!("expected FunctionDeclaration, got {:?}", stmt.kind())
};
assert!(f.return_type.is_none(), "no return type");
assert!(
matches!(f.predicate, Some(Node::DeclaredPredicate(_))),
"declared predicate"
);
}
}
#[test]
fn flow_this_param() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(
&gc,
&mut sm,
b"function g(this: Object, a: number): void {}",
);
let Node::FunctionDeclaration(f) = stmt else {
panic!("expected FunctionDeclaration, got {:?}", stmt.kind())
};
let params: Vec<_> = f.params.iter().collect();
assert_eq!(params.len(), 2, "two params");
let Node::Identifier(this_param) = params[0] else {
panic!("expected Identifier, got {:?}", params[0].kind())
};
assert_eq!(
gc.ctx().atom_table.bytes(this_param.name.get()),
b"this",
"first param is 'this'"
);
assert!(this_param.type_annotation.is_some(), "'this' annotation");
assert!(!this_param.optional.get());
let Node::Identifier(a_param) = params[1] else {
panic!("expected Identifier, got {:?}", params[1].kind())
};
assert_eq!(gc.ctx().atom_table.bytes(a_param.name.get()), b"a");
}
#[test]
fn flow_binding_annotations() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
fn decl_id<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
src: &[u8],
) -> &'gc Node<'gc> {
let mut sm = SourceErrorManager::new();
let stmt = parse_one_stmt(gc, &mut sm, src);
let Node::VariableDeclaration(d) = stmt else {
panic!("expected VariableDeclaration, got {:?}", stmt.kind())
};
let Node::VariableDeclarator(declarator) =
d.declarations.iter().next().expect("one declarator")
else {
panic!("expected VariableDeclarator")
};
declarator.id
}
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let id = decl_id(&gc, b"var a?: number;");
let Node::Identifier(id) = id else {
panic!("expected Identifier, got {:?}", id.kind())
};
assert!(id.optional.get(), "optional");
assert!(id.type_annotation.is_some(), "id annotation");
let pat = decl_id(&gc, b"var [x, y]: T = c;");
let Node::ArrayPattern(pat) = pat else {
panic!("expected ArrayPattern, got {:?}", pat.kind())
};
assert!(pat.type_annotation.is_some(), "array pattern annotation");
let pat = decl_id(&gc, b"var {x}: T = c;");
let Node::ObjectPattern(pat) = pat else {
panic!("expected ObjectPattern, got {:?}", pat.kind())
};
assert!(pat.type_annotation.is_some(), "object pattern annotation");
let mut sm = SourceErrorManager::new();
let stmt = parse_one_stmt(&gc, &mut sm, b"function fd(a?: T) {}");
let Node::FunctionDeclaration(f) = stmt else {
panic!("expected FunctionDeclaration, got {:?}", stmt.kind())
};
let param = f.params.iter().next().expect("one param");
let Node::Identifier(p) = param else {
panic!("expected Identifier param, got {:?}", param.kind())
};
assert!(p.optional.get(), "optional param");
assert!(p.type_annotation.is_some(), "optional param annotation");
}
#[test]
fn flow_class_integration() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(
&gc,
&mut sm,
b"class C<T> extends B<T> implements I, J<T> {\n\
\x20 x: number;\n\
\x20 +ro: T;\n\
\x20 readonly r: V;\n\
\x20 #p: T;\n\
\x20 static: number;\n\
\x20 m<U>(a: U): U { return a; }\n\
\x20 get g(): T { return this.x; }\n\
}",
);
let Node::ClassDeclaration(c) = stmt else {
panic!("expected ClassDeclaration, got {:?}", stmt.kind())
};
assert!(c.type_parameters.is_some(), "class type params");
assert!(c.super_class.is_some(), "super class");
assert!(c.super_type_arguments.is_some(), "super type args");
let impls: Vec<_> = c.implements.iter().collect();
assert_eq!(impls.len(), 2, "two implements entries");
let Node::ClassImplements(i0) = impls[0] else {
panic!("expected ClassImplements, got {:?}", impls[0].kind())
};
assert!(i0.type_parameters.is_none(), "I has no type args");
let Node::ClassImplements(i1) = impls[1] else {
panic!("expected ClassImplements, got {:?}", impls[1].kind())
};
assert!(i1.type_parameters.is_some(), "J<T> has type args");
let Node::ClassBody(body) = c.body else {
panic!("expected ClassBody")
};
let elems: Vec<_> = body.body.iter().collect();
assert_eq!(elems.len(), 7, "seven class elements");
let Node::ClassProperty(x) = elems[0] else {
panic!("expected ClassProperty, got {:?}", elems[0].kind())
};
assert!(x.type_annotation.is_some(), "x annotation");
assert!(x.variance.is_none(), "x has no variance");
let Node::ClassProperty(ro) = elems[1] else {
panic!("expected ClassProperty, got {:?}", elems[1].kind())
};
let Some(Node::Variance(v)) = ro.variance else {
panic!("expected Variance on +ro")
};
assert_eq!(gc.ctx().atom_table.bytes(v.kind.get()), b"plus");
let Node::ClassProperty(r) = elems[2] else {
panic!("expected ClassProperty, got {:?}", elems[2].kind())
};
let Some(Node::Variance(v)) = r.variance else {
panic!("expected Variance on readonly r")
};
assert_eq!(gc.ctx().atom_table.bytes(v.kind.get()), b"readonly");
let Node::ClassPrivateProperty(p) = elems[3] else {
panic!("expected ClassPrivateProperty, got {:?}", elems[3].kind())
};
assert!(p.type_annotation.is_some(), "#p annotation");
let Node::ClassProperty(s) = elems[4] else {
panic!("expected ClassProperty, got {:?}", elems[4].kind())
};
let Node::Identifier(s_key) = s.key else {
panic!("expected Identifier key")
};
assert_eq!(gc.ctx().atom_table.bytes(s_key.name.get()), b"static");
assert!(!s.r#static.get(), "'static' is the name, not a modifier");
assert!(s.type_annotation.is_some(), "static-field annotation");
let Node::MethodDefinition(m) = elems[5] else {
panic!("expected MethodDefinition, got {:?}", elems[5].kind())
};
let Node::FunctionExpression(mf) = m.value else {
panic!("expected FunctionExpression")
};
assert!(mf.type_parameters.is_some(), "method type params");
assert!(mf.return_type.is_some(), "method return type");
let Node::MethodDefinition(getter) = elems[6] else {
panic!("expected MethodDefinition, got {:?}", elems[6].kind())
};
assert_eq!(gc.ctx().atom_table.bytes(getter.kind.get()), b"get");
let Node::FunctionExpression(gf) = getter.value else {
panic!("expected FunctionExpression")
};
assert!(gf.return_type.is_some(), "getter return type");
}
#[test]
fn flow_class_expression_heritage() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(
&gc,
&mut sm,
atoms,
b"(class <T> implements K { y: T; });",
);
let Node::ClassExpression(c) = expr else {
panic!("expected ClassExpression, got {:?}", expr.kind())
};
assert!(c.id.is_none(), "anonymous");
assert!(c.type_parameters.is_some(), "type params");
assert_eq!(c.implements.iter().count(), 1, "one implements entry");
}
#[test]
fn flow_object_literal_methods() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let expr = parse_expr_from(
&gc,
&mut sm,
atoms,
b"({ m<T>(x: T): T { return x; },\n\
\x20 get x(): number { return 1; },\n\
\x20 set y(v: number): void {},\n\
\x20 get<T>(x) { return x; } });",
);
let Node::ObjectExpression(obj) = expr else {
panic!("expected ObjectExpression, got {:?}", expr.kind())
};
let props: Vec<_> = obj.properties.iter().collect();
assert_eq!(props.len(), 4, "four properties");
let Node::Property(m) = props[0] else {
panic!("expected Property")
};
assert!(m.method.get(), "m is a method");
let Node::FunctionExpression(mf) = m.value else {
panic!("expected FunctionExpression")
};
assert!(mf.type_parameters.is_some(), "method type params");
assert!(mf.return_type.is_some(), "method return type");
let Node::Property(g) = props[1] else {
panic!("expected Property")
};
assert_eq!(gc.ctx().atom_table.bytes(g.kind.get()), b"get");
let Node::FunctionExpression(gf) = g.value else {
panic!("expected FunctionExpression")
};
assert!(gf.return_type.is_some(), "getter return type");
let Node::Property(s) = props[2] else {
panic!("expected Property")
};
assert_eq!(gc.ctx().atom_table.bytes(s.kind.get()), b"set");
let Node::FunctionExpression(sf) = s.value else {
panic!("expected FunctionExpression")
};
assert!(sf.return_type.is_some(), "setter return type");
let Node::Property(gm) = props[3] else {
panic!("expected Property")
};
assert!(gm.method.get(), "get<T> is a method");
let Node::Identifier(gm_key) = gm.key else {
panic!("expected Identifier key")
};
assert_eq!(gc.ctx().atom_table.bytes(gm_key.name.get()), b"get");
let Node::FunctionExpression(gmf) = gm.value else {
panic!("expected FunctionExpression")
};
assert!(gmf.type_parameters.is_some(), "get<T> type params");
}
#[test]
fn flow_p54_errors() {
use hermes_ast::context::Context;
use hermes_support::diag::{CollectingHandler, DiagKind};
use hermes_support::manager::SourceErrorManager;
let assert_error = |src: &[u8], expected: &str| {
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let _ = parse_with_collector(&gc, &mut sm, atoms, src);
let h = sm.handler_as::<CollectingHandler>().unwrap();
assert!(
h.messages()
.iter()
.any(|m| m.kind == DiagKind::Error && m.message == expected),
"expected {:?}, got {:?}",
expected,
h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
);
};
assert_error(
b"class C { get x<T>() { return 1; } }",
"accessor method may not have type parameters",
);
assert_error(b"class C { +m() {} }", "Unexpected variance sigil");
}
fn comp_parse_stmt_at<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
idx: usize,
) -> &'gc hermes_ast::node::Node<'gc> {
let buf_id = sm.add_buffer_bytes("input", src);
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(gc, lexer);
let program = parser.parse().expect("parse succeeded");
assert_eq!(parser.error_count_pub(), 0, "zero errors for {src:?}");
if let hermes_ast::node::Node::Program(p) = program {
return p.body.iter().nth(idx).expect("has enough statements");
}
panic!("expected Program");
}
fn comp_ctx() -> hermes_ast::context::Context<'static> {
let mut ctx = hermes_ast::context::Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_component_syntax(true);
ctx
}
#[test]
fn flow_component_basic() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = comp_ctx();
let gc = ctx.lock();
let stmt = comp_parse_stmt_at(&gc, &mut sm, b"component Foo() {}", 0);
let Node::ComponentDeclaration(c) = stmt else {
panic!("expected ComponentDeclaration, got {:?}", stmt.kind())
};
assert_eq!(ident_bytes(&gc, c.id), b"Foo");
assert_eq!(c.params.iter().count(), 0);
assert!(c.type_parameters.is_none());
assert!(c.renders_type.is_none());
assert!(!c.r#async.get());
}
#[test]
fn flow_component_parameters() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = comp_ctx();
let gc = ctx.lock();
let stmt = comp_parse_stmt_at(
&gc,
&mut sm,
b"component Foo(\"data-id\" as id, name?: string, x: number = 5, ...rest: Props) {}",
0,
);
let Node::ComponentDeclaration(c) = stmt else {
panic!("expected ComponentDeclaration, got {:?}", stmt.kind())
};
let params: Vec<_> = c.params.iter().collect();
assert_eq!(params.len(), 4);
let Node::ComponentParameter(p0) = params[0] else {
panic!("expected ComponentParameter, got {:?}", params[0].kind())
};
assert!(matches!(p0.name, Node::StringLiteral(_)));
assert!(!p0.shorthand.get());
let Node::ComponentParameter(p1) = params[1] else {
panic!("expected ComponentParameter, got {:?}", params[1].kind())
};
assert!(p1.shorthand.get());
let Node::Identifier(local1) = p1.local else {
panic!("expected Identifier local, got {:?}", p1.local.kind())
};
assert!(local1.optional.get());
assert!(local1.type_annotation.is_some());
let Node::ComponentParameter(p2) = params[2] else {
panic!("expected ComponentParameter, got {:?}", params[2].kind())
};
assert!(p2.shorthand.get());
assert!(matches!(p2.local, Node::AssignmentPattern(_)));
assert!(matches!(params[3], Node::RestElement(_)));
}
#[test]
fn flow_component_renders_operators() {
use hermes_ast::node::Node;
let check = |src: &[u8], op: &[u8]| {
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = comp_ctx();
let gc = ctx.lock();
let stmt = comp_parse_stmt_at(&gc, &mut sm, src, 0);
let Node::ComponentDeclaration(c) = stmt else {
panic!("expected ComponentDeclaration, got {:?}", stmt.kind())
};
let renders = c.renders_type.expect("has renders type");
let Node::TypeOperator(t) = renders else {
panic!("expected TypeOperator, got {:?}", renders.kind())
};
assert_eq!(gc.ctx().atom_table.bytes(t.operator.get()), op);
};
check(b"component A() renders React.Node {}", b"renders");
check(b"component B() renders? Bar {}", b"renders?");
check(b"component C() renders* Baz {}", b"renders*");
}
#[test]
fn flow_component_async_and_generic_hook() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = comp_ctx();
let gc = ctx.lock();
let stmt0 = comp_parse_stmt_at(
&gc,
&mut sm,
b"async component App() renders null { return null; }\nhook useZ<T>(x: T): T { return x; }",
0,
);
let Node::ComponentDeclaration(c) = stmt0 else {
panic!("expected ComponentDeclaration, got {:?}", stmt0.kind())
};
assert!(c.r#async.get());
assert!(c.renders_type.is_some());
let stmt1 = comp_parse_stmt_at(
&gc,
&mut sm,
b"async component App() renders null { return null; }\nhook useZ<T>(x: T): T { return x; }",
1,
);
let Node::HookDeclaration(h) = stmt1 else {
panic!("expected HookDeclaration, got {:?}", stmt1.kind())
};
assert_eq!(ident_bytes(&gc, h.id), b"useZ");
assert!(h.type_parameters.is_some());
assert!(h.return_type.is_some());
assert!(!h.r#async.get());
}
#[test]
fn flow_component_and_hook_type_annotations() {
use hermes_ast::node::Node;
let alias_right = |stmt: &hermes_ast::node::Node<'_>| -> hermes_ast::node::NodeKind {
let Node::TypeAlias(a) = stmt else {
panic!("expected TypeAlias, got {:?}", stmt.kind())
};
a.right.kind()
};
{
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = comp_ctx();
let gc = ctx.lock();
let stmt = comp_parse_stmt_at(
&gc,
&mut sm,
b"type C = component(foo: string, ...bar: number) renders Baz;",
0,
);
let Node::TypeAlias(a) = stmt else {
panic!("expected TypeAlias, got {:?}", stmt.kind())
};
let Node::ComponentTypeAnnotation(ct) = a.right else {
panic!("expected ComponentTypeAnnotation, got {:?}", a.right.kind())
};
assert_eq!(ct.params.iter().count(), 1);
assert!(ct.rest.is_some());
assert!(ct.renders_type.is_some());
}
{
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = comp_ctx();
let gc = ctx.lock();
let stmt = comp_parse_stmt_at(
&gc,
&mut sm,
b"type H = hook(a: number, b: string) => void;",
0,
);
assert_eq!(alias_right(stmt), hermes_ast::node::NodeKind::HookTypeAnnotation);
let Node::TypeAlias(a) = stmt else { unreachable!() };
let Node::HookTypeAnnotation(h) = a.right else {
panic!("expected HookTypeAnnotation, got {:?}", a.right.kind())
};
assert_eq!(h.params.iter().count(), 2);
assert!(h.rest.is_none());
}
}
#[test]
fn flow_hook_type_rejects_this() {
use hermes_ast::context::Context;
use hermes_support::manager::SourceErrorManager;
let src = b"type H = hook(this: number) => void;";
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer_bytes("input", src);
let mut ctx = Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_component_syntax(true);
let gc = ctx.lock();
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let _ = parser.parse();
assert!(
parser.error_count_pub() >= 1,
"hook type 'this' constraint must error"
);
}
#[test]
fn flow_component_syntax_gated_off() {
assert_parse_has_errors_impl(
b"component Foo() {}",
"component needs component-syntax flag",
true,
);
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = hermes_ast::context::Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = parse_one_stmt(&gc, &mut sm, b"var component = 1;");
assert!(matches!(stmt, hermes_ast::node::Node::VariableDeclaration(_)));
}
#[test]
fn flow_p54_no_leak() {
assert_parse_has_errors(
b"class C extends B<T> {}",
"super type args need Flow",
);
assert_parse_has_errors(
b"function f(): T { return 1; }",
"return type needs Flow",
);
assert_parse_has_errors(b"var a: T;", "binding annotation needs Flow");
assert_parse_has_errors(
b"var o = { m<T>() {} };",
"object-method type params need Flow",
);
}
fn rec_ctx() -> hermes_ast::context::Context<'static> {
let mut ctx = hermes_ast::context::Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_ambiguous(true);
ctx.set_parse_flow_records(true);
ctx
}
fn rec_parse_stmt_at<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
idx: usize,
) -> &'gc hermes_ast::node::Node<'gc> {
comp_parse_stmt_at(gc, sm, src, idx)
}
#[test]
fn flow_record_empty() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = rec_ctx();
let gc = ctx.lock();
let stmt = rec_parse_stmt_at(&gc, &mut sm, b"record Foo {}", 0);
let Node::RecordDeclaration(r) = stmt else {
panic!("expected RecordDeclaration, got {:?}", stmt.kind())
};
assert_eq!(ident_bytes(&gc, r.id), b"Foo");
assert!(r.type_parameters.is_none(), "no type params");
assert_eq!(r.implements.iter().count(), 0, "no implements");
let Node::RecordDeclarationBody(body) = r.body else {
panic!("expected RecordDeclarationBody, got {:?}", r.body.kind())
};
assert_eq!(body.elements.iter().count(), 0, "empty body");
}
#[test]
fn flow_record_full() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = rec_ctx();
let gc = ctx.lock();
let stmt = rec_parse_stmt_at(
&gc,
&mut sm,
b"record Point<T> implements I, J<K> {\n\
x: number, y: T,\n\
static origin: Point = mk(),\n\
dist(o: Point): number { return 0; }\n\
async *gen<U>(): U {}\n\
}",
0,
);
let Node::RecordDeclaration(r) = stmt else {
panic!("expected RecordDeclaration, got {:?}", stmt.kind())
};
assert_eq!(ident_bytes(&gc, r.id), b"Point");
assert!(r.type_parameters.is_some(), "has type params");
let impls: Vec<_> = r.implements.iter().collect();
assert_eq!(impls.len(), 2, "two implements entries");
let Node::RecordDeclarationImplements(i0) = impls[0] else {
panic!("expected RecordDeclarationImplements")
};
assert_eq!(ident_bytes(&gc, i0.id), b"I");
assert!(i0.type_arguments.is_none(), "I has no type-args");
let Node::RecordDeclarationImplements(i1) = impls[1] else {
panic!("expected RecordDeclarationImplements")
};
assert_eq!(ident_bytes(&gc, i1.id), b"J");
assert!(i1.type_arguments.is_some(), "J<K> has type-args");
let Node::RecordDeclarationBody(body) = r.body else {
panic!("expected RecordDeclarationBody")
};
let elems: Vec<_> = body.elements.iter().collect();
assert_eq!(elems.len(), 5, "x, y, static origin, dist, gen");
let Node::RecordDeclarationProperty(p_x) = elems[0] else {
panic!("expected RecordDeclarationProperty, got {:?}", elems[0].kind())
};
assert_eq!(ident_bytes(&gc, p_x.key), b"x");
assert!(p_x.default_value.is_none(), "x has no initializer");
let Node::RecordDeclarationStaticProperty(p_origin) = elems[2] else {
panic!(
"expected RecordDeclarationStaticProperty, got {:?}",
elems[2].kind()
)
};
assert_eq!(ident_bytes(&gc, p_origin.key), b"origin");
let Node::MethodDefinition(m_dist) = elems[3] else {
panic!("expected MethodDefinition, got {:?}", elems[3].kind())
};
assert!(!m_dist.r#static.get(), "dist is not static");
let Node::FunctionExpression(f_dist) = m_dist.value else {
panic!("expected FunctionExpression")
};
assert!(!f_dist.generator.get() && !f_dist.r#async.get());
assert!(f_dist.return_type.is_some(), "dist has a return type");
let Node::MethodDefinition(m_gen) = elems[4] else {
panic!("expected MethodDefinition, got {:?}", elems[4].kind())
};
let Node::FunctionExpression(f_gen) = m_gen.value else {
panic!("expected FunctionExpression")
};
assert!(f_gen.generator.get(), "gen is a generator");
assert!(f_gen.r#async.get(), "gen is async");
assert!(f_gen.type_parameters.is_some(), "gen has type params");
}
#[test]
fn flow_record_expr_ident() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = rec_ctx();
let gc = ctx.lock();
let init = {
let stmt =
rec_parse_stmt_at(&gc, &mut sm, b"const p = Point { x: 1 };", 0);
let Node::VariableDeclaration(vd) = stmt else {
panic!("expected VariableDeclaration")
};
let Node::VariableDeclarator(d) =
vd.declarations.iter().next().unwrap()
else {
panic!("expected VariableDeclarator")
};
d.init.expect("has init")
};
let Node::RecordExpression(re) = init else {
panic!("expected RecordExpression, got {:?}", init.kind())
};
assert_eq!(ident_bytes(&gc, re.record_constructor), b"Point");
assert!(re.type_arguments.is_none(), "no type-args");
let Node::RecordExpressionProperties(props) = re.properties else {
panic!("expected RecordExpressionProperties")
};
assert_eq!(props.properties.iter().count(), 1, "one property");
}
#[test]
fn flow_record_expr_member_typeargs() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = rec_ctx();
let gc = ctx.lock();
let init = {
let stmt = rec_parse_stmt_at(
&gc,
&mut sm,
b"const q = ns.Maker<T> { a: 3 };",
0,
);
let Node::VariableDeclaration(vd) = stmt else {
panic!("expected VariableDeclaration")
};
let Node::VariableDeclarator(d) =
vd.declarations.iter().next().unwrap()
else {
panic!("expected VariableDeclarator")
};
d.init.expect("has init")
};
let Node::RecordExpression(re) = init else {
panic!("expected RecordExpression, got {:?}", init.kind())
};
assert!(
matches!(re.record_constructor, Node::MemberExpression(_)),
"MemberExpression constructor"
);
assert!(re.type_arguments.is_some(), "ns.Maker<T> has type-args");
}
#[test]
fn flow_record_expr_lowercase_rejected() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = rec_ctx();
let gc = ctx.lock();
let stmt = rec_parse_stmt_at(&gc, &mut sm, b"point\n{ x }", 0);
assert!(
!matches!(stmt, Node::RecordExpression(_)),
"lowercase ctor must not form a RecordExpression"
);
}
#[test]
fn flow_record_disabled_is_not_record() {
use hermes_ast::context::Context;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true); let gc = ctx.lock();
let buf_id = sm.add_buffer_bytes("input", b"record R {}");
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let _ = parser.parse();
assert!(
parser.error_count_pub() > 0,
"record disabled: `record R {{}}` must report a syntax error"
);
}
fn match_ctx() -> hermes_ast::context::Context<'static> {
use hermes_ast::context::Context;
let mut ctx = Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_match(true);
ctx
}
fn match_parse_stmt_at<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
idx: usize,
) -> &'gc hermes_ast::node::Node<'gc> {
let buf_id = sm.add_buffer_bytes("input", src);
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(gc, lexer);
let program = parser.parse().expect("parse succeeded");
assert_eq!(parser.error_count_pub(), 0, "zero errors for {src:?}");
if let hermes_ast::node::Node::Program(p) = program {
return p.body.iter().nth(idx).expect("has enough statements");
}
panic!("expected Program");
}
fn match_expr_from<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
) -> &'gc hermes_ast::node::Node<'gc> {
use hermes_ast::node::Node;
let stmt = match_parse_stmt_at(gc, sm, src, 0);
let Node::VariableDeclaration(vd) = stmt else {
panic!("expected VariableDeclaration, got {:?}", stmt.kind())
};
let decl = vd.declarations.iter().next().expect("one declarator");
let Node::VariableDeclarator(d) = decl else {
panic!("expected VariableDeclarator")
};
d.init.expect("has init")
}
#[test]
fn flow_match_expression_basic() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = match_ctx();
let gc = ctx.lock();
let expr =
match_expr_from(&gc, &mut sm, b"const r = match (x) { 1 => 'a', _ => 'c' };");
let Node::MatchExpression(m) = expr else {
panic!("expected MatchExpression, got {:?}", expr.kind())
};
assert!(matches!(m.argument, Node::Identifier(_)), "arg is `x`");
let mut it = m.cases.iter();
let Node::MatchExpressionCase(c0) = it.next().unwrap() else {
panic!("expected MatchExpressionCase")
};
assert!(
matches!(c0.pattern, Node::MatchLiteralPattern(_)),
"first case is a literal pattern"
);
assert!(c0.guard.is_none(), "no guard");
let Node::MatchExpressionCase(c1) = it.next().unwrap() else {
panic!("expected MatchExpressionCase")
};
assert!(
matches!(c1.pattern, Node::MatchWildcardPattern(_)),
"second case is the wildcard `_`"
);
assert!(it.next().is_none(), "exactly two cases");
}
#[test]
fn flow_match_statement_basic() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = match_ctx();
let gc = ctx.lock();
let stmt =
match_parse_stmt_at(&gc, &mut sm, b"match (x) { 1 => { f(); } _ => { g(); } }", 0);
let Node::MatchStatement(m) = stmt else {
panic!("expected MatchStatement, got {:?}", stmt.kind())
};
let mut it = m.cases.iter();
let Node::MatchStatementCase(c0) = it.next().unwrap() else {
panic!("expected MatchStatementCase")
};
assert!(
matches!(c0.body, Node::BlockStatement(_)),
"statement case body is a block"
);
assert!(it.next().is_some(), "second case present");
}
#[test]
fn flow_match_call_not_match() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = match_ctx();
let gc = ctx.lock();
let stmt0 = match_parse_stmt_at(&gc, &mut sm, b"match(1, 2);\nmatch (foo)(bar);", 0);
let Node::ExpressionStatement(es0) = stmt0 else {
panic!("expected ExpressionStatement")
};
let Node::CallExpression(call) = es0.expression else {
panic!("expected CallExpression, got {:?}", es0.expression.kind())
};
assert!(matches!(call.callee, Node::Identifier(_)), "callee is `match`");
let stmt1 = match_parse_stmt_at(&gc, &mut sm, b"match(1, 2);\nmatch (foo)(bar);", 1);
let Node::ExpressionStatement(es1) = stmt1 else {
panic!("expected ExpressionStatement")
};
let Node::CallExpression(outer) = es1.expression else {
panic!("expected CallExpression")
};
assert!(
matches!(outer.callee, Node::CallExpression(_)),
"outer callee is `match(foo)`"
);
}
#[test]
fn flow_match_newline_is_not_match() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = match_ctx();
let gc = ctx.lock();
let buf_id = sm.add_buffer_bytes("input", b"match\n(x) { _ => 1 }\n");
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let _ = parser.parse();
assert!(
parser.error_count_pub() > 0,
"`match\\n(x) {{…}}` must error (newline blocks the match), not parse as a match"
);
}
#[test]
fn flow_match_expr_vs_stmt() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = match_ctx();
let gc = ctx.lock();
let e = match_expr_from(&gc, &mut sm, b"const r = match (x) { _ => 1 };");
assert!(matches!(e, Node::MatchExpression(_)), "expr form");
let s = match_parse_stmt_at(&gc, &mut sm, b"match (x) { _ => { y; } }", 0);
assert!(matches!(s, Node::MatchStatement(_)), "stmt form");
}
#[test]
fn flow_match_object_array_patterns() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = match_ctx();
let gc = ctx.lock();
let expr = match_expr_from(
&gc,
&mut sm,
b"const r = match (x) { {a: 1, b: _} => 1, [1, 2, ...const rest] => 2, _ => 3 };",
);
let Node::MatchExpression(m) = expr else {
panic!("expected MatchExpression")
};
let mut it = m.cases.iter();
let Node::MatchExpressionCase(c0) = it.next().unwrap() else {
panic!("case 0")
};
let Node::MatchObjectPattern(obj) = c0.pattern else {
panic!("expected MatchObjectPattern, got {:?}", c0.pattern.kind())
};
assert_eq!(obj.properties.iter().count(), 2, "two object props");
assert!(obj.rest.is_none(), "no object rest");
let Node::MatchExpressionCase(c1) = it.next().unwrap() else {
panic!("case 1")
};
let Node::MatchArrayPattern(arr) = c1.pattern else {
panic!("expected MatchArrayPattern, got {:?}", c1.pattern.kind())
};
assert_eq!(arr.elements.iter().count(), 2, "two array elements");
let rest = arr.rest.expect("array rest present");
let Node::MatchRestPattern(rp) = rest else {
panic!("expected MatchRestPattern")
};
assert!(rp.argument.is_some(), "rest binds `const rest`");
}
#[test]
fn flow_match_or_and_guard() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = match_ctx();
let gc = ctx.lock();
let expr = match_expr_from(
&gc,
&mut sm,
b"const r = match (x) { 1 | 2 | 3 => 'low', const y if (y > 0) => 'pos', _ => 'z' };",
);
let Node::MatchExpression(m) = expr else {
panic!("expected MatchExpression")
};
let mut it = m.cases.iter();
let Node::MatchExpressionCase(c0) = it.next().unwrap() else {
panic!("case 0")
};
let Node::MatchOrPattern(or) = c0.pattern else {
panic!("expected MatchOrPattern, got {:?}", c0.pattern.kind())
};
assert_eq!(or.patterns.iter().count(), 3, "three or-alternatives");
let Node::MatchExpressionCase(c1) = it.next().unwrap() else {
panic!("case 1")
};
assert!(
matches!(c1.pattern, Node::MatchBindingPattern(_)),
"second case is a `const y` binding"
);
assert!(c1.guard.is_some(), "second case has an `if` guard");
}
#[test]
fn flow_match_member_unary_instance() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = match_ctx();
let gc = ctx.lock();
let expr = match_expr_from(
&gc,
&mut sm,
b"const r = match (x) { Foo.Bar => 1, -2 => 2, Status{value: const v} => v, _ => 0 };",
);
let Node::MatchExpression(m) = expr else {
panic!("expected MatchExpression")
};
let mut it = m.cases.iter();
let Node::MatchExpressionCase(c0) = it.next().unwrap() else {
panic!("case 0")
};
assert!(
matches!(c0.pattern, Node::MatchMemberPattern(_)),
"first case is a member pattern, got {:?}",
c0.pattern.kind()
);
let Node::MatchExpressionCase(c1) = it.next().unwrap() else {
panic!("case 1")
};
let Node::MatchUnaryPattern(u) = c1.pattern else {
panic!("expected MatchUnaryPattern, got {:?}", c1.pattern.kind())
};
assert_eq!(
gc.ctx().atom_table.bytes(u.operator.get()),
b"-",
"unary operator is `-`"
);
let Node::MatchExpressionCase(c2) = it.next().unwrap() else {
panic!("case 2")
};
let Node::MatchInstancePattern(inst) = c2.pattern else {
panic!("expected MatchInstancePattern, got {:?}", c2.pattern.kind())
};
assert!(
matches!(inst.properties, Node::MatchInstanceObjectPattern(_)),
"instance properties are a MatchInstanceObjectPattern"
);
}
#[test]
fn flow_match_as_pattern() {
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = match_ctx();
let gc = ctx.lock();
let expr =
match_expr_from(&gc, &mut sm, b"const r = match (x) { (1 | 2) as const k => k, _ => 0 };");
let Node::MatchExpression(m) = expr else {
panic!("expected MatchExpression")
};
let c0 = m.cases.iter().next().unwrap();
let Node::MatchExpressionCase(c0) = c0 else {
panic!("case 0")
};
let Node::MatchAsPattern(asp) = c0.pattern else {
panic!("expected MatchAsPattern, got {:?}", c0.pattern.kind())
};
assert!(
matches!(asp.pattern, Node::MatchOrPattern(_)),
"as-pattern wraps a group-elided or-pattern"
);
assert!(
matches!(asp.target, Node::MatchBindingPattern(_)),
"as-target is a `const k` binding"
);
}
#[test]
fn flow_match_rest_needs_binding() {
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = match_ctx();
let gc = ctx.lock();
let buf_id =
sm.add_buffer_bytes("input", b"const r = match (x) { [...rest] => 1, _ => 0 };");
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let _ = parser.parse();
assert!(
parser.error_count_pub() > 0,
"`...rest` without a binding keyword must error"
);
}
#[test]
fn flow_match_disabled_is_identifier() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
let mut sm = hermes_support::manager::SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let buf_id = sm.add_buffer_bytes("input", b"const r = match;\nmatch(x);");
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
&mut sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(&gc, lexer);
let program = parser.parse().expect("parses");
assert_eq!(parser.error_count_pub(), 0, "no errors when match is off");
let Node::Program(p) = program else {
panic!("expected Program")
};
let mut body = p.body.iter();
let Node::VariableDeclaration(vd) = body.next().unwrap() else {
panic!("expected VariableDeclaration")
};
let Node::VariableDeclarator(d) = vd.declarations.iter().next().unwrap() else {
panic!("expected VariableDeclarator")
};
assert!(
matches!(d.init, Some(Node::Identifier(_))),
"`match` is a plain identifier reference when the flag is off"
);
}
fn flow_parse_body<'gc>(
gc: &'gc hermes_ast::context::GCLock<'_, '_>,
sm: &mut hermes_support::manager::SourceErrorManager,
src: &[u8],
components: bool,
) -> Vec<&'gc hermes_ast::node::Node<'gc>> {
let _ = components; let buf_id = sm.add_buffer_bytes("input", src);
let atoms = &gc.ctx().atom_table;
let lexer = crate::lexer::JSLexer::new(
buf_id,
sm,
atoms,
crate::lexer::GrammarContext::AllowRegExp,
);
let mut parser = JSParserImpl::new(gc, lexer);
let program = parser.parse().expect("parse succeeded");
assert_eq!(
parser.error_count_pub(),
0,
"zero errors for {:?}",
String::from_utf8_lossy(src)
);
if let hermes_ast::node::Node::Program(p) = program {
return p.body.iter().collect();
}
panic!("expected Program");
}
#[test]
fn flow_declare_forms() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let check = |src: &[u8], pred: fn(&Node) -> bool| {
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = flow_parse_stmt_at(&gc, &mut sm, src, 0);
assert!(pred(stmt), "wrong node for {:?}: {:?}", String::from_utf8_lossy(src), stmt.kind());
};
check(b"declare function foo(x: number): string;", |n| {
matches!(n, Node::DeclareFunction(_))
});
check(b"declare var x: number;", |n| {
matches!(n, Node::DeclareVariable(_))
});
check(b"declare type T = number;", |n| {
matches!(n, Node::DeclareTypeAlias(_))
});
check(b"declare interface I { foo(): void }", |n| {
matches!(n, Node::DeclareInterface(_))
});
check(
b"declare class C<T> extends B mixins M implements I { x: number; }",
|n| matches!(n, Node::DeclareClass(_)),
);
check(b"declare module 'x' { declare var y: number; }", |n| {
matches!(n, Node::DeclareModule(_))
});
check(b"declare module.exports: { a: number };", |n| {
matches!(n, Node::DeclareModuleExports(_))
});
check(b"declare namespace NS { declare var z: string; }", |n| {
matches!(n, Node::DeclareNamespace(_))
});
check(b"declare opaque type O: number;", |n| {
matches!(n, Node::DeclareOpaqueType(_))
});
check(b"declare enum E { A, B }", |n| {
matches!(n, Node::DeclareEnum(_))
});
}
#[test]
fn flow_declare_export_forms() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let decl_of = |src: &[u8]| -> bool {
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = flow_parse_stmt_at(&gc, &mut sm, src, 0);
let Node::DeclareExportDeclaration(d) = stmt else {
panic!("expected DeclareExportDeclaration for {:?}, got {:?}",
String::from_utf8_lossy(src), stmt.kind())
};
d.default.get()
};
assert!(!decl_of(b"declare export function f(): void;"));
assert!(decl_of(b"declare export default number;"));
assert!(!decl_of(b"declare export opaque type T2: number;"));
{
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = flow_parse_stmt_at(
&gc,
&mut sm,
b"declare export interface I2 { a: number }",
0,
);
let Node::DeclareExportDeclaration(d) = stmt else {
panic!("expected DeclareExportDeclaration")
};
assert!(
matches!(d.declaration, Some(Node::InterfaceDeclaration(_))),
"declare export interface wraps an InterfaceDeclaration"
);
}
{
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt2 = flow_parse_stmt_at(
&gc,
&mut sm,
b"declare export * from 'mod';",
0,
);
assert!(matches!(stmt2, Node::DeclareExportAllDeclaration(_)));
}
}
#[test]
fn flow_import_type_kinds() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let import_kind = |src: &[u8]| -> Vec<u8> {
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = flow_parse_stmt_at(&gc, &mut sm, src, 0);
let Node::ImportDeclaration(d) = stmt else {
panic!("expected ImportDeclaration for {:?}, got {:?}",
String::from_utf8_lossy(src), stmt.kind())
};
gc.ctx().atom_table.bytes(d.import_kind.get()).to_vec()
};
assert_eq!(import_kind(b"import type {A} from 'x';"), b"type");
assert_eq!(import_kind(b"import typeof B from 'x';"), b"typeof");
assert_eq!(import_kind(b"import {A} from 'x';"), b"value");
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = flow_parse_stmt_at(
&gc,
&mut sm,
b"import {type A2, typeof C} from 'x';",
0,
);
let Node::ImportDeclaration(d) = stmt else {
panic!("expected ImportDeclaration")
};
assert_eq!(gc.ctx().atom_table.bytes(d.import_kind.get()), b"value");
let kinds: Vec<Vec<u8>> = d
.specifiers
.iter()
.map(|s| {
let Node::ImportSpecifier(is) = s else {
panic!("expected ImportSpecifier")
};
gc.ctx().atom_table.bytes(is.import_kind.get()).to_vec()
})
.collect();
assert_eq!(kinds, vec![b"type".to_vec(), b"typeof".to_vec()]);
}
#[test]
fn flow_import_type_from_trap() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = flow_parse_stmt_at(&gc, &mut sm, b"import type from 'x';", 0);
let Node::ImportDeclaration(d) = stmt else {
panic!("expected ImportDeclaration")
};
assert_eq!(
gc.ctx().atom_table.bytes(d.import_kind.get()),
b"value",
"the trap resets the kind to value"
);
let spec = d.specifiers.iter().next().expect("one specifier");
let Node::ImportDefaultSpecifier(s) = spec else {
panic!("expected ImportDefaultSpecifier, got {:?}", spec.kind())
};
let Node::Identifier(local) = s.local else {
panic!("expected Identifier local")
};
assert_eq!(gc.ctx().atom_table.bytes(local.name.get()), b"type");
}
#[test]
fn flow_declare_module_body_recursion() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
let gc = ctx.lock();
let stmt = flow_parse_stmt_at(
&gc,
&mut sm,
b"declare module 'x' { declare export var y: number; }",
0,
);
let Node::DeclareModule(m) = stmt else {
panic!("expected DeclareModule")
};
let Node::BlockStatement(b) = m.body else {
panic!("expected BlockStatement body")
};
let inner = b.body.iter().next().expect("one inner declaration");
assert!(
matches!(inner, Node::DeclareExportDeclaration(_)),
"inner declare export var parses, got {:?}",
inner.kind()
);
}
#[test]
fn flow_declare_component_and_hook() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
ctx.set_parse_flow(true);
ctx.set_parse_flow_component_syntax(true);
let gc = ctx.lock();
let body = flow_parse_body(
&gc,
&mut sm,
b"declare component Foo(p: number) renders Bar;\n\
declare hook useY(a: string): number;",
true,
);
assert!(matches!(body[0], Node::DeclareComponent(_)));
assert!(matches!(body[1], Node::DeclareHook(_)));
}
#[test]
fn flow_plain_import_export_unaffected() {
use hermes_ast::context::Context;
use hermes_ast::node::Node;
use hermes_support::manager::SourceErrorManager;
let mut sm = SourceErrorManager::new();
let mut ctx = Context::new();
let gc = ctx.lock();
let stmt =
flow_parse_stmt_at(&gc, &mut sm, b"import {a as b} from 'm';", 0);
let Node::ImportDeclaration(d) = stmt else {
panic!("expected ImportDeclaration")
};
assert_eq!(gc.ctx().atom_table.bytes(d.import_kind.get()), b"value");
let Node::ImportSpecifier(is) =
d.specifiers.iter().next().unwrap()
else {
panic!("expected ImportSpecifier")
};
assert_eq!(gc.ctx().atom_table.bytes(is.import_kind.get()), b"value");
let stmt2 =
flow_parse_stmt_at(&gc, &mut sm, b"export {a as b};", 0);
assert!(matches!(stmt2, Node::ExportNamedDeclaration(_)));
}
}