1use std::cmp::Ordering;
8use std::fmt;
9use std::fmt::{Display, Formatter, Write as _};
10
11use colored::{Color, Colorize as _};
12use itoa::Buffer;
13use mozjs::jsapi::{ESClass, JSProtoKey, Type};
14use mozjs::typedarray::{ClampedU8, Float32, Float64, Int8, Int16, Int32, Uint8, Uint16, Uint32};
15
16use crate::conversions::ToValue as _;
17use crate::format::array::format_array;
18use crate::format::boxed::format_boxed_primitive;
19use crate::format::date::format_date;
20use crate::format::descriptor::format_descriptor;
21use crate::format::function::format_function;
22use crate::format::key::format_key;
23use crate::format::promise::format_promise;
24use crate::format::regexp::format_regexp;
25use crate::format::string::format_string;
26use crate::format::typedarray::{format_array_buffer, format_typed_array};
27use crate::format::{Config, NEWLINE, indent_str, prefix};
28use crate::typedarray::{ArrayBuffer, ArrayBufferView, TypedArray, TypedArrayElement};
29use crate::{
30 Array, Context, Date, Exception, Function, Local, Object, Promise, PropertyDescriptor, PropertyKey, RegExp,
31};
32
33pub fn format_object<'cx>(cx: &'cx Context, cfg: Config, object: Object<'cx>) -> ObjectDisplay<'cx> {
36 ObjectDisplay { cx, object, cfg }
37}
38
39#[must_use]
40pub struct ObjectDisplay<'cx> {
41 cx: &'cx Context,
42 object: Object<'cx>,
43 cfg: Config,
44}
45
46impl Display for ObjectDisplay<'_> {
47 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
48 use ESClass as ESC;
49
50 let cx = self.cx;
51 let cfg = self.cfg;
52 let object = Object::from(Local::from_handle(self.object.handle()));
53
54 let class = self.object.get_builtin_class(cx);
55
56 match class {
57 ESC::Boolean | ESC::Number | ESC::String | ESC::BigInt => {
58 format_boxed_primitive(cx, cfg, &self.object).fmt(f)
59 }
60 ESC::Array => format_array(cx, cfg, &Array::from(cx, object.into_local()).unwrap()).fmt(f),
61 ESC::Date => format_date(cx, cfg, &Date::from(cx, object.into_local()).unwrap()).fmt(f),
62 ESC::Promise => format_promise(cx, cfg, &Promise::from(object.into_local()).unwrap()).fmt(f),
63 ESC::RegExp => format_regexp(cx, cfg, &RegExp::from(cx, object.into_local()).unwrap()).fmt(f),
64 ESC::Function => format_function(cx, cfg, &Function::from_object(cx, &self.object).unwrap()).fmt(f),
65 ESC::ArrayBuffer => format_array_buffer(cfg, &ArrayBuffer::from(object.into_local()).unwrap()).fmt(f),
66 ESC::Error => match Exception::from_object(cx, &self.object)? {
67 Exception::Error(error) => error.format().fmt(f),
68 _ => unreachable!("Expected Error"),
69 },
70 ESC::Object => format_raw_object(cx, cfg, &self.object).fmt(f),
71 ESC::Other => {
72 if let Some(view) = ArrayBufferView::from(cx.root(object.handle().get()))
73 && let Some(res) = format_array_buffer_view(f, view, cfg)
74 {
75 return res;
76 }
77
78 format_raw_object(cx, cfg, &self.object).fmt(f)
79 }
80 _ => format_string(cx, cfg, &self.object.as_value(cx).to_source(cx)).fmt(f),
81 }
82 }
83}
84
85fn format_array_buffer_view(f: &mut Formatter<'_>, view: ArrayBufferView, cfg: Config) -> Option<fmt::Result> {
86 fn view_into_array<T: TypedArrayElement>(view: ArrayBufferView) -> Option<TypedArray<T>> {
87 TypedArray::from(view.into_local())
88 }
89
90 let res = match view.view_type() {
91 Type::Int8 => format_typed_array(cfg, &view_into_array::<Int8>(view)?).fmt(f),
92 Type::Uint8 => format_typed_array(cfg, &view_into_array::<Uint8>(view)?).fmt(f),
93 Type::Int16 => format_typed_array(cfg, &view_into_array::<Int16>(view)?).fmt(f),
94 Type::Uint16 => format_typed_array(cfg, &view_into_array::<Uint16>(view)?).fmt(f),
95 Type::Int32 => format_typed_array(cfg, &view_into_array::<Int32>(view)?).fmt(f),
96 Type::Uint32 => format_typed_array(cfg, &view_into_array::<Uint32>(view)?).fmt(f),
97 Type::Float32 => format_typed_array(cfg, &view_into_array::<Float32>(view)?).fmt(f),
98 Type::Float64 => format_typed_array(cfg, &view_into_array::<Float64>(view)?).fmt(f),
99 Type::Uint8Clamped => format_typed_array(cfg, &view_into_array::<ClampedU8>(view)?).fmt(f),
100 _ => return None,
101 };
102 Some(res)
103}
104
105pub fn format_raw_object<'cx>(cx: &'cx Context, cfg: Config, object: &'cx Object<'cx>) -> RawObjectDisplay<'cx> {
107 RawObjectDisplay { cx, object, cfg }
108}
109
110#[must_use]
111pub struct RawObjectDisplay<'cx> {
112 cx: &'cx Context,
113 object: &'cx Object<'cx>,
114 cfg: Config,
115}
116
117impl Display for RawObjectDisplay<'_> {
118 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
119 let colour = self.cfg.colours.object;
120
121 prefix::write_prefix(f, self.cx, self.cfg, self.object, "Object", JSProtoKey::JSProto_Object)?;
122
123 if self.cfg.depth > 3 {
124 return "[Object]".color(colour).fmt(f);
125 }
126
127 let keys = self.object.keys(self.cx, Some(self.cfg.iteration));
128 let length = keys.len();
129 if length == 0 {
130 return "{}".color(colour).fmt(f);
131 }
132
133 "{".color(colour).fmt(f)?;
134 if self.cfg.multiline {
135 f.write_str(NEWLINE)?;
136 let inner = indent_str((self.cfg.indentation + self.cfg.depth + 1) as usize);
137
138 for key in keys {
139 inner.fmt(f)?;
140 let desc = self.object.get_descriptor(self.cx, &key)?.unwrap();
141 write_key_descriptor(f, self.cx, self.cfg, &key, &desc, Some(self.object))?;
142 ",".color(colour).fmt(f)?;
143 f.write_str(NEWLINE)?;
144 }
145
146 indent_str((self.cfg.indentation + self.cfg.depth) as usize).fmt(f)?;
147 } else {
148 f.write_char(' ')?;
149 let len = length.clamp(0, 3);
150
151 for (i, key) in keys.enumerate() {
152 let desc = self.object.get_descriptor(self.cx, &key)?.unwrap();
153 write_key_descriptor(f, self.cx, self.cfg, &key, &desc, Some(self.object))?;
154
155 if i != len - 1 {
156 ",".color(colour).fmt(f)?;
157 f.write_char(' ')?;
158 }
159 }
160
161 let remaining = length - len;
162 write_remaining(f, remaining, None, colour)?;
163 }
164
165 "}".color(colour).fmt(f)
166 }
167}
168
169fn write_key_descriptor(
170 f: &mut Formatter, cx: &Context, cfg: Config, key: &PropertyKey, desc: &PropertyDescriptor, object: Option<&Object>,
171) -> fmt::Result {
172 format_key(cx, cfg, &key.to_owned_key(cx)?).fmt(f)?;
173 ": ".color(cfg.colours.object).fmt(f)?;
174 format_descriptor(cx, cfg, desc, object).fmt(f)
175}
176
177pub(crate) fn write_remaining(f: &mut Formatter, remaining: usize, inner: Option<&str>, colour: Color) -> fmt::Result {
178 if remaining > 0 {
179 if let Some(inner) = inner {
180 f.write_str(inner)?;
181 }
182
183 match remaining.cmp(&1) {
184 Ordering::Equal => "... 1 more item".color(colour).fmt(f)?,
185 Ordering::Greater => {
186 let mut buffer = Buffer::new();
187 write!(
188 f,
189 "{} {} {}",
190 "...".color(colour),
191 buffer.format(remaining).color(colour),
192 "more items".color(colour)
193 )?;
194 }
195 _ => (),
196 }
197 if inner.is_some() {
198 ",".color(colour).fmt(f)?;
199 } else {
200 f.write_char(' ')?;
201 }
202 }
203 Ok(())
204}