swc_ecma_lexer/common/parser/
jsx.rs1use either::Either;
2use swc_common::{BytePos, Span, Spanned};
3use swc_ecma_ast::*;
4
5use super::{PResult, Parser};
6use crate::{
7 common::{
8 context::Context,
9 lexer::token::TokenFactory,
10 parser::{
11 buffer::Buffer,
12 eof_error,
13 expr::{parse_assignment_expr, parse_str_lit},
14 get_qualified_jsx_name,
15 ident::parse_ident_ref,
16 typescript::{parse_ts_type_args, try_parse_ts},
17 },
18 },
19 error::SyntaxError,
20};
21
22fn parse_jsx_closing_element_at<'a, P: Parser<'a>>(
24 p: &mut P,
25 start: BytePos,
26) -> PResult<Either<JSXClosingFragment, JSXClosingElement>> {
27 debug_assert!(p.input().syntax().jsx());
28
29 if p.input_mut().eat(&P::Token::JSX_TAG_END) {
30 return Ok(Either::Left(JSXClosingFragment {
31 span: p.span(start),
32 }));
33 }
34
35 let name = parse_jsx_element_name(p)?;
36 expect!(p, &P::Token::JSX_TAG_END);
37 Ok(Either::Right(JSXClosingElement {
38 span: p.span(start),
39 name,
40 }))
41}
42
43pub fn parse_jsx_expr_container<'a, P: Parser<'a>>(p: &mut P) -> PResult<JSXExprContainer> {
45 debug_assert!(p.input().syntax().jsx());
46 debug_assert!(p.input().is(&P::Token::LBRACE));
47
48 let start = p.input().cur_pos();
49 p.bump(); let expr = if p.input().is(&P::Token::RBRACE) {
51 JSXExpr::JSXEmptyExpr(parse_jsx_empty_expr(p))
52 } else {
53 p.parse_expr().map(JSXExpr::Expr)?
54 };
55 expect!(p, &P::Token::RBRACE);
56 Ok(JSXExprContainer {
57 span: p.span(start),
58 expr,
59 })
60}
61
62fn parse_jsx_ident<'a, P: Parser<'a>>(p: &mut P) -> PResult<Ident> {
64 debug_assert!(p.input().syntax().jsx());
65 trace_cur!(p, parse_jsx_ident);
66 let cur = p.input().cur();
67 if cur.is_jsx_name() {
68 let name = p.input_mut().expect_jsx_name_token_and_bump();
69 let span = p.input().prev_span();
70 Ok(Ident::new_no_ctxt(name, span))
71 } else if p.ctx().contains(Context::InForcedJsxContext) {
72 parse_ident_ref(p)
73 } else {
74 unexpected!(p, "jsx identifier")
75 }
76}
77
78fn parse_jsx_namespaced_name<'a, P: Parser<'a>>(p: &mut P) -> PResult<JSXAttrName> {
80 debug_assert!(p.input().syntax().jsx());
81 trace_cur!(p, parse_jsx_namespaced_name);
82 let start = p.input().cur_pos();
83 let ns = parse_jsx_ident(p)?.into();
84 if !p.input_mut().eat(&P::Token::COLON) {
85 return Ok(JSXAttrName::Ident(ns));
86 }
87 let name = parse_jsx_ident(p).map(IdentName::from)?;
88 Ok(JSXAttrName::JSXNamespacedName(JSXNamespacedName {
89 span: Span::new_with_checked(start, name.span.hi),
90 ns,
91 name,
92 }))
93}
94
95fn parse_jsx_element_name<'a, P: Parser<'a>>(p: &mut P) -> PResult<JSXElementName> {
98 debug_assert!(p.input().syntax().jsx());
99 trace_cur!(p, parse_jsx_element_name);
100 let start = p.input().cur_pos();
101 let mut node = match parse_jsx_namespaced_name(p)? {
102 JSXAttrName::Ident(i) => JSXElementName::Ident(i.into()),
103 JSXAttrName::JSXNamespacedName(i) => JSXElementName::JSXNamespacedName(i),
104 #[cfg(swc_ast_unknown)]
105 _ => unreachable!(),
106 };
107 while p.input_mut().eat(&P::Token::DOT) {
108 let prop = parse_jsx_ident(p).map(IdentName::from)?;
109 let new_node = JSXElementName::JSXMemberExpr(JSXMemberExpr {
110 span: p.span(start),
111 obj: match node {
112 JSXElementName::Ident(i) => JSXObject::Ident(i),
113 JSXElementName::JSXMemberExpr(i) => JSXObject::JSXMemberExpr(Box::new(i)),
114 _ => unimplemented!("JSXNamespacedName -> JSXObject"),
115 },
116 prop,
117 });
118 node = new_node;
119 }
120 Ok(node)
121}
122
123pub fn parse_jsx_empty_expr<'a>(p: &mut impl Parser<'a>) -> JSXEmptyExpr {
127 debug_assert!(p.input().syntax().jsx());
128 let start = p.input().cur_pos();
129 JSXEmptyExpr {
130 span: Span::new_with_checked(start, start),
131 }
132}
133
134pub fn parse_jsx_text<'a>(p: &mut impl Parser<'a>) -> JSXText {
135 debug_assert!(p.input().syntax().jsx());
136
137 let cur = p.input().cur();
138 debug_assert!(cur.is_jsx_text());
139 let (value, raw) = p.input_mut().expect_jsx_text_token_and_bump();
140 let span = p.input().prev_span();
141 JSXText { span, value, raw }
142}
143
144pub fn jsx_expr_container_to_jsx_attr_value<'a, P: Parser<'a>>(
145 p: &mut P,
146 start: BytePos,
147 node: JSXExprContainer,
148) -> PResult<JSXAttrValue> {
149 match node.expr {
150 JSXExpr::JSXEmptyExpr(..) => {
151 syntax_error!(p, p.span(start), SyntaxError::EmptyJSXAttr)
152 }
153 JSXExpr::Expr(..) => Ok(node.into()),
154 #[cfg(swc_ast_unknown)]
155 _ => unreachable!(),
156 }
157}
158
159fn parse_jsx_attr_value<'a, P: Parser<'a>>(p: &mut P) -> PResult<JSXAttrValue> {
163 debug_assert!(p.input().syntax().jsx());
164 trace_cur!(p, parse_jsx_attr_value);
165
166 let start = p.cur_pos();
167
168 let cur = p.input().cur();
169 if cur.is_lbrace() {
170 let node = parse_jsx_expr_container(p)?;
171 jsx_expr_container_to_jsx_attr_value(p, start, node)
172 } else if cur.is_str() {
173 Ok(JSXAttrValue::Lit(Lit::Str(parse_str_lit(p))))
174 } else if cur.is_jsx_tag_start() {
175 let expr = parse_jsx_element(p)?;
176 match expr {
177 Either::Left(n) => Ok(JSXAttrValue::JSXFragment(n)),
178 Either::Right(n) => Ok(JSXAttrValue::JSXElement(Box::new(n))),
179 }
180 } else {
181 let span = p.input().cur_span();
182 syntax_error!(p, span, SyntaxError::InvalidJSXValue)
183 }
184}
185
186fn parse_jsx_spread_child<'a, P: Parser<'a>>(p: &mut P) -> PResult<JSXSpreadChild> {
188 debug_assert!(p.input().syntax().jsx());
189 debug_assert!(p.input().cur().is_lbrace());
190 debug_assert!(peek!(p).is_some_and(|peek| peek.is_dotdotdot()));
191
192 let start = p.cur_pos();
193 p.bump(); p.bump(); let expr = p.parse_expr()?;
196 expect!(p, &P::Token::RBRACE);
197
198 Ok(JSXSpreadChild {
199 span: p.span(start),
200 expr,
201 })
202}
203
204fn parse_jsx_attr<'a, P: Parser<'a>>(p: &mut P) -> PResult<JSXAttrOrSpread> {
206 debug_assert!(p.input().syntax().jsx());
207 let start = p.cur_pos();
208
209 debug_tracing!(p, "parse_jsx_attr");
210
211 if p.input_mut().eat(&P::Token::LBRACE) {
212 let dot3_start = p.cur_pos();
213 expect!(p, &P::Token::DOTDOTDOT);
214 let dot3_token = p.span(dot3_start);
215 let expr = parse_assignment_expr(p)?;
216 expect!(p, &P::Token::RBRACE);
217 return Ok(SpreadElement { dot3_token, expr }.into());
218 }
219
220 let name = parse_jsx_namespaced_name(p)?;
221 let value = if p.input_mut().eat(&P::Token::EQUAL) {
222 p.do_outside_of_context(
223 Context::InCondExpr.union(Context::WillExpectColonForCond),
224 parse_jsx_attr_value,
225 )
226 .map(Some)?
227 } else {
228 None
229 };
230
231 Ok(JSXAttr {
232 span: p.span(start),
233 name,
234 value,
235 }
236 .into())
237}
238
239fn parse_jsx_opening_element_at<'a, P: Parser<'a>>(
241 p: &mut P,
242 start: BytePos,
243) -> PResult<Either<JSXOpeningFragment, JSXOpeningElement>> {
244 debug_assert!(p.input().syntax().jsx());
245
246 if p.input_mut().eat(&P::Token::JSX_TAG_END) {
247 return Ok(Either::Left(JSXOpeningFragment {
248 span: p.span(start),
249 }));
250 }
251
252 let name =
253 p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, parse_jsx_element_name)?;
254 parse_jsx_opening_element_after_name(p, start, name).map(Either::Right)
255}
256
257#[inline(always)]
258fn parse_jsx_attrs<'a, P: Parser<'a>>(p: &mut P) -> PResult<Vec<JSXAttrOrSpread>> {
259 let mut attrs = Vec::with_capacity(8);
260
261 while !p.input().cur().is_eof() {
262 trace_cur!(p, parse_jsx_opening__attrs_loop);
263
264 let cur = p.input().cur();
265 if cur.is_slash() || cur.is_jsx_tag_end() {
266 break;
267 }
268
269 let attr = parse_jsx_attr(p)?;
270 attrs.push(attr);
271 }
272
273 Ok(attrs)
274}
275
276fn parse_jsx_opening_element_after_name<'a, P: Parser<'a>>(
278 p: &mut P,
279 start: BytePos,
280 name: JSXElementName,
281) -> PResult<JSXOpeningElement> {
282 debug_assert!(p.input().syntax().jsx());
283
284 let type_args = if p.input().syntax().typescript() && p.input().is(&P::Token::LESS) {
285 try_parse_ts(p, |p| {
286 let ret = parse_ts_type_args(p)?;
287 p.assert_and_bump(&P::Token::GREATER);
288 Ok(Some(ret))
289 })
290 } else {
291 None
292 };
293
294 let attrs = parse_jsx_attrs(p)?;
295
296 let self_closing = p.input_mut().eat(&P::Token::DIV);
297 if !p.input_mut().eat(&P::Token::JSX_TAG_END)
298 & !(p.ctx().contains(Context::InForcedJsxContext) && p.input_mut().eat(&P::Token::GREATER))
299 {
300 unexpected!(p, "> (jsx closing tag)");
301 }
302 Ok(JSXOpeningElement {
303 span: p.span(start),
304 name,
305 attrs,
306 self_closing,
307 type_args,
308 })
309}
310
311fn parse_jsx_element_at<'a, P: Parser<'a>>(
316 p: &mut P,
317 start_pos: BytePos,
318) -> PResult<Either<JSXFragment, JSXElement>> {
319 debug_assert!(p.input().syntax().jsx());
320
321 let cur = p.input().cur();
322 if cur.is_error() {
323 let error = p.input_mut().expect_error_token_and_bump();
324 return Err(error);
325 } else if cur.is_eof() {
326 return Err(eof_error(p));
327 }
328 let forced_jsx_context = if cur.is_less() {
329 true
330 } else {
331 debug_assert!(cur.is_jsx_tag_start());
332 false
333 };
334 let start = p.cur_pos();
335 p.bump();
336
337 p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| {
338 let f = |p: &mut P| {
339 debug_tracing!(p, "parse_jsx_element");
340
341 let opening_element = parse_jsx_opening_element_at(p, start_pos)?;
342
343 trace_cur!(p, parse_jsx_element__after_opening_element);
344
345 let mut children = Vec::new();
346 let mut closing_element = None;
347
348 let self_closing = match opening_element {
349 Either::Right(ref el) => el.self_closing,
350 _ => false,
351 };
352
353 if !self_closing {
354 'contents: loop {
355 let cur = p.input().cur();
356 if cur.is_jsx_tag_start() {
357 let start = p.cur_pos();
358 if peek!(p).is_some_and(|peek| peek.is_slash()) {
359 p.bump(); if p.input().cur().is_eof() {
361 return Err(eof_error(p));
362 }
363 p.assert_and_bump(&P::Token::DIV);
364 closing_element = parse_jsx_closing_element_at(p, start).map(Some)?;
365 break 'contents;
366 }
367 children.push(parse_jsx_element_at(p, start).map(|e| match e {
368 Either::Left(e) => JSXElementChild::from(e),
369 Either::Right(e) => JSXElementChild::from(Box::new(e)),
370 })?);
371 } else if cur.is_jsx_text() {
372 children.push(JSXElementChild::from(parse_jsx_text(p)))
373 } else if cur.is_lbrace() {
374 if peek!(p).is_some_and(|peek| peek.is_dotdotdot()) {
375 children.push(parse_jsx_spread_child(p).map(JSXElementChild::from)?);
376 } else {
377 children.push(parse_jsx_expr_container(p).map(JSXElementChild::from)?);
378 }
379 } else {
380 unexpected!(p, "< (jsx tag start), jsx text or {")
381 }
382 }
383 }
384 let span = p.span(start);
385
386 Ok(match (opening_element, closing_element) {
387 (Either::Left(..), Some(Either::Right(closing))) => {
388 syntax_error!(p, closing.span(), SyntaxError::JSXExpectedClosingTagForLtGt);
389 }
390 (Either::Right(opening), Some(Either::Left(closing))) => {
391 syntax_error!(
392 p,
393 closing.span(),
394 SyntaxError::JSXExpectedClosingTag {
395 tag: get_qualified_jsx_name(&opening.name)
396 }
397 );
398 }
399 (Either::Left(opening), Some(Either::Left(closing))) => Either::Left(JSXFragment {
400 span,
401 opening,
402 children,
403 closing,
404 }),
405 (Either::Right(opening), None) => Either::Right(JSXElement {
406 span,
407 opening,
408 children,
409 closing: None,
410 }),
411 (Either::Right(opening), Some(Either::Right(closing))) => {
412 if get_qualified_jsx_name(&closing.name)
413 != get_qualified_jsx_name(&opening.name)
414 {
415 syntax_error!(
416 p,
417 closing.span(),
418 SyntaxError::JSXExpectedClosingTag {
419 tag: get_qualified_jsx_name(&opening.name)
420 }
421 );
422 }
423 Either::Right(JSXElement {
424 span,
425 opening,
426 children,
427 closing: Some(closing),
428 })
429 }
430 _ => unreachable!(),
431 })
432 };
433 if forced_jsx_context {
434 p.do_inside_of_context(Context::InForcedJsxContext, f)
435 } else {
436 p.do_outside_of_context(Context::InForcedJsxContext, f)
437 }
438 })
439}
440
441pub(crate) fn parse_jsx_element<'a, P: Parser<'a>>(
445 p: &mut P,
446) -> PResult<Either<JSXFragment, JSXElement>> {
447 trace_cur!(p, parse_jsx_element);
448
449 debug_assert!(p.input().syntax().jsx());
450 debug_assert!({
451 let cur = p.input().cur();
452 cur.is_jsx_tag_start() || cur.is_less()
453 });
454
455 let start_pos = p.cur_pos();
456
457 p.do_outside_of_context(
458 Context::InCondExpr.union(Context::WillExpectColonForCond),
459 |p| parse_jsx_element_at(p, start_pos),
460 )
461}