1use rquickjs::function::{Func, Opt, Rest, This};
13use rquickjs::{Ctx, Function, Object, Promise, Result, Value};
14
15use super::deep_equal::{Mode, deep_equal};
16use super::inspect::{Inspector, MAX_DIR_DEPTH};
17
18fn bind<'js>(ctx: &Ctx<'js>, target: &Function<'js>, args: Vec<Value<'js>>) -> Result<Function<'js>> {
20 let bind_fn: Function<'js> = target.get("bind")?;
21 let mut call_args = rquickjs::function::Args::new(ctx.clone(), args.len() + 1);
22 call_args.this(target.clone())?;
23 call_args.push_arg(Value::new_undefined(ctx.clone()))?;
24 for arg in args {
25 call_args.push_arg(arg)?;
26 }
27 call_args.apply(&bind_fn)
28}
29
30fn options_of<'js>(options: &Opt<Value<'js>>) -> Option<Object<'js>> {
31 options.0.as_ref().and_then(|v| v.as_object().cloned())
32}
33
34fn depth_of(options: Option<&Object<'_>>) -> usize {
37 let Some(options) = options else {
38 return 2;
39 };
40 match options.get::<_, Value<'_>>("depth") {
41 Ok(v) if v.is_null() => MAX_DIR_DEPTH,
42 Ok(v) => v.as_number().map_or(2, |n| n.max(0.0) as usize),
43 Err(_) => 2,
44 }
45}
46
47fn colors_of(options: Option<&Object<'_>>) -> bool {
48 options.and_then(|o| o.get::<_, bool>("colors").ok()).unwrap_or(false)
49}
50
51fn format_args(args: &[Value<'_>], colors: bool) -> Result<String> {
52 let mut out = String::new();
53 Inspector::new(colors).args(&mut out, args)?;
54 Ok(out)
55}
56
57fn inspect<'js>(value: Value<'js>, options: Opt<Value<'js>>) -> Result<String> {
58 let options = options_of(&options);
59 let mut out = String::new();
60 Inspector::new(colors_of(options.as_ref()))
61 .with_depth(depth_of(options.as_ref()))
62 .quoted()
63 .value(&mut out, &value, 0)?;
64 Ok(out)
65}
66
67fn settle_promise<'js>(
70 resolve: Function<'js>,
71 reject: Function<'js>,
72 err: Value<'js>,
73 rest: Rest<Value<'js>>,
74) -> Result<()> {
75 if err.is_null() || err.is_undefined() {
76 let value = rest.0.into_iter().next();
77 match value {
78 Some(v) => resolve.call::<_, ()>((v,)),
79 None => resolve.call::<_, ()>(()),
80 }
81 } else {
82 reject.call::<_, ()>((err,))
83 }
84}
85
86fn promisified<'js>(
89 ctx: Ctx<'js>,
90 this: This<Value<'js>>,
91 original: Function<'js>,
92 args: Rest<Value<'js>>,
93) -> Result<Promise<'js>> {
94 let (promise, resolve, reject) = ctx.promise()?;
95 let settle = Function::new(ctx.clone(), settle_promise)?;
96 let callback = bind(&ctx, &settle, vec![resolve.into_value(), reject.into_value()])?;
97
98 let mut call = rquickjs::function::Args::new(ctx.clone(), args.0.len() + 1);
99 call.this(this.0)?;
100 for arg in args.0 {
101 call.push_arg(arg)?;
102 }
103 call.push_arg(callback)?;
104 call.apply::<()>(&original)?;
105 Ok(promise)
106}
107
108fn promisify<'js>(ctx: Ctx<'js>, original: Function<'js>) -> Result<Function<'js>> {
109 if let Ok(custom) = original.get::<_, Function<'js>>("__promisify__") {
111 return Ok(custom);
112 }
113 let body = Function::new(ctx.clone(), promisified)?;
114 bind(&ctx, &body, vec![original.into_value()])
115}
116
117fn settle_callback<'js>(callback: Function<'js>, is_error: bool, value: Value<'js>) -> Result<()> {
120 if is_error {
121 callback.call::<_, ()>((value,))
122 } else {
123 let ctx = callback.ctx().clone();
124 callback.call::<_, ()>((Value::new_null(ctx), value))
125 }
126}
127
128fn callbackified<'js>(ctx: Ctx<'js>, this: This<Value<'js>>, original: Function<'js>, args: Rest<Value<'js>>) -> Result<()> {
131 let mut args = args.0;
132 let callback: Function<'js> = match args.pop().and_then(|v| v.as_function().cloned()) {
133 Some(f) => f,
134 None => {
135 return Err(rquickjs::Exception::throw_type(
136 &ctx,
137 "The last argument must be of type function",
138 ));
139 },
140 };
141
142 let mut call = rquickjs::function::Args::new(ctx.clone(), args.len());
143 call.this(this.0)?;
144 for arg in args {
145 call.push_arg(arg)?;
146 }
147 let promise: Promise<'js> = call.apply(&original)?;
148
149 let settle = Function::new(ctx.clone(), settle_callback)?;
150 let on_ok = bind(&ctx, &settle, vec![callback.clone().into_value(), Value::new_bool(ctx.clone(), false)])?;
151 let on_err = bind(&ctx, &settle, vec![callback.into_value(), Value::new_bool(ctx.clone(), true)])?;
152 let then: Function<'js> = promise.get("then")?;
153 then.call::<_, ()>((This(promise), on_ok, on_err))
154}
155
156fn callbackify<'js>(ctx: Ctx<'js>, original: Function<'js>) -> Result<Function<'js>> {
157 let body = Function::new(ctx.clone(), callbackified)?;
158 bind(&ctx, &body, vec![original.into_value()])
159}
160
161fn deprecated<'js>(
165 ctx: Ctx<'js>,
166 this: This<Value<'js>>,
167 original: Function<'js>,
168 message: String,
169 args: Rest<Value<'js>>,
170) -> Result<Value<'js>> {
171 if original.get::<_, bool>("__deprecation_warned__").unwrap_or(false) {
172 } else {
174 original.set("__deprecation_warned__", true)?;
175 if let Ok(console) = ctx.globals().get::<_, Object<'js>>("console") {
176 if let Ok(warn) = console.get::<_, Function<'js>>("warn") {
177 warn.call::<_, ()>((format!("DeprecationWarning: {message}"),))?;
178 }
179 }
180 }
181 let mut call = rquickjs::function::Args::new(ctx.clone(), args.0.len());
182 call.this(this.0)?;
183 for arg in args.0 {
184 call.push_arg(arg)?;
185 }
186 call.apply(&original)
187}
188
189fn deprecate<'js>(ctx: Ctx<'js>, original: Function<'js>, message: String) -> Result<Function<'js>> {
190 let body = Function::new(ctx.clone(), deprecated)?;
191 bind(
192 &ctx,
193 &body,
194 vec![original.into_value(), rquickjs::String::from_str(ctx.clone(), &message)?.into_value()],
195 )
196}
197
198fn is_deep_strict_equal<'js>(a: Value<'js>, b: Value<'js>) -> Result<bool> {
199 deep_equal(&a, &b, Mode::Strict)
200}
201
202fn inherits<'js>(ctor: Function<'js>, super_ctor: Function<'js>) -> Result<()> {
204 let super_proto: Object<'js> = super_ctor.get("prototype")?;
205 let proto: Object<'js> = ctor.get("prototype")?;
206 proto.set_prototype(Some(&super_proto))?;
207 ctor.set("super_", super_ctor)?;
208 Ok(())
209}
210
211fn tag_of(value: &Value<'_>) -> Result<String> {
214 let object: Object<'_> = value.ctx().globals().get("Object")?;
215 let proto: Object<'_> = object.get("prototype")?;
216 let to_string: Function<'_> = proto.get("toString")?;
217 to_string.call((This(value.clone()),))
218}
219
220const TYPED_ARRAY_TAGS: &[&str] = &[
221 "[object Int8Array]",
222 "[object Uint8Array]",
223 "[object Uint8ClampedArray]",
224 "[object Int16Array]",
225 "[object Uint16Array]",
226 "[object Int32Array]",
227 "[object Uint32Array]",
228 "[object Float16Array]",
229 "[object Float32Array]",
230 "[object Float64Array]",
231 "[object BigInt64Array]",
232 "[object BigUint64Array]",
233];
234
235fn types_object<'js>(ctx: &Ctx<'js>) -> Result<Object<'js>> {
236 let types = Object::new(ctx.clone())?;
237 let tagged = |tag: &'static str| {
238 Func::from(move |value: Value<'_>| -> Result<bool> { Ok(tag_of(&value)? == tag) })
239 };
240 types.set("isDate", tagged("[object Date]"))?;
241 types.set("isRegExp", tagged("[object RegExp]"))?;
242 types.set("isMap", tagged("[object Map]"))?;
243 types.set("isSet", tagged("[object Set]"))?;
244 types.set("isWeakMap", tagged("[object WeakMap]"))?;
245 types.set("isWeakSet", tagged("[object WeakSet]"))?;
246 types.set("isPromise", tagged("[object Promise]"))?;
247 types.set("isArrayBuffer", tagged("[object ArrayBuffer]"))?;
248 types.set("isSharedArrayBuffer", tagged("[object SharedArrayBuffer]"))?;
249 types.set("isDataView", tagged("[object DataView]"))?;
250 types.set("isNativeError", tagged("[object Error]"))?;
251 types.set("isAsyncFunction", tagged("[object AsyncFunction]"))?;
252 types.set("isGeneratorFunction", tagged("[object GeneratorFunction]"))?;
253 types.set("isGeneratorObject", tagged("[object Generator]"))?;
254 types.set("isArgumentsObject", tagged("[object Arguments]"))?;
255 types.set(
256 "isTypedArray",
257 Func::from(|value: Value<'_>| -> Result<bool> { Ok(TYPED_ARRAY_TAGS.contains(&tag_of(&value)?.as_str())) }),
258 )?;
259 types.set(
260 "isBoxedPrimitive",
261 Func::from(|value: Value<'_>| -> Result<bool> {
262 if !value.is_object() {
263 return Ok(false);
264 }
265 let tag = tag_of(&value)?;
266 Ok(matches!(
267 tag.as_str(),
268 "[object String]" | "[object Number]" | "[object Boolean]" | "[object Symbol]" | "[object BigInt]"
269 ))
270 }),
271 )?;
272 Ok(types)
273}
274
275pub fn util_object<'js>(ctx: &Ctx<'js>) -> Result<Object<'js>> {
282 let util = Object::new(ctx.clone())?;
283
284 util.set(
285 "format",
286 Func::from(|args: Rest<Value<'_>>| -> Result<String> { format_args(&args.0, false) }),
287 )?;
288 util.set(
289 "formatWithOptions",
290 Func::from(|options: Opt<Value<'_>>, args: Rest<Value<'_>>| -> Result<String> {
291 format_args(&args.0, colors_of(options_of(&options).as_ref()))
292 }),
293 )?;
294
295 let inspect_fn = Function::new(ctx.clone(), inspect)?.with_name("inspect")?;
296 let symbol: Object<'js> = ctx.globals().get("Symbol")?;
300 let symbol_for: Function<'js> = symbol.get("for")?;
301 let custom: Value<'js> = symbol_for.call(("nodejs.util.inspect.custom",))?;
302 inspect_fn.set("custom", custom)?;
303 util.set("inspect", inspect_fn)?;
304
305 util.set("promisify", Func::from(promisify))?;
306 util.set("callbackify", Func::from(callbackify))?;
307 util.set("deprecate", Func::from(deprecate))?;
308 util.set("inherits", Func::from(inherits))?;
309 util.set("types", types_object(ctx)?)?;
310 util.set(
311 "isDeepStrictEqual",
312 Func::from(is_deep_strict_equal),
313 )?;
314
315 for name in ["TextEncoder", "TextDecoder"] {
318 if let Ok(class) = ctx.globals().get::<_, Value<'js>>(name) {
319 if !class.is_undefined() {
320 util.set(name, class)?;
321 }
322 }
323 }
324
325 Ok(util)
326}
327
328pub const UTIL_MEMBERS: &[&str] = &[
330 "TextDecoder",
331 "TextEncoder",
332 "callbackify",
333 "deprecate",
334 "format",
335 "formatWithOptions",
336 "inherits",
337 "inspect",
338 "isDeepStrictEqual",
339 "promisify",
340 "types",
341];