#[allow(clippy::wildcard_imports, reason = "many destructure AST types used")]
use oxc_ast::ast::*;
use std::borrow::Cow;
use super::super::{BindingTarget, ModuleInfoExtractor};
use super::{static_member_object_name, unwrap_static_expr};
use crate::MemberAccess;
use crate::js_text::static_name_text;
use crate::visitor::helpers::extract_type_reference_name;
impl<'a> ModuleInfoExtractor {
pub(in crate::visitor) fn record_declarator_member_destructuring(
&mut self,
declarator: &VariableDeclarator<'a>,
init: &Expression<'a>,
) {
if let BindingPattern::ObjectPattern(pattern) = &declarator.id {
self.record_object_destructure(DestructureObject::Binding(pattern), init);
}
}
pub(in crate::visitor) fn record_assignment_member_destructuring(
&mut self,
expr: &AssignmentExpression<'a>,
) {
if let AssignmentTarget::ObjectAssignmentTarget(target) = &expr.left {
self.record_object_destructure(DestructureObject::Assignment(target), &expr.right);
}
}
fn record_object_destructure(
&mut self,
pattern: DestructureObject<'_, 'a>,
source: &Expression<'a>,
) {
let source = unwrap_static_expr(source);
if let Expression::ObjectExpression(object) = source {
self.record_literal_destructure(pattern, object);
return;
}
let Some(path) = static_member_object_name(source) else {
self.record_nested_entries(&pattern.entries(), None);
return;
};
let ident = match source {
Expression::Identifier(ident) => Some(&**ident),
_ => None,
};
self.record_source_path_destructure(pattern, &path, ident);
}
fn record_source_path_destructure(
&mut self,
pattern: DestructureObject<'_, 'a>,
path: &str,
source: Option<&IdentifierReference<'_>>,
) {
let entries = pattern.entries();
if let Some(BindingTarget::Class(class_name)) = self.resolve_bound_object_name(path) {
let keys: Vec<_> = entries.iter().map(|entry| entry.key.clone()).collect();
self.credit_destructured_class_members(&class_name, source, &keys, pattern.has_rest());
self.record_nested_entries(&entries, Some(path));
return;
}
if pattern.has_rest() || entries.iter().any(|entry| entry.key.is_none()) {
if !self.imported_root_is_shadowed(path) {
self.record_whole_object_identifier_use(path);
}
self.record_nested_entries(&entries, Some(path));
return;
}
self.record_path_destructure(pattern, path);
}
fn imported_root_is_shadowed(&self, path: &str) -> bool {
let root = path.split('.').next().unwrap_or(path);
self.nested_scope_shadows(root)
&& self.imports.iter().any(|import| import.local_name == root)
}
fn record_nested_entries(&mut self, entries: &[DestructureEntry<'_, 'a>], path: Option<&str>) {
for entry in entries {
let Some(nested) = entry.nested else {
continue;
};
if let Some(default) = entry.default {
self.record_object_destructure(nested, default);
}
if let (Some(path), Some(key)) = (path, entry.key.as_deref()) {
self.record_path_destructure(nested, &format!("{path}.{key}"));
}
}
}
fn record_literal_destructure(
&mut self,
pattern: DestructureObject<'_, 'a>,
object: &ObjectExpression<'a>,
) {
for property in &object.properties {
if let ObjectPropertyKind::SpreadProperty(spread) = property {
self.record_object_destructure(pattern, &spread.argument);
}
}
for entry in pattern.entries() {
let Some(nested) = entry.nested else {
continue;
};
if let Some(default) = entry.default {
self.record_object_destructure(nested, default);
}
if let Some(value) = entry
.key
.as_deref()
.and_then(|key| object_literal_property_value(object, key))
{
self.record_object_destructure(nested, value);
}
}
}
fn record_path_destructure(&mut self, pattern: DestructureObject<'_, 'a>, path: &str) {
for entry in pattern.entries() {
if let (Some(nested), Some(default)) = (entry.nested, entry.default) {
self.record_object_destructure(nested, default);
}
let Some(key) = entry.key.as_deref() else {
continue;
};
self.record_named_member_access(path, key);
if let Some(nested) = entry.nested {
self.record_path_destructure(nested, &format!("{path}.{key}"));
}
}
}
pub(in crate::visitor) fn record_function_member_destructuring(&mut self, func: &Function<'a>) {
if func.body.is_some() {
self.record_parameter_member_destructuring(&func.params);
}
}
pub(in crate::visitor) fn record_parameter_member_destructuring(
&mut self,
params: &FormalParameters<'a>,
) {
for param in ¶ms.items {
let BindingPattern::ObjectPattern(pattern) = ¶m.pattern else {
continue;
};
if let Some(type_annotation) = param.type_annotation.as_deref() {
self.record_typed_member_destructuring(pattern, &type_annotation.type_annotation);
}
if let Some(initializer) = param.initializer.as_deref() {
self.record_object_destructure(DestructureObject::Binding(pattern), initializer);
}
}
}
pub(in crate::visitor) fn record_for_of_member_destructuring(
&mut self,
stmt: &ForOfStatement<'a>,
) {
let pattern = match &stmt.left {
ForStatementLeft::VariableDeclaration(declaration) => {
match declaration
.declarations
.first()
.map(|declarator| &declarator.id)
{
Some(BindingPattern::ObjectPattern(pattern)) => {
DestructureObject::Binding(pattern)
}
_ => return,
}
}
ForStatementLeft::ObjectAssignmentTarget(target) => {
DestructureObject::Assignment(target)
}
_ => return,
};
let iterable = unwrap_static_expr(&stmt.right);
let Expression::ArrayExpression(array) = iterable else {
self.record_iterable_element_destructure(pattern, iterable);
return;
};
for element in &array.elements {
if let ArrayExpressionElement::SpreadElement(spread) = element {
self.record_iterable_element_destructure(pattern, &spread.argument);
} else if let Some(element) = element.as_expression() {
self.record_object_destructure(pattern, element);
}
}
}
fn record_iterable_element_destructure(
&mut self,
pattern: DestructureObject<'_, 'a>,
iterable: &Expression<'a>,
) {
let Some(receiver) = static_member_object_name(unwrap_static_expr(iterable)) else {
return;
};
let receiver = self.qualify_this_scope(&receiver);
if let Some(class_name) = self.iterable_element_type_for(&receiver) {
let keys: Vec<_> = pattern
.entries()
.into_iter()
.map(|entry| entry.key)
.collect();
self.credit_destructured_class_members(&class_name, None, &keys, pattern.has_rest());
}
}
fn record_typeof_destructure(&mut self, pattern: &ObjectPattern<'a>, path: &str) {
self.record_source_path_destructure(DestructureObject::Binding(pattern), path, None);
}
fn record_typed_member_destructuring(&mut self, pattern: &ObjectPattern<'a>, ty: &TSType<'_>) {
if let TSType::TSTypeLiteral(type_lit) = ty {
for property in &pattern.properties {
let Some(key) = property.key.static_name() else {
continue;
};
let value = match &property.value {
BindingPattern::AssignmentPattern(assignment) => &assignment.left,
value => value,
};
let BindingPattern::ObjectPattern(inner) = value else {
continue;
};
let Some(property_type) = type_lit.members.iter().find_map(|member| {
let TSSignature::TSPropertySignature(signature) = member else {
return None;
};
(signature.key.static_name().as_ref() == Some(&key))
.then_some(signature.type_annotation.as_deref())
.flatten()
}) else {
continue;
};
self.record_typed_member_destructuring(inner, &property_type.type_annotation);
}
return;
}
if let TSType::TSTypeQuery(query) = ty {
if query.type_arguments.is_none()
&& let Some(path) = type_query_path(&query.expr_name)
{
self.record_typeof_destructure(pattern, &path);
}
return;
}
if self.store_factory_for_type(ty).is_some() {
return;
}
let Some(class_name) =
extract_type_reference_name(ty).and_then(|name| self.resolve_class_type_param(&name))
else {
return;
};
let keys: Vec<_> = pattern
.properties
.iter()
.map(|property| property.key.static_name().map(static_name_text))
.collect();
self.credit_destructured_class_members(&class_name, None, &keys, pattern.rest.is_some());
}
fn credit_destructured_class_members(
&mut self,
class_name: &str,
source: Option<&IdentifierReference<'_>>,
keys: &[Option<Cow<'_, str>>],
has_rest: bool,
) {
if has_rest || keys.iter().any(Option::is_none) {
self.whole_object_uses.push(class_name.to_string());
}
for member in keys.iter().flatten() {
if let Some(source) = source {
self.capture_subclass_binding_access(
source.name.as_str(),
Some(source.span),
member,
);
}
self.member_accesses.push(MemberAccess {
object: class_name.to_string(),
member: member.to_string(),
});
}
}
}
#[derive(Clone, Copy)]
enum DestructureObject<'b, 'a> {
Binding(&'b ObjectPattern<'a>),
Assignment(&'b ObjectAssignmentTarget<'a>),
}
struct DestructureEntry<'b, 'a> {
key: Option<Cow<'b, str>>,
nested: Option<DestructureObject<'b, 'a>>,
default: Option<&'b Expression<'a>>,
}
impl<'b, 'a> DestructureObject<'b, 'a> {
fn has_rest(self) -> bool {
match self {
Self::Binding(pattern) => pattern.rest.is_some(),
Self::Assignment(target) => target.rest.is_some(),
}
}
fn entries(self) -> Vec<DestructureEntry<'b, 'a>> {
match self {
Self::Binding(pattern) => pattern
.properties
.iter()
.map(|property| {
let (value, default) = match &property.value {
BindingPattern::AssignmentPattern(assignment) => {
(&assignment.left, Some(&assignment.right))
}
value => (value, None),
};
let nested = match value {
BindingPattern::ObjectPattern(inner) => Some(Self::Binding(inner)),
_ => None,
};
DestructureEntry {
key: property.key.static_name().map(static_name_text),
nested,
default: nested.and(default),
}
})
.collect(),
Self::Assignment(target) => target
.properties
.iter()
.map(|property| match property {
AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(shorthand) => {
DestructureEntry {
key: Some(Cow::Borrowed(shorthand.binding.name.as_str())),
nested: None,
default: None,
}
}
AssignmentTargetProperty::AssignmentTargetPropertyProperty(property) => {
let (value, default) = match &property.binding {
AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(
with_default,
) => (Some(&with_default.binding), Some(&with_default.init)),
value => (value.as_assignment_target(), None),
};
let nested = match value {
Some(AssignmentTarget::ObjectAssignmentTarget(inner)) => {
Some(Self::Assignment(inner))
}
_ => None,
};
DestructureEntry {
key: property.name.static_name().map(static_name_text),
nested,
default: nested.and(default),
}
}
})
.collect(),
}
}
}
fn object_literal_property_value<'b, 'a>(
object: &'b ObjectExpression<'a>,
key: &str,
) -> Option<&'b Expression<'a>> {
object.properties.iter().rev().find_map(|property| {
let ObjectPropertyKind::ObjectProperty(property) = property else {
return None;
};
if !property.key.static_name().is_some_and(|name| name == key) {
return None;
}
match property.kind {
PropertyKind::Init => Some(&property.value),
PropertyKind::Get => getter_return_value(&property.value),
PropertyKind::Set => None,
}
})
}
fn getter_return_value<'b, 'a>(value: &'b Expression<'a>) -> Option<&'b Expression<'a>> {
let Expression::FunctionExpression(getter) = value else {
return None;
};
match getter.body.as_deref()?.statements.as_slice() {
[Statement::ReturnStatement(statement)] => statement.argument.as_ref(),
_ => None,
}
}
fn type_query_path(name: &TSTypeQueryExprName<'_>) -> Option<String> {
fn type_name_path(name: &TSTypeName<'_>) -> Option<String> {
match name {
TSTypeName::IdentifierReference(ident) => Some(ident.name.to_string()),
TSTypeName::QualifiedName(qualified) => Some(format!(
"{}.{}",
type_name_path(&qualified.left)?,
qualified.right.name
)),
TSTypeName::ThisExpression(_) => None,
}
}
type_name_path(name.as_ts_type_name()?)
}