@weborigami/language 0.6.5 → 0.6.7
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.
- package/index.ts +6 -0
- package/main.js +3 -0
- package/package.json +2 -2
- package/src/compiler/optimize.js +14 -5
- package/src/compiler/origami.pegjs +8 -6
- package/src/compiler/parse.js +26 -23
- package/src/project/projectConfig.js +2 -2
- package/src/project/projectGlobals.js +2 -2
- package/src/project/projectRoot.js +5 -54
- package/src/project/projectRootFromPath.js +68 -0
- package/src/protocols/fetchAndHandleExtension.js +5 -0
- package/src/protocols/files.js +9 -6
- package/src/protocols/package.js +26 -39
- package/src/runtime/expressionObject.js +165 -150
- package/src/runtime/handleExtension.js +18 -2
- package/test/compiler/parse.test.js +10 -20
- package/test/project/{projectRoot.test.js → projectRootFromPath.test.js} +7 -7
- package/test/protocols/package.test.js +6 -1
- package/test/runtime/expressionObject.test.js +25 -10
package/index.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Origami is a JavaScript project, but we use TypeScript as an internal tool to
|
|
3
|
+
* confirm our code is type safe.
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import { SyncOrAsyncMap, UnpackFunction } from "@weborigami/async-tree";
|
|
2
7
|
|
|
8
|
+
// Re-export all exports from main.js
|
|
3
9
|
export * from "./main.js";
|
|
4
10
|
|
|
5
11
|
/**
|
package/main.js
CHANGED
|
@@ -4,9 +4,12 @@ export * as compile from "./src/compiler/compile.js";
|
|
|
4
4
|
export { default as isOrigamiFrontMatter } from "./src/compiler/isOrigamiFrontMatter.js";
|
|
5
5
|
export * as Handlers from "./src/handlers/handlers.js";
|
|
6
6
|
export { default as builtins } from "./src/project/builtins.js";
|
|
7
|
+
export { default as coreGlobals } from "./src/project/coreGlobals.js";
|
|
7
8
|
export { default as jsGlobals } from "./src/project/jsGlobals.js";
|
|
9
|
+
export { default as projectConfig } from "./src/project/projectConfig.js";
|
|
8
10
|
export { default as projectGlobals } from "./src/project/projectGlobals.js";
|
|
9
11
|
export { default as projectRoot } from "./src/project/projectRoot.js";
|
|
12
|
+
export { default as projectRootFromPath } from "./src/project/projectRootFromPath.js";
|
|
10
13
|
export * as Protocols from "./src/protocols/protocols.js";
|
|
11
14
|
export { formatError, highlightError } from "./src/runtime/errors.js";
|
|
12
15
|
export { default as evaluate } from "./src/runtime/evaluate.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"typescript": "5.9.3"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/async-tree": "0.6.
|
|
14
|
+
"@weborigami/async-tree": "0.6.7",
|
|
15
15
|
"exif-parser": "0.1.12",
|
|
16
16
|
"watcher": "2.3.1",
|
|
17
17
|
"yaml": "2.8.1"
|
package/src/compiler/optimize.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { pathFromKeys, trailingSlash } from "@weborigami/async-tree";
|
|
2
2
|
import jsGlobals from "../project/jsGlobals.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
KEY_TYPE,
|
|
5
|
+
normalizeKey,
|
|
6
|
+
propertyInfo,
|
|
7
|
+
} from "../runtime/expressionObject.js";
|
|
4
8
|
import { ops } from "../runtime/internal.js";
|
|
5
9
|
import { annotate, markers, spanLocations } from "./parserHelpers.js";
|
|
6
10
|
|
|
@@ -62,10 +66,7 @@ export default function optimize(code, options = {}) {
|
|
|
62
66
|
|
|
63
67
|
case ops.object:
|
|
64
68
|
const entries = args;
|
|
65
|
-
|
|
66
|
-
const propertyNames = entries
|
|
67
|
-
.map((entry) => entryKey(entry))
|
|
68
|
-
.filter((key) => key !== null);
|
|
69
|
+
const propertyNames = getPropertyNames(entries);
|
|
69
70
|
locals.push({
|
|
70
71
|
type: REFERENCE_INHERITED,
|
|
71
72
|
names: propertyNames,
|
|
@@ -207,6 +208,14 @@ function findLocalDetails(key, locals) {
|
|
|
207
208
|
return null;
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
function getPropertyNames(entries) {
|
|
212
|
+
const infos = entries.map(([key, value]) => propertyInfo(key, value));
|
|
213
|
+
// Filter out computed property keys when determining local variables
|
|
214
|
+
return infos
|
|
215
|
+
.filter((info) => info.keyType !== KEY_TYPE.COMPUTED)
|
|
216
|
+
.map((info) => normalizeKey(info));
|
|
217
|
+
}
|
|
218
|
+
|
|
210
219
|
function globalReference(key, globals) {
|
|
211
220
|
const normalized = trailingSlash.remove(key);
|
|
212
221
|
return globals[normalized];
|
|
@@ -163,16 +163,18 @@ comment "comment"
|
|
|
163
163
|
/ singleLineComment
|
|
164
164
|
|
|
165
165
|
computedPropertyAccess
|
|
166
|
-
= computedPropertySpace "[" expression:expectExpression expectClosingBracket {
|
|
166
|
+
= computedPropertySpace? "[" expression:expectExpression expectClosingBracket {
|
|
167
167
|
return annotate([markers.property, expression], location());
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
//
|
|
171
|
-
// mode. In shell mode `foo [bar]` should parse as a function call with
|
|
172
|
-
// argument of an array, not as a property access.
|
|
170
|
+
// An inline space before a computed property access. This is allowed when not
|
|
171
|
+
// in shell mode. In shell mode `foo [bar]` should parse as a function call with
|
|
172
|
+
// a single argument of an array, not as a property access. In program made, we
|
|
173
|
+
// allow an inline space per JavaScript. JavaScript also allows newlines, but we
|
|
174
|
+
// disallow those to avoid confusion with array/list/object entry separators.
|
|
173
175
|
computedPropertySpace
|
|
174
176
|
= shellMode
|
|
175
|
-
/ !shellMode
|
|
177
|
+
/ !shellMode inlineSpace
|
|
176
178
|
|
|
177
179
|
conditionalExpression
|
|
178
180
|
= condition:logicalOrExpression tail:(__
|
|
@@ -750,7 +752,7 @@ parenthesesArgumentList "list"
|
|
|
750
752
|
// Function arguments in parentheses
|
|
751
753
|
parenthesesArguments "function arguments in parentheses"
|
|
752
754
|
= inlineSpace* "(" __ list:parenthesesArgumentList? __ expectClosingParenthesis {
|
|
753
|
-
return annotate(list ?? [
|
|
755
|
+
return annotate(list ?? [], location());
|
|
754
756
|
}
|
|
755
757
|
|
|
756
758
|
// A slash-separated path of keys that follows a call target, such as the path
|
package/src/compiler/parse.js
CHANGED
|
@@ -931,7 +931,7 @@ function peg$parse(input, options) {
|
|
|
931
931
|
return annotate(args, location());
|
|
932
932
|
}
|
|
933
933
|
function peg$f98(list) {
|
|
934
|
-
return annotate(list ?? [
|
|
934
|
+
return annotate(list ?? [], location());
|
|
935
935
|
}
|
|
936
936
|
function peg$f99(keys) {
|
|
937
937
|
const args = keys ?? [];
|
|
@@ -2192,25 +2192,23 @@ function peg$parse(input, options) {
|
|
|
2192
2192
|
|
|
2193
2193
|
s0 = peg$currPos;
|
|
2194
2194
|
s1 = peg$parsecomputedPropertySpace();
|
|
2195
|
-
if (s1
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
if (
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
s0 = peg$FAILED;
|
|
2213
|
-
}
|
|
2195
|
+
if (s1 === peg$FAILED) {
|
|
2196
|
+
s1 = null;
|
|
2197
|
+
}
|
|
2198
|
+
if (input.charCodeAt(peg$currPos) === 91) {
|
|
2199
|
+
s2 = peg$c6;
|
|
2200
|
+
peg$currPos++;
|
|
2201
|
+
} else {
|
|
2202
|
+
s2 = peg$FAILED;
|
|
2203
|
+
if (peg$silentFails === 0) { peg$fail(peg$e9); }
|
|
2204
|
+
}
|
|
2205
|
+
if (s2 !== peg$FAILED) {
|
|
2206
|
+
s3 = peg$parseexpectExpression();
|
|
2207
|
+
if (s3 !== peg$FAILED) {
|
|
2208
|
+
s4 = peg$parseexpectClosingBracket();
|
|
2209
|
+
if (s4 !== peg$FAILED) {
|
|
2210
|
+
peg$savedPos = s0;
|
|
2211
|
+
s0 = peg$f16(s3);
|
|
2214
2212
|
} else {
|
|
2215
2213
|
peg$currPos = s0;
|
|
2216
2214
|
s0 = peg$FAILED;
|
|
@@ -2244,9 +2242,14 @@ function peg$parse(input, options) {
|
|
|
2244
2242
|
s1 = peg$FAILED;
|
|
2245
2243
|
}
|
|
2246
2244
|
if (s1 !== peg$FAILED) {
|
|
2247
|
-
s2 = peg$
|
|
2248
|
-
|
|
2249
|
-
|
|
2245
|
+
s2 = peg$parseinlineSpace();
|
|
2246
|
+
if (s2 !== peg$FAILED) {
|
|
2247
|
+
s1 = [s1, s2];
|
|
2248
|
+
s0 = s1;
|
|
2249
|
+
} else {
|
|
2250
|
+
peg$currPos = s0;
|
|
2251
|
+
s0 = peg$FAILED;
|
|
2252
|
+
}
|
|
2250
2253
|
} else {
|
|
2251
2254
|
peg$currPos = s0;
|
|
2252
2255
|
s0 = peg$FAILED;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { FileMap, toString } from "@weborigami/async-tree";
|
|
2
2
|
import ori_handler from "../handlers/ori_handler.js";
|
|
3
3
|
import coreGlobals from "./coreGlobals.js";
|
|
4
|
-
import
|
|
4
|
+
import projectRootFromPath from "./projectRootFromPath.js";
|
|
5
5
|
|
|
6
6
|
const mapPathToConfig = new Map();
|
|
7
7
|
|
|
8
8
|
export default async function config(dir = process.cwd()) {
|
|
9
|
-
const root = await
|
|
9
|
+
const root = await projectRootFromPath(dir);
|
|
10
10
|
|
|
11
11
|
const rootPath = root.path;
|
|
12
12
|
const cached = mapPathToConfig.get(rootPath);
|
|
@@ -4,14 +4,14 @@ import projectConfig from "./projectConfig.js";
|
|
|
4
4
|
let globals;
|
|
5
5
|
|
|
6
6
|
// Core globals plus project config
|
|
7
|
-
export default async function projectGlobals() {
|
|
7
|
+
export default async function projectGlobals(dir = process.cwd()) {
|
|
8
8
|
if (!globals) {
|
|
9
9
|
// Start with core globals
|
|
10
10
|
globals = await coreGlobals();
|
|
11
11
|
// Now get config. The config.ori file may require access to globals,
|
|
12
12
|
// which will obtain the core globals set above. Once we've got the
|
|
13
13
|
// config, we add it to the globals.
|
|
14
|
-
const config = await projectConfig();
|
|
14
|
+
const config = await projectConfig(dir);
|
|
15
15
|
Object.assign(globals, config);
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -1,58 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import OrigamiFileMap from "../runtime/OrigamiFileMap.js";
|
|
4
|
-
|
|
5
|
-
const configFileName = "config.ori";
|
|
6
|
-
const packageFileName = "package.json";
|
|
7
|
-
|
|
8
|
-
const mapPathToRoot = new Map();
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
9
2
|
|
|
10
3
|
/**
|
|
11
|
-
* Return an OrigamiFileMap object for the current
|
|
12
|
-
*
|
|
13
|
-
* This searches the current directory and its ancestors for an Origami file
|
|
14
|
-
* called `config.ori`. If an Origami configuration file is found, the
|
|
15
|
-
* containing folder is considered to be the project root.
|
|
16
|
-
*
|
|
17
|
-
* Otherwise, this looks for a package.json file to determine the project root.
|
|
18
|
-
* If no package.json is found, the current folder is used as the project root.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @param {string} [dirname]
|
|
4
|
+
* Return an OrigamiFileMap object for the current code context.
|
|
22
5
|
*/
|
|
23
|
-
export default async function projectRoot(
|
|
24
|
-
|
|
25
|
-
if (cached) {
|
|
26
|
-
return cached;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let root;
|
|
30
|
-
let value;
|
|
31
|
-
// Use a plain FileMap to avoid loading extension handlers
|
|
32
|
-
const currentTree = new FileMap(dirname);
|
|
33
|
-
// Try looking for config file
|
|
34
|
-
value = await currentTree.get(configFileName);
|
|
35
|
-
if (value) {
|
|
36
|
-
// Found config file
|
|
37
|
-
root = new OrigamiFileMap(currentTree.path);
|
|
38
|
-
} else {
|
|
39
|
-
// Try looking for package.json
|
|
40
|
-
value = await currentTree.get(packageFileName);
|
|
41
|
-
if (value) {
|
|
42
|
-
// Found package.json
|
|
43
|
-
root = new OrigamiFileMap(currentTree.path);
|
|
44
|
-
} else {
|
|
45
|
-
// Move up a folder and try again
|
|
46
|
-
const parentPath = path.dirname(dirname);
|
|
47
|
-
if (parentPath !== dirname) {
|
|
48
|
-
root = await projectRoot(parentPath);
|
|
49
|
-
} else {
|
|
50
|
-
// At filesystem root, use current working directory
|
|
51
|
-
root = new OrigamiFileMap(process.cwd());
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
mapPathToRoot.set(dirname, root);
|
|
57
|
-
return root;
|
|
6
|
+
export default async function projectRoot(state) {
|
|
7
|
+
return Tree.root(state.container);
|
|
58
8
|
}
|
|
9
|
+
projectRoot.needsState = true;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { FileMap } from "@weborigami/async-tree";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import OrigamiFileMap from "../runtime/OrigamiFileMap.js";
|
|
4
|
+
|
|
5
|
+
const configFileName = "config.ori";
|
|
6
|
+
const packageFileName = "package.json";
|
|
7
|
+
|
|
8
|
+
const mapPathToRoot = new Map();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Return an OrigamiFileMap object for the current project root.
|
|
12
|
+
*
|
|
13
|
+
* This searches the current directory and its ancestors for an Origami file
|
|
14
|
+
* called `config.ori`. If an Origami configuration file is found, the
|
|
15
|
+
* containing folder is considered to be the project root.
|
|
16
|
+
*
|
|
17
|
+
* Otherwise, this looks for a package.json file to determine the project root.
|
|
18
|
+
* If no package.json is found, the current folder is used as the project root.
|
|
19
|
+
*
|
|
20
|
+
*
|
|
21
|
+
* @param {string} [dirname]
|
|
22
|
+
*/
|
|
23
|
+
export default async function projectRootFromPath(dirname = process.cwd()) {
|
|
24
|
+
const cached = mapPathToRoot.get(dirname);
|
|
25
|
+
if (cached) {
|
|
26
|
+
return cached;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let root;
|
|
30
|
+
let value;
|
|
31
|
+
// Use a plain FileMap to avoid loading extension handlers
|
|
32
|
+
let currentFolder = new FileMap(dirname);
|
|
33
|
+
while (currentFolder) {
|
|
34
|
+
// Try looking for config file
|
|
35
|
+
value = await currentFolder.get(configFileName);
|
|
36
|
+
if (value) {
|
|
37
|
+
// Found config file
|
|
38
|
+
root = new OrigamiFileMap(currentFolder.path);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Try looking for package.json
|
|
43
|
+
value = await currentFolder.get(packageFileName);
|
|
44
|
+
if (value) {
|
|
45
|
+
// Found package.json
|
|
46
|
+
root = new OrigamiFileMap(currentFolder.path);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Move up a folder and try again
|
|
51
|
+
const parentPath = path.dirname(currentFolder.path);
|
|
52
|
+
if (parentPath !== currentFolder.path) {
|
|
53
|
+
currentFolder = new FileMap(parentPath);
|
|
54
|
+
} else {
|
|
55
|
+
// At filesystem root; not found
|
|
56
|
+
root = null;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!root) {
|
|
62
|
+
// Default to using the provided folder as the project root
|
|
63
|
+
root = new OrigamiFileMap(dirname);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
mapPathToRoot.set(dirname, root);
|
|
67
|
+
return root;
|
|
68
|
+
}
|
|
@@ -13,6 +13,11 @@ export default async function fetchAndHandleExtension(href) {
|
|
|
13
13
|
}
|
|
14
14
|
let buffer = await response.arrayBuffer();
|
|
15
15
|
|
|
16
|
+
const mediaType = response.headers.get("Content-Type");
|
|
17
|
+
if (mediaType) {
|
|
18
|
+
/** @type {any} */ (buffer).mediaType = mediaType;
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
// Attach any loader defined for the file type.
|
|
17
22
|
const url = new URL(href);
|
|
18
23
|
const filename = url.pathname.split("/").pop();
|
package/src/protocols/files.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import process from "node:process";
|
|
4
3
|
import OrigamiFileMap from "../runtime/OrigamiFileMap.js";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
*
|
|
8
|
-
* @param {
|
|
7
|
+
* @param {any[]} args
|
|
9
8
|
*/
|
|
10
|
-
export default async function files(...
|
|
9
|
+
export default async function files(...args) {
|
|
10
|
+
const state = args.pop(); // Remaining args are the path
|
|
11
|
+
|
|
11
12
|
// If path begins with `~`, treat it relative to the home directory.
|
|
12
|
-
// Otherwise, treat it relative to the current
|
|
13
|
-
let relativePath =
|
|
13
|
+
// Otherwise, treat it relative to the current container.
|
|
14
|
+
let relativePath = args.join(path.sep);
|
|
14
15
|
let basePath;
|
|
15
16
|
if (relativePath.startsWith("~")) {
|
|
16
17
|
basePath = os.homedir();
|
|
17
18
|
relativePath = relativePath.slice(2);
|
|
18
19
|
} else {
|
|
19
|
-
|
|
20
|
+
const { container } = state;
|
|
21
|
+
basePath = container.path;
|
|
20
22
|
}
|
|
21
23
|
const resolved = path.resolve(basePath, relativePath);
|
|
22
24
|
|
|
23
25
|
const result = new OrigamiFileMap(resolved);
|
|
24
26
|
return result;
|
|
25
27
|
}
|
|
28
|
+
files.needsState = true;
|
package/src/protocols/package.js
CHANGED
|
@@ -2,66 +2,53 @@ import { Tree, keysFromPath } from "@weborigami/async-tree";
|
|
|
2
2
|
import projectRoot from "../project/projectRoot.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* The package: protocol handler
|
|
6
|
+
*
|
|
7
|
+
* @param {any[]} args
|
|
6
8
|
*/
|
|
7
|
-
export default async function
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
getPackage(parent, organization, name, keys);
|
|
19
|
-
}
|
|
20
|
-
name = keys.shift();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return getPackage(parent, organization, name, keys);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function getPackage(parent, organization, name, keys) {
|
|
27
|
-
const packagePath = ["node_modules"];
|
|
28
|
-
if (organization) {
|
|
29
|
-
packagePath.push(organization);
|
|
9
|
+
export default async function packageProtocol(...args) {
|
|
10
|
+
const state = args.pop(); // Remaining args are the path
|
|
11
|
+
const root = await projectRoot(state);
|
|
12
|
+
|
|
13
|
+
// Identify the path to the package root
|
|
14
|
+
const packageRootPath = ["node_modules"];
|
|
15
|
+
const name = args.shift();
|
|
16
|
+
packageRootPath.push(name);
|
|
17
|
+
if (name.startsWith("@")) {
|
|
18
|
+
// First key is an npm organization, add next key as name
|
|
19
|
+
packageRootPath.push(args.shift());
|
|
30
20
|
}
|
|
31
|
-
packagePath.push(name);
|
|
32
|
-
|
|
33
|
-
const parentScope = await Tree.scope(parent);
|
|
34
|
-
const packageRoot = await Tree.traverse(
|
|
35
|
-
// @ts-ignore
|
|
36
|
-
parentScope,
|
|
37
|
-
...packagePath
|
|
38
|
-
);
|
|
39
21
|
|
|
22
|
+
// Get the package root (top level folder of the package)
|
|
23
|
+
const packageRoot = await Tree.traverse(root, ...packageRootPath);
|
|
40
24
|
if (!packageRoot) {
|
|
41
|
-
throw new Error(`Can't find ${
|
|
25
|
+
throw new Error(`Can't find ${packageRootPath.join("/")}`);
|
|
42
26
|
}
|
|
43
27
|
|
|
28
|
+
// Identify the main entry point
|
|
44
29
|
const mainPath = await Tree.traverse(packageRoot, "package.json", "main");
|
|
45
30
|
if (!mainPath) {
|
|
46
31
|
throw new Error(
|
|
47
|
-
|
|
32
|
+
`${packageRootPath.join(
|
|
48
33
|
"/"
|
|
49
34
|
)} doesn't contain a package.json with a "main" entry.`
|
|
50
35
|
);
|
|
51
36
|
}
|
|
52
37
|
|
|
38
|
+
// Identify the folder containing the main entry point
|
|
53
39
|
const mainKeys = keysFromPath(mainPath);
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const mainContainer = await Tree.traverse(packageRoot, ...mainContainerKeys);
|
|
40
|
+
const mainFileName = mainKeys.pop();
|
|
41
|
+
const mainContainer = await Tree.traverse(packageRoot, ...mainKeys);
|
|
57
42
|
const packageExports = await mainContainer.import(mainFileName);
|
|
58
43
|
|
|
59
44
|
let result =
|
|
60
45
|
"default" in packageExports ? packageExports.default : packageExports;
|
|
61
46
|
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
// If there are remaining args, traverse into the package exports
|
|
48
|
+
if (args.length > 0) {
|
|
49
|
+
result = await Tree.traverse(result, ...args);
|
|
64
50
|
}
|
|
65
51
|
|
|
66
52
|
return result;
|
|
67
53
|
}
|
|
54
|
+
packageProtocol.needsState = true;
|
|
@@ -9,20 +9,22 @@ import {
|
|
|
9
9
|
import handleExtension from "./handleExtension.js";
|
|
10
10
|
import { evaluate, ops } from "./internal.js";
|
|
11
11
|
|
|
12
|
+
export const KEY_TYPE = {
|
|
13
|
+
STRING: 0, // Simple string key: `a: 1`
|
|
14
|
+
COMPUTED: 1, // Computed key: `[code]: 1`
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const VALUE_TYPE = {
|
|
18
|
+
PRIMITIVE: 0, // Primitive value: `a: 1`
|
|
19
|
+
EAGER: 1, // Calculated immediately: `a: 1 + 1`
|
|
20
|
+
GETTER: 2, // Calculated on demand: `a = fn()`
|
|
21
|
+
};
|
|
22
|
+
|
|
12
23
|
/**
|
|
13
24
|
* Given an array of entries with string keys and Origami code values (arrays of
|
|
14
25
|
* ops and operands), return an object with the same keys defining properties
|
|
15
26
|
* whose getters evaluate the code.
|
|
16
|
-
|
|
17
|
-
* The value can take three forms:
|
|
18
|
-
*
|
|
19
|
-
* 1. A primitive value (string, etc.). This will be defined directly as an
|
|
20
|
-
* object property.
|
|
21
|
-
* 1. An eager (as opposed to lazy) code entry. This will be evaluated during
|
|
22
|
-
* this call and its result defined as an object property.
|
|
23
|
-
* 1. A code entry that starts with ops.getter. This will be defined as a
|
|
24
|
-
* property getter on the object.
|
|
25
|
-
*
|
|
27
|
+
|
|
26
28
|
* @param {*} entries
|
|
27
29
|
* @param {import("../../index.ts").RuntimeState} [state]
|
|
28
30
|
*/
|
|
@@ -35,191 +37,204 @@ export default async function expressionObject(entries, state = {}) {
|
|
|
35
37
|
}
|
|
36
38
|
setParent(object, parent);
|
|
37
39
|
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
let hasLazyProperties = false;
|
|
49
|
-
for (let i = 0; i < entries.length; i++) {
|
|
50
|
-
let key = computedKeys[i];
|
|
51
|
-
let value = entries[i][1];
|
|
52
|
-
|
|
53
|
-
// Determine if we need to define a getter or a regular property. If the key
|
|
54
|
-
// has an extension, we need to define a getter. If the value is code (an
|
|
55
|
-
// array), we need to define a getter -- but if that code takes the form
|
|
56
|
-
// [ops.getter, <primitive>] or [ops.literal, <value>], we can define a
|
|
57
|
-
// regular property.
|
|
58
|
-
let defineProperty;
|
|
59
|
-
const extname = extension.extname(key);
|
|
60
|
-
if (extname) {
|
|
61
|
-
defineProperty = false;
|
|
62
|
-
} else if (!(value instanceof Array)) {
|
|
63
|
-
defineProperty = true;
|
|
64
|
-
} else if (value[0] === ops.getter && !(value[1] instanceof Array)) {
|
|
65
|
-
defineProperty = true;
|
|
66
|
-
value = value[1];
|
|
67
|
-
} else if (value[0] === ops.literal) {
|
|
68
|
-
defineProperty = true;
|
|
69
|
-
value = value[1];
|
|
70
|
-
} else {
|
|
71
|
-
defineProperty = false;
|
|
40
|
+
// The object in Map form for use on the stack
|
|
41
|
+
const map = new ObjectMap(object);
|
|
42
|
+
|
|
43
|
+
// Preparation: gather information about all properties
|
|
44
|
+
const infos = entries.map(([key, value]) => propertyInfo(key, value));
|
|
45
|
+
|
|
46
|
+
// First pass: define all properties with plain string keys
|
|
47
|
+
for (const info of infos) {
|
|
48
|
+
if (info.keyType === KEY_TYPE.STRING) {
|
|
49
|
+
defineProperty(object, info, state, map);
|
|
72
50
|
}
|
|
51
|
+
}
|
|
73
52
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
53
|
+
// Second pass: redefine eager string-keyed properties with actual values.
|
|
54
|
+
for (const info of infos) {
|
|
55
|
+
if (
|
|
56
|
+
info.keyType === KEY_TYPE.STRING &&
|
|
57
|
+
info.valueType === VALUE_TYPE.EAGER
|
|
58
|
+
) {
|
|
59
|
+
await redefineProperty(object, info);
|
|
79
60
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const get = async () => {
|
|
102
|
-
tree ??= new ObjectMap(object);
|
|
103
|
-
const newState = Object.assign({}, state, { object: tree });
|
|
104
|
-
const result = await evaluate(code, newState);
|
|
105
|
-
return extname ? handleExtension(result, key, tree) : result;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
Object.defineProperty(object, key, {
|
|
109
|
-
configurable: true,
|
|
110
|
-
enumerable,
|
|
111
|
-
get,
|
|
112
|
-
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Third pass: define all computed properties. These may refer to the
|
|
64
|
+
// properties we just defined.
|
|
65
|
+
for (const info of infos) {
|
|
66
|
+
if (info.keyType === KEY_TYPE.COMPUTED) {
|
|
67
|
+
const newState = Object.assign({}, state, { object: map });
|
|
68
|
+
const key = await evaluate(/** @type {any} */ (info.key), newState);
|
|
69
|
+
// Destructively update the property info with the computed key
|
|
70
|
+
info.key = key;
|
|
71
|
+
defineProperty(object, info, state, map);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fourth pass: redefine eager computed-keyed properties with actual values.
|
|
76
|
+
for (const info of infos) {
|
|
77
|
+
if (
|
|
78
|
+
info.keyType === KEY_TYPE.COMPUTED &&
|
|
79
|
+
info.valueType === VALUE_TYPE.EAGER
|
|
80
|
+
) {
|
|
81
|
+
await redefineProperty(object, info);
|
|
113
82
|
}
|
|
114
83
|
}
|
|
115
84
|
|
|
116
|
-
// Attach a keys method
|
|
85
|
+
// Attach a keys method, where keys for primitive/eager properties with
|
|
86
|
+
// maplike values get a trailing slash.
|
|
117
87
|
Object.defineProperty(object, symbols.keys, {
|
|
118
88
|
configurable: true,
|
|
119
89
|
enumerable: false,
|
|
120
90
|
value: () =>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
eagerProperties,
|
|
125
|
-
propertyIsEnumerable,
|
|
126
|
-
entries
|
|
127
|
-
),
|
|
91
|
+
infos
|
|
92
|
+
.filter((info) => info.enumerable)
|
|
93
|
+
.map((info) => normalizeKey(info, object)),
|
|
128
94
|
writable: true,
|
|
129
95
|
});
|
|
130
96
|
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
97
|
+
// TODO: If there are any getters, mark the object as async. Note: this code
|
|
98
|
+
// was added so that Tree.from() could know whether to return an ObjectMap or
|
|
99
|
+
// a hypothetical AsyncObjectMap, which in turn would let a map operation know
|
|
100
|
+
// whether to expect async property values. const hasGetters =
|
|
101
|
+
// infos.some((info) => info.valueType === VALUE_TYPE.GETTER); if (hasGetters)
|
|
102
|
+
// { Object.defineProperty(object, symbols.async, { configurable: true,
|
|
103
|
+
// enumerable: false, value: true, writable: true,
|
|
104
|
+
// });
|
|
105
|
+
// }
|
|
106
|
+
|
|
107
|
+
return object;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Define a single property on the object
|
|
112
|
+
*/
|
|
113
|
+
function defineProperty(object, propertyInfo, state, map) {
|
|
114
|
+
let { enumerable, hasExtension, key, value, valueType } = propertyInfo;
|
|
115
|
+
if (valueType == VALUE_TYPE.PRIMITIVE) {
|
|
116
|
+
// Define simple property
|
|
136
117
|
Object.defineProperty(object, key, {
|
|
137
118
|
configurable: true,
|
|
138
119
|
enumerable,
|
|
139
120
|
value,
|
|
140
121
|
writable: true,
|
|
141
122
|
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (hasLazyProperties) {
|
|
146
|
-
Object.defineProperty(object, symbols.async, {
|
|
123
|
+
} else {
|
|
124
|
+
// Eager or getter; will evaluate eager property later
|
|
125
|
+
Object.defineProperty(object, key, {
|
|
147
126
|
configurable: true,
|
|
148
|
-
enumerable
|
|
149
|
-
|
|
150
|
-
|
|
127
|
+
enumerable,
|
|
128
|
+
get: async () => {
|
|
129
|
+
const newState = Object.assign({}, state, { object: map });
|
|
130
|
+
const result = await evaluate(value, newState);
|
|
131
|
+
return hasExtension ? handleExtension(result, key, map) : result;
|
|
132
|
+
},
|
|
151
133
|
});
|
|
152
134
|
}
|
|
153
|
-
|
|
154
|
-
return object;
|
|
155
135
|
}
|
|
156
136
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// constructor, but can happen in situations encountered by the compiler's
|
|
168
|
-
// optimizer.
|
|
169
|
-
key = key.slice(1, -1);
|
|
170
|
-
}
|
|
137
|
+
/**
|
|
138
|
+
* Return a normalized version of the property key for use in the keys() method.
|
|
139
|
+
* Among other things, this adds trailing slashes to keys that correspond to
|
|
140
|
+
* maplike values.
|
|
141
|
+
*
|
|
142
|
+
* @param {any} propertyInfo
|
|
143
|
+
* @param {object|null} [object]
|
|
144
|
+
*/
|
|
145
|
+
export function normalizeKey(propertyInfo, object = null) {
|
|
146
|
+
const { key, value, valueType } = propertyInfo;
|
|
171
147
|
|
|
172
148
|
if (trailingSlash.has(key)) {
|
|
173
149
|
// Explicit trailing slash, return as is
|
|
174
150
|
return key;
|
|
175
151
|
}
|
|
176
152
|
|
|
177
|
-
// If
|
|
178
|
-
if (
|
|
153
|
+
// If actual property value is maplike, add slash
|
|
154
|
+
if (
|
|
155
|
+
(valueType === VALUE_TYPE.EAGER || valueType === VALUE_TYPE.PRIMITIVE) &&
|
|
156
|
+
Tree.isMaplike(object?.[key])
|
|
157
|
+
) {
|
|
179
158
|
return trailingSlash.add(key);
|
|
180
159
|
}
|
|
181
160
|
|
|
161
|
+
// Look at value code to see if it will produce a maplike value
|
|
182
162
|
if (!(value instanceof Array)) {
|
|
183
163
|
// Can't be a subtree
|
|
184
164
|
return trailingSlash.remove(key);
|
|
185
165
|
}
|
|
186
|
-
|
|
187
|
-
// If we're dealing with a getter, work with what that gets
|
|
188
|
-
if (value[0] === ops.getter) {
|
|
189
|
-
value = value[1];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// If entry will definitely create a subtree, add a trailing slash
|
|
193
166
|
if (value[0] === ops.object) {
|
|
194
|
-
//
|
|
167
|
+
// Creates an object; maplike
|
|
195
168
|
return trailingSlash.add(key);
|
|
196
169
|
}
|
|
197
|
-
|
|
198
|
-
// See if it looks a merged object
|
|
199
170
|
if (value[1] === "_result" && value[0][0] === ops.object) {
|
|
200
|
-
//
|
|
171
|
+
// Merges an object; maplike
|
|
201
172
|
return trailingSlash.add(key);
|
|
202
173
|
}
|
|
203
174
|
|
|
175
|
+
// Return as is
|
|
204
176
|
return key;
|
|
205
177
|
}
|
|
206
178
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Given a key and the code for its value, determine some basic aspects of the
|
|
181
|
+
* property. This may return an updated key and/or value as well.
|
|
182
|
+
*/
|
|
183
|
+
export function propertyInfo(key, value) {
|
|
184
|
+
// If the key is wrapped in parentheses, it is not enumerable.
|
|
185
|
+
let enumerable = true;
|
|
186
|
+
if (
|
|
187
|
+
typeof key === "string" &&
|
|
188
|
+
key[0] === "(" &&
|
|
189
|
+
key[key.length - 1] === ")"
|
|
190
|
+
) {
|
|
191
|
+
key = key.slice(1, -1);
|
|
192
|
+
enumerable = false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const keyType = key instanceof Array ? KEY_TYPE.COMPUTED : KEY_TYPE.STRING;
|
|
196
|
+
|
|
197
|
+
let valueType;
|
|
198
|
+
if (!(value instanceof Array)) {
|
|
199
|
+
// Primitive, no code to evaluate
|
|
200
|
+
valueType = VALUE_TYPE.PRIMITIVE;
|
|
201
|
+
} else if (value[0] !== ops.getter) {
|
|
202
|
+
// Code will be eagerly evaluated when object is constructed
|
|
203
|
+
valueType = VALUE_TYPE.EAGER;
|
|
204
|
+
} else {
|
|
205
|
+
// Defined as a getter
|
|
206
|
+
value = value[1]; // The actual code
|
|
207
|
+
if (!(value instanceof Array)) {
|
|
208
|
+
// Getter returns a primitive value; treat as regular property
|
|
209
|
+
valueType = VALUE_TYPE.PRIMITIVE;
|
|
210
|
+
} else if (value[0] === ops.literal) {
|
|
211
|
+
// Getter returns a literal value; treat as eager property
|
|
212
|
+
valueType = VALUE_TYPE.EAGER;
|
|
213
|
+
} else {
|
|
214
|
+
valueType = VALUE_TYPE.GETTER;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Special case: a key with an extension has to be a getter
|
|
219
|
+
const hasExtension =
|
|
220
|
+
typeof key === "string" && extension.extname(key).length > 0;
|
|
221
|
+
if (hasExtension) {
|
|
222
|
+
valueType = VALUE_TYPE.GETTER;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return { enumerable, hasExtension, key, keyType, value, valueType };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get the value of the indicated eager property and overwrite the property
|
|
230
|
+
* definition with the actual value.
|
|
231
|
+
*/
|
|
232
|
+
async function redefineProperty(object, info) {
|
|
233
|
+
const value = await object[info.key];
|
|
234
|
+
Object.defineProperty(object, info.key, {
|
|
235
|
+
configurable: true,
|
|
236
|
+
enumerable: info.enumerable,
|
|
237
|
+
value,
|
|
238
|
+
writable: true,
|
|
239
|
+
});
|
|
225
240
|
}
|
|
@@ -21,7 +21,6 @@ let projectGlobals;
|
|
|
21
21
|
* @param {import("@weborigami/async-tree").SyncOrAsyncMap} [parent]
|
|
22
22
|
*/
|
|
23
23
|
export default async function handleExtension(value, key, parent) {
|
|
24
|
-
projectGlobals ??= await globals();
|
|
25
24
|
if (isPacked(value) && isStringlike(key) && value.unpack === undefined) {
|
|
26
25
|
const hasSlash = trailingSlash.has(key);
|
|
27
26
|
if (hasSlash) {
|
|
@@ -34,7 +33,8 @@ export default async function handleExtension(value, key, parent) {
|
|
|
34
33
|
: extension.extname(key);
|
|
35
34
|
if (extname) {
|
|
36
35
|
const handlerName = `${extname.slice(1)}_handler`;
|
|
37
|
-
|
|
36
|
+
const handlers = await getHandlers(parent);
|
|
37
|
+
let handler = await handlers[handlerName];
|
|
38
38
|
if (handler) {
|
|
39
39
|
if (isUnpackable(handler)) {
|
|
40
40
|
// The extension handler itself needs to be unpacked
|
|
@@ -66,3 +66,19 @@ export default async function handleExtension(value, key, parent) {
|
|
|
66
66
|
}
|
|
67
67
|
return value;
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
async function getHandlers(parent) {
|
|
71
|
+
// Walk up the parent chain to find first `handlers` property
|
|
72
|
+
let current = parent;
|
|
73
|
+
|
|
74
|
+
while (current) {
|
|
75
|
+
if (current.handlers) {
|
|
76
|
+
return current.handlers;
|
|
77
|
+
}
|
|
78
|
+
current = current.parent;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Fall back to project globals
|
|
82
|
+
projectGlobals ??= await globals();
|
|
83
|
+
return projectGlobals;
|
|
84
|
+
}
|
|
@@ -172,7 +172,7 @@ describe("Origami parser", () => {
|
|
|
172
172
|
ops.lambda,
|
|
173
173
|
0,
|
|
174
174
|
[],
|
|
175
|
-
[[markers.traverse, [markers.reference, "fn"]]
|
|
175
|
+
[[markers.traverse, [markers.reference, "fn"]]],
|
|
176
176
|
],
|
|
177
177
|
],
|
|
178
178
|
],
|
|
@@ -371,7 +371,7 @@ describe("Origami parser", () => {
|
|
|
371
371
|
describe("callExpression", () => {
|
|
372
372
|
test("call chains", () => {
|
|
373
373
|
assertParse("callExpression", "(foo.js())('arg')", [
|
|
374
|
-
[[markers.traverse, [markers.reference, "foo.js"]]
|
|
374
|
+
[[markers.traverse, [markers.reference, "foo.js"]]],
|
|
375
375
|
[ops.literal, "arg"],
|
|
376
376
|
]);
|
|
377
377
|
assertParse("callExpression", "fn('a')('b')", [
|
|
@@ -382,7 +382,7 @@ describe("Origami parser", () => {
|
|
|
382
382
|
[ops.literal, "b"],
|
|
383
383
|
]);
|
|
384
384
|
assertParse("callExpression", "(foo.js())(a, b)", [
|
|
385
|
-
[[markers.traverse, [markers.reference, "foo.js"]]
|
|
385
|
+
[[markers.traverse, [markers.reference, "foo.js"]]],
|
|
386
386
|
[markers.traverse, [markers.reference, "a"]],
|
|
387
387
|
[markers.traverse, [markers.reference, "b"]],
|
|
388
388
|
]);
|
|
@@ -546,7 +546,6 @@ describe("Origami parser", () => {
|
|
|
546
546
|
test("parentheses arguments", () => {
|
|
547
547
|
assertParse("callExpression", "fn()", [
|
|
548
548
|
[markers.traverse, [markers.reference, "fn"]],
|
|
549
|
-
undefined,
|
|
550
549
|
]);
|
|
551
550
|
assertParse("callExpression", "foo.js(arg)", [
|
|
552
551
|
[markers.traverse, [markers.reference, "foo.js"]],
|
|
@@ -563,7 +562,7 @@ describe("Origami parser", () => {
|
|
|
563
562
|
[markers.traverse, [markers.reference, "b"]],
|
|
564
563
|
]);
|
|
565
564
|
assertParse("callExpression", "fn()(arg)", [
|
|
566
|
-
[[markers.traverse, [markers.reference, "fn"]]
|
|
565
|
+
[[markers.traverse, [markers.reference, "fn"]]],
|
|
567
566
|
[markers.traverse, [markers.reference, "arg"]],
|
|
568
567
|
]);
|
|
569
568
|
});
|
|
@@ -617,19 +616,14 @@ describe("Origami parser", () => {
|
|
|
617
616
|
|
|
618
617
|
test("path and parentheses chains", () => {
|
|
619
618
|
assertParse("callExpression", "foo.js()/key", [
|
|
620
|
-
[[markers.traverse, [markers.reference, "foo.js"]]
|
|
619
|
+
[[markers.traverse, [markers.reference, "foo.js"]]],
|
|
621
620
|
[ops.literal, "key"],
|
|
622
621
|
]);
|
|
623
622
|
assertParse("callExpression", "tree/key()", [
|
|
624
623
|
[markers.traverse, [markers.reference, "tree/"], [ops.literal, "key"]],
|
|
625
|
-
undefined,
|
|
626
624
|
]);
|
|
627
625
|
assertParse("callExpression", "fn()/key()", [
|
|
628
|
-
[
|
|
629
|
-
[[markers.traverse, [markers.reference, "fn"]], undefined],
|
|
630
|
-
[ops.literal, "key"],
|
|
631
|
-
],
|
|
632
|
-
undefined,
|
|
626
|
+
[[[markers.traverse, [markers.reference, "fn"]]], [ops.literal, "key"]],
|
|
633
627
|
]);
|
|
634
628
|
assertParse("callExpression", "package:@weborigami/dropbox/auth(creds)", [
|
|
635
629
|
[
|
|
@@ -1035,7 +1029,7 @@ Body`,
|
|
|
1035
1029
|
ops.lambda,
|
|
1036
1030
|
1,
|
|
1037
1031
|
[["name", [[ops.params, 0], 0]]],
|
|
1038
|
-
[[markers.traverse, [markers.reference, "_template"]]
|
|
1032
|
+
[[markers.traverse, [markers.reference, "_template"]]],
|
|
1039
1033
|
],
|
|
1040
1034
|
"program",
|
|
1041
1035
|
false
|
|
@@ -1053,7 +1047,6 @@ Body`,
|
|
|
1053
1047
|
]);
|
|
1054
1048
|
assertParse("group", "(fn())", [
|
|
1055
1049
|
[markers.traverse, [markers.reference, "fn"]],
|
|
1056
|
-
undefined,
|
|
1057
1050
|
]);
|
|
1058
1051
|
assertParse("group", "(a -> b)", [
|
|
1059
1052
|
[markers.traverse, [markers.reference, "b"]],
|
|
@@ -1105,7 +1098,7 @@ Body`,
|
|
|
1105
1098
|
[markers.traverse, [markers.reference, "c"]],
|
|
1106
1099
|
]);
|
|
1107
1100
|
assertParse("implicitParenthesesCallExpression", "(fn()) 'arg'", [
|
|
1108
|
-
[[markers.traverse, [markers.reference, "fn"]]
|
|
1101
|
+
[[markers.traverse, [markers.reference, "fn"]]],
|
|
1109
1102
|
[ops.literal, "arg"],
|
|
1110
1103
|
]);
|
|
1111
1104
|
assertParse(
|
|
@@ -1311,10 +1304,7 @@ Body`,
|
|
|
1311
1304
|
ops.object,
|
|
1312
1305
|
[
|
|
1313
1306
|
"b",
|
|
1314
|
-
[
|
|
1315
|
-
ops.getter,
|
|
1316
|
-
[[markers.traverse, [markers.reference, "fn"]], undefined],
|
|
1317
|
-
],
|
|
1307
|
+
[ops.getter, [[markers.traverse, [markers.reference, "fn"]]]],
|
|
1318
1308
|
],
|
|
1319
1309
|
],
|
|
1320
1310
|
],
|
|
@@ -1506,7 +1496,7 @@ Body`,
|
|
|
1506
1496
|
});
|
|
1507
1497
|
|
|
1508
1498
|
test("parenthesesArguments", () => {
|
|
1509
|
-
assertParse("parenthesesArguments", "()", [
|
|
1499
|
+
assertParse("parenthesesArguments", "()", []);
|
|
1510
1500
|
assertParse("parenthesesArguments", "(a, b, c)", [
|
|
1511
1501
|
[markers.traverse, [markers.reference, "a"]],
|
|
1512
1502
|
[markers.traverse, [markers.reference, "b"]],
|
|
@@ -2,9 +2,9 @@ import assert from "node:assert";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { describe, test } from "node:test";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import
|
|
5
|
+
import projectRootFromPath from "../../src/project/projectRootFromPath.js";
|
|
6
6
|
|
|
7
|
-
describe("
|
|
7
|
+
describe("projectRootFromPath", () => {
|
|
8
8
|
test("finds Origami configuration file", async () => {
|
|
9
9
|
// Find the folder that represents the project root.
|
|
10
10
|
const projectUrl = new URL("fixtures/withConfig/", import.meta.url);
|
|
@@ -12,7 +12,7 @@ describe("projectRoot", () => {
|
|
|
12
12
|
const subfolderUrl = new URL("./subfolder/", projectUrl);
|
|
13
13
|
const subfolderPath = fileURLToPath(subfolderUrl);
|
|
14
14
|
|
|
15
|
-
const root = await
|
|
15
|
+
const root = await projectRootFromPath(subfolderPath);
|
|
16
16
|
|
|
17
17
|
// Get result path, it'll need a trailing slash to compare.
|
|
18
18
|
const resultPath = root.path + path.sep;
|
|
@@ -26,15 +26,15 @@ describe("projectRoot", () => {
|
|
|
26
26
|
const subfolderUrl = new URL("./subfolder/", projectUrl);
|
|
27
27
|
const subfolderPath = fileURLToPath(subfolderUrl);
|
|
28
28
|
|
|
29
|
-
const root = await
|
|
29
|
+
const root = await projectRootFromPath(subfolderPath);
|
|
30
30
|
|
|
31
31
|
// Get result path, it'll need a trailing slash to compare.
|
|
32
32
|
const resultPath = root.path + path.sep;
|
|
33
33
|
assert.equal(resultPath, fileURLToPath(projectUrl));
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
test("defaults to
|
|
37
|
-
const root = await
|
|
38
|
-
assert.equal(root.path,
|
|
36
|
+
test("defaults to supplied directory", async () => {
|
|
37
|
+
const root = await projectRootFromPath("/");
|
|
38
|
+
assert.equal(root.path, "/");
|
|
39
39
|
});
|
|
40
40
|
});
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
+
import projectRootFromPath from "../../src/project/projectRootFromPath.js";
|
|
3
4
|
import packageProtocol from "../../src/protocols/package.js";
|
|
4
5
|
|
|
5
6
|
describe("package: protocol", () => {
|
|
6
7
|
test("returns a package's main export(s)", async () => {
|
|
7
|
-
|
|
8
|
+
// Reproduce the type of evaluation context object the runtime would create
|
|
9
|
+
const projectRoot = await projectRootFromPath();
|
|
10
|
+
const context = { container: projectRoot };
|
|
11
|
+
|
|
12
|
+
const result = await packageProtocol("@weborigami", "async-tree", context);
|
|
8
13
|
const { toString } = result;
|
|
9
14
|
assert.equal(toString(123), "123");
|
|
10
15
|
});
|
|
@@ -53,11 +53,26 @@ describe("expressionObject", () => {
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
test("can compute a property key", async () => {
|
|
56
|
-
const entries = [
|
|
56
|
+
const entries = [
|
|
57
|
+
[
|
|
58
|
+
[
|
|
59
|
+
ops.concat,
|
|
60
|
+
[
|
|
61
|
+
[ops.inherited, 0],
|
|
62
|
+
[ops.literal, "name"], // references `name` on same object
|
|
63
|
+
],
|
|
64
|
+
".json",
|
|
65
|
+
],
|
|
66
|
+
1,
|
|
67
|
+
],
|
|
68
|
+
["name", "data"],
|
|
69
|
+
];
|
|
57
70
|
const context = new SyncMap();
|
|
58
71
|
const object = await expressionObject(entries, { object: context });
|
|
59
|
-
assert.
|
|
60
|
-
|
|
72
|
+
assert.deepEqual(await Tree.plain(object), {
|
|
73
|
+
"data.json": 1,
|
|
74
|
+
name: "data",
|
|
75
|
+
});
|
|
61
76
|
});
|
|
62
77
|
|
|
63
78
|
test("returned object values can be unpacked", async () => {
|
|
@@ -79,7 +94,7 @@ describe("expressionObject", () => {
|
|
|
79
94
|
assert.equal(object["hidden"], "shh");
|
|
80
95
|
});
|
|
81
96
|
|
|
82
|
-
test("provides a symbols.keys method", async () => {
|
|
97
|
+
test("provides a symbols.keys method returning normalized keys", async () => {
|
|
83
98
|
const entries = [
|
|
84
99
|
// Will return a tree, should have a slash
|
|
85
100
|
["getter", [ops.getter, [ops.object, ["b", [ops.literal, 2]]]]],
|
|
@@ -100,11 +115,11 @@ describe("expressionObject", () => {
|
|
|
100
115
|
]);
|
|
101
116
|
});
|
|
102
117
|
|
|
103
|
-
test("sets symbols.async on objects with getters", async () => {
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
// test("sets symbols.async on objects with getters", async () => {
|
|
119
|
+
// const noGetter = await expressionObject([["eager", 1]]);
|
|
120
|
+
// assert.equal(noGetter[symbols.async], undefined);
|
|
106
121
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
});
|
|
122
|
+
// const hasGetter = await expressionObject([["lazy", [ops.getter, [2]]]]);
|
|
123
|
+
// assert.equal(hasGetter[symbols.async], true);
|
|
124
|
+
// });
|
|
110
125
|
});
|