@weborigami/language 0.3.4-jse.6 → 0.3.4-jse.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.
@@ -1,6 +1,7 @@
1
1
  // Text we look for in an error stack to guess whether a given line represents a
2
2
 
3
3
  import {
4
+ box,
4
5
  scope as scopeFn,
5
6
  trailingSlash,
6
7
  TraverseError,
@@ -8,6 +9,7 @@ import {
8
9
  import path from "node:path";
9
10
  import { fileURLToPath } from "node:url";
10
11
  import codeFragment from "./codeFragment.js";
12
+ import * as symbols from "./symbols.js";
11
13
  import { typos } from "./typos.js";
12
14
 
13
15
  // function in the Origami source code.
@@ -18,6 +20,19 @@ const origamiSourceSignals = [
18
20
  "at Scope.evaluate",
19
21
  ];
20
22
 
23
+ const displayedWarnings = new Set();
24
+
25
+ export function attachWarning(value, message) {
26
+ if (typeof value === "object" && value?.[symbols.warningSymbol]) {
27
+ // Already has a warning, don't overwrite it
28
+ return value;
29
+ }
30
+
31
+ const boxed = box(value);
32
+ boxed[symbols.warningSymbol] = message;
33
+ return boxed;
34
+ }
35
+
21
36
  export async function builtinReferenceError(tree, builtins, key) {
22
37
  // See if the key is in scope (but not as a builtin)
23
38
  const scope = scopeFn(tree);
@@ -40,6 +55,16 @@ export async function builtinReferenceError(tree, builtins, key) {
40
55
  return new ReferenceError(message);
41
56
  }
42
57
 
58
+ // Display a warning message in the console, but only once for each unique
59
+ // message and location.
60
+ export function displayWarning(message, location) {
61
+ const warning = "Warning: " + message + lineInfo(location);
62
+ if (!displayedWarnings.has(warning)) {
63
+ displayedWarnings.add(warning);
64
+ console.warn(warning);
65
+ }
66
+ }
67
+
43
68
  /**
44
69
  * Format an error for display in the console.
45
70
  *
@@ -80,33 +105,10 @@ export function formatError(error) {
80
105
 
81
106
  // Add location
82
107
  if (location) {
83
- let { source, start } = location;
84
- // Adjust line number with offset if present (for example, if the code is in
85
- // an Origami template document with front matter that was stripped)
86
- let line = start.line + (source.offset ?? 0);
87
108
  if (!fragmentInMessage) {
88
109
  message += `\nevaluating: ${fragment}`;
89
110
  }
90
-
91
- if (typeof source === "object" && source.url) {
92
- const { url } = source;
93
- let fileRef;
94
- // If URL is a file: URL, change to a relative path
95
- if (url.protocol === "file:") {
96
- fileRef = fileURLToPath(url);
97
- const relativePath = path.relative(process.cwd(), fileRef);
98
- if (!relativePath.startsWith("..")) {
99
- fileRef = relativePath;
100
- }
101
- } else {
102
- // Not a file: URL, use as is
103
- fileRef = url.href;
104
- }
105
- message += `\n at ${fileRef}:${line}:${start.column}`;
106
- } else if (source.text.includes("\n")) {
107
- // Don't know the URL, but has multiple lines so add line number
108
- message += `\n at line ${line}, column ${start.column}`;
109
- }
111
+ message += lineInfo(location);
110
112
  }
111
113
 
112
114
  return message;
@@ -128,6 +130,36 @@ export function maybeOrigamiSourceCode(text) {
128
130
  return origamiSourceSignals.some((signal) => text.includes(signal));
129
131
  }
130
132
 
133
+ // Return user-friendly line information for the error location
134
+ function lineInfo(location) {
135
+ let { source, start } = location;
136
+ // Adjust line number with offset if present (for example, if the code is in
137
+ // an Origami template document with front matter that was stripped)
138
+ let line = start.line + (source.offset ?? 0);
139
+
140
+ if (typeof source === "object" && source.url) {
141
+ const { url } = source;
142
+ let fileRef;
143
+ // If URL is a file: URL, change to a relative path
144
+ if (url.protocol === "file:") {
145
+ fileRef = fileURLToPath(url);
146
+ const relativePath = path.relative(process.cwd(), fileRef);
147
+ if (!relativePath.startsWith("..")) {
148
+ fileRef = relativePath;
149
+ }
150
+ } else {
151
+ // Not a file: URL, use as is
152
+ fileRef = url.href;
153
+ }
154
+ return `\n at ${fileRef}:${line}:${start.column}`;
155
+ } else if (source.text.includes("\n")) {
156
+ // Don't know the URL, but has multiple lines so add line number
157
+ return `\n at line ${line}, column ${start.column}`;
158
+ } else {
159
+ return "";
160
+ }
161
+ }
162
+
131
163
  export async function scopeReferenceError(scope, key) {
132
164
  const messages = [
133
165
  `"${key}" is not in scope or is undefined.`,
@@ -1,4 +1,5 @@
1
1
  import { Tree, isUnpackable } from "@weborigami/async-tree";
2
+ import { displayWarning, symbols } from "@weborigami/language";
2
3
  import codeFragment from "./codeFragment.js";
3
4
 
4
5
  /**
@@ -66,33 +67,10 @@ export default async function evaluate(code) {
66
67
  throw error;
67
68
  }
68
69
 
69
- // To aid debugging, add the code to the result.
70
- // if (Object.isExtensible(result)) {
71
- // try {
72
- // if (code.location && !result[sourceSymbol]) {
73
- // Object.defineProperty(result, sourceSymbol, {
74
- // value: codeFragment(code.location),
75
- // enumerable: false,
76
- // });
77
- // }
78
- // if (!result[codeSymbol]) {
79
- // Object.defineProperty(result, codeSymbol, {
80
- // value: code,
81
- // enumerable: false,
82
- // });
83
- // }
84
- // if (!result[scopeSymbol]) {
85
- // Object.defineProperty(result, scopeSymbol, {
86
- // get() {
87
- // return scope(this).trees;
88
- // },
89
- // enumerable: false,
90
- // });
91
- // }
92
- // } catch (/** @type {any} */ error) {
93
- // // Ignore errors.
94
- // }
95
- // }
70
+ if (result?.[symbols.warningSymbol]) {
71
+ displayWarning(result[symbols.warningSymbol], code.location);
72
+ delete result[symbols.warningSymbol];
73
+ }
96
74
 
97
75
  return result;
98
76
  }
@@ -2,3 +2,4 @@ export const codeSymbol = Symbol("code");
2
2
  export const configSymbol = Symbol("config");
3
3
  export const scopeSymbol = Symbol("scope");
4
4
  export const sourceSymbol = Symbol("source");
5
+ export const warningSymbol = Symbol("warning");
@@ -155,8 +155,8 @@ describe("optimize", () => {
155
155
  test("global reference", () => {
156
156
  // Compilation of `Math` where Math is a global variable
157
157
  const code = createCode([markers.traverse, [markers.reference, "Math"]]);
158
- const globals = { Math: null }; // value doesn't matter
159
- const expected = [globals, "Math"];
158
+ const globals = { Math: {} }; // value doesn't matter
159
+ const expected = globals.Math;
160
160
  assertCodeEqual(optimize(code, { globals }), expected);
161
161
  });
162
162
 
@@ -167,7 +167,7 @@ describe("optimize", () => {
167
167
  [markers.reference, "Math.PI"],
168
168
  ]);
169
169
  const globals = { Math: { PI: null } }; // value doesn't matter
170
- const expected = [[globals, "Math"], "PI"];
170
+ const expected = [globals.Math, "PI"];
171
171
  assertCodeEqual(optimize(code, { globals }), expected);
172
172
  });
173
173
 
@@ -618,6 +618,12 @@ Body`,
618
618
  [markers.traverse, [markers.reference, "keys"]],
619
619
  [markers.traverse, [markers.external, "~"]],
620
620
  ]);
621
+ assertParse("expression", "!x ? 0 : 1", [
622
+ ops.conditional,
623
+ [ops.logicalNot, [markers.traverse, [markers.reference, "x"]]],
624
+ [ops.literal, 0],
625
+ [ops.literal, 1],
626
+ ]);
621
627
  });
622
628
 
623
629
  test("complex object", () => {