Skip to content

Commit

Permalink
Improved Prefixes in Formatted Objects
Browse files Browse the repository at this point in the history
Added Prefix to Formatted Arrays
Added ToStringTag and Null Prototype Support to Prefix
  • Loading branch information
Redfire75369 committed Jan 16, 2024
1 parent 403dd73 commit 74a68bb
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 68 deletions.
13 changes: 12 additions & 1 deletion ion/src/format/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ use std::fmt;
use std::fmt::{Display, Formatter, Write};

use colored::Colorize;
use mozjs::jsapi::JSProtoKey;

use crate::{Array, Context};
use crate::format::{indent_str, NEWLINE};
use crate::format::Config;
use crate::format::descriptor::format_descriptor;
use crate::format::object::write_remaining;
use crate::format::object::{write_prefix, write_remaining};

/// Formats an [JavaScript Array](Array) using the given [configuration](Config).
pub fn format_array<'cx>(cx: &'cx Context, cfg: Config, array: &'cx Array<'cx>) -> ArrayDisplay<'cx> {
Expand All @@ -30,6 +31,16 @@ pub struct ArrayDisplay<'cx> {
impl Display for ArrayDisplay<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let colour = self.cfg.colours.array;

write_prefix(
f,
self.cx,
self.cfg,
self.array.as_object(),
"Array",
JSProtoKey::JSProto_Array,
)?;

if self.cfg.depth < 5 {
let length = self.array.len(self.cx);

Expand Down
10 changes: 6 additions & 4 deletions ion/src/format/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ use crate::format::primitive::format_primitive;

/// Formats a boxed primitive ([Object]) using the given [configuration](Config).
/// The supported boxed types are `Boolean`, `Number`, `String` and `BigInt`.
pub fn format_boxed<'cx>(cx: &'cx Context, cfg: Config, object: &'cx Object<'cx>) -> BoxedDisplay<'cx> {
BoxedDisplay { cx, object, cfg }
pub fn format_boxed_primitive<'cx>(
cx: &'cx Context, cfg: Config, object: &'cx Object<'cx>,
) -> BoxedPrimitiveDisplay<'cx> {
BoxedPrimitiveDisplay { cx, object, cfg }
}

#[must_use]
pub struct BoxedDisplay<'cx> {
pub struct BoxedPrimitiveDisplay<'cx> {
cx: &'cx Context,
object: &'cx Object<'cx>,
cfg: Config,
}

impl Display for BoxedDisplay<'_> {
impl Display for BoxedPrimitiveDisplay<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Some(primitive) = self.object.unbox_primitive(self.cx) {
format_primitive(self.cx, self.cfg, &primitive).fmt(f)
Expand Down
38 changes: 0 additions & 38 deletions ion/src/format/class.rs

This file was deleted.

9 changes: 6 additions & 3 deletions ion/src/format/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

use std::fmt::{Display, Formatter};
use std::fmt::{Display, Formatter, Write};

use colored::Colorize;

Expand Down Expand Up @@ -37,22 +37,25 @@ impl Display for DescriptorDisplay<'_> {
if self.desc.setter(self.cx).is_some() {
"/Setter".color(color).fmt(f)?;
}

return if let Some(object) = self.object {
let value = match getter.call(self.cx, object, &[]) {
Ok(value) => value,
Err(report) => {
f.write_str("<inspection threw ")?;
f.write_str(" <Inspection threw (")?;
match report {
Some(mut report) => {
report.stack = None;
report.format(self.cx).fmt(f)?;
f.write_char(')')?;
}
None => f.write_str("unknown error")?,
}
f.write_str(">")?;
f.write_char('>')?;
return "]".color(color).fmt(f);
}
};

if value.handle().is_object() {
"] ".color(color).fmt(f)?;
format_object(
Expand Down
1 change: 0 additions & 1 deletion ion/src/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use crate::format::primitive::format_primitive;

pub mod array;
pub mod boxed;
pub mod class;
mod config;
pub mod date;
pub mod descriptor;
Expand Down
111 changes: 95 additions & 16 deletions ion/src/format/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ use std::fmt::{Display, Formatter, Write};

use colored::{Color, Colorize};
use itoa::Buffer;
use mozjs::jsapi::{ESClass, Type};
use mozjs::jsapi::{
ESClass, IdentifyStandardPrototype, JS_GetConstructor, JS_GetPrototype, JS_HasInstance, JSProtoKey, Type,
};

use crate::{Array, Context, Date, Exception, Function, Object, Promise, PropertyDescriptor, PropertyKey, RegExp};
use crate::conversions::ToValue;
use crate::format::{indent_str, NEWLINE};
use crate::format::array::format_array;
use crate::format::boxed::format_boxed;
use crate::format::class::format_class_object;
use crate::format::boxed::format_boxed_primitive;
use crate::format::Config;
use crate::format::date::format_date;
use crate::format::descriptor::format_descriptor;
Expand All @@ -26,6 +27,7 @@ use crate::format::key::format_key;
use crate::format::promise::format_promise;
use crate::format::regexp::format_regexp;
use crate::format::typedarray::{format_array_buffer, format_typed_array};
use crate::symbol::WellKnownSymbolCode;
use crate::typedarray::{
ArrayBuffer, ArrayBufferView, ClampedUint8Array, Float32Array, Float64Array, Int16Array, Int32Array, Int8Array,
Uint16Array, Uint32Array, Uint8Array,
Expand Down Expand Up @@ -55,7 +57,9 @@ impl Display for ObjectDisplay<'_> {
let class = self.object.get_builtin_class(cx);

match class {
ESC::Boolean | ESC::Number | ESC::String | ESC::BigInt => format_boxed(cx, cfg, &self.object).fmt(f),
ESC::Boolean | ESC::Number | ESC::String | ESC::BigInt => {
format_boxed_primitive(cx, cfg, &self.object).fmt(f)
}
ESC::Array => format_array(cx, cfg, &Array::from(cx, object.into_local()).unwrap()).fmt(f),
ESC::Date => format_date(cx, cfg, &Date::from(cx, object.into_local()).unwrap()).fmt(f),
ESC::Promise => format_promise(cx, cfg, &Promise::from(object.into_local()).unwrap()).fmt(f),
Expand All @@ -66,7 +70,7 @@ impl Display for ObjectDisplay<'_> {
Exception::Error(error) => error.format().fmt(f),
_ => unreachable!("Expected Error"),
},
ESC::Object => format_plain_object(cx, cfg, &self.object).fmt(f),
ESC::Object => format_raw_object(cx, cfg, &self.object).fmt(f),
ESC::Other => {
if let Some(view) = ArrayBufferView::from(cx.root_object(object.handle().get())) {
'view: {
Expand Down Expand Up @@ -101,30 +105,31 @@ impl Display for ObjectDisplay<'_> {
}
}

format_class_object(cx, cfg, &self.object).fmt(f)
format_raw_object(cx, cfg, &self.object).fmt(f)
}
_ => self.object.as_value(cx).to_source(cx).to_owned(cx).fmt(f),
}
}
}

/// Formats a [JavaScript Object](Object) using the given [configuration](Config).
/// Disregards the class of the object.
pub fn format_plain_object<'cx>(cx: &'cx Context, cfg: Config, object: &'cx Object<'cx>) -> PlainObjectDisplay<'cx> {
PlainObjectDisplay { cx, object, cfg }
/// Formats an [object](Object) using the given [configuration](Config).
pub fn format_raw_object<'cx>(cx: &'cx Context, cfg: Config, object: &'cx Object<'cx>) -> RawObjectDisplay<'cx> {
RawObjectDisplay { cx, object, cfg }
}

#[must_use]
pub struct PlainObjectDisplay<'cx> {
pub struct RawObjectDisplay<'cx> {
cx: &'cx Context,
object: &'cx Object<'cx>,
cfg: Config,
}

impl Display for PlainObjectDisplay<'_> {
impl Display for RawObjectDisplay<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let colour = self.cfg.colours.object;

write_prefix(f, self.cx, self.cfg, self.object, "Object", JSProtoKey::JSProto_Object)?;

if self.cfg.depth < 4 {
let keys = self.object.keys(self.cx, Some(self.cfg.iteration));
let length = keys.len();
Expand All @@ -141,7 +146,7 @@ impl Display for PlainObjectDisplay<'_> {
for key in keys {
inner.fmt(f)?;
let desc = self.object.get_descriptor(self.cx, &key).unwrap();
write_key_descriptor(f, self.cx, self.cfg, &key, &desc, self.object)?;
write_key_descriptor(f, self.cx, self.cfg, &key, &desc, Some(self.object))?;
",".color(colour).fmt(f)?;
f.write_str(NEWLINE)?;
}
Expand All @@ -153,7 +158,7 @@ impl Display for PlainObjectDisplay<'_> {

for (i, key) in keys.enumerate() {
let desc = self.object.get_descriptor(self.cx, &key).unwrap();
write_key_descriptor(f, self.cx, self.cfg, &key, &desc, self.object)?;
write_key_descriptor(f, self.cx, self.cfg, &key, &desc, Some(self.object))?;

if i != len - 1 {
",".color(colour).fmt(f)?;
Expand All @@ -173,12 +178,86 @@ impl Display for PlainObjectDisplay<'_> {
}
}

pub(crate) fn write_prefix(
f: &mut Formatter, cx: &Context, cfg: Config, object: &Object, fallback: &str, standard: JSProtoKey,
) -> fmt::Result {
fn get_constructor_name(cx: &Context, object: &Object, proto: &mut Object) -> Option<String> {
let value = object.as_value(cx);
let constructor = unsafe {
JS_GetPrototype(cx.as_ptr(), object.handle().into(), proto.handle_mut().into());
if proto.handle().get().is_null() {
return None;
} else {
cx.root_object(JS_GetConstructor(cx.as_ptr(), proto.handle().into()))
}
};

Function::from_object(cx, &constructor)
.and_then(|constructor_fn| constructor_fn.name(cx))
.and_then(|name| {
let mut has_instance = false;
(unsafe {
JS_HasInstance(
cx.as_ptr(),
constructor.handle().into(),
value.handle().into(),
&mut has_instance,
)
} && has_instance)
.then_some(name)
})
}

fn get_tag(cx: &Context, object: &Object) -> Option<String> {
if object.has_own(cx, WellKnownSymbolCode::ToStringTag) {
if let Some(tag) = object.get_as::<_, String>(cx, WellKnownSymbolCode::ToStringTag, true, ()) {
return (!tag.is_empty()).then_some(tag);
}
}
None
}

fn write_tag(f: &mut Formatter, colour: Color, tag: Option<&str>, fallback: &str) -> fmt::Result {
if let Some(tag) = tag {
if tag != fallback {
"[".color(colour).fmt(f)?;
tag.color(colour).fmt(f)?;
"] ".color(colour).fmt(f)?;
}
}
Ok(())
}

let mut proto = Object::null(cx);
let constructor_name = get_constructor_name(cx, object, &mut proto);
let tag = get_tag(cx, object);

let colour = cfg.colours.object;
let mut fallback = fallback;
if let Some(name) = &constructor_name {
let proto = unsafe { IdentifyStandardPrototype(proto.handle().get()) };
if proto != standard {
name.color(colour).fmt(f)?;
f.write_char(' ')?;
fallback = &name;
} else if tag.is_some() {
fallback.color(colour).fmt(f)?;
f.write_char(' ')?;
}
} else {
"[".color(colour).fmt(f)?;
fallback.color(colour).fmt(f)?;
": null prototype] ".color(colour).fmt(f)?;
}
write_tag(f, colour, tag.as_deref(), fallback)
}

fn write_key_descriptor(
f: &mut Formatter, cx: &Context, cfg: Config, key: &PropertyKey, desc: &PropertyDescriptor, object: &Object,
f: &mut Formatter, cx: &Context, cfg: Config, key: &PropertyKey, desc: &PropertyDescriptor, object: Option<&Object>,
) -> fmt::Result {
format_key(cx, cfg, &key.to_owned_key(cx)).fmt(f)?;
": ".color(cfg.colours.object).fmt(f)?;
format_descriptor(cx, cfg, desc, Some(object)).fmt(f)
format_descriptor(cx, cfg, desc, object).fmt(f)
}

pub(crate) fn write_remaining(f: &mut Formatter, remaining: usize, inner: Option<&str>, colour: Color) -> fmt::Result {
Expand Down
2 changes: 1 addition & 1 deletion ion/src/format/typedarray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl Display for ArrayBufferDisplay<'_> {
"[Uint8Contents]".color(self.cfg.colours.symbol),
":".color(colour)
)?;
f.write_str("<")?;
f.write_char('<')?;

for (i, byte) in bytes.iter().enumerate() {
write!(f, "{:02x}", byte)?;
Expand Down
8 changes: 4 additions & 4 deletions ion/src/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

use std::{fmt, ptr};
use std::fmt::{Display, Formatter};
use std::fmt::{Display, Formatter, Write};
use std::mem::MaybeUninit;

use mozjs::conversions::jsstr_to_string;
Expand Down Expand Up @@ -68,11 +68,11 @@ impl StackRecord {
impl Display for StackRecord {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(self.function.as_deref().unwrap_or(""))?;
f.write_str("@")?;
f.write_char('@')?;
f.write_str(&self.location.file)?;
f.write_str(":")?;
f.write_char(':')?;
f.write_str(&self.location.lineno.to_string())?;
f.write_str(":")?;
f.write_char(':')?;
f.write_str(&self.location.column.to_string())?;
Ok(())
}
Expand Down

0 comments on commit 74a68bb

Please sign in to comment.