use oxc_ast::ast::{
Argument, CallExpression, ChainElement, Expression, IdentifierReference, SimpleAssignmentTarget,
};
use oxc_span::GetSpan;
use vize_l0::Span;
use crate::expr::JsExpr;
mod budget;
mod compound;
mod exports;
mod for_head;
mod handler;
mod jsx;
#[cfg(test)]
mod syntax_tests;
mod values;
pub(super) use exports::export_local;
pub(super) use for_head::original as original_for_head;
pub(super) use handler::handler_body;
use super::sink::{ReferenceEvent, ReferenceSink};
use super::source::ReferenceSource;
use super::{ResolutionError, ResolutionErrorKind, SyntaxEdge, SyntaxKind, Usage};
pub(super) fn expression<'a>(
expression: &JsExpr<'a>,
sink: &mut impl ReferenceSink<'a>,
) -> Result<(), ResolutionError> {
retained(
ReferenceSource::Expression(*expression),
expression.ast,
sink,
)
}
pub(super) fn retained<'a>(
source: ReferenceSource<'a>,
expression: &Expression<'a>,
sink: &mut impl ReferenceSink<'a>,
) -> Result<(), ResolutionError> {
let checkpoint = sink.checkpoint();
let mut resolver = Resolver::new(source, sink);
let result = resolver.expression(expression, 0);
if result.is_err() {
resolver.sink.rollback(checkpoint);
}
result
}
struct Resolver<'a, 'b, S> {
source: ReferenceSource<'a>,
sink: &'b mut S,
last_end: Option<u32>,
visited: usize,
in_new_callee: bool,
}
impl<'a, 'b, S: ReferenceSink<'a>> Resolver<'a, 'b, S> {
fn new(source: ReferenceSource<'a>, sink: &'b mut S) -> Self {
Self {
source,
sink,
last_end: None,
visited: 0,
in_new_callee: false,
}
}
fn fail(&self, span: oxc_span::Span, kind: ResolutionErrorKind) -> ResolutionError {
ResolutionError {
span: self
.source
.span(span)
.unwrap_or(Span::new(0, self.source.length())),
kind,
}
}
fn visit(&mut self, span: oxc_span::Span, depth: usize) -> Result<Span, ResolutionError> {
if !self.advance(depth) {
return Err(self.fail(span, ResolutionErrorKind::TraversalLimit));
}
self.source
.span(span)
.ok_or_else(|| self.fail(span, ResolutionErrorKind::InvalidSpan))
}
fn identifier(
&mut self,
identifier: &IdentifierReference<'a>,
usage: Usage,
shorthand: bool,
) -> Result<(), ResolutionError> {
self.reference(identifier.span, identifier.name.as_str(), usage, shorthand)
}
fn reference(
&mut self,
ast_span: oxc_span::Span,
name: &'a str,
usage: Usage,
shorthand: bool,
) -> Result<(), ResolutionError> {
let span = self.visit(ast_span, 0)?;
if span.start == span.end || self.last_end.is_some_and(|last| last > span.start) {
return Err(self.fail(ast_span, ResolutionErrorKind::InvalidSpan));
}
self.sink
.reference(ReferenceEvent::new(
span,
name,
usage,
shorthand,
self.in_new_callee,
))
.map_err(|kind| self.fail(ast_span, kind))?;
self.last_end = Some(span.end);
Ok(())
}
fn expression(
&mut self,
expression: &Expression<'a>,
depth: usize,
) -> Result<(), ResolutionError> {
self.visit(expression.span(), depth)?;
if self.source.allows_jsx() {
self.syntax(
values::syntax_value(expression),
SyntaxEdge::Enter,
expression.span(),
)?;
}
let next = depth + 1;
let result = match expression {
Expression::Identifier(identifier) => self.identifier(identifier, Usage::Read, false),
Expression::JSXElement(value) => self.jsx_element(value, next),
Expression::JSXFragment(value) => self.jsx_fragment(value, next),
Expression::BooleanLiteral(_)
| Expression::NullLiteral(_)
| Expression::NumericLiteral(_)
| Expression::BigIntLiteral(_)
| Expression::RegExpLiteral(_)
| Expression::StringLiteral(_) => Ok(()),
Expression::TemplateLiteral(value) => {
for expression in &value.expressions {
self.expression(expression, next)?;
}
Ok(())
}
Expression::TaggedTemplateExpression(value) if value.type_arguments.is_none() => {
self.invocation(expression)?;
self.expression(&value.tag, next)?;
for expression in &value.quasi.expressions {
self.expression(expression, next)?;
}
Ok(())
}
Expression::ParenthesizedExpression(value) => self.expression(&value.expression, next),
Expression::UnaryExpression(value) if !value.operator.is_delete() => {
self.expression(&value.argument, next)
}
Expression::BinaryExpression(value) => {
self.expression(&value.left, next)?;
self.expression(&value.right, next)
}
Expression::LogicalExpression(value) => {
self.expression(&value.left, next)?;
self.expression(&value.right, next)
}
Expression::ConditionalExpression(value) => {
self.expression(&value.test, next)?;
self.expression(&value.consequent, next)?;
self.expression(&value.alternate, next)
}
Expression::SequenceExpression(value) => {
for expression in &value.expressions {
self.expression(expression, next)?;
}
Ok(())
}
Expression::StaticMemberExpression(value) => self.expression(&value.object, next),
Expression::ComputedMemberExpression(value) => {
self.expression(&value.object, next)?;
self.expression(&value.expression, next)
}
Expression::CallExpression(value) => self.call(value, next),
Expression::NewExpression(value) if value.type_arguments.is_none() => {
self.invocation(expression)?;
let previous = self.in_new_callee;
self.in_new_callee = true;
let result = self.expression(&value.callee, next);
self.in_new_callee = previous;
result?;
for argument in &value.arguments {
self.argument(argument, next)?;
}
Ok(())
}
Expression::ChainExpression(value) => match &value.expression {
ChainElement::CallExpression(call) => self.call(call, next),
ChainElement::StaticMemberExpression(member) => {
self.expression(&member.object, next)
}
ChainElement::ComputedMemberExpression(member) => {
self.expression(&member.object, next)?;
self.expression(&member.expression, next)
}
other => Err(self.fail(other.span(), ResolutionErrorKind::UnsupportedSyntax)),
},
Expression::ImportExpression(value) if value.phase.is_none() => {
self.invocation(expression)?;
self.expression(&value.source, next)?;
if let Some(options) = &value.options {
self.expression(options, next)?;
}
Ok(())
}
Expression::ArrayExpression(value) => self.array(value, next),
Expression::ObjectExpression(value) => self.object(value, next),
Expression::AssignmentExpression(value) => {
let Some(target) = value.left.as_simple_assignment_target() else {
return Err(
self.fail(value.left.span(), ResolutionErrorKind::UnsupportedSyntax)
);
};
let usage = if value.operator.is_assign() {
Usage::Write
} else {
Usage::ReadWrite
};
self.target(target, usage, next)?;
self.expression(&value.right, next)
}
Expression::UpdateExpression(value) => {
self.target(&value.argument, Usage::ReadWrite, next)
}
other => Err(self.fail(other.span(), ResolutionErrorKind::UnsupportedSyntax)),
};
result?;
if self.source.allows_jsx() {
self.syntax(
values::syntax_value(expression),
SyntaxEdge::Leave,
expression.span(),
)?;
}
Ok(())
}
fn syntax(
&mut self,
kind: SyntaxKind<'a>,
edge: SyntaxEdge,
ast_span: oxc_span::Span,
) -> Result<(), ResolutionError> {
let span = self
.source
.span(ast_span)
.ok_or_else(|| self.fail(ast_span, ResolutionErrorKind::InvalidSpan))?;
if span.start == span.end
&& matches!(
kind,
SyntaxKind::Component(_)
| SyntaxKind::Intrinsic(_)
| SyntaxKind::Property(_)
| SyntaxKind::AttributeName(_)
)
{
return Err(self.fail(ast_span, ResolutionErrorKind::InvalidSpan));
}
self.sink
.observe_syntax(kind, edge, span)
.map_err(|kind| self.fail(ast_span, kind))
}
fn invocation(&mut self, expression: &Expression<'a>) -> Result<(), ResolutionError> {
let span = self
.source
.span(expression.span())
.ok_or_else(|| self.fail(expression.span(), ResolutionErrorKind::InvalidSpan))?;
self.sink
.observe_invocation(expression, span)
.map_err(|kind| self.fail(expression.span(), kind))
}
fn call(&mut self, value: &CallExpression<'a>, next: usize) -> Result<(), ResolutionError> {
if value.type_arguments.is_some()
|| matches!(value.callee.get_inner_expression(), Expression::Identifier(id) if id.name == "eval")
{
return Err(self.fail(value.span, ResolutionErrorKind::UnsupportedSyntax));
}
let span = self
.source
.span(value.span)
.ok_or_else(|| self.fail(value.span, ResolutionErrorKind::InvalidSpan))?;
self.sink
.observe_call(value, span)
.map_err(|kind| self.fail(value.span, kind))?;
self.expression(&value.callee, next)?;
for argument in &value.arguments {
self.argument(argument, next)?;
}
Ok(())
}
fn argument(&mut self, argument: &Argument<'a>, next: usize) -> Result<(), ResolutionError> {
self.visit(argument.span(), next)?;
match argument {
Argument::SpreadElement(spread) => self.expression(&spread.argument, next),
other => self.expression(other.to_expression(), next),
}
}
fn target(
&mut self,
target: &SimpleAssignmentTarget<'a>,
usage: Usage,
depth: usize,
) -> Result<(), ResolutionError> {
self.visit(target.span(), depth)?;
match target {
SimpleAssignmentTarget::AssignmentTargetIdentifier(identifier) => {
self.identifier(identifier, usage, false)
}
SimpleAssignmentTarget::StaticMemberExpression(value) if !value.optional => {
self.expression(&value.object, depth + 1)
}
SimpleAssignmentTarget::ComputedMemberExpression(value) if !value.optional => {
self.expression(&value.object, depth + 1)?;
self.expression(&value.expression, depth + 1)
}
other => Err(self.fail(other.span(), ResolutionErrorKind::UnsupportedSyntax)),
}
}
}