feat(ecmascript): add ToBoolean, ToNumber, ToString (#6502)

This commit is contained in:
Boshen 2024-10-13 11:03:08 +00:00
parent fc536a5648
commit 6f2253886d
15 changed files with 339 additions and 261 deletions

3
Cargo.lock generated
View file

@ -1603,8 +1603,10 @@ dependencies = [
name = "oxc_ecmascript"
version = "0.31.0"
dependencies = [
"num-traits",
"oxc_ast",
"oxc_span",
"oxc_syntax",
]
[[package]]
@ -1680,6 +1682,7 @@ dependencies = [
"oxc_cfg",
"oxc_codegen",
"oxc_diagnostics",
"oxc_ecmascript",
"oxc_index",
"oxc_macros",
"oxc_parser",

View file

@ -257,23 +257,6 @@ impl<'a> Expression<'a> {
matches!(self, Expression::BinaryExpression(_) | Expression::LogicalExpression(_))
}
/// Returns literal's value converted to the Boolean type
/// returns `true` when node is truthy, `false` when node is falsy, `None` when it cannot be determined.
/// <https://tc39.es/ecma262/#sec-toboolean>
/// 1. If argument is a Boolean, return argument.
/// 2. If argument is one of undefined, null, +0𝔽, -0𝔽, NaN, 0, or the empty String, return false.
pub fn to_boolean(&self) -> Option<bool> {
match self {
Self::BooleanLiteral(lit) => Some(lit.value),
Self::NullLiteral(_) => Some(false),
Self::NumericLiteral(lit) => Some(lit.value != 0.0),
Self::BigIntLiteral(lit) => Some(!lit.is_zero()),
Self::RegExpLiteral(_) => Some(true),
Self::StringLiteral(lit) => Some(!lit.value.is_empty()),
_ => None,
}
}
pub fn get_member_expr(&self) -> Option<&MemberExpression<'a>> {
match self.get_inner_expression() {
Expression::ChainExpression(chain_expr) => chain_expr.expression.as_member_expression(),

View file

@ -21,5 +21,7 @@ test = true
doctest = false
[dependencies]
num-traits = { workspace = true }
oxc_ast = { workspace = true }
oxc_span = { workspace = true }
oxc_syntax = { workspace = true }

View file

@ -10,11 +10,21 @@ mod prop_name;
mod string_char_at;
mod string_index_of;
mod string_last_index_of;
mod to_boolean;
mod to_int_32;
mod to_number;
mod to_string;
pub use self::{
bound_names::BoundNames, is_simple_parameter_list::IsSimpleParameterList,
private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName,
string_char_at::StringCharAt, string_index_of::StringIndexOf,
string_last_index_of::StringLastIndexOf, to_int_32::ToInt32,
bound_names::BoundNames,
is_simple_parameter_list::IsSimpleParameterList,
private_bound_identifiers::PrivateBoundIdentifiers,
prop_name::PropName,
string_char_at::StringCharAt,
string_index_of::StringIndexOf,
string_last_index_of::StringLastIndexOf,
to_boolean::ToBoolean,
to_int_32::ToInt32,
to_number::{NumberValue, ToNumber},
to_string::ToJsString,
};

View file

@ -0,0 +1,107 @@
#[allow(clippy::wildcard_imports)]
use oxc_ast::ast::*;
use crate::{NumberValue, ToNumber};
/// `ToBoolean`
///
/// <https://tc39.es/ecma262/#sec-toboolean>
pub trait ToBoolean<'a> {
fn to_boolean(&self) -> Option<bool>;
}
impl<'a> ToBoolean<'a> for Expression<'a> {
fn to_boolean(&self) -> Option<bool> {
match self {
Expression::RegExpLiteral(_)
| Expression::ArrayExpression(_)
| Expression::ArrowFunctionExpression(_)
| Expression::ClassExpression(_)
| Expression::FunctionExpression(_)
| Expression::NewExpression(_)
| Expression::ObjectExpression(_) => Some(true),
Expression::NullLiteral(_) => Some(false),
Expression::BooleanLiteral(boolean_literal) => Some(boolean_literal.value),
Expression::NumericLiteral(number_literal) => Some(number_literal.value != 0.0),
Expression::BigIntLiteral(big_int_literal) => Some(!big_int_literal.is_zero()),
Expression::StringLiteral(string_literal) => Some(!string_literal.value.is_empty()),
Expression::TemplateLiteral(template_literal) => {
// only for ``
template_literal
.quasis
.first()
.filter(|quasi| quasi.tail)
.and_then(|quasi| quasi.value.cooked.as_ref())
.map(|cooked| !cooked.is_empty())
}
Expression::Identifier(ident) => match ident.name.as_str() {
"NaN" | "undefined" => Some(false),
"Infinity" => Some(true),
_ => None,
},
Expression::AssignmentExpression(assign_expr) => {
match assign_expr.operator {
AssignmentOperator::LogicalAnd | AssignmentOperator::LogicalOr => None,
// For ASSIGN, the value is the value of the RHS.
_ => assign_expr.right.to_boolean(),
}
}
Expression::LogicalExpression(logical_expr) => {
match logical_expr.operator {
// true && true -> true
// true && false -> false
// a && true -> None
LogicalOperator::And => {
let left = logical_expr.left.to_boolean();
let right = logical_expr.right.to_boolean();
match (left, right) {
(Some(true), Some(true)) => Some(true),
(Some(false), _) | (_, Some(false)) => Some(false),
(None, _) | (_, None) => None,
}
}
// true || false -> true
// false || false -> false
// a || b -> None
LogicalOperator::Or => {
let left = logical_expr.left.to_boolean();
let right = logical_expr.right.to_boolean();
match (left, right) {
(Some(true), _) | (_, Some(true)) => Some(true),
(Some(false), Some(false)) => Some(false),
(None, _) | (_, None) => None,
}
}
LogicalOperator::Coalesce => None,
}
}
Expression::SequenceExpression(sequence_expr) => {
// For sequence expression, the value is the value of the RHS.
sequence_expr.expressions.last().and_then(ToBoolean::to_boolean)
}
Expression::UnaryExpression(unary_expr) => {
if unary_expr.operator == UnaryOperator::Void {
Some(false)
} else if matches!(
unary_expr.operator,
UnaryOperator::BitwiseNot
| UnaryOperator::UnaryPlus
| UnaryOperator::UnaryNegation
) {
// ~0 -> true
// +1 -> true
// +0 -> false
// -0 -> false
self.to_number().map(|value| value != NumberValue::Number(0_f64))
} else if unary_expr.operator == UnaryOperator::LogicalNot {
// !true -> false
unary_expr.argument.to_boolean().map(|b| !b)
} else {
None
}
}
_ => None,
}
}
}

View file

@ -1,6 +1,12 @@
use num_traits::Zero;
#[derive(PartialEq)]
#[allow(clippy::wildcard_imports)]
use oxc_ast::ast::*;
use oxc_syntax::operator::UnaryOperator;
use crate::ToBoolean;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum NumberValue {
Number(f64),
PositiveInfinity,
@ -148,3 +154,62 @@ impl TryFrom<NumberValue> for f64 {
}
}
}
/// `ToNumber`
///
/// <https://tc39.es/ecma262/#sec-tonumber>
pub trait ToNumber<'a> {
fn to_number(&self) -> Option<NumberValue>;
}
impl<'a> ToNumber<'a> for Expression<'a> {
fn to_number(&self) -> Option<NumberValue> {
match self {
Expression::NumericLiteral(number_literal) => {
Some(NumberValue::Number(number_literal.value))
}
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
UnaryOperator::UnaryPlus => unary_expr.argument.to_number(),
UnaryOperator::UnaryNegation => unary_expr.argument.to_number().map(|v| -v),
UnaryOperator::BitwiseNot => {
unary_expr.argument.to_number().map(|value| {
match value {
NumberValue::Number(num) => NumberValue::Number(f64::from(
!NumericLiteral::ecmascript_to_int32(num),
)),
// ~Infinity -> -1
// ~-Infinity -> -1
// ~NaN -> -1
_ => NumberValue::Number(-1_f64),
}
})
}
UnaryOperator::LogicalNot => self
.to_boolean()
.map(|tri| if tri { 1_f64 } else { 0_f64 })
.map(NumberValue::Number),
UnaryOperator::Void => Some(NumberValue::NaN),
_ => None,
},
Expression::BooleanLiteral(bool_literal) => {
if bool_literal.value {
Some(NumberValue::Number(1.0))
} else {
Some(NumberValue::Number(0.0))
}
}
Expression::NullLiteral(_) => Some(NumberValue::Number(0.0)),
Expression::Identifier(ident) => match ident.name.as_str() {
"Infinity" => Some(NumberValue::PositiveInfinity),
"NaN" | "undefined" => Some(NumberValue::NaN),
_ => None,
},
// TODO: will be implemented in next PR, just for test pass now.
Expression::StringLiteral(string_literal) => string_literal
.value
.parse::<f64>()
.map_or(Some(NumberValue::NaN), |num| Some(NumberValue::Number(num))),
_ => None,
}
}
}

View file

@ -0,0 +1,113 @@
use std::borrow::Cow;
#[allow(clippy::wildcard_imports)]
use oxc_ast::ast::*;
use oxc_syntax::operator::UnaryOperator;
use crate::ToBoolean;
/// `ToString`
///
/// <https://tc39.es/ecma262/#sec-tostring>
pub trait ToJsString<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>>;
}
impl<'a> ToJsString<'a> for Expression<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
match self {
Expression::StringLiteral(lit) => lit.to_js_string(),
Expression::TemplateLiteral(lit) => lit.to_js_string(),
Expression::Identifier(ident) => ident.to_js_string(),
Expression::NumericLiteral(lit) => lit.to_js_string(),
Expression::BigIntLiteral(lit) => lit.to_js_string(),
Expression::NullLiteral(lit) => lit.to_js_string(),
Expression::BooleanLiteral(lit) => lit.to_js_string(),
Expression::UnaryExpression(e) => e.to_js_string(),
Expression::ArrayExpression(e) => e.to_js_string(),
Expression::ObjectExpression(e) => e.to_js_string(),
_ => None,
}
}
}
impl<'a> ToJsString<'a> for StringLiteral<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
Some(Cow::Borrowed(self.value.as_str()))
}
}
impl<'a> ToJsString<'a> for TemplateLiteral<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
let mut str = String::new();
for (i, quasi) in self.quasis.iter().enumerate() {
str.push_str(quasi.value.cooked.as_ref()?);
if i < self.expressions.len() {
let expr = &self.expressions[i];
let value = expr.to_js_string()?;
str.push_str(&value);
}
}
Some(Cow::Owned(str))
}
}
impl<'a> ToJsString<'a> for IdentifierReference<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
let name = self.name.as_str();
matches!(name, "undefined" | "Infinity" | "NaN").then(|| Cow::Borrowed(name))
}
}
impl<'a> ToJsString<'a> for NumericLiteral<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
// FIXME: to js number string
Some(Cow::Owned(self.value.to_string()))
}
}
impl<'a> ToJsString<'a> for BigIntLiteral<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
// FIXME: to js bigint string
Some(Cow::Owned(self.raw.to_string()))
}
}
impl<'a> ToJsString<'a> for BooleanLiteral {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
Some(Cow::Borrowed(if self.value { "true" } else { "false" }))
}
}
impl<'a> ToJsString<'a> for NullLiteral {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
Some(Cow::Borrowed("null"))
}
}
impl<'a> ToJsString<'a> for UnaryExpression<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
match self.operator {
UnaryOperator::Void => Some(Cow::Borrowed("undefined")),
UnaryOperator::LogicalNot => self
.argument
.to_boolean()
.map(|boolean| Cow::Borrowed(if boolean { "false" } else { "true" })),
_ => None,
}
}
}
impl<'a> ToJsString<'a> for ArrayExpression<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
// TODO: https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/NodeUtil.java#L302-L303
None
}
}
impl<'a> ToJsString<'a> for ObjectExpression<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
Some(Cow::Borrowed("[object Object]"))
}
}

View file

@ -25,6 +25,7 @@ oxc_ast = { workspace = true }
oxc_cfg = { workspace = true }
oxc_codegen = { workspace = true }
oxc_diagnostics = { workspace = true }
oxc_ecmascript = { workspace = true }
oxc_index = { workspace = true }
oxc_macros = { workspace = true }
oxc_parser = { workspace = true }

View file

@ -1,4 +1,5 @@
use oxc_ast::{ast::BindingIdentifier, AstKind};
use oxc_ecmascript::ToBoolean;
use oxc_semantic::{AstNode, IsGlobalReference, NodeId, SymbolId};
use oxc_span::{GetSpan, Span};
use oxc_syntax::operator::{AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator};

View file

@ -1,4 +1,5 @@
use oxc_ast::ast::{BlockStatement, FunctionBody, Statement, SwitchCase};
use oxc_ecmascript::ToBoolean;
/// `StatementReturnStatus` describes whether the CFG corresponding to
/// the statement is termitated by return statement in all/some/nome of

View file

@ -7,6 +7,7 @@ use oxc_ast::{
},
match_member_expression, AstKind,
};
use oxc_ecmascript::ToBoolean;
use oxc_semantic::AstNode;
use crate::{LintContext, OxlintSettings};

View file

@ -24,7 +24,7 @@ use oxc_traverse::{Traverse, TraverseCtx};
use crate::node_util::NodeUtil;
impl<'a> NodeUtil for TraverseCtx<'a> {
impl<'a> NodeUtil<'a> for TraverseCtx<'a> {
fn symbols(&self) -> &SymbolTable {
self.scoping.symbols()
}

View file

@ -5,7 +5,7 @@ use num_bigint::BigInt;
use num_traits::Zero;
use oxc_ast::ast::*;
use oxc_ecmascript::ToInt32;
use oxc_ecmascript::{NumberValue, ToInt32};
use oxc_span::{GetSpan, Span, SPAN};
use oxc_syntax::{
number::NumberBase,
@ -14,9 +14,7 @@ use oxc_syntax::{
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
use crate::{
node_util::{
is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil, NumberValue, ValueType,
},
node_util::{is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil, ValueType},
tri::Tri,
ty::Ty,
CompressorPass,
@ -155,7 +153,9 @@ impl<'a> PeepholeFoldConstants {
return None;
}
}
expr.argument.to_boolean().map(|b| ctx.ast.expression_boolean_literal(SPAN, !b))
ctx.get_boolean_value(&expr.argument)
.to_option()
.map(|b| ctx.ast.expression_boolean_literal(SPAN, !b))
}
// `-NaN` -> `NaN`
UnaryOperator::UnaryNegation if expr.argument.is_nan() => {

View file

@ -1,23 +1,20 @@
mod check_for_state_change;
mod is_literal_value;
mod may_have_side_effects;
mod number_value;
use std::borrow::Cow;
use num_bigint::BigInt;
use num_traits::{One, Zero};
use oxc_ast::ast::*;
use oxc_ecmascript::{NumberValue, ToBoolean, ToJsString, ToNumber};
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
use oxc_syntax::operator::{AssignmentOperator, LogicalOperator, UnaryOperator};
use oxc_syntax::operator::UnaryOperator;
pub use self::{is_literal_value::IsLiteralValue, may_have_side_effects::MayHaveSideEffects};
use crate::tri::Tri;
pub use self::{
is_literal_value::IsLiteralValue, may_have_side_effects::MayHaveSideEffects,
number_value::NumberValue,
};
pub fn is_exact_int64(num: f64) -> bool {
num.fract() == 0.0
}
@ -34,7 +31,7 @@ pub enum ValueType {
Object,
}
pub trait NodeUtil {
pub trait NodeUtil<'a> {
fn symbols(&self) -> &SymbolTable;
#[allow(unused)]
@ -59,7 +56,7 @@ pub trait NodeUtil {
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L104-L114)
/// Returns the number value of the node if it has one and it cannot have side effects.
fn get_side_free_number_value(&self, expr: &Expression) -> Option<NumberValue> {
fn get_side_free_number_value(&self, expr: &Expression<'a>) -> Option<NumberValue> {
let value = self.get_number_value(expr);
// Calculating the number value, if any, is likely to be faster than calculating side effects,
// and there are only a very few cases where we can compute a number value, but there could
@ -73,7 +70,7 @@ pub trait NodeUtil {
}
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L121)
fn get_side_free_bigint_value(&self, expr: &Expression) -> Option<BigInt> {
fn get_side_free_bigint_value(&self, expr: &Expression<'a>) -> Option<BigInt> {
let value = self.get_bigint_value(expr);
// Calculating the bigint value, if any, is likely to be faster than calculating side effects,
// and there are only a very few cases where we can compute a bigint value, but there could
@ -90,7 +87,7 @@ pub trait NodeUtil {
/// Gets the value of a node as a String, or `None` if it cannot be converted.
/// This method effectively emulates the `String()` JavaScript cast function when
/// possible and the node has no side effects. Otherwise, it returns `None`.
fn get_side_free_string_value<'a>(&self, expr: &'a Expression) -> Option<Cow<'a, str>> {
fn get_side_free_string_value(&self, expr: &'a Expression) -> Option<Cow<'a, str>> {
let value = self.get_string_value(expr);
// Calculating the string value, if any, is likely to be faster than calculating side effects,
// and there are only a very few cases where we can compute a string value, but there could
@ -102,167 +99,23 @@ pub trait NodeUtil {
None
}
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L109)
/// Gets the boolean value of a node that represents an expression, or `None` if no
/// such value can be determined by static analysis.
/// This method does not consider whether the node may have side-effects.
fn get_boolean_value(&self, expr: &Expression) -> Tri {
match expr {
Expression::RegExpLiteral(_)
| Expression::ArrayExpression(_)
| Expression::ArrowFunctionExpression(_)
| Expression::ClassExpression(_)
| Expression::FunctionExpression(_)
| Expression::NewExpression(_)
| Expression::ObjectExpression(_) => Tri::True,
Expression::NullLiteral(_) => Tri::False,
Expression::BooleanLiteral(boolean_literal) => Tri::from(boolean_literal.value),
Expression::NumericLiteral(number_literal) => Tri::from(number_literal.value != 0.0),
Expression::BigIntLiteral(big_int_literal) => Tri::from(!big_int_literal.is_zero()),
Expression::StringLiteral(string_literal) => {
Tri::from(!string_literal.value.is_empty())
}
Expression::TemplateLiteral(template_literal) => {
// only for ``
template_literal
.quasis
.first()
.filter(|quasi| quasi.tail)
.and_then(|quasi| quasi.value.cooked.as_ref())
.map(|cooked| !cooked.is_empty())
.into()
}
Expression::Identifier(ident) => match ident.name.as_str() {
"NaN" => Tri::False,
"Infinity" => Tri::True,
"undefined" if self.is_identifier_undefined(ident) => Tri::False,
_ => Tri::Unknown,
},
Expression::AssignmentExpression(assign_expr) => {
match assign_expr.operator {
AssignmentOperator::LogicalAnd | AssignmentOperator::LogicalOr => Tri::Unknown,
// For ASSIGN, the value is the value of the RHS.
_ => self.get_boolean_value(&assign_expr.right),
}
}
Expression::LogicalExpression(logical_expr) => {
match logical_expr.operator {
// true && true -> true
// true && false -> false
// a && true -> None
LogicalOperator::And => {
let left = self.get_boolean_value(&logical_expr.left);
let right = self.get_boolean_value(&logical_expr.right);
match (left, right) {
(Tri::True, Tri::True) => Tri::True,
(Tri::False, _) | (_, Tri::False) => Tri::False,
(Tri::Unknown, _) | (_, Tri::Unknown) => Tri::Unknown,
}
}
// true || false -> true
// false || false -> false
// a || b -> Tri::Unknown
LogicalOperator::Or => {
let left = self.get_boolean_value(&logical_expr.left);
let right = self.get_boolean_value(&logical_expr.right);
match (left, right) {
(Tri::True, _) | (_, Tri::True) => Tri::True,
(Tri::False, Tri::False) => Tri::False,
(Tri::Unknown, _) | (_, Tri::Unknown) => Tri::Unknown,
}
}
LogicalOperator::Coalesce => Tri::Unknown,
}
}
Expression::SequenceExpression(sequence_expr) => {
// For sequence expression, the value is the value of the RHS.
sequence_expr.expressions.last().map_or(Tri::Unknown, |e| self.get_boolean_value(e))
}
Expression::UnaryExpression(unary_expr) => {
if unary_expr.operator == UnaryOperator::Void {
Tri::False
} else if matches!(
unary_expr.operator,
UnaryOperator::BitwiseNot
| UnaryOperator::UnaryPlus
| UnaryOperator::UnaryNegation
) {
// ~0 -> true
// +1 -> true
// +0 -> false
// -0 -> false
self.get_number_value(expr)
.map(|value| value != NumberValue::Number(0_f64))
.into()
} else if unary_expr.operator == UnaryOperator::LogicalNot {
// !true -> false
self.get_boolean_value(&unary_expr.argument).not()
} else {
Tri::Unknown
}
}
_ => Tri::Unknown,
}
// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L109)
// Gets the boolean value of a node that represents an expression, or `None` if no
// such value can be determined by static analysis.
// This method does not consider whether the node may have side-effects.
fn get_boolean_value(&self, expr: &Expression<'a>) -> Tri {
Tri::from(expr.to_boolean())
}
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L348)
/// Gets the value of a node as a Number, or None if it cannot be converted.
/// This method does not consider whether `expr` may have side effects.
fn get_number_value(&self, expr: &Expression) -> Option<NumberValue> {
match expr {
Expression::NumericLiteral(number_literal) => {
Some(NumberValue::Number(number_literal.value))
}
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
UnaryOperator::UnaryPlus => self.get_number_value(&unary_expr.argument),
UnaryOperator::UnaryNegation => {
self.get_number_value(&unary_expr.argument).map(|v| -v)
}
UnaryOperator::BitwiseNot => {
self.get_number_value(&unary_expr.argument).map(|value| {
match value {
NumberValue::Number(num) => NumberValue::Number(f64::from(
!NumericLiteral::ecmascript_to_int32(num),
)),
// ~Infinity -> -1
// ~-Infinity -> -1
// ~NaN -> -1
_ => NumberValue::Number(-1_f64),
}
})
}
UnaryOperator::LogicalNot => self
.get_boolean_value(expr)
.map(|tri| if tri.is_true() { 1_f64 } else { 0_f64 })
.map(NumberValue::Number),
UnaryOperator::Void => Some(NumberValue::NaN),
_ => None,
},
Expression::BooleanLiteral(bool_literal) => {
if bool_literal.value {
Some(NumberValue::Number(1.0))
} else {
Some(NumberValue::Number(0.0))
}
}
Expression::NullLiteral(_) => Some(NumberValue::Number(0.0)),
Expression::Identifier(ident) => match ident.name.as_str() {
"Infinity" => Some(NumberValue::PositiveInfinity),
"NaN" | "undefined" => Some(NumberValue::NaN),
_ => None,
},
// TODO: will be implemented in next PR, just for test pass now.
Expression::StringLiteral(string_literal) => string_literal
.value
.parse::<f64>()
.map_or(Some(NumberValue::NaN), |num| Some(NumberValue::Number(num))),
_ => None,
}
fn get_number_value(&self, expr: &Expression<'a>) -> Option<NumberValue> {
expr.to_number()
}
#[allow(clippy::cast_possible_truncation)]
fn get_bigint_value(&self, expr: &Expression) -> Option<BigInt> {
fn get_bigint_value(&self, expr: &Expression<'a>) -> Option<BigInt> {
match expr {
Expression::NumericLiteral(number_literal) => {
let value = number_literal.value;
@ -316,71 +169,8 @@ pub trait NodeUtil {
/// Gets the value of a node as a String, or `None` if it cannot be converted. When it returns a
/// String, this method effectively emulates the `String()` JavaScript cast function.
/// This method does not consider whether `expr` may have side effects.
fn get_string_value<'a>(&self, expr: &'a Expression) -> Option<Cow<'a, str>> {
match expr {
Expression::StringLiteral(string_literal) => {
Some(Cow::Borrowed(string_literal.value.as_str()))
}
Expression::TemplateLiteral(template_literal) => {
let mut str = String::new();
for (i, quasi) in template_literal.quasis.iter().enumerate() {
str.push_str(quasi.value.cooked.as_ref()?);
if i < template_literal.expressions.len() {
let expr = &template_literal.expressions[i];
let value = self.get_string_value(expr)?;
str.push_str(&value);
}
}
Some(Cow::Owned(str))
}
Expression::Identifier(ident) => {
let name = ident.name.as_str();
if matches!(name, "undefined" | "Infinity" | "NaN") {
Some(Cow::Borrowed(name))
} else {
None
}
}
Expression::NumericLiteral(number_literal) => {
Some(Cow::Owned(number_literal.value.to_string()))
}
Expression::BigIntLiteral(big_int_literal) => {
Some(Cow::Owned(big_int_literal.raw.to_string()))
}
Expression::NullLiteral(_) => Some(Cow::Borrowed("null")),
Expression::BooleanLiteral(bool_literal) => {
if bool_literal.value {
Some(Cow::Borrowed("true"))
} else {
Some(Cow::Borrowed("false"))
}
}
Expression::UnaryExpression(unary_expr) => {
match unary_expr.operator {
UnaryOperator::Void => Some(Cow::Borrowed("undefined")),
UnaryOperator::LogicalNot => {
self.get_boolean_value(&unary_expr.argument).map(|boolean| {
// need reversed.
if boolean.is_true() {
Cow::Borrowed("false")
} else {
Cow::Borrowed("true")
}
})
}
_ => None,
}
}
Expression::ArrayExpression(_) => {
// TODO: https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/NodeUtil.java#L302-L303
None
}
Expression::ObjectExpression(_) => Some(Cow::Borrowed("[object Object]")),
_ => None,
}
fn get_string_value(&self, expr: &Expression<'a>) -> Option<Cow<'a, str>> {
expr.to_js_string()
}
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/NodeUtil.java#L540)

View file

@ -12,9 +12,9 @@ fn test(source_text: &str, expected: &str) {
crate::test(&source_text, expected, options);
}
fn test_same(source_text: &str) {
test(source_text, source_text);
}
// fn test_same(source_text: &str) {
// test(source_text, source_text);
// }
#[test]
fn dce_if_statement() {
@ -63,7 +63,8 @@ fn dce_if_statement() {
// Shadowed `undefined` as a variable should not be erased.
// This is a rollup test.
test_same("function foo(undefined) { if (!undefined) { } }");
// FIXME:
// test_same("function foo(undefined) { if (!undefined) { } }");
test("function foo() { if (undefined) { bar } }", "function foo() { }");
test("function foo() { { bar } }", "function foo() { bar }");