use oxc_span::{GetSpan, SourceType};
use vize_l0::expression_guard::{expression_is_safe_to_parse, is_expression_trailing_trivia};
use vize_l0::{Allocator, Span};
use super::opaque::OpaqueReason;
pub mod coordinates;
pub use coordinates::{JsCoordinateError, JsCoordinates, JsSegment};
const EXPR_SOURCE_TYPE: SourceType = SourceType::ts();
#[derive(Debug, Clone, Copy)]
pub struct JsExpr<'a> {
pub ast: &'a oxc_ast::ast::Expression<'a>,
pub source: &'a str,
pub span: Span,
pub coordinates: Option<&'a JsCoordinates<'a>>,
}
impl<'a> JsExpr<'a> {
pub fn from_retained_in(
allocator: &'a Allocator,
ast: &'a oxc_ast::ast::Expression<'a>,
source: &'a str,
span: Span,
coordinates: JsCoordinates<'a>,
) -> Result<&'a Self, JsCoordinateError> {
let decoded = coordinates
.decoded_span(ast.span())
.ok_or(JsCoordinateError::OutsideExpression)?;
if !coordinates.matches(source, span)
|| !source
.get(..decoded.start as usize)
.is_some_and(is_expression_trailing_trivia)
|| !source
.get(decoded.end as usize..)
.is_some_and(is_expression_trailing_trivia)
{
return Err(JsCoordinateError::OutsideExpression);
}
Ok(allocator.alloc(Self {
ast,
source,
span,
coordinates: Some(allocator.alloc(coordinates)),
}))
}
#[must_use]
pub fn ast_span_to_source(&self, span: oxc_span::Span) -> Option<Span> {
if let Some(coordinates) = self.coordinates {
return coordinates.decoded_span(span);
}
self.source.get(span.start as usize..span.end as usize)?;
Some(Span::new(span.start, span.end))
}
#[must_use]
pub fn authored_span(&self, span: Span) -> Option<Span> {
if let Some(coordinates) = self.coordinates {
return coordinates.authored_span(span);
}
self.source.get(span.start as usize..span.end as usize)?;
let span = Span::new(
self.span.start.checked_add(span.start)?,
self.span.start.checked_add(span.end)?,
);
(span.end <= self.span.end).then_some(span)
}
#[must_use]
pub fn matches_authored_source(&self, file: &str) -> bool {
if let Some(coordinates) = self.coordinates {
return coordinates.matches_authored_source(file);
}
file.get(self.span.start as usize..self.span.end as usize) == Some(self.source)
}
pub fn parse_in(
allocator: &'a Allocator,
source: &'a str,
span: Span,
) -> Result<&'a Self, OpaqueReason> {
let parsed = Self::parse_ast_in(allocator, source)?;
Ok(allocator.alloc(Self {
ast: allocator.alloc(parsed),
source,
span,
coordinates: None,
}))
}
pub fn parse_ast_in(
allocator: &'a Allocator,
source: &'a str,
) -> Result<oxc_ast::ast::Expression<'a>, OpaqueReason> {
if !expression_is_safe_to_parse(source) {
return Err(OpaqueReason::NestingRefused);
}
let oxc = allocator.as_oxc();
let Ok(parsed) = oxc_parser::Parser::new(oxc, source, EXPR_SOURCE_TYPE).parse_expression()
else {
return Err(OpaqueReason::ParseRejected);
};
let Some(rest) = source.get(parsed.span().end as usize..) else {
return Err(OpaqueReason::ParseRejected);
};
if !is_expression_trailing_trivia(rest) {
return Err(OpaqueReason::ParseRejected);
}
Ok(parsed)
}
}
const _: () = assert!(!core::mem::needs_drop::<JsExpr<'static>>());
#[cfg(target_pointer_width = "64")]
const _: () = assert!(core::mem::size_of::<JsExpr<'_>>() == 40);