use oxc_allocator::{Allocator, Box, Dummy, Vec};
use oxc_ast::ast::*;
use oxc_span::{GetSpan, Span};
use oxc_str::Str;
use crate::{ParserConfig as Config, ParserImpl, diagnostics, lexer::Kind};
enum JSXClosing<'a> {
Element(Box<'a, JSXClosingElement<'a>>),
Fragment(JSXClosingFragment),
}
impl<'a> Dummy<'a> for JSXClosing<'a> {
fn dummy(allocator: &'a Allocator) -> Self {
JSXClosing::Fragment(Dummy::dummy(allocator))
}
}
impl<'a, C: Config> ParserImpl<'a, C> {
pub(crate) fn parse_jsx_expression(&mut self) -> Expression<'a> {
let span = self.start_span();
self.bump_any(); let kind = self.cur_kind();
if kind == Kind::RAngle {
Expression::JSXFragment(self.parse_jsx_fragment(span, false))
} else if kind.is_identifier_or_keyword() {
Expression::JSXElement(self.parse_jsx_element(span, false))
} else {
self.unexpected()
}
}
fn parse_jsx_fragment(&mut self, span: u32, in_jsx_child: bool) -> Box<'a, JSXFragment<'a>> {
self.expect_jsx_child(Kind::RAngle);
let opening_fragment = self.ast.jsx_opening_fragment(self.end_span(span));
let (children, closing) = self.parse_jsx_children_and_closing(in_jsx_child);
let closing_fragment = match closing {
JSXClosing::Fragment(f) => f,
JSXClosing::Element(e) => {
self.error(diagnostics::jsx_fragment_no_match(
opening_fragment.span,
e.name.span(),
));
self.ast.jsx_closing_fragment(e.span)
}
};
self.ast.alloc_jsx_fragment(
self.end_span(span),
opening_fragment,
children,
closing_fragment,
)
}
fn parse_jsx_element(&mut self, span: u32, in_jsx_child: bool) -> Box<'a, JSXElement<'a>> {
let (opening_element, self_closing) = self.parse_jsx_opening_element(span, in_jsx_child);
let (children, closing_element) = if self_closing {
(self.ast.vec(), None)
} else {
let (children, closing) = self.parse_jsx_children_and_closing(in_jsx_child);
let closing_element = match closing {
JSXClosing::Element(e) => {
if !Self::jsx_element_name_eq(&opening_element.name, &e.name) {
self.error(diagnostics::jsx_element_no_match(
opening_element.name.span(),
e.name.span(),
opening_element.name.span().source_text(self.source_text),
));
}
e
}
JSXClosing::Fragment(f) => {
return self.fatal_error(diagnostics::jsx_element_no_match(
opening_element.name.span(),
f.span,
opening_element.name.span().source_text(self.source_text),
));
}
};
(children, Some(closing_element))
};
self.ast.alloc_jsx_element(self.end_span(span), opening_element, children, closing_element)
}
fn parse_jsx_opening_element(
&mut self,
span: u32,
in_jsx_child: bool,
) -> (
Box<'a, JSXOpeningElement<'a>>,
bool, // `true` if self-closing
) {
let name = self.parse_jsx_element_name();
let type_arguments = if self.is_ts { self.try_parse_type_arguments() } else { None };
let attributes = self.parse_jsx_attributes();
let self_closing = self.eat(Kind::Slash);
if !self_closing || in_jsx_child {
self.expect_jsx_child(Kind::RAngle);
} else {
self.expect(Kind::RAngle);
}
let elem = self.ast.alloc_jsx_opening_element(
self.end_span(span),
name,
type_arguments,
attributes,
);
(elem, self_closing)
}
fn parse_jsx_element_name(&mut self) -> JSXElementName<'a> {
let span = self.start_span();
let identifier = self.parse_jsx_identifier();
if self.eat(Kind::Colon) {
let property = self.parse_jsx_identifier();
return self.ast.jsx_element_name_namespaced_name(
self.end_span(span),
identifier,
property,
);
}
if self.at(Kind::Dot) {
return JSXElementName::MemberExpression(
self.parse_jsx_member_expression(span, &identifier),
);
}
if self.fatal_error.is_some() {
return JSXElementName::dummy(self.ast.allocator);
}
let name = identifier.name.as_str();
let is_reference = match name.as_bytes()[0] {
b if b.is_ascii() => !b.is_ascii_lowercase(), _ => true, } && !name.contains('-');
if is_reference {
let identifier = self.ast.alloc_identifier_reference(identifier.span, identifier.name);
JSXElementName::IdentifierReference(identifier)
} else if name == "this" {
JSXElementName::ThisExpression(self.ast.alloc_this_expression(identifier.span))
} else {
JSXElementName::Identifier(self.alloc(identifier))
}
}
fn parse_jsx_member_expression(
&mut self,
span: u32,
object: &JSXIdentifier<'a>,
) -> Box<'a, JSXMemberExpression<'a>> {
let mut object = if object.name == "this" {
self.ast.jsx_member_expression_object_this_expression(object.span)
} else {
self.ast.jsx_member_expression_object_identifier_reference(object.span, object.name)
};
let mut span = Span::new(span, 0);
let mut property = None;
while self.eat(Kind::Dot) && self.fatal_error.is_none() {
if let Some(prop) = property {
object =
self.ast.jsx_member_expression_object_member_expression(span, object, prop);
}
let ident = self.parse_jsx_identifier();
if ident.name.contains('-') {
let error = diagnostics::identifier_expected_jsx_no_hyphen(ident.span);
return self.fatal_error(error);
}
property = Some(ident);
span = self.end_span(span.start);
}
if let Some(property) = property {
return self.ast.alloc_jsx_member_expression(
self.end_span(span.start),
object,
property,
);
}
self.unexpected()
}
fn parse_jsx_children_and_closing(
&mut self,
in_jsx_child: bool,
) -> (Vec<'a, JSXChild<'a>>, JSXClosing<'a>) {
let mut children = self.ast.vec();
loop {
if self.fatal_error.is_some() {
let closing = self.ast.jsx_closing_fragment(self.cur_token().span());
return (children, JSXClosing::Fragment(closing));
}
match self.cur_kind() {
Kind::LAngle => {
let span = self.start_span();
self.bump_any(); let kind = self.cur_kind();
if kind == Kind::RAngle {
children.push(JSXChild::Fragment(self.parse_jsx_fragment(span, true)));
continue;
}
if kind == Kind::Ident || kind.is_any_keyword() {
children.push(JSXChild::Element(self.parse_jsx_element(span, true)));
continue;
}
if kind == Kind::Slash {
self.bump_any(); let closing = self.parse_jsx_closing_inline(span, in_jsx_child);
return (children, closing);
}
return (children, self.unexpected());
}
Kind::LCurly => {
let span_start = self.start_span();
self.bump_any();
if self.eat(Kind::Dot3) {
children.push(JSXChild::Spread(self.parse_jsx_spread_child(span_start)));
continue;
}
children.push(JSXChild::ExpressionContainer(
self.parse_jsx_expression_container(
span_start, true,
),
));
}
Kind::JSXText => {
children.push(JSXChild::Text(self.parse_jsx_text()));
}
_ => {
return (children, self.unexpected());
}
}
}
}
fn parse_jsx_closing_inline(
&mut self,
open_angle_span: u32,
in_jsx_child: bool,
) -> JSXClosing<'a> {
if self.at(Kind::RAngle) {
if in_jsx_child {
self.expect_jsx_child(Kind::RAngle);
} else {
self.expect(Kind::RAngle);
}
JSXClosing::Fragment(self.ast.jsx_closing_fragment(self.end_span(open_angle_span)))
} else {
let name = self.parse_jsx_element_name();
if in_jsx_child {
self.expect_jsx_child(Kind::RAngle);
} else {
self.expect(Kind::RAngle);
}
JSXClosing::Element(
self.ast.alloc_jsx_closing_element(self.end_span(open_angle_span), name),
)
}
}
fn parse_jsx_expression_container(
&mut self,
span_start: u32,
in_jsx_child: bool,
) -> Box<'a, JSXExpressionContainer<'a>> {
let expr = if self.at(Kind::RCurly) {
if in_jsx_child {
self.expect_jsx_child(Kind::RCurly);
} else {
self.expect(Kind::RCurly);
}
let span = self.end_span(span_start);
if !in_jsx_child {
self.error(diagnostics::jsx_attribute_value_empty_expression(span));
}
let expr = self.ast.jsx_empty_expression(Span::new(span.start + 1, span.end - 1));
JSXExpression::EmptyExpression(expr)
} else {
let expr = JSXExpression::from(self.parse_expr());
if in_jsx_child {
self.expect_jsx_child(Kind::RCurly);
} else {
self.expect(Kind::RCurly);
}
expr
};
self.ast.alloc_jsx_expression_container(self.end_span(span_start), expr)
}
fn parse_jsx_spread_child(&mut self, span_start: u32) -> Box<'a, JSXSpreadChild<'a>> {
let expr = self.parse_expr();
self.expect_jsx_child(Kind::RCurly);
self.ast.alloc_jsx_spread_child(self.end_span(span_start), expr)
}
fn parse_jsx_attributes(&mut self) -> Vec<'a, JSXAttributeItem<'a>> {
let mut attributes = self.ast.vec();
loop {
let kind = self.cur_kind();
if matches!(kind, Kind::LAngle | Kind::RAngle | Kind::Slash)
|| self.fatal_error.is_some()
{
break;
}
let attribute = match kind {
Kind::LCurly => {
JSXAttributeItem::SpreadAttribute(self.parse_jsx_spread_attribute())
}
_ => JSXAttributeItem::Attribute(self.parse_jsx_attribute()),
};
attributes.push(attribute);
}
attributes
}
fn parse_jsx_attribute(&mut self) -> Box<'a, JSXAttribute<'a>> {
let span = self.start_span();
let name = self.parse_jsx_attribute_name();
let value = if self.at(Kind::Eq) {
self.expect_jsx_attribute_value(Kind::Eq);
Some(self.parse_jsx_attribute_value())
} else {
None
};
self.ast.alloc_jsx_attribute(self.end_span(span), name, value)
}
fn parse_jsx_spread_attribute(&mut self) -> Box<'a, JSXSpreadAttribute<'a>> {
let span = self.start_span();
self.bump_any(); self.expect(Kind::Dot3);
let argument = self.parse_expr();
self.expect(Kind::RCurly);
self.ast.alloc_jsx_spread_attribute(self.end_span(span), argument)
}
fn parse_jsx_attribute_name(&mut self) -> JSXAttributeName<'a> {
let span = self.start_span();
let identifier = self.parse_jsx_identifier();
if self.eat(Kind::Colon) {
let property = self.parse_jsx_identifier();
return self.ast.jsx_attribute_name_namespaced_name(
self.end_span(span),
identifier,
property,
);
}
JSXAttributeName::Identifier(self.alloc(identifier))
}
fn parse_jsx_attribute_value(&mut self) -> JSXAttributeValue<'a> {
match self.cur_kind() {
Kind::Str => {
let str_lit = self.parse_literal_string();
JSXAttributeValue::StringLiteral(self.alloc(str_lit))
}
Kind::LCurly => {
let span_start = self.start_span();
self.bump_any();
let expr =
self.parse_jsx_expression_container(span_start, false);
JSXAttributeValue::ExpressionContainer(expr)
}
Kind::LAngle => match self.parse_jsx_expression() {
Expression::JSXFragment(fragment) => JSXAttributeValue::Fragment(fragment),
Expression::JSXElement(element) => JSXAttributeValue::Element(element),
_ => self.unexpected(),
},
_ => self.unexpected(),
}
}
fn parse_jsx_identifier(&mut self) -> JSXIdentifier<'a> {
let span = self.start_span();
let kind = self.cur_kind();
if kind != Kind::Ident && !kind.is_any_keyword() {
return self.unexpected();
}
self.continue_lex_jsx_identifier();
self.bump_any();
let span = self.end_span(span);
let name = span.source_text(self.source_text);
self.ast.jsx_identifier(span, name)
}
fn parse_jsx_text(&mut self) -> Box<'a, JSXText<'a>> {
let span = self.cur_token().span();
let raw = Str::from(self.cur_src());
let value = Str::from(self.cur_string());
self.bump_any();
self.ast.alloc_jsx_text(span, value, Some(raw))
}
fn jsx_element_name_eq(lhs: &JSXElementName<'a>, rhs: &JSXElementName<'a>) -> bool {
match (lhs, rhs) {
(JSXElementName::Identifier(lhs), JSXElementName::Identifier(rhs)) => {
lhs.name == rhs.name
}
(
JSXElementName::IdentifierReference(lhs),
JSXElementName::IdentifierReference(rhs),
) => lhs.name == rhs.name,
(JSXElementName::NamespacedName(lhs), JSXElementName::NamespacedName(rhs)) => {
lhs.namespace.name == rhs.namespace.name && lhs.name.name == rhs.name.name
}
(JSXElementName::MemberExpression(lhs), JSXElementName::MemberExpression(rhs)) => {
Self::jsx_member_expression_eq(lhs, rhs)
}
(JSXElementName::ThisExpression(_), JSXElementName::ThisExpression(_)) => true,
_ => false,
}
}
fn jsx_member_expression_eq(
lhs: &JSXMemberExpression<'a>,
rhs: &JSXMemberExpression<'a>,
) -> bool {
if lhs.property.name != rhs.property.name {
return false;
}
match (&lhs.object, &rhs.object) {
(
JSXMemberExpressionObject::IdentifierReference(lhs),
JSXMemberExpressionObject::IdentifierReference(rhs),
) => lhs.name == rhs.name,
(
JSXMemberExpressionObject::MemberExpression(lhs),
JSXMemberExpressionObject::MemberExpression(rhs),
) => Self::jsx_member_expression_eq(lhs, rhs),
(
JSXMemberExpressionObject::ThisExpression(_),
JSXMemberExpressionObject::ThisExpression(_),
) => true,
_ => false,
}
}
}