use hermes_ast::context::{Context, GCLock};
use hermes_ast::node::{Node, NodeKind};
use hermes_ast::visitor::Visitor;
use hermes_parser::js::JSParserImpl;
use hermes_parser::lexer::{GrammarContext, JSLexer};
use hermes_support::manager::SourceErrorManager;
struct FindFirst<'gc> {
kind: NodeKind,
found: Option<&'gc Node<'gc>>,
}
impl<'gc> Visitor<'gc> for FindFirst<'gc> {
fn visit_node(&mut self, node: &'gc Node<'gc>) {
if self.found.is_some() {
return;
}
if node.kind() == self.kind {
self.found = Some(node);
return;
}
node.visit_children(self);
}
}
fn first<'gc>(root: &'gc Node<'gc>, kind: NodeKind) -> &'gc Node<'gc> {
let mut f = FindFirst { kind, found: None };
f.visit_node(root);
f.found
.unwrap_or_else(|| panic!("no {kind:?} node in the parsed tree"))
}
fn with_parsed<R>(
src: &str,
body: impl for<'gc> FnOnce(&'gc GCLock<'_, '_>, &'gc Node<'gc>) -> R,
) -> R {
let mut sm = SourceErrorManager::new();
let buf_id = sm.add_buffer("accessors.js", src);
let mut ctx = Context::new();
let gc = ctx.lock();
let program = {
let atoms = &gc.ctx().atom_table;
let lexer = JSLexer::new(buf_id, &mut sm, atoms, GrammarContext::AllowRegExp);
let mut parser = JSParserImpl::new(&gc, lexer);
parser.parse().unwrap_or_else(|| panic!("`{src}` must parse"))
};
body(&gc, program)
}
#[test]
fn identifier_name_reads_back_as_a_str() {
with_parsed("function greet() { let x = 1; }\n", |gc, program| {
let id = first(program, NodeKind::Identifier)
.as_identifier()
.expect("kind() said Identifier");
assert_eq!(id.name_str(gc), "greet");
});
}
#[test]
fn binary_operator_label_reads_back_as_a_str() {
with_parsed("a + b;\n", |gc, program| {
let bin = first(program, NodeKind::BinaryExpression)
.as_binary_expression()
.expect("kind() said BinaryExpression");
assert_eq!(bin.operator_str(gc), "+");
});
}
#[test]
fn plain_string_literal_reads_back_both_ways() {
with_parsed("var s = \"hello\";\n", |gc, program| {
let lit = first(program, NodeKind::StringLiteral)
.as_string_literal()
.expect("kind() said StringLiteral");
assert_eq!(lit.try_value_str(gc), Some("hello"));
assert_eq!(lit.value_str_lossy(gc), "hello");
assert_eq!(gc.bytes(lit.value.get()), b"hello");
assert_eq!(
lit.try_value_str(gc).unwrap().as_ptr(),
gc.bytes(lit.value.get()).as_ptr()
);
});
}
#[test]
fn emoji_string_literal_is_representable() {
with_parsed("var s = \"\u{1F600}\";\n", |gc, program| {
let lit = first(program, NodeKind::StringLiteral)
.as_string_literal()
.expect("kind() said StringLiteral");
assert_eq!(
gc.bytes(lit.value.get()),
&[0xED, 0xA0, 0xBD, 0xED, 0xB8, 0x80]
);
assert_eq!(lit.try_value_str(gc), Some("\u{1F600}"));
assert_eq!(lit.value_str_lossy(gc), "\u{1F600}");
});
}
#[test]
fn lone_surrogate_string_literal_is_not_representable_but_survives() {
with_parsed("var s = \"\\uD800\";\n", |gc, program| {
let lit = first(program, NodeKind::StringLiteral)
.as_string_literal()
.expect("kind() said StringLiteral");
assert_eq!(lit.try_value_str(gc), None);
assert_eq!(lit.value_str_lossy(gc), "\u{FFFD}");
assert_eq!(gc.bytes(lit.value.get()), &[0xED, 0xA0, 0x80]);
});
}
#[test]
fn expression_statement_directive_reads_back_as_a_str() {
with_parsed("\"use strict\";\n", |gc, program| {
let stmt = first(program, NodeKind::ExpressionStatement)
.as_expression_statement()
.expect("kind() said ExpressionStatement");
assert_eq!(stmt.try_directive_str(gc), Some("use strict"));
assert_eq!(stmt.directive_str_lossy(gc), "use strict");
});
}