@opencode/codemode 2.0.13 → 2.0.15

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.
@@ -1,9 +1,9 @@
1
- import type { Program } from "acorn";
1
+ import type { Program, TaggedTemplateExpression } from "acorn";
2
2
  import { Effect } from "effect";
3
3
  import { type Json } from "../data.js";
4
4
  import { type ToolRuntime } from "../tool-runtime.js";
5
5
  import { type Builtins } from "./intrinsics.js";
6
- import { type Cursor, PromiseObj, type Value } from "./objects.js";
6
+ import { type Cursor, Arr, PromiseObj, type Value } from "./objects.js";
7
7
  import { Pending } from "./promises.js";
8
8
  /** One program execution: the tool bridge, promise scheduler, captured logs, and the global scope built once. */
9
9
  export declare class Interpreter<R> {
@@ -11,6 +11,8 @@ export declare class Interpreter<R> {
11
11
  readonly pending: Pending<R>;
12
12
  readonly builtins: Builtins;
13
13
  readonly logs: Array<string>;
14
+ /** Template objects by site: a tag sees the same `strings` array every time its literal is evaluated, as in JS. */
15
+ readonly templates: WeakMap<TaggedTemplateExpression, Arr>;
14
16
  private readonly root;
15
17
  constructor(options: {
16
18
  readonly tools: ToolRuntime<R>;
@@ -6,7 +6,7 @@ import { checkStringLength } from "./limits.js";
6
6
  import { locate, materialize } from "./errors.js";
7
7
  import { primitivePrototype } from "./intrinsics.js";
8
8
  import { globals } from "./globals.js";
9
- import { assign, Callable, define, get, hostCursor, IteratorObj, has, hidden, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
9
+ import { assign, Callable, define, frozen, get, hostCursor, IteratorObj, has, hidden, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
10
10
  import { preserveConsumerError } from "./callback.js";
11
11
  import { Pending, resolvePromise, resolvePromiseValue } from "./promises.js";
12
12
  import { containsOpaqueReference, describeValue, rejectCircularInsertion, typeofValue } from "./references.js";
@@ -163,6 +163,8 @@ export class Interpreter {
163
163
  pending;
164
164
  builtins;
165
165
  logs;
166
+ /** Template objects by site: a tag sees the same `strings` array every time its literal is evaluated, as in JS. */
167
+ templates = new WeakMap();
166
168
  root;
167
169
  constructor(options) {
168
170
  this.tools = options.tools;
@@ -715,10 +717,10 @@ class Frame {
715
717
  self.predeclarePattern(declared.pattern, declared.mutable, left);
716
718
  const right = yield* self.evaluateExpression(node.right);
717
719
  const keys = self.enumerableKeys(right, node.right);
718
- if (left.type !== "Identifier" && left.type !== "VariableDeclaration") {
720
+ if (left.type === "RestElement" || left.type === "AssignmentPattern") {
719
721
  throw typeError("Unsupported for...in binding.", left);
720
722
  }
721
- const assignmentName = left.type === "Identifier" ? left.name : undefined;
723
+ const assignment = left.type === "VariableDeclaration" ? undefined : left;
722
724
  for (const key of keys) {
723
725
  const result = yield* Effect.gen(function* () {
724
726
  if (declared?.lexical) {
@@ -729,8 +731,8 @@ class Frame {
729
731
  else if (declared) {
730
732
  yield* self.assignPattern(declared.pattern, key, left);
731
733
  }
732
- else if (assignmentName) {
733
- self.scopes.set(assignmentName, key, left);
734
+ else if (assignment) {
735
+ yield* self.assignPattern(assignment, key, left);
734
736
  }
735
737
  return yield* self.evaluateStatement(node.body);
736
738
  }).pipe(Effect.ensuring(Effect.sync(() => {
@@ -1016,6 +1018,8 @@ class Frame {
1016
1018
  return this.evaluateArrayExpression(node);
1017
1019
  case "TemplateLiteral":
1018
1020
  return this.evaluateTemplateLiteral(node);
1021
+ case "TaggedTemplateExpression":
1022
+ return this.evaluateTaggedTemplate(node);
1019
1023
  case "ConditionalExpression":
1020
1024
  return this.evaluateConditionalExpression(node);
1021
1025
  case "UpdateExpression":
@@ -1366,12 +1370,16 @@ class Frame {
1366
1370
  paramScope.set(name, { mutable: true, value: undefined, initialized: false });
1367
1371
  }
1368
1372
  }
1373
+ const parameters = fn.parameters.map((parameter) => parameter.type === "Identifier" ? parameter.name : undefined);
1369
1374
  const bind = Effect.gen(function* () {
1370
1375
  for (const [index, parameter] of fn.parameters.entries()) {
1371
1376
  if (parameter.type === "RestElement") {
1372
1377
  yield* invocation.declarePattern(parameter.argument, new Arr(self.ctx.builtins.Array, args.slice(index)), true, parameter, true);
1373
1378
  break;
1374
1379
  }
1380
+ // A sloppy simple parameter list may repeat a name; the last occurrence wins, as in JS.
1381
+ if (parameter.type === "Identifier" && parameters.lastIndexOf(parameter.name) !== index)
1382
+ continue;
1375
1383
  yield* invocation.declarePattern(parameter, args[index], true, parameter, true);
1376
1384
  }
1377
1385
  });
@@ -1666,7 +1674,7 @@ class Frame {
1666
1674
  return Effect.gen(function* () {
1667
1675
  for (let index = 0; index < quasis.length; index += 1) {
1668
1676
  const quasi = quasis[index];
1669
- // acorn only omits `cooked` for invalid escapes in tagged templates, which are unsupported.
1677
+ // acorn only omits `cooked` for invalid escapes, which are a parse error outside a tagged template.
1670
1678
  if (typeof quasi.value.cooked !== "string") {
1671
1679
  throw typeError("Invalid template literal quasi.", quasi);
1672
1680
  }
@@ -1681,6 +1689,27 @@ class Frame {
1681
1689
  return output;
1682
1690
  });
1683
1691
  }
1692
+ // `tag\`a${x}b\`` is `tag(strings, x)`: the tag is read like a callee (a member keeps its receiver), the
1693
+ // substitutions are evaluated in order, and `strings` carries the escaped source as `strings.raw`.
1694
+ evaluateTaggedTemplate(node) {
1695
+ const self = this;
1696
+ return Effect.gen(function* () {
1697
+ const { callable, thisValue } = node.tag.type === "MemberExpression"
1698
+ ? yield* self.readMethod(node.tag)
1699
+ : { callable: yield* self.evaluateExpression(node.tag), thisValue: undefined };
1700
+ const strings = self.ctx.templates.get(node) ?? self.createTemplateObject(node);
1701
+ const values = yield* Effect.forEach(node.quasi.expressions, (expression) => self.evaluateExpression(expression));
1702
+ return yield* self.call(callable, thisValue, [strings, ...values], node, node.tag);
1703
+ });
1704
+ }
1705
+ createTemplateObject(node) {
1706
+ const array = this.ctx.builtins.Array;
1707
+ // An invalid escape such as `\unicode` cooks to `undefined` and survives only in `raw`.
1708
+ const strings = new Arr(array, node.quasi.quasis.map((quasi) => quasi.value.cooked ?? undefined));
1709
+ define(strings, "raw", new Arr(array, node.quasi.quasis.map((quasi) => quasi.value.raw)), frozen);
1710
+ this.ctx.templates.set(node, strings);
1711
+ return strings;
1712
+ }
1684
1713
  evaluateConditionalExpression(node) {
1685
1714
  return Effect.flatMap(this.evaluateExpression(node.test), (test) => this.evaluateExpression(test ? node.consequent : node.alternate));
1686
1715
  }
@@ -62,7 +62,7 @@ export declare const rangeError: (message: string, node?: AstNode) => PendingThr
62
62
  export declare const referenceError: (message: string, node?: AstNode) => PendingThrow;
63
63
  export declare const syntaxError: (message: string, node?: AstNode) => PendingThrow;
64
64
  export declare const uriError: (message: string, node?: AstNode) => PendingThrow;
65
- export declare const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, tagged templates, BigInt, and custom Symbols. Use plain functions and data objects instead.";
65
+ export declare const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, BigInt, and custom Symbols. Use plain functions and data objects instead.";
66
66
  export declare const unsupportedSyntax: (kind: string, node: AstNode) => PendingThrow;
67
67
  export declare const sourceLocation: (node: AstNode) => {
68
68
  readonly line: number;
@@ -49,7 +49,7 @@ export const referenceError = failure("ReferenceError");
49
49
  export const syntaxError = failure("SyntaxError");
50
50
  export const uriError = failure("URIError");
51
51
  // Orient the agent rather than enumerate JavaScript; interpreter-support.md is the full matrix.
52
- export const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, tagged templates, BigInt, and custom Symbols. Use plain functions and data objects instead.";
52
+ export const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, BigInt, and custom Symbols. Use plain functions and data objects instead.";
53
53
  export const unsupportedSyntax = (kind, node) => new PendingThrow("SyntaxError", `Syntax '${kind}' is not supported. ${supportedSyntaxMessage}`, node, "UnsupportedSyntax", [supportedSyntaxMessage]);
54
54
  // Acorn lines are 1-based and its columns are 0-based. Diagnostics use 1-based columns of the submitted source.
55
55
  export const sourceLocation = (node) => ({
@@ -15,20 +15,16 @@ const flagProperties = [
15
15
  const regexFailureReason = (error) => (error instanceof Error ? error.message : String(error)).replace(/^Invalid regular expression:\s*/i, "");
16
16
  const escapeRegexHint = 'To match special characters like ( ) [ ] { } + * ? . literally, escape them with a backslash (e.g. "\\\\(") or test for them with String.includes instead.';
17
17
  export const toHostRegex = (arg, method, extraFlags = "") => {
18
- // Native parity: an undefined pattern behaves as an empty pattern.
19
- if (arg === undefined)
20
- return new RegExp("", extraFlags);
21
18
  if (arg instanceof RegExpObj)
22
19
  return arg.regex;
23
- if (typeof arg === "string") {
24
- try {
25
- return new RegExp(arg, extraFlags);
26
- }
27
- catch (error) {
28
- throw syntaxError(`String.${method} received the string ${JSON.stringify(arg)}, which is not a valid regular expression pattern (${regexFailureReason(error)}). ${escapeRegexHint}`);
29
- }
20
+ // Anything else is a pattern string, as `new RegExp(arg)` would read it: undefined is the empty pattern.
21
+ const source = arg === undefined ? "" : coerceToString(arg);
22
+ try {
23
+ return new RegExp(source, extraFlags);
24
+ }
25
+ catch (error) {
26
+ throw syntaxError(`String.${method} received the string ${JSON.stringify(source)}, which is not a valid regular expression pattern (${regexFailureReason(error)}). ${escapeRegexHint}`);
30
27
  }
31
- throw typeError(`String.${method} expects a regular expression (a /pattern/flags literal or new RegExp(...)) or a string pattern, not ${arg === null ? "null" : typeof arg}.`);
32
28
  };
33
29
  export const matchToValue = (builtins, match) => {
34
30
  const result = new Arr(builtins.Array, Array.from(match, (group) => group));
@@ -2,9 +2,9 @@ import { Effect } from "effect";
2
2
  import { constructor, fn, methods } from "../interpreter/native.js";
3
3
  import { checkArrayLength, checkStringLength } from "../interpreter/limits.js";
4
4
  import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
5
- import { define, hidden, Arr, hostIterator, RegExpObj, record, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
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 } from "../interpreter/callback.js";
7
+ import { applyCollectionCallback, isSupportedCallback, toPrimitiveString } 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.
@@ -78,6 +78,25 @@ export const stringGlobal = (ctx) => {
78
78
  methods(builtins, string, [
79
79
  codeUnits("fromCharCode", String.fromCharCode),
80
80
  codeUnits("fromCodePoint", String.fromCodePoint),
81
+ [
82
+ "raw",
83
+ 1,
84
+ (_, args) => {
85
+ const template = args[0];
86
+ const raw = template instanceof Obj ? get(template, "raw") : undefined;
87
+ if (!(raw instanceof Obj))
88
+ throw typeError("String.raw expects a template object with a raw array.");
89
+ const count = Math.max(0, coerceToInteger(get(raw, "length")));
90
+ checkArrayLength(count);
91
+ // Each literal is followed by its substitution, except the last literal, or when substitutions run out.
92
+ const parts = Array.from({ length: count }, (_, index) => index + 1 < count && index + 1 < args.length ? [get(raw, index), args[index + 1]] : [get(raw, index)]).flat();
93
+ return Effect.map(Effect.forEach(parts, (part) => toPrimitiveString(ctx, part)), (strings) => {
94
+ const output = strings.join("");
95
+ checkStringLength(output.length);
96
+ return output;
97
+ });
98
+ },
99
+ ],
81
100
  ]);
82
101
  const self = (thisValue, name) => {
83
102
  if (typeof thisValue === "string")
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.13",
4
+ "version": "2.0.15",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",