swc_ecma_fast_parser/lexer/
jsx.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! JSX syntax processing for the lexer
//!
//! This module handles the parsing of JSX syntax in React-style templates.

use swc_atoms::Atom;

use super::Lexer;
use crate::{
    error::Result,
    token::{Token, TokenType, TokenValue},
};

impl Lexer<'_> {
    /// Read a JSX token when inside JSX context
    pub(super) fn read_jsx_token(&mut self, had_line_break: bool) -> Result<Token> {
        match self.cursor.peek() {
            // Start of JSX element or fragment
            Some(b'<') => {
                self.cursor.advance();

                // Check for JSX fragment opening
                if self.cursor.peek() == Some(b'>') {
                    self.cursor.advance();
                    Ok(Token::new(
                        TokenType::JSXTagStart,
                        self.span(),
                        had_line_break,
                        TokenValue::None,
                    ))
                } else {
                    Ok(Token::new(
                        TokenType::JSXTagStart,
                        self.span(),
                        had_line_break,
                        TokenValue::None,
                    ))
                }
            }

            // End of JSX element or fragment
            Some(b'>') => {
                self.cursor.advance();
                Ok(Token::new(
                    TokenType::Gt,
                    self.span(),
                    had_line_break,
                    TokenValue::None,
                ))
            }

            // JSX closing tag or fragment closing
            Some(b'/') => {
                self.cursor.advance();

                if self.cursor.peek() == Some(b'>') {
                    self.cursor.advance();

                    // Self-closing tag
                    Ok(Token::new(
                        TokenType::JSXTagEnd,
                        self.span(),
                        had_line_break,
                        TokenValue::None,
                    ))
                } else {
                    // Closing tag start
                    Ok(Token::new(
                        TokenType::Slash,
                        self.span(),
                        had_line_break,
                        TokenValue::None,
                    ))
                }
            }

            // JSX attribute value start
            Some(b'=') => {
                self.cursor.advance();
                Ok(Token::new(
                    TokenType::Eq,
                    self.span(),
                    had_line_break,
                    TokenValue::None,
                ))
            }

            // JSX quoted attribute value
            Some(b'"') | Some(b'\'') => self.read_string(self.cursor.peek().unwrap()),

            // JSX expression in attributes or children
            Some(b'{') => {
                self.cursor.advance();
                self.in_jsx_element = false; // Exit JSX context
                Ok(Token::new(
                    TokenType::LBrace,
                    self.span(),
                    had_line_break,
                    TokenValue::None,
                ))
            }

            // JSX text content
            _ => self.read_jsx_text(had_line_break),
        }
    }

    /// Read JSX text content
    fn read_jsx_text(&mut self, had_line_break: bool) -> Result<Token> {
        let start_pos = self.start_pos;
        let start_idx = start_pos.0;

        let mut text = String::new();

        // Read until we find <, {, or >
        loop {
            match self.cursor.peek() {
                Some(b'<') | Some(b'{') | Some(b'>') | None => {
                    break;
                }
                Some(_) => {
                    // For performance, read chunks of text at once if possible
                    let start = self.cursor.position();
                    self.cursor
                        .advance_while(|c| c != b'<' && c != b'{' && c != b'>');
                    let end = self.cursor.position();

                    if end > start {
                        let slice = unsafe { self.cursor.slice_unchecked(start, end) };
                        text.push_str(unsafe { std::str::from_utf8_unchecked(slice) });
                    }
                }
            }
        }

        // Skip whitespace-only JSX text
        if text.trim().is_empty() {
            // Return either a new token or the next token
            if self.cursor.peek().is_none() {
                return Ok(Token::new(
                    TokenType::EOF,
                    self.span(),
                    had_line_break,
                    TokenValue::None,
                ));
            } else {
                return self.read_jsx_token(had_line_break);
            }
        }

        // Extract the raw text
        let end_idx = self.cursor.position();
        let raw_bytes = unsafe { self.cursor.slice_unchecked(start_idx, end_idx) };
        let raw_str = unsafe { std::str::from_utf8_unchecked(raw_bytes) };

        let span = self.span();

        Ok(Token::new(
            TokenType::JSXText,
            span,
            self.had_line_break.into(),
            TokenValue::Str {
                value: Atom::from(text),
                raw: Atom::from(raw_str),
            },
        ))
    }
}