Skip to content

Commit

Permalink
Add tests from WPT and fix them in the Console (#3979)
Browse files Browse the repository at this point in the history
  • Loading branch information
hansl committed Sep 11, 2024
1 parent 50356e9 commit dd32789
Show file tree
Hide file tree
Showing 3 changed files with 408 additions and 112 deletions.
33 changes: 25 additions & 8 deletions core/engine/src/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ use boa_profiler::Profiler;
#[doc(inline)]
pub use conversions::convert::Convert;

use crate::object::{JsFunction, JsPromise};
pub(crate) use self::conversions::IntoOrUndefined;
#[doc(inline)]
pub use self::{
conversions::try_from_js::TryFromJs, display::ValueDisplay, integer::IntegerOrInfinity,
operations::*, r#type::Type,
};
use crate::builtins::RegExp;
use crate::object::{JsFunction, JsPromise, JsRegExp};
use crate::{
builtins::{
number::{f64_to_int32, f64_to_uint32},
Expand All @@ -35,13 +42,6 @@ use crate::{
Context, JsBigInt, JsResult, JsString,
};

pub(crate) use self::conversions::IntoOrUndefined;
#[doc(inline)]
pub use self::{
conversions::try_from_js::TryFromJs, display::ValueDisplay, integer::IntegerOrInfinity,
operations::*, r#type::Type,
};

mod conversions;
pub(crate) mod display;
mod equality;
Expand Down Expand Up @@ -221,6 +221,23 @@ impl JsValue {
.and_then(|o| JsPromise::from_object(o).ok())
}

/// Returns true if the value is a regular expression object.
#[inline]
#[must_use]
pub fn is_regexp(&self) -> bool {
matches!(self, Self::Object(obj) if obj.is::<RegExp>())
}

/// Returns the value as a regular expression if the value is a regexp, otherwise `None`.
#[inline]
#[must_use]
pub fn as_regexp(&self) -> Option<JsRegExp> {
self.as_object()
.filter(|obj| obj.is::<RegExp>())
.cloned()
.and_then(|o| JsRegExp::from_object(o).ok())
}

/// Returns true if the value is a symbol.
#[inline]
#[must_use]
Expand Down
258 changes: 155 additions & 103 deletions core/runtime/src/console/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
#[cfg(test)]
mod tests;

use boa_engine::property::Attribute;
use boa_engine::{
js_str, js_string,
native_function::NativeFunction,
object::{JsObject, ObjectInitializer},
value::{JsValue, Numeric},
Context, JsArgs, JsData, JsError, JsResult, JsStr, JsString,
Context, JsArgs, JsData, JsError, JsResult, JsStr, JsString, JsSymbol,
};
use boa_gc::{Finalize, Trace};
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -86,9 +87,19 @@ impl Logger for DefaultLogger {

/// This represents the `console` formatter.
fn formatter(data: &[JsValue], context: &mut Context) -> JsResult<String> {
fn to_string(value: &JsValue, context: &mut Context) -> JsResult<String> {
// There is a slight difference between the standard [`JsValue::to_string`] and
// the way Console actually logs, w.r.t Symbols.
if let Some(s) = value.as_symbol() {
Ok(s.to_string())
} else {
Ok(value.to_string(context)?.to_std_string_escaped())
}
}

match data {
[] => Ok(String::new()),
[val] => Ok(val.to_string(context)?.to_std_string_escaped()),
[val] => to_string(val, context),
data => {
let mut formatted = String::new();
let mut arg_index = 1;
Expand Down Expand Up @@ -124,11 +135,27 @@ fn formatter(data: &[JsValue], context: &mut Context) -> JsResult<String> {
}
/* string */
's' => {
let arg = data
.get_or_undefined(arg_index)
.to_string(context)?
.to_std_string_escaped();
formatted.push_str(&arg);
let arg = data.get_or_undefined(arg_index);

// If a JS value implements `toString()`, call it.
let mut written = false;
if let Some(obj) = arg.as_object() {
if let Ok(to_string) = obj.get(js_str!("toString"), context) {
if let Some(to_string_fn) = to_string.as_function() {
let arg = to_string_fn
.call(arg, &[], context)?
.to_string(context)?;
formatted.push_str(&arg.to_std_string_escaped());
written = true;
}
}
}

if !written {
let arg = arg.to_string(context)?.to_std_string_escaped();
formatted.push_str(&arg);
}

arg_index += 1;
}
'%' => formatted.push('%'),
Expand All @@ -145,10 +172,8 @@ fn formatter(data: &[JsValue], context: &mut Context) -> JsResult<String> {

/* unformatted data */
for rest in data.iter().skip(arg_index) {
formatted.push_str(&format!(
" {}",
rest.to_string(context)?.to_std_string_escaped()
));
formatted.push(' ');
formatted.push_str(&to_string(rest, context)?);
}

Ok(formatted)
Expand All @@ -175,6 +200,24 @@ impl Console {
/// Name of the built-in `console` property.
pub const NAME: JsStr<'static> = js_str!("console");

/// Modify the context to include the `console` object.
///
/// # Errors
/// This function will return an error if the property cannot be defined on the global object.
pub fn register_with_logger<L>(context: &mut Context, logger: L) -> JsResult<()>
where
L: Logger + 'static,
{
let console = Self::init_with_logger(context, logger);
context.register_global_property(
Self::NAME,
console,
Attribute::WRITABLE | Attribute::CONFIGURABLE,
)?;

Ok(())
}

/// Initializes the `console` with a special logger.
#[allow(clippy::too_many_lines)]
pub fn init_with_logger<L>(context: &mut Context, logger: L) -> JsObject
Expand Down Expand Up @@ -210,98 +253,107 @@ impl Console {
let state = Rc::new(RefCell::new(Self::default()));
let logger = Rc::new(logger);

ObjectInitializer::with_native_data(Self::default(), context)
.function(
console_method(Self::assert, state.clone(), logger.clone()),
js_string!("assert"),
0,
)
.function(
console_method_mut(Self::clear, state.clone(), logger.clone()),
js_string!("clear"),
0,
)
.function(
console_method(Self::debug, state.clone(), logger.clone()),
js_string!("debug"),
0,
)
.function(
console_method(Self::error, state.clone(), logger.clone()),
js_string!("error"),
0,
)
.function(
console_method(Self::info, state.clone(), logger.clone()),
js_string!("info"),
0,
)
.function(
console_method(Self::log, state.clone(), logger.clone()),
js_string!("log"),
0,
)
.function(
console_method(Self::trace, state.clone(), logger.clone()),
js_string!("trace"),
0,
)
.function(
console_method(Self::warn, state.clone(), logger.clone()),
js_string!("warn"),
0,
)
.function(
console_method_mut(Self::count, state.clone(), logger.clone()),
js_string!("count"),
0,
)
.function(
console_method_mut(Self::count_reset, state.clone(), logger.clone()),
js_string!("countReset"),
0,
)
.function(
console_method_mut(Self::group, state.clone(), logger.clone()),
js_string!("group"),
0,
)
.function(
console_method_mut(Self::group_collapsed, state.clone(), logger.clone()),
js_string!("groupCollapsed"),
0,
)
.function(
console_method_mut(Self::group_end, state.clone(), logger.clone()),
js_string!("groupEnd"),
0,
)
.function(
console_method_mut(Self::time, state.clone(), logger.clone()),
js_string!("time"),
0,
)
.function(
console_method(Self::time_log, state.clone(), logger.clone()),
js_string!("timeLog"),
0,
)
.function(
console_method_mut(Self::time_end, state.clone(), logger.clone()),
js_string!("timeEnd"),
0,
)
.function(
console_method(Self::dir, state.clone(), logger.clone()),
js_string!("dir"),
0,
)
.function(
console_method(Self::dir, state, logger.clone()),
js_string!("dirxml"),
0,
)
.build()
ObjectInitializer::with_native_data_and_proto(
Self::default(),
JsObject::with_object_proto(context.realm().intrinsics()),
context,
)
.property(
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::CONFIGURABLE,
)
.function(
console_method(Self::assert, state.clone(), logger.clone()),
js_string!("assert"),
0,
)
.function(
console_method_mut(Self::clear, state.clone(), logger.clone()),
js_string!("clear"),
0,
)
.function(
console_method(Self::debug, state.clone(), logger.clone()),
js_string!("debug"),
0,
)
.function(
console_method(Self::error, state.clone(), logger.clone()),
js_string!("error"),
0,
)
.function(
console_method(Self::info, state.clone(), logger.clone()),
js_string!("info"),
0,
)
.function(
console_method(Self::log, state.clone(), logger.clone()),
js_string!("log"),
0,
)
.function(
console_method(Self::trace, state.clone(), logger.clone()),
js_string!("trace"),
0,
)
.function(
console_method(Self::warn, state.clone(), logger.clone()),
js_string!("warn"),
0,
)
.function(
console_method_mut(Self::count, state.clone(), logger.clone()),
js_string!("count"),
0,
)
.function(
console_method_mut(Self::count_reset, state.clone(), logger.clone()),
js_string!("countReset"),
0,
)
.function(
console_method_mut(Self::group, state.clone(), logger.clone()),
js_string!("group"),
0,
)
.function(
console_method_mut(Self::group_collapsed, state.clone(), logger.clone()),
js_string!("groupCollapsed"),
0,
)
.function(
console_method_mut(Self::group_end, state.clone(), logger.clone()),
js_string!("groupEnd"),
0,
)
.function(
console_method_mut(Self::time, state.clone(), logger.clone()),
js_string!("time"),
0,
)
.function(
console_method(Self::time_log, state.clone(), logger.clone()),
js_string!("timeLog"),
0,
)
.function(
console_method_mut(Self::time_end, state.clone(), logger.clone()),
js_string!("timeEnd"),
0,
)
.function(
console_method(Self::dir, state.clone(), logger.clone()),
js_string!("dir"),
0,
)
.function(
console_method(Self::dir, state, logger.clone()),
js_string!("dirxml"),
0,
)
.build()
}

/// Initializes the `console` built-in object.
Expand Down
Loading

0 comments on commit dd32789

Please sign in to comment.