@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.
@@ -4,7 +4,7 @@ import { checkArrayLength, checkStringLength } from "../interpreter/limits.js";
4
4
  import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
5
5
  import { define, get, hidden, Arr, hostIterator, Obj, RegExpObj, record, coerceToInteger, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
6
6
  import { containsOpaqueReference, typeofValue } from "../interpreter/references.js";
7
- import { applyCollectionCallback, isSupportedCallback, toPrimitiveString } from "../interpreter/callback.js";
7
+ import { applyCollectionCallback, isSupportedCallback, toPrimitiveString, withPrimitives, } from "../interpreter/callback.js";
8
8
  import { matchToValue, toHostRegex } from "./regexp.js";
9
9
  import { coercion } from "./value.js";
10
10
  // console is intercepted by the interpreter before reaching here.
@@ -111,28 +111,50 @@ export const stringGlobal = (ctx) => {
111
111
  const num = (name, args, index) => coerceToNumber(requireDataArgument(name, index, args[index]));
112
112
  const optNum = (name, args, index) => args[index] === undefined ? undefined : num(name, args, index);
113
113
  const optStr = (name, args, index) => args[index] === undefined ? undefined : str(name, args, index);
114
- const rejectRegex = (name, args) => {
114
+ // ToPrimitive in spec order: the receiver, then the arguments the method consumes, one hint per position; the
115
+ // rest pass through as they are.
116
+ const simple = (name, length, op, hints = []) => [
117
+ name,
118
+ length,
119
+ (thisValue, args) => {
120
+ if (thisValue === null || thisValue === undefined) {
121
+ throw typeError(`String.prototype.${name} called on null or undefined.`);
122
+ }
123
+ return withPrimitives(ctx, ["string", ...hints], [thisValue, ...args.slice(0, hints.length)], ([value, ...primitives]) => op(coerceToString(value), [...primitives, ...args.slice(hints.length)]));
124
+ },
125
+ ];
126
+ // includes, startsWith, and endsWith reject a RegExp before converting their search string and position.
127
+ const searching = (name, op) => simple(name, 1, (value, args) => {
115
128
  if (args[0] instanceof RegExpObj) {
116
129
  throw typeError(`String.${name} cannot take a regular expression; use regex.test(string) or String.search instead.`);
117
130
  }
118
- };
119
- const simple = (name, length, op) => [name, length, (thisValue, args) => op(self(thisValue, name), args)];
131
+ return withPrimitives(ctx, ["string", "number"], args.slice(0, 2), (primitives) => op(value, str(name, primitives, 0), optNum(name, primitives, 1)));
132
+ });
133
+ // match, matchAll, and search read a RegExp as is and convert anything else to its pattern text.
134
+ const withPattern = (args, op) => args[0] instanceof RegExpObj ? op(args[0]) : withPrimitives(ctx, "string", [args[0]], ([text]) => op(text));
120
135
  const replace = (name) => simple(name, 2, (value, args) => {
121
- if (isSupportedCallback(args[1]))
122
- return replaceWithCallback(ctx, value, name, args);
123
- if (typeofValue(args[1]) === "function") {
124
- throw typeError(`String.${name} cannot use this callable as a replacer; wrap it in an arrow function, e.g. (match) => tools.ns.tool(match).`);
125
- }
126
- if (args[0] instanceof RegExpObj) {
127
- const pattern = args[0].regex;
128
- const replacement = str(name, args, 1);
129
- if (name === "replaceAll")
130
- replaceAllNeedsGlobal(pattern);
131
- return name === "replace" ? value.replace(pattern, replacement) : value.replaceAll(pattern, replacement);
132
- }
133
- if (name === "replace")
134
- return value.replace(str(name, args, 0), str(name, args, 1));
135
- return value.replaceAll(str(name, args, 0), str(name, args, 1));
136
+ const pattern = args[0];
137
+ const replacer = args[1];
138
+ // A RegExp pattern is used as is; a plain one converts to its search string, then a non-callable replacement.
139
+ return withPrimitives(ctx, "string", [pattern instanceof RegExpObj ? undefined : pattern, isSupportedCallback(replacer) ? undefined : replacer], ([search, replacement]) => {
140
+ if (isSupportedCallback(replacer)) {
141
+ return replaceWithCallback(ctx, value, name, [pattern instanceof RegExpObj ? pattern : search, replacer]);
142
+ }
143
+ if (typeofValue(replacer) === "function") {
144
+ throw typeError(`String.${name} cannot use this callable as a replacer; wrap it in an arrow function, e.g. (match) => tools.ns.tool(match).`);
145
+ }
146
+ const primitives = [search, replacement];
147
+ if (pattern instanceof RegExpObj) {
148
+ const regex = pattern.regex;
149
+ const text = str(name, primitives, 1);
150
+ if (name === "replaceAll")
151
+ replaceAllNeedsGlobal(regex);
152
+ return name === "replace" ? value.replace(regex, text) : value.replaceAll(regex, text);
153
+ }
154
+ if (name === "replace")
155
+ return value.replace(str(name, primitives, 0), str(name, primitives, 1));
156
+ return value.replaceAll(str(name, primitives, 0), str(name, primitives, 1));
157
+ });
136
158
  });
137
159
  methods(builtins, builtins.String, [
138
160
  simple("toString", 0, (value) => value),
@@ -147,7 +169,7 @@ export const stringGlobal = (ctx) => {
147
169
  simple("trimEnd", 0, (value) => value.trimEnd()),
148
170
  simple("trimRight", 0, (value) => value.trimEnd()),
149
171
  // Locale/options are deliberately unsupported; comparison uses the host default locale.
150
- simple("localeCompare", 1, (value, args) => value.localeCompare(str("localeCompare", args, 0))),
172
+ simple("localeCompare", 1, (value, args) => value.localeCompare(str("localeCompare", args, 0)), ["string"]),
151
173
  simple("normalize", 0, (value, args) => {
152
174
  const form = optStr("normalize", args, 0);
153
175
  try {
@@ -156,61 +178,62 @@ export const stringGlobal = (ctx) => {
156
178
  catch {
157
179
  throw rangeError(`String.normalize expects the form "NFC", "NFD", "NFKC", or "NFKD" (got ${JSON.stringify(form)}).`);
158
180
  }
159
- }),
181
+ }, ["string"]),
160
182
  simple("split", 2, (value, args) => {
161
- const wrap = (parts) => new Arr(builtins.Array, parts);
162
- // Native: an undefined separator returns the whole string, not a split on "undefined",
163
- // unless the limit truncates to zero.
164
- const requestedLimit = optNum("split", args, 1);
165
- if (args[0] === undefined) {
166
- return wrap(requestedLimit !== undefined && requestedLimit >>> 0 === 0 ? [] : [value]);
167
- }
168
- const parts = args[0] instanceof RegExpObj
169
- ? value.split(args[0].regex, requestedLimit)
170
- : value.split(str("split", args, 0), requestedLimit === undefined ? undefined : requestedLimit >>> 0);
171
- checkArrayLength(parts.length);
172
- return wrap(parts);
173
- }),
174
- simple("slice", 2, (value, args) => value.slice(optNum("slice", args, 0), optNum("slice", args, 1))),
175
- simple("includes", 1, (value, args) => {
176
- rejectRegex("includes", args);
177
- return value.includes(str("includes", args, 0), optNum("includes", args, 1));
183
+ const separator = args[0];
184
+ // A RegExp separator is used as is; the limit converts before a plain separator does, as in the spec.
185
+ return withPrimitives(ctx, ["number", "string"], [args[1], separator instanceof RegExpObj ? undefined : separator], ([limit, pattern]) => {
186
+ const wrap = (parts) => new Arr(builtins.Array, parts);
187
+ // Native: an undefined separator returns the whole string, not a split on "undefined",
188
+ // unless the limit truncates to zero.
189
+ const requestedLimit = args[1] === undefined ? undefined : num("split", [pattern, limit], 1);
190
+ if (separator === undefined) {
191
+ return wrap(requestedLimit !== undefined && requestedLimit >>> 0 === 0 ? [] : [value]);
192
+ }
193
+ const parts = separator instanceof RegExpObj
194
+ ? value.split(separator.regex, requestedLimit)
195
+ : value.split(str("split", [pattern], 0), requestedLimit === undefined ? undefined : requestedLimit >>> 0);
196
+ checkArrayLength(parts.length);
197
+ return wrap(parts);
198
+ });
178
199
  }),
179
- simple("startsWith", 1, (value, args) => {
180
- rejectRegex("startsWith", args);
181
- return value.startsWith(str("startsWith", args, 0), optNum("startsWith", args, 1));
182
- }),
183
- simple("endsWith", 1, (value, args) => {
184
- rejectRegex("endsWith", args);
185
- return value.endsWith(str("endsWith", args, 0), optNum("endsWith", args, 1));
186
- }),
187
- simple("indexOf", 1, (value, args) => value.indexOf(str("indexOf", args, 0), optNum("indexOf", args, 1))),
188
- simple("lastIndexOf", 1, (value, args) => value.lastIndexOf(str("lastIndexOf", args, 0), optNum("lastIndexOf", args, 1))),
200
+ simple("slice", 2, (value, args) => value.slice(optNum("slice", args, 0), optNum("slice", args, 1)), [
201
+ "number",
202
+ "number",
203
+ ]),
204
+ searching("includes", (value, search, position) => value.includes(search, position)),
205
+ searching("startsWith", (value, search, position) => value.startsWith(search, position)),
206
+ searching("endsWith", (value, search, position) => value.endsWith(search, position)),
207
+ simple("indexOf", 1, (value, args) => value.indexOf(str("indexOf", args, 0), optNum("indexOf", args, 1)), [
208
+ "string",
209
+ "number",
210
+ ]),
211
+ simple("lastIndexOf", 1, (value, args) => value.lastIndexOf(str("lastIndexOf", args, 0), optNum("lastIndexOf", args, 1)), ["string", "number"]),
189
212
  replace("replace"),
190
213
  replace("replaceAll"),
191
- simple("match", 1, (value, args) => {
192
- const pattern = toHostRegex(args[0], "match");
193
- const matched = value.match(pattern);
214
+ simple("match", 1, (value, args) => withPattern(args, (arg) => {
215
+ const regex = toHostRegex(arg, "match");
216
+ const matched = value.match(regex);
194
217
  if (matched === null)
195
218
  return null;
196
219
  // Preserve the own `index` and `groups` properties on non-global matches.
197
- if (pattern.global)
220
+ if (regex.global)
198
221
  return new Arr(builtins.Array, [...matched]);
199
222
  return matchToValue(builtins, matched);
200
- }),
201
- simple("matchAll", 1, (value, args) => {
202
- const pattern = toHostRegex(args[0], "matchAll", "g");
203
- if (!pattern.global) {
204
- throw typeError(`String.matchAll requires a regular expression with the global (g) flag: write /${pattern.source}/${pattern.flags}g, or use String.match for a single match.`);
223
+ })),
224
+ simple("matchAll", 1, (value, args) => withPattern(args, (arg) => {
225
+ const regex = toHostRegex(arg, "matchAll", "g");
226
+ if (!regex.global) {
227
+ throw typeError(`String.matchAll requires a regular expression with the global (g) flag: write /${regex.source}/${regex.flags}g, or use String.match for a single match.`);
205
228
  }
206
229
  const matches = [];
207
- for (const match of value.matchAll(pattern)) {
230
+ for (const match of value.matchAll(regex)) {
208
231
  checkArrayLength(matches.length + 1);
209
232
  matches.push(matchToValue(builtins, match));
210
233
  }
211
234
  return new Arr(builtins.Array, matches);
212
- }),
213
- simple("search", 1, (value, args) => value.search(toHostRegex(args[0], "search"))),
235
+ })),
236
+ simple("search", 1, (value, args) => withPattern(args, (arg) => value.search(toHostRegex(arg, "search")))),
214
237
  simple("repeat", 1, (value, args) => {
215
238
  const count = num("repeat", args, 0);
216
239
  if (!Number.isFinite(count) || count < 0) {
@@ -218,30 +241,33 @@ export const stringGlobal = (ctx) => {
218
241
  }
219
242
  checkStringLength(value.length * count);
220
243
  return value.repeat(count);
221
- }),
244
+ }, ["number"]),
222
245
  simple("padStart", 1, (value, args) => {
223
246
  const length = num("padStart", args, 0);
224
247
  checkStringLength(length);
225
248
  return value.padStart(length, optStr("padStart", args, 1));
226
- }),
249
+ }, ["number", "string"]),
227
250
  simple("padEnd", 1, (value, args) => {
228
251
  const length = num("padEnd", args, 0);
229
252
  checkStringLength(length);
230
253
  return value.padEnd(length, optStr("padEnd", args, 1));
231
- }),
232
- simple("charAt", 1, (value, args) => value.charAt(optNum("charAt", args, 0) ?? 0)),
233
- simple("at", 1, (value, args) => value.at(optNum("at", args, 0) ?? 0)),
234
- simple("substring", 2, (value, args) => value.substring(optNum("substring", args, 0) ?? 0, optNum("substring", args, 1))),
235
- simple("substr", 2, (value, args) => value.substr(optNum("substr", args, 0) ?? 0, optNum("substr", args, 1))),
254
+ }, ["number", "string"]),
255
+ simple("charAt", 1, (value, args) => value.charAt(optNum("charAt", args, 0) ?? 0), ["number"]),
256
+ simple("at", 1, (value, args) => value.at(optNum("at", args, 0) ?? 0), ["number"]),
257
+ simple("substring", 2, (value, args) => value.substring(optNum("substring", args, 0) ?? 0, optNum("substring", args, 1)), ["number", "number"]),
258
+ simple("substr", 2, (value, args) => value.substr(optNum("substr", args, 0) ?? 0, optNum("substr", args, 1)), [
259
+ "number",
260
+ "number",
261
+ ]),
236
262
  simple("isWellFormed", 0, (value) => value.isWellFormed()),
237
263
  simple("toWellFormed", 0, (value) => value.toWellFormed()),
238
- simple("charCodeAt", 1, (value, args) => value.charCodeAt(optNum("charCodeAt", args, 0) ?? 0)),
239
- simple("codePointAt", 1, (value, args) => value.codePointAt(optNum("codePointAt", args, 0) ?? 0)),
240
- simple("concat", 1, (value, args) => {
241
- const joined = value.concat(...args.map((_, index) => str("concat", args, index)));
264
+ simple("charCodeAt", 1, (value, args) => value.charCodeAt(optNum("charCodeAt", args, 0) ?? 0), ["number"]),
265
+ simple("codePointAt", 1, (value, args) => value.codePointAt(optNum("codePointAt", args, 0) ?? 0), ["number"]),
266
+ simple("concat", 1, (value, args) => withPrimitives(ctx, "string", args, (parts) => {
267
+ const joined = value.concat(...parts.map((_, index) => str("concat", parts, index)));
242
268
  checkStringLength(joined.length);
243
269
  return joined;
244
- }),
270
+ })),
245
271
  ]);
246
272
  define(builtins.String, IteratorSymbol, fn(builtins, "[Symbol.iterator]", 0, (thisValue) => hostIterator(builtins, self(thisValue, "[Symbol.iterator]")[Symbol.iterator]())), hidden);
247
273
  return string;
@@ -246,7 +246,7 @@ export const urlSearchParamsGlobal = (ctx) => {
246
246
  const apply = applyCollectionCallback(ctx, args[0], "URLSearchParams.forEach");
247
247
  return Effect.gen(function* () {
248
248
  for (const [key, value] of Array.from(target.params.entries()))
249
- yield* apply([value, key, target]);
249
+ yield* apply([value, key, target], args[1]);
250
250
  return undefined;
251
251
  });
252
252
  },
@@ -1,6 +1,7 @@
1
- import { type Native } from "../interpreter/objects.js";
1
+ import { type Native, type Value } from "../interpreter/objects.js";
2
2
  import type { Interpreter } from "../interpreter/interpreter.js";
3
3
  export declare const compoundOperators: Set<string>;
4
4
  export type Coercion = "Number" | "String" | "Boolean" | "parseInt" | "parseFloat" | "isFinite" | "isNaN";
5
+ export declare const coerce: <R>(ctx: Interpreter<R>, name: Coercion, args: Array<Value>) => Value | import("effect/Effect").Effect<Value, unknown, R>;
5
6
  /** A global coercion function such as `Number` or `parseInt`. */
6
7
  export declare const coercion: <R>(ctx: Interpreter<R>, name: Coercion, length?: number) => Native<R>;
@@ -1,7 +1,8 @@
1
1
  import { fn } from "../interpreter/native.js";
2
2
  import { coerceToNumber, coerceToString } from "../interpreter/objects.js";
3
+ import { withPrimitives } from "../interpreter/callback.js";
3
4
  export const compoundOperators = new Set(["+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>="]);
4
- const coerce = (ctx, name, args) => {
5
+ export const coerce = (ctx, name, args) => {
5
6
  // Native: Number() is 0 and String() is "", unlike their undefined-argument forms; the
6
7
  // other coercers match native through the undefined-argument path below.
7
8
  if (args.length === 0) {
@@ -11,20 +12,22 @@ const coerce = (ctx, name, args) => {
11
12
  return "";
12
13
  }
13
14
  const raw = args[0];
14
- if (name === "Number")
15
- return coerceToNumber(raw);
16
15
  if (name === "Boolean")
17
16
  return Boolean(raw);
18
- if (name === "isFinite")
19
- return Number.isFinite(coerceToNumber(raw));
20
- if (name === "isNaN")
21
- return Number.isNaN(coerceToNumber(raw));
22
17
  if (name === "parseInt") {
23
- return parseInt(coerceToString(raw), coerceToNumber(args[1]));
18
+ return withPrimitives(ctx, ["string", "number"], [raw, args[1]], ([text, radix]) => parseInt(coerceToString(text), coerceToNumber(radix)));
24
19
  }
25
- if (name === "parseFloat")
26
- return parseFloat(coerceToString(raw));
27
- return coerceToString(raw);
20
+ return withPrimitives(ctx, name === "String" || name === "parseFloat" ? "string" : "number", [raw], ([value]) => {
21
+ if (name === "Number")
22
+ return coerceToNumber(value);
23
+ if (name === "isFinite")
24
+ return Number.isFinite(coerceToNumber(value));
25
+ if (name === "isNaN")
26
+ return Number.isNaN(coerceToNumber(value));
27
+ if (name === "parseFloat")
28
+ return parseFloat(coerceToString(value));
29
+ return coerceToString(value);
30
+ });
28
31
  };
29
32
  /** A global coercion function such as `Number` or `parseInt`. */
30
33
  export const coercion = (ctx, name, length = 1) => fn(ctx.builtins, name, length, (_, args) => coerce(ctx, name, args));
@@ -1,6 +1,6 @@
1
1
  import { fn, methods } from "../interpreter/native.js";
2
2
  import { typeError } from "../interpreter/model.js";
3
- import { define, entries, get, getOwn, hasOwn, hidden, isRuntimeReference, Arr, Bytes, DateObj, ErrorObj, MapObj, Obj, RegExpObj, SetObj, coerceToString, } from "../interpreter/objects.js";
3
+ import { define, entries, get, getOwn, hasOwn, hidden, isRuntimeReference, Arr, Bytes, DateObj, ErrorObj, MapObj, Obj, RegExpObj, SetObj, WeakMapObj, WeakSetObj, coerceToString, } from "../interpreter/objects.js";
4
4
  import { createErrorValue, isErrorType } from "../interpreter/intrinsics.js";
5
5
  import { describeValue } from "../interpreter/references.js";
6
6
  import { ToolReference } from "../tool-runtime.js";
@@ -67,8 +67,9 @@ export const structuredCloneGlobal = (ctx) => fn(ctx.builtins, "structuredClone"
67
67
  define(copy, "cause", clone(getOwn(value, "cause")), hidden);
68
68
  return copy;
69
69
  }
70
- if (isRuntimeReference(value))
70
+ if (isRuntimeReference(value) || value instanceof WeakMapObj || value instanceof WeakSetObj) {
71
71
  throw typeError(`DataCloneError: ${describeValue(value)} could not be cloned.`);
72
+ }
72
73
  const copy = remember(value instanceof Arr ? new Arr(builtins.Array, new Array(value.items.length)) : new Obj(builtins.Object));
73
74
  for (const [key, item] of entries(value))
74
75
  define(copy, key, clone(item));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode/codemode",
4
- "version": "2.0.16",
4
+ "version": "2.0.18",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",