ion/format/
object.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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
33/// Formats a [JavaScript Object](Object), depending on its class, using the given [configuration](Config).
34/// The object is passed to more specific formatting functions, such as [format_array] and [format_date].
35pub 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
105/// Formats an [object](Object) using the given [configuration](Config).
106pub 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}