@weborigami/language 0.7.0-beta.2 → 0.7.0-beta.3

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.
Files changed (36) hide show
  1. package/main.js +1 -0
  2. package/package.json +2 -2
  3. package/src/compiler/compile.js +0 -3
  4. package/src/compiler/optimize.js +56 -23
  5. package/src/compiler/parserHelpers.js +11 -8
  6. package/src/handlers/getSource.js +1 -0
  7. package/src/handlers/ori_handler.js +2 -2
  8. package/src/handlers/oridocument_handler.js +2 -2
  9. package/src/handlers/{processOriExport.js → processOrigamiExport.js} +1 -1
  10. package/src/runtime/AsyncCacheTransform.d.ts +5 -1
  11. package/src/runtime/AsyncCacheTransform.js +22 -25
  12. package/src/runtime/OrigamiFileMap.js +3 -3
  13. package/src/runtime/SyncCacheTransform.d.ts +2 -1
  14. package/src/runtime/SyncCacheTransform.js +31 -34
  15. package/src/runtime/SystemCacheMap.js +28 -17
  16. package/src/runtime/counters.js +3 -0
  17. package/src/runtime/enableValueCaching.js +8 -7
  18. package/src/runtime/errors.js +33 -1
  19. package/src/runtime/execute.js +3 -0
  20. package/src/runtime/explainReferenceError.js +11 -24
  21. package/src/runtime/explainTraverseError.js +15 -6
  22. package/src/runtime/expressionObject.js +53 -34
  23. package/src/runtime/handleExtension.js +18 -9
  24. package/src/runtime/ops.js +4 -3
  25. package/test/compiler/compile.test.js +0 -1
  26. package/test/compiler/optimize.test.js +60 -15
  27. package/test/compiler/parse.test.js +38 -9
  28. package/test/runtime/AsyncCacheTransform.test.js +9 -4
  29. package/test/runtime/SyncCacheTransform.test.js +4 -0
  30. package/test/runtime/asyncCalcs.js +26 -4
  31. package/test/runtime/errors.test.js +4 -4
  32. package/test/runtime/expressionObject.test.js +23 -12
  33. package/test/runtime/handleExtension.test.js +0 -2
  34. package/test/runtime/ops.test.js +5 -3
  35. package/test/runtime/syncCalcs.js +26 -3
  36. package/test/runtime/systemCache.test.js +4 -0
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import codeFragment from "./codeFragment.js";
5
5
  import explainReferenceError from "./explainReferenceError.js";
6
6
  import explainTraverseError from "./explainTraverseError.js";
7
+ import { ops } from "./internal.js";
7
8
 
8
9
  // Text we look for in an error stack to guess whether a given line represents a
9
10
  // function in the Origami source code.
@@ -61,7 +62,8 @@ export async function formatError(error) {
61
62
  message += "\n" + explanation;
62
63
  }
63
64
  } else if (error instanceof TraverseError) {
64
- const explanation = await explainTraverseError(error);
65
+ // TraverseError explainer wants the full code
66
+ const explanation = await explainTraverseError(context.code, error);
65
67
  if (explanation) {
66
68
  message += "\n" + explanation;
67
69
  }
@@ -158,3 +160,33 @@ export function lineInfo(location) {
158
160
  export function maybeOrigamiSourceCode(text) {
159
161
  return origamiSourceSignals.some((signal) => text.includes(signal));
160
162
  }
163
+
164
+ // Return the keys traversed by the given code, or null if undetermined.
165
+ export function reconstructTraversalKeys(code) {
166
+ const [op, ...args] = code;
167
+
168
+ switch (op) {
169
+ case ops.cache:
170
+ return reconstructTraversalKeys(code[2]);
171
+
172
+ case ops.unpack:
173
+ return reconstructTraversalKeys(code[1]);
174
+ }
175
+
176
+ if (op instanceof Array) {
177
+ if (op[0] === ops.scope) {
178
+ // Simple scope reference
179
+ const key = args[0][1];
180
+ return [key];
181
+ } else if (op[0] === ops.cache) {
182
+ // Traversal starting with external scope reference
183
+ const head = op[2][1][1];
184
+ const tail = args.map((code) => code[1]);
185
+ const keys = [head, ...tail];
186
+ return keys;
187
+ }
188
+ }
189
+
190
+ // Can't determine keys
191
+ return null;
192
+ }
@@ -1,4 +1,5 @@
1
1
  import { isUnpackable, Tree } from "@weborigami/async-tree";
2
+ import counters from "../runtime/counters.js";
2
3
  import executionContext from "./executionContext.js";
3
4
  import "./interop.js";
4
5
 
@@ -14,6 +15,8 @@ import "./interop.js";
14
15
  * @param {RuntimeState} [state]
15
16
  */
16
17
  export default async function execute(code, state = {}) {
18
+ counters.executions++;
19
+
17
20
  if (!(code instanceof Array)) {
18
21
  // Simple scalar; return as is.
19
22
  return code;
@@ -4,6 +4,7 @@ import {
4
4
  trailingSlash,
5
5
  Tree,
6
6
  } from "@weborigami/async-tree";
7
+ import { reconstructTraversalKeys } from "./errors.js";
7
8
  import { ops } from "./internal.js";
8
9
  import { typos } from "./typos.js";
9
10
 
@@ -35,39 +36,25 @@ export default async function explainReferenceError(code, state) {
35
36
  return explanation;
36
37
  }
37
38
 
38
- // See if the code looks like an external scope reference that failed
39
- let key;
40
- if (code[0] === ops.cache) {
41
- // External scope reference
42
- let refCall = code[2];
43
- if (refCall[0] === ops.unpack) {
44
- // Unwrap implied unpack
45
- refCall = refCall[1];
46
- }
47
- const scopeArgs = refCall.slice(1); // get the scope reference arguments
48
- const keys = scopeArgs.map((part) => part[1]);
49
- const path = pathFromKeys(keys);
50
-
51
- if (keys.length > 1) {
52
- return `This path returned undefined: ${path}`;
53
- }
54
- key = keys[0];
55
- } else if (code[0]?.[0] === ops.scope) {
56
- // Simple scope reference
57
- key = code[1][1];
58
- } else {
59
- // Generic reference error, can't offer help
39
+ const keys = reconstructTraversalKeys(code);
40
+ if (!keys) {
60
41
  return null;
42
+ } else if (keys.length > 1) {
43
+ const path = pathFromKeys(keys);
44
+ return `This path returned undefined: ${path}`;
61
45
  }
62
46
 
63
- key = trailingSlash.remove(key);
47
+ // A single key caused the problem, try to explain it
48
+ let key = keys[0];
64
49
  if (typeof key !== "string") {
65
50
  return null;
66
51
  }
52
+ key = trailingSlash.remove(key);
67
53
 
68
- // Common case of a single key
54
+ // Baseline message
69
55
  let message = `"${key}" is not in scope.`;
70
56
 
57
+ // Various explainers can add suggestions
71
58
  const explainers = [
72
59
  mathExplainer,
73
60
  qualifiedReferenceExplainer,
@@ -6,6 +6,7 @@ import {
6
6
  TraverseError,
7
7
  Tree,
8
8
  } from "@weborigami/async-tree";
9
+ import { reconstructTraversalKeys } from "./errors.js";
9
10
  import { typos } from "./typos.js";
10
11
 
11
12
  /**
@@ -14,7 +15,7 @@ import { typos } from "./typos.js";
14
15
  *
15
16
  * @param {TraverseError} error
16
17
  */
17
- export default async function explainTraverseError(error) {
18
+ export default async function explainTraverseError(code, error) {
18
19
  const { lastValue, keys, position } = error;
19
20
  if (lastValue === undefined || keys === undefined || position === undefined) {
20
21
  // Don't have sufficient information; shouldn't happen
@@ -27,9 +28,15 @@ export default async function explainTraverseError(error) {
27
28
  }
28
29
 
29
30
  // The key that caused the error is the one before the current position
30
- const path = pathFromKeys(keys);
31
- const problemKey = keys[position - 1];
32
- let message = `Tried to traverse path: ${path}\nStopped unexpectedly at: ${problemKey}`;
31
+ const problemKey = error.keys[position - 1];
32
+ const reconstructedKeys = reconstructTraversalKeys(code);
33
+ let message;
34
+ if (reconstructedKeys === null) {
35
+ message = `Path traversal stopped unexpectedly at: ${problemKey}`;
36
+ } else {
37
+ const path = pathFromKeys(reconstructedKeys);
38
+ message = `Tried to traverse path: ${path}\nStopped unexpectedly at: ${problemKey}`;
39
+ }
33
40
 
34
41
  const key = trailingSlash.remove(keys[position - 1]);
35
42
 
@@ -50,10 +57,12 @@ export default async function explainTraverseError(error) {
50
57
  const normalizedKeys = lastValueKeys.map(trailingSlash.remove);
51
58
 
52
59
  const keyAsNumber = Number(key);
53
- if (!isNaN(keyAsNumber)) {
60
+ if (reconstructedKeys && !isNaN(keyAsNumber)) {
54
61
  // See if the string version of the key is present
55
62
  if (lastValueKeys.includes(keyAsNumber)) {
56
- const suggestedPath = `${pathFromKeys(keys.slice(0, position - 1))}(${keyAsNumber})`;
63
+ const suggestedKeys = reconstructedKeys?.slice(0);
64
+ suggestedKeys[position] = `(${keyAsNumber})`;
65
+ const suggestedPath = pathFromKeys(suggestedKeys);
57
66
  message += `\nSlash-separated keys are searched as strings. Here there's no string "${key}" key, but there is a number ${keyAsNumber} key.
58
67
  To get the value for that number key, use parentheses: ${suggestedPath}`;
59
68
  }
@@ -25,24 +25,34 @@ const VALUE_TYPE = {
25
25
  GETTER: 2, // Calculated on demand: `a = fn()`
26
26
  };
27
27
 
28
+ const mapUnattachedObjectToIndex = new Map();
29
+
28
30
  /**
29
31
  * Given an array of entries with string keys and Origami code values (arrays of
30
32
  * ops and operands), return an object with the same keys defining properties
31
33
  * whose getters evaluate the code.
32
-
34
+ *
35
+ * @param {string|null} cachePath
33
36
  * @param {*} entries
34
37
  * @param {import("../../index.ts").RuntimeState} [state]
35
38
  */
36
- export default async function expressionObject(entries, state = {}) {
39
+ export default async function expressionObject(cachePath, entries, state = {}) {
37
40
  // Create the object and set its parent
38
41
  const object = {};
39
42
  const parent = state?.object ?? null;
40
43
  if (parent !== null && !Tree.isMap(parent)) {
41
44
  throw new TypeError(`Parent must be a map or null`);
42
45
  }
43
-
44
46
  setParent(object, parent);
45
47
 
48
+ if (cachePath?.endsWith("/_objects/")) {
49
+ // Unattached object, add a unique index to the cache path
50
+ let index = mapUnattachedObjectToIndex.get(cachePath) ?? 0;
51
+ mapUnattachedObjectToIndex.set(cachePath, index + 1);
52
+ cachePath += index + "/";
53
+ }
54
+ object[cachePathSymbol] = cachePath;
55
+
46
56
  // The object in Map form for use on the stack
47
57
  const map = new ObjectMap(object);
48
58
 
@@ -93,8 +103,7 @@ export default async function expressionObject(entries, state = {}) {
93
103
  * Define a single property on the object
94
104
  */
95
105
  function defineProperty(object, propertyInfo, state, map) {
96
- const { globals } = state;
97
- let { enumerable, hasExtension, key, value, valueType } = propertyInfo;
106
+ let { enumerable, key, value, valueType } = propertyInfo;
98
107
  if (valueType == VALUE_TYPE.PRIMITIVE) {
99
108
  // Define simple property
100
109
  Object.defineProperty(object, key, {
@@ -109,43 +118,53 @@ function defineProperty(object, propertyInfo, state, map) {
109
118
  configurable: true,
110
119
  enumerable,
111
120
  get: async () => {
112
- // Execute the code to get the value of the property
113
- const propertyCachePath = getPropertyCachePath(object, key);
114
-
115
- const newState = Object.assign({}, state, { object: map });
116
- let result = propertyCachePath
117
- ? await systemCache.getOrInsertComputedAsync(propertyCachePath, () =>
118
- execute(value, newState),
119
- )
120
- : await execute(value, newState);
121
-
122
- if (hasExtension) {
123
- // Handle extension
124
- result = handleExtension(result, key, globals, map);
125
- }
126
-
127
- if (valueType === VALUE_TYPE.EAGER) {
128
- // Memoize result on the object itself
129
- Object.defineProperty(object, key, {
130
- configurable: true,
131
- enumerable,
132
- value: result,
133
- writable: true,
134
- });
135
- } else if (propertyCachePath) {
136
- result = enableValueCaching(result, propertyCachePath);
137
- }
138
-
139
- return result;
121
+ return executeProperty(object, propertyInfo, state, map);
140
122
  },
141
123
  });
142
124
  }
143
125
  }
144
126
 
127
+ // Execute the property code to get the value of the property
128
+ async function executeProperty(object, propertyInfo, state, map) {
129
+ const { globals } = state;
130
+ let { enumerable, hasExtension, key, value, valueType } = propertyInfo;
131
+ const propertyCachePath = getPropertyCachePath(object, key);
132
+
133
+ const newState = Object.assign({}, state, { object: map });
134
+ const cacheProperty = valueType === VALUE_TYPE.GETTER && propertyCachePath;
135
+ let result = cacheProperty
136
+ ? await systemCache.getOrInsertComputedAsync(propertyCachePath, () =>
137
+ execute(value, newState),
138
+ )
139
+ : await execute(value, newState);
140
+
141
+ if (hasExtension) {
142
+ // Handle extension
143
+ result = handleExtension(result, key, globals, map);
144
+ }
145
+
146
+ if (propertyCachePath) {
147
+ // Enable caching on value
148
+ result = enableValueCaching(result, propertyCachePath);
149
+ }
150
+
151
+ if (valueType === VALUE_TYPE.EAGER) {
152
+ // Memoize result on the object itself
153
+ Object.defineProperty(object, key, {
154
+ configurable: true,
155
+ enumerable,
156
+ value: result,
157
+ writable: true,
158
+ });
159
+ }
160
+
161
+ return result;
162
+ }
163
+
145
164
  function getPropertyCachePath(object, key) {
146
165
  // Follow parent chain looking for a parent that has caching enabled
147
166
  let current = object;
148
- while (current[cachePathSymbol] === undefined) {
167
+ while (current[cachePathSymbol] == null) {
149
168
  current = current[symbols.parent];
150
169
  if (!current) {
151
170
  // Caching isn't enabled on this object tree
@@ -13,6 +13,9 @@ import { cachePathSymbol } from "./symbols.js";
13
13
  import systemCache from "./systemCache.js";
14
14
  import SystemCacheMap from "./SystemCacheMap.js";
15
15
 
16
+ // Base class for async functions
17
+ const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
18
+
16
19
  /**
17
20
  * If the given value is packed (e.g., buffer) and the key is a string-like path
18
21
  * that ends in an extension, search for a handler for that extension and, if
@@ -46,19 +49,27 @@ export default function handleExtension(value, key, handlers, parent = null) {
46
49
  // Use `in` to look for handle so that, if the handler is a promise, we
47
50
  // can still find it without awaiting it here.
48
51
  if (handlerName in handlers) {
49
- let handler = handlers[handlerName];
50
-
51
52
  // If the value is a primitive, box it so we can attach data to it.
52
53
  value = box(value);
53
54
 
54
- if (handler.mediaType) {
55
- value.mediaType = handler.mediaType;
56
- }
57
-
58
55
  if (parent) {
59
56
  setParent(value, parent);
60
57
  }
61
58
 
59
+ // Avoid triggering the loading of the handler if it's async because
60
+ // we may be doing this work inside a sync function.
61
+ const descriptor = Object.getOwnPropertyDescriptor(
62
+ handlers,
63
+ handlerName,
64
+ );
65
+ const lazyHandler = descriptor?.get instanceof AsyncFunction;
66
+ if (!lazyHandler) {
67
+ const mediaType = handlers[handlerName]?.mediaType;
68
+ if (mediaType) {
69
+ value.mediaType = mediaType;
70
+ }
71
+ }
72
+
62
73
  // Wrap the unpack function so it caches the unpacked value, and so we
63
74
  // can add the file path to any errors the unpack function throws.
64
75
  const filePath = getPackedPath(value, { key: normalized, parent });
@@ -74,9 +85,7 @@ export default function handleExtension(value, key, handlers, parent = null) {
74
85
  const unpackCachePath = trailingSlash.add(fileCachePath);
75
86
  value.unpack = async () =>
76
87
  systemCache.getOrInsertComputedAsync(unpackCachePath, async () => {
77
- if (handler instanceof Promise) {
78
- handler = await handler;
79
- }
88
+ let handler = await handlers[handlerName];
80
89
  if (isUnpackable(handler)) {
81
90
  // The extension handler itself needs to be unpacked
82
91
  handler = await handler.unpack();
@@ -255,7 +255,7 @@ export function lambda(length, parameters, code, state = {}) {
255
255
  const interimStack = stack.slice();
256
256
  interimStack.push(args);
257
257
  const paramState = { ...state, stack: interimStack };
258
- const frame = await expressionObject(parameters, paramState);
258
+ const frame = await expressionObject(null, parameters, paramState);
259
259
  // Record which code this stack frame is associated with
260
260
  Object.defineProperty(frame, codeSymbol, {
261
261
  value: code,
@@ -414,11 +414,12 @@ addOpLabel(nullishCoalescing, "«ops.nullishCoalescing»");
414
414
  * parameter's, and the values will be the results of evaluating the
415
415
  * corresponding code values in `obj`.
416
416
  *
417
+ * @param {string} cachePath
417
418
  * @param {any[]} entries
418
419
  */
419
- export async function object(...entries) {
420
+ export async function object(cachePath, ...entries) {
420
421
  const state = entries.pop();
421
- return expressionObject(entries, state);
422
+ return expressionObject(cachePath, entries, state);
422
423
  }
423
424
  addOpLabel(object, "«ops.object»");
424
425
  object.unevaluatedArgs = true;
@@ -16,7 +16,6 @@ const globals = {
16
16
  * features, which compare the output of evaluate2() against an expected result.
17
17
  */
18
18
  describe("compile", () => {
19
-
20
19
  test("async object", async () => {
21
20
  const fn = compile.expression("{ a: { b = name }}", { globals });
22
21
  const object = await fn();
@@ -15,6 +15,16 @@ import {
15
15
  } from "./codeHelpers.js";
16
16
 
17
17
  describe("optimize", () => {
18
+ test("define cache path based on source", () => {
19
+ const expression = "{ a = { b: 1 } }";
20
+ const expected = [
21
+ ops.object,
22
+ "/folder/test.ori/",
23
+ ["a", [ops.getter, [ops.object, "/folder/test.ori/a/", ["b", 1]]]],
24
+ ];
25
+ assertCompile(expression, expected);
26
+ });
27
+
18
28
  test("change local references to context references", () => {
19
29
  const expression = `(name) => {
20
30
  a: name,
@@ -26,6 +36,7 @@ describe("optimize", () => {
26
36
  [["name", [[ops.params, 0], 0]]],
27
37
  [
28
38
  ops.object,
39
+ "/folder/test.ori/_objects/",
29
40
  [
30
41
  "a",
31
42
  [
@@ -49,11 +60,13 @@ describe("optimize", () => {
49
60
  const expression = `{ a: 1, more: { a } }`;
50
61
  const expected = [
51
62
  ops.object,
63
+ "/folder/test.ori/",
52
64
  ["a", 1],
53
65
  [
54
66
  "more",
55
67
  [
56
68
  ops.object,
69
+ "/folder/test.ori/more/",
57
70
  [
58
71
  "a",
59
72
  [
@@ -76,11 +89,13 @@ describe("optimize", () => {
76
89
  }`;
77
90
  const expected = [
78
91
  ops.object,
92
+ "/folder/test.ori/",
79
93
  ["name", "Alice"],
80
94
  [
81
95
  "user",
82
96
  [
83
97
  ops.object,
98
+ "/folder/test.ori/user/",
84
99
  [
85
100
  "name",
86
101
  [
@@ -95,7 +110,7 @@ describe("optimize", () => {
95
110
  assertCompile(expression, expected, "shell");
96
111
  });
97
112
 
98
- describe.only("resolve reference", () => {
113
+ describe("resolve reference", () => {
99
114
  test("external reference", () => {
100
115
  // Compilation of `folder` where folder isn't a variable
101
116
  const code = createCode([
@@ -115,6 +130,31 @@ describe("optimize", () => {
115
130
  );
116
131
  });
117
132
 
133
+ test("external reference with path", () => {
134
+ // Compilation of `src/templates/page.ori` where src isn't a variable
135
+ const code = createCode([
136
+ markers.traverse,
137
+ [markers.reference, "src/"],
138
+ [ops.literal, "templates/"],
139
+ [ops.literal, "page.ori"],
140
+ ]);
141
+ const parent = {};
142
+ const expected = [
143
+ [
144
+ ops.cache,
145
+ "test.ori/_refs/src/",
146
+ [[ops.scope], [ops.literal, "src/"]],
147
+ ],
148
+ [ops.literal, "templates/"],
149
+ [ops.literal, "page.ori"],
150
+ ];
151
+ const globals = {};
152
+ assertCodeEqual(
153
+ optimize(code, { cachePath: "test.ori", globals, parent }),
154
+ expected,
155
+ );
156
+ });
157
+
118
158
  test("external reference with implied unpack", () => {
119
159
  // Compilation of `greet.ori("world")`
120
160
  const code = createCode([
@@ -141,6 +181,7 @@ describe("optimize", () => {
141
181
  // Compilation of `{ (posts) = posts.txt }`
142
182
  const code = createCode([
143
183
  ops.object,
184
+ null,
144
185
  [
145
186
  "(posts)",
146
187
  [ops.getter, [markers.traverse, [markers.reference, "posts.txt"]]],
@@ -149,13 +190,14 @@ describe("optimize", () => {
149
190
  const parent = {};
150
191
  const expected = [
151
192
  ops.object,
193
+ "/folder/test.ori/",
152
194
  [
153
195
  "(posts)",
154
196
  [
155
197
  ops.getter,
156
198
  [
157
199
  ops.cache,
158
- "test.ori/_refs/posts.txt",
200
+ "/folder/test.ori/_refs/posts.txt",
159
201
  [[ops.scope], [ops.literal, "posts.txt"]],
160
202
  ],
161
203
  ],
@@ -163,7 +205,7 @@ describe("optimize", () => {
163
205
  ];
164
206
  const globals = {};
165
207
  assertCodeEqual(
166
- optimize(code, { cachePath: "test.ori", globals, parent }),
208
+ optimize(code, { cachePath: "/folder/test.ori", globals, parent }),
167
209
  expected,
168
210
  );
169
211
  });
@@ -233,9 +275,9 @@ describe("optimize", () => {
233
275
  [ops.literal, "passwd"],
234
276
  ]);
235
277
  const expected = [
236
- ops.cache,
237
- "test.ori/_refs//etc/passwd",
238
- [[ops.rootDirectory], [ops.literal, "etc/"], [ops.literal, "passwd"]],
278
+ [ops.cache, "test.ori/_refs//", [ops.rootDirectory]],
279
+ [ops.literal, "etc/"],
280
+ [ops.literal, "passwd"],
239
281
  ];
240
282
  assertCodeEqual(optimize(code, { cachePath: "test.ori" }), expected);
241
283
  });
@@ -259,14 +301,13 @@ describe("optimize", () => {
259
301
  ]);
260
302
  const parent = {};
261
303
  const expected = [
262
- ops.cache,
263
- "test.ori/_refs/path/to/file",
264
304
  [
265
- [ops.scope],
266
- [ops.literal, "path/"],
267
- [ops.literal, "to/"],
268
- [ops.literal, "file"],
305
+ ops.cache,
306
+ "test.ori/_refs/path/",
307
+ [[ops.scope], [ops.literal, "path/"]],
269
308
  ],
309
+ [ops.literal, "to/"],
310
+ [ops.literal, "file"],
270
311
  ];
271
312
  assertCodeEqual(
272
313
  optimize(code, { cachePath: "test.ori", parent }),
@@ -284,9 +325,12 @@ describe("optimize", () => {
284
325
  const globals = {};
285
326
  const parent = {};
286
327
  const expected = [
287
- ops.cache,
288
- "test.ori/_refs/package.json/name",
289
- [[ops.scope], [ops.literal, "package.json/"], [ops.literal, "name"]],
328
+ [
329
+ ops.cache,
330
+ "test.ori/_refs/package.json/",
331
+ [[ops.scope], [ops.literal, "package.json/"]],
332
+ ],
333
+ [ops.literal, "name"],
290
334
  ];
291
335
  assertCodeEqual(
292
336
  optimize(code, { cachePath: "test.ori", globals, parent }),
@@ -319,6 +363,7 @@ function assertCompile(expression, expected, mode = "shell") {
319
363
  typeof expression !== "string"
320
364
  ? expression
321
365
  : {
366
+ cachePath: "/folder/test.ori",
322
367
  text: expression,
323
368
  relativePath: "test.ori",
324
369
  };