@opencode/codemode 2.0.16 → 2.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -84,6 +84,7 @@ export declare abstract class Opaque extends Obj {
84
84
  inspect(): string;
85
85
  }
86
86
  export declare abstract class Callable extends Opaque {
87
+ readonly length: number;
87
88
  readonly tag = "Function";
88
89
  constructor(proto: Obj, name: string, length: number);
89
90
  get describe(): string;
@@ -94,7 +95,17 @@ export declare class Fn extends Callable {
94
95
  readonly capturedScopes: Array<Map<string, Binding>>;
95
96
  readonly async: boolean;
96
97
  readonly generator: boolean;
97
- constructor(proto: Obj, name: string, parameters: ReadonlyArray<Pattern>, body: BlockStatement | Expression, capturedScopes: Array<Map<string, Binding>>, async: boolean, generator: boolean);
98
+ /** Arrows have no `this` or `arguments` of their own; they read the enclosing function's. */
99
+ readonly arrow: boolean;
100
+ constructor(proto: Obj, name: string, parameters: ReadonlyArray<Pattern>, body: BlockStatement | Expression, capturedScopes: Array<Map<string, Binding>>, async: boolean, generator: boolean,
101
+ /** Arrows have no `this` or `arguments` of their own; they read the enclosing function's. */
102
+ arrow: boolean);
103
+ }
104
+ /** The strict `arguments` object: an ordinary object with indexed own properties and a hidden `length`. */
105
+ export declare class Arguments extends Obj {
106
+ readonly tag = "Arguments";
107
+ constructor(proto: Obj, args: Array<Value>);
108
+ iterator(): ArrayIterator<Value>;
98
109
  }
99
110
  export type NativeCall<R> = (thisValue: Value, args: Array<Value>) => Effect.Effect<Value, unknown, R>;
100
111
  export type NativeConstruct<R> = (args: Array<Value>, newTarget: Callable) => Effect.Effect<Value, unknown, R>;
@@ -181,6 +192,17 @@ export declare class SetObj extends Wrapper {
181
192
  toHost(item: (value: Value) => unknown): Set<unknown>;
182
193
  iterator(): SetIterator<Value>;
183
194
  }
195
+ /** Keys are program objects, so a host WeakMap gives the same lifetime rule as JavaScript without any bookkeeping. */
196
+ export declare class WeakMapObj extends Wrapper {
197
+ readonly tag = "WeakMap";
198
+ readonly map: WeakMap<Obj, Value>;
199
+ inspect(): string;
200
+ }
201
+ export declare class WeakSetObj extends Wrapper {
202
+ readonly tag = "WeakSet";
203
+ readonly set: WeakSet<Obj>;
204
+ inspect(): string;
205
+ }
184
206
  export declare class URLSearchParamsObj extends Wrapper {
185
207
  readonly params: URLSearchParams;
186
208
  readonly tag = "URLSearchParams";
@@ -112,9 +112,11 @@ export class Opaque extends Obj {
112
112
  }
113
113
  }
114
114
  export class Callable extends Opaque {
115
+ length;
115
116
  tag = "Function";
116
117
  constructor(proto, name, length) {
117
118
  super(proto);
119
+ this.length = length;
118
120
  define(this, "length", length, readonly);
119
121
  define(this, "name", name, readonly);
120
122
  }
@@ -128,7 +130,10 @@ export class Fn extends Callable {
128
130
  capturedScopes;
129
131
  async;
130
132
  generator;
131
- constructor(proto, name, parameters, body, capturedScopes, async, generator) {
133
+ arrow;
134
+ constructor(proto, name, parameters, body, capturedScopes, async, generator,
135
+ /** Arrows have no `this` or `arguments` of their own; they read the enclosing function's. */
136
+ arrow) {
132
137
  const optional = parameters.findIndex((p) => p.type === "AssignmentPattern" || p.type === "RestElement");
133
138
  super(proto, name, optional === -1 ? parameters.length : optional);
134
139
  this.parameters = parameters;
@@ -136,6 +141,21 @@ export class Fn extends Callable {
136
141
  this.capturedScopes = capturedScopes;
137
142
  this.async = async;
138
143
  this.generator = generator;
144
+ this.arrow = arrow;
145
+ }
146
+ }
147
+ /** The strict `arguments` object: an ordinary object with indexed own properties and a hidden `length`. */
148
+ export class Arguments extends Obj {
149
+ tag = "Arguments";
150
+ constructor(proto, args) {
151
+ super(proto);
152
+ args.forEach((arg, index) => define(this, String(index), arg));
153
+ define(this, "length", args.length, hidden);
154
+ }
155
+ iterator() {
156
+ return keys(this)
157
+ .map((key) => get(this, key))
158
+ .values();
139
159
  }
140
160
  }
141
161
  export class Native extends Callable {
@@ -261,6 +281,21 @@ export class SetObj extends Wrapper {
261
281
  return this.set.values();
262
282
  }
263
283
  }
284
+ /** Keys are program objects, so a host WeakMap gives the same lifetime rule as JavaScript without any bookkeeping. */
285
+ export class WeakMapObj extends Wrapper {
286
+ tag = "WeakMap";
287
+ map = new WeakMap();
288
+ inspect() {
289
+ return "WeakMap { <items unknown> }";
290
+ }
291
+ }
292
+ export class WeakSetObj extends Wrapper {
293
+ tag = "WeakSet";
294
+ set = new WeakSet();
295
+ inspect() {
296
+ return "WeakSet { <items unknown> }";
297
+ }
298
+ }
264
299
  export class URLSearchParamsObj extends Wrapper {
265
300
  params;
266
301
  tag = "URLSearchParams";
@@ -4,7 +4,7 @@ import { checkArrayLength, checkStringLength, MAX_ARRAY_LENGTH } from "../interp
4
4
  import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
5
5
  import { define, get, hidden, Arr, GeneratorObj, hostIterator, Obj, PromiseObj, coerceToInteger, coerceToNumber, coerceToString, rejectAddition, } from "../interpreter/objects.js";
6
6
  import { describeValue, rejectCircularInsertion } from "../interpreter/references.js";
7
- import { applyCollectionCallback, invoke, preserveConsumerError } from "../interpreter/callback.js";
7
+ import { applyCollectionCallback, invoke, preserveConsumerError, withPrimitives } from "../interpreter/callback.js";
8
8
  import { compareText } from "../tool-runtime.js";
9
9
  const arrayLikeSource = (source) => {
10
10
  // JS would treat a promise as an empty array-like; that would hide a missing `await`.
@@ -29,7 +29,7 @@ const arrayFrom = (ctx, args) => {
29
29
  const values = [];
30
30
  for (let index = 0; index < arrayLike.length; index += 1) {
31
31
  const item = get(arrayLike.source, index);
32
- values.push(apply === undefined ? item : yield* apply([item, index]));
32
+ values.push(apply === undefined ? item : yield* apply([item, index], args[2]));
33
33
  }
34
34
  return new Arr(proto, values);
35
35
  }
@@ -39,7 +39,9 @@ const arrayFrom = (ctx, args) => {
39
39
  const step = yield* cursor.next;
40
40
  if (step.done)
41
41
  return new Arr(proto, values);
42
- values.push(apply === undefined ? step.value : yield* preserveConsumerError(cursor.close, apply([step.value, index])));
42
+ values.push(apply === undefined
43
+ ? step.value
44
+ : yield* preserveConsumerError(cursor.close, apply([step.value, index], args[2])));
43
45
  index += 1;
44
46
  }
45
47
  });
@@ -123,19 +125,19 @@ export const arrayGlobal = (ctx) => {
123
125
  "join",
124
126
  1,
125
127
  (thisValue, args) => {
126
- const joined = self(thisValue, "join")
127
- .items.map((item) => coerceToString(item ?? ""))
128
- .join(args[0] === undefined ? "," : coerceToString(args[0]));
129
- checkStringLength(joined.length);
130
- return joined;
128
+ // .map would keep holes, which Effect.forEach would then hand to the body as undefined.
129
+ const parts = Array.from(self(thisValue, "join").items, (item) => item ?? "");
130
+ return withPrimitives(ctx, "string", [args[0] === undefined ? "," : args[0], ...parts], ([separator, ...items]) => {
131
+ const joined = items.map(coerceToString).join(coerceToString(separator));
132
+ checkStringLength(joined.length);
133
+ return joined;
134
+ });
131
135
  },
132
136
  ],
133
137
  [
134
138
  "toString",
135
139
  0,
136
- (thisValue) => self(thisValue, "toString")
137
- .items.map((item) => coerceToString(item ?? ""))
138
- .join(","),
140
+ (thisValue) => withPrimitives(ctx, "string", Array.from(self(thisValue, "toString").items, (item) => item ?? ""), (items) => items.map(coerceToString).join(",")),
139
141
  ],
140
142
  [
141
143
  "includes",
@@ -332,7 +334,7 @@ export const arrayGlobal = (ctx) => {
332
334
  for (let index = 0; index < length; index += 1) {
333
335
  if (!(index in target.items))
334
336
  continue;
335
- const mapped = yield* apply([target.items[index], index, target]);
337
+ const mapped = yield* apply([target.items[index], index, target], args[1]);
336
338
  if (mapped instanceof Arr)
337
339
  values.push(...mapped.items);
338
340
  else
@@ -357,7 +359,10 @@ export const callbackMethods = (ctx, label, self, elements, wrap) => {
357
359
  length,
358
360
  (thisValue, args) => {
359
361
  const target = self(thisValue, name);
360
- return body(elements(target), target, applyCollectionCallback(ctx, args[0], `${label}.${name}`), args);
362
+ const call = applyCollectionCallback(ctx, args[0], `${label}.${name}`);
363
+ // reduce and reduceRight take an initial value where the others take a thisArg.
364
+ const thisArg = name.startsWith("reduce") ? undefined : args[1];
365
+ return body(elements(target), target, (callbackArgs) => call(callbackArgs, thisArg), args);
361
366
  },
362
367
  ];
363
368
  return [
@@ -3,3 +3,5 @@ import type { Interpreter } from "../interpreter/interpreter.js";
3
3
  export declare const groupBy: <R>(ctx: Interpreter<R>, namespace: "Map" | "Object") => import("../interpreter/objects.js").Native<R>;
4
4
  export declare const mapGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
5
5
  export declare const setGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
6
+ export declare const weakMapGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
7
+ export declare const weakSetGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
@@ -1,7 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, fn, methods, prototypeFrom, receiver, requiresNew } from "../interpreter/native.js";
3
3
  import { invalidData, IteratorSymbol, typeError } from "../interpreter/model.js";
4
- import { define, defineAccessor, get, getOwn, hidden, Arr, coerceToString, hostCursor, hostIterator, MapObj, Obj, PromiseObj, SetObj, } from "../interpreter/objects.js";
4
+ import { define, defineAccessor, get, getOwn, hidden, Arr, coerceToString, hostCursor, hostIterator, MapObj, Obj, PromiseObj, SetObj, WeakMapObj, WeakSetObj, } from "../interpreter/objects.js";
5
5
  import { describeValue, isOpaque } from "../interpreter/references.js";
6
6
  import { applyCollectionCallback, isSupportedCallback, preserveConsumerError, toPrimitiveNumber, toPrimitiveString, } from "../interpreter/callback.js";
7
7
  const coerceGroupByPropertyKey = (ctx, value) => {
@@ -167,7 +167,7 @@ export const mapGlobal = (ctx) => {
167
167
  const apply = applyCollectionCallback(ctx, args[0], "Map.forEach");
168
168
  return Effect.gen(function* () {
169
169
  for (const [key, item] of target.map.entries())
170
- yield* apply([item, key, target]);
170
+ yield* apply([item, key, target], args[1]);
171
171
  return undefined;
172
172
  });
173
173
  },
@@ -357,7 +357,7 @@ export const setGlobal = (ctx) => {
357
357
  const apply = applyCollectionCallback(ctx, args[0], "Set.forEach");
358
358
  return Effect.gen(function* () {
359
359
  for (const item of target.set.values())
360
- yield* apply([item, item, target]);
360
+ yield* apply([item, item, target], args[1]);
361
361
  return undefined;
362
362
  });
363
363
  },
@@ -373,3 +373,130 @@ export const setGlobal = (ctx) => {
373
373
  define(proto, IteratorSymbol, get(proto, "values"), hidden);
374
374
  return set;
375
375
  };
376
+ // CanBeHeldWeakly: only program objects; tool references are rebuilt on every access, so they could never be found again.
377
+ const weakKey = (value, label) => {
378
+ if (value instanceof Obj)
379
+ return value;
380
+ throw typeError(`Invalid value used ${label}: ${describeValue(value)} cannot be held weakly.`);
381
+ };
382
+ export const weakMapGlobal = (ctx) => {
383
+ const builtins = ctx.builtins;
384
+ const proto = builtins.WeakMap;
385
+ const weakMap = constructor(builtins, proto, {
386
+ name: "WeakMap",
387
+ call: requiresNew("WeakMap"),
388
+ construct: (args, newTarget) => {
389
+ const target = new WeakMapObj(prototypeFrom(newTarget, proto));
390
+ if (args[0] === undefined || args[0] === null)
391
+ return Effect.succeed(target);
392
+ return Effect.gen(function* () {
393
+ const cursor = yield* ctx.iterate(args[0]);
394
+ if (cursor === undefined) {
395
+ throw typeError(`new WeakMap(...) expects an iterable of [key, value] pairs, received ${describeValue(args[0])}.`);
396
+ }
397
+ while (true) {
398
+ const step = yield* cursor.next;
399
+ if (step.done)
400
+ return target;
401
+ yield* preserveConsumerError(cursor.close, Effect.sync(() => {
402
+ if (!(step.value instanceof Obj)) {
403
+ throw typeError("new WeakMap(...) expects [key, value] pairs as entry objects.");
404
+ }
405
+ target.map.set(weakKey(getOwn(step.value, 0), "as weak map key"), getOwn(step.value, 1));
406
+ }));
407
+ }
408
+ });
409
+ },
410
+ });
411
+ const self = (thisValue, name) => receiver(WeakMapObj, thisValue, `WeakMap.prototype.${name}`).map;
412
+ // Lookups pass any key through: the host collection answers false for a non-object, as the spec requires.
413
+ const key = (value) => weakKey(value, "as weak map key");
414
+ methods(builtins, proto, [
415
+ [
416
+ "get",
417
+ 1,
418
+ (thisValue, args) => {
419
+ const target = self(thisValue, "get");
420
+ return args[0] instanceof Obj ? target.get(args[0]) : undefined;
421
+ },
422
+ ],
423
+ ["has", 1, (thisValue, args) => self(thisValue, "has").has(args[0])],
424
+ ["delete", 1, (thisValue, args) => self(thisValue, "delete").delete(args[0])],
425
+ [
426
+ "set",
427
+ 2,
428
+ (thisValue, args) => {
429
+ self(thisValue, "set").set(key(args[0]), args[1]);
430
+ return thisValue;
431
+ },
432
+ ],
433
+ [
434
+ "getOrInsert",
435
+ 2,
436
+ (thisValue, args) => {
437
+ const target = self(thisValue, "getOrInsert");
438
+ const k = key(args[0]);
439
+ if (!target.has(k))
440
+ target.set(k, args[1]);
441
+ return target.get(k);
442
+ },
443
+ ],
444
+ [
445
+ "getOrInsertComputed",
446
+ 2,
447
+ (thisValue, args) => {
448
+ const target = self(thisValue, "getOrInsertComputed");
449
+ const k = key(args[0]);
450
+ const apply = applyCollectionCallback(ctx, args[1], "WeakMap.getOrInsertComputed");
451
+ if (target.has(k))
452
+ return target.get(k);
453
+ return Effect.map(apply([k]), (value) => {
454
+ target.set(k, value);
455
+ return value;
456
+ });
457
+ },
458
+ ],
459
+ ]);
460
+ return weakMap;
461
+ };
462
+ export const weakSetGlobal = (ctx) => {
463
+ const builtins = ctx.builtins;
464
+ const proto = builtins.WeakSet;
465
+ const weakSet = constructor(builtins, proto, {
466
+ name: "WeakSet",
467
+ call: requiresNew("WeakSet"),
468
+ construct: (args, newTarget) => {
469
+ const target = new WeakSetObj(prototypeFrom(newTarget, proto));
470
+ if (args[0] === undefined || args[0] === null)
471
+ return Effect.succeed(target);
472
+ return Effect.gen(function* () {
473
+ const cursor = yield* ctx.iterate(args[0]);
474
+ if (cursor === undefined) {
475
+ throw typeError(`new WeakSet(...) expects a synchronous iterable, received ${describeValue(args[0])}.`);
476
+ }
477
+ while (true) {
478
+ const step = yield* cursor.next;
479
+ if (step.done)
480
+ return target;
481
+ yield* preserveConsumerError(cursor.close, Effect.sync(() => {
482
+ target.set.add(weakKey(step.value, "in weak set"));
483
+ }));
484
+ }
485
+ });
486
+ },
487
+ });
488
+ const self = (thisValue, name) => receiver(WeakSetObj, thisValue, `WeakSet.prototype.${name}`).set;
489
+ methods(builtins, proto, [
490
+ ["has", 1, (thisValue, args) => self(thisValue, "has").has(args[0])],
491
+ ["delete", 1, (thisValue, args) => self(thisValue, "delete").delete(args[0])],
492
+ [
493
+ "add",
494
+ 1,
495
+ (thisValue, args) => {
496
+ self(thisValue, "add").add(weakKey(args[0], "in weak set"));
497
+ return thisValue;
498
+ },
499
+ ],
500
+ ]);
501
+ return weakSet;
502
+ };
@@ -14,8 +14,8 @@ const constructDate = (ctx, args, proto) => {
14
14
  ? new DateObj(proto, Date.parse(value))
15
15
  : new DateObj(proto, new Date(coerceToNumber(value)).getTime()));
16
16
  }
17
- const parts = args.map((arg) => coerceToNumber(arg));
18
- return Effect.succeed(new DateObj(proto, new Date(...parts).getTime()));
17
+ // The spec converts at most seven components, in order, so extra arguments never run program code.
18
+ return Effect.map(Effect.forEach(args.slice(0, 7), (arg) => toPrimitiveNumber(ctx, arg), { concurrency: 1 }), (parts) => new DateObj(proto, new Date(...parts).getTime()));
19
19
  };
20
20
  const getters = [
21
21
  "getFullYear",
@@ -66,7 +66,11 @@ export const dateGlobal = (ctx) => {
66
66
  methods(builtins, date, [
67
67
  ["now", 0, () => Date.now()],
68
68
  ["parse", 1, (_, args) => Date.parse(coerceToString(args[0]))],
69
- ["UTC", 7, (_, args) => Date.UTC(...args.map((arg) => coerceToNumber(arg)))],
69
+ [
70
+ "UTC",
71
+ 7,
72
+ (_, args) => Effect.map(Effect.forEach(args.slice(0, 7), (arg) => toPrimitiveNumber(ctx, arg), { concurrency: 1 }), (parts) => Date.UTC(...parts)),
73
+ ],
70
74
  ]);
71
75
  const self = (thisValue, name) => receiver(DateObj, thisValue, `Date.prototype.${name}`);
72
76
  const iso = (value) => {
@@ -107,6 +111,9 @@ export const dateGlobal = (ctx) => {
107
111
  return Effect.map(Effect.forEach(args.slice(0, length), (arg) => toPrimitiveNumber(ctx, arg), {
108
112
  concurrency: 1,
109
113
  }), (values) => {
114
+ // Every setter but setTime and setFullYear leaves an invalid Date untouched and answers NaN.
115
+ if (Number.isNaN(hosted.getTime()) && name !== "setTime" && !name.endsWith("FullYear"))
116
+ return NaN;
110
117
  target.time = hosted[name](...values);
111
118
  return target.time;
112
119
  });
@@ -104,7 +104,7 @@ export const headersGlobal = (ctx) => {
104
104
  const apply = applyCollectionCallback(ctx, args[0], "Headers.forEach");
105
105
  return Effect.gen(function* () {
106
106
  for (const [key, value] of Array.from(target.headers.entries()))
107
- yield* apply([value, key, target]);
107
+ yield* apply([value, key, target], args[1]);
108
108
  return undefined;
109
109
  });
110
110
  },
@@ -38,7 +38,7 @@ const parse = (ctx, args) => {
38
38
  set(value, name, revived);
39
39
  }
40
40
  }
41
- return yield* apply([key, value]);
41
+ return yield* apply([key, value], holder);
42
42
  });
43
43
  return visit(record(ctx.builtins.Object, { "": parsed }), "");
44
44
  };
@@ -2,23 +2,27 @@ import { Effect } from "effect";
2
2
  import { constants, methods } from "../interpreter/native.js";
3
3
  import { typeError } from "../interpreter/model.js";
4
4
  import { Obj, coerceToNumber } from "../interpreter/objects.js";
5
- import { preserveConsumerError } from "../interpreter/callback.js";
6
- // Validate only the arguments a method consumes; like JS, extras are ignored
7
- // (so built-ins work as callbacks receiving (element, index, array)).
8
- const unary = (name, op) => [name, 1, (_, args) => op(coerceToNumber(args[0]))];
9
- const binary = (name, op) => [
10
- name,
11
- 2,
12
- (_, args) => op(coerceToNumber(args[0]), coerceToNumber(args[1])),
13
- ];
14
- const variadic = (name, op) => [
15
- name,
16
- 2,
17
- (_, args) => op(...args.map(coerceToNumber)),
18
- ];
5
+ import { preserveConsumerError, withPrimitives } from "../interpreter/callback.js";
19
6
  export const mathGlobal = (ctx) => {
20
7
  const builtins = ctx.builtins;
21
8
  const math = new Obj(builtins.Object);
9
+ // Convert only the arguments a method consumes; like JS, extras are ignored
10
+ // (so built-ins work as callbacks receiving (element, index, array)).
11
+ const unary = (name, op) => [
12
+ name,
13
+ 1,
14
+ (_, args) => withPrimitives(ctx, "number", [args[0]], ([a]) => op(coerceToNumber(a))),
15
+ ];
16
+ const binary = (name, op) => [
17
+ name,
18
+ 2,
19
+ (_, args) => withPrimitives(ctx, "number", [args[0], args[1]], ([a, b]) => op(coerceToNumber(a), coerceToNumber(b))),
20
+ ];
21
+ const variadic = (name, op) => [
22
+ name,
23
+ 2,
24
+ (_, args) => withPrimitives(ctx, "number", args, (values) => op(...values.map(coerceToNumber))),
25
+ ];
22
26
  constants(math, {
23
27
  PI: Math.PI,
24
28
  E: Math.E,
@@ -1,7 +1,8 @@
1
1
  import { constructor, constants, methods } from "../interpreter/native.js";
2
- import { coerceToNumber, coerceToString } from "../interpreter/objects.js";
2
+ import { coerceToNumber } from "../interpreter/objects.js";
3
3
  import { rangeError, typeError } from "../interpreter/model.js";
4
- import { coercion } from "./value.js";
4
+ import { withPrimitives } from "../interpreter/callback.js";
5
+ import { coerce, coercion } from "./value.js";
5
6
  export const numberGlobal = (ctx) => {
6
7
  const builtins = ctx.builtins;
7
8
  const number = constructor(builtins, builtins.Number, {
@@ -24,46 +25,34 @@ export const numberGlobal = (ctx) => {
24
25
  ["isFinite", 1, (_, args) => Number.isFinite(args[0])],
25
26
  ["isNaN", 1, (_, args) => Number.isNaN(args[0])],
26
27
  ["isSafeInteger", 1, (_, args) => Number.isSafeInteger(args[0])],
27
- [
28
- "parseInt",
29
- 2,
30
- (_, args) => {
31
- return parseInt(coerceToString(args[0]), coerceToNumber(args[1]));
32
- },
33
- ],
34
- ["parseFloat", 1, (_, args) => parseFloat(coerceToString(args[0]))],
28
+ ["parseInt", 2, (_, args) => coerce(ctx, "parseInt", args)],
29
+ ["parseFloat", 1, (_, args) => coerce(ctx, "parseFloat", args)],
35
30
  ]);
36
31
  const self = (thisValue, name) => {
37
32
  if (typeof thisValue === "number")
38
33
  return thisValue;
39
34
  throw typeError(`Number.prototype.${name} requires that 'this' be a Number.`);
40
35
  };
41
- const optNum = (arg) => (arg === undefined ? undefined : coerceToNumber(arg));
36
+ // The receiver is checked first, then the one argument converts through ToPrimitive with the number hint.
37
+ const formatting = (name, op) => [
38
+ name,
39
+ 1,
40
+ (thisValue, args) => {
41
+ const value = self(thisValue, name);
42
+ return withPrimitives(ctx, "number", [args[0]], ([digits]) => op(value, digits === undefined ? undefined : coerceToNumber(digits)));
43
+ },
44
+ ];
42
45
  methods(builtins, builtins.Number, [
43
- ["toFixed", 1, (thisValue, args) => self(thisValue, "toFixed").toFixed(optNum(args[0]))],
46
+ formatting("toFixed", (value, digits) => value.toFixed(digits)),
44
47
  ["toLocaleString", 0, (thisValue) => self(thisValue, "toLocaleString").toLocaleString("en-US")],
45
- ["toExponential", 1, (thisValue, args) => self(thisValue, "toExponential").toExponential(optNum(args[0]))],
46
- [
47
- "toPrecision",
48
- 1,
49
- (thisValue, args) => {
50
- const value = self(thisValue, "toPrecision");
51
- const digits = optNum(args[0]);
52
- return digits === undefined ? value.toString() : value.toPrecision(digits);
53
- },
54
- ],
55
- [
56
- "toString",
57
- 1,
58
- (thisValue, args) => {
59
- const value = self(thisValue, "toString");
60
- const radix = optNum(args[0]);
61
- if (radix !== undefined && (radix < 2 || radix > 36)) {
62
- throw rangeError("Number.toString radix must be between 2 and 36.");
63
- }
64
- return value.toString(radix);
65
- },
66
- ],
48
+ formatting("toExponential", (value, digits) => value.toExponential(digits)),
49
+ formatting("toPrecision", (value, digits) => (digits === undefined ? value.toString() : value.toPrecision(digits))),
50
+ formatting("toString", (value, radix) => {
51
+ if (radix !== undefined && (radix < 2 || radix > 36)) {
52
+ throw rangeError("Number.toString radix must be between 2 and 36.");
53
+ }
54
+ return value.toString(radix);
55
+ }),
67
56
  ["valueOf", 0, (thisValue) => self(thisValue, "valueOf")],
68
57
  ]);
69
58
  return number;