@weborigami/language 0.2.7 → 0.2.8

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,6 +4,9 @@ import * as ops from "../runtime/ops.js";
4
4
 
5
5
  // Parser helpers
6
6
 
7
+ /** @typedef {import("../../index.ts").AnnotatedCode} AnnotatedCode */
8
+ /** @typedef {import("../../index.ts").AnnotatedCodeItem} AnnotatedCodeItem */
9
+ /** @typedef {import("../../index.ts").CodeLocation} CodeLocation */
7
10
  /** @typedef {import("../../index.ts").Code} Code */
8
11
 
9
12
  // Marker for a reference that may be a builtin or a scope reference
@@ -15,15 +18,16 @@ const builtinRegex = /^[A-Za-z][A-Za-z0-9]*$/;
15
18
  * If a parse result is an object that will be evaluated at runtime, attach the
16
19
  * location of the source code that produced it for debugging and error messages.
17
20
  *
18
- * @param {Code} code
19
- * @param {any} location
21
+ * @param {Code[]} code
22
+ * @param {CodeLocation} location
20
23
  */
21
24
  export function annotate(code, location) {
22
- if (typeof code === "object" && code !== null && location) {
23
- code.location = location;
24
- code.source = codeFragment(location);
25
- }
26
- return code;
25
+ /** @type {AnnotatedCode} */
26
+ // @ts-ignore - Need to add annotation below before type is correct
27
+ const annotated = code.slice();
28
+ annotated.location = location;
29
+ annotated.source = codeFragment(location);
30
+ return annotated;
27
31
  }
28
32
 
29
33
  /**
@@ -31,7 +35,7 @@ export function annotate(code, location) {
31
35
  * Rewrite any [ops.scope, key] calls to be [ops.inherited, key] to avoid
32
36
  * infinite recursion.
33
37
  *
34
- * @param {Code} code
38
+ * @param {AnnotatedCode} code
35
39
  * @param {string} key
36
40
  */
37
41
  function avoidRecursivePropertyCalls(code, key) {
@@ -45,45 +49,49 @@ function avoidRecursivePropertyCalls(code, key) {
45
49
  trailingSlash.remove(code[1]) === trailingSlash.remove(key)
46
50
  ) {
47
51
  // Rewrite to avoid recursion
48
- // @ts-ignore
49
52
  modified = [ops.inherited, code[1]];
50
53
  } else if (code[0] === ops.lambda && code[1].includes(key)) {
51
54
  // Lambda that defines the key; don't rewrite
52
55
  return code;
53
56
  } else {
54
57
  // Process any nested code
55
- // @ts-ignore
56
58
  modified = code.map((value) => avoidRecursivePropertyCalls(value, key));
57
59
  }
58
- annotate(modified, code.location);
59
- return modified;
60
+ return annotate(modified, code.location);
60
61
  }
61
62
 
62
63
  /**
63
64
  * Downgrade a potential builtin reference to a scope reference.
64
65
  *
65
- * @param {Code} code
66
+ * @param {AnnotatedCode} code
66
67
  */
67
68
  export function downgradeReference(code) {
68
69
  if (code && code.length === 2 && code[0] === undetermined) {
69
- /** @type {Code} */
70
- // @ts-ignore
71
- const result = [ops.scope, code[1]];
72
- annotate(result, code.location);
73
- return result;
70
+ return annotate([ops.scope, code[1]], code.location);
74
71
  } else {
75
72
  return code;
76
73
  }
77
74
  }
78
75
 
79
- export function makeArray(entries) {
76
+ /**
77
+ * Create an array
78
+ *
79
+ * @param {AnnotatedCode[]} entries
80
+ * @param {CodeLocation} location
81
+ */
82
+ export function makeArray(entries, location) {
80
83
  let currentEntries = [];
81
84
  const spreads = [];
82
85
 
83
86
  for (const value of entries) {
84
87
  if (Array.isArray(value) && value[0] === ops.spread) {
85
88
  if (currentEntries.length > 0) {
86
- spreads.push([ops.array, ...currentEntries]);
89
+ const location = { ...currentEntries[0].location };
90
+ location.end = currentEntries[currentEntries.length - 1].location.end;
91
+ /** @type {AnnotatedCodeItem} */
92
+ const fn = ops.array;
93
+ const spread = annotate([fn, ...currentEntries], location);
94
+ spreads.push(spread);
87
95
  currentEntries = [];
88
96
  }
89
97
  spreads.push(...value.slice(1));
@@ -98,21 +106,24 @@ export function makeArray(entries) {
98
106
  currentEntries = [];
99
107
  }
100
108
 
109
+ let result;
101
110
  if (spreads.length > 1) {
102
- return [ops.merge, ...spreads];
103
- }
104
- if (spreads.length === 1) {
105
- return spreads[0];
111
+ result = [ops.merge, ...spreads];
112
+ } else if (spreads.length === 1) {
113
+ result = spreads[0];
106
114
  } else {
107
- return [ops.array];
115
+ result = [ops.array];
108
116
  }
117
+
118
+ return annotate(result, location);
109
119
  }
110
120
 
111
121
  /**
112
122
  * Create a chain of binary operators. The head is the first value, and the tail
113
- * is an array of [operator, value] pairs.
123
+ * is a [operator, value] pair as an array.
114
124
  *
115
- * @param {Code} left
125
+ * @param {AnnotatedCode} left
126
+ * @param {[token: any, right: AnnotatedCode]} tail
116
127
  */
117
128
  export function makeBinaryOperation(left, [operatorToken, right]) {
118
129
  const operators = {
@@ -139,20 +150,19 @@ export function makeBinaryOperation(left, [operatorToken, right]) {
139
150
  };
140
151
  const op = operators[operatorToken];
141
152
 
142
- /** @type {Code} */
143
- // @ts-ignore
144
- const value = [op, left, right];
145
- value.location = {
153
+ const location = {
146
154
  source: left.location.source,
147
155
  start: left.location.start,
148
156
  end: right.location.end,
149
157
  };
150
158
 
151
- return value;
159
+ return annotate([op, left, right], location);
152
160
  }
153
161
 
154
162
  /**
155
- * @param {Code} target
163
+ * Create a function call.
164
+ *
165
+ * @param {AnnotatedCode} target
156
166
  * @param {any[]} args
157
167
  */
158
168
  export function makeCall(target, args) {
@@ -162,10 +172,6 @@ export function makeCall(target, args) {
162
172
  throw error;
163
173
  }
164
174
 
165
- const source = target.location.source;
166
- let start = target.location.start;
167
- let end = target.location.end;
168
-
169
175
  let fnCall;
170
176
  if (args[0] === ops.traverse) {
171
177
  let tree = target;
@@ -196,18 +202,21 @@ export function makeCall(target, args) {
196
202
  }
197
203
 
198
204
  // Create a location spanning the newly-constructed function call.
205
+ const location = { ...target.location };
199
206
  if (args instanceof Array) {
200
- // @ts-ignore
201
- end = args.location?.end ?? args.at(-1)?.location?.end;
207
+ let end;
208
+ if ("location" in args) {
209
+ end = /** @type {any} */ (args).location.end;
210
+ } else if ("location" in args.at(-1)) {
211
+ end = args.at(-1).location.end;
212
+ }
202
213
  if (end === undefined) {
203
214
  throw "Internal parser error: no location for function call argument";
204
215
  }
216
+ location.end = end;
205
217
  }
206
218
 
207
- // @ts-ignore
208
- annotate(fnCall, { start, source, end });
209
-
210
- return fnCall;
219
+ return annotate(fnCall, location);
211
220
  }
212
221
 
213
222
  /**
@@ -215,25 +224,32 @@ export function makeCall(target, args) {
215
224
  * the arguments until the function is called. Exception: if the argument is a
216
225
  * literal, we leave it alone.
217
226
  *
218
- * @param {any[]} args
227
+ * @param {AnnotatedCode[]} args
219
228
  */
220
229
  export function makeDeferredArguments(args) {
221
230
  return args.map((arg) => {
222
231
  if (arg instanceof Array && arg[0] === ops.literal) {
223
232
  return arg;
224
233
  }
225
- const fn = [ops.lambda, [], arg];
226
- // @ts-ignore
227
- annotate(fn, arg.location);
228
- return fn;
234
+ const lambdaParameters = annotate([], arg.location);
235
+ /** @type {AnnotatedCodeItem} */
236
+ const fn = [ops.lambda, lambdaParameters, arg];
237
+ return annotate(fn, arg.location);
229
238
  });
230
239
  }
231
240
 
232
- export function makeObject(entries, op) {
241
+ /**
242
+ * Make an object.
243
+ *
244
+ * @param {AnnotatedCode[]} entries
245
+ * @param {CodeLocation} location
246
+ */
247
+ export function makeObject(entries, location) {
233
248
  let currentEntries = [];
234
249
  const spreads = [];
235
250
 
236
- for (let [key, value] of entries) {
251
+ for (let entry of entries) {
252
+ const [key, value] = entry;
237
253
  if (key === ops.spread) {
238
254
  if (value[0] === ops.object) {
239
255
  // Spread of an object; fold into current object
@@ -241,7 +257,10 @@ export function makeObject(entries, op) {
241
257
  } else {
242
258
  // Spread of a tree; accumulate
243
259
  if (currentEntries.length > 0) {
244
- spreads.push([op, ...currentEntries]);
260
+ const location = { ...currentEntries[0].location };
261
+ location.end = currentEntries[currentEntries.length - 1].location.end;
262
+ const spread = annotate([ops.object, ...currentEntries], location);
263
+ spreads.push(spread);
245
264
  currentEntries = [];
246
265
  }
247
266
  spreads.push(value);
@@ -256,39 +275,50 @@ export function makeObject(entries, op) {
256
275
  value[1][0] === ops.literal
257
276
  ) {
258
277
  // Optimize a getter for a primitive value to a regular property
259
- value = value[1];
278
+ entry = annotate([key, value[1]], entry.location);
260
279
  }
261
280
  }
262
281
 
263
- currentEntries.push([key, value]);
282
+ currentEntries.push(entry);
264
283
  }
265
284
 
266
285
  // Finish any current entries.
267
286
  if (currentEntries.length > 0) {
268
- spreads.push([op, ...currentEntries]);
287
+ const location = { ...currentEntries[0].location };
288
+ location.end = currentEntries[currentEntries.length - 1].location.end;
289
+ const spread = annotate([ops.object, ...currentEntries], location);
290
+ spreads.push(spread);
269
291
  currentEntries = [];
270
292
  }
271
293
 
294
+ let code;
272
295
  if (spreads.length > 1) {
273
- return [ops.merge, ...spreads];
274
- }
275
- if (spreads.length === 1) {
276
- return spreads[0];
296
+ // Merge multiple spreads
297
+ code = [ops.merge, ...spreads];
298
+ } else if (spreads.length === 1) {
299
+ // A single spread can just be the object
300
+ code = spreads[0];
277
301
  } else {
278
- return [op];
302
+ // Empty object
303
+ code = [ops.object];
279
304
  }
305
+
306
+ return annotate(code, location);
280
307
  }
281
308
 
282
- // Similar to a function call, but the order is reversed.
309
+ /**
310
+ * Make a pipline: similar to a function call, but the order is reversed.
311
+ *
312
+ * @param {AnnotatedCode} arg
313
+ * @param {AnnotatedCode} fn
314
+ */
283
315
  export function makePipeline(arg, fn) {
284
316
  const upgraded = upgradeReference(fn);
285
317
  const result = makeCall(upgraded, [arg]);
286
318
  const source = fn.location.source;
287
319
  let start = arg.location.start;
288
320
  let end = fn.location.end;
289
- // @ts-ignore
290
- annotate(result, { start, source, end });
291
- return result;
321
+ return annotate(result, { start, source, end });
292
322
  }
293
323
 
294
324
  // Define a property on an object.
@@ -312,38 +342,55 @@ export function makeReference(identifier) {
312
342
  return [op, identifier];
313
343
  }
314
344
 
315
- export function makeTemplate(op, head, tail) {
316
- const strings = [head];
345
+ /**
346
+ * Make a template
347
+ *
348
+ * @param {any} op
349
+ * @param {AnnotatedCode} head
350
+ * @param {AnnotatedCode} tail
351
+ * @param {CodeLocation} location
352
+ */
353
+ export function makeTemplate(op, head, tail, location) {
354
+ const strings = [head[1]];
317
355
  const values = [];
318
- for (const [value, string] of tail) {
319
- values.push([ops.concat, value]);
320
- strings.push(string);
356
+ for (const [value, literal] of tail) {
357
+ const concat = annotate([ops.concat, value], value.location);
358
+ values.push(concat);
359
+ strings.push(literal[1]);
321
360
  }
322
- return [op, [ops.literal, strings], ...values];
361
+ const stringsCode = annotate(strings, location);
362
+ /** @type {AnnotatedCodeItem} */
363
+ const fn = ops.literal;
364
+ const literalCode = annotate([fn, stringsCode], location);
365
+ return annotate([op, literalCode, ...values], location);
323
366
  }
324
367
 
325
- export function makeUnaryOperation(operator, value) {
368
+ /**
369
+ * Make a unary operation.
370
+ *
371
+ * @param {AnnotatedCode} operator
372
+ * @param {AnnotatedCode} value
373
+ * @param {CodeLocation} location
374
+ */
375
+ export function makeUnaryOperation(operator, value, location) {
326
376
  const operators = {
327
377
  "!": ops.logicalNot,
328
378
  "+": ops.unaryPlus,
329
379
  "-": ops.unaryMinus,
330
380
  "~": ops.bitwiseNot,
331
381
  };
332
- return [operators[operator], value];
382
+ return annotate([operators[operator], value], location);
333
383
  }
334
384
 
335
385
  /**
336
386
  * Upgrade a potential builtin reference to an actual builtin reference.
337
387
  *
338
- * @param {Code} code
388
+ * @param {AnnotatedCode} code
339
389
  */
340
390
  export function upgradeReference(code) {
341
391
  if (code.length === 2 && code[0] === undetermined) {
342
- /** @type {Code} */
343
- // @ts-ignore
344
392
  const result = [ops.builtin, code[1]];
345
- annotate(result, code.location);
346
- return result;
393
+ return annotate(result, code.location);
347
394
  } else {
348
395
  return code;
349
396
  }
@@ -79,13 +79,16 @@ export function formatError(error) {
79
79
  // Add location
80
80
  if (location) {
81
81
  let { source, start } = location;
82
+ // Adjust line number with offset if present (for example, if the code is in
83
+ // an Origami template document with front matter that was stripped)
84
+ let line = start.line + (source.offset ?? 0);
82
85
  if (!fragmentInMessage) {
83
86
  message += `\nevaluating: ${fragment}`;
84
87
  }
85
88
  if (typeof source === "object" && source.url) {
86
- message += `\n at ${source.url.href}:${start.line}:${start.column}`;
89
+ message += `\n at ${source.url.href}:${line}:${start.column}`;
87
90
  } else if (source.text.includes("\n")) {
88
- message += `\n at line ${start.line}, column ${start.column}`;
91
+ message += `\n at line ${line}, column ${start.column}`;
89
92
  }
90
93
  }
91
94
 
@@ -9,7 +9,7 @@ import { codeSymbol, scopeSymbol, sourceSymbol } from "./symbols.js";
9
9
  * `this` should be the tree used as the context for the evaluation.
10
10
  *
11
11
  * @this {import("@weborigami/types").AsyncTree|null}
12
- * @param {import("../../index.ts").Code} code
12
+ * @param {import("../../index.ts").AnnotatedCode} code
13
13
  */
14
14
  export default async function evaluate(code) {
15
15
  const tree = this;
@@ -54,15 +54,6 @@ export default async function evaluate(code) {
54
54
  fn = await fn.unpack();
55
55
  }
56
56
 
57
- if (!Tree.isTreelike(fn)) {
58
- const text = fn.toString?.() ?? codeFragment(code[0].location);
59
- const error = new TypeError(
60
- `Not a callable function or tree: ${text.slice(0, 80)}`
61
- );
62
- /** @type {any} */ (error).location = code.location;
63
- throw error;
64
- }
65
-
66
57
  // Execute the function or traverse the tree.
67
58
  let result;
68
59
  try {
@@ -5,7 +5,7 @@ import { evaluate } from "./internal.js";
5
5
  /**
6
6
  * Given parsed Origami code, return a function that executes that code.
7
7
  *
8
- * @param {import("../../index.js").Code} code - parsed Origami expression
8
+ * @param {import("../../index.js").AnnotatedCode} code - parsed Origami expression
9
9
  * @param {string} [name] - optional name of the function
10
10
  */
11
11
  export function createExpressionFunction(code, name) {
@@ -1,7 +1,8 @@
1
1
  /**
2
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
2
+ * @typedef {import("../../index.ts").AnnotatedCode} AnnotatedCode
3
3
  * @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
4
4
  * @typedef {import("@weborigami/async-tree").Treelike} Treelike
5
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
5
6
  */
6
7
 
7
8
  import {
@@ -83,6 +84,7 @@ export async function builtin(key) {
83
84
 
84
85
  return value;
85
86
  }
87
+ addOpLabel(builtin, "«ops.builtin»");
86
88
 
87
89
  /**
88
90
  * JavaScript comma operator, returns the last argument.
@@ -107,7 +109,8 @@ export async function concat(...args) {
107
109
  addOpLabel(concat, "«ops.concat»");
108
110
 
109
111
  export async function conditional(condition, truthy, falsy) {
110
- return condition ? truthy() : falsy();
112
+ const value = condition ? truthy : falsy;
113
+ return value instanceof Function ? await value() : value;
111
114
  }
112
115
 
113
116
  export function division(a, b) {
@@ -154,6 +157,7 @@ export async function external(path, code, cache) {
154
157
 
155
158
  return value;
156
159
  }
160
+ addOpLabel(external, "«ops.external»");
157
161
 
158
162
  /**
159
163
  * This op is only used during parsing. It signals to ops.object that the
@@ -181,6 +185,7 @@ export async function homeDirectory() {
181
185
  tree.parent = this ? Tree.root(this) : null;
182
186
  return tree;
183
187
  }
188
+ addOpLabel(homeDirectory, "«ops.homeDirectory»");
184
189
 
185
190
  /**
186
191
  * Search the parent's scope -- i.e., exclude the current tree -- for the given
@@ -201,10 +206,9 @@ addOpLabel(inherited, "«ops.inherited»");
201
206
  /**
202
207
  * Return a function that will invoke the given code.
203
208
  *
204
- * @typedef {import("../../index.ts").Code} Code
205
209
  * @this {AsyncTree|null}
206
210
  * @param {string[]} parameters
207
- * @param {Code} code
211
+ * @param {AnnotatedCode} code
208
212
  */
209
213
  export function lambda(parameters, code) {
210
214
  const context = this;
@@ -271,6 +275,9 @@ addOpLabel(lessThanOrEqual, "«ops.lessThanOrEqual»");
271
275
 
272
276
  /**
273
277
  * Return a primitive value
278
+ *
279
+ * This op is optimized away during compilation, with the exception of array
280
+ * literals.
274
281
  */
275
282
  export async function literal(value) {
276
283
  return value;
@@ -296,6 +303,7 @@ export async function logicalAnd(head, ...tail) {
296
303
  // Return the last value (not `true`)
297
304
  return lastValue;
298
305
  }
306
+ addOpLabel(logicalAnd, "«ops.logicalAnd»");
299
307
 
300
308
  /**
301
309
  * Logical NOT operator
@@ -303,6 +311,7 @@ export async function logicalAnd(head, ...tail) {
303
311
  export async function logicalNot(value) {
304
312
  return !value;
305
313
  }
314
+ addOpLabel(logicalNot, "«ops.logicalNot»");
306
315
 
307
316
  /**
308
317
  * Logical OR operator
@@ -323,12 +332,13 @@ export async function logicalOr(head, ...tail) {
323
332
 
324
333
  return lastValue;
325
334
  }
335
+ addOpLabel(logicalOr, "«ops.logicalOr»");
326
336
 
327
337
  /**
328
338
  * Merge the given trees. If they are all plain objects, return a plain object.
329
339
  *
330
340
  * @this {AsyncTree|null}
331
- * @param {Code[]} codes
341
+ * @param {AnnotatedCode[]} codes
332
342
  */
333
343
  export async function merge(...codes) {
334
344
  // First pass: evaluate the direct property entries in a single object
@@ -400,6 +410,7 @@ export async function nullishCoalescing(head, ...tail) {
400
410
 
401
411
  return lastValue;
402
412
  }
413
+ addOpLabel(nullishCoalescing, "«ops.nullishCoalescing»");
403
414
 
404
415
  /**
405
416
  * Construct an object. The keys will be the same as the given `obj`
@@ -432,6 +443,7 @@ export async function rootDirectory(key) {
432
443
  tree.parent = this ? Tree.root(this) : null;
433
444
  return key ? tree.get(key) : tree;
434
445
  }
446
+ addOpLabel(rootDirectory, "«ops.rootDirectory»");
435
447
 
436
448
  /**
437
449
  * Look up the given key in the scope for the current tree.
@@ -527,3 +539,4 @@ addOpLabel(unaryPlus, "«ops.unaryPlus»");
527
539
  export async function unpack(value) {
528
540
  return isUnpackable(value) ? value.unpack() : value;
529
541
  }
542
+ addOpLabel(unpack, "«ops.unpack»");
@@ -1,4 +1,28 @@
1
1
  import { isPlainObject } from "@weborigami/async-tree";
2
+ import assert from "node:assert";
3
+
4
+ export function assertCodeEqual(actual, expected) {
5
+ const actualStripped = stripCodeLocations(actual);
6
+ const expectedStripped = stripCodeLocations(expected);
7
+ assert.deepEqual(actualStripped, expectedStripped);
8
+ }
9
+
10
+ /**
11
+ * Adds a fake source to code.
12
+ *
13
+ * @returns {import("../../index.ts").AnnotatedCode}
14
+ */
15
+ export function createCode(array) {
16
+ const code = array;
17
+ /** @type {any} */ (code).location = {
18
+ end: 0,
19
+ source: {
20
+ text: "",
21
+ },
22
+ start: 0,
23
+ };
24
+ return code;
25
+ }
2
26
 
3
27
  // For comparison purposes, strip the `location` property added by the parser.
4
28
  export function stripCodeLocations(parseResult) {
@@ -3,7 +3,7 @@ import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
4
  import * as compile from "../../src/compiler/compile.js";
5
5
  import { ops } from "../../src/runtime/internal.js";
6
- import { stripCodeLocations } from "./stripCodeLocations.js";
6
+ import { assertCodeEqual } from "./codeHelpers.js";
7
7
 
8
8
  const shared = new ObjectTree({
9
9
  greet: (name) => `Hello, ${name}!`,
@@ -70,7 +70,7 @@ describe("compile", () => {
70
70
  let saved;
71
71
  const scope = new ObjectTree({
72
72
  tag: (strings, ...values) => {
73
- assert.deepEqual(strings, ["Hello, ", "!"]);
73
+ assertCodeEqual(strings, ["Hello, ", "!"]);
74
74
  if (saved) {
75
75
  assert.equal(strings, saved);
76
76
  } else {
@@ -96,7 +96,7 @@ describe("compile", () => {
96
96
  },
97
97
  });
98
98
  const code = fn.code;
99
- assert.deepEqual(stripCodeLocations(code), [ops.object, ["a", literal]]);
99
+ assertCodeEqual(code, [ops.object, ["a", 1]]);
100
100
  });
101
101
  });
102
102
 
@@ -1,9 +1,8 @@
1
- import assert from "node:assert";
2
1
  import { describe, test } from "node:test";
3
2
  import * as compile from "../../src/compiler/compile.js";
4
3
  import optimize from "../../src/compiler/optimize.js";
5
4
  import { ops } from "../../src/runtime/internal.js";
6
- import { stripCodeLocations } from "./stripCodeLocations.js";
5
+ import { assertCodeEqual, createCode } from "./codeHelpers.js";
7
6
 
8
7
  describe("optimize", () => {
9
8
  test("optimize non-local ops.scope calls to ops.external", async () => {
@@ -17,12 +16,12 @@ describe("optimize", () => {
17
16
  `;
18
17
  const fn = compile.expression(expression);
19
18
  const code = fn.code;
20
- assert.deepEqual(stripCodeLocations(code), [
19
+ assertCodeEqual(code, [
21
20
  ops.lambda,
22
21
  ["name"],
23
22
  [
24
23
  ops.object,
25
- ["a", [ops.literal, 1]],
24
+ ["a", 1],
26
25
  ["b", [ops.scope, "a"]],
27
26
  ["c", [ops.external, "elsewhere", [ops.scope, "elsewhere"], {}]],
28
27
  ["d", [ops.scope, "name"]],
@@ -32,13 +31,12 @@ describe("optimize", () => {
32
31
 
33
32
  test("optimize scope traversals with all literal keys", async () => {
34
33
  // Compilation of `x/y.js`
35
- const code = [ops.traverse, [ops.scope, "x/"], [ops.literal, "y.js"]];
36
- const optimized = optimize(code);
37
- assert.deepEqual(stripCodeLocations(optimized), [
38
- ops.external,
39
- "x/y.js",
40
- code,
41
- {},
34
+ const code = createCode([
35
+ ops.traverse,
36
+ [ops.scope, "x/"],
37
+ [ops.literal, "y.js"],
42
38
  ]);
39
+ const optimized = optimize(code);
40
+ assertCodeEqual(optimized, [ops.external, "x/y.js", code, {}]);
43
41
  });
44
42
  });