use crate::db::TypeDatabase;
use crate::types::{ObjectShapeId, PropertyInfo, PropertyLookup, TypeId};
use tsz_common::interner::Atom;
pub fn is_numeric_property_name(interner: &dyn TypeDatabase, name: Atom) -> bool {
let prop_name = interner.resolve_atom_ref(name);
is_numeric_literal_name(prop_name.as_ref())
}
pub fn is_numeric_literal_name(name: &str) -> bool {
if name == "NaN" || name == "Infinity" || name == "-Infinity" {
return true;
}
let value: f64 = match name.parse() {
Ok(value) => value,
Err(_) => return false,
};
if !value.is_finite() {
return false;
}
js_number_to_string(value) == name
}
pub fn canonicalize_numeric_name(name: &str) -> Option<String> {
let value: f64 = tsz_common::numeric::parse_numeric_literal_value(name)?;
if !value.is_finite() && !value.is_nan() {
return None;
}
Some(js_number_to_string(value))
}
fn js_number_to_string(value: f64) -> String {
if value.is_nan() {
return "NaN".to_string();
}
if value == 0.0 {
return "0".to_string();
}
if value.is_infinite() {
return if value.is_sign_negative() {
"-Infinity".to_string()
} else {
"Infinity".to_string()
};
}
let abs = value.abs();
if !(1e-6..1e21).contains(&abs) {
let mut formatted = format!("{value:e}");
if let Some(split) = formatted.find('e') {
let (mantissa, exp) = formatted.split_at(split);
let exp_digits = exp.strip_prefix('e').unwrap_or("");
let (sign, digits) = if let Some(digits) = exp_digits.strip_prefix('-') {
('-', digits)
} else {
('+', exp_digits)
};
let trimmed = digits.trim_start_matches('0');
let digits = if trimmed.is_empty() { "0" } else { trimmed };
formatted = format!("{mantissa}e{sign}{digits}");
}
return formatted;
}
let formatted = value.to_string();
if formatted == "-0" {
"0".to_string()
} else {
formatted
}
}
pub fn union_or_single(db: &dyn TypeDatabase, types: Vec<TypeId>) -> TypeId {
match types.len() {
0 => TypeId::NEVER,
1 => types[0],
_ => db.union(types),
}
}
pub fn intersection_or_single(db: &dyn TypeDatabase, types: Vec<TypeId>) -> TypeId {
match types.len() {
0 => TypeId::NEVER,
1 => types[0],
_ => db.intersection(types),
}
}
pub trait TypeIdExt {
fn non_never(self) -> Option<Self>
where
Self: Sized;
}
impl TypeIdExt for TypeId {
#[inline]
fn non_never(self) -> Option<Self> {
(self != Self::NEVER).then_some(self)
}
}
pub fn lookup_property<'props>(
db: &dyn TypeDatabase,
props: &'props [PropertyInfo],
shape_id: Option<ObjectShapeId>,
name: Atom,
) -> Option<&'props PropertyInfo> {
if let Some(shape_id) = shape_id {
match db.object_property_index(shape_id, name) {
PropertyLookup::Found(idx) => return props.get(idx),
PropertyLookup::NotFound => return None,
PropertyLookup::Uncached => {}
}
}
props
.binary_search_by_key(&name, |p| p.name)
.ok()
.map(|idx| &props[idx])
}
#[cfg(test)]
#[path = "../tests/utils_tests.rs"]
mod tests;