fervid_parser/
script.rs

1use fervid_core::{SfcScriptBlock, SfcScriptLang};
2use swc_core::{
3    common::Span,
4    ecma::ast::{Expr, Module, Pat},
5};
6use swc_ecma_parser::{lexer::Lexer, EsConfig, Parser, StringInput, Syntax, TsConfig};
7use swc_html_ast::{Child, Element};
8
9use crate::{error::ParseErrorKind, ParseError, SfcParser};
10
11impl SfcParser<'_, '_, '_> {
12    /// Parses the `<script>` and `<script setup>`, both in EcmaScript and TypeScript
13    pub fn parse_sfc_script_element(
14        &mut self,
15        element: Element,
16    ) -> Result<Option<SfcScriptBlock>, ParseError> {
17        // Find `setup` and `lang`
18        let mut is_setup = false;
19        let mut is_setup_seen = false;
20        let mut is_lang_seen = false;
21        let mut lang = SfcScriptLang::Es;
22        for attr in element.attributes.iter() {
23            match attr.name.as_str() {
24                "setup" => {
25                    if is_setup_seen {
26                        self.report_error(ParseError {
27                            kind: ParseErrorKind::DuplicateAttribute,
28                            span: attr.span,
29                        });
30                    }
31
32                    is_setup = true;
33                    is_setup_seen = true;
34                }
35                "lang" if is_lang_seen => self.errors.push(ParseError {
36                    kind: ParseErrorKind::DuplicateAttribute,
37                    span: attr.span,
38                }),
39                "lang" => {
40                    is_lang_seen = true;
41
42                    lang = match attr.value.as_ref().map(|v| v.as_str()) {
43                        Some("ts" | "typescript") => SfcScriptLang::Typescript,
44                        None | Some("js" | "javascript") => SfcScriptLang::Es,
45                        Some(_) => {
46                            return Err(ParseError {
47                                kind: ParseErrorKind::UnsupportedLang,
48                                span: attr.span,
49                            });
50                        }
51                    }
52                }
53                _ => {}
54            }
55        }
56
57        // `<script>` should always have a single `Text` child
58        let script_content = match element.children.get(0) {
59            Some(Child::Text(t)) => t,
60            Some(_) => {
61                return Err(ParseError {
62                    kind: ParseErrorKind::UnexpectedNonRawTextContent,
63                    span: element.span,
64                });
65            }
66            None if self.ignore_empty => {
67                return Ok(None);
68            }
69            None => {
70                // Allow empty
71                return Ok(Some(SfcScriptBlock {
72                    content: Box::new(Module {
73                        span: element.span,
74                        body: Vec::new(),
75                        shebang: None,
76                    }),
77                    lang,
78                    is_setup,
79                    span: element.span,
80                }));
81            }
82        };
83
84        // Ignore empty unless allowed
85        if self.ignore_empty && script_content.data.trim().is_empty() {
86            return Ok(None);
87        }
88
89        let module_content = self.parse_module(
90            &script_content.data,
91            if matches!(lang, SfcScriptLang::Typescript) {
92                Syntax::Typescript(TsConfig::default())
93            } else {
94                Syntax::Es(EsConfig::default())
95            },
96            script_content.span,
97        )?;
98
99        Ok(Some(SfcScriptBlock {
100            content: Box::new(module_content),
101            lang,
102            is_setup,
103            span: element.span,
104        }))
105    }
106
107    #[inline]
108    pub fn parse_module(
109        &mut self,
110        raw: &str,
111        syntax: Syntax,
112        span: Span,
113    ) -> Result<Module, ParseError> {
114        let lexer = Lexer::new(
115            syntax,
116            // EsVersion defaults to es5
117            Default::default(),
118            StringInput::new(raw, span.lo, span.hi),
119            Some(&self.comments),
120        );
121
122        let mut parser = Parser::new_from(lexer);
123        let parse_result = parser.parse_module();
124
125        // Map errors to EcmaSyntaxError
126        self.errors
127            .extend(parser.take_errors().into_iter().map(From::from));
128
129        parse_result.map_err(From::from)
130    }
131
132    pub fn parse_expr(
133        &mut self,
134        raw: &str,
135        syntax: Syntax,
136        span: Span,
137    ) -> Result<Box<Expr>, ParseError> {
138        let lexer = Lexer::new(
139            syntax,
140            // EsVersion defaults to es5
141            Default::default(),
142            StringInput::new(raw, span.lo, span.hi),
143            Some(&self.comments),
144        );
145
146        let mut parser = Parser::new_from(lexer);
147        let parse_result = parser.parse_expr();
148
149        // Map errors to EcmaSyntaxError
150        self.errors
151            .extend(parser.take_errors().into_iter().map(From::from));
152
153        parse_result.map_err(From::from)
154    }
155
156    pub fn parse_pat(&mut self, raw: &str, syntax: Syntax, span: Span) -> Result<Pat, ParseError> {
157        let lexer = Lexer::new(
158            syntax,
159            // EsVersion defaults to es5
160            Default::default(),
161            StringInput::new(raw, span.lo, span.hi),
162            Some(&self.comments),
163        );
164
165        let mut parser = Parser::new_from(lexer);
166        let parse_result = parser.parse_pat();
167
168        // Map errors to EcmaSyntaxError
169        self.errors
170            .extend(parser.take_errors().into_iter().map(From::from));
171
172        parse_result.map_err(From::from)
173    }
174}