tachyon-dom 0.1.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/LICENSE +21 -0
- package/README.md +339 -0
- package/dist/adapters/lambda.d.ts +65 -0
- package/dist/adapters/lambda.js +211 -0
- package/dist/adapters/node.d.ts +25 -0
- package/dist/adapters/node.js +360 -0
- package/dist/adapters/workers.d.ts +149 -0
- package/dist/adapters/workers.js +272 -0
- package/dist/adapters.d.ts +3 -0
- package/dist/adapters.js +3 -0
- package/dist/app.d.ts +98 -0
- package/dist/app.js +306 -0
- package/dist/cli.d.ts +55 -0
- package/dist/cli.js +711 -0
- package/dist/compiler/expression.d.ts +55 -0
- package/dist/compiler/expression.js +858 -0
- package/dist/compiler/index.d.ts +8 -0
- package/dist/compiler/index.js +50 -0
- package/dist/compiler/ir.d.ts +4 -0
- package/dist/compiler/ir.js +255 -0
- package/dist/compiler/parser.d.ts +3 -0
- package/dist/compiler/parser.js +236 -0
- package/dist/compiler/sfc.d.ts +33 -0
- package/dist/compiler/sfc.js +439 -0
- package/dist/compiler/targets/client.d.ts +3 -0
- package/dist/compiler/targets/client.js +568 -0
- package/dist/compiler/targets/server.d.ts +7 -0
- package/dist/compiler/targets/server.js +396 -0
- package/dist/compiler/targets/stream.d.ts +2 -0
- package/dist/compiler/targets/stream.js +204 -0
- package/dist/compiler/types.d.ts +185 -0
- package/dist/compiler/types.js +1 -0
- package/dist/compiler/utils.d.ts +40 -0
- package/dist/compiler/utils.js +185 -0
- package/dist/compiler/whitespace.d.ts +2 -0
- package/dist/compiler/whitespace.js +66 -0
- package/dist/compiler.d.ts +1 -0
- package/dist/compiler.js +1 -0
- package/dist/constant-time.d.ts +1 -0
- package/dist/constant-time.js +26 -0
- package/dist/cookies.d.ts +44 -0
- package/dist/cookies.js +199 -0
- package/dist/diagnostics.d.ts +22 -0
- package/dist/diagnostics.js +45 -0
- package/dist/env.d.ts +27 -0
- package/dist/env.js +37 -0
- package/dist/html-escape.d.ts +2 -0
- package/dist/html-escape.js +20 -0
- package/dist/html-whitespace.d.ts +6 -0
- package/dist/html-whitespace.js +83 -0
- package/dist/i18n.d.ts +19 -0
- package/dist/i18n.js +93 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +18 -0
- package/dist/language-server.d.ts +17 -0
- package/dist/language-server.js +79 -0
- package/dist/package-integrity.d.ts +9 -0
- package/dist/package-integrity.js +64 -0
- package/dist/result.d.ts +13 -0
- package/dist/result.js +4 -0
- package/dist/router-node.d.ts +2 -0
- package/dist/router-node.js +12 -0
- package/dist/router.d.ts +324 -0
- package/dist/router.js +1024 -0
- package/dist/runtime/attr.d.ts +3 -0
- package/dist/runtime/attr.js +66 -0
- package/dist/runtime/class.d.ts +2 -0
- package/dist/runtime/class.js +10 -0
- package/dist/runtime/conditional.d.ts +78 -0
- package/dist/runtime/conditional.js +140 -0
- package/dist/runtime/enhancement.d.ts +11 -0
- package/dist/runtime/enhancement.js +70 -0
- package/dist/runtime/error-boundary.d.ts +8 -0
- package/dist/runtime/error-boundary.js +27 -0
- package/dist/runtime/event.d.ts +1 -0
- package/dist/runtime/event.js +11 -0
- package/dist/runtime/form.d.ts +41 -0
- package/dist/runtime/form.js +167 -0
- package/dist/runtime/fragment.d.ts +6 -0
- package/dist/runtime/fragment.js +18 -0
- package/dist/runtime/html.d.ts +8 -0
- package/dist/runtime/html.js +6 -0
- package/dist/runtime/hydrate.d.ts +68 -0
- package/dist/runtime/hydrate.js +205 -0
- package/dist/runtime/keyed-rows.d.ts +61 -0
- package/dist/runtime/keyed-rows.js +192 -0
- package/dist/runtime/list.d.ts +84 -0
- package/dist/runtime/list.js +424 -0
- package/dist/runtime/portal.d.ts +6 -0
- package/dist/runtime/portal.js +12 -0
- package/dist/runtime/router.d.ts +102 -0
- package/dist/runtime/router.js +649 -0
- package/dist/runtime/signal.d.ts +30 -0
- package/dist/runtime/signal.js +275 -0
- package/dist/runtime/store.d.ts +1 -0
- package/dist/runtime/store.js +1 -0
- package/dist/runtime/stream-client.d.ts +8 -0
- package/dist/runtime/stream-client.js +33 -0
- package/dist/runtime/text.d.ts +2 -0
- package/dist/runtime/text.js +10 -0
- package/dist/runtime/virtual-list.d.ts +15 -0
- package/dist/runtime/virtual-list.js +89 -0
- package/dist/security.d.ts +27 -0
- package/dist/security.js +173 -0
- package/dist/server/form-action.d.ts +52 -0
- package/dist/server/form-action.js +102 -0
- package/dist/server/html.d.ts +19 -0
- package/dist/server/html.js +101 -0
- package/dist/server/stream.d.ts +4 -0
- package/dist/server/stream.js +72 -0
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +18 -0
- package/dist/tachyon-html.d.ts +110 -0
- package/dist/testing.d.ts +14 -0
- package/dist/testing.js +36 -0
- package/dist/typed.d.ts +9 -0
- package/dist/typed.js +4 -0
- package/dist/vite.d.ts +84 -0
- package/dist/vite.js +426 -0
- package/package.json +604 -0
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
import { err, ok } from "../result.js";
|
|
2
|
+
const operators = [
|
|
3
|
+
"===",
|
|
4
|
+
"!==",
|
|
5
|
+
"==",
|
|
6
|
+
"!=",
|
|
7
|
+
"??",
|
|
8
|
+
"?.",
|
|
9
|
+
">=",
|
|
10
|
+
"<=",
|
|
11
|
+
"&&",
|
|
12
|
+
"||",
|
|
13
|
+
"+",
|
|
14
|
+
"-",
|
|
15
|
+
"*",
|
|
16
|
+
"/",
|
|
17
|
+
"%",
|
|
18
|
+
">",
|
|
19
|
+
"<",
|
|
20
|
+
"?",
|
|
21
|
+
":",
|
|
22
|
+
"!",
|
|
23
|
+
".",
|
|
24
|
+
",",
|
|
25
|
+
"(",
|
|
26
|
+
")",
|
|
27
|
+
"[",
|
|
28
|
+
"]",
|
|
29
|
+
"{",
|
|
30
|
+
"}",
|
|
31
|
+
];
|
|
32
|
+
const operatorsByFirstChar = new Map();
|
|
33
|
+
for (const operator of operators) {
|
|
34
|
+
const first = operator[0];
|
|
35
|
+
const entries = operatorsByFirstChar.get(first) ?? [];
|
|
36
|
+
entries.push(operator);
|
|
37
|
+
entries.sort((left, right) => right.length - left.length);
|
|
38
|
+
operatorsByFirstChar.set(first, entries);
|
|
39
|
+
}
|
|
40
|
+
const binaryPrecedence = new Map([
|
|
41
|
+
["||", 1],
|
|
42
|
+
["??", 1],
|
|
43
|
+
["&&", 2],
|
|
44
|
+
["==", 3],
|
|
45
|
+
["!=", 3],
|
|
46
|
+
["===", 3],
|
|
47
|
+
["!==", 3],
|
|
48
|
+
[">", 4],
|
|
49
|
+
["<", 4],
|
|
50
|
+
[">=", 4],
|
|
51
|
+
["<=", 4],
|
|
52
|
+
["+", 5],
|
|
53
|
+
["-", 5],
|
|
54
|
+
["*", 6],
|
|
55
|
+
["/", 6],
|
|
56
|
+
["%", 6],
|
|
57
|
+
]);
|
|
58
|
+
const expressionError = (message) => err({ message, offset: 0 });
|
|
59
|
+
const expressionCacheLimit = 512;
|
|
60
|
+
const expressionCache = new Map();
|
|
61
|
+
const deniedPropertyNames = new Set(["constructor", "__proto__", "prototype"]);
|
|
62
|
+
const deniedPropertyNameList = [...deniedPropertyNames];
|
|
63
|
+
const cacheKeyFor = (source, backend) => `${backend}\u0000${source}`;
|
|
64
|
+
const rememberExpression = (key, result) => {
|
|
65
|
+
if (expressionCache.has(key)) {
|
|
66
|
+
expressionCache.delete(key);
|
|
67
|
+
}
|
|
68
|
+
expressionCache.set(key, result);
|
|
69
|
+
while (expressionCache.size > expressionCacheLimit) {
|
|
70
|
+
const oldest = expressionCache.keys().next().value;
|
|
71
|
+
if (oldest === undefined) {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
expressionCache.delete(oldest);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
};
|
|
78
|
+
const tokenize = (source) => {
|
|
79
|
+
const tokens = [];
|
|
80
|
+
let offset = 0;
|
|
81
|
+
while (offset < source.length) {
|
|
82
|
+
const char = source[offset];
|
|
83
|
+
if (/\s/.test(char)) {
|
|
84
|
+
offset++;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (/[A-Za-z_$]/.test(char)) {
|
|
88
|
+
const start = offset;
|
|
89
|
+
offset++;
|
|
90
|
+
while (offset < source.length && /[\w$]/.test(source[offset])) {
|
|
91
|
+
offset++;
|
|
92
|
+
}
|
|
93
|
+
tokens.push({ type: "identifier", value: source.slice(start, offset) });
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (/\d/.test(char)) {
|
|
97
|
+
const start = offset;
|
|
98
|
+
offset++;
|
|
99
|
+
while (offset < source.length && /[\d.]/.test(source[offset])) {
|
|
100
|
+
offset++;
|
|
101
|
+
}
|
|
102
|
+
tokens.push({ type: "number", value: source.slice(start, offset) });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (char === `"` || char === `'`) {
|
|
106
|
+
const quote = char;
|
|
107
|
+
offset++;
|
|
108
|
+
let value = "";
|
|
109
|
+
while (offset < source.length && source[offset] !== quote) {
|
|
110
|
+
if (source[offset] === "\\") {
|
|
111
|
+
offset++;
|
|
112
|
+
const escaped = source[offset];
|
|
113
|
+
if (escaped === undefined) {
|
|
114
|
+
return expressionError("Unclosed string literal.");
|
|
115
|
+
}
|
|
116
|
+
value +=
|
|
117
|
+
escaped === "n"
|
|
118
|
+
? "\n"
|
|
119
|
+
: escaped === "r"
|
|
120
|
+
? "\r"
|
|
121
|
+
: escaped === "t"
|
|
122
|
+
? "\t"
|
|
123
|
+
: escaped === "b"
|
|
124
|
+
? "\b"
|
|
125
|
+
: escaped === "f"
|
|
126
|
+
? "\f"
|
|
127
|
+
: escaped === "v"
|
|
128
|
+
? "\v"
|
|
129
|
+
: escaped === "0"
|
|
130
|
+
? "\0"
|
|
131
|
+
: escaped;
|
|
132
|
+
offset++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
value += source[offset];
|
|
136
|
+
offset++;
|
|
137
|
+
}
|
|
138
|
+
if (source[offset] !== quote) {
|
|
139
|
+
return expressionError("Unclosed string literal.");
|
|
140
|
+
}
|
|
141
|
+
offset++;
|
|
142
|
+
tokens.push({ type: "string", value });
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const operator = operatorsByFirstChar.get(char)?.find((candidate) => source.startsWith(candidate, offset));
|
|
146
|
+
if (!operator) {
|
|
147
|
+
return expressionError(`Unsupported expression token: ${char}.`);
|
|
148
|
+
}
|
|
149
|
+
tokens.push(/[.(),[\]{}:]/.test(operator) ? { type: "punct", value: operator } : { type: "operator", value: operator });
|
|
150
|
+
offset += operator.length;
|
|
151
|
+
}
|
|
152
|
+
tokens.push({ type: "eof", value: "" });
|
|
153
|
+
return ok(tokens);
|
|
154
|
+
};
|
|
155
|
+
const peek = (parser) => parser.tokens[parser.offset] ?? { type: "eof", value: "" };
|
|
156
|
+
const consume = (parser) => parser.tokens[parser.offset++] ?? { type: "eof", value: "" };
|
|
157
|
+
const match = (parser, value) => {
|
|
158
|
+
if (peek(parser).value !== value) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
parser.offset++;
|
|
162
|
+
return true;
|
|
163
|
+
};
|
|
164
|
+
const expect = (parser, value) => match(parser, value) ? ok(undefined) : expressionError(`Expected "${value}" in expression.`);
|
|
165
|
+
const parsePrimary = (parser) => {
|
|
166
|
+
const token = consume(parser);
|
|
167
|
+
if (token.type === "identifier") {
|
|
168
|
+
if (token.value === "true" || token.value === "false") {
|
|
169
|
+
return ok({ type: "literal", value: token.value === "true" });
|
|
170
|
+
}
|
|
171
|
+
if (token.value === "null") {
|
|
172
|
+
return ok({ type: "literal", value: null });
|
|
173
|
+
}
|
|
174
|
+
return ok({ type: "identifier", path: [token.value] });
|
|
175
|
+
}
|
|
176
|
+
if (token.type === "number") {
|
|
177
|
+
return ok({ type: "literal", value: Number(token.value) });
|
|
178
|
+
}
|
|
179
|
+
if (token.type === "string") {
|
|
180
|
+
return ok({ type: "literal", value: token.value });
|
|
181
|
+
}
|
|
182
|
+
if (token.value === "(") {
|
|
183
|
+
const expression = parseExpressionInternal(parser, 0);
|
|
184
|
+
if (!expression.ok) {
|
|
185
|
+
return expression;
|
|
186
|
+
}
|
|
187
|
+
const expected = expect(parser, ")");
|
|
188
|
+
return expected.ok ? expression : expected;
|
|
189
|
+
}
|
|
190
|
+
if (token.value === "[") {
|
|
191
|
+
const items = [];
|
|
192
|
+
while (peek(parser).value !== "]") {
|
|
193
|
+
const item = parseExpressionInternal(parser, 0);
|
|
194
|
+
if (!item.ok) {
|
|
195
|
+
return item;
|
|
196
|
+
}
|
|
197
|
+
items.push(item.value);
|
|
198
|
+
if (!match(parser, ",")) {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const expected = expect(parser, "]");
|
|
203
|
+
return expected.ok ? ok({ type: "array", items }) : expected;
|
|
204
|
+
}
|
|
205
|
+
if (token.value === "{") {
|
|
206
|
+
const entries = [];
|
|
207
|
+
while (peek(parser).value !== "}") {
|
|
208
|
+
const key = consume(parser);
|
|
209
|
+
if (key.type !== "identifier" && key.type !== "string") {
|
|
210
|
+
return expressionError("Expected object key.");
|
|
211
|
+
}
|
|
212
|
+
const colon = expect(parser, ":");
|
|
213
|
+
if (!colon.ok) {
|
|
214
|
+
return colon;
|
|
215
|
+
}
|
|
216
|
+
const value = parseExpressionInternal(parser, 0);
|
|
217
|
+
if (!value.ok) {
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
entries.push({ key: key.value, value: value.value });
|
|
221
|
+
if (!match(parser, ",")) {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const expected = expect(parser, "}");
|
|
226
|
+
return expected.ok ? ok({ type: "object", entries }) : expected;
|
|
227
|
+
}
|
|
228
|
+
return expressionError(`Unexpected expression token: ${token.value}.`);
|
|
229
|
+
};
|
|
230
|
+
const parsePostfix = (parser) => {
|
|
231
|
+
let current = parsePrimary(parser);
|
|
232
|
+
if (!current.ok) {
|
|
233
|
+
return current;
|
|
234
|
+
}
|
|
235
|
+
while (true) {
|
|
236
|
+
if (match(parser, ".")) {
|
|
237
|
+
const property = consume(parser);
|
|
238
|
+
if (property.type !== "identifier") {
|
|
239
|
+
return expressionError("Expected property name after dot.");
|
|
240
|
+
}
|
|
241
|
+
if (current.value.type !== "identifier") {
|
|
242
|
+
return expressionError("Only identifier paths can use dot access.");
|
|
243
|
+
}
|
|
244
|
+
current = ok({ type: "identifier", path: [...current.value.path, property.value] });
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (match(parser, "(")) {
|
|
248
|
+
const args = [];
|
|
249
|
+
while (peek(parser).value !== ")") {
|
|
250
|
+
const arg = parseExpressionInternal(parser, 0);
|
|
251
|
+
if (!arg.ok) {
|
|
252
|
+
return arg;
|
|
253
|
+
}
|
|
254
|
+
args.push(arg.value);
|
|
255
|
+
if (!match(parser, ",")) {
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const expected = expect(parser, ")");
|
|
260
|
+
if (!expected.ok) {
|
|
261
|
+
return expected;
|
|
262
|
+
}
|
|
263
|
+
current = ok({ type: "call", callee: current.value, args });
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
return current;
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
const parseUnary = (parser) => {
|
|
270
|
+
const token = peek(parser);
|
|
271
|
+
if (token.value === "!" || token.value === "-") {
|
|
272
|
+
consume(parser);
|
|
273
|
+
const argument = parseUnary(parser);
|
|
274
|
+
return argument.ok ? ok({ type: "unary", operator: token.value, argument: argument.value }) : argument;
|
|
275
|
+
}
|
|
276
|
+
return parsePostfix(parser);
|
|
277
|
+
};
|
|
278
|
+
const parseExpressionInternal = (parser, minPrecedence) => {
|
|
279
|
+
let left = parseUnary(parser);
|
|
280
|
+
if (!left.ok) {
|
|
281
|
+
return left;
|
|
282
|
+
}
|
|
283
|
+
while (true) {
|
|
284
|
+
if (match(parser, "?")) {
|
|
285
|
+
const consequent = parseExpressionInternal(parser, 0);
|
|
286
|
+
if (!consequent.ok) {
|
|
287
|
+
return consequent;
|
|
288
|
+
}
|
|
289
|
+
const colon = expect(parser, ":");
|
|
290
|
+
if (!colon.ok) {
|
|
291
|
+
return colon;
|
|
292
|
+
}
|
|
293
|
+
const alternate = parseExpressionInternal(parser, 0);
|
|
294
|
+
if (!alternate.ok) {
|
|
295
|
+
return alternate;
|
|
296
|
+
}
|
|
297
|
+
const conditional = {
|
|
298
|
+
type: "conditional",
|
|
299
|
+
test: left.value,
|
|
300
|
+
consequent: consequent.value,
|
|
301
|
+
alternate: alternate.value,
|
|
302
|
+
};
|
|
303
|
+
left = ok(conditional);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const operator = peek(parser).value;
|
|
307
|
+
const precedence = binaryPrecedence.get(operator);
|
|
308
|
+
if (!precedence || precedence < minPrecedence) {
|
|
309
|
+
return left;
|
|
310
|
+
}
|
|
311
|
+
consume(parser);
|
|
312
|
+
const right = parseExpressionInternal(parser, precedence + 1);
|
|
313
|
+
if (!right.ok) {
|
|
314
|
+
return right;
|
|
315
|
+
}
|
|
316
|
+
left = ok({ type: "binary", operator, left: left.value, right: right.value });
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
const parseNativeExpression = (source) => {
|
|
320
|
+
const tokens = tokenize(source);
|
|
321
|
+
if (!tokens.ok) {
|
|
322
|
+
return tokens;
|
|
323
|
+
}
|
|
324
|
+
const parser = { tokens: tokens.value, offset: 0 };
|
|
325
|
+
const expression = parseExpressionInternal(parser, 0);
|
|
326
|
+
if (!expression.ok) {
|
|
327
|
+
return expression;
|
|
328
|
+
}
|
|
329
|
+
if (peek(parser).type !== "eof") {
|
|
330
|
+
return expressionError(`Unexpected expression token: ${peek(parser).value}.`);
|
|
331
|
+
}
|
|
332
|
+
return expression;
|
|
333
|
+
};
|
|
334
|
+
const literalPropertyName = (node) => node.type === "literal" && typeof node.value === "string" ? node.value : undefined;
|
|
335
|
+
const denyForbiddenPropertyName = (name) => deniedPropertyNames.has(name) ? expressionError(`Disallowed expression property: ${name}.`) : ok(undefined);
|
|
336
|
+
const isDeniedPropertyKey = (key) => deniedPropertyNames.has(String(key));
|
|
337
|
+
const validateExpressionNode = (node) => {
|
|
338
|
+
if (node.type === "identifier") {
|
|
339
|
+
for (const part of node.path) {
|
|
340
|
+
const denied = denyForbiddenPropertyName(part);
|
|
341
|
+
if (!denied.ok) {
|
|
342
|
+
return denied;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return ok(node);
|
|
346
|
+
}
|
|
347
|
+
if (node.type === "literal") {
|
|
348
|
+
return ok(node);
|
|
349
|
+
}
|
|
350
|
+
if (node.type === "array") {
|
|
351
|
+
for (const item of node.items) {
|
|
352
|
+
const valid = validateExpressionNode(item);
|
|
353
|
+
if (!valid.ok) {
|
|
354
|
+
return valid;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return ok(node);
|
|
358
|
+
}
|
|
359
|
+
if (node.type === "object") {
|
|
360
|
+
for (const entry of node.entries) {
|
|
361
|
+
const denied = denyForbiddenPropertyName(entry.key);
|
|
362
|
+
if (!denied.ok) {
|
|
363
|
+
return denied;
|
|
364
|
+
}
|
|
365
|
+
const valid = validateExpressionNode(entry.value);
|
|
366
|
+
if (!valid.ok) {
|
|
367
|
+
return valid;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return ok(node);
|
|
371
|
+
}
|
|
372
|
+
if (node.type === "unary") {
|
|
373
|
+
const valid = validateExpressionNode(node.argument);
|
|
374
|
+
return valid.ok ? ok(node) : valid;
|
|
375
|
+
}
|
|
376
|
+
if (node.type === "conditional") {
|
|
377
|
+
const test = validateExpressionNode(node.test);
|
|
378
|
+
if (!test.ok)
|
|
379
|
+
return test;
|
|
380
|
+
const consequent = validateExpressionNode(node.consequent);
|
|
381
|
+
if (!consequent.ok)
|
|
382
|
+
return consequent;
|
|
383
|
+
const alternate = validateExpressionNode(node.alternate);
|
|
384
|
+
return alternate.ok ? ok(node) : alternate;
|
|
385
|
+
}
|
|
386
|
+
if (node.type === "call") {
|
|
387
|
+
const callee = validateExpressionNode(node.callee);
|
|
388
|
+
if (!callee.ok) {
|
|
389
|
+
return callee;
|
|
390
|
+
}
|
|
391
|
+
for (const arg of node.args) {
|
|
392
|
+
const valid = validateExpressionNode(arg);
|
|
393
|
+
if (!valid.ok) {
|
|
394
|
+
return valid;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return ok(node);
|
|
398
|
+
}
|
|
399
|
+
if (node.type === "member") {
|
|
400
|
+
const propertyName = literalPropertyName(node.property);
|
|
401
|
+
if (propertyName) {
|
|
402
|
+
const denied = denyForbiddenPropertyName(propertyName);
|
|
403
|
+
if (!denied.ok) {
|
|
404
|
+
return denied;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const object = validateExpressionNode(node.object);
|
|
408
|
+
if (!object.ok) {
|
|
409
|
+
return object;
|
|
410
|
+
}
|
|
411
|
+
const property = validateExpressionNode(node.property);
|
|
412
|
+
return property.ok ? ok(node) : property;
|
|
413
|
+
}
|
|
414
|
+
if (node.type === "template") {
|
|
415
|
+
for (const part of node.parts) {
|
|
416
|
+
if (typeof part === "string") {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const valid = validateExpressionNode(part);
|
|
420
|
+
if (!valid.ok) {
|
|
421
|
+
return valid;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return ok(node);
|
|
425
|
+
}
|
|
426
|
+
const left = validateExpressionNode(node.left);
|
|
427
|
+
if (!left.ok) {
|
|
428
|
+
return left;
|
|
429
|
+
}
|
|
430
|
+
const right = validateExpressionNode(node.right);
|
|
431
|
+
return right.ok ? ok(node) : right;
|
|
432
|
+
};
|
|
433
|
+
const validateParsedExpression = (result) => (result.ok ? validateExpressionNode(result.value) : result);
|
|
434
|
+
const maybeIdentifierPath = (object, property, computed, optional) => {
|
|
435
|
+
const propertyName = literalPropertyName(property);
|
|
436
|
+
if (!computed && !optional && object.type === "identifier" && propertyName) {
|
|
437
|
+
return { type: "identifier", path: [...object.path, propertyName] };
|
|
438
|
+
}
|
|
439
|
+
return { type: "member", object, property, computed, optional };
|
|
440
|
+
};
|
|
441
|
+
const oxcError = (message) => expressionError(message);
|
|
442
|
+
let cachedOxcParser;
|
|
443
|
+
const loadOxcParser = () => {
|
|
444
|
+
if (cachedOxcParser) {
|
|
445
|
+
return cachedOxcParser;
|
|
446
|
+
}
|
|
447
|
+
const moduleApi = globalThis.process?.getBuiltinModule?.("module");
|
|
448
|
+
const require = moduleApi?.createRequire?.(import.meta.url);
|
|
449
|
+
const loaded = require?.("oxc-parser");
|
|
450
|
+
if (loaded?.parseSync) {
|
|
451
|
+
cachedOxcParser = loaded;
|
|
452
|
+
}
|
|
453
|
+
return cachedOxcParser;
|
|
454
|
+
};
|
|
455
|
+
const oxcExpressionToNode = (node) => {
|
|
456
|
+
if (node.type === "Literal") {
|
|
457
|
+
if (typeof node.value === "string" ||
|
|
458
|
+
typeof node.value === "number" ||
|
|
459
|
+
typeof node.value === "boolean" ||
|
|
460
|
+
node.value === null) {
|
|
461
|
+
return ok({ type: "literal", value: node.value });
|
|
462
|
+
}
|
|
463
|
+
return oxcError("Unsupported literal expression.");
|
|
464
|
+
}
|
|
465
|
+
if (node.type === "Identifier") {
|
|
466
|
+
return ok({ type: "identifier", path: [node.name] });
|
|
467
|
+
}
|
|
468
|
+
if (node.type === "ArrayExpression") {
|
|
469
|
+
const items = [];
|
|
470
|
+
for (const item of node.elements) {
|
|
471
|
+
if (!item || item.type === "SpreadElement") {
|
|
472
|
+
return oxcError("Unsupported array expression.");
|
|
473
|
+
}
|
|
474
|
+
const parsed = oxcExpressionToNode(item);
|
|
475
|
+
if (!parsed.ok) {
|
|
476
|
+
return parsed;
|
|
477
|
+
}
|
|
478
|
+
items.push(parsed.value);
|
|
479
|
+
}
|
|
480
|
+
return ok({ type: "array", items });
|
|
481
|
+
}
|
|
482
|
+
if (node.type === "ObjectExpression") {
|
|
483
|
+
const entries = [];
|
|
484
|
+
for (const property of node.properties) {
|
|
485
|
+
if (property.type !== "Property" || property.kind !== "init" || property.method || property.computed) {
|
|
486
|
+
return oxcError("Unsupported object property expression.");
|
|
487
|
+
}
|
|
488
|
+
const key = property.key.type === "Identifier"
|
|
489
|
+
? property.key.name
|
|
490
|
+
: property.key.type === "Literal" &&
|
|
491
|
+
(typeof property.key.value === "string" || typeof property.key.value === "number")
|
|
492
|
+
? String(property.key.value)
|
|
493
|
+
: undefined;
|
|
494
|
+
if (!key) {
|
|
495
|
+
return oxcError("Unsupported object key expression.");
|
|
496
|
+
}
|
|
497
|
+
const value = oxcExpressionToNode(property.value);
|
|
498
|
+
if (!value.ok) {
|
|
499
|
+
return value;
|
|
500
|
+
}
|
|
501
|
+
entries.push({ key, value: value.value });
|
|
502
|
+
}
|
|
503
|
+
return ok({ type: "object", entries });
|
|
504
|
+
}
|
|
505
|
+
if (node.type === "UnaryExpression") {
|
|
506
|
+
if (node.operator !== "!" && node.operator !== "-") {
|
|
507
|
+
return oxcError(`Unsupported unary operator: ${node.operator}.`);
|
|
508
|
+
}
|
|
509
|
+
const argument = oxcExpressionToNode(node.argument);
|
|
510
|
+
return argument.ok ? ok({ type: "unary", operator: node.operator, argument: argument.value }) : argument;
|
|
511
|
+
}
|
|
512
|
+
if (node.type === "BinaryExpression" || node.type === "LogicalExpression") {
|
|
513
|
+
if (node.left.type === "PrivateIdentifier") {
|
|
514
|
+
return oxcError("Unsupported private identifier expression.");
|
|
515
|
+
}
|
|
516
|
+
const left = oxcExpressionToNode(node.left);
|
|
517
|
+
if (!left.ok) {
|
|
518
|
+
return left;
|
|
519
|
+
}
|
|
520
|
+
const right = oxcExpressionToNode(node.right);
|
|
521
|
+
if (!right.ok) {
|
|
522
|
+
return right;
|
|
523
|
+
}
|
|
524
|
+
return ok({ type: "binary", operator: node.operator, left: left.value, right: right.value });
|
|
525
|
+
}
|
|
526
|
+
if (node.type === "ConditionalExpression") {
|
|
527
|
+
const test = oxcExpressionToNode(node.test);
|
|
528
|
+
const consequent = oxcExpressionToNode(node.consequent);
|
|
529
|
+
const alternate = oxcExpressionToNode(node.alternate);
|
|
530
|
+
if (!test.ok)
|
|
531
|
+
return test;
|
|
532
|
+
if (!consequent.ok)
|
|
533
|
+
return consequent;
|
|
534
|
+
if (!alternate.ok)
|
|
535
|
+
return alternate;
|
|
536
|
+
return ok({ type: "conditional", test: test.value, consequent: consequent.value, alternate: alternate.value });
|
|
537
|
+
}
|
|
538
|
+
if (node.type === "CallExpression") {
|
|
539
|
+
const callee = oxcExpressionToNode(node.callee);
|
|
540
|
+
if (!callee.ok) {
|
|
541
|
+
return callee;
|
|
542
|
+
}
|
|
543
|
+
const args = [];
|
|
544
|
+
for (const argument of node.arguments) {
|
|
545
|
+
if (argument.type === "SpreadElement") {
|
|
546
|
+
return oxcError("Unsupported spread call argument.");
|
|
547
|
+
}
|
|
548
|
+
const parsed = oxcExpressionToNode(argument);
|
|
549
|
+
if (!parsed.ok) {
|
|
550
|
+
return parsed;
|
|
551
|
+
}
|
|
552
|
+
args.push(parsed.value);
|
|
553
|
+
}
|
|
554
|
+
return ok({ type: "call", callee: callee.value, args });
|
|
555
|
+
}
|
|
556
|
+
if (node.type === "ChainExpression") {
|
|
557
|
+
return oxcExpressionToNode(node.expression);
|
|
558
|
+
}
|
|
559
|
+
if (node.type === "MemberExpression") {
|
|
560
|
+
const object = oxcExpressionToNode(node.object);
|
|
561
|
+
if (!object.ok) {
|
|
562
|
+
return object;
|
|
563
|
+
}
|
|
564
|
+
const property = node.computed
|
|
565
|
+
? oxcExpressionToNode(node.property)
|
|
566
|
+
: node.property.type === "Identifier"
|
|
567
|
+
? ok({ type: "literal", value: node.property.name })
|
|
568
|
+
: oxcError("Unsupported private member expression.");
|
|
569
|
+
return property.ok ? ok(maybeIdentifierPath(object.value, property.value, node.computed, node.optional)) : property;
|
|
570
|
+
}
|
|
571
|
+
if (node.type === "TemplateLiteral") {
|
|
572
|
+
const parts = [];
|
|
573
|
+
for (const [index, quasi] of node.quasis.entries()) {
|
|
574
|
+
parts.push(quasi.value.cooked ?? quasi.value.raw);
|
|
575
|
+
const expression = node.expressions[index];
|
|
576
|
+
if (!expression) {
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const parsed = oxcExpressionToNode(expression);
|
|
580
|
+
if (!parsed.ok) {
|
|
581
|
+
return parsed;
|
|
582
|
+
}
|
|
583
|
+
parts.push(parsed.value);
|
|
584
|
+
}
|
|
585
|
+
return ok({ type: "template", parts });
|
|
586
|
+
}
|
|
587
|
+
if (node.type === "ParenthesizedExpression" ||
|
|
588
|
+
node.type === "TSAsExpression" ||
|
|
589
|
+
node.type === "TSSatisfiesExpression" ||
|
|
590
|
+
node.type === "TSNonNullExpression" ||
|
|
591
|
+
node.type === "TSInstantiationExpression") {
|
|
592
|
+
return oxcExpressionToNode(node.expression);
|
|
593
|
+
}
|
|
594
|
+
return oxcError(`Unsupported expression syntax: ${node.type}.`);
|
|
595
|
+
};
|
|
596
|
+
const parseOxcExpression = (source) => {
|
|
597
|
+
const parser = loadOxcParser();
|
|
598
|
+
if (!parser) {
|
|
599
|
+
return expressionError("OXC parser backend is unavailable in this runtime.");
|
|
600
|
+
}
|
|
601
|
+
const wrapped = `const __tachyon_expr = (${source});`;
|
|
602
|
+
try {
|
|
603
|
+
const result = parser.parseSync("tachyon-expression.ts", wrapped, {
|
|
604
|
+
astType: "js",
|
|
605
|
+
lang: "ts",
|
|
606
|
+
preserveParens: false,
|
|
607
|
+
sourceType: "module",
|
|
608
|
+
});
|
|
609
|
+
const [error] = result.errors;
|
|
610
|
+
if (error) {
|
|
611
|
+
return expressionError(error.message);
|
|
612
|
+
}
|
|
613
|
+
const declaration = result.program.body[0];
|
|
614
|
+
const init = declaration?.type === "VariableDeclaration" ? declaration.declarations[0]?.init : undefined;
|
|
615
|
+
return init ? oxcExpressionToNode(init) : expressionError("Unable to read OXC expression.");
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
return expressionError(error instanceof Error ? error.message : "Unable to parse expression with OXC.");
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
export const parseExpression = (source, options = {}) => {
|
|
622
|
+
const backend = options.backend ?? "auto";
|
|
623
|
+
const key = cacheKeyFor(source, backend);
|
|
624
|
+
const cached = expressionCache.get(key);
|
|
625
|
+
if (cached) {
|
|
626
|
+
expressionCache.delete(key);
|
|
627
|
+
expressionCache.set(key, cached);
|
|
628
|
+
return cached;
|
|
629
|
+
}
|
|
630
|
+
if (backend === "oxc") {
|
|
631
|
+
return rememberExpression(key, validateParsedExpression(parseOxcExpression(source)));
|
|
632
|
+
}
|
|
633
|
+
const native = parseNativeExpression(source);
|
|
634
|
+
if (backend === "native" || native.ok) {
|
|
635
|
+
return rememberExpression(key, validateParsedExpression(native));
|
|
636
|
+
}
|
|
637
|
+
return rememberExpression(key, validateParsedExpression(parseOxcExpression(source)));
|
|
638
|
+
};
|
|
639
|
+
const readPath = (scope, path) => {
|
|
640
|
+
let current = scope;
|
|
641
|
+
for (let index = 0; index < path.length; index++) {
|
|
642
|
+
const part = path[index];
|
|
643
|
+
if (isDeniedPropertyKey(part)) {
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
if (current == null || typeof current !== "object") {
|
|
647
|
+
throw new TypeError(`Cannot read properties of ${current}.`);
|
|
648
|
+
}
|
|
649
|
+
current = current[part];
|
|
650
|
+
}
|
|
651
|
+
return current;
|
|
652
|
+
};
|
|
653
|
+
export const evaluateExpressionNode = (node, scope) => {
|
|
654
|
+
if (node.type === "identifier") {
|
|
655
|
+
return readPath(scope, node.path);
|
|
656
|
+
}
|
|
657
|
+
if (node.type === "literal") {
|
|
658
|
+
return node.value;
|
|
659
|
+
}
|
|
660
|
+
if (node.type === "array") {
|
|
661
|
+
return node.items.map((item) => evaluateExpressionNode(item, scope));
|
|
662
|
+
}
|
|
663
|
+
if (node.type === "object") {
|
|
664
|
+
return Object.fromEntries(node.entries.map((entry) => [entry.key, evaluateExpressionNode(entry.value, scope)]));
|
|
665
|
+
}
|
|
666
|
+
if (node.type === "unary") {
|
|
667
|
+
const value = evaluateExpressionNode(node.argument, scope);
|
|
668
|
+
return node.operator === "!" ? !value : -Number(value);
|
|
669
|
+
}
|
|
670
|
+
if (node.type === "conditional") {
|
|
671
|
+
return evaluateExpressionNode(node.test, scope)
|
|
672
|
+
? evaluateExpressionNode(node.consequent, scope)
|
|
673
|
+
: evaluateExpressionNode(node.alternate, scope);
|
|
674
|
+
}
|
|
675
|
+
if (node.type === "call") {
|
|
676
|
+
if (node.callee.type === "identifier" && node.callee.path.length > 1) {
|
|
677
|
+
const object = readPath(scope, node.callee.path.slice(0, -1));
|
|
678
|
+
const property = node.callee.path.at(-1);
|
|
679
|
+
if (isDeniedPropertyKey(property)) {
|
|
680
|
+
return undefined;
|
|
681
|
+
}
|
|
682
|
+
const callee = Object(object)[property];
|
|
683
|
+
if (typeof callee !== "function") {
|
|
684
|
+
return undefined;
|
|
685
|
+
}
|
|
686
|
+
return callee.apply(object, node.args.map((arg) => evaluateExpressionNode(arg, scope)));
|
|
687
|
+
}
|
|
688
|
+
if (node.callee.type === "member") {
|
|
689
|
+
const object = evaluateExpressionNode(node.callee.object, scope);
|
|
690
|
+
const property = evaluateExpressionNode(node.callee.property, scope);
|
|
691
|
+
if (isDeniedPropertyKey(property)) {
|
|
692
|
+
return undefined;
|
|
693
|
+
}
|
|
694
|
+
if (object == null) {
|
|
695
|
+
if (node.callee.optional) {
|
|
696
|
+
return undefined;
|
|
697
|
+
}
|
|
698
|
+
throw new TypeError(`Cannot read properties of ${object}.`);
|
|
699
|
+
}
|
|
700
|
+
const callee = object[property];
|
|
701
|
+
if (typeof callee !== "function") {
|
|
702
|
+
return undefined;
|
|
703
|
+
}
|
|
704
|
+
return callee.apply(object, node.args.map((arg) => evaluateExpressionNode(arg, scope)));
|
|
705
|
+
}
|
|
706
|
+
const callee = evaluateExpressionNode(node.callee, scope);
|
|
707
|
+
if (typeof callee !== "function") {
|
|
708
|
+
return undefined;
|
|
709
|
+
}
|
|
710
|
+
return callee(...node.args.map((arg) => evaluateExpressionNode(arg, scope)));
|
|
711
|
+
}
|
|
712
|
+
if (node.type === "member") {
|
|
713
|
+
const object = evaluateExpressionNode(node.object, scope);
|
|
714
|
+
const property = evaluateExpressionNode(node.property, scope);
|
|
715
|
+
if (isDeniedPropertyKey(property)) {
|
|
716
|
+
return undefined;
|
|
717
|
+
}
|
|
718
|
+
if (object == null) {
|
|
719
|
+
if (node.optional) {
|
|
720
|
+
return undefined;
|
|
721
|
+
}
|
|
722
|
+
throw new TypeError(`Cannot read properties of ${object}.`);
|
|
723
|
+
}
|
|
724
|
+
return object[property];
|
|
725
|
+
}
|
|
726
|
+
if (node.type === "template") {
|
|
727
|
+
return node.parts
|
|
728
|
+
.map((part) => (typeof part === "string" ? part : String(evaluateExpressionNode(part, scope))))
|
|
729
|
+
.join("");
|
|
730
|
+
}
|
|
731
|
+
const left = evaluateExpressionNode(node.left, scope);
|
|
732
|
+
if (node.operator === "??") {
|
|
733
|
+
return left ?? evaluateExpressionNode(node.right, scope);
|
|
734
|
+
}
|
|
735
|
+
const right = evaluateExpressionNode(node.right, scope);
|
|
736
|
+
if (node.operator === "===")
|
|
737
|
+
return left === right;
|
|
738
|
+
if (node.operator === "!==")
|
|
739
|
+
return left !== right;
|
|
740
|
+
if (node.operator === "==")
|
|
741
|
+
return left == right;
|
|
742
|
+
if (node.operator === "!=")
|
|
743
|
+
return left != right;
|
|
744
|
+
if (node.operator === "&&")
|
|
745
|
+
return left && right;
|
|
746
|
+
if (node.operator === "||")
|
|
747
|
+
return left || right;
|
|
748
|
+
if (node.operator === "+") {
|
|
749
|
+
return left + right;
|
|
750
|
+
}
|
|
751
|
+
if (node.operator === "-")
|
|
752
|
+
return Number(left) - Number(right);
|
|
753
|
+
if (node.operator === "*")
|
|
754
|
+
return Number(left) * Number(right);
|
|
755
|
+
if (node.operator === "/")
|
|
756
|
+
return Number(left) / Number(right);
|
|
757
|
+
if (node.operator === "%")
|
|
758
|
+
return Number(left) % Number(right);
|
|
759
|
+
const comparableLeft = left;
|
|
760
|
+
const comparableRight = right;
|
|
761
|
+
if (node.operator === ">")
|
|
762
|
+
return comparableLeft > comparableRight;
|
|
763
|
+
if (node.operator === "<")
|
|
764
|
+
return comparableLeft < comparableRight;
|
|
765
|
+
if (node.operator === ">=")
|
|
766
|
+
return comparableLeft >= comparableRight;
|
|
767
|
+
if (node.operator === "<=")
|
|
768
|
+
return comparableLeft <= comparableRight;
|
|
769
|
+
return undefined;
|
|
770
|
+
};
|
|
771
|
+
export const evaluateExpression = (source, scope) => {
|
|
772
|
+
const parsed = parseExpression(source);
|
|
773
|
+
return parsed.ok ? evaluateExpressionNode(parsed.value, scope) : undefined;
|
|
774
|
+
};
|
|
775
|
+
export const expressionToJs = (source, locals = new Set(), scopeName = "scope", options = {}) => {
|
|
776
|
+
const parsed = parseExpression(source, options);
|
|
777
|
+
return parsed.ok ? expressionNodeToJs(parsed.value, locals, scopeName) : "undefined";
|
|
778
|
+
};
|
|
779
|
+
const memberObjectToJs = (node, locals, scopeName) => {
|
|
780
|
+
const expression = expressionNodeToJs(node, locals, scopeName);
|
|
781
|
+
return node.type === "identifier" || node.type === "member" || node.type === "call" ? expression : `(${expression})`;
|
|
782
|
+
};
|
|
783
|
+
const isIdentifierName = (value) => /^[A-Za-z_$][\w$]*$/.test(value);
|
|
784
|
+
const deniedPropertyCheckToJs = (propertyName) => deniedPropertyNameList.map((name) => `String(${propertyName}) === ${JSON.stringify(name)}`).join(" || ");
|
|
785
|
+
const guardedMemberAccessToJs = (object, property, optional) => `((__tachyonObject, __tachyonProperty) => ${optional ? "__tachyonObject == null ? undefined : " : ""}${deniedPropertyCheckToJs("__tachyonProperty")} ? undefined : __tachyonObject[__tachyonProperty])(${object}, ${property})`;
|
|
786
|
+
const guardedMemberCallToJs = (object, property, args, optional) => {
|
|
787
|
+
const argsArray = `[${args.join(", ")}]`;
|
|
788
|
+
return `((__tachyonObject, __tachyonProperty) => { if (${optional ? "__tachyonObject == null || " : ""}${deniedPropertyCheckToJs("__tachyonProperty")}) return undefined; const __tachyonCallee = __tachyonObject[__tachyonProperty]; return typeof __tachyonCallee === "function" ? __tachyonCallee.apply(__tachyonObject, ${argsArray}) : undefined; })(${object}, ${property})`;
|
|
789
|
+
};
|
|
790
|
+
const genericCallToJs = (callee, args) => `((__tachyonCallee) => typeof __tachyonCallee === "function" ? __tachyonCallee(${args.join(", ")}) : undefined)(${callee})`;
|
|
791
|
+
export const expressionNodeToJs = (node, locals = new Set(), scopeName = "scope") => {
|
|
792
|
+
if (node.type === "identifier") {
|
|
793
|
+
const [head, ...tail] = node.path;
|
|
794
|
+
const root = head && locals.has(head) ? head : `${scopeName}.${head ?? ""}`;
|
|
795
|
+
return [root, ...tail].join(".");
|
|
796
|
+
}
|
|
797
|
+
if (node.type === "literal") {
|
|
798
|
+
return JSON.stringify(node.value);
|
|
799
|
+
}
|
|
800
|
+
if (node.type === "array") {
|
|
801
|
+
return `[${node.items.map((item) => expressionNodeToJs(item, locals, scopeName)).join(", ")}]`;
|
|
802
|
+
}
|
|
803
|
+
if (node.type === "object") {
|
|
804
|
+
return `{ ${node.entries
|
|
805
|
+
.map((entry) => `${JSON.stringify(entry.key)}: ${expressionNodeToJs(entry.value, locals, scopeName)}`)
|
|
806
|
+
.join(", ")} }`;
|
|
807
|
+
}
|
|
808
|
+
if (node.type === "unary") {
|
|
809
|
+
return `(${node.operator}${expressionNodeToJs(node.argument, locals, scopeName)})`;
|
|
810
|
+
}
|
|
811
|
+
if (node.type === "conditional") {
|
|
812
|
+
return `(${expressionNodeToJs(node.test, locals, scopeName)} ? ${expressionNodeToJs(node.consequent, locals, scopeName)} : ${expressionNodeToJs(node.alternate, locals, scopeName)})`;
|
|
813
|
+
}
|
|
814
|
+
if (node.type === "call") {
|
|
815
|
+
const args = node.args.map((arg) => expressionNodeToJs(arg, locals, scopeName));
|
|
816
|
+
if (node.callee.type === "member") {
|
|
817
|
+
const object = memberObjectToJs(node.callee.object, locals, scopeName);
|
|
818
|
+
const propertyName = literalPropertyName(node.callee.property);
|
|
819
|
+
if (!node.callee.computed && propertyName && isIdentifierName(propertyName)) {
|
|
820
|
+
return `${object}${node.callee.optional ? "?." : "."}${propertyName}(${args.join(", ")})`;
|
|
821
|
+
}
|
|
822
|
+
const property = expressionNodeToJs(node.callee.property, locals, scopeName);
|
|
823
|
+
return guardedMemberCallToJs(object, property, args, node.callee.optional);
|
|
824
|
+
}
|
|
825
|
+
if (node.callee.type === "identifier") {
|
|
826
|
+
return `${expressionNodeToJs(node.callee, locals, scopeName)}(${args.join(", ")})`;
|
|
827
|
+
}
|
|
828
|
+
return genericCallToJs(expressionNodeToJs(node.callee, locals, scopeName), args);
|
|
829
|
+
}
|
|
830
|
+
if (node.type === "member") {
|
|
831
|
+
const object = memberObjectToJs(node.object, locals, scopeName);
|
|
832
|
+
const propertyName = literalPropertyName(node.property);
|
|
833
|
+
if (!node.computed && propertyName && isIdentifierName(propertyName)) {
|
|
834
|
+
return `${object}${node.optional ? "?." : "."}${propertyName}`;
|
|
835
|
+
}
|
|
836
|
+
const property = expressionNodeToJs(node.property, locals, scopeName);
|
|
837
|
+
return guardedMemberAccessToJs(object, property, node.optional);
|
|
838
|
+
}
|
|
839
|
+
if (node.type === "template") {
|
|
840
|
+
return node.parts
|
|
841
|
+
.map((part) => typeof part === "string" ? JSON.stringify(part) : `String(${expressionNodeToJs(part, locals, scopeName)})`)
|
|
842
|
+
.join(" + ");
|
|
843
|
+
}
|
|
844
|
+
return `(${expressionNodeToJs(node.left, locals, scopeName)} ${node.operator} ${expressionNodeToJs(node.right, locals, scopeName)})`;
|
|
845
|
+
};
|
|
846
|
+
const isAssignableNode = (node) => {
|
|
847
|
+
if (node.type === "identifier") {
|
|
848
|
+
return true;
|
|
849
|
+
}
|
|
850
|
+
if (node.type === "member") {
|
|
851
|
+
return !node.optional && isAssignableNode(node.object);
|
|
852
|
+
}
|
|
853
|
+
return false;
|
|
854
|
+
};
|
|
855
|
+
export const isAssignableExpression = (source, options = {}) => {
|
|
856
|
+
const parsed = parseExpression(source, options);
|
|
857
|
+
return parsed.ok && isAssignableNode(parsed.value);
|
|
858
|
+
};
|