Skip to content

Commit

Permalink
Set more strict types to parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
NorbertGarfield committed May 8, 2022
1 parent 648d2e4 commit 75e994b
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 206 deletions.
80 changes: 35 additions & 45 deletions boa_engine/src/builtins/intl/date_time_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,52 +134,50 @@ pub(crate) fn to_date_time_options(
) -> JsResult<JsObject> {
// 1. If options is undefined, let options be null;
// otherwise let options be ? ToObject(options).
let maybe_options = if options.is_undefined() {
Ok(JsObject::empty())
// 2. Let options be ! OrdinaryObjectCreate(options).
let options = if options.is_undefined() {
JsObject::from_proto_and_data(None, ObjectData::ordinary())
} else {
options.to_object(context)
let opt = options.to_object(context)?;
JsObject::from_proto_and_data(opt, ObjectData::ordinary())
};
let options = maybe_options.unwrap_or_else(|_| JsObject::empty());

// 2. Let options be ! OrdinaryObjectCreate(options).
let options = JsObject::from_proto_and_data(options, ObjectData::ordinary());

// 3. Let needDefaults be true.
let mut need_defaults = true;

// 4. If required is "date" or "any", then
if required.eq("date") || required.eq("any") {
if ["date", "any"].contains(&required) {
// a. For each property name prop of « "weekday", "year", "month", "day" », do
let property_names = vec!["weekday", "year", "month", "day"];
// i. Let value be ? Get(options, prop).
// ii. If value is not undefined, let needDefaults be false.
need_defaults = property_names.iter().all(|prop_name| {
options
.get(*prop_name, context)
.unwrap_or_else(|_| JsValue::undefined())
.is_undefined()
});
for property in ["weekday", "year", "month", "day"] {
// i. Let value be ? Get(options, prop).
let value = options.get(property, context)?;

// ii. If value is not undefined, let needDefaults be false.
if !value.is_undefined() {
need_defaults = false;
}
}
}

// 5. If required is "time" or "any", then
if required.eq("time") || required.eq("any") {
if ["time", "any"].contains(&required) {
// a. For each property name prop of « "dayPeriod", "hour", "minute", "second",
// "fractionalSecondDigits" », do
let property_names = vec![
for property in [
"dayPeriod",
"hour",
"minute",
"second",
"fractionalSecondDigits",
];
// i. Let value be ? Get(options, prop).
// ii. If value is not undefined, let needDefaults be false.
need_defaults = property_names.iter().all(|prop_name| {
options
.get(*prop_name, context)
.unwrap_or_else(|_| JsValue::undefined())
.is_undefined()
});
] {
// i. Let value be ? Get(options, prop).
let value = options.get(property, context)?;

// ii. If value is not undefined, let needDefaults be false.
if !value.is_undefined() {
need_defaults = false;
}
}
}

// 6. Let dateStyle be ? Get(options, "dateStyle").
Expand All @@ -188,9 +186,7 @@ pub(crate) fn to_date_time_options(
.unwrap_or_else(|_| JsValue::undefined());

// 7. Let timeStyle be ? Get(options, "timeStyle").
let time_style = options
.get("timeStyle", context)
.unwrap_or_else(|_| JsValue::undefined());
let time_style = options.get("timeStyle", context)?;

// 8. If dateStyle is not undefined or timeStyle is not undefined, let needDefaults be false.
if !date_style.is_undefined() || !time_style.is_undefined() {
Expand All @@ -210,26 +206,20 @@ pub(crate) fn to_date_time_options(
}

// 11. If needDefaults is true and defaults is either "date" or "all", then
if need_defaults && (defaults.eq("date") || defaults.eq("all")) {
if need_defaults && ["date", "all"].contains(&defaults) {
// a. For each property name prop of « "year", "month", "day" », do
let property_names = vec!["year", "month", "day"];
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
for prop_name in property_names {
options
.create_data_property_or_throw(prop_name, "numeric", context)
.expect("CreateDataPropertyOrThrow must not fail");
for property in ["year", "month", "day"] {
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
options.create_data_property_or_throw(property, "numeric", context)?;
}
}

// 12. If needDefaults is true and defaults is either "time" or "all", then
if need_defaults && (defaults.eq("time") || defaults.eq("all")) {
if need_defaults && ["time", "all"].contains(&defaults) {
// a. For each property name prop of « "hour", "minute", "second" », do
let property_names = vec!["hour", "minute", "second"];
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
for prop_name in property_names {
options
.create_data_property_or_throw(prop_name, "numeric", context)
.expect("CreateDataPropertyOrThrow must not fail");
for property in ["hour", "minute", "second"] {
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
options.create_data_property_or_throw(property, "numeric", context)?;
}
}

Expand Down
100 changes: 40 additions & 60 deletions boa_engine/src/builtins/intl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ use crate::{
Context, JsResult, JsString, JsValue,
};

#[cfg(test)]
use crate::object::JsObject;

pub mod date_time_format;
#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -654,6 +657,12 @@ fn resolve_locale(
result
}

#[cfg(test)]
pub(crate) enum GetOptionType {
String,
Boolean,
}

/// The abstract operation `GetOption` extracts the value of the property named `property` from the
/// provided `options` object, converts it to the required `type`, checks whether it is one of a
/// `List` of allowed `values`, and fills in a `fallback` value if necessary. If `values` is
Expand All @@ -665,58 +674,39 @@ fn resolve_locale(
/// [spec]: https://tc39.es/ecma402/#sec-getoption
#[cfg(test)]
pub(crate) fn get_option(
options: &JsValue,
options: &JsObject,
property: &str,
r#type: &str,
values: &[JsValue],
r#type: &GetOptionType,
values: &[JsString],
fallback: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Assert: Type(options) is Object.
if !options.is_object() {
return context.throw_type_error("GetOption: options should be an Object");
}

let options_obj = options
.to_object(context)
.expect("GetOption: options should be an Object");

// 2. Let value be ? Get(options, property).
let mut value = options_obj
.get(property, context)
.unwrap_or_else(|_| JsValue::undefined());
let mut value = options.get(property, context)?;

// 3. If value is undefined, return fallback.
if value.is_undefined() {
return Ok(fallback.clone());
}

// 4. Assert: type is "boolean" or "string".
if r#type.ne("boolean") && r#type.ne("string") {
return context.throw_type_error("GetOption: type should be either 'boolean' or 'string'");
}

// 5. If type is "boolean", then
if r#type.eq("boolean") {
// a. Set value to ! ToBoolean(value).
value = JsValue::Boolean(value.to_boolean());
}

// a. Set value to ! ToBoolean(value).
// 6. If type is "string", then
if r#type.eq("string") {
// a. Set value to ? ToString(value).
value = JsValue::String(
value
.to_string(context)
.expect("GetOption: failed to convert value to string"),
);
}

// a. Set value to ? ToString(value).
// 7. If values is not undefined and values does not contain an element equal to value,
// throw a RangeError exception.
if !values.is_empty() && !values.contains(&value) {
return context.throw_range_error("GetOption: values array does not contain value");
}
value = match r#type {
GetOptionType::Boolean => JsValue::Boolean(value.to_boolean()),
GetOptionType::String => {
let string_value = value.to_string(context)?;
if !values.is_empty() && !values.contains(&string_value) {
return context.throw_range_error("GetOption: values array does not contain value");
}
JsValue::String(string_value)
}
};

// 8. Return value.
Ok(value)
Expand All @@ -732,27 +722,16 @@ pub(crate) fn get_option(
/// [spec]: https://tc39.es/ecma402/#sec-getnumberoption
#[cfg(test)]
pub(crate) fn get_number_option(
options: &JsValue,
options: &JsObject,
property: &str,
minimum: &JsValue,
maximum: &JsValue,
fallback: &JsValue,
minimum: f64,
maximum: f64,
fallback: Option<f64>,
context: &mut Context,
) -> JsResult<JsValue> {
// FIXME untested
) -> JsResult<f64> {
// 1. Assert: Type(options) is Object.
if !options.is_object() {
return context.throw_type_error("GetOption: options should be an Object");
}

let options_obj = options
.to_object(context)
.expect("GetOption: options should be an Object");

// 2. Let value be ? Get(options, property).
let value = options_obj
.get(property, context)
.unwrap_or_else(|_| JsValue::undefined());
let value = options.get(property, context)?;

// 3. Return ? DefaultNumberOption(value, minimum, maximum, fallback).
default_number_option(&value, minimum, maximum, fallback, context)
Expand All @@ -768,26 +747,27 @@ pub(crate) fn get_number_option(
#[cfg(test)]
pub(crate) fn default_number_option(
value: &JsValue,
minimum: &JsValue,
maximum: &JsValue,
fallback: &JsValue,
minimum: f64,
maximum: f64,
fallback: Option<f64>,
context: &mut Context,
) -> JsResult<JsValue> {
) -> JsResult<f64> {
// 1. If value is undefined, return fallback.
if value.is_undefined() {
return Ok(fallback.clone());
match fallback {
Some(val_f64) => return Ok(val_f64),
None => return context.throw_type_error("DefaultNumberOption: no fallback provided"),
};
}

// 2. Set value to ? ToNumber(value).
let value = value.to_number(context)?;
let minimum = minimum.to_number(context).unwrap_or(f64::NEG_INFINITY);
let maximum = maximum.to_number(context).unwrap_or(f64::INFINITY);

// 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
if value.is_nan() || value.lt(&minimum) || value.gt(&maximum) {
return context.throw_range_error("DefaultNumberOption: value is out of range.");
}

// 4. Return floor(value).
Ok(JsValue::new(value.floor()))
Ok(value.floor())
}
Loading

0 comments on commit 75e994b

Please sign in to comment.