Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 0 additions & 17 deletions crates/oxc_ast/src/ast_impl/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_ecmascript/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
18 changes: 14 additions & 4 deletions crates/oxc_ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
107 changes: 107 additions & 0 deletions crates/oxc_ecmascript/src/to_boolean.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
}
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
}
}
}
113 changes: 113 additions & 0 deletions crates/oxc_ecmascript/src/to_string.rs
Original file line number Diff line number Diff line change
@@ -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]"))
}
}
1 change: 1 addition & 0 deletions crates/oxc_linter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_linter/src/ast_util.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading