@valbuild/server 0.146.4 → 0.148.0
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/CHANGELOG.md +129 -0
- package/browser/dist/valbuild-server-browser.cjs.d.ts +2 -0
- package/browser/dist/valbuild-server-browser.cjs.dev.js +146 -0
- package/browser/dist/valbuild-server-browser.cjs.js +7 -0
- package/browser/dist/valbuild-server-browser.cjs.prod.js +146 -0
- package/browser/dist/valbuild-server-browser.esm.js +142 -0
- package/browser/package.json +4 -0
- package/dist/ValRouterCore-42772c21.cjs.prod.js +10824 -0
- package/dist/ValRouterCore-7ca3dc68.esm.js +10784 -0
- package/dist/ValRouterCore-9bdaaf89.cjs.dev.js +10824 -0
- package/dist/declarations/src/ValOps.d.ts +44 -11
- package/dist/declarations/src/ValOpsFS.d.ts +1 -0
- package/dist/declarations/src/ValOpsHttp.d.ts +1 -0
- package/dist/declarations/src/ValOpsMemory.d.ts +13 -4
- package/dist/declarations/src/ValRouter.d.ts +1 -1
- package/dist/declarations/src/ValServer.d.ts +9 -3
- package/dist/declarations/src/browser/inTabFetch.d.ts +27 -0
- package/dist/declarations/src/browser/index.d.ts +9 -0
- package/dist/declarations/src/checkRemoteRef.d.ts +40 -2
- package/dist/declarations/src/createFixPatch.d.ts +25 -3
- package/dist/declarations/src/fixFiles.d.ts +42 -0
- package/dist/declarations/src/fixHandlers.d.ts +15 -1
- package/dist/declarations/src/getSettings.d.ts +1 -0
- package/dist/declarations/src/index.d.ts +1 -0
- package/dist/declarations/src/linkCheck/probeUrl.d.ts +40 -0
- package/dist/declarations/src/platform.d.ts +55 -0
- package/dist/declarations/src/remoteFileCheck.d.ts +116 -0
- package/dist/declarations/src/remoteHostCheck.d.ts +101 -0
- package/dist/declarations/src/studioValidation.d.ts +84 -0
- package/dist/declarations/src/uploadRemoteFile.d.ts +1 -1
- package/dist/declarations/src/videosetFixes.d.ts +3 -2
- package/dist/getJsonEntryAtCommit-2e36e805.cjs.prod.js +142 -0
- package/dist/getJsonEntryAtCommit-40b6d9c1.esm.js +135 -0
- package/dist/getJsonEntryAtCommit-64f67a50.cjs.dev.js +142 -0
- package/dist/jsonValuesModule-427d68c8.esm.js +273 -0
- package/dist/jsonValuesModule-8ae503a8.cjs.dev.js +288 -0
- package/dist/jsonValuesModule-d1bb8d8f.cjs.prod.js +288 -0
- package/dist/ops-0d05feaf.esm.js +353 -0
- package/dist/ops-b15239ac.cjs.prod.js +361 -0
- package/dist/ops-f37d18c6.cjs.dev.js +361 -0
- package/dist/publishKit-580f558a.cjs.prod.js +41 -0
- package/dist/publishKit-5865095c.cjs.dev.js +41 -0
- package/dist/publishKit-bc8df53d.esm.js +31 -0
- package/dist/valbuild-server.cjs.dev.js +9239 -18895
- package/dist/valbuild-server.cjs.prod.js +9239 -18895
- package/dist/valbuild-server.esm.js +9107 -18763
- package/package.json +17 -5
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ts = require('typescript');
|
|
4
|
+
var fp = require('@valbuild/core/fp');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
|
7
|
+
|
|
8
|
+
var ts__default = /*#__PURE__*/_interopDefault(ts);
|
|
9
|
+
|
|
10
|
+
class ValSyntaxError {
|
|
11
|
+
constructor(message, node) {
|
|
12
|
+
this.message = message;
|
|
13
|
+
this.node = node;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function forEachError(tree, callback) {
|
|
17
|
+
if (Array.isArray(tree)) {
|
|
18
|
+
for (const subtree of tree) {
|
|
19
|
+
forEachError(subtree, callback);
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
callback(tree);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function flatMapErrors(tree, cb) {
|
|
26
|
+
const result = [];
|
|
27
|
+
forEachError(tree, error => result.push(cb(error)));
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
function formatSyntaxError(error, sourceFile) {
|
|
31
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(error.node.pos);
|
|
32
|
+
return `${pos.line}:${pos.character} ${error.message}`;
|
|
33
|
+
}
|
|
34
|
+
function formatSyntaxErrorTree(tree, sourceFile) {
|
|
35
|
+
return flatMapErrors(tree, error => formatSyntaxError(error, sourceFile));
|
|
36
|
+
}
|
|
37
|
+
function isLiteralPropertyName(name) {
|
|
38
|
+
return ts__default["default"].isIdentifier(name) || ts__default["default"].isStringLiteral(name) || ts__default["default"].isNumericLiteral(name);
|
|
39
|
+
}
|
|
40
|
+
function validateObjectProperties(nodes) {
|
|
41
|
+
const errors = [];
|
|
42
|
+
for (const node of nodes) {
|
|
43
|
+
if (!ts__default["default"].isPropertyAssignment(node)) {
|
|
44
|
+
errors.push(new ValSyntaxError("Object literal element must be property assignment", node));
|
|
45
|
+
} else if (!isLiteralPropertyName(node.name)) {
|
|
46
|
+
errors.push(new ValSyntaxError("Object literal element key must be an identifier or a literal", node));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (errors.length > 0) {
|
|
50
|
+
return fp.result.err(errors);
|
|
51
|
+
}
|
|
52
|
+
return fp.result.ok(nodes);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validates that the expression is a JSON compatible type of expression without
|
|
57
|
+
* validating its children.
|
|
58
|
+
*/
|
|
59
|
+
function shallowValidateExpression(value) {
|
|
60
|
+
return ts__default["default"].isStringLiteralLike(value) || ts__default["default"].isNumericLiteral(value) || value.kind === ts__default["default"].SyntaxKind.TrueKeyword || value.kind === ts__default["default"].SyntaxKind.FalseKeyword || value.kind === ts__default["default"].SyntaxKind.NullKeyword || ts__default["default"].isArrayLiteralExpression(value) || ts__default["default"].isObjectLiteralExpression(value) ? undefined : new ValSyntaxError("Expression must be a literal", value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Evaluates the expression as a JSON value
|
|
65
|
+
*/
|
|
66
|
+
function evaluateExpression(value) {
|
|
67
|
+
// The text property of a LiteralExpression stores the interpreted value of the literal in text form. For a StringLiteral,
|
|
68
|
+
// or any literal of a template, this means quotes have been removed and escapes have been converted to actual characters.
|
|
69
|
+
// For a NumericLiteral, the stored value is the toString() representation of the number. For example 1, 1.00, and 1e0 are all stored as just "1".
|
|
70
|
+
// https://github.com/microsoft/TypeScript/blob/4b794fe1dd0d184d3f8f17e94d8187eace57c91e/src/compiler/types.ts#L2127-L2131
|
|
71
|
+
|
|
72
|
+
if (ts__default["default"].isStringLiteralLike(value)) {
|
|
73
|
+
return fp.result.ok(value.text);
|
|
74
|
+
} else if (ts__default["default"].isNumericLiteral(value)) {
|
|
75
|
+
return fp.result.ok(Number(value.text));
|
|
76
|
+
} else if (value.kind === ts__default["default"].SyntaxKind.TrueKeyword) {
|
|
77
|
+
return fp.result.ok(true);
|
|
78
|
+
} else if (value.kind === ts__default["default"].SyntaxKind.FalseKeyword) {
|
|
79
|
+
return fp.result.ok(false);
|
|
80
|
+
} else if (value.kind === ts__default["default"].SyntaxKind.NullKeyword) {
|
|
81
|
+
return fp.result.ok(null);
|
|
82
|
+
} else if (ts__default["default"].isArrayLiteralExpression(value)) {
|
|
83
|
+
return fp.result.all(value.elements.map(evaluateExpression));
|
|
84
|
+
} else if (ts__default["default"].isObjectLiteralExpression(value)) {
|
|
85
|
+
return fp.pipe(validateObjectProperties(value.properties), fp.result.flatMap(assignments => fp.pipe(assignments.map(assignment => fp.pipe(evaluateExpression(assignment.initializer), fp.result.map(value => [assignment.name.text, value]))), fp.result.all)), fp.result.map(Object.fromEntries));
|
|
86
|
+
} else {
|
|
87
|
+
return fp.result.err(new ValSyntaxError("Expression must be a literal", value));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function findObjectPropertyAssignment(value, key) {
|
|
91
|
+
return fp.pipe(validateObjectProperties(value.properties), fp.result.flatMap(assignments => {
|
|
92
|
+
const matchingAssignments = assignments.filter(assignment => assignment.name.text === key);
|
|
93
|
+
if (matchingAssignments.length === 0) return fp.result.ok(undefined);
|
|
94
|
+
if (matchingAssignments.length > 1) {
|
|
95
|
+
return fp.result.err(new ValSyntaxError(`Object key "${key}" is ambiguous`, value));
|
|
96
|
+
}
|
|
97
|
+
const [assignment] = matchingAssignments;
|
|
98
|
+
return fp.result.ok(assignment);
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
function validateInitializers(nodes) {
|
|
102
|
+
for (const node of nodes) {
|
|
103
|
+
if (ts__default["default"].isSpreadElement(node)) {
|
|
104
|
+
return new ValSyntaxError("Unexpected spread element", node);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isPath(node, path) {
|
|
111
|
+
let currentNode = node;
|
|
112
|
+
for (let i = path.length - 1; i > 0; --i) {
|
|
113
|
+
const name = path[i];
|
|
114
|
+
if (!ts__default["default"].isPropertyAccessExpression(currentNode)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (!ts__default["default"].isIdentifier(currentNode.name) || currentNode.name.text !== name) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
currentNode = currentNode.expression;
|
|
121
|
+
}
|
|
122
|
+
return ts__default["default"].isIdentifier(currentNode) && currentNode.text === path[0];
|
|
123
|
+
}
|
|
124
|
+
function validateArguments(node, validators) {
|
|
125
|
+
return fp.result.allV([node.arguments.length === validators.length ? fp.result.voidOk : fp.result.err(new ValSyntaxError(`Expected ${validators.length} arguments`, node)), ...node.arguments.slice(0, validators.length).map((argument, index) => validators[index](argument))]);
|
|
126
|
+
}
|
|
127
|
+
function analyzeDefaultExport(node) {
|
|
128
|
+
const cDefine = node.expression;
|
|
129
|
+
if (!ts__default["default"].isCallExpression(cDefine)) {
|
|
130
|
+
return fp.result.err(new ValSyntaxError("Expected default expression to be a call expression", cDefine));
|
|
131
|
+
}
|
|
132
|
+
if (!isPath(cDefine.expression, ["c", "define"])) {
|
|
133
|
+
return fp.result.err(new ValSyntaxError("Expected default expression to be calling c.define", cDefine.expression));
|
|
134
|
+
}
|
|
135
|
+
return fp.pipe(validateArguments(cDefine, [id => {
|
|
136
|
+
// TODO: validate ID value here?
|
|
137
|
+
if (!ts__default["default"].isStringLiteralLike(id)) {
|
|
138
|
+
return fp.result.err(new ValSyntaxError("Expected first argument to c.define to be a string literal", id));
|
|
139
|
+
}
|
|
140
|
+
return fp.result.voidOk;
|
|
141
|
+
}, () => {
|
|
142
|
+
return fp.result.voidOk;
|
|
143
|
+
}, () => {
|
|
144
|
+
return fp.result.voidOk;
|
|
145
|
+
}]), fp.result.map(() => {
|
|
146
|
+
const [, schema, source] = cDefine.arguments;
|
|
147
|
+
return {
|
|
148
|
+
schema,
|
|
149
|
+
source
|
|
150
|
+
};
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
function analyzeValModule(sourceFile) {
|
|
154
|
+
const analysis = sourceFile.forEachChild(node => {
|
|
155
|
+
if (ts__default["default"].isExportAssignment(node)) {
|
|
156
|
+
return analyzeDefaultExport(node);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (!analysis) {
|
|
160
|
+
throw Error("Failed to find fixed content node in val module");
|
|
161
|
+
}
|
|
162
|
+
return analysis;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Given the `source` object-literal expression of a `.jsonValues()` record /
|
|
167
|
+
* router (the 3rd argument to `c.define`, via {@link analyzeValModule}),
|
|
168
|
+
* extracts each entry's `c.json(() => import("..."))` import path, keyed by
|
|
169
|
+
* entry key. Entries whose value is not a `c.json(...)` call are skipped.
|
|
170
|
+
*/
|
|
171
|
+
function analyzeJsonValuesEntries(source) {
|
|
172
|
+
const entries = new Map();
|
|
173
|
+
if (!ts__default["default"].isObjectLiteralExpression(source)) {
|
|
174
|
+
return entries;
|
|
175
|
+
}
|
|
176
|
+
for (const prop of source.properties) {
|
|
177
|
+
if (!ts__default["default"].isPropertyAssignment(prop)) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const key = getPropertyKey(prop.name);
|
|
181
|
+
if (key === null) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const entry = analyzeCJsonCall(prop.initializer);
|
|
185
|
+
if (entry) {
|
|
186
|
+
entries.set(key, entry);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return entries;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Every key of a `.jsonValues()` record's source literal, thunked or not.
|
|
194
|
+
*
|
|
195
|
+
* {@link analyzeJsonValuesEntries} answers "which entries have a file behind
|
|
196
|
+
* them", which is the question for resolving a path. This is the other one:
|
|
197
|
+
* which keys the record HAS. They differ on a value that is not a
|
|
198
|
+
* `c.json(...)` call - written inline, or mid-edit - and telling an existing
|
|
199
|
+
* key from a new one is what decides whether a whole-record write adds a
|
|
200
|
+
* `c.json(...)` thunk or replaces one. Using the thunk set for that would
|
|
201
|
+
* insert a second property with a key the literal already has.
|
|
202
|
+
*/
|
|
203
|
+
function jsonValuesRecordKeys(source) {
|
|
204
|
+
const keys = [];
|
|
205
|
+
if (!ts__default["default"].isObjectLiteralExpression(source)) {
|
|
206
|
+
return keys;
|
|
207
|
+
}
|
|
208
|
+
for (const prop of source.properties) {
|
|
209
|
+
if (!ts__default["default"].isPropertyAssignment(prop)) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const key = getPropertyKey(prop.name);
|
|
213
|
+
if (key !== null) {
|
|
214
|
+
keys.push(key);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return keys;
|
|
218
|
+
}
|
|
219
|
+
function getPropertyKey(name) {
|
|
220
|
+
if (ts__default["default"].isStringLiteralLike(name)) {
|
|
221
|
+
return name.text;
|
|
222
|
+
}
|
|
223
|
+
if (ts__default["default"].isIdentifier(name)) {
|
|
224
|
+
return name.text;
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
function analyzeCJsonCall(node) {
|
|
229
|
+
// c.json(() => import("path"))
|
|
230
|
+
if (!ts__default["default"].isCallExpression(node) || !isCJson(node.expression)) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
const [thunk] = node.arguments;
|
|
234
|
+
if (!thunk) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const importPath = getImportPathFromThunk(thunk);
|
|
238
|
+
if (importPath === null) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
importPath
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function isCJson(expr) {
|
|
246
|
+
// c.json
|
|
247
|
+
return ts__default["default"].isPropertyAccessExpression(expr) && ts__default["default"].isIdentifier(expr.expression) && expr.expression.text === "c" && ts__default["default"].isIdentifier(expr.name) && expr.name.text === "json";
|
|
248
|
+
}
|
|
249
|
+
function getImportPathFromThunk(thunk) {
|
|
250
|
+
// () => import("path") (concise body) OR () => { return import("path"); }
|
|
251
|
+
if (!ts__default["default"].isArrowFunction(thunk)) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
let importCall;
|
|
255
|
+
if (ts__default["default"].isCallExpression(thunk.body)) {
|
|
256
|
+
importCall = thunk.body;
|
|
257
|
+
} else if (ts__default["default"].isBlock(thunk.body)) {
|
|
258
|
+
for (const stmt of thunk.body.statements) {
|
|
259
|
+
if (ts__default["default"].isReturnStatement(stmt) && stmt.expression && ts__default["default"].isCallExpression(stmt.expression)) {
|
|
260
|
+
importCall = stmt.expression;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!importCall || !ts__default["default"].isCallExpression(importCall)) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const isImport = importCall.expression.kind === ts__default["default"].SyntaxKind.ImportKeyword || ts__default["default"].isIdentifier(importCall.expression) && importCall.expression.text === "import";
|
|
269
|
+
if (!isImport) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const arg = importCall.arguments[0];
|
|
273
|
+
if (arg && ts__default["default"].isStringLiteralLike(arg)) {
|
|
274
|
+
return arg.text;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
exports.analyzeJsonValuesEntries = analyzeJsonValuesEntries;
|
|
280
|
+
exports.analyzeValModule = analyzeValModule;
|
|
281
|
+
exports.evaluateExpression = evaluateExpression;
|
|
282
|
+
exports.findObjectPropertyAssignment = findObjectPropertyAssignment;
|
|
283
|
+
exports.flatMapErrors = flatMapErrors;
|
|
284
|
+
exports.formatSyntaxError = formatSyntaxError;
|
|
285
|
+
exports.formatSyntaxErrorTree = formatSyntaxErrorTree;
|
|
286
|
+
exports.jsonValuesRecordKeys = jsonValuesRecordKeys;
|
|
287
|
+
exports.shallowValidateExpression = shallowValidateExpression;
|
|
288
|
+
exports.validateInitializers = validateInitializers;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ts = require('typescript');
|
|
4
|
+
var fp = require('@valbuild/core/fp');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
|
7
|
+
|
|
8
|
+
var ts__default = /*#__PURE__*/_interopDefault(ts);
|
|
9
|
+
|
|
10
|
+
class ValSyntaxError {
|
|
11
|
+
constructor(message, node) {
|
|
12
|
+
this.message = message;
|
|
13
|
+
this.node = node;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function forEachError(tree, callback) {
|
|
17
|
+
if (Array.isArray(tree)) {
|
|
18
|
+
for (const subtree of tree) {
|
|
19
|
+
forEachError(subtree, callback);
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
callback(tree);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function flatMapErrors(tree, cb) {
|
|
26
|
+
const result = [];
|
|
27
|
+
forEachError(tree, error => result.push(cb(error)));
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
function formatSyntaxError(error, sourceFile) {
|
|
31
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(error.node.pos);
|
|
32
|
+
return `${pos.line}:${pos.character} ${error.message}`;
|
|
33
|
+
}
|
|
34
|
+
function formatSyntaxErrorTree(tree, sourceFile) {
|
|
35
|
+
return flatMapErrors(tree, error => formatSyntaxError(error, sourceFile));
|
|
36
|
+
}
|
|
37
|
+
function isLiteralPropertyName(name) {
|
|
38
|
+
return ts__default["default"].isIdentifier(name) || ts__default["default"].isStringLiteral(name) || ts__default["default"].isNumericLiteral(name);
|
|
39
|
+
}
|
|
40
|
+
function validateObjectProperties(nodes) {
|
|
41
|
+
const errors = [];
|
|
42
|
+
for (const node of nodes) {
|
|
43
|
+
if (!ts__default["default"].isPropertyAssignment(node)) {
|
|
44
|
+
errors.push(new ValSyntaxError("Object literal element must be property assignment", node));
|
|
45
|
+
} else if (!isLiteralPropertyName(node.name)) {
|
|
46
|
+
errors.push(new ValSyntaxError("Object literal element key must be an identifier or a literal", node));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (errors.length > 0) {
|
|
50
|
+
return fp.result.err(errors);
|
|
51
|
+
}
|
|
52
|
+
return fp.result.ok(nodes);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validates that the expression is a JSON compatible type of expression without
|
|
57
|
+
* validating its children.
|
|
58
|
+
*/
|
|
59
|
+
function shallowValidateExpression(value) {
|
|
60
|
+
return ts__default["default"].isStringLiteralLike(value) || ts__default["default"].isNumericLiteral(value) || value.kind === ts__default["default"].SyntaxKind.TrueKeyword || value.kind === ts__default["default"].SyntaxKind.FalseKeyword || value.kind === ts__default["default"].SyntaxKind.NullKeyword || ts__default["default"].isArrayLiteralExpression(value) || ts__default["default"].isObjectLiteralExpression(value) ? undefined : new ValSyntaxError("Expression must be a literal", value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Evaluates the expression as a JSON value
|
|
65
|
+
*/
|
|
66
|
+
function evaluateExpression(value) {
|
|
67
|
+
// The text property of a LiteralExpression stores the interpreted value of the literal in text form. For a StringLiteral,
|
|
68
|
+
// or any literal of a template, this means quotes have been removed and escapes have been converted to actual characters.
|
|
69
|
+
// For a NumericLiteral, the stored value is the toString() representation of the number. For example 1, 1.00, and 1e0 are all stored as just "1".
|
|
70
|
+
// https://github.com/microsoft/TypeScript/blob/4b794fe1dd0d184d3f8f17e94d8187eace57c91e/src/compiler/types.ts#L2127-L2131
|
|
71
|
+
|
|
72
|
+
if (ts__default["default"].isStringLiteralLike(value)) {
|
|
73
|
+
return fp.result.ok(value.text);
|
|
74
|
+
} else if (ts__default["default"].isNumericLiteral(value)) {
|
|
75
|
+
return fp.result.ok(Number(value.text));
|
|
76
|
+
} else if (value.kind === ts__default["default"].SyntaxKind.TrueKeyword) {
|
|
77
|
+
return fp.result.ok(true);
|
|
78
|
+
} else if (value.kind === ts__default["default"].SyntaxKind.FalseKeyword) {
|
|
79
|
+
return fp.result.ok(false);
|
|
80
|
+
} else if (value.kind === ts__default["default"].SyntaxKind.NullKeyword) {
|
|
81
|
+
return fp.result.ok(null);
|
|
82
|
+
} else if (ts__default["default"].isArrayLiteralExpression(value)) {
|
|
83
|
+
return fp.result.all(value.elements.map(evaluateExpression));
|
|
84
|
+
} else if (ts__default["default"].isObjectLiteralExpression(value)) {
|
|
85
|
+
return fp.pipe(validateObjectProperties(value.properties), fp.result.flatMap(assignments => fp.pipe(assignments.map(assignment => fp.pipe(evaluateExpression(assignment.initializer), fp.result.map(value => [assignment.name.text, value]))), fp.result.all)), fp.result.map(Object.fromEntries));
|
|
86
|
+
} else {
|
|
87
|
+
return fp.result.err(new ValSyntaxError("Expression must be a literal", value));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function findObjectPropertyAssignment(value, key) {
|
|
91
|
+
return fp.pipe(validateObjectProperties(value.properties), fp.result.flatMap(assignments => {
|
|
92
|
+
const matchingAssignments = assignments.filter(assignment => assignment.name.text === key);
|
|
93
|
+
if (matchingAssignments.length === 0) return fp.result.ok(undefined);
|
|
94
|
+
if (matchingAssignments.length > 1) {
|
|
95
|
+
return fp.result.err(new ValSyntaxError(`Object key "${key}" is ambiguous`, value));
|
|
96
|
+
}
|
|
97
|
+
const [assignment] = matchingAssignments;
|
|
98
|
+
return fp.result.ok(assignment);
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
function validateInitializers(nodes) {
|
|
102
|
+
for (const node of nodes) {
|
|
103
|
+
if (ts__default["default"].isSpreadElement(node)) {
|
|
104
|
+
return new ValSyntaxError("Unexpected spread element", node);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isPath(node, path) {
|
|
111
|
+
let currentNode = node;
|
|
112
|
+
for (let i = path.length - 1; i > 0; --i) {
|
|
113
|
+
const name = path[i];
|
|
114
|
+
if (!ts__default["default"].isPropertyAccessExpression(currentNode)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (!ts__default["default"].isIdentifier(currentNode.name) || currentNode.name.text !== name) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
currentNode = currentNode.expression;
|
|
121
|
+
}
|
|
122
|
+
return ts__default["default"].isIdentifier(currentNode) && currentNode.text === path[0];
|
|
123
|
+
}
|
|
124
|
+
function validateArguments(node, validators) {
|
|
125
|
+
return fp.result.allV([node.arguments.length === validators.length ? fp.result.voidOk : fp.result.err(new ValSyntaxError(`Expected ${validators.length} arguments`, node)), ...node.arguments.slice(0, validators.length).map((argument, index) => validators[index](argument))]);
|
|
126
|
+
}
|
|
127
|
+
function analyzeDefaultExport(node) {
|
|
128
|
+
const cDefine = node.expression;
|
|
129
|
+
if (!ts__default["default"].isCallExpression(cDefine)) {
|
|
130
|
+
return fp.result.err(new ValSyntaxError("Expected default expression to be a call expression", cDefine));
|
|
131
|
+
}
|
|
132
|
+
if (!isPath(cDefine.expression, ["c", "define"])) {
|
|
133
|
+
return fp.result.err(new ValSyntaxError("Expected default expression to be calling c.define", cDefine.expression));
|
|
134
|
+
}
|
|
135
|
+
return fp.pipe(validateArguments(cDefine, [id => {
|
|
136
|
+
// TODO: validate ID value here?
|
|
137
|
+
if (!ts__default["default"].isStringLiteralLike(id)) {
|
|
138
|
+
return fp.result.err(new ValSyntaxError("Expected first argument to c.define to be a string literal", id));
|
|
139
|
+
}
|
|
140
|
+
return fp.result.voidOk;
|
|
141
|
+
}, () => {
|
|
142
|
+
return fp.result.voidOk;
|
|
143
|
+
}, () => {
|
|
144
|
+
return fp.result.voidOk;
|
|
145
|
+
}]), fp.result.map(() => {
|
|
146
|
+
const [, schema, source] = cDefine.arguments;
|
|
147
|
+
return {
|
|
148
|
+
schema,
|
|
149
|
+
source
|
|
150
|
+
};
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
function analyzeValModule(sourceFile) {
|
|
154
|
+
const analysis = sourceFile.forEachChild(node => {
|
|
155
|
+
if (ts__default["default"].isExportAssignment(node)) {
|
|
156
|
+
return analyzeDefaultExport(node);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (!analysis) {
|
|
160
|
+
throw Error("Failed to find fixed content node in val module");
|
|
161
|
+
}
|
|
162
|
+
return analysis;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Given the `source` object-literal expression of a `.jsonValues()` record /
|
|
167
|
+
* router (the 3rd argument to `c.define`, via {@link analyzeValModule}),
|
|
168
|
+
* extracts each entry's `c.json(() => import("..."))` import path, keyed by
|
|
169
|
+
* entry key. Entries whose value is not a `c.json(...)` call are skipped.
|
|
170
|
+
*/
|
|
171
|
+
function analyzeJsonValuesEntries(source) {
|
|
172
|
+
const entries = new Map();
|
|
173
|
+
if (!ts__default["default"].isObjectLiteralExpression(source)) {
|
|
174
|
+
return entries;
|
|
175
|
+
}
|
|
176
|
+
for (const prop of source.properties) {
|
|
177
|
+
if (!ts__default["default"].isPropertyAssignment(prop)) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const key = getPropertyKey(prop.name);
|
|
181
|
+
if (key === null) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const entry = analyzeCJsonCall(prop.initializer);
|
|
185
|
+
if (entry) {
|
|
186
|
+
entries.set(key, entry);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return entries;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Every key of a `.jsonValues()` record's source literal, thunked or not.
|
|
194
|
+
*
|
|
195
|
+
* {@link analyzeJsonValuesEntries} answers "which entries have a file behind
|
|
196
|
+
* them", which is the question for resolving a path. This is the other one:
|
|
197
|
+
* which keys the record HAS. They differ on a value that is not a
|
|
198
|
+
* `c.json(...)` call - written inline, or mid-edit - and telling an existing
|
|
199
|
+
* key from a new one is what decides whether a whole-record write adds a
|
|
200
|
+
* `c.json(...)` thunk or replaces one. Using the thunk set for that would
|
|
201
|
+
* insert a second property with a key the literal already has.
|
|
202
|
+
*/
|
|
203
|
+
function jsonValuesRecordKeys(source) {
|
|
204
|
+
const keys = [];
|
|
205
|
+
if (!ts__default["default"].isObjectLiteralExpression(source)) {
|
|
206
|
+
return keys;
|
|
207
|
+
}
|
|
208
|
+
for (const prop of source.properties) {
|
|
209
|
+
if (!ts__default["default"].isPropertyAssignment(prop)) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const key = getPropertyKey(prop.name);
|
|
213
|
+
if (key !== null) {
|
|
214
|
+
keys.push(key);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return keys;
|
|
218
|
+
}
|
|
219
|
+
function getPropertyKey(name) {
|
|
220
|
+
if (ts__default["default"].isStringLiteralLike(name)) {
|
|
221
|
+
return name.text;
|
|
222
|
+
}
|
|
223
|
+
if (ts__default["default"].isIdentifier(name)) {
|
|
224
|
+
return name.text;
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
function analyzeCJsonCall(node) {
|
|
229
|
+
// c.json(() => import("path"))
|
|
230
|
+
if (!ts__default["default"].isCallExpression(node) || !isCJson(node.expression)) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
const [thunk] = node.arguments;
|
|
234
|
+
if (!thunk) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const importPath = getImportPathFromThunk(thunk);
|
|
238
|
+
if (importPath === null) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
importPath
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function isCJson(expr) {
|
|
246
|
+
// c.json
|
|
247
|
+
return ts__default["default"].isPropertyAccessExpression(expr) && ts__default["default"].isIdentifier(expr.expression) && expr.expression.text === "c" && ts__default["default"].isIdentifier(expr.name) && expr.name.text === "json";
|
|
248
|
+
}
|
|
249
|
+
function getImportPathFromThunk(thunk) {
|
|
250
|
+
// () => import("path") (concise body) OR () => { return import("path"); }
|
|
251
|
+
if (!ts__default["default"].isArrowFunction(thunk)) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
let importCall;
|
|
255
|
+
if (ts__default["default"].isCallExpression(thunk.body)) {
|
|
256
|
+
importCall = thunk.body;
|
|
257
|
+
} else if (ts__default["default"].isBlock(thunk.body)) {
|
|
258
|
+
for (const stmt of thunk.body.statements) {
|
|
259
|
+
if (ts__default["default"].isReturnStatement(stmt) && stmt.expression && ts__default["default"].isCallExpression(stmt.expression)) {
|
|
260
|
+
importCall = stmt.expression;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!importCall || !ts__default["default"].isCallExpression(importCall)) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const isImport = importCall.expression.kind === ts__default["default"].SyntaxKind.ImportKeyword || ts__default["default"].isIdentifier(importCall.expression) && importCall.expression.text === "import";
|
|
269
|
+
if (!isImport) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const arg = importCall.arguments[0];
|
|
273
|
+
if (arg && ts__default["default"].isStringLiteralLike(arg)) {
|
|
274
|
+
return arg.text;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
exports.analyzeJsonValuesEntries = analyzeJsonValuesEntries;
|
|
280
|
+
exports.analyzeValModule = analyzeValModule;
|
|
281
|
+
exports.evaluateExpression = evaluateExpression;
|
|
282
|
+
exports.findObjectPropertyAssignment = findObjectPropertyAssignment;
|
|
283
|
+
exports.flatMapErrors = flatMapErrors;
|
|
284
|
+
exports.formatSyntaxError = formatSyntaxError;
|
|
285
|
+
exports.formatSyntaxErrorTree = formatSyntaxErrorTree;
|
|
286
|
+
exports.jsonValuesRecordKeys = jsonValuesRecordKeys;
|
|
287
|
+
exports.shallowValidateExpression = shallowValidateExpression;
|
|
288
|
+
exports.validateInitializers = validateInitializers;
|