use oxc_allocator::{Address, GetAddress};
use oxc_ast::ast::*;
use oxc_semantic::Scoping;
use sha1::{Digest, Sha1};
use crate::chunk::dedupe::DedupeState;
pub fn dedupe_hash<'a>(
state: &mut DedupeState,
node: &Expression<'a>,
scoping: &Scoping,
) -> Option<()> {
walk_expr(state, None, node, scoping, node.address())?;
Some(())
}
fn walk_expr<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &Expression<'a>,
scoping: &Scoping,
address: Address,
) -> Option<()> {
match node {
Expression::BooleanLiteral(node) => walk_boolean_literal(w, node),
Expression::NullLiteral(_) => walk_null_literal(w),
Expression::NumericLiteral(node) => walk_numeric_literal(w, node),
Expression::BigIntLiteral(node) => walk_big_int_literal(state, w, node, address),
Expression::RegExpLiteral(node) => walk_reg_exp_literal(state, w, node, address),
Expression::StringLiteral(node) => walk_string_literal(state, w, node, address),
Expression::TemplateLiteral(node) => {
walk_template_literal(state, w, node, scoping, address)
}
Expression::Identifier(node) => walk_identifier_reference(w, node, scoping),
Expression::CallExpression(node) => walk_call_expression(state, w, node, scoping, address),
Expression::ArrayExpression(node) => {
walk_array_expression(state, w, node, scoping, address)
}
Expression::ObjectExpression(node) => {
walk_object_expression(state, w, node, scoping, address)
}
Expression::ParenthesizedExpression(node) => {
walk_parenthesized_expression(state, w, node, scoping, address)
}
Expression::TaggedTemplateExpression(node) => {
walk_tagged_template_expression(state, w, node, scoping, address)
}
Expression::StaticMemberExpression(node) => {
walk_static_member_expression(state, w, node, scoping, address)
}
Expression::MetaProperty(_)
| Expression::Super(_)
| Expression::ArrowFunctionExpression(_)
| Expression::AssignmentExpression(_)
| Expression::AwaitExpression(_)
| Expression::BinaryExpression(_)
| Expression::ChainExpression(_)
| Expression::ClassExpression(_)
| Expression::ConditionalExpression(_)
| Expression::FunctionExpression(_)
| Expression::ImportExpression(_)
| Expression::LogicalExpression(_)
| Expression::NewExpression(_)
| Expression::SequenceExpression(_)
| Expression::ThisExpression(_)
| Expression::UnaryExpression(_)
| Expression::UpdateExpression(_)
| Expression::YieldExpression(_)
| Expression::PrivateInExpression(_)
| Expression::JSXElement(_)
| Expression::JSXFragment(_)
| Expression::TSAsExpression(_)
| Expression::TSSatisfiesExpression(_)
| Expression::TSTypeAssertion(_)
| Expression::TSNonNullExpression(_)
| Expression::TSInstantiationExpression(_)
| Expression::V8IntrinsicExpression(_)
| Expression::ComputedMemberExpression(_)
| Expression::PrivateFieldExpression(_) => None,
}
}
fn walk_call_expression(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &CallExpression,
scoping: &Scoping,
address: Address,
) -> Option<()> {
let mut h = Sha1::default();
h.update(Tag::Call.to_ne_bytes());
walk_expr(state, Some(&mut h), &node.callee, scoping, node.callee.address())?;
h.update(node.arguments.len().to_ne_bytes());
for arg in &node.arguments {
if let Some(expr) = arg.as_expression() {
walk_expr(state, Some(&mut h), expr, scoping, expr.address())?;
} else {
return None;
}
}
let hash = h.finalize();
state.add(address, hash.into());
if let Some(w) = w {
w.update(Tag::Hash.to_ne_bytes());
w.update(hash);
}
Some(())
}
fn walk_array_expression<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &ArrayExpression<'a>,
scoping: &Scoping,
address: Address,
) -> Option<()> {
let mut h = Sha1::default();
h.update(Tag::ArrayExpression.to_ne_bytes());
h.update(node.elements.len().to_ne_bytes());
for item in &node.elements {
walk_array_expression_element(state, &mut h, item, scoping)?;
}
let hash = h.finalize();
state.add(address, hash.into());
if let Some(w) = w {
w.update(Tag::Hash.to_ne_bytes());
w.update(hash);
}
Some(())
}
fn walk_array_expression_element<'a>(
state: &mut DedupeState,
w: &mut Sha1,
node: &ArrayExpressionElement<'a>,
scoping: &Scoping,
) -> Option<()> {
match node {
ArrayExpressionElement::SpreadElement(node) => walk_spread_element(state, w, node, scoping),
ArrayExpressionElement::Elision(_) => walk_elision(w),
ArrayExpressionElement::BooleanLiteral(_)
| ArrayExpressionElement::NullLiteral(_)
| ArrayExpressionElement::NumericLiteral(_)
| ArrayExpressionElement::BigIntLiteral(_)
| ArrayExpressionElement::RegExpLiteral(_)
| ArrayExpressionElement::StringLiteral(_)
| ArrayExpressionElement::TemplateLiteral(_)
| ArrayExpressionElement::Identifier(_)
| ArrayExpressionElement::MetaProperty(_)
| ArrayExpressionElement::Super(_)
| ArrayExpressionElement::ArrayExpression(_)
| ArrayExpressionElement::ArrowFunctionExpression(_)
| ArrayExpressionElement::AssignmentExpression(_)
| ArrayExpressionElement::AwaitExpression(_)
| ArrayExpressionElement::BinaryExpression(_)
| ArrayExpressionElement::CallExpression(_)
| ArrayExpressionElement::ChainExpression(_)
| ArrayExpressionElement::ClassExpression(_)
| ArrayExpressionElement::ConditionalExpression(_)
| ArrayExpressionElement::FunctionExpression(_)
| ArrayExpressionElement::ImportExpression(_)
| ArrayExpressionElement::LogicalExpression(_)
| ArrayExpressionElement::NewExpression(_)
| ArrayExpressionElement::ObjectExpression(_)
| ArrayExpressionElement::ParenthesizedExpression(_)
| ArrayExpressionElement::SequenceExpression(_)
| ArrayExpressionElement::TaggedTemplateExpression(_)
| ArrayExpressionElement::ThisExpression(_)
| ArrayExpressionElement::UnaryExpression(_)
| ArrayExpressionElement::UpdateExpression(_)
| ArrayExpressionElement::YieldExpression(_)
| ArrayExpressionElement::PrivateInExpression(_)
| ArrayExpressionElement::JSXElement(_)
| ArrayExpressionElement::JSXFragment(_)
| ArrayExpressionElement::TSAsExpression(_)
| ArrayExpressionElement::TSSatisfiesExpression(_)
| ArrayExpressionElement::TSTypeAssertion(_)
| ArrayExpressionElement::TSNonNullExpression(_)
| ArrayExpressionElement::TSInstantiationExpression(_)
| ArrayExpressionElement::V8IntrinsicExpression(_)
| ArrayExpressionElement::ComputedMemberExpression(_)
| ArrayExpressionElement::StaticMemberExpression(_)
| ArrayExpressionElement::PrivateFieldExpression(_) => {
let expr = node.as_expression()?;
walk_expr(state, Some(w), expr, scoping, expr.address())
}
}
}
fn walk_object_expression<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &ObjectExpression<'a>,
scoping: &Scoping,
address: Address,
) -> Option<()> {
let mut h = Sha1::default();
h.update(Tag::ObjectExpression.to_ne_bytes());
h.update(node.properties.len().to_ne_bytes());
for item in &node.properties {
walk_object_property_kind(state, &mut h, item, scoping)?;
}
let hash = h.finalize();
state.add(address, hash.into());
if let Some(w) = w {
w.update(Tag::Hash.to_ne_bytes());
w.update(hash);
}
Some(())
}
fn walk_object_property_kind<'a>(
state: &mut DedupeState,
w: &mut Sha1,
node: &ObjectPropertyKind<'a>,
scoping: &Scoping,
) -> Option<()> {
match node {
ObjectPropertyKind::ObjectProperty(node) => walk_object_property(state, w, node, scoping),
ObjectPropertyKind::SpreadProperty(node) => walk_spread_element(state, w, node, scoping),
}
}
fn walk_object_property<'a>(
state: &mut DedupeState,
w: &mut Sha1,
node: &ObjectProperty<'a>,
scoping: &Scoping,
) -> Option<()> {
w.update(Tag::ObjectPropertyKey.to_ne_bytes());
walk_property_key(state, Some(w), &node.key, scoping, node.key.address())?;
w.update(Tag::ObjectPropertyValue.to_ne_bytes());
walk_expr(state, Some(w), &node.value, scoping, node.value.address())?;
Some(())
}
fn walk_property_key<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &PropertyKey<'a>,
scoping: &Scoping,
address: Address,
) -> Option<()> {
match node {
PropertyKey::StaticIdentifier(node) => walk_identifier_name(w, node),
PropertyKey::PrivateIdentifier(node) => walk_private_identifier(w, node),
PropertyKey::BooleanLiteral(node) => walk_boolean_literal(w, node),
PropertyKey::NullLiteral(_) => walk_null_literal(w),
PropertyKey::NumericLiteral(node) => walk_numeric_literal(w, node),
PropertyKey::BigIntLiteral(node) => walk_big_int_literal(state, w, node, address),
PropertyKey::RegExpLiteral(node) => walk_reg_exp_literal(state, w, node, address),
PropertyKey::StringLiteral(node) => walk_string_literal(state, w, node, address),
PropertyKey::TemplateLiteral(node) => {
walk_template_literal(state, w, node, scoping, address)
}
PropertyKey::Identifier(node) => walk_identifier_reference(w, node, scoping),
PropertyKey::MetaProperty(_)
| PropertyKey::Super(_)
| PropertyKey::ArrayExpression(_)
| PropertyKey::ArrowFunctionExpression(_)
| PropertyKey::AssignmentExpression(_)
| PropertyKey::AwaitExpression(_)
| PropertyKey::BinaryExpression(_)
| PropertyKey::CallExpression(_)
| PropertyKey::ChainExpression(_)
| PropertyKey::ClassExpression(_)
| PropertyKey::ConditionalExpression(_)
| PropertyKey::FunctionExpression(_)
| PropertyKey::ImportExpression(_)
| PropertyKey::LogicalExpression(_)
| PropertyKey::NewExpression(_)
| PropertyKey::ObjectExpression(_)
| PropertyKey::ParenthesizedExpression(_)
| PropertyKey::SequenceExpression(_)
| PropertyKey::TaggedTemplateExpression(_)
| PropertyKey::ThisExpression(_)
| PropertyKey::UnaryExpression(_)
| PropertyKey::UpdateExpression(_)
| PropertyKey::YieldExpression(_)
| PropertyKey::PrivateInExpression(_)
| PropertyKey::JSXElement(_)
| PropertyKey::JSXFragment(_)
| PropertyKey::TSAsExpression(_)
| PropertyKey::TSSatisfiesExpression(_)
| PropertyKey::TSTypeAssertion(_)
| PropertyKey::TSNonNullExpression(_)
| PropertyKey::TSInstantiationExpression(_)
| PropertyKey::V8IntrinsicExpression(_)
| PropertyKey::ComputedMemberExpression(_)
| PropertyKey::StaticMemberExpression(_)
| PropertyKey::PrivateFieldExpression(_) => None,
}
}
fn walk_template_literal<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &TemplateLiteral<'a>,
scoping: &Scoping,
address: Address,
) -> Option<()> {
let mut h = Sha1::default();
h.update(Tag::TemplateLiteral.to_ne_bytes());
h.update(node.quasis.len().to_ne_bytes());
for item in &node.quasis {
walk_template_element(&mut h, item)?;
}
h.update(node.expressions.len().to_ne_bytes());
for item in &node.expressions {
walk_expr(state, Some(&mut h), item, scoping, item.address())?;
}
let hash = h.finalize();
state.add(address, hash.into());
if let Some(w) = w {
w.update(Tag::Hash.to_ne_bytes());
w.update(hash);
}
Some(())
}
fn walk_template_element<'a>(w: &mut Sha1, node: &TemplateElement<'a>) -> Option<()> {
w.update(Tag::TemplateElement.to_ne_bytes());
let s = &node.value.raw;
w.update(s.len().to_ne_bytes());
w.update(s.as_bytes());
Some(())
}
fn walk_tagged_template_expression<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &TaggedTemplateExpression<'a>,
scoping: &Scoping,
address: Address,
) -> Option<()> {
let mut h = Sha1::default();
h.update(Tag::TaggedTemplateExpression.to_ne_bytes());
walk_expr(state, Some(&mut h), &node.tag, scoping, address)?;
h.update(node.quasi.quasis.len().to_ne_bytes());
for item in &node.quasi.quasis {
walk_template_element(&mut h, item)?;
}
h.update(node.quasi.expressions.len().to_ne_bytes());
for item in &node.quasi.expressions {
walk_expr(state, Some(&mut h), item, scoping, item.address())?;
}
let hash = h.finalize();
state.add(address, hash.into());
if let Some(w) = w {
w.update(Tag::Hash.to_ne_bytes());
w.update(hash);
}
Some(())
}
fn walk_static_member_expression<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &StaticMemberExpression<'a>,
scoping: &Scoping,
address: Address,
) -> Option<()> {
let mut h = Sha1::default();
h.update(Tag::StaticMemberExpression.to_ne_bytes());
walk_expr(state, Some(&mut h), &node.object, scoping, address)?;
walk_identifier_name(Some(&mut h), &node.property)?;
let hash = h.finalize();
state.add(address, hash.into());
if let Some(w) = w {
w.update(Tag::Hash.to_ne_bytes());
w.update(hash);
}
Some(())
}
fn walk_parenthesized_expression<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &ParenthesizedExpression<'a>,
scoping: &Scoping,
address: Address,
) -> Option<()> {
walk_expr(state, w, &node.expression, scoping, address)
}
fn walk_boolean_literal(w: Option<&mut Sha1>, node: &BooleanLiteral) -> Option<()> {
if let Some(h) = w {
h.update((node.value as u8).to_ne_bytes());
}
Some(())
}
fn walk_null_literal(w: Option<&mut Sha1>) -> Option<()> {
if let Some(w) = w {
w.update(Tag::NullLiteral.to_ne_bytes());
}
Some(())
}
fn walk_numeric_literal<'a>(w: Option<&mut Sha1>, node: &NumericLiteral<'a>) -> Option<()> {
if let Some(h) = w {
h.update(Tag::NumericLiteral.to_ne_bytes());
h.update(node.value.to_ne_bytes());
}
Some(())
}
fn walk_string_literal<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &StringLiteral<'a>,
address: Address,
) -> Option<()> {
let s = &node.value;
if s.len() > 16 {
let mut h = Sha1::default();
h.update(Tag::StringLiteral.to_ne_bytes());
h.update(s.len().to_ne_bytes());
h.update(s.as_bytes());
let hash = h.finalize();
state.add(address, hash.into());
if let Some(w) = w {
w.update(Tag::Hash.to_ne_bytes());
w.update(hash);
}
} else if let Some(h) = w {
h.update(Tag::StringLiteral.to_ne_bytes());
h.update(s.len().to_ne_bytes());
h.update(s.as_bytes());
};
Some(())
}
fn walk_big_int_literal<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &BigIntLiteral<'a>,
address: Address,
) -> Option<()> {
let mut h = Sha1::default();
h.update(Tag::BigIntLiteral.to_ne_bytes());
let s = &node.value;
h.update(s.len().to_ne_bytes());
h.update(s.as_bytes());
let hash = h.finalize();
state.add(address, hash.into());
if let Some(w) = w {
w.update(Tag::Hash.to_ne_bytes());
w.update(hash);
}
Some(())
}
fn walk_reg_exp_literal<'a>(
state: &mut DedupeState,
w: Option<&mut Sha1>,
node: &RegExpLiteral<'a>,
address: Address,
) -> Option<()> {
let mut h = Sha1::default();
h.update(Tag::RegExpLiteral.to_ne_bytes());
let Some(s) = &node.raw else {
return None;
};
h.update(s.len().to_ne_bytes());
h.update(s.as_bytes());
let hash = h.finalize();
state.add(address, hash.into());
if let Some(w) = w {
w.update(Tag::Hash.to_ne_bytes());
w.update(hash);
}
Some(())
}
fn walk_spread_element<'a>(
state: &mut DedupeState,
w: &mut Sha1,
node: &SpreadElement<'a>,
scoping: &Scoping,
) -> Option<()> {
w.update(Tag::SpreadElement.to_ne_bytes());
walk_expr(state, Some(w), &node.argument, scoping, node.argument.address())?;
Some(())
}
fn walk_elision(w: &mut Sha1) -> Option<()> {
w.update(Tag::Elision.to_ne_bytes());
Some(())
}
fn walk_identifier_reference<'a>(
w: Option<&mut Sha1>,
node: &IdentifierReference<'a>,
scoping: &Scoping,
) -> Option<()> {
if let Some(h) = w {
let r = scoping.get_reference(node.reference_id());
if let Some(s) = r.symbol_id() {
h.update(Tag::IdentifierReferenceSymbol.to_ne_bytes());
h.update(s.index().to_ne_bytes());
} else {
h.update(Tag::IdentifierReferenceGlobal.to_ne_bytes());
let s = &node.name;
h.update(s.len().to_ne_bytes());
h.update(s.as_bytes());
}
}
Some(())
}
fn walk_identifier_name<'a>(w: Option<&mut Sha1>, node: &IdentifierName<'a>) -> Option<()> {
if let Some(h) = w {
h.update(Tag::IdentifierName.to_ne_bytes());
h.update(node.name.as_bytes());
}
Some(())
}
fn walk_private_identifier<'a>(w: Option<&mut Sha1>, node: &PrivateIdentifier<'a>) -> Option<()> {
if let Some(h) = w {
h.update(Tag::PrivateIdentifier.to_ne_bytes());
h.update(node.name.as_bytes());
}
Some(())
}
#[expect(dead_code)]
#[derive(Clone, Copy)]
#[repr(u8)]
enum Tag {
BooleanLiteralFalse,
BooleanLiteralTrue,
NumericLiteral,
StringLiteral,
NullLiteral,
BigIntLiteral,
RegExpLiteral,
TemplateLiteral,
TemplateElement,
TaggedTemplateExpression,
StaticMemberExpression,
IdentifierReferenceSymbol,
IdentifierReferenceGlobal,
ArrayExpression,
ObjectExpression,
ObjectPropertyKey,
ObjectPropertyValue,
IdentifierName,
PrivateIdentifier,
SpreadElement,
Elision,
Call,
Hash,
}
impl Tag {
#[inline]
fn to_ne_bytes(self) -> [u8; 1] {
(self as u8).to_ne_bytes()
}
}