Skip to main content

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}