@weborigami/language 0.3.0 → 0.3.2

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.
@@ -364,6 +364,15 @@ export function makeProperty(key, value) {
364
364
  return [key, modified];
365
365
  }
366
366
 
367
+ export function makeJsPropertyAccess(expression, property) {
368
+ const location = {
369
+ source: expression.location.source,
370
+ start: expression.location.start,
371
+ end: property.location.end,
372
+ };
373
+ return annotate([expression, property], location);
374
+ }
375
+
367
376
  export function makeReference(identifier) {
368
377
  // We can't know for sure that an identifier is a builtin reference until we
369
378
  // see whether it's being called as a function.
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  extension,
3
3
  ObjectTree,
4
+ setParent,
4
5
  symbols,
5
6
  trailingSlash,
6
7
  Tree,
@@ -116,12 +117,7 @@ export default async function expressionObject(entries, parent) {
116
117
  });
117
118
 
118
119
  // Attach the parent
119
- Object.defineProperty(object, symbols.parent, {
120
- configurable: true,
121
- enumerable: false,
122
- value: parent,
123
- writable: true,
124
- });
120
+ setParent(object, parent);
125
121
 
126
122
  // Evaluate any properties that were declared as immediate: get their value
127
123
  // and overwrite the property getter with the actual value.
@@ -5,7 +5,7 @@ import {
5
5
  isStringLike,
6
6
  isUnpackable,
7
7
  scope,
8
- symbols,
8
+ setParent,
9
9
  trailingSlash,
10
10
  } from "@weborigami/async-tree";
11
11
 
@@ -92,7 +92,7 @@ export async function handleExtension(parent, value, key) {
92
92
  if (handler.mediaType) {
93
93
  value.mediaType = handler.mediaType;
94
94
  }
95
- value[symbols.parent] = parent;
95
+ setParent(value, parent);
96
96
 
97
97
  const unpack = handler.unpack;
98
98
  if (unpack) {
@@ -8,10 +8,10 @@
8
8
  import {
9
9
  ObjectTree,
10
10
  Tree,
11
- concatTrees,
11
+ deepText,
12
12
  isUnpackable,
13
13
  scope as scopeFn,
14
- symbols,
14
+ setParent,
15
15
  concat as treeConcat,
16
16
  } from "@weborigami/async-tree";
17
17
  import os from "node:os";
@@ -131,7 +131,7 @@ export async function document(frontData, bodyCode) {
131
131
  ...frontData,
132
132
  "@text": body,
133
133
  };
134
- object[symbols.parent] = this;
134
+ setParent(object, this);
135
135
  return object;
136
136
  }
137
137
  addOpLabel(document, "«ops.document");
@@ -224,7 +224,8 @@ export async function inherited(key) {
224
224
  return undefined;
225
225
  }
226
226
  const parentScope = scopeFn(this.parent);
227
- return parentScope.get(key);
227
+ const value = await parentScope.get(key);
228
+ return value;
228
229
  }
229
230
  addOpLabel(inherited, "«ops.inherited»");
230
231
 
@@ -388,17 +389,33 @@ export async function merge(...codes) {
388
389
  return directObject;
389
390
  }
390
391
 
391
- // Second pass: evaluate the trees with the direct properties object in scope
392
- let context;
393
- if (directObject) {
394
- context = Tree.from(directObject);
395
- context.parent = this;
396
- } else {
397
- context = this;
398
- }
399
-
392
+ // If we have direct property entries, create a context for them. The
393
+ // `expressionObject` function will set the object's parent symbol to `this`.
394
+ // Tree.from will call the ObjectTree constructor, which will use that symbol
395
+ // to set the parent for the new tree to `this`.
396
+ const context = directObject ? Tree.from(directObject) : this;
397
+
398
+ // Second pass: evaluate the trees. For the trees which are direct property
399
+ // entries, we'll copy over the values we've already calculated. We can't
400
+ // reuse the `directObject` as is because in a merge we need to respect the
401
+ // order in which the properties are defined. Trees that aren't direct
402
+ // property entries are evaluated with the direct property entries in scope.
400
403
  const trees = await Promise.all(
401
- codes.map(async (code) => evaluate.call(context, code))
404
+ codes.map(async (code) => {
405
+ if (code[0] === object) {
406
+ // Using the code as reference, create a new object with the direct
407
+ // property values we've already calculated.
408
+ const object = {};
409
+ for (const [key] of code.slice(1)) {
410
+ // @ts-ignore directObject will always be defined
411
+ object[key] = directObject[key];
412
+ }
413
+ setParent(object, this);
414
+ return object;
415
+ } else {
416
+ return evaluate.call(context, code);
417
+ }
418
+ })
402
419
  );
403
420
 
404
421
  return mergeTrees.call(this, ...trees);
@@ -533,7 +550,7 @@ addOpLabel(subtraction, "«ops.subtraction»");
533
550
  * Apply the default tagged template function.
534
551
  */
535
552
  export async function template(strings, ...values) {
536
- return concatTrees(strings, ...values);
553
+ return deepText(strings, ...values);
537
554
  }
538
555
  addOpLabel(template, "«ops.template»");
539
556
 
@@ -236,6 +236,24 @@ describe("Origami parser", () => {
236
236
  ]);
237
237
  });
238
238
 
239
+ test("callExpression using property acccess", () => {
240
+ assertParse("callExpression", "(foo).bar", [
241
+ ops.traverse,
242
+ [ops.scope, "foo"],
243
+ [ops.literal, "bar"],
244
+ ]);
245
+ assertParse("callExpression", "(foo).bar.baz", [
246
+ ops.traverse,
247
+ [ops.traverse, [ops.scope, "foo"], [ops.literal, "bar"]],
248
+ [ops.literal, "baz"],
249
+ ]);
250
+ assertParse("callExpression", "foo[bar]", [
251
+ ops.traverse,
252
+ [ops.scope, "foo/"],
253
+ [ops.scope, "bar"],
254
+ ]);
255
+ });
256
+
239
257
  test("commaExpression", () => {
240
258
  assertParse("commaExpression", "1", [ops.literal, 1]);
241
259
  assertParse("commaExpression", "a, b, c", [
@@ -567,6 +585,26 @@ Body`,
567
585
  ]);
568
586
  });
569
587
 
588
+ test("jsIdentifier", () => {
589
+ assertParse("jsIdentifier", "foo", "foo", false);
590
+ assertParse("jsIdentifier", "$Δelta", "$Δelta", false);
591
+ assertThrows(
592
+ "jsIdentifier",
593
+ "1stCharacterIsNumber",
594
+ "Expected JavaScript identifier start"
595
+ );
596
+ assertThrows(
597
+ "jsIdentifier",
598
+ "has space",
599
+ "Expected JavaScript identifier continuation"
600
+ );
601
+ assertThrows(
602
+ "jsIdentifier",
603
+ "foo.bar",
604
+ "Expected JavaScript identifier continuation"
605
+ );
606
+ });
607
+
570
608
  test("list", () => {
571
609
  assertParse("list", "1", [[ops.literal, 1]]);
572
610
  assertParse("list", "1,2,3", [