use oxc_allocator::Allocator;
use oxc_ast::ast::{Expression, FormalParameters};
use oxc_diagnostics::OxcDiagnostic;
use oxc_parser::Parser;
use oxc_span::SourceType;
use rustc_hash::FxHashSet;
use super::span::subtract_formal_parameters_spans;
use crate::common::Span;
use crate::utils::oxc::bindings::{
collect_expression_reference_spans, collect_pattern_local_spans,
collect_pattern_reference_spans, collect_type_reference_spans,
};
#[derive(Debug)]
pub struct VSlotParseResult<'a> {
pub offset: u32,
pub params: Option<FormalParameters<'a>>,
pub errors: Option<Vec<OxcDiagnostic>>,
}
impl<'a> VSlotParseResult<'a> {
pub fn is_ok(&self) -> bool {
self.errors.is_none() && self.params.is_some()
}
}
#[derive(Debug)]
pub struct VSlotWithBindings<'a> {
pub result: VSlotParseResult<'a>,
pub locals: Vec<Span>,
pub references: Vec<Span>,
}
impl<'a> VSlotWithBindings<'a> {
pub fn params(&self) -> Option<&FormalParameters<'a>> {
self.result.params.as_ref()
}
pub fn has_errors(&self) -> bool {
self.result.errors.is_some()
}
pub fn is_ok(&self) -> bool {
self.result.is_ok()
}
pub fn offset(&self) -> u32 {
self.result.offset
}
}
fn extract_slot_bindings_internal(
params: &FormalParameters<'_>,
source: &str,
) -> (Vec<Span>, Vec<Span>) {
let mut locals = Vec::new();
let mut references_set = FxHashSet::default();
for param in ¶ms.items {
collect_pattern_local_spans(¶m.pattern, &mut locals);
}
if let Some(rest) = ¶ms.rest {
collect_pattern_local_spans(&rest.rest.argument, &mut locals);
}
let ignored: FxHashSet<&[u8]> = locals
.iter()
.map(|span| span.slice(source).as_bytes())
.collect();
for param in ¶ms.items {
if let Some(init) = ¶m.initializer {
collect_expression_reference_spans(init, &ignored, &mut references_set);
}
if let Some(annotation) = ¶m.type_annotation {
collect_type_reference_spans(&annotation.type_annotation, &mut references_set);
}
collect_pattern_reference_spans(¶m.pattern, &ignored, &mut references_set);
}
if let Some(rest) = ¶ms.rest {
if let Some(annotation) = &rest.type_annotation {
collect_type_reference_spans(&annotation.type_annotation, &mut references_set);
}
}
let references: Vec<Span> = references_set.into_iter().collect();
(locals, references)
}
pub fn parse_vslot<'a>(
allocator: &'a Allocator,
source: &str,
source_type: SourceType,
) -> VSlotParseResult<'a> {
if source.trim().is_empty() {
return VSlotParseResult {
params: None,
offset: 0,
errors: None,
};
}
const WRAPPER_OFFSET: u32 = 1;
let wrapped_string = format!("({})=>{{}}", source);
let wrapped = allocator.alloc_str(&wrapped_string);
let parser = Parser::new(allocator, wrapped, source_type);
let result = parser.parse_expression();
match result {
Ok(expr) => {
if let Expression::ArrowFunctionExpression(arrow) = expr {
let mut params = arrow.unbox().params.unbox();
subtract_formal_parameters_spans(&mut params, WRAPPER_OFFSET);
VSlotParseResult {
params: Some(params),
offset: WRAPPER_OFFSET,
errors: None,
}
} else {
VSlotParseResult {
params: None,
offset: WRAPPER_OFFSET,
errors: Some(vec![OxcDiagnostic::error(
"Failed to parse slot expression as arrow function parameters",
)]),
}
}
}
Err(errors) => VSlotParseResult {
params: None,
offset: WRAPPER_OFFSET,
errors: Some(errors),
},
}
}
pub fn parse_vslot_with_bindings<'a>(
allocator: &'a Allocator,
source: &str,
source_type: SourceType,
) -> VSlotWithBindings<'a> {
let result = parse_vslot(allocator, source, source_type);
let (locals, references) = if result.errors.is_some() {
(Vec::new(), Vec::new())
} else if let Some(params) = &result.params {
extract_slot_bindings_internal(params, source)
} else {
(Vec::new(), Vec::new())
};
VSlotWithBindings {
result,
locals,
references,
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxc_ast::ast::BindingPattern;
fn parse(source: &str) -> VSlotParseResult<'static> {
let allocator = Box::leak(Box::new(Allocator::default()));
parse_vslot(allocator, source, SourceType::tsx())
}
#[test]
fn test_simple_identifier() {
let result = parse("data");
assert!(result.is_ok());
assert_eq!(result.offset, 1);
let params = result.params.unwrap();
assert_eq!(params.items.len(), 1);
if let BindingPattern::BindingIdentifier(id) = ¶ms.items[0].pattern {
assert_eq!(id.name.as_str(), "data");
assert_eq!(id.span.start, 0);
assert_eq!(id.span.end, 4);
} else {
panic!("Expected BindingIdentifier");
}
}
#[test]
fn test_object_destructuring() {
let result = parse("{ item, index }");
assert!(result.is_ok());
let params = result.params.unwrap();
assert_eq!(params.items.len(), 1);
if let BindingPattern::ObjectPattern(obj) = ¶ms.items[0].pattern {
assert_eq!(obj.properties.len(), 2);
} else {
panic!("Expected ObjectPattern");
}
}
#[test]
fn test_renamed_destructuring() {
let result = parse("{ rowData: role }");
assert!(result.is_ok());
let params = result.params.unwrap();
assert_eq!(params.items.len(), 1);
if let BindingPattern::ObjectPattern(obj) = ¶ms.items[0].pattern {
assert_eq!(obj.properties.len(), 1);
if let BindingPattern::BindingIdentifier(id) = &obj.properties[0].value {
assert_eq!(id.name.as_str(), "role");
} else {
panic!("Expected BindingIdentifier for renamed property");
}
} else {
panic!("Expected ObjectPattern");
}
}
#[test]
fn test_with_default_value() {
let result = parse("{ item = defaultItem }");
assert!(result.is_ok());
let params = result.params.unwrap();
assert_eq!(params.items.len(), 1);
if let BindingPattern::ObjectPattern(obj) = ¶ms.items[0].pattern {
assert_eq!(obj.properties.len(), 1);
if let BindingPattern::AssignmentPattern(_) = &obj.properties[0].value {
} else {
panic!("Expected AssignmentPattern for default value");
}
} else {
panic!("Expected ObjectPattern");
}
}
#[test]
fn test_with_type_annotation() {
let result = parse("{ data }: { data: MyType }");
assert!(result.is_ok());
let params = result.params.unwrap();
assert_eq!(params.items.len(), 1);
assert!(params.items[0].type_annotation.is_some());
}
#[test]
fn test_multiple_params() {
let result = parse("item, index, extra");
assert!(result.is_ok());
let params = result.params.unwrap();
assert_eq!(params.items.len(), 3);
}
#[test]
fn test_rest_parameter() {
let result = parse("first, ...rest");
assert!(result.is_ok());
let params = result.params.unwrap();
assert_eq!(params.items.len(), 1); assert!(params.rest.is_some()); }
#[test]
fn test_nested_destructuring() {
let result = parse("{ user: { name, id } }");
assert!(result.is_ok());
let params = result.params.unwrap();
assert_eq!(params.items.len(), 1);
}
#[test]
fn test_array_destructuring() {
let result = parse("[first, second]");
assert!(result.is_ok());
let params = result.params.unwrap();
assert_eq!(params.items.len(), 1);
if let BindingPattern::ArrayPattern(_) = ¶ms.items[0].pattern {
} else {
panic!("Expected ArrayPattern");
}
}
#[test]
fn test_empty_input() {
let result = parse("");
assert!(result.params.is_none());
assert!(result.errors.is_none());
}
#[test]
fn test_whitespace_only() {
let result = parse(" ");
assert!(result.params.is_none());
assert!(result.errors.is_none());
}
#[test]
fn test_invalid_syntax() {
let result = parse("{ invalid: }");
assert!(!result.is_ok());
assert!(!result.errors.is_none());
}
#[test]
fn test_complex_type_annotation() {
let result = parse("data: Array<Item>");
assert!(result.is_ok());
let params = result.params.unwrap();
assert!(params.items[0].type_annotation.is_some());
}
#[test]
fn test_default_with_function_call() {
let result = parse("data = getData()");
assert!(result.is_ok());
let params = result.params.unwrap();
assert!(params.items[0].initializer.is_some());
}
#[test]
fn test_span_offset() {
let result = parse("data");
assert!(result.is_ok());
let params = result.params.unwrap();
if let BindingPattern::BindingIdentifier(id) = ¶ms.items[0].pattern {
assert_eq!(id.span.start, 0);
assert_eq!(id.span.end, 4);
}
}
#[test]
fn test_complex_slot_expression() {
let result = parse("{ rowData: role }: { rowData: ProjectRole }");
assert!(result.is_ok());
let params = result.params.unwrap();
assert_eq!(params.items.len(), 1);
if let BindingPattern::ObjectPattern(obj) = ¶ms.items[0].pattern {
if let BindingPattern::BindingIdentifier(id) = &obj.properties[0].value {
assert_eq!(id.name.as_str(), "role");
}
}
assert!(params.items[0].type_annotation.is_some());
}
}