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 };
105 while p.input_mut().eat(&P::Token::DOT) {
106 let prop = parse_jsx_ident(p).map(IdentName::from)?;
107 let new_node = JSXElementName::JSXMemberExpr(JSXMemberExpr {
108 span: p.span(start),
109 obj: match node {
110 JSXElementName::Ident(i) => JSXObject::Ident(i),
111 JSXElementName::JSXMemberExpr(i) => JSXObject::JSXMemberExpr(Box::new(i)),
112 _ => unimplemented!("JSXNamespacedName -> JSXObject"),
113 },
114 prop,
115 });
116 node = new_node;
117 }
118 Ok(node)
119}
120
121pub fn parse_jsx_empty_expr<'a>(p: &mut impl Parser<'a>) -> JSXEmptyExpr {
125 debug_assert!(p.input().syntax().jsx());
126 let start = p.input().cur_pos();
127 JSXEmptyExpr {
128 span: Span::new_with_checked(start, start),
129 }
130}
131
132pub fn parse_jsx_text<'a>(p: &mut impl Parser<'a>) -> JSXText {
133 debug_assert!(p.input().syntax().jsx());
134
135 let cur = p.input().cur();
136 debug_assert!(cur.is_jsx_text());
137 let (value, raw) = p.input_mut().expect_jsx_text_token_and_bump();
138 let span = p.input().prev_span();
139 JSXText { span, value, raw }
140}
141
142pub fn jsx_expr_container_to_jsx_attr_value<'a, P: Parser<'a>>(
143 p: &mut P,
144 start: BytePos,
145 node: JSXExprContainer,
146) -> PResult<JSXAttrValue> {
147 match node.expr {
148 JSXExpr::JSXEmptyExpr(..) => {
149 syntax_error!(p, p.span(start), SyntaxError::EmptyJSXAttr)
150 }
151 JSXExpr::Expr(..) => Ok(node.into()),
152 }
153}
154
155fn parse_jsx_attr_value<'a, P: Parser<'a>>(p: &mut P) -> PResult<JSXAttrValue> {
159 debug_assert!(p.input().syntax().jsx());
160 trace_cur!(p, parse_jsx_attr_value);
161
162 let start = p.cur_pos();
163
164 let cur = p.input().cur();
165 if cur.is_lbrace() {
166 let node = parse_jsx_expr_container(p)?;
167 jsx_expr_container_to_jsx_attr_value(p, start, node)
168 } else if cur.is_str() {
169 Ok(JSXAttrValue::Lit(Lit::Str(parse_str_lit(p))))
170 } else if cur.is_jsx_tag_start() {
171 let expr = parse_jsx_element(p)?;
172 match expr {
173 Either::Left(n) => Ok(JSXAttrValue::JSXFragment(n)),
174 Either::Right(n) => Ok(JSXAttrValue::JSXElement(Box::new(n))),
175 }
176 } else {
177 let span = p.input().cur_span();
178 syntax_error!(p, span, SyntaxError::InvalidJSXValue)
179 }
180}
181
182fn parse_jsx_spread_child<'a, P: Parser<'a>>(p: &mut P) -> PResult<JSXSpreadChild> {
184 debug_assert!(p.input().syntax().jsx());
185 debug_assert!(p.input().cur().is_lbrace());
186 debug_assert!(peek!(p).is_some_and(|peek| peek.is_dotdotdot()));
187
188 let start = p.cur_pos();
189 p.bump(); p.bump(); let expr = p.parse_expr()?;
192 expect!(p, &P::Token::RBRACE);
193
194 Ok(JSXSpreadChild {
195 span: p.span(start),
196 expr,
197 })
198}
199
200fn parse_jsx_attr<'a, P: Parser<'a>>(p: &mut P) -> PResult<JSXAttrOrSpread> {
202 debug_assert!(p.input().syntax().jsx());
203 let start = p.cur_pos();
204
205 debug_tracing!(p, "parse_jsx_attr");
206
207 if p.input_mut().eat(&P::Token::LBRACE) {
208 let dot3_start = p.cur_pos();
209 expect!(p, &P::Token::DOTDOTDOT);
210 let dot3_token = p.span(dot3_start);
211 let expr = parse_assignment_expr(p)?;
212 expect!(p, &P::Token::RBRACE);
213 return Ok(SpreadElement { dot3_token, expr }.into());
214 }
215
216 let name = parse_jsx_namespaced_name(p)?;
217 let value = if p.input_mut().eat(&P::Token::EQUAL) {
218 p.do_outside_of_context(
219 Context::InCondExpr.union(Context::WillExpectColonForCond),
220 parse_jsx_attr_value,
221 )
222 .map(Some)?
223 } else {
224 None
225 };
226
227 Ok(JSXAttr {
228 span: p.span(start),
229 name,
230 value,
231 }
232 .into())
233}
234
235fn parse_jsx_opening_element_at<'a, P: Parser<'a>>(
237 p: &mut P,
238 start: BytePos,
239) -> PResult<Either<JSXOpeningFragment, JSXOpeningElement>> {
240 debug_assert!(p.input().syntax().jsx());
241
242 if p.input_mut().eat(&P::Token::JSX_TAG_END) {
243 return Ok(Either::Left(JSXOpeningFragment {
244 span: p.span(start),
245 }));
246 }
247
248 let name =
249 p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, parse_jsx_element_name)?;
250 parse_jsx_opening_element_after_name(p, start, name).map(Either::Right)
251}
252
253#[inline(always)]
254fn parse_jsx_attrs<'a, P: Parser<'a>>(p: &mut P) -> PResult<Vec<JSXAttrOrSpread>> {
255 let mut attrs = Vec::with_capacity(8);
256
257 while !p.input().cur().is_eof() {
258 trace_cur!(p, parse_jsx_opening__attrs_loop);
259
260 let cur = p.input().cur();
261 if cur.is_slash() || cur.is_jsx_tag_end() {
262 break;
263 }
264
265 let attr = parse_jsx_attr(p)?;
266 attrs.push(attr);
267 }
268
269 Ok(attrs)
270}
271
272fn parse_jsx_opening_element_after_name<'a, P: Parser<'a>>(
274 p: &mut P,
275 start: BytePos,
276 name: JSXElementName,
277) -> PResult<JSXOpeningElement> {
278 debug_assert!(p.input().syntax().jsx());
279
280 let type_args = if p.input().syntax().typescript() && p.input().is(&P::Token::LESS) {
281 try_parse_ts(p, |p| {
282 let ret = parse_ts_type_args(p)?;
283 p.assert_and_bump(&P::Token::GREATER);
284 Ok(Some(ret))
285 })
286 } else {
287 None
288 };
289
290 let attrs = parse_jsx_attrs(p)?;
291
292 let self_closing = p.input_mut().eat(&P::Token::DIV);
293 if !p.input_mut().eat(&P::Token::JSX_TAG_END)
294 & !(p.ctx().contains(Context::InForcedJsxContext) && p.input_mut().eat(&P::Token::GREATER))
295 {
296 unexpected!(p, "> (jsx closing tag)");
297 }
298 Ok(JSXOpeningElement {
299 span: p.span(start),
300 name,
301 attrs,
302 self_closing,
303 type_args,
304 })
305}
306
307fn parse_jsx_element_at<'a, P: Parser<'a>>(
312 p: &mut P,
313 start_pos: BytePos,
314) -> PResult<Either<JSXFragment, JSXElement>> {
315 debug_assert!(p.input().syntax().jsx());
316
317 let cur = p.input().cur();
318 if cur.is_error() {
319 let error = p.input_mut().expect_error_token_and_bump();
320 return Err(error);
321 } else if cur.is_eof() {
322 return Err(eof_error(p));
323 }
324 let forced_jsx_context = if cur.is_less() {
325 true
326 } else {
327 debug_assert!(cur.is_jsx_tag_start());
328 false
329 };
330 let start = p.cur_pos();
331 p.bump();
332
333 p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| {
334 let f = |p: &mut P| {
335 debug_tracing!(p, "parse_jsx_element");
336
337 let opening_element = parse_jsx_opening_element_at(p, start_pos)?;
338
339 trace_cur!(p, parse_jsx_element__after_opening_element);
340
341 let mut children = Vec::new();
342 let mut closing_element = None;
343
344 let self_closing = match opening_element {
345 Either::Right(ref el) => el.self_closing,
346 _ => false,
347 };
348
349 if !self_closing {
350 'contents: loop {
351 let cur = p.input().cur();
352 if cur.is_jsx_tag_start() {
353 let start = p.cur_pos();
354 if peek!(p).is_some_and(|peek| peek.is_slash()) {
355 p.bump(); if p.input().cur().is_eof() {
357 return Err(eof_error(p));
358 }
359 p.assert_and_bump(&P::Token::DIV);
360 closing_element = parse_jsx_closing_element_at(p, start).map(Some)?;
361 break 'contents;
362 }
363 children.push(parse_jsx_element_at(p, start).map(|e| match e {
364 Either::Left(e) => JSXElementChild::from(e),
365 Either::Right(e) => JSXElementChild::from(Box::new(e)),
366 })?);
367 } else if cur.is_jsx_text() {
368 children.push(JSXElementChild::from(parse_jsx_text(p)))
369 } else if cur.is_lbrace() {
370 if peek!(p).is_some_and(|peek| peek.is_dotdotdot()) {
371 children.push(parse_jsx_spread_child(p).map(JSXElementChild::from)?);
372 } else {
373 children.push(parse_jsx_expr_container(p).map(JSXElementChild::from)?);
374 }
375 } else {
376 unexpected!(p, "< (jsx tag start), jsx text or {")
377 }
378 }
379 }
380 let span = p.span(start);
381
382 Ok(match (opening_element, closing_element) {
383 (Either::Left(..), Some(Either::Right(closing))) => {
384 syntax_error!(p, closing.span(), SyntaxError::JSXExpectedClosingTagForLtGt);
385 }
386 (Either::Right(opening), Some(Either::Left(closing))) => {
387 syntax_error!(
388 p,
389 closing.span(),
390 SyntaxError::JSXExpectedClosingTag {
391 tag: get_qualified_jsx_name(&opening.name)
392 }
393 );
394 }
395 (Either::Left(opening), Some(Either::Left(closing))) => Either::Left(JSXFragment {
396 span,
397 opening,
398 children,
399 closing,
400 }),
401 (Either::Right(opening), None) => Either::Right(JSXElement {
402 span,
403 opening,
404 children,
405 closing: None,
406 }),
407 (Either::Right(opening), Some(Either::Right(closing))) => {
408 if get_qualified_jsx_name(&closing.name)
409 != get_qualified_jsx_name(&opening.name)
410 {
411 syntax_error!(
412 p,
413 closing.span(),
414 SyntaxError::JSXExpectedClosingTag {
415 tag: get_qualified_jsx_name(&opening.name)
416 }
417 );
418 }
419 Either::Right(JSXElement {
420 span,
421 opening,
422 children,
423 closing: Some(closing),
424 })
425 }
426 _ => unreachable!(),
427 })
428 };
429 if forced_jsx_context {
430 p.do_inside_of_context(Context::InForcedJsxContext, f)
431 } else {
432 p.do_outside_of_context(Context::InForcedJsxContext, f)
433 }
434 })
435}
436
437pub(crate) fn parse_jsx_element<'a, P: Parser<'a>>(
441 p: &mut P,
442) -> PResult<Either<JSXFragment, JSXElement>> {
443 trace_cur!(p, parse_jsx_element);
444
445 debug_assert!(p.input().syntax().jsx());
446 debug_assert!({
447 let cur = p.input().cur();
448 cur.is_jsx_tag_start() || cur.is_less()
449 });
450
451 let start_pos = p.cur_pos();
452
453 p.do_outside_of_context(
454 Context::InCondExpr.union(Context::WillExpectColonForCond),
455 |p| parse_jsx_element_at(p, start_pos),
456 )
457}