use hermes_ast::node::{
JSXAttribute, JSXClosingElement, JSXClosingFragment, JSXElement,
JSXEmptyExpression, JSXExpressionContainer, JSXFragment, JSXIdentifier,
JSXMemberExpression, JSXNamespacedName, JSXOpeningElement,
JSXOpeningFragment, JSXSpreadAttribute, JSXSpreadChild, JSXStringLiteral,
JSXText, Node,
};
use hermes_ast::node_child::{NodeList, NodeMetadata};
use hermes_support::location::SMLoc;
use crate::js::flow::{AllowTypedArrowFunction, CoverTypedParameters};
use crate::js::{JSParserImpl, PARAM_IN};
use crate::lexer::GrammarContext;
use crate::token_kinds::TokenKind;
fn tag_names_match<'gc>(
opening_name: &'gc Node<'gc>,
closing_name: &'gc Node<'gc>,
) -> bool {
let mut name1 = opening_name;
let mut name2 = closing_name;
loop {
if let Node::JSXIdentifier(name1_id) = name1 {
if let Node::JSXIdentifier(name2_id) = name2 {
return name1_id.name.get() == name2_id.name.get();
}
return false;
} else if let Node::JSXNamespacedName(name1_ns) = name1 {
if let Node::JSXNamespacedName(name2_ns) = name2 {
let name1_ns_id =
name1_ns.namespace.as_jsx_identifier().unwrap();
let name1_id = name1_ns.name.as_jsx_identifier().unwrap();
let name2_ns_id =
name2_ns.namespace.as_jsx_identifier().unwrap();
let name2_id = name2_ns.name.as_jsx_identifier().unwrap();
return name1_ns_id.name.get() == name2_ns_id.name.get()
&& name1_id.name.get() == name2_id.name.get();
}
return false;
} else {
let name1_me = name1.as_jsx_member_expression().unwrap();
if let Node::JSXMemberExpression(name2_me) = name2 {
let name1_id =
name1_me.property.as_jsx_identifier().unwrap();
let name2_id =
name2_me.property.as_jsx_identifier().unwrap();
if name1_id.name.get() != name2_id.name.get() {
return false;
}
name1 = name1_me.object;
name2 = name2_me.object;
continue;
}
return false;
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum AllowJSXMemberExpression {
No,
Yes,
}
impl<'gc, 'ast, 'ctx, 'a> JSParserImpl<'gc, 'ast, 'ctx, 'a> {
pub(super) fn parse_jsx_root(&mut self) -> Option<&'gc Node<'gc>> {
debug_assert!(self.check(TokenKind::less));
let _depth_guard = self.save_jsx_depth(0);
let start =
self.advance(GrammarContext::AllowJSXIdentifier).start;
if self.check(TokenKind::greater) {
return self.parse_jsx_fragment(start);
}
self.parse_jsx_element(start)
}
fn parse_jsx_element(&mut self, start: SMLoc) -> Option<&'gc Node<'gc>> {
let _depth_guard = self.save_jsx_depth(self.jsx_depth.get() + 1);
let opening = self.parse_jsx_opening_element(start)?;
let opening_el = opening
.as_jsx_opening_element()
.expect("parse_jsx_opening_element returns a JSXOpeningElement");
if opening_el.self_closing.get() {
let end = opening.metadata().range.get().end;
let node = Node::JSXElement(JSXElement::new(
NodeMetadata::new(self.dummy_range()),
opening,
NodeList::empty(),
None,
));
return Some(self.set_location(start, end, node));
}
let mut children: Vec<&'gc Node<'gc>> = Vec::new();
let closing = self.parse_jsx_children(&mut children)?;
if let Node::JSXClosingElement(closing_el) = closing {
let opening_name = opening_el.name;
if !tag_names_match(opening_name, closing_el.name) {
let range = closing.metadata().range.get();
self.error_at(range, "Closing tag must match opening");
}
} else {
let range = closing.metadata().range.get();
self.error_at(range, "Closing tag must not be a fragment");
}
let end = closing.metadata().range.get().end;
let node = Node::JSXElement(JSXElement::new(
NodeMetadata::new(self.dummy_range()),
opening,
NodeList::from_iter(self.gc, children),
Some(closing),
));
Some(self.set_location(start, end, node))
}
fn parse_jsx_opening_element(
&mut self,
start: SMLoc,
) -> Option<&'gc Node<'gc>> {
let name =
self.parse_jsx_element_name(AllowJSXMemberExpression::Yes)?;
let type_arguments: Option<&'gc Node<'gc>> =
if self.check(TokenKind::less) {
Some(self.parse_type_args_flow(
GrammarContext::AllowJSXIdentifier,
)?)
} else {
None
};
let mut attributes: Vec<&'gc Node<'gc>> = Vec::new();
while !self.check2(TokenKind::slash, TokenKind::greater) {
if self.check(TokenKind::l_brace) {
attributes.push(self.parse_jsx_spread_attribute()?);
continue;
}
attributes.push(self.parse_jsx_attribute()?);
}
let self_closing =
self.check_and_eat(TokenKind::slash, GrammarContext::AllowRegExp);
let end = self.cur_range().end;
if !self.need_at(
TokenKind::greater,
" at end of JSX tag",
Some("start of tag"),
start,
) {
return None;
}
if self_closing && self.jsx_depth.get() <= 1 {
self.advance(GrammarContext::AllowRegExp);
} else {
self.lexer.advance_in_jsx_child();
}
let node = Node::JSXOpeningElement(JSXOpeningElement::new(
NodeMetadata::new(self.dummy_range()),
name,
NodeList::from_iter(self.gc, attributes),
self_closing,
type_arguments,
));
Some(self.set_location(start, end, node))
}
fn parse_jsx_fragment(&mut self, start: SMLoc) -> Option<&'gc Node<'gc>> {
debug_assert!(self.check(TokenKind::greater));
let _depth_guard = self.save_jsx_depth(self.jsx_depth.get() + 1);
let frag_end = self.cur_range().end;
let opening = self.set_location(
start,
frag_end,
Node::JSXOpeningFragment(JSXOpeningFragment::new(
NodeMetadata::new(self.dummy_range()),
)),
);
self.lexer.advance_in_jsx_child();
let mut children: Vec<&'gc Node<'gc>> = Vec::new();
let closing = self.parse_jsx_children(&mut children)?;
if !matches!(closing, Node::JSXClosingFragment(_)) {
let range = closing.metadata().range.get();
self.error_at(range, "Closing tag must be a fragment");
return None;
}
let end = closing.metadata().range.get().end;
let node = Node::JSXFragment(JSXFragment::new(
NodeMetadata::new(self.dummy_range()),
opening,
NodeList::from_iter(self.gc, children),
closing,
));
Some(self.set_location(start, end, node))
}
fn parse_jsx_children(
&mut self,
children: &mut Vec<&'gc Node<'gc>>,
) -> Option<&'gc Node<'gc>> {
loop {
if self.check(TokenKind::less) {
let start =
self.advance(GrammarContext::AllowJSXIdentifier).start;
if self.check(TokenKind::slash) {
return self.parse_jsx_closing(start);
}
let elem = if self.check(TokenKind::greater) {
self.parse_jsx_fragment(start)?
} else {
self.parse_jsx_element(start)?
};
children.push(elem);
} else if self.check(TokenKind::l_brace) {
let start_range = self.advance(GrammarContext::AllowRegExp);
let start = start_range.start;
if self.check(TokenKind::r_brace) {
let end_range = self.cur_range();
let empty = self.set_location(
start_range.end,
end_range.start,
Node::JSXEmptyExpression(JSXEmptyExpression::new(
NodeMetadata::new(self.dummy_range()),
)),
);
let container = self.set_location(
start,
end_range.end,
Node::JSXExpressionContainer(
JSXExpressionContainer::new(
NodeMetadata::new(self.dummy_range()),
empty,
),
),
);
children.push(container);
} else {
let child_expr = self.parse_jsx_child_expression(start)?;
if !self.need_at(
TokenKind::r_brace,
" in JSX child expression",
Some("start of expression"),
start,
) {
return None;
}
children.push(child_expr);
}
self.lexer.advance_in_jsx_child();
} else {
if !self.need(TokenKind::jsx_text, " in JSX child expression")
{
return None;
}
let tok_range = self.cur_range();
let value = self.lexer.token().get_jsx_text_value();
let raw = self.lexer.token().get_jsx_text_raw();
let text = self.set_location(
tok_range.start,
tok_range.end,
Node::JSXText(JSXText::new(
NodeMetadata::new(self.dummy_range()),
value,
raw,
)),
);
children.push(text);
self.lexer.advance_in_jsx_child();
}
}
}
fn parse_jsx_child_expression(
&mut self,
start: SMLoc,
) -> Option<&'gc Node<'gc>> {
if self.check_and_eat(TokenKind::dotdotdot, GrammarContext::AllowRegExp)
{
let assign = self.parse_assignment_expression(
PARAM_IN,
false,
AllowTypedArrowFunction::Yes,
CoverTypedParameters::Yes,
None,
)?;
let end = self.cur_range().end;
return Some(self.set_location(
start,
end,
Node::JSXSpreadChild(JSXSpreadChild::new(
NodeMetadata::new(self.dummy_range()),
assign,
)),
));
}
let assign = self.parse_assignment_expression(
PARAM_IN,
false,
AllowTypedArrowFunction::Yes,
CoverTypedParameters::Yes,
None,
)?;
let end = self.cur_range().end;
Some(self.set_location(
start,
end,
Node::JSXExpressionContainer(JSXExpressionContainer::new(
NodeMetadata::new(self.dummy_range()),
assign,
)),
))
}
fn parse_jsx_spread_attribute(&mut self) -> Option<&'gc Node<'gc>> {
debug_assert!(self.check(TokenKind::l_brace));
let start = self.advance(GrammarContext::AllowRegExp).start;
if !self.eat_at(
TokenKind::dotdotdot,
GrammarContext::AllowRegExp,
" in JSX spread attribute",
Some("location of attribute"),
start,
) {
return None;
}
let assign = self.parse_assignment_expression(
PARAM_IN,
false,
AllowTypedArrowFunction::Yes,
CoverTypedParameters::Yes,
None,
)?;
let end = self.cur_range().end;
if !self.eat_at(
TokenKind::r_brace,
GrammarContext::AllowJSXIdentifier,
" in JSX spread attribute",
Some("location of attribute"),
start,
) {
return None;
}
Some(self.set_location(
start,
end,
Node::JSXSpreadAttribute(JSXSpreadAttribute::new(
NodeMetadata::new(self.dummy_range()),
assign,
)),
))
}
fn parse_jsx_attribute(&mut self) -> Option<&'gc Node<'gc>> {
let start = self.cur_start();
let name = self.parse_jsx_element_name(AllowJSXMemberExpression::No)?;
if !self.check_and_eat(
TokenKind::equal,
GrammarContext::AllowJSXIdentifier,
) {
let name_range = name.metadata().range.get();
return Some(self.set_location(
name_range.start,
name_range.end,
Node::JSXAttribute(JSXAttribute::new(
NodeMetadata::new(self.dummy_range()),
name,
None,
)),
));
}
let value: &'gc Node<'gc> = if self.check(TokenKind::string_literal) {
let raw =
self.lexer.get_string_literal(self.lexer.token_input_str());
let str_value = self.lexer.token().get_string_literal();
let tok_range = self.cur_range();
let v = self.set_location(
tok_range.start,
tok_range.end,
Node::JSXStringLiteral(JSXStringLiteral::new(
NodeMetadata::new(self.dummy_range()),
str_value,
raw,
)),
);
self.advance(GrammarContext::AllowJSXIdentifier);
v
} else {
if !self.need_at(
TokenKind::l_brace,
" in JSX attribute",
Some("location of attribute"),
start,
) {
return None;
}
let value_start = self.advance(GrammarContext::AllowRegExp).start;
let assign = self.parse_assignment_expression(
PARAM_IN,
false,
AllowTypedArrowFunction::Yes,
CoverTypedParameters::Yes,
None,
)?;
let value_end = self.cur_range().end;
if !self.eat_at(
TokenKind::r_brace,
GrammarContext::AllowJSXIdentifier,
" in JSX attribute",
Some("location of attribute"),
start,
) {
return None;
}
self.set_location(
value_start,
value_end,
Node::JSXExpressionContainer(JSXExpressionContainer::new(
NodeMetadata::new(self.dummy_range()),
assign,
)),
)
};
let value_end = value.metadata().range.get().end;
Some(self.set_location(
start,
value_end,
Node::JSXAttribute(JSXAttribute::new(
NodeMetadata::new(self.dummy_range()),
name,
Some(value),
)),
))
}
fn parse_jsx_closing(&mut self, start: SMLoc) -> Option<&'gc Node<'gc>> {
debug_assert!(self.check(TokenKind::slash));
self.advance(GrammarContext::AllowJSXIdentifier);
if self.check(TokenKind::greater) {
let end = self.cur_range().end;
if self.jsx_depth.get() > 1 {
self.lexer.advance_in_jsx_child();
} else {
self.advance(GrammarContext::AllowRegExp);
}
return Some(self.set_location(
start,
end,
Node::JSXClosingFragment(JSXClosingFragment::new(
NodeMetadata::new(self.dummy_range()),
)),
));
}
let name = self.parse_jsx_element_name(AllowJSXMemberExpression::Yes)?;
if !self.need_at(
TokenKind::greater,
" at end of JSX closing tag",
Some("start of tag"),
start,
) {
return None;
}
let end = self.cur_range().end;
if self.jsx_depth.get() > 1 {
self.lexer.advance_in_jsx_child();
} else {
self.advance(GrammarContext::AllowRegExp);
}
Some(self.set_location(
start,
end,
Node::JSXClosingElement(JSXClosingElement::new(
NodeMetadata::new(self.dummy_range()),
name,
)),
))
}
fn parse_jsx_element_name(
&mut self,
allow_jsx_member_expression: AllowJSXMemberExpression,
) -> Option<&'gc Node<'gc>> {
let start = self.cur_start();
if !self.check(TokenKind::identifier)
&& !self.lexer.token().is_res_word()
{
self.error_expected_jsx_element_name(
" as JSX element name",
None,
None,
);
return None;
}
let name_range = self.cur_range();
let mut name: &'gc Node<'gc> = self.set_location(
name_range.start,
name_range.end,
Node::JSXIdentifier(JSXIdentifier::new(
NodeMetadata::new(self.dummy_range()),
self.lexer.token().get_res_word_or_identifier(),
)),
);
self.advance(GrammarContext::AllowJSXIdentifier);
if self.check(TokenKind::colon) {
self.advance(GrammarContext::AllowJSXIdentifier);
if !self.check(TokenKind::identifier)
&& !self.lexer.token().is_res_word()
{
self.error_expected_jsx_element_name(
" in JSX element name",
Some("start of JSX element name"),
Some(start),
);
return None;
}
let child_range = self.cur_range();
let child = self.set_location(
child_range.start,
child_range.end,
Node::JSXIdentifier(JSXIdentifier::new(
NodeMetadata::new(self.dummy_range()),
self.lexer.token().get_res_word_or_identifier(),
)),
);
let child_end = child.metadata().range.get().end;
self.advance(GrammarContext::AllowJSXIdentifier);
return Some(self.set_location(
start,
child_end,
Node::JSXNamespacedName(JSXNamespacedName::new(
NodeMetadata::new(self.dummy_range()),
name,
child,
)),
));
}
while self.check(TokenKind::period) {
self.advance(GrammarContext::AllowJSXIdentifier);
if !self.check(TokenKind::identifier)
&& !self.lexer.token().is_res_word()
{
self.error_expected_jsx_element_name(
" in JSX element name",
Some("start of JSX element name"),
Some(start),
);
return None;
}
let child_range = self.cur_range();
let child = self.set_location(
child_range.start,
child_range.end,
Node::JSXIdentifier(JSXIdentifier::new(
NodeMetadata::new(self.dummy_range()),
self.lexer.token().get_res_word_or_identifier(),
)),
);
let child_end = child.metadata().range.get().end;
self.advance(GrammarContext::AllowJSXIdentifier);
name = self.set_location(
start,
child_end,
Node::JSXMemberExpression(JSXMemberExpression::new(
NodeMetadata::new(self.dummy_range()),
name,
child,
)),
);
}
if matches!(name, Node::JSXMemberExpression(_))
&& allow_jsx_member_expression == AllowJSXMemberExpression::No
{
let range = name.metadata().range.get();
self.error_at(range, "unexpected member expression");
}
Some(name)
}
fn error_expected_jsx_element_name(
&mut self,
where_: &str,
what: Option<&str>,
what_loc: Option<SMLoc>,
) {
let msg = format!(
"'{}' expected{}",
crate::token_kinds::token_kind_str(TokenKind::identifier),
where_,
);
self.error_expected_msg(&msg, what, what_loc);
}
}