@tsrx/core 0.0.26 → 0.0.28
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/package.json +5 -4
- package/src/analyze/validation.js +59 -0
- package/src/index.js +15 -1
- package/src/plugin.js +18 -62
- package/src/runtime/language-helpers.js +57 -0
- package/src/runtime/ref.js +250 -0
- package/src/transform/jsx/ast-builders.js +64 -13
- package/src/transform/jsx/index.js +563 -98
- package/src/transform/segments.js +21 -19
- package/src/transform/stylesheet.js +19 -0
- package/types/index.d.ts +58 -12
- package/types/jsx-platform.d.ts +47 -3
- package/types/runtime/ref.d.ts +32 -0
- package/src/runtime/merge-refs.js +0 -61
- package/types/runtime/merge-refs.d.ts +0 -12
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Core compiler infrastructure for TSRX syntax",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.28",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -27,10 +27,11 @@
|
|
|
27
27
|
"./types/acorn": {
|
|
28
28
|
"types": "./types/acorn.d.ts"
|
|
29
29
|
},
|
|
30
|
-
"./runtime/
|
|
31
|
-
"types": "./types/runtime/
|
|
32
|
-
"default": "./src/runtime/
|
|
30
|
+
"./runtime/ref": {
|
|
31
|
+
"types": "./types/runtime/ref.d.ts",
|
|
32
|
+
"default": "./src/runtime/ref.js"
|
|
33
33
|
},
|
|
34
|
+
"./runtime/*": "./src/runtime/*.js",
|
|
34
35
|
"./test-harness/source-mappings": "./tests/shared/source-mappings.js",
|
|
35
36
|
"./test-harness/compile": "./tests/shared/compile.js"
|
|
36
37
|
},
|
|
@@ -19,6 +19,10 @@ export const COMPONENT_WHILE_STATEMENT_ERROR =
|
|
|
19
19
|
'While loops are not supported in components. Move the while loop into a function.';
|
|
20
20
|
export const COMPONENT_DO_WHILE_STATEMENT_ERROR =
|
|
21
21
|
'Do...while loops are not supported in components. Move the do...while loop into a function.';
|
|
22
|
+
export const CLASS_COMPONENT_AS_NON_ARROW_PROPERTY_ERROR =
|
|
23
|
+
'Components declared inside a class must be defined as an arrow function class property (e.g. `Foo = component() => { ... }`). Non-arrow component property values are not allowed.';
|
|
24
|
+
export const COMPONENT_MULTIPLE_PARAMS_ERROR =
|
|
25
|
+
'Components accept a single props parameter. Move additional inputs into the props object instead.';
|
|
22
26
|
|
|
23
27
|
const invalid_nestings = {
|
|
24
28
|
// <p> cannot contain block-level elements
|
|
@@ -247,6 +251,61 @@ export function validate_component_unsupported_loop_statement(node, filename, er
|
|
|
247
251
|
error(message, filename ?? null, node, errors, comments);
|
|
248
252
|
}
|
|
249
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Validates that a component declares at most a single (props) parameter.
|
|
256
|
+
* Components have one slot for props; additional positional parameters are
|
|
257
|
+
* silently dropped or naively passed through depending on the target, so
|
|
258
|
+
* reject them at analysis time. Reports one error per extra parameter so
|
|
259
|
+
* every offending input gets its own TS diagnostic squiggle. In throwing
|
|
260
|
+
* mode the first call raises and aborts before the loop continues.
|
|
261
|
+
*
|
|
262
|
+
* @param {AST.Component} component
|
|
263
|
+
* @param {string | null | undefined} filename
|
|
264
|
+
* @param {CompileError[]} [errors]
|
|
265
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
266
|
+
*/
|
|
267
|
+
export function validate_component_params(component, filename, errors, comments) {
|
|
268
|
+
const params = /** @type {AST.Pattern[] | undefined} */ (component.params);
|
|
269
|
+
if (!params || params.length <= 1) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
for (let i = 1; i < params.length; i++) {
|
|
274
|
+
error(COMPONENT_MULTIPLE_PARAMS_ERROR, filename ?? null, params[i], errors, comments);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Validates that components declared at the top level of a class body use the
|
|
280
|
+
* only allowed form: an arrow function class property (regular or static).
|
|
281
|
+
* Reports an error for non-arrow component property values such as
|
|
282
|
+
* `Foo = component() { ... }`. The method form (`component foo() {}` inside
|
|
283
|
+
* a class body) is rejected at parse time and never reaches this check.
|
|
284
|
+
*
|
|
285
|
+
* @param {AST.ClassBody} class_body
|
|
286
|
+
* @param {string | null | undefined} filename
|
|
287
|
+
* @param {CompileError[]} [errors]
|
|
288
|
+
* @param {AST.CommentWithLocation[]} [comments]
|
|
289
|
+
*/
|
|
290
|
+
export function validate_class_component_declarations(class_body, filename, errors, comments) {
|
|
291
|
+
for (const member of class_body.body) {
|
|
292
|
+
if (member.type !== 'PropertyDefinition') {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const value = /** @type {any} */ (member).value;
|
|
297
|
+
if (value && value.type === 'Component' && !value.metadata?.arrow) {
|
|
298
|
+
error(
|
|
299
|
+
CLASS_COMPONENT_AS_NON_ARROW_PROPERTY_ERROR,
|
|
300
|
+
filename ?? null,
|
|
301
|
+
member,
|
|
302
|
+
errors,
|
|
303
|
+
comments,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
250
309
|
/**
|
|
251
310
|
* @param {AST.Element} element
|
|
252
311
|
* @param {AnalysisContext} context
|
package/src/index.js
CHANGED
|
@@ -141,8 +141,14 @@ export { escape, escape_script as escapeScript } from './utils/escaping.js';
|
|
|
141
141
|
|
|
142
142
|
// Transform
|
|
143
143
|
export {
|
|
144
|
+
add_jsx_setup_declaration as addJsxSetupDeclaration,
|
|
144
145
|
createJsxTransform,
|
|
146
|
+
CREATE_REF_PROP_INTERNAL_NAME,
|
|
147
|
+
extract_jsx_setup_declarations as extractJsxSetupDeclarations,
|
|
148
|
+
is_ref_prop_expression as isRefPropExpression,
|
|
149
|
+
MERGE_REFS_INTERNAL_NAME,
|
|
145
150
|
merge_duplicate_refs as mergeDuplicateRefs,
|
|
151
|
+
NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
|
|
146
152
|
rewrite_loop_continues_to_bare_returns as rewriteLoopContinuesToBareReturns,
|
|
147
153
|
to_jsx_attribute as toJsxAttribute,
|
|
148
154
|
validate_at_most_one_ref_attribute as validateAtMostOneRefAttribute,
|
|
@@ -164,12 +170,16 @@ export {
|
|
|
164
170
|
flatten_switch_consequent,
|
|
165
171
|
get_for_of_iteration_params,
|
|
166
172
|
identifier_to_jsx_name,
|
|
173
|
+
is_bare_render_expression,
|
|
167
174
|
is_dynamic_element_id,
|
|
168
175
|
is_jsx_child,
|
|
169
176
|
set_loc,
|
|
170
177
|
to_text_expression,
|
|
171
178
|
} from './transform/jsx/ast-builders.js';
|
|
172
|
-
export {
|
|
179
|
+
export {
|
|
180
|
+
render_stylesheets as renderStylesheets,
|
|
181
|
+
render_css_result as renderCssResult,
|
|
182
|
+
} from './transform/stylesheet.js';
|
|
173
183
|
export {
|
|
174
184
|
prepare_stylesheet_for_render as prepareStylesheetForRender,
|
|
175
185
|
is_style_element as isStyleElement,
|
|
@@ -213,17 +223,21 @@ export {
|
|
|
213
223
|
// Analyze
|
|
214
224
|
export { analyze_css as analyzeCss } from './analyze/css-analyze.js';
|
|
215
225
|
export {
|
|
226
|
+
CLASS_COMPONENT_AS_NON_ARROW_PROPERTY_ERROR,
|
|
216
227
|
COMPONENT_DO_WHILE_STATEMENT_ERROR,
|
|
217
228
|
COMPONENT_FOR_IN_STATEMENT_ERROR,
|
|
218
229
|
COMPONENT_FOR_STATEMENT_ERROR,
|
|
219
230
|
COMPONENT_LOOP_BREAK_ERROR,
|
|
220
231
|
COMPONENT_LOOP_RETURN_ERROR,
|
|
232
|
+
COMPONENT_MULTIPLE_PARAMS_ERROR,
|
|
221
233
|
COMPONENT_RETURN_VALUE_ERROR,
|
|
222
234
|
COMPONENT_WHILE_STATEMENT_ERROR,
|
|
223
235
|
get_return_keyword_node as getReturnKeywordNode,
|
|
224
236
|
get_statement_keyword_node as getStatementKeywordNode,
|
|
237
|
+
validate_class_component_declarations as validateClassComponentDeclarations,
|
|
225
238
|
validate_component_loop_break_statement as validateComponentLoopBreakStatement,
|
|
226
239
|
validate_component_loop_return_statement as validateComponentLoopReturnStatement,
|
|
240
|
+
validate_component_params as validateComponentParams,
|
|
227
241
|
validate_component_return_statement as validateComponentReturnStatement,
|
|
228
242
|
validate_component_unsupported_loop_statement as validateComponentUnsupportedLoopStatement,
|
|
229
243
|
validate_nesting as validateNesting,
|
package/src/plugin.js
CHANGED
|
@@ -559,65 +559,6 @@ export function TSRXPlugin(config) {
|
|
|
559
559
|
return super.parseProperty(isPattern, refDestructuringErrors);
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
-
/**
|
|
563
|
-
* Override parseClassElement to support component methods in classes.
|
|
564
|
-
* Handles syntax like `class Foo { component something() { <div /> } }`
|
|
565
|
-
* Also supports computed names: `class Foo { component ['something']() { <div /> } }`
|
|
566
|
-
* @type {Parse.Parser['parseClassElement']}
|
|
567
|
-
*/
|
|
568
|
-
parseClassElement(constructorAllowsSuper) {
|
|
569
|
-
// Check if this is a component method: component name( ... ) { ... }
|
|
570
|
-
if (this.type === tt.name && this.value === 'component') {
|
|
571
|
-
// Look ahead to see if this is "component identifier(",
|
|
572
|
-
// "component identifier<", "component [", or "component 'string'"
|
|
573
|
-
const lookahead = this.input.slice(this.pos).match(/^\s*(?:(\w+)\s*[(<]|\[|['"])/);
|
|
574
|
-
if (lookahead) {
|
|
575
|
-
// This is a component method definition
|
|
576
|
-
const node = /** @type {AST.MethodDefinition} */ (this.startNode());
|
|
577
|
-
const isComputed = lookahead[0].trim().startsWith('[');
|
|
578
|
-
const isStringLiteral = /^['"]/.test(lookahead[0].trim());
|
|
579
|
-
|
|
580
|
-
if (isComputed) {
|
|
581
|
-
// For computed names, consume 'component'
|
|
582
|
-
// parse the key, then parse component without name
|
|
583
|
-
this.next(); // consume 'component'
|
|
584
|
-
this.next(); // consume '['
|
|
585
|
-
node.key = this.parseExpression();
|
|
586
|
-
this.expect(tt.bracketR);
|
|
587
|
-
node.computed = true;
|
|
588
|
-
|
|
589
|
-
// Parse component without name (skipName: true)
|
|
590
|
-
const component_node = this.parseComponent({ skipName: true });
|
|
591
|
-
/** @type {AST.TSRXMethodDefinition} */ (node).value = component_node;
|
|
592
|
-
} else if (isStringLiteral) {
|
|
593
|
-
// For string literal names, consume 'component'
|
|
594
|
-
// parse the string key, then parse component without name
|
|
595
|
-
this.next(); // consume 'component'
|
|
596
|
-
node.key = /** @type {AST.Literal} */ (this.parseExprAtom());
|
|
597
|
-
node.computed = false;
|
|
598
|
-
|
|
599
|
-
// Parse component without name (skipName: true)
|
|
600
|
-
const component_node = this.parseComponent({ skipName: true });
|
|
601
|
-
/** @type {AST.TSRXMethodDefinition} */ (node).value = component_node;
|
|
602
|
-
} else {
|
|
603
|
-
// Use parseComponent which handles consuming 'component', parsing name, params, and body
|
|
604
|
-
const component_node = this.parseComponent({ requireName: true });
|
|
605
|
-
|
|
606
|
-
node.key = /** @type {AST.Identifier} */ (component_node.id);
|
|
607
|
-
/** @type {AST.TSRXMethodDefinition} */ (node).value = component_node;
|
|
608
|
-
node.computed = false;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
node.static = false;
|
|
612
|
-
node.kind = 'method';
|
|
613
|
-
|
|
614
|
-
return this.finishNode(node, 'MethodDefinition');
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
return super.parseClassElement(constructorAllowsSuper);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
562
|
/**
|
|
622
563
|
* Override parsePropertyValue to support TypeScript generic methods in object literals.
|
|
623
564
|
* By default, acorn-typescript doesn't handle `{ method<T>() {} }` syntax.
|
|
@@ -1371,15 +1312,15 @@ export function TSRXPlugin(config) {
|
|
|
1371
1312
|
*/
|
|
1372
1313
|
checkUnreserved(ref) {
|
|
1373
1314
|
if (ref.name === 'component') {
|
|
1374
|
-
// Allow 'component' when it's followed by an identifier and '(' or '<' (component method in object literal
|
|
1375
|
-
// e.g., { component something() { ... } }
|
|
1315
|
+
// Allow 'component' when it's followed by an identifier and '(' or '<' (component method in object literal)
|
|
1316
|
+
// e.g., { component something() { ... } }
|
|
1376
1317
|
// Also allow computed names: { component ['name']() { ... } }
|
|
1377
1318
|
// Also allow string literal names: { component 'name'() { ... } }
|
|
1378
1319
|
const nextChars = this.input.slice(this.pos).match(/^\s*(?:(\w+)\s*[(<]|\[|['"])/);
|
|
1379
1320
|
if (!nextChars) {
|
|
1380
1321
|
this.raise(
|
|
1381
1322
|
ref.start,
|
|
1382
|
-
'"component" is a
|
|
1323
|
+
'"component" is a TSRX keyword and cannot be used as an identifier',
|
|
1383
1324
|
);
|
|
1384
1325
|
}
|
|
1385
1326
|
}
|
|
@@ -1404,6 +1345,21 @@ export function TSRXPlugin(config) {
|
|
|
1404
1345
|
let node = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
|
|
1405
1346
|
this.next();
|
|
1406
1347
|
|
|
1348
|
+
if (this.type === tt.name && this.value === 'ref') {
|
|
1349
|
+
const ref_node = /** @type {AST.RefExpression} */ (this.startNode());
|
|
1350
|
+
this.next();
|
|
1351
|
+
if (this.type === tt.braceR) {
|
|
1352
|
+
this.raise(
|
|
1353
|
+
this.start,
|
|
1354
|
+
'"ref" is a TSRX keyword and must be used in the form {ref item}',
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
ref_node.argument = this.parseMaybeAssign();
|
|
1358
|
+
node.expression = /** @type {any} */ (this.finishNode(ref_node, 'RefExpression'));
|
|
1359
|
+
this.expect(tt.braceR);
|
|
1360
|
+
return this.finishNode(node, 'JSXExpressionContainer');
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1407
1363
|
if (this.type === tt.name && this.value === 'html') {
|
|
1408
1364
|
node.html = true;
|
|
1409
1365
|
this.next();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** @type {typeof Object.getOwnPropertyDescriptor} */
|
|
2
|
+
export var get_descriptor = Object.getOwnPropertyDescriptor;
|
|
3
|
+
/** @type {typeof Object.getOwnPropertyDescriptors} */
|
|
4
|
+
export var get_descriptors = Object.getOwnPropertyDescriptors;
|
|
5
|
+
/** @type {typeof Array.from} */
|
|
6
|
+
export var array_from = Array.from;
|
|
7
|
+
/** @type {typeof Array.isArray} */
|
|
8
|
+
export var is_array = Array.isArray;
|
|
9
|
+
/** @type {typeof Object.defineProperty} */
|
|
10
|
+
export var define_property = Object.defineProperty;
|
|
11
|
+
/** @type {typeof Object.getPrototypeOf} */
|
|
12
|
+
export var get_prototype_of = Object.getPrototypeOf;
|
|
13
|
+
/** @type {typeof Object.values} */
|
|
14
|
+
export var object_values = Object.values;
|
|
15
|
+
/** @type {typeof Object.entries} */
|
|
16
|
+
export var object_entries = Object.entries;
|
|
17
|
+
/** @type {typeof Object.keys} */
|
|
18
|
+
export var object_keys = Object.keys;
|
|
19
|
+
/** @type {typeof Object.getOwnPropertySymbols} */
|
|
20
|
+
export var get_own_property_symbols = Object.getOwnPropertySymbols;
|
|
21
|
+
/** @type {typeof structuredClone} */
|
|
22
|
+
export var structured_clone = structuredClone;
|
|
23
|
+
/** @type {typeof Object.prototype} */
|
|
24
|
+
export var object_prototype = Object.prototype;
|
|
25
|
+
/** @type {typeof Array.prototype} */
|
|
26
|
+
export var array_prototype = Array.prototype;
|
|
27
|
+
/** @type {typeof Object.prototype.hasOwnProperty} */
|
|
28
|
+
export var has_own_property = object_prototype.hasOwnProperty;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {object} value
|
|
32
|
+
* @param {PropertyKey} key
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
*/
|
|
35
|
+
export function has_prototype_accessor(value, key) {
|
|
36
|
+
var proto = get_prototype_of(value);
|
|
37
|
+
while (proto != null) {
|
|
38
|
+
var descriptor = get_descriptor(proto, key);
|
|
39
|
+
if (descriptor !== undefined) {
|
|
40
|
+
return typeof descriptor.get === 'function' || typeof descriptor.set === 'function';
|
|
41
|
+
}
|
|
42
|
+
proto = get_prototype_of(proto);
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Slice helper for arrays and array-like values.
|
|
49
|
+
* @param {ArrayLike<any>} array_like
|
|
50
|
+
* @param {...number} args
|
|
51
|
+
* @returns {any[]}
|
|
52
|
+
*/
|
|
53
|
+
export function array_slice(array_like, ...args) {
|
|
54
|
+
return is_array(array_like)
|
|
55
|
+
? array_like.slice(...args)
|
|
56
|
+
: array_prototype.slice.call(array_like, ...args);
|
|
57
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
has_own_property,
|
|
3
|
+
get_descriptor,
|
|
4
|
+
has_prototype_accessor,
|
|
5
|
+
} from '@tsrx/core/runtime/language-helpers';
|
|
6
|
+
|
|
7
|
+
const REF_VALUE = Symbol();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Merge multiple refs (function refs and ref objects) into a single
|
|
11
|
+
* callback ref. Used by React, Preact, and Vue targets when an element has
|
|
12
|
+
* more than one `ref` attribute.
|
|
13
|
+
* This is a public method and also used by the compiler to unite any refs with
|
|
14
|
+
* any of the supported syntaxes. It does not process spreads, that is delegated to
|
|
15
|
+
* `normalize_spread_props`.
|
|
16
|
+
*
|
|
17
|
+
* @param {...((node: any) => void | (() => void)) | { current: any } | { value: any } | null | undefined} refs
|
|
18
|
+
* @returns {(node: any) => (() => void)}
|
|
19
|
+
*/
|
|
20
|
+
export function mergeRefs(...refs) {
|
|
21
|
+
return (node) => {
|
|
22
|
+
/** @type {Array<() => void>} */
|
|
23
|
+
const cleanups = [];
|
|
24
|
+
for (const ref of refs) {
|
|
25
|
+
if (ref == null) continue;
|
|
26
|
+
if (typeof ref === 'function') {
|
|
27
|
+
const result = ref(node);
|
|
28
|
+
if (typeof result === 'function') {
|
|
29
|
+
cleanups.push(result);
|
|
30
|
+
} else {
|
|
31
|
+
cleanups.push(() => ref(null));
|
|
32
|
+
}
|
|
33
|
+
} else if (is_ref_object(ref, 'current')) {
|
|
34
|
+
/** @type {{ current: any }} */ (ref).current = node;
|
|
35
|
+
cleanups.push(() => {
|
|
36
|
+
/** @type {{ current: any }} */ (ref).current = null;
|
|
37
|
+
});
|
|
38
|
+
} else if (is_ref_object(ref, 'value')) {
|
|
39
|
+
/** @type {{ value: any }} */ (ref).value = node;
|
|
40
|
+
cleanups.push(() => {
|
|
41
|
+
/** @type {{ value: any }} */ (ref).value = null;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return () => {
|
|
46
|
+
for (const cleanup of cleanups) cleanup();
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { is_ref_prop as isRefProp };
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {unknown} value
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
function is_ref_prop(value) {
|
|
58
|
+
return typeof value === 'function' && REF_VALUE in value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {any} ref_value
|
|
63
|
+
* @param {any} node
|
|
64
|
+
* @param {(value: any) => void} [set_ref_value]
|
|
65
|
+
* @returns {void | (() => void)}
|
|
66
|
+
*/
|
|
67
|
+
export function apply_ref_value(ref_value, node, set_ref_value) {
|
|
68
|
+
if (typeof ref_value === 'function') {
|
|
69
|
+
return ref_value(node);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (ref_value && typeof ref_value === 'object') {
|
|
73
|
+
if (is_ref_object(ref_value, 'current')) {
|
|
74
|
+
ref_value.current = node;
|
|
75
|
+
return () => {
|
|
76
|
+
ref_value.current = null;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (is_ref_object(ref_value, 'value')) {
|
|
81
|
+
ref_value.value = node;
|
|
82
|
+
return () => {
|
|
83
|
+
ref_value.value = null;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (set_ref_value !== undefined) {
|
|
89
|
+
set_ref_value(node);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {() => any} get_ref_value
|
|
95
|
+
* @param {(value: any) => void} [set_ref_value]
|
|
96
|
+
* @returns {(node: any) => void | (() => void)}
|
|
97
|
+
*/
|
|
98
|
+
export function create_ref_prop(get_ref_value, set_ref_value) {
|
|
99
|
+
/**
|
|
100
|
+
* @param {any} node
|
|
101
|
+
* @returns {void | (() => void)}
|
|
102
|
+
*/
|
|
103
|
+
function ref_prop_callback(node) {
|
|
104
|
+
const ref_value = get_ref_value();
|
|
105
|
+
const cleanup = apply_ref_value(ref_value, node, set_ref_value);
|
|
106
|
+
if (typeof cleanup === 'function' || node === null) {
|
|
107
|
+
return cleanup;
|
|
108
|
+
}
|
|
109
|
+
return () => {
|
|
110
|
+
apply_ref_value(ref_value, null, set_ref_value);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
Object.defineProperty(ref_prop_callback, REF_VALUE, {
|
|
115
|
+
value: 'ref_value',
|
|
116
|
+
enumerable: false,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return ref_prop_callback;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {...any} refs
|
|
124
|
+
* @returns {any}
|
|
125
|
+
*/
|
|
126
|
+
export function merge_ref_props(...refs) {
|
|
127
|
+
const filtered = refs.filter((ref) => ref != null);
|
|
128
|
+
|
|
129
|
+
if (filtered.length === 0) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (filtered.length === 1) {
|
|
134
|
+
return filtered[0];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {any} node
|
|
139
|
+
* @returns {void | (() => void)}
|
|
140
|
+
*/
|
|
141
|
+
function merged_ref_prop(node) {
|
|
142
|
+
/** @type {Array<() => void>} */
|
|
143
|
+
const cleanups = [];
|
|
144
|
+
|
|
145
|
+
for (const ref of filtered) {
|
|
146
|
+
const cleanup = apply_ref_value(ref, node);
|
|
147
|
+
if (typeof cleanup === 'function') {
|
|
148
|
+
cleanups.push(cleanup);
|
|
149
|
+
} else if (typeof ref === 'function' && node !== null) {
|
|
150
|
+
cleanups.push(() => ref(null));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return () => {
|
|
155
|
+
for (const cleanup of cleanups) {
|
|
156
|
+
cleanup();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return merged_ref_prop;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {Record<string | symbol, any> | null | undefined} props
|
|
166
|
+
* @param {...any} outer_refs
|
|
167
|
+
* @returns {Record<string | symbol, any> | null | undefined}
|
|
168
|
+
*/
|
|
169
|
+
export function normalize_spread_props(props, ...outer_refs) {
|
|
170
|
+
if (props == null) {
|
|
171
|
+
return props;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** @type {any[]} */
|
|
175
|
+
const refs = [];
|
|
176
|
+
/** @type {Record<string | symbol, any>} */
|
|
177
|
+
let next = {};
|
|
178
|
+
let changed = false;
|
|
179
|
+
let existing_ref;
|
|
180
|
+
|
|
181
|
+
for (const key of Reflect.ownKeys(props)) {
|
|
182
|
+
const descriptor = get_descriptor(props, key);
|
|
183
|
+
if (!descriptor?.enumerable) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const value = /** @type {any} */ (props)[key];
|
|
188
|
+
|
|
189
|
+
if (key === 'ref') {
|
|
190
|
+
if (is_ref_prop(value)) {
|
|
191
|
+
refs.push(value);
|
|
192
|
+
changed = true;
|
|
193
|
+
} else {
|
|
194
|
+
existing_ref = value;
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (is_ref_prop(value)) {
|
|
200
|
+
refs.push(value);
|
|
201
|
+
changed = true;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
next[key] = value;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!changed && outer_refs.length === 0) {
|
|
209
|
+
return props;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const merged_ref = merge_ref_props(existing_ref, ...refs, ...outer_refs);
|
|
213
|
+
if (merged_ref !== undefined) {
|
|
214
|
+
next.ref = merged_ref;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return next;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {object} value
|
|
222
|
+
* @param {'current' | 'value'} key
|
|
223
|
+
* @returns {boolean}
|
|
224
|
+
*/
|
|
225
|
+
function is_ref_object(value, key) {
|
|
226
|
+
if (is_dom_node(value)) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
if (key === 'value' && '__v_isRef' in value) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (has_own_property.call(value, key)) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return key === 'value' && has_prototype_accessor(value, 'value');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @param {object} value
|
|
240
|
+
* @returns {boolean}
|
|
241
|
+
*/
|
|
242
|
+
function is_dom_node(value) {
|
|
243
|
+
return (
|
|
244
|
+
(typeof Node !== 'undefined' && value instanceof Node) ||
|
|
245
|
+
('nodeType' in value &&
|
|
246
|
+
typeof (/** @type {{ nodeType?: unknown }} */ (value).nodeType) === 'number' &&
|
|
247
|
+
'nodeName' in value &&
|
|
248
|
+
typeof (/** @type {{ nodeName?: unknown }} */ (value).nodeName) === 'string')
|
|
249
|
+
);
|
|
250
|
+
}
|
|
@@ -143,7 +143,7 @@ export function identifier_to_jsx_name(id) {
|
|
|
143
143
|
/** @type {any} */ ({
|
|
144
144
|
type: 'JSXIdentifier',
|
|
145
145
|
name: id.name,
|
|
146
|
-
metadata: { path: [], is_component: /^[A-Z]/.test(id.name) },
|
|
146
|
+
metadata: { ...(id.metadata || {}), path: [], is_component: /^[A-Z]/.test(id.name) },
|
|
147
147
|
}),
|
|
148
148
|
id,
|
|
149
149
|
);
|
|
@@ -187,6 +187,57 @@ export function is_jsx_child(node) {
|
|
|
187
187
|
);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* The parser represents `<>{expr}</>` / `<tsx>{expr}</tsx>` as a Tsx node,
|
|
192
|
+
* and expression-position lowering unwraps that to the inner expression.
|
|
193
|
+
* When such a node appears directly in a component or statement render body,
|
|
194
|
+
* the unwrapped expression is still render output rather than an executable
|
|
195
|
+
* statement.
|
|
196
|
+
*
|
|
197
|
+
* @param {any} node
|
|
198
|
+
* @returns {boolean}
|
|
199
|
+
*/
|
|
200
|
+
export function is_bare_render_expression(node) {
|
|
201
|
+
if (!node || typeof node !== 'object') {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
switch (node.type) {
|
|
206
|
+
case 'ArrayExpression':
|
|
207
|
+
case 'ArrowFunctionExpression':
|
|
208
|
+
case 'AssignmentExpression':
|
|
209
|
+
case 'AwaitExpression':
|
|
210
|
+
case 'BinaryExpression':
|
|
211
|
+
case 'CallExpression':
|
|
212
|
+
case 'ChainExpression':
|
|
213
|
+
case 'ClassExpression':
|
|
214
|
+
case 'ConditionalExpression':
|
|
215
|
+
case 'FunctionExpression':
|
|
216
|
+
case 'Identifier':
|
|
217
|
+
case 'ImportExpression':
|
|
218
|
+
case 'Literal':
|
|
219
|
+
case 'LogicalExpression':
|
|
220
|
+
case 'MemberExpression':
|
|
221
|
+
case 'MetaProperty':
|
|
222
|
+
case 'NewExpression':
|
|
223
|
+
case 'ObjectExpression':
|
|
224
|
+
case 'ParenthesizedExpression':
|
|
225
|
+
case 'SequenceExpression':
|
|
226
|
+
case 'TaggedTemplateExpression':
|
|
227
|
+
case 'TemplateLiteral':
|
|
228
|
+
case 'ThisExpression':
|
|
229
|
+
case 'TSAsExpression':
|
|
230
|
+
case 'TSSatisfiesExpression':
|
|
231
|
+
case 'TSNonNullExpression':
|
|
232
|
+
case 'UnaryExpression':
|
|
233
|
+
case 'UpdateExpression':
|
|
234
|
+
case 'YieldExpression':
|
|
235
|
+
return true;
|
|
236
|
+
default:
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
190
241
|
/**
|
|
191
242
|
* A dynamic element id is one whose identifier is `tracked` — i.e. it was
|
|
192
243
|
* introduced by reactive destructuring so its value can change at runtime.
|
|
@@ -325,26 +376,26 @@ export function to_text_expression(expression, source_node = expression) {
|
|
|
325
376
|
}
|
|
326
377
|
|
|
327
378
|
/**
|
|
328
|
-
* Deep-clone an AST subtree.
|
|
329
|
-
* reference rather than recursed into — `loc` objects can contain back-refs
|
|
330
|
-
* to sub-objects that would blow the stack with a naive deep clone, and
|
|
331
|
-
* every other traversal in the targets treats these positional keys as
|
|
332
|
-
* shared.
|
|
379
|
+
* Deep-clone an AST subtree.
|
|
333
380
|
*
|
|
334
381
|
* @param {any} node
|
|
382
|
+
* @param {boolean} with_locations
|
|
335
383
|
* @returns {any}
|
|
336
384
|
*/
|
|
337
|
-
export function clone_expression_node(node) {
|
|
385
|
+
export function clone_expression_node(node, with_locations = true) {
|
|
338
386
|
if (!node || typeof node !== 'object') return node;
|
|
339
|
-
if (Array.isArray(node)) return node.map(clone_expression_node);
|
|
340
|
-
const clone = {
|
|
341
|
-
|
|
342
|
-
|
|
387
|
+
if (Array.isArray(node)) return node.map((child) => clone_expression_node(child, with_locations));
|
|
388
|
+
const clone = /** @type {Record<string, any>} */ ({});
|
|
389
|
+
|
|
390
|
+
for (const key of Object.keys(node)) {
|
|
391
|
+
if (!with_locations && (key === 'loc' || key === 'start' || key === 'end')) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
343
394
|
if (key === 'metadata') {
|
|
344
|
-
clone.metadata =
|
|
395
|
+
clone.metadata = node.metadata ? { ...node.metadata } : { path: [] };
|
|
345
396
|
continue;
|
|
346
397
|
}
|
|
347
|
-
clone[key] = clone_expression_node(
|
|
398
|
+
clone[key] = clone_expression_node(node[key], with_locations);
|
|
348
399
|
}
|
|
349
400
|
return clone;
|
|
350
401
|
}
|