Skip to main content

oxc_parser/
embedding.rs

1//! Authenticated fixed language wrappers over the original source and arena.
2
3use compact_str::CompactString;
4use oxc_allocator::Allocator;
5use oxc_ast::ast::{Comment, Expression, FormalParameters, FunctionBody};
6use oxc_diagnostics::Diagnostics;
7use oxc_span::{SourceType, Span};
8
9use crate::{ParseOptions, Parser, ParserReturn};
10
11mod projection;
12mod shapes;
13pub use projection::{
14    AdmittedExpression, AdmittedHandlerBody, AdmittedParameters, ExpressionObservation,
15    HandlerBodyObservation, ParametersObservation,
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum EmbeddingGoal {
20    Expr,
21    HandlerBody,
22    Parameters,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum EmbeddingInputError {
27    SourceTooLarge,
28    UnsupportedProfile,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum EmbeddingHole {
33    Syntax,
34    UnsupportedFlow,
35    InvalidExpressionShape,
36    InvalidWrappedShape,
37    InvalidModuleContext,
38    InvalidParameterContext,
39}
40
41/// A private fixed wrapper and its original allocation/source identity.
42/// No caller AST, wrapper, options or lexer configuration can be substituted.
43///
44/// ```compile_fail,E0451
45/// use oxc_parser::EmbeddingInput;
46/// fn forge<'a>(input: EmbeddingInput<'a>) -> EmbeddingInput<'a> {
47///     EmbeddingInput { content: "replacement", ..input }
48/// }
49/// ```
50pub struct EmbeddingInput<'a> {
51    allocator: &'a Allocator,
52    content: &'a str,
53    source_type: SourceType,
54    goal: EmbeddingGoal,
55    wrapper: CompactString,
56    content_span: Span,
57}
58
59impl<'a> EmbeddingInput<'a> {
60    /// Prepare ordinary temporary bytes, without promoting them into the arena.
61    /// Current embedding contexts admit explicit modules, not inferred goals.
62    pub fn prepare_in(
63        allocator: &'a Allocator,
64        content: &'a str,
65        source_type: SourceType,
66        goal: EmbeddingGoal,
67    ) -> Result<Self, EmbeddingInputError> {
68        if !source_type.is_module() || source_type.is_typescript_definition() {
69            return Err(EmbeddingInputError::UnsupportedProfile);
70        }
71        let (prefix, suffix) = shapes::wrapper(goal);
72        let length = content
73            .len()
74            .checked_add(prefix.len() + suffix.len())
75            .filter(|length| *length <= crate::MAX_LEN)
76            .ok_or(EmbeddingInputError::SourceTooLarge)?;
77        let content_span = Span::new(prefix.len() as u32, (prefix.len() + content.len()) as u32);
78        let mut wrapper = CompactString::with_capacity(length);
79        wrapper.push_str(prefix);
80        wrapper.push_str(content);
81        wrapper.push_str(suffix);
82        Ok(Self { allocator, content, source_type, goal, wrapper, content_span })
83    }
84
85    /// The exact temporary parser input, available to the caller's resource guard.
86    #[must_use]
87    pub fn parser_source(&self) -> &str {
88        self.wrapper.as_str()
89    }
90
91    #[must_use]
92    pub const fn parser_content_span(&self) -> Span {
93        self.content_span
94    }
95
96    /// Promote into the original arena and call the stock parser exactly once.
97    /// This has no custom configuration, options or replacement arena argument.
98    ///
99    /// ```compile_fail,E0061
100    /// use oxc_allocator::Allocator;
101    /// use oxc_parser::EmbeddingInput;
102    /// fn substitute<'a>(input: EmbeddingInput<'a>, other: &'a Allocator) {
103    ///     input.observe(other);
104    /// }
105    /// ```
106    #[must_use]
107    pub fn observe(self) -> EmbeddingObservation<'a> {
108        let source = self.allocator.alloc_str(self.wrapper.as_str());
109        let options = ParseOptions::default();
110        let parsed =
111            Parser::new(self.allocator, source, self.source_type).with_options(options).parse();
112        let hole = if parsed.is_flow_language {
113            Some(EmbeddingHole::UnsupportedFlow)
114        } else if parsed.panicked || parsed.diagnostics.has_errors() {
115            Some(EmbeddingHole::Syntax)
116        } else {
117            shapes::shape_hole(&parsed.program, self.goal, self.content_span).or_else(|| {
118                (self.goal != EmbeddingGoal::Expr)
119                    .then(|| shapes::context_hole(&parsed.program))
120                    .flatten()
121            })
122        };
123        EmbeddingObservation {
124            allocator: self.allocator,
125            content: self.content,
126            source_type: self.source_type,
127            goal: self.goal,
128            content_span: self.content_span,
129            parsed,
130            options,
131            hole,
132        }
133    }
134}
135
136/// The complete ordinary result, including all diagnostics and parser fields.
137/// Shared selected syntax cannot mint a replacement admitted projection.
138pub struct EmbeddingObservation<'a> {
139    allocator: &'a Allocator,
140    content: &'a str,
141    source_type: SourceType,
142    goal: EmbeddingGoal,
143    content_span: Span,
144    parsed: ParserReturn<'a>,
145    options: ParseOptions,
146    hole: Option<EmbeddingHole>,
147}
148
149impl core::fmt::Debug for EmbeddingObservation<'_> {
150    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
151        f.debug_struct("EmbeddingObservation")
152            .field("goal", &self.goal)
153            .field("source_type", &self.source_type)
154            .field("hole", &self.hole)
155            .finish_non_exhaustive()
156    }
157}
158
159impl<'a> EmbeddingObservation<'a> {
160    #[must_use]
161    pub const fn content(&self) -> &'a str {
162        self.content
163    }
164    #[must_use]
165    pub const fn source_type(&self) -> SourceType {
166        self.source_type
167    }
168    #[must_use]
169    pub const fn options(&self) -> ParseOptions {
170        self.options
171    }
172    #[must_use]
173    pub const fn hole(&self) -> Option<EmbeddingHole> {
174        self.hole
175    }
176    #[must_use]
177    pub const fn parser_content_span(&self) -> Span {
178        self.content_span
179    }
180    #[must_use]
181    pub fn comments(&self) -> &[Comment] {
182        self.parsed.program.comments.as_slice()
183    }
184    #[must_use]
185    pub fn diagnostics(&self) -> &Diagnostics {
186        &self.parsed.diagnostics
187    }
188    #[must_use]
189    pub fn panicked(&self) -> bool {
190        self.parsed.panicked
191    }
192    #[must_use]
193    pub fn is_flow_language(&self) -> bool {
194        self.parsed.is_flow_language
195    }
196
197    #[must_use]
198    pub fn expression(&self) -> Option<&Expression<'a>> {
199        (self.hole.is_none() && self.goal == EmbeddingGoal::Expr)
200            .then(|| shapes::expression(&self.parsed.program))
201            .flatten()
202    }
203    #[must_use]
204    pub fn handler_body(&self) -> Option<&FunctionBody<'a>> {
205        (self.hole.is_none() && self.goal == EmbeddingGoal::HandlerBody)
206            .then(|| shapes::arrow(&self.parsed.program).map(|arrow| &*arrow.body))
207            .flatten()
208    }
209    #[must_use]
210    pub fn parameters(&self) -> Option<&FormalParameters<'a>> {
211        (self.hole.is_none() && self.goal == EmbeddingGoal::Parameters)
212            .then(|| shapes::arrow(&self.parsed.program).map(|arrow| &*arrow.params))
213            .flatten()
214    }
215}