use memchr::memmem;
use oxc_allocator::Allocator;
use oxc_ast::ast::{ArrayExpressionElement, Expression, ObjectPropertyKind, PropertyKey};
use oxc_diagnostics::OxcDiagnostic;
use oxc_parser::Parser;
use oxc_span::SourceType;
use rustc_hash::FxHashSet;
use super::span::adjust_expression_spans;
use crate::common::Span;
use crate::utils::oxc::bindings::{
collect_expression_reference_spans, collect_ts_type_reference_spans_from_expression,
};
#[derive(Debug)]
pub struct VForParseResult<'a> {
pub left: Option<Expression<'a>>,
pub right: Option<Expression<'a>>,
pub is_of: bool,
pub left_offset: u32,
pub right_offset: u32,
pub left_errors: Vec<OxcDiagnostic>,
pub right_errors: Vec<OxcDiagnostic>,
}
impl<'a> VForParseResult<'a> {
pub fn is_ok(&self) -> bool {
self.left_errors.is_empty()
&& self.right_errors.is_empty()
&& self.left.is_some()
&& self.right.is_some()
}
pub fn has_left_errors(&self) -> bool {
!self.left_errors.is_empty()
}
pub fn has_right_errors(&self) -> bool {
!self.right_errors.is_empty()
}
}
#[derive(Debug)]
pub struct VForWithBindings<'a> {
pub result: VForParseResult<'a>,
pub locals: Vec<Span>,
pub references: Vec<Span>,
}
impl<'a> VForWithBindings<'a> {
pub fn left(&self) -> Option<&Expression<'a>> {
self.result.left.as_ref()
}
pub fn right(&self) -> Option<&Expression<'a>> {
self.result.right.as_ref()
}
pub fn is_of(&self) -> bool {
self.result.is_of
}
pub fn has_errors(&self) -> bool {
self.result.has_left_errors() || self.result.has_right_errors()
}
pub fn is_ok(&self) -> bool {
self.result.is_ok()
}
pub fn left_offset(&self) -> u32 {
self.result.left_offset
}
pub fn right_offset(&self) -> u32 {
self.result.right_offset
}
}
fn collect_vfor_left_local_spans(expr: &Expression<'_>, locals: &mut Vec<Span>) {
match expr {
Expression::Identifier(ident) => {
locals.push(ident.span.into());
}
Expression::ObjectExpression(obj) => {
for prop in &obj.properties {
match prop {
ObjectPropertyKind::ObjectProperty(p) => {
if p.shorthand {
if let PropertyKey::StaticIdentifier(ident) = &p.key {
locals.push(ident.span.into());
}
} else {
collect_vfor_left_local_spans(&p.value, locals);
}
}
ObjectPropertyKind::SpreadProperty(spread) => {
collect_vfor_left_local_spans(&spread.argument, locals);
}
}
}
}
Expression::ArrayExpression(arr) => {
for elem in &arr.elements {
match elem {
ArrayExpressionElement::SpreadElement(spread) => {
collect_vfor_left_local_spans(&spread.argument, locals);
}
ArrayExpressionElement::Elision(_) => {}
_ => {
if let Some(e) = elem.as_expression() {
collect_vfor_left_local_spans(e, locals);
}
}
}
}
}
Expression::ParenthesizedExpression(paren) => {
collect_vfor_left_local_spans(&paren.expression, locals);
}
Expression::SequenceExpression(seq) => {
for e in &seq.expressions {
collect_vfor_left_local_spans(e, locals);
}
}
Expression::AssignmentExpression(assign) => {
use oxc_ast::ast::{AssignmentTarget, SimpleAssignmentTarget};
if let AssignmentTarget::AssignmentTargetIdentifier(id) = &assign.left {
locals.push(id.span.into());
} else if let Some(SimpleAssignmentTarget::AssignmentTargetIdentifier(id)) =
assign.left.as_simple_assignment_target()
{
locals.push(id.span.into());
}
}
_ => {}
}
}
fn extract_vfor_bindings_internal(
result: &VForParseResult<'_>,
source: &str,
) -> (Vec<Span>, Vec<Span>) {
let mut locals = Vec::new();
let mut references_set = FxHashSet::default();
if let Some(left) = &result.left {
collect_vfor_left_local_spans(left, &mut locals);
}
let ignored: FxHashSet<&[u8]> = locals
.iter()
.map(|span| span.slice(source).as_bytes())
.collect();
if let Some(right) = &result.right {
collect_expression_reference_spans(right, &ignored, &mut references_set);
collect_ts_type_reference_spans_from_expression(right, &mut references_set);
}
let references: Vec<Span> = references_set.into_iter().collect();
(locals, references)
}
pub fn parse_vfor<'a>(
allocator: &'a Allocator,
source: &str,
source_type: SourceType,
) -> VForParseResult<'a> {
if source.is_empty() {
return VForParseResult {
left: None,
right: None,
is_of: false,
left_offset: 0,
right_offset: 0,
left_errors: vec![],
right_errors: vec![],
};
}
let source_bytes = source.as_bytes();
let of_pos = memmem::find(source_bytes, b" of ");
let in_pos = memmem::find(source_bytes, b" in ");
let (is_of, separator_pos) = match (of_pos, in_pos) {
(Some(of), Some(r#in)) => {
if of < r#in {
(true, of)
} else {
(false, r#in)
}
}
(Some(of), None) => (true, of),
(None, Some(r#in)) => (false, r#in),
(None, None) => {
return VForParseResult {
left: None,
right: None,
is_of: false,
left_offset: 0,
right_offset: 0,
left_errors: vec![OxcDiagnostic::error(
"Invalid v-for expression: missing 'in' or 'of' keyword",
)],
right_errors: vec![],
};
}
};
let left_str = &source[..separator_pos];
let right_start = separator_pos + 4; let right_str = &source[right_start..];
let left_alloc = allocator.alloc_str(left_str);
let right_alloc = allocator.alloc_str(right_str);
let left_parser = Parser::new(allocator, left_alloc, source_type);
let left_result = left_parser.parse_expression();
let right_parser = Parser::new(allocator, right_alloc, source_type);
let right_result = right_parser.parse_expression();
let (left, left_errors) = match left_result {
Ok(expr) => (Some(expr), vec![]),
Err(errors) => (None, errors),
};
let right_offset = right_start as u32;
let (right, right_errors) = match right_result {
Ok(mut expr) => {
adjust_expression_spans(&mut expr, right_offset);
(Some(expr), vec![])
}
Err(errors) => (None, errors),
};
VForParseResult {
left,
right,
is_of,
left_offset: 0,
right_offset,
left_errors,
right_errors,
}
}
pub fn parse_vfor_with_bindings<'a>(
allocator: &'a Allocator,
source: &str,
source_type: SourceType,
) -> VForWithBindings<'a> {
let result = parse_vfor(allocator, source, source_type);
let (locals, references) = if result.has_left_errors() || result.has_right_errors() {
(Vec::new(), Vec::new())
} else {
extract_vfor_bindings_internal(&result, source)
};
VForWithBindings {
result,
locals,
references,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(source: &str) -> VForParseResult<'static> {
let allocator = Box::leak(Box::new(Allocator::default()));
parse_vfor(allocator, source, SourceType::tsx())
}
#[test]
fn test_simple_of() {
let result = parse("item of items");
assert!(result.is_ok());
assert!(result.is_of);
assert_eq!(result.left_offset, 0);
assert_eq!(result.right_offset, 8);
if let Some(Expression::Identifier(id)) = &result.left {
assert_eq!(id.name.as_str(), "item");
assert_eq!(id.span.start, 0);
assert_eq!(id.span.end, 4);
} else {
panic!("Expected Identifier, got {:?}", result.left);
}
if let Some(Expression::Identifier(id)) = &result.right {
assert_eq!(id.name.as_str(), "items");
assert_eq!(id.span.start, 8); assert_eq!(id.span.end, 13); } else {
panic!("Expected Identifier expression");
}
}
#[test]
fn test_simple_in() {
let result = parse("item in items");
assert!(result.is_ok());
assert!(!result.is_of);
assert_eq!(result.right_offset, 8); }
#[test]
fn test_destructuring_object() {
let result = parse("{ id, name } of items");
assert!(result.is_ok());
assert!(result.is_of);
if let Some(Expression::ObjectExpression(_)) = &result.left {
} else {
panic!("Expected ObjectExpression, got {:?}", result.left);
}
}
#[test]
fn test_destructuring_array() {
let result = parse("[first, second] of items");
assert!(result.is_ok());
assert!(result.is_of);
if let Some(Expression::ArrayExpression(_)) = &result.left {
} else {
panic!("Expected ArrayExpression, got {:?}", result.left);
}
}
#[test]
fn test_with_parentheses() {
let result = parse("(item, index) of items");
assert!(result.is_ok());
assert!(result.is_of);
if let Some(Expression::ParenthesizedExpression(paren)) = &result.left {
if let Expression::SequenceExpression(seq) = &paren.expression {
assert_eq!(seq.expressions.len(), 2);
if let Expression::Identifier(id) = &seq.expressions[0] {
assert_eq!(id.name.as_str(), "item");
}
if let Expression::Identifier(id) = &seq.expressions[1] {
assert_eq!(id.name.as_str(), "index");
}
} else {
panic!("Expected SequenceExpression inside parentheses");
}
} else {
panic!("Expected ParenthesizedExpression, got {:?}", result.left);
}
}
#[test]
fn test_index_key_value() {
let result = parse("(value, key, index) in obj");
assert!(result.is_ok());
assert!(!result.is_of);
if let Some(Expression::ParenthesizedExpression(paren)) = &result.left {
if let Expression::SequenceExpression(seq) = &paren.expression {
assert_eq!(seq.expressions.len(), 3);
} else {
panic!("Expected SequenceExpression");
}
} else {
panic!("Expected ParenthesizedExpression");
}
}
#[test]
fn test_member_expression_iterable() {
let result = parse("item of data.items");
assert!(result.is_ok());
assert!(result.is_of);
if let Some(Expression::StaticMemberExpression(_)) = &result.right {
} else {
panic!("Expected StaticMemberExpression, got {:?}", result.right);
}
}
#[test]
fn test_function_call_iterable() {
let result = parse("item of getItems()");
assert!(result.is_ok());
if let Some(Expression::CallExpression(_)) = &result.right {
} else {
panic!("Expected CallExpression");
}
}
#[test]
fn test_empty_input() {
let result = parse("");
assert!(!result.is_ok());
assert!(result.left.is_none());
assert!(result.right.is_none());
}
#[test]
fn test_missing_separator() {
let result = parse("item items");
assert!(!result.is_ok());
assert!(!result.left_errors.is_empty());
}
#[test]
fn test_typescript_assertion() {
let result = parse("item of (items as Item[])");
assert!(result.is_ok());
assert!(result.is_of);
if let Some(Expression::ParenthesizedExpression(paren)) = &result.right {
if let Expression::TSAsExpression(_) = &paren.expression {
} else {
panic!("Expected TSAsExpression inside parentheses");
}
} else {
panic!("Expected ParenthesizedExpression");
}
}
#[test]
fn test_span_offset_calculation() {
let result = parse("item of items");
assert!(result.is_ok());
if let Some(Expression::Identifier(id)) = &result.left {
assert_eq!(id.span.start, 0);
assert_eq!(id.span.end, 4);
assert_eq!(id.span.start + result.left_offset, 0);
assert_eq!(id.span.end + result.left_offset, 4);
}
if let Some(Expression::Identifier(id)) = &result.right {
assert_eq!(id.span.start, 8);
assert_eq!(id.span.end, 13);
}
}
#[test]
fn test_complex_destructuring_with_index() {
let result = parse("({ id, name }, index) of items");
assert!(result.is_ok());
if let Some(Expression::ParenthesizedExpression(paren)) = &result.left {
if let Expression::SequenceExpression(seq) = &paren.expression {
assert_eq!(seq.expressions.len(), 2);
assert!(matches!(
&seq.expressions[0],
Expression::ObjectExpression(_)
));
if let Expression::Identifier(id) = &seq.expressions[1] {
assert_eq!(id.name.as_str(), "index");
}
}
}
}
#[test]
fn test_array_iterable() {
let result = parse("item of [1, 2, 3]");
assert!(result.is_ok());
if let Some(Expression::ArrayExpression(arr)) = &result.right {
assert_eq!(arr.elements.len(), 3);
} else {
panic!("Expected ArrayExpression");
}
}
#[test]
fn test_range_expression() {
let result = parse("n of Array(10).keys()");
assert!(result.is_ok());
if let Some(Expression::CallExpression(_)) = &result.right {
} else {
panic!("Expected CallExpression");
}
}
#[test]
fn test_object_literal_with_shorthand() {
let result = parse("item of [{ foo }, { bar }]");
assert!(result.is_ok());
if let Some(Expression::ArrayExpression(arr)) = &result.right {
assert_eq!(arr.elements.len(), 2);
} else {
panic!("Expected ArrayExpression, got {:?}", result.right);
}
}
#[test]
fn test_object_literal_iterable() {
let result = parse("item of { a: 1, b: 2 }");
assert!(result.is_ok());
if let Some(Expression::ObjectExpression(obj)) = &result.right {
assert_eq!(obj.properties.len(), 2);
} else {
panic!("Expected ObjectExpression, got {:?}", result.right);
}
}
#[test]
fn test_mixed_object_properties() {
let result = parse("key of { foo, bar: baz, qux }");
assert!(result.is_ok());
if let Some(Expression::ObjectExpression(obj)) = &result.right {
assert_eq!(obj.properties.len(), 3);
} else {
panic!("Expected ObjectExpression, got {:?}", result.right);
}
}
}