1use swc_atoms::Atom;
2use swc_common::{BytePos, Span, Spanned};
3use swc_ecma_ast::*;
4
5use super::{input::Tokens, Parser};
6use crate::{
7 error::SyntaxError,
8 lexer::{Token, TokenFlags},
9 Context, PResult,
10};
11
12impl<I: Tokens> Parser<I> {
13 fn parse_jsx_expr_container(&mut self) -> PResult<JSXExprContainer> {
15 debug_assert!(self.input().syntax().jsx());
16 debug_assert!(self.input().is(Token::LBrace));
17
18 let start = self.input().cur_pos();
19 self.bump(); let expr = if self.input().is(Token::RBrace) {
21 JSXExpr::JSXEmptyExpr(self.parse_jsx_empty_expr())
22 } else {
23 self.parse_expr().map(JSXExpr::Expr)?
24 };
25 expect!(self, Token::RBrace);
26 Ok(JSXExprContainer {
27 span: self.span(start),
28 expr,
29 })
30 }
31
32 fn parse_jsx_empty_expr(&mut self) -> JSXEmptyExpr {
36 debug_assert!(self.input().syntax().jsx());
37 let start = self.input().cur_pos();
38 JSXEmptyExpr {
39 span: Span::new_with_checked(start, start),
40 }
41 }
42
43 fn jsx_expr_container_to_jsx_attr_value(
44 &mut self,
45 start: BytePos,
46 node: JSXExprContainer,
47 ) -> PResult<JSXAttrValue> {
48 match node.expr {
49 JSXExpr::JSXEmptyExpr(..) => {
50 syntax_error!(self, self.span(start), SyntaxError::EmptyJSXAttr)
51 }
52 JSXExpr::Expr(..) => Ok(node.into()),
53 #[cfg(swc_ast_unknown)]
54 _ => unreachable!(),
55 }
56 }
57
58 fn parse_jsx_text(&mut self) -> JSXText {
59 debug_assert!(self.input().syntax().jsx());
60 let cur = self.input_mut().cur();
61 debug_assert!(cur == Token::JSXText);
62 let (value, raw) = cur.take_jsx_text(self.input_mut());
63 self.input_mut().scan_jsx_token(true);
64 let span = self.input().prev_span();
65 JSXText { span, value, raw }
66 }
67
68 fn parse_jsx_ident(&mut self) -> PResult<Ident> {
69 debug_assert!(self.input().syntax().jsx());
70 trace_cur!(self, parse_jsx_ident);
71 let cur = self.input().cur();
72 if cur == Token::JSXName || cur == Token::Ident {
73 if self.input().token_flags().contains(TokenFlags::UNICODE) {
74 syntax_error!(
75 self,
76 self.input().cur_span(),
77 SyntaxError::InvalidUnicodeEscape
78 );
79 }
80 let name = cur.take_jsx_name(self.input_mut());
81 self.bump();
82 let span = self.input().prev_span();
83 Ok(Ident::new_no_ctxt(name, span))
84 } else {
85 unexpected!(self, "jsx identifier")
86 }
87 }
88
89 fn parse_jsx_tag_name(&mut self) -> PResult<JSXAttrName> {
90 debug_assert!(self.input().syntax().jsx());
91 trace_cur!(self, parse_jsx_tag_name);
92 let start = self.input().cur_pos();
93 self.input_mut().scan_jsx_identifier();
94
95 let ns = self.parse_jsx_ident()?.into();
96 Ok(if self.input_mut().eat(Token::Colon) {
97 self.input_mut().scan_jsx_identifier();
98 let name: IdentName = self.parse_jsx_ident()?.into();
99 JSXAttrName::JSXNamespacedName(JSXNamespacedName {
100 span: Span::new_with_checked(start, name.span.hi),
101 ns,
102 name,
103 })
104 } else {
105 JSXAttrName::Ident(ns)
106 })
107 }
108
109 fn parse_jsx_element_name(&mut self) -> PResult<JSXElementName> {
110 debug_assert!(self.input().syntax().jsx());
111 trace_cur!(self, parse_jsx_element_name);
112 let start = self.input().cur_pos();
113 let mut node = match self.parse_jsx_tag_name()? {
114 JSXAttrName::Ident(i) => JSXElementName::Ident(i.into()),
115 JSXAttrName::JSXNamespacedName(i) => JSXElementName::JSXNamespacedName(i),
116 #[cfg(swc_ast_unknown)]
117 _ => unreachable!(),
118 };
119 while self.input_mut().eat(Token::Dot) {
120 self.input_mut().scan_jsx_identifier();
121 let prop: IdentName = self.parse_jsx_ident()?.into();
122 let new_node = JSXElementName::JSXMemberExpr(JSXMemberExpr {
123 span: self.span(start),
124 obj: match node {
125 JSXElementName::Ident(i) => JSXObject::Ident(i),
126 JSXElementName::JSXMemberExpr(i) => JSXObject::JSXMemberExpr(Box::new(i)),
127 _ => unreachable!("JSXNamespacedName -> JSXObject"),
128 },
129 prop,
130 });
131 node = new_node;
132 }
133 Ok(node)
134 }
135
136 fn parse_jsx_closing_element(
137 &mut self,
138 in_expr_context: bool,
139 open_name: &JSXElementName,
140 ) -> PResult<JSXClosingElement> {
141 let start = self.cur_pos();
142 self.expect(Token::LessSlash)?;
143 let tagname = self.parse_jsx_element_name()?;
144
145 self.input_mut().rescan_jsx_open_el_terminal_token();
149 self.expect_without_advance(Token::Gt)?;
150
151 if in_expr_context {
152 self.bump();
153 } else {
154 self.input_mut().scan_jsx_token(true);
155 }
156
157 if get_qualified_jsx_name(open_name) != get_qualified_jsx_name(&tagname) {
158 syntax_error!(
159 self,
160 tagname.span(),
161 SyntaxError::JSXExpectedClosingTag {
162 tag: get_qualified_jsx_name(open_name),
163 }
164 )
165 }
166
167 let span = self.span(start);
168 Ok(JSXClosingElement {
169 span,
170 name: tagname,
171 })
172 }
173
174 fn parse_jsx_closing_fragment(&mut self, in_expr_context: bool) -> PResult<JSXClosingFragment> {
175 let start = self.cur_pos();
176 self.expect(Token::LessSlash)?;
177
178 self.input_mut().rescan_jsx_open_el_terminal_token();
182 self.expect_without_advance(Token::Gt)?;
183
184 if in_expr_context {
185 self.bump();
186 } else {
187 self.input_mut().scan_jsx_token(true);
188 }
189 let span = self.span(start);
190 Ok(JSXClosingFragment { span })
191 }
192
193 fn parse_jsx_children(&mut self) -> Vec<JSXElementChild> {
194 let mut list = Vec::with_capacity(8);
195 loop {
196 self.input_mut().rescan_jsx_token(true);
197 let Ok(Some(child)) = self.parse_jsx_child(self.input().get_cur().token) else {
198 break;
199 };
200 list.push(child);
201 }
202 list
203 }
204
205 fn parse_jsx_child(&mut self, t: Token) -> PResult<Option<JSXElementChild>> {
206 debug_assert!(self.input().syntax().jsx());
207
208 match t {
209 Token::LessSlash => Ok(None),
210 Token::LBrace => Ok(Some({
211 self.do_outside_of_context(
212 Context::InCondExpr.union(Context::WillExpectColonForCond),
213 |p| {
214 let start = p.cur_pos();
215 p.bump(); let ret = if p.input().cur() == Token::DotDotDot {
217 p.bump(); let expr = p.parse_expr()?;
219 p.expect_without_advance(Token::RBrace)?;
220 p.input_mut().scan_jsx_token(true);
221 JSXElementChild::JSXSpreadChild(JSXSpreadChild {
222 span: p.span(start),
223 expr,
224 })
225 } else {
226 let expr = if p.input().cur() == Token::RBrace {
227 JSXExpr::JSXEmptyExpr(p.parse_jsx_empty_expr())
228 } else {
229 p.parse_expr().map(JSXExpr::Expr)?
230 };
231 p.expect_without_advance(Token::RBrace)?;
232 p.input_mut().scan_jsx_token(true);
233 JSXElementChild::JSXExprContainer(JSXExprContainer {
234 span: p.span(start),
235 expr,
236 })
237 };
238 Ok(ret)
239 },
240 )?
241 })),
242 Token::Lt => {
243 let ele = self.parse_jsx_element(false)?;
244 match ele {
245 either::Either::Left(frag) => Ok(Some(JSXElementChild::JSXFragment(frag))),
246 either::Either::Right(ele) => {
247 Ok(Some(JSXElementChild::JSXElement(Box::new(ele))))
248 }
249 }
250 }
251 Token::JSXText => Ok(Some(JSXElementChild::JSXText(self.parse_jsx_text()))),
252 Token::Eof => {
253 unexpected!(self, "< (jsx tag start), jsx text or {")
254 }
255 _ => unreachable!(),
256 }
257 }
258
259 fn parse_jsx_attr_name(&mut self) -> PResult<JSXAttrName> {
260 debug_assert!(self.input().syntax().jsx());
261 trace_cur!(self, parse_jsx_attr_name);
262 let start = self.input().cur_pos();
263 self.input_mut().scan_jsx_identifier();
264
265 let attr_name = self.parse_jsx_ident()?;
266 if self.input_mut().eat(Token::Colon) {
267 self.input_mut().scan_jsx_identifier();
268 let name = self.parse_jsx_ident()?;
269 Ok(JSXAttrName::JSXNamespacedName(JSXNamespacedName {
270 span: Span::new_with_checked(start, name.span.hi),
271 ns: attr_name.into(),
272 name: name.into(),
273 }))
274 } else {
275 Ok(JSXAttrName::Ident(attr_name.into()))
276 }
277 }
278
279 fn parse_jsx_attr_value(&mut self) -> PResult<Option<JSXAttrValue>> {
280 debug_assert!(self.input().syntax().jsx());
281 trace_cur!(self, parse_jsx_attr_value);
282 if self.input().is(Token::Eq) {
283 self.input_mut().scan_jsx_attribute_value();
284 let cur = self.input().get_cur();
285 match cur.token {
286 Token::Str => {
287 let value = self.parse_str_lit();
288 Ok(Some(JSXAttrValue::Str(value)))
289 }
290 Token::LBrace => {
291 let start = self.cur_pos();
292 let node = self.parse_jsx_expr_container()?;
293 self.jsx_expr_container_to_jsx_attr_value(start, node)
294 .map(Some)
295 }
296 Token::Lt => match self.parse_jsx_element(true)? {
297 either::Either::Left(frag) => Ok(Some(JSXAttrValue::JSXFragment(frag))),
298 either::Either::Right(ele) => Ok(Some(JSXAttrValue::JSXElement(Box::new(ele)))),
299 },
300 _ => {
301 let span = self.input().cur_span();
302 syntax_error!(self, span, SyntaxError::InvalidJSXValue)
303 }
304 }
305 } else {
306 Ok(None)
307 }
308 }
309
310 fn parse_jsx_attr(&mut self) -> PResult<JSXAttrOrSpread> {
311 debug_assert!(self.input().syntax().jsx());
312 trace_cur!(self, parse_jsx_attr);
313 if self.input_mut().eat(Token::LBrace) {
314 let dot3_start = self.input().cur_pos();
315 self.expect(Token::DotDotDot)?;
316 let dot3_token = self.span(dot3_start);
317 let expr = self.parse_assignment_expr()?;
318 self.expect(Token::RBrace)?;
319 Ok(JSXAttrOrSpread::SpreadElement(SpreadElement {
320 dot3_token,
321 expr,
322 }))
323 } else {
324 let start = self.input().cur_pos();
325 let name = self.parse_jsx_attr_name()?;
326 let value = self.do_outside_of_context(
327 Context::InCondExpr.union(Context::WillExpectColonForCond),
328 |p| p.parse_jsx_attr_value(),
329 )?;
330 Ok(JSXAttrOrSpread::JSXAttr(JSXAttr {
331 span: self.span(start),
332 name,
333 value,
334 }))
335 }
336 }
337
338 fn parse_jsx_attrs(&mut self) -> PResult<Vec<JSXAttrOrSpread>> {
339 let mut attrs = Vec::with_capacity(8);
340
341 loop {
342 trace_cur!(self, parse_jsx_opening__attrs_loop);
343 self.input_mut().rescan_jsx_open_el_terminal_token();
344 let cur = self.input().get_cur();
345 if matches!(cur.token, Token::Gt | Token::Slash) {
346 break;
347 }
348 let attr = self.parse_jsx_attr()?;
349 attrs.push(attr);
350 }
351
352 Ok(attrs)
353 }
354
355 pub(crate) fn parse_jsx_element(
356 &mut self,
357 in_expr_context: bool,
358 ) -> PResult<either::Either<JSXFragment, JSXElement>> {
359 debug_assert!(self.input().syntax().jsx());
360 trace_cur!(self, parse_jsx_element);
361
362 let start = self.cur_pos();
363
364 self.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| {
365 p.expect(Token::Lt)?;
366
367 p.input_mut().rescan_jsx_open_el_terminal_token();
371
372 if p.input().cur() == Token::Gt {
373 p.input_mut().scan_jsx_token(true);
375 let opening = JSXOpeningFragment {
376 span: p.span(start),
377 };
378 let children = p.parse_jsx_children();
379 let closing = p.parse_jsx_closing_fragment(in_expr_context)?;
380 let span = p.span(start);
381 Ok(either::Either::Left(JSXFragment {
382 span,
383 opening,
384 children,
385 closing,
386 }))
387 } else {
388 let name = p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| {
389 p.parse_jsx_element_name()
390 })?;
391 let type_args = if p.input().syntax().typescript() && p.input().is(Token::Lt) {
392 p.try_parse_ts(|p| {
393 let ret = p.parse_ts_type_args()?;
394 p.assert_and_bump(Token::Gt);
395 Ok(Some(ret))
396 })
397 } else {
398 None
399 };
400 let attrs = p.parse_jsx_attrs()?;
401 if p.input().cur() == Token::Gt {
402 p.input_mut().scan_jsx_token(true);
404 let span = Span::new_with_checked(start, p.input.get_cur().span.lo);
405 let opening = JSXOpeningElement {
406 span,
407 name,
408 type_args,
409 attrs,
410 self_closing: false,
411 };
412 let children = p.parse_jsx_children();
413 let closing = p.parse_jsx_closing_element(in_expr_context, &opening.name)?;
414 let span = if in_expr_context {
415 Span::new_with_checked(start, p.last_pos())
416 } else {
417 Span::new_with_checked(start, p.cur_pos())
418 };
419 Ok(either::Either::Right(JSXElement {
420 span,
421 opening,
422 children,
423 closing: Some(closing),
424 }))
425 } else {
426 p.expect(Token::Slash)?;
428
429 p.input_mut().rescan_jsx_open_el_terminal_token();
433 p.expect_without_advance(Token::Gt)?;
434
435 if in_expr_context {
436 p.bump();
437 } else {
438 p.input_mut().scan_jsx_token(true);
439 }
440 let span = if in_expr_context {
441 p.span(start)
442 } else {
443 Span::new_with_checked(start, p.cur_pos())
444 };
445 Ok(either::Either::Right(JSXElement {
446 span,
447 opening: JSXOpeningElement {
448 span,
449 name,
450 type_args,
451 attrs,
452 self_closing: true,
453 },
454 children: Vec::new(),
455 closing: None,
456 }))
457 }
458 }
459 })
460 }
461}
462
463fn get_qualified_jsx_name(name: &JSXElementName) -> Atom {
464 fn get_qualified_obj_name(obj: &JSXObject) -> Atom {
465 match *obj {
466 JSXObject::Ident(ref i) => i.sym.clone(),
467 JSXObject::JSXMemberExpr(ref member) => format!(
468 "{}.{}",
469 get_qualified_obj_name(&member.obj),
470 member.prop.sym
471 )
472 .into(),
473 #[cfg(swc_ast_unknown)]
474 _ => unreachable!(),
475 }
476 }
477 match *name {
478 JSXElementName::Ident(ref i) => i.sym.clone(),
479 JSXElementName::JSXNamespacedName(JSXNamespacedName {
480 ref ns, ref name, ..
481 }) => format!("{}:{}", ns.sym, name.sym).into(),
482 JSXElementName::JSXMemberExpr(JSXMemberExpr {
483 ref obj, ref prop, ..
484 }) => format!("{}.{}", get_qualified_obj_name(obj), prop.sym).into(),
485 #[cfg(swc_ast_unknown)]
486 _ => unreachable!(),
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use swc_atoms::atom;
493 use swc_common::DUMMY_SP as span;
494 use swc_ecma_visit::assert_eq_ignore_span;
495
496 use super::super::*;
497
498 fn jsx(src: &'static str) -> Box<Expr> {
499 test_parser(
500 src,
501 crate::Syntax::Es(crate::EsSyntax {
502 jsx: true,
503 ..Default::default()
504 }),
505 |p| p.parse_expr(),
506 )
507 }
508
509 #[test]
510 fn self_closing_01() {
511 assert_eq_ignore_span!(
512 jsx("<a />"),
513 Box::new(Expr::JSXElement(Box::new(JSXElement {
514 span,
515 opening: JSXOpeningElement {
516 span,
517 name: JSXElementName::Ident(Ident::new_no_ctxt(atom!("a"), span)),
518 self_closing: true,
519 attrs: Vec::new(),
520 type_args: None,
521 },
522 children: Vec::new(),
523 closing: None,
524 })))
525 );
526 }
527
528 #[test]
529 fn normal_01() {
530 assert_eq_ignore_span!(
531 jsx("<a>foo</a>"),
532 Box::new(Expr::JSXElement(Box::new(JSXElement {
533 span,
534 opening: JSXOpeningElement {
535 span,
536 name: JSXElementName::Ident(Ident::new_no_ctxt(atom!("a"), span)),
537 self_closing: false,
538 attrs: Vec::new(),
539 type_args: None,
540 },
541 children: vec![JSXElementChild::JSXText(JSXText {
542 span,
543 raw: atom!("foo"),
544 value: atom!("foo"),
545 })],
546 closing: Some(JSXClosingElement {
547 span,
548 name: JSXElementName::Ident(Ident::new_no_ctxt(atom!("a"), span)),
549 })
550 })))
551 );
552 }
553
554 #[test]
555 fn escape_in_attr() {
556 assert_eq_ignore_span!(
557 jsx(r#"<div id="w < w" />;"#),
558 Box::new(Expr::JSXElement(Box::new(JSXElement {
559 span,
560 opening: JSXOpeningElement {
561 span,
562 attrs: vec![JSXAttrOrSpread::JSXAttr(JSXAttr {
563 span,
564 name: JSXAttrName::Ident(IdentName::new(atom!("id"), span)),
565 value: Some(JSXAttrValue::Str(Str {
566 span,
567 value: atom!("w < w").into(),
568 raw: Some(atom!("\"w < w\"")),
569 })),
570 })],
571 name: JSXElementName::Ident(Ident::new_no_ctxt(atom!("div"), span)),
572 self_closing: true,
573 type_args: None,
574 },
575 children: Vec::new(),
576 closing: None
577 })))
578 );
579 }
580
581 #[test]
582 fn issue_584() {
583 assert_eq_ignore_span!(
584 jsx(r#"<test other={4} />;"#),
585 Box::new(Expr::JSXElement(Box::new(JSXElement {
586 span,
587 opening: JSXOpeningElement {
588 span,
589 name: JSXElementName::Ident(Ident::new_no_ctxt(atom!("test"), span)),
590 attrs: vec![JSXAttrOrSpread::JSXAttr(JSXAttr {
591 span,
592 name: JSXAttrName::Ident(IdentName::new(atom!("other"), span)),
593 value: Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
594 span,
595 expr: JSXExpr::Expr(Box::new(Expr::Lit(Lit::Num(Number {
596 span,
597 value: 4.0,
598 raw: Some(atom!("4"))
599 }))))
600 })),
601 })],
602 self_closing: true,
603 type_args: None,
604 },
605 children: Vec::new(),
606 closing: None
607 })))
608 );
609 }
610}