use super::*;
use swc_ecma_visit::Visit;
mod assignment;
mod call_expr;
mod literal_expr;
fn prop_ident(prop: &PropName) -> Option<Ident> {
match prop {
PropName::Ident(ident) => Some(ident.clone().into()),
PropName::Str(value) => Some(Ident::new(
value.value.clone(),
value.span,
swc_common::SyntaxContext::empty(),
)),
_ => None,
}
}
pub(super) fn prop_name(prop: &PropName) -> Option<String> {
prop_ident(prop).map(|ident| ident.sym.to_string())
}
fn is_placeholder_name(name: &str) -> bool {
name.starts_with('<') && name.ends_with('>')
}
impl JsParser {
pub(super) fn try_infer_numeric_literal(&self, expr: &Expr) -> Option<f64> {
match expr {
Expr::Lit(Lit::Num(n)) => Some(n.value),
Expr::Bin(b) => {
let l = self.try_infer_numeric_literal(&b.left)?;
let r = self.try_infer_numeric_literal(&b.right)?;
match b.op {
BinaryOp::Add => Some(l + r),
BinaryOp::Sub => Some(l - r),
BinaryOp::Mul => Some(l * r),
BinaryOp::Div => {
if r != 0.0 {
Some(l / r)
} else {
None
}
}
BinaryOp::BitOr => Some(((l as i64) | (r as i64)) as f64),
BinaryOp::BitAnd => Some(((l as i64) & (r as i64)) as f64),
BinaryOp::BitXor => Some(((l as i64) ^ (r as i64)) as f64),
BinaryOp::LShift => Some(((l as i64) << (r as i64)) as f64),
BinaryOp::RShift => Some(((l as i64) >> (r as i64)) as f64),
_ => None,
}
}
Expr::Paren(p) => self.try_infer_numeric_literal(&p.expr),
_ => None,
}
}
pub(super) fn try_infer_string_literal(&self, expr: &Expr) -> Option<String> {
match expr {
Expr::Lit(Lit::Str(s)) => Some(s.value.to_string()),
Expr::Bin(b) => {
if b.op == BinaryOp::Add {
let left = self.try_infer_string_literal(&b.left)?;
let right = self.try_infer_string_literal(&b.right)?;
Some(left + right.as_str())
} else {
None
}
}
Expr::Tpl(t) => {
let mut s = String::new();
for (i, q) in t.quasis.iter().enumerate() {
s.push_str(q.raw.as_ref());
if let Some(expr) = t.exprs.get(i) {
s.push_str(&self.try_infer_string_literal(expr)?);
}
}
Some(s)
}
Expr::Ident(i) => self.known_strings.get(&i.sym.to_string()).cloned(),
Expr::Paren(p) => self.try_infer_string_literal(&p.expr),
Expr::Call(c) => {
if let Callee::Expr(ce) = &c.callee {
if let Expr::Member(m) = &**ce {
if let Expr::Ident(obj) = &*m.obj {
if obj.sym == "String" {
if let MemberProp::Ident(prop) = &m.prop {
if prop.sym == "fromCharCode" {
let mut s = String::new();
for arg in &c.args {
if let Some(n) = self.try_infer_numeric_literal(&arg.expr) {
if let Some(ch) = std::char::from_u32(n as u32) {
s.push(ch);
} else {
return None;
}
} else {
return None;
}
}
return Some(s);
}
}
}
}
}
if let Expr::Member(m) = &**ce {
if let MemberProp::Ident(prop) = &m.prop {
if prop.sym == "join" {
if let Expr::Ident(arr_ident) = &*m.obj {
if let Some(arr) =
self.known_arrays.get(&arr_ident.sym.to_string())
{
let sep = c
.args
.first()
.and_then(|a| match &*a.expr {
Expr::Lit(Lit::Str(s)) => Some(s.value.to_string()),
_ => None,
})
.unwrap_or_else(|| ",".to_string());
return Some(arr.join(&sep));
}
}
}
}
}
}
None
}
_ => None,
}
}
pub(super) fn try_infer_array_literal(&self, expr: &Expr) -> Option<Vec<String>> {
match expr {
Expr::Array(a) => {
let mut arr = Vec::new();
for e in a.elems.iter().flatten() {
if let Expr::Lit(Lit::Str(s)) = &*e.expr {
arr.push(s.value.to_string());
} else {
return None;
}
}
Some(arr)
}
Expr::Ident(i) => self.known_arrays.get(&i.sym.to_string()).cloned(),
Expr::Paren(p) => self.try_infer_array_literal(&p.expr),
_ => None,
}
}
pub(super) fn node_binding_alias(&self, id: NodeId) -> Option<String> {
let node = self.graph.node(id)?;
if let Some(alias) = &node.alias {
return Some(alias.clone());
}
if matches!(node.kind, NodeKind::Variable | NodeKind::Import | NodeKind::Call)
&& !is_placeholder_name(&node.name)
{
Some(node.name.clone())
} else {
None
}
}
pub(super) fn binding_name(&self, id: NodeId) -> String {
self.node_binding_alias(id).unwrap_or_else(|| self.name(id))
}
pub(super) fn member_binding_source(&mut self, source: NodeId, property: &str) -> NodeId {
let full = format!("{}.{}", self.binding_name(source), property);
if let Some(id) = self.resolve(&full) {
return id;
}
let id = self.graph.add_node(NodeKind::Variable, full.clone(), None);
if let Some(node) = self.graph.node_mut(id) {
node.alias = Some(full.clone());
}
self.cur().define(full, id);
self.flow(source, id);
id
}
pub(super) fn indexed_binding_source(&mut self, source: NodeId, index: usize) -> NodeId {
let full = format!("{}[{}]", self.binding_name(source), index);
if let Some(id) = self.resolve(&full) {
return id;
}
let id = self.graph.add_node(NodeKind::Variable, full.clone(), None);
if let Some(node) = self.graph.node_mut(id) {
node.alias = Some(full.clone());
}
self.cur().define(full, id);
self.flow(source, id);
id
}
pub(super) fn array_element_binding_source(&mut self, source: NodeId) -> NodeId {
let full = format!("{}[]", self.binding_name(source));
if let Some(id) = self.resolve(&full) {
return id;
}
let id = self.graph.add_node(NodeKind::Variable, full.clone(), None);
if let Some(node) = self.graph.node_mut(id) {
node.alias = Some(full.clone());
}
self.cur().define(full, id);
self.flow(source, id);
id
}
pub(super) fn materialize_composite_bindings(&mut self, base_name: &str, source: NodeId) {
let Some(bindings) = self.composite_bindings.get(&source).cloned() else {
return;
};
for (suffix, node_id) in &bindings {
let full = format!("{base_name}{suffix}");
if let Some(node) = self.graph.node_mut(*node_id) {
node.alias = Some(full.clone());
}
self.cur().define(full, *node_id);
}
if let Some(var_node) = self.resolve(base_name) {
self.composite_bindings.insert(var_node, bindings);
}
}
fn eval_member_expr(&mut self, member: &MemberExpr) -> NodeId {
let obj = self.eval_expr(&member.obj);
match &member.prop {
MemberProp::Ident(i) => {
let full = format!("{}.{}", self.binding_name(obj), i.sym);
if let Some(id) = self.resolve(&full) {
if self.assigned_members.contains(&full) {
let snap = self.graph.add_node(NodeKind::Variable, full.clone(), None);
if let Some(node) = self.graph.node_mut(snap) {
node.alias = Some(full.clone());
}
self.flow(id, snap);
let proto_prop = format!("Object.prototype.{}", i.sym);
let proto_node = self.var(&proto_prop);
self.flow(proto_node, snap);
return snap;
}
let proto_prop = format!("Object.prototype.{}", i.sym);
let proto_node = self.var(&proto_prop);
self.flow(proto_node, id);
return id;
}
let id = self.graph.add_node(NodeKind::Variable, full.clone(), None);
if let Some(node) = self.graph.node_mut(id) {
node.alias = Some(full.clone());
}
self.cur().define(full, id);
self.flow(obj, id);
let proto_prop = format!("Object.prototype.{}", i.sym);
let proto_node = self.var(&proto_prop);
self.flow(proto_node, id);
id
}
MemberProp::Computed(c) => {
let _ = self.eval_expr(&c.expr);
let property = self
.try_infer_string_literal(&c.expr)
.unwrap_or_else(|| "<computed>".to_string());
let full = format!("{}.{}", self.binding_name(obj), property);
if property != "<computed>" {
if let Some(id) = self.resolve(&full) {
let proto_prop = format!("Object.prototype.{}", property);
let proto_node = self.var(&proto_prop);
self.flow(proto_node, id);
return id;
}
}
let id = self.graph.add_node(NodeKind::Variable, full.clone(), None);
if let Some(node) = self.graph.node_mut(id) {
node.alias = Some(full.clone());
}
self.cur().define(full, id);
self.flow(obj, id);
if property != "<computed>" {
let proto_prop = format!("Object.prototype.{}", property);
let proto_node = self.var(&proto_prop);
self.flow(proto_node, id);
}
id
}
_ => {
let id = self.graph.add_node(
NodeKind::Variable,
format!("{}.<private>", self.binding_name(obj)),
None,
);
self.flow(obj, id);
id
}
}
}
fn eval_opt_chain(&mut self, opt_chain: &OptChainExpr) -> NodeId {
match &*opt_chain.base {
OptChainBase::Member(member) => self.eval_member_expr(member),
OptChainBase::Call(call) => self.eval_call_expr(&CallExpr::from(call.clone())),
}
}
pub(super) fn eval_expr(&mut self, expr: &Expr) -> NodeId {
match expr {
Expr::Ident(i) => {
let name = i.sym.to_string();
self.resolve(&name).unwrap_or_else(|| {
let id = self.graph.add_node(NodeKind::Variable, name.clone(), None);
self.cur().define(name, id);
id
})
}
Expr::Member(m) => self.eval_member_expr(m),
Expr::Call(c) => self.eval_call_expr(c),
Expr::New(n) => self.eval_new_expr(n),
Expr::Lit(Lit::Str(s)) => self.graph.add_node(NodeKind::Literal, s.value.to_string(), None),
Expr::Lit(_) => self.lit(),
Expr::TaggedTpl(t) => self.eval_tagged_template(t),
Expr::Tpl(t) => {
let id = self.graph.add_node(NodeKind::Variable, "<template>".into(), None);
for expr in &t.exprs {
let value = self.eval_expr(expr);
self.flow(value, id);
}
id
}
Expr::Array(a) => self.eval_array_literal(a),
Expr::Object(o) => self.eval_object_literal(o),
Expr::Assign(a) => self.eval_assignment_expr(a),
Expr::Bin(b) => {
let left = self.eval_expr(&b.left);
let right = self.eval_expr(&b.right);
let id = self.graph.add_node(NodeKind::Variable, "<bin>".into(), None);
self.flow(left, id);
self.flow(right, id);
id
}
Expr::Unary(u) => self.eval_expr(&u.arg),
Expr::Update(u) => self.eval_expr(&u.arg),
Expr::Cond(c) => {
let _ = self.eval_expr(&c.test);
let cons = self.eval_expr(&c.cons);
let alt = self.eval_expr(&c.alt);
let id = self.graph.add_node(NodeKind::Variable, "<cond>".into(), None);
self.flow(cons, id);
self.flow(alt, id);
id
}
Expr::Seq(s) => {
let mut last = None;
for expr in &s.exprs {
last = Some(self.eval_expr(expr));
}
last.unwrap_or_else(|| self.lit())
}
Expr::Paren(p) => self.eval_expr(&p.expr),
Expr::Fn(f) => self.eval_fn(f),
Expr::Arrow(a) => self.eval_arrow(a),
Expr::Await(a) => self.eval_expr(&a.arg),
Expr::Yield(y) => {
let arg = y
.arg
.as_deref()
.map(|expr| self.eval_expr(expr))
.unwrap_or_else(|| self.lit());
if let Some(fn_node) = self.current_function_node {
self.graph.add_edge(arg, fn_node, EdgeKind::Return);
}
arg
}
Expr::OptChain(o) => self.eval_opt_chain(o),
Expr::TsTypeAssertion(t) => self.eval_expr(&t.expr),
Expr::TsConstAssertion(t) => self.eval_expr(&t.expr),
Expr::TsNonNull(t) => self.eval_expr(&t.expr),
Expr::TsAs(t) => self.eval_expr(&t.expr),
Expr::TsInstantiation(t) => self.eval_expr(&t.expr),
_ => self.lit(),
}
}
fn infer_require(&mut self, c: &CallExpr) -> String {
if let Some(first) = c.args.first() {
if let Expr::Lit(Lit::Str(s)) = &*first.expr {
let spec = s.value.to_string();
let id = self.graph.add_node(NodeKind::Import, format!("require({})", spec), None);
self.requires.push((id, spec.clone()));
return spec;
}
if let Expr::Bin(b) = &*first.expr {
if b.op == BinaryOp::Add {
if let (Expr::Lit(Lit::Str(left)), Expr::Lit(Lit::Str(right))) =
(&*b.left, &*b.right)
{
let spec = format!("{}{}", left.value, right.value);
let id =
self.graph.add_node(NodeKind::Import, format!("require({})", spec), None);
self.requires.push((id, spec.clone()));
return spec;
}
}
}
if let Some(spec) = self.try_infer_string_literal(&first.expr) {
let id = self.graph.add_node(NodeKind::Import, format!("require({})", spec), None);
self.requires.push((id, spec.clone()));
return spec;
}
}
"require(<dynamic>)".to_string()
}
}