mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
Box all enum variants for JSX types (`JSXAttributeName`, `JSXAttributeValue`, `JSXChild`, `JSXElementName`, `JSXMemberExpressionObject`). Part of #3047. I'm not sure how to interpret the benchmark results. As I said on #3047: > I imagine it may cost a little in performance in the parser due to extra calls to `alloc`, but in return traversing the AST should be cheaper, as the data is more compact, so less cache misses. Sure enough, there is a small impact (1%) on the 2 parser benchmarks for JSX files. However, the other benchmarks have too much noise in them to see whether this is repaid in a speed up on transformer etc, especially as the transformer benchmarks also include parsing. What do you think @Boshen?
386 lines
14 KiB
Rust
386 lines
14 KiB
Rust
//! [JSX](https://facebook.github.io/jsx)
|
|
|
|
#![allow(clippy::missing_errors_doc)]
|
|
|
|
use oxc_allocator::{Box, Vec};
|
|
use oxc_ast::ast::*;
|
|
use oxc_diagnostics::Result;
|
|
use oxc_span::{Atom, Span};
|
|
|
|
use crate::{diagnostics, lexer::Kind, Context, ParserImpl};
|
|
|
|
impl<'a> ParserImpl<'a> {
|
|
pub(crate) fn parse_jsx_expression(&mut self) -> Result<Expression<'a>> {
|
|
if self.peek_at(Kind::RAngle) {
|
|
self.parse_jsx_fragment(false).map(Expression::JSXFragment)
|
|
} else {
|
|
self.parse_jsx_element(false).map(Expression::JSXElement)
|
|
}
|
|
}
|
|
|
|
/// `JSXFragment` :
|
|
/// < > `JSXChildren_opt` < / >
|
|
fn parse_jsx_fragment(&mut self, in_jsx_child: bool) -> Result<Box<'a, JSXFragment<'a>>> {
|
|
let span = self.start_span();
|
|
let opening_fragment = self.parse_jsx_opening_fragment(span)?;
|
|
let children = self.parse_jsx_children()?;
|
|
let closing_fragment = self.parse_jsx_closing_fragment(in_jsx_child)?;
|
|
Ok(self.ast.jsx_fragment(self.end_span(span), opening_fragment, closing_fragment, children))
|
|
}
|
|
|
|
/// <>
|
|
fn parse_jsx_opening_fragment(&mut self, span: Span) -> Result<JSXOpeningFragment> {
|
|
self.expect(Kind::LAngle)?;
|
|
self.expect_jsx_child(Kind::RAngle)?;
|
|
Ok(self.ast.jsx_opening_fragment(self.end_span(span)))
|
|
}
|
|
|
|
/// </>
|
|
fn parse_jsx_closing_fragment(&mut self, in_jsx_child: bool) -> Result<JSXClosingFragment> {
|
|
let span = self.start_span();
|
|
self.expect(Kind::LAngle)?;
|
|
self.expect(Kind::Slash)?;
|
|
if in_jsx_child {
|
|
self.expect_jsx_child(Kind::RAngle)?;
|
|
} else {
|
|
self.expect(Kind::RAngle)?;
|
|
}
|
|
Ok(self.ast.jsx_closing_fragment(self.end_span(span)))
|
|
}
|
|
|
|
/// `JSXElement` :
|
|
/// `JSXSelfClosingElement`
|
|
/// `JSXOpeningElement` `JSXChildren_opt` `JSXClosingElement`
|
|
/// `in_jsx_child`:
|
|
/// used for telling `JSXClosingElement` to parse the next jsx child or not
|
|
/// true when inside jsx element, false when at top level expression
|
|
fn parse_jsx_element(&mut self, in_jsx_child: bool) -> Result<Box<'a, JSXElement<'a>>> {
|
|
let span = self.start_span();
|
|
let opening_element = self.parse_jsx_opening_element(span, in_jsx_child)?;
|
|
let children = if opening_element.self_closing {
|
|
self.ast.new_vec()
|
|
} else {
|
|
self.parse_jsx_children()?
|
|
};
|
|
let closing_element = (!opening_element.self_closing)
|
|
.then(|| self.parse_jsx_closing_element(in_jsx_child))
|
|
.transpose()?;
|
|
Ok(self.ast.jsx_element(self.end_span(span), opening_element, closing_element, children))
|
|
}
|
|
|
|
/// `JSXOpeningElement` :
|
|
/// < `JSXElementName` `JSXAttributes_opt` >
|
|
fn parse_jsx_opening_element(
|
|
&mut self,
|
|
span: Span,
|
|
in_jsx_child: bool,
|
|
) -> Result<Box<'a, JSXOpeningElement<'a>>> {
|
|
self.expect(Kind::LAngle)?;
|
|
let name = self.parse_jsx_element_name()?;
|
|
// <Component<TsType> for tsx
|
|
let type_parameters = if self.ts_enabled() {
|
|
let ctx = self.ctx;
|
|
self.ctx = Context::default();
|
|
let args = self.parse_ts_type_arguments()?;
|
|
self.ctx = ctx;
|
|
args
|
|
} 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)?;
|
|
}
|
|
Ok(self.ast.jsx_opening_element(
|
|
self.end_span(span),
|
|
self_closing,
|
|
name,
|
|
attributes,
|
|
type_parameters,
|
|
))
|
|
}
|
|
|
|
fn parse_jsx_closing_element(
|
|
&mut self,
|
|
in_jsx_child: bool,
|
|
) -> Result<Box<'a, JSXClosingElement<'a>>> {
|
|
let span = self.start_span();
|
|
self.expect(Kind::LAngle)?;
|
|
self.expect(Kind::Slash)?;
|
|
let name = self.parse_jsx_element_name()?;
|
|
if in_jsx_child {
|
|
self.expect_jsx_child(Kind::RAngle)?;
|
|
} else {
|
|
self.expect(Kind::RAngle)?;
|
|
}
|
|
Ok(self.ast.jsx_closing_element(self.end_span(span), name))
|
|
}
|
|
|
|
/// `JSXElementName` :
|
|
/// `JSXIdentifier`
|
|
/// `JSXNamespacedName`
|
|
/// `JSXMemberExpression`
|
|
fn parse_jsx_element_name(&mut self) -> Result<JSXElementName<'a>> {
|
|
let span = self.start_span();
|
|
let identifier = self.parse_jsx_identifier()?;
|
|
|
|
// <namespace:property />
|
|
if self.eat(Kind::Colon) {
|
|
let property = self.parse_jsx_identifier()?;
|
|
return Ok(JSXElementName::NamespacedName(self.ast.jsx_namespaced_name(
|
|
self.end_span(span),
|
|
identifier,
|
|
property,
|
|
)));
|
|
}
|
|
|
|
// <member.foo.bar />
|
|
if self.at(Kind::Dot) {
|
|
return self
|
|
.parse_jsx_member_expression(span, identifier)
|
|
.map(JSXElementName::MemberExpression);
|
|
}
|
|
|
|
Ok(JSXElementName::Identifier(self.ast.alloc(identifier)))
|
|
}
|
|
|
|
/// `JSXMemberExpression` :
|
|
/// `JSXIdentifier` . `JSXIdentifier`
|
|
/// `JSXMemberExpression` . `JSXIdentifier`
|
|
fn parse_jsx_member_expression(
|
|
&mut self,
|
|
span: Span,
|
|
object: JSXIdentifier<'a>,
|
|
) -> Result<Box<'a, JSXMemberExpression<'a>>> {
|
|
let mut span = span;
|
|
let mut object = JSXMemberExpressionObject::Identifier(self.ast.alloc(object));
|
|
let mut property = None;
|
|
|
|
while self.eat(Kind::Dot) && !self.at(Kind::Eof) {
|
|
// <foo.bar.baz>
|
|
if let Some(prop) = property {
|
|
let obj = self.ast.jsx_member_expression(span, object, prop);
|
|
object = JSXMemberExpressionObject::MemberExpression(obj);
|
|
}
|
|
|
|
// <foo.bar>
|
|
property = Some(self.parse_jsx_identifier()?);
|
|
span = self.end_span(span);
|
|
}
|
|
|
|
if let Some(property) = property {
|
|
return Ok(self.ast.jsx_member_expression(self.end_span(span), object, property));
|
|
}
|
|
|
|
Err(self.unexpected())
|
|
}
|
|
|
|
/// `JSXChildren` :
|
|
/// `JSXChild` `JSXChildren_opt`
|
|
fn parse_jsx_children(&mut self) -> Result<Vec<'a, JSXChild<'a>>> {
|
|
let mut children = self.ast.new_vec();
|
|
while !self.at(Kind::Eof) {
|
|
if let Some(child) = self.parse_jsx_child()? {
|
|
children.push(child);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
Ok(children)
|
|
}
|
|
|
|
/// `JSXChild` :
|
|
/// `JSXText`
|
|
/// `JSXElement`
|
|
/// `JSXFragment`
|
|
/// { `JSXChildExpression_opt` }
|
|
fn parse_jsx_child(&mut self) -> Result<Option<JSXChild<'a>>> {
|
|
match self.cur_kind() {
|
|
// </ close fragment
|
|
Kind::LAngle if self.peek_at(Kind::Slash) => Ok(None),
|
|
// <> open fragment
|
|
Kind::LAngle if self.peek_at(Kind::RAngle) => {
|
|
self.parse_jsx_fragment(true).map(JSXChild::Fragment).map(Some)
|
|
}
|
|
// <ident open element
|
|
Kind::LAngle if self.peek_at(Kind::Ident) || self.peek_kind().is_all_keyword() => {
|
|
self.parse_jsx_element(true).map(JSXChild::Element).map(Some)
|
|
}
|
|
// {...expr}
|
|
Kind::LCurly if self.peek_at(Kind::Dot3) => {
|
|
self.parse_jsx_spread_child().map(JSXChild::Spread).map(Some)
|
|
}
|
|
// {expr}
|
|
Kind::LCurly => {
|
|
self.parse_jsx_expression_container(/* is_jsx_child */ true)
|
|
.map(JSXChild::ExpressionContainer)
|
|
.map(Some)
|
|
}
|
|
// text
|
|
Kind::JSXText => Ok(Some(JSXChild::Text(self.parse_jsx_text()))),
|
|
_ => Err(self.unexpected()),
|
|
}
|
|
}
|
|
|
|
/// { `JSXChildExpression_opt` }
|
|
fn parse_jsx_expression_container(
|
|
&mut self,
|
|
in_jsx_child: bool,
|
|
) -> Result<Box<'a, JSXExpressionContainer<'a>>> {
|
|
let span = self.start_span();
|
|
self.bump_any(); // bump `{`
|
|
|
|
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);
|
|
// Handle comment between curly braces (ex. `{/* comment */}`)
|
|
// ^^^^^^^^^^^^^ span
|
|
let expr = self.ast.jsx_empty_expression(Span::new(span.start + 1, span.end - 1));
|
|
JSXExpression::EmptyExpression(expr)
|
|
} else {
|
|
let expr = self.parse_jsx_assignment_expression().map(JSXExpression::Expression)?;
|
|
if in_jsx_child {
|
|
self.expect_jsx_child(Kind::RCurly)
|
|
} else {
|
|
self.expect(Kind::RCurly)
|
|
}?;
|
|
expr
|
|
};
|
|
|
|
Ok(self.ast.jsx_expression_container(self.end_span(span), expr))
|
|
}
|
|
|
|
fn parse_jsx_assignment_expression(&mut self) -> Result<Expression<'a>> {
|
|
let ctx = self.ctx;
|
|
self.ctx = Context::default().and_await(ctx.has_await());
|
|
let expr = self.parse_expression();
|
|
if let Ok(Expression::SequenceExpression(seq)) = &expr {
|
|
return Err(diagnostics::JSXExpressionsMayNotUseTheCommaOperator(seq.span).into());
|
|
}
|
|
self.ctx = ctx;
|
|
expr
|
|
}
|
|
|
|
/// `JSXChildExpression` :
|
|
/// { ... `AssignmentExpression` }
|
|
fn parse_jsx_spread_child(&mut self) -> Result<Box<'a, JSXSpreadChild<'a>>> {
|
|
let span = self.start_span();
|
|
self.bump_any(); // bump `{`
|
|
self.expect(Kind::Dot3)?;
|
|
let expr = self.parse_jsx_assignment_expression()?;
|
|
self.expect_jsx_child(Kind::RCurly)?;
|
|
Ok(self.ast.jsx_spread_child(self.end_span(span), expr))
|
|
}
|
|
|
|
/// `JSXAttributes` :
|
|
/// `JSXSpreadAttribute` `JSXAttributes_opt`
|
|
/// `JSXAttribute` `JSXAttributes_opt`
|
|
fn parse_jsx_attributes(&mut self) -> Result<Vec<'a, JSXAttributeItem<'a>>> {
|
|
let mut attributes = self.ast.new_vec();
|
|
while !matches!(self.cur_kind(), Kind::Eof | Kind::LAngle | Kind::RAngle | Kind::Slash) {
|
|
let attribute = match self.cur_kind() {
|
|
Kind::LCurly => {
|
|
self.parse_jsx_spread_attribute().map(JSXAttributeItem::SpreadAttribute)
|
|
}
|
|
_ => self.parse_jsx_attribute().map(JSXAttributeItem::Attribute),
|
|
}?;
|
|
attributes.push(attribute);
|
|
}
|
|
Ok(attributes)
|
|
}
|
|
|
|
/// `JSXAttribute` :
|
|
/// `JSXAttributeName` `JSXAttributeInitializer_opt`
|
|
fn parse_jsx_attribute(&mut self) -> Result<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
|
|
};
|
|
Ok(self.ast.jsx_attribute(self.end_span(span), name, value))
|
|
}
|
|
|
|
/// `JSXSpreadAttribute` :
|
|
/// { ... `AssignmentExpression` }
|
|
fn parse_jsx_spread_attribute(&mut self) -> Result<Box<'a, JSXSpreadAttribute<'a>>> {
|
|
let span = self.start_span();
|
|
self.bump_any(); // bump `{`
|
|
self.expect(Kind::Dot3)?;
|
|
let argument = self.parse_jsx_assignment_expression()?;
|
|
self.expect(Kind::RCurly)?;
|
|
Ok(self.ast.jsx_spread_attribute(self.end_span(span), argument))
|
|
}
|
|
|
|
/// `JSXAttributeName` :
|
|
/// `JSXIdentifier`
|
|
/// `JSXNamespacedName`
|
|
fn parse_jsx_attribute_name(&mut self) -> Result<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 Ok(JSXAttributeName::NamespacedName(self.ast.jsx_namespaced_name(
|
|
self.end_span(span),
|
|
identifier,
|
|
property,
|
|
)));
|
|
}
|
|
|
|
Ok(JSXAttributeName::Identifier(self.ast.alloc(identifier)))
|
|
}
|
|
|
|
fn parse_jsx_attribute_value(&mut self) -> Result<JSXAttributeValue<'a>> {
|
|
match self.cur_kind() {
|
|
Kind::Str => self
|
|
.parse_literal_string()
|
|
.map(|str_lit| JSXAttributeValue::StringLiteral(self.ast.alloc(str_lit))),
|
|
Kind::LCurly => {
|
|
let expr = self.parse_jsx_expression_container(/* is_jsx_child */ false)?;
|
|
Ok(JSXAttributeValue::ExpressionContainer(expr))
|
|
}
|
|
Kind::LAngle => {
|
|
if self.peek_at(Kind::RAngle) {
|
|
self.parse_jsx_fragment(false).map(JSXAttributeValue::Fragment)
|
|
} else {
|
|
self.parse_jsx_element(false).map(JSXAttributeValue::Element)
|
|
}
|
|
}
|
|
_ => Err(self.unexpected()),
|
|
}
|
|
}
|
|
|
|
/// `JSXIdentifier` :
|
|
/// `IdentifierStart`
|
|
/// `JSXIdentifier` `IdentifierPart`
|
|
/// `JSXIdentifier` [no `WhiteSpace` or Comment here] -
|
|
fn parse_jsx_identifier(&mut self) -> Result<JSXIdentifier<'a>> {
|
|
let span = self.start_span();
|
|
if !self.at(Kind::Ident) && !self.cur_kind().is_all_keyword() {
|
|
return Err(self.unexpected());
|
|
}
|
|
// Currently at a valid normal Ident or Keyword, keep on lexing for `-` in `<component-name />`
|
|
self.continue_lex_jsx_identifier();
|
|
self.bump_any();
|
|
let span = self.end_span(span);
|
|
let name = span.source_text(self.source_text);
|
|
Ok(self.ast.jsx_identifier(span, name.into()))
|
|
}
|
|
|
|
fn parse_jsx_text(&mut self) -> Box<'a, JSXText<'a>> {
|
|
let span = self.start_span();
|
|
let value = Atom::from(self.cur_string());
|
|
self.bump_any();
|
|
self.ast.jsx_text(self.end_span(span), value)
|
|
}
|
|
}
|