1use 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
41pub 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 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 #[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 #[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
136pub 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}