vue_oxc_toolkit/lib.rs
1use oxc_allocator::{Allocator, Dummy};
2use oxc_ast::ast::Program;
3use oxc_diagnostics::OxcDiagnostic;
4use oxc_parser::ParseOptions;
5use oxc_span::Span;
6use oxc_syntax::module_record::ModuleRecord;
7
8use crate::parser::{ParserImpl, ParserImplReturn};
9
10mod irregular_whitespaces;
11mod parser;
12
13#[cfg(test)]
14mod test;
15
16pub struct VueOxcParser<'a> {
17 allocator: &'a Allocator,
18 source_text: &'a str,
19 options: ParseOptions,
20}
21
22/// The return value of [`VueOxcParser::parse`].
23///
24/// Mirrors [`oxc_parser::ParserReturn`] as a workaround for its
25/// `#[non_exhaustive]` attribute. The `is_flow_language` field is intentionally
26/// omitted because Vue does not support Flow.
27#[non_exhaustive]
28pub struct VueParserReturn<'a> {
29 pub program: Program<'a>,
30 pub module_record: ModuleRecord<'a>,
31 pub errors: Vec<OxcDiagnostic>,
32 pub irregular_whitespaces: Box<[Span]>,
33 pub panicked: bool,
34}
35
36impl<'a> VueOxcParser<'a> {
37 /// Creates a new [`VueOxcParser`] for the given Vue SFC `source_text`.
38 ///
39 /// The `allocator` must outlive the returned parser and the resulting
40 /// [`VueParserReturn`], because the produced AST nodes are arena-allocated.
41 ///
42 /// # Examples
43 ///
44 /// ```
45 /// use oxc_allocator::Allocator;
46 /// use vue_oxc_toolkit::VueOxcParser;
47 ///
48 /// let allocator = Allocator::default();
49 /// let source = r#"<template><div>{{ msg }}</div></template>
50 /// <script setup>
51 /// const msg = 'hello';
52 /// </script>"#;
53 ///
54 /// let ret = VueOxcParser::new(&allocator, source).parse();
55 /// assert!(!ret.panicked);
56 /// ```
57 pub fn new(allocator: &'a Allocator, source_text: &'a str) -> Self {
58 Self { allocator, source_text, options: ParseOptions::default() }
59 }
60
61 /// Overrides the [`ParseOptions`] passed to the underlying `oxc_parser`.
62 ///
63 /// # Examples
64 ///
65 /// ```
66 /// use oxc_allocator::Allocator;
67 /// use oxc_parser::ParseOptions;
68 /// use vue_oxc_toolkit::VueOxcParser;
69 ///
70 /// let allocator = Allocator::default();
71 /// let source = "<script setup lang=\"ts\">const n: number = 1;</script>";
72 ///
73 /// let options = ParseOptions { parse_regular_expression: true, ..ParseOptions::default() };
74 /// let ret = VueOxcParser::new(&allocator, source).with_options(options).parse();
75 /// assert!(!ret.panicked);
76 /// ```
77 #[must_use]
78 pub const fn with_options(mut self, options: ParseOptions) -> Self {
79 self.options = options;
80 self
81 }
82}
83
84impl<'a> VueOxcParser<'a> {
85 /// Parses the Vue SFC and returns a [`VueParserReturn`] containing the
86 /// JS/TS [`Program`], the [`ModuleRecord`], collected diagnostics, and any
87 /// irregular whitespace spans found in the source.
88 ///
89 /// On a fatal parse failure, [`VueParserReturn::panicked`] is `true` and
90 /// [`VueParserReturn::program`] is a dummy program; callers should inspect
91 /// [`VueParserReturn::errors`] in that case.
92 ///
93 /// # Examples
94 ///
95 /// ```
96 /// use oxc_allocator::Allocator;
97 /// use vue_oxc_toolkit::VueOxcParser;
98 ///
99 /// let allocator = Allocator::default();
100 /// let source = r#"<script setup>const count = 1;</script>"#;
101 ///
102 /// let ret = VueOxcParser::new(&allocator, source).parse();
103 /// assert!(!ret.panicked);
104 /// assert!(ret.errors.is_empty());
105 /// ```
106 #[must_use]
107 pub fn parse(self) -> VueParserReturn<'a> {
108 let ParserImplReturn { program, errors, fatal, module_record } =
109 ParserImpl::new(self.allocator, self.source_text, self.options).parse();
110
111 if fatal {
112 VueParserReturn {
113 program: Program::dummy(self.allocator),
114 module_record, // Dummy one if fatal, can be directly passed there without recreate a new one
115 errors,
116 irregular_whitespaces: Box::new([]),
117 panicked: true,
118 }
119 } else {
120 VueParserReturn {
121 program,
122 errors,
123 panicked: false,
124 irregular_whitespaces: self.get_irregular_whitespaces(),
125 module_record,
126 }
127 }
128 }
129}