@tsrx/core 0.0.27 → 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/package.json +5 -4
- package/src/index.js +6 -0
- package/src/plugin.js +131 -25
- package/src/runtime/language-helpers.js +57 -0
- package/src/runtime/ref.js +250 -0
- package/src/scope.js +7 -0
- package/src/transform/jsx/ast-builders.js +14 -13
- package/src/transform/jsx/index.js +490 -55
- package/src/transform/segments.js +19 -9
- package/types/index.d.ts +20 -1
- package/types/jsx-platform.d.ts +14 -1
- package/types/parse.d.ts +1 -1
- 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.1.0",
|
|
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
|
},
|
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,
|
package/src/plugin.js
CHANGED
|
@@ -18,7 +18,7 @@ import { error } from './errors.js';
|
|
|
18
18
|
import { DIAGNOSTIC_CODES } from './diagnostics.js';
|
|
19
19
|
|
|
20
20
|
const JSX_EXPRESSION_VALUE_ERROR =
|
|
21
|
-
'JSX elements cannot be used as expressions. Wrap with `<>...</>` or `<tsx>...</tsx
|
|
21
|
+
'JSX elements cannot be used as expressions. Wrap JSX with `<>...</>` or `<tsx>...</tsx>`, wrap TSRX templates with `<tsrx>...</tsrx>`, or use elements as statements within a component.';
|
|
22
22
|
|
|
23
23
|
/** @type {WeakMap<Record<string, boolean>, Map<string, number>>} */
|
|
24
24
|
const argument_clash_first_positions = new WeakMap();
|
|
@@ -325,7 +325,10 @@ export function TSRXPlugin(config) {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
const parent = this.#path.at(-1);
|
|
328
|
-
if (
|
|
328
|
+
if (
|
|
329
|
+
!parent ||
|
|
330
|
+
(parent.type !== 'Component' && parent.type !== 'Element' && parent.type !== 'Tsrx')
|
|
331
|
+
) {
|
|
329
332
|
return false;
|
|
330
333
|
}
|
|
331
334
|
|
|
@@ -1320,7 +1323,7 @@ export function TSRXPlugin(config) {
|
|
|
1320
1323
|
if (!nextChars) {
|
|
1321
1324
|
this.raise(
|
|
1322
1325
|
ref.start,
|
|
1323
|
-
'"component" is a
|
|
1326
|
+
'"component" is a TSRX keyword and cannot be used as an identifier',
|
|
1324
1327
|
);
|
|
1325
1328
|
}
|
|
1326
1329
|
}
|
|
@@ -1345,6 +1348,21 @@ export function TSRXPlugin(config) {
|
|
|
1345
1348
|
let node = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
|
|
1346
1349
|
this.next();
|
|
1347
1350
|
|
|
1351
|
+
if (this.type === tt.name && this.value === 'ref') {
|
|
1352
|
+
const ref_node = /** @type {AST.RefExpression} */ (this.startNode());
|
|
1353
|
+
this.next();
|
|
1354
|
+
if (this.type === tt.braceR) {
|
|
1355
|
+
this.raise(
|
|
1356
|
+
this.start,
|
|
1357
|
+
'"ref" is a TSRX keyword and must be used in the form {ref item}',
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
ref_node.argument = this.parseMaybeAssign();
|
|
1361
|
+
node.expression = /** @type {any} */ (this.finishNode(ref_node, 'RefExpression'));
|
|
1362
|
+
this.expect(tt.braceR);
|
|
1363
|
+
return this.finishNode(node, 'JSXExpressionContainer');
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1348
1366
|
if (this.type === tt.name && this.value === 'html') {
|
|
1349
1367
|
node.html = true;
|
|
1350
1368
|
this.next();
|
|
@@ -1794,7 +1812,8 @@ export function TSRXPlugin(config) {
|
|
|
1794
1812
|
ch === 125 &&
|
|
1795
1813
|
(this.#path.length === 0 ||
|
|
1796
1814
|
this.#path.at(-1)?.type === 'Component' ||
|
|
1797
|
-
this.#path.at(-1)?.type === 'Element'
|
|
1815
|
+
this.#path.at(-1)?.type === 'Element' ||
|
|
1816
|
+
this.#path.at(-1)?.type === 'Tsrx')
|
|
1798
1817
|
) {
|
|
1799
1818
|
this.#resetTokenStartToCurrentPosition();
|
|
1800
1819
|
return original.readToken.call(this, ch);
|
|
@@ -1834,7 +1853,9 @@ export function TSRXPlugin(config) {
|
|
|
1834
1853
|
* Override jsx_parseElement to intercept expression-level JSX.
|
|
1835
1854
|
* This is called by acorn-jsx's parseExprAtom when it encounters <
|
|
1836
1855
|
* in expression position. Bare fragments are treated as shorthand
|
|
1837
|
-
* for <tsx>...</tsx
|
|
1856
|
+
* for <tsx>...</tsx>. <tsrx>...</tsrx> admits native TSRX
|
|
1857
|
+
* template syntax as an expression value. Other tags must still use
|
|
1858
|
+
* <tsx>, <tsrx>, or <tsx:*>.
|
|
1838
1859
|
* @type {Parse.Parser['jsx_parseElement']}
|
|
1839
1860
|
*/
|
|
1840
1861
|
jsx_parseElement() {
|
|
@@ -1844,11 +1865,12 @@ export function TSRXPlugin(config) {
|
|
|
1844
1865
|
return super.jsx_parseElement();
|
|
1845
1866
|
}
|
|
1846
1867
|
|
|
1847
|
-
// Check if the element being parsed IS a <tsx
|
|
1868
|
+
// Check if the element being parsed IS a <tsx>, <tsrx>, or <tsx:*> tag
|
|
1848
1869
|
// Current token is jsxTagStart, this.end is position after '<'
|
|
1849
1870
|
const tag_name_start = this.end;
|
|
1850
1871
|
const is_fragment_tag = this.input.charCodeAt(tag_name_start) === 62;
|
|
1851
1872
|
const char_after_tsx = this.input.charCodeAt(tag_name_start + 3);
|
|
1873
|
+
const char_after_tsrx = this.input.charCodeAt(tag_name_start + 4);
|
|
1852
1874
|
const is_tsx_tag =
|
|
1853
1875
|
this.input.startsWith('tsx', tag_name_start) &&
|
|
1854
1876
|
(tag_name_start + 3 >= this.input.length ||
|
|
@@ -1859,9 +1881,18 @@ export function TSRXPlugin(config) {
|
|
|
1859
1881
|
char_after_tsx === 10 || // newline
|
|
1860
1882
|
char_after_tsx === 13 || // carriage return
|
|
1861
1883
|
char_after_tsx === 58); // : (tsx:react)
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1884
|
+
const is_tsrx_tag =
|
|
1885
|
+
this.input.startsWith('tsrx', tag_name_start) &&
|
|
1886
|
+
(tag_name_start + 4 >= this.input.length ||
|
|
1887
|
+
char_after_tsrx === 62 || // >
|
|
1888
|
+
char_after_tsrx === 47 || // / (self-closing)
|
|
1889
|
+
char_after_tsrx === 32 || // space
|
|
1890
|
+
char_after_tsrx === 9 || // tab
|
|
1891
|
+
char_after_tsrx === 10 || // newline
|
|
1892
|
+
char_after_tsrx === 13); // carriage return
|
|
1893
|
+
|
|
1894
|
+
if (is_fragment_tag || is_tsx_tag || is_tsrx_tag) {
|
|
1895
|
+
// Use Ripple's parseElement to create a Tsx/Tsrx/TsxCompat node.
|
|
1865
1896
|
// Bare fragments (<></>) are shorthand for <tsx>...</tsx>.
|
|
1866
1897
|
this.next();
|
|
1867
1898
|
return /** @type {import('estree-jsx').JSXElement} */ (
|
|
@@ -1892,7 +1923,9 @@ export function TSRXPlugin(config) {
|
|
|
1892
1923
|
const start = this.start - 1;
|
|
1893
1924
|
const position = new acorn.Position(this.curLine, start - this.lineStart);
|
|
1894
1925
|
|
|
1895
|
-
const element = /** @type {AST.Element | AST.Tsx | AST.TsxCompat} */ (
|
|
1926
|
+
const element = /** @type {AST.Element | AST.Tsx | AST.Tsrx | AST.TsxCompat} */ (
|
|
1927
|
+
this.startNode()
|
|
1928
|
+
);
|
|
1896
1929
|
element.start = start;
|
|
1897
1930
|
/** @type {AST.NodeWithLocation} */ (element).loc.start = position;
|
|
1898
1931
|
element.metadata = { path: [] };
|
|
@@ -1913,6 +1946,11 @@ export function TSRXPlugin(config) {
|
|
|
1913
1946
|
!is_tsx_compat &&
|
|
1914
1947
|
open.name.type === 'JSXIdentifier' &&
|
|
1915
1948
|
open.name.name === 'tsx';
|
|
1949
|
+
const is_tsrx =
|
|
1950
|
+
!is_fragment &&
|
|
1951
|
+
!is_tsx_compat &&
|
|
1952
|
+
open.name.type === 'JSXIdentifier' &&
|
|
1953
|
+
open.name.name === 'tsrx';
|
|
1916
1954
|
|
|
1917
1955
|
if (is_tsx_compat) {
|
|
1918
1956
|
const namespace_node = /** @type {ESTreeJSX.JSXNamespacedName} */ (open.name);
|
|
@@ -1935,6 +1973,15 @@ export function TSRXPlugin(config) {
|
|
|
1935
1973
|
`TSX elements cannot be self-closing. '<tsx />' must have a closing tag '</tsx>'.`,
|
|
1936
1974
|
);
|
|
1937
1975
|
}
|
|
1976
|
+
} else if (is_tsrx) {
|
|
1977
|
+
/** @type {AST.Tsrx} */ (element).type = 'Tsrx';
|
|
1978
|
+
|
|
1979
|
+
if (open.selfClosing) {
|
|
1980
|
+
this.raise(
|
|
1981
|
+
open.start,
|
|
1982
|
+
`TSRX elements cannot be self-closing. '<tsrx />' must have a closing tag '</tsrx>'.`,
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1938
1985
|
} else if (is_fragment) {
|
|
1939
1986
|
/** @type {AST.Tsx} */ (element).type = 'Tsx';
|
|
1940
1987
|
} else {
|
|
@@ -1972,7 +2019,7 @@ export function TSRXPlugin(config) {
|
|
|
1972
2019
|
}
|
|
1973
2020
|
}
|
|
1974
2021
|
|
|
1975
|
-
if (!is_tsx_compat && !is_tsx && !is_fragment) {
|
|
2022
|
+
if (!is_tsx_compat && !is_tsx && !is_tsrx && !is_fragment) {
|
|
1976
2023
|
/** @type {AST.Element} */ (element).id = /** @type {AST.Identifier} */ (
|
|
1977
2024
|
convert_from_jsx(/** @type {ESTreeJSX.JSXIdentifier} */ (open.name))
|
|
1978
2025
|
);
|
|
@@ -2166,6 +2213,7 @@ export function TSRXPlugin(config) {
|
|
|
2166
2213
|
parent?.type === 'Component' ||
|
|
2167
2214
|
parent?.type === 'Element' ||
|
|
2168
2215
|
parent?.type === 'Tsx' ||
|
|
2216
|
+
parent?.type === 'Tsrx' ||
|
|
2169
2217
|
parent?.type === 'TsxCompat';
|
|
2170
2218
|
|
|
2171
2219
|
if (curContext === tstc.tc_expr && !insideTemplate) {
|
|
@@ -2234,7 +2282,21 @@ export function TSRXPlugin(config) {
|
|
|
2234
2282
|
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2235
2283
|
this.next();
|
|
2236
2284
|
}
|
|
2237
|
-
} else if (this.#path[this.#path.length - 1] === element) {
|
|
2285
|
+
} else if (element.type === 'Tsrx' && this.#path[this.#path.length - 1] === element) {
|
|
2286
|
+
this.#report_broken_markup_error(
|
|
2287
|
+
this.start,
|
|
2288
|
+
"Unclosed tag '<tsrx>'. Expected '</tsrx>' before end of component.",
|
|
2289
|
+
);
|
|
2290
|
+
element.unclosed = true;
|
|
2291
|
+
/** @type {AST.SourceLocation} */ (element.loc).end = {
|
|
2292
|
+
.../** @type {AST.SourceLocation} */ (element.openingElement.loc).end,
|
|
2293
|
+
};
|
|
2294
|
+
element.end = element.openingElement.end;
|
|
2295
|
+
this.#path.pop();
|
|
2296
|
+
} else if (
|
|
2297
|
+
element.type === 'Element' &&
|
|
2298
|
+
this.#path[this.#path.length - 1] === element
|
|
2299
|
+
) {
|
|
2238
2300
|
// Check if this element was properly closed
|
|
2239
2301
|
const tagName = this.getElementName(element.id);
|
|
2240
2302
|
this.#report_broken_markup_error(
|
|
@@ -2242,7 +2304,7 @@ export function TSRXPlugin(config) {
|
|
|
2242
2304
|
`Unclosed tag '<${tagName}>'. Expected '</${tagName}>' before end of component.`,
|
|
2243
2305
|
);
|
|
2244
2306
|
element.unclosed = true;
|
|
2245
|
-
element.loc.end = {
|
|
2307
|
+
/** @type {AST.SourceLocation} */ (element.loc).end = {
|
|
2246
2308
|
.../** @type {AST.SourceLocation} */ (element.openingElement.loc).end,
|
|
2247
2309
|
};
|
|
2248
2310
|
element.end = element.openingElement.end;
|
|
@@ -2257,6 +2319,7 @@ export function TSRXPlugin(config) {
|
|
|
2257
2319
|
parent?.type === 'Component' ||
|
|
2258
2320
|
parent?.type === 'Element' ||
|
|
2259
2321
|
parent?.type === 'Tsx' ||
|
|
2322
|
+
parent?.type === 'Tsrx' ||
|
|
2260
2323
|
parent?.type === 'TsxCompat';
|
|
2261
2324
|
|
|
2262
2325
|
if (curContext === tstc.tc_expr && !insideTemplate) {
|
|
@@ -2264,7 +2327,13 @@ export function TSRXPlugin(config) {
|
|
|
2264
2327
|
}
|
|
2265
2328
|
}
|
|
2266
2329
|
|
|
2267
|
-
if (
|
|
2330
|
+
if (
|
|
2331
|
+
element.closingElement &&
|
|
2332
|
+
!is_tsx_compat &&
|
|
2333
|
+
!is_tsx &&
|
|
2334
|
+
!is_tsrx &&
|
|
2335
|
+
element.closingElement.name
|
|
2336
|
+
) {
|
|
2268
2337
|
/** @type {unknown} */ (element.closingElement.name) = convert_from_jsx(
|
|
2269
2338
|
element.closingElement.name,
|
|
2270
2339
|
);
|
|
@@ -2465,10 +2534,26 @@ export function TSRXPlugin(config) {
|
|
|
2465
2534
|
this.context.pop();
|
|
2466
2535
|
}
|
|
2467
2536
|
return;
|
|
2468
|
-
} else if (
|
|
2537
|
+
} else if (
|
|
2538
|
+
this.type === tstt.jsxTagStart ||
|
|
2539
|
+
(this.input.charCodeAt(this.start) === 60 /* < */ &&
|
|
2540
|
+
this.input.charCodeAt(this.start + 1) === 47) /* / */
|
|
2541
|
+
) {
|
|
2469
2542
|
const startPos = this.start;
|
|
2470
2543
|
const startLoc = this.startLoc;
|
|
2471
|
-
this.
|
|
2544
|
+
if (this.type === tstt.jsxTagStart) {
|
|
2545
|
+
this.next();
|
|
2546
|
+
} else {
|
|
2547
|
+
// A control-flow block inside <tsrx> can leave the tokenizer
|
|
2548
|
+
// in normal JS mode, so `</tsrx>` may arrive as a relational
|
|
2549
|
+
// `<` token. Re-enter JSX closing-tag parsing manually.
|
|
2550
|
+
this.pos = startPos + 1;
|
|
2551
|
+
this.type = tstt.jsxTagStart;
|
|
2552
|
+
this.start = startPos;
|
|
2553
|
+
this.startLoc = startLoc;
|
|
2554
|
+
this.exprAllowed = false;
|
|
2555
|
+
this.next();
|
|
2556
|
+
}
|
|
2472
2557
|
if (this.value === '/' || this.type === tt.slash) {
|
|
2473
2558
|
// Consume '/'
|
|
2474
2559
|
this.next();
|
|
@@ -2485,6 +2570,7 @@ export function TSRXPlugin(config) {
|
|
|
2485
2570
|
!currentElement ||
|
|
2486
2571
|
(currentElement.type !== 'Element' &&
|
|
2487
2572
|
currentElement.type !== 'Tsx' &&
|
|
2573
|
+
currentElement.type !== 'Tsrx' &&
|
|
2488
2574
|
currentElement.type !== 'TsxCompat')
|
|
2489
2575
|
) {
|
|
2490
2576
|
this.raise(this.start, 'Unexpected closing tag');
|
|
@@ -2507,6 +2593,12 @@ export function TSRXPlugin(config) {
|
|
|
2507
2593
|
closingElement.name?.type === 'JSXNamespacedName'
|
|
2508
2594
|
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
2509
2595
|
: this.getElementName(closingElement.name);
|
|
2596
|
+
} else if (currentElement.type === 'Tsrx') {
|
|
2597
|
+
openingTagName = 'tsrx';
|
|
2598
|
+
closingTagName =
|
|
2599
|
+
closingElement.name?.type === 'JSXNamespacedName'
|
|
2600
|
+
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
2601
|
+
: this.getElementName(closingElement.name);
|
|
2510
2602
|
} else {
|
|
2511
2603
|
// Regular Element node (or fragment)
|
|
2512
2604
|
openingTagName = currentElement.id ? this.getElementName(currentElement.id) : null;
|
|
@@ -2529,7 +2621,12 @@ export function TSRXPlugin(config) {
|
|
|
2529
2621
|
const elem = this.#path[this.#path.length - 1];
|
|
2530
2622
|
|
|
2531
2623
|
// Stop at non-Element boundaries (Component, etc.)
|
|
2532
|
-
if (
|
|
2624
|
+
if (
|
|
2625
|
+
elem.type !== 'Element' &&
|
|
2626
|
+
elem.type !== 'Tsx' &&
|
|
2627
|
+
elem.type !== 'Tsrx' &&
|
|
2628
|
+
elem.type !== 'TsxCompat'
|
|
2629
|
+
) {
|
|
2533
2630
|
break;
|
|
2534
2631
|
}
|
|
2535
2632
|
|
|
@@ -2540,9 +2637,11 @@ export function TSRXPlugin(config) {
|
|
|
2540
2637
|
? elem.openingElement.name
|
|
2541
2638
|
? 'tsx'
|
|
2542
2639
|
: null
|
|
2543
|
-
: elem.
|
|
2544
|
-
?
|
|
2545
|
-
:
|
|
2640
|
+
: elem.type === 'Tsrx'
|
|
2641
|
+
? 'tsrx'
|
|
2642
|
+
: elem.id
|
|
2643
|
+
? this.getElementName(elem.id)
|
|
2644
|
+
: null;
|
|
2546
2645
|
|
|
2547
2646
|
// Found matching opening tag
|
|
2548
2647
|
if (elemName === closingTagName) {
|
|
@@ -2561,12 +2660,19 @@ export function TSRXPlugin(config) {
|
|
|
2561
2660
|
}
|
|
2562
2661
|
|
|
2563
2662
|
const elementToClose = this.#path[this.#path.length - 1];
|
|
2564
|
-
if (
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2663
|
+
if (
|
|
2664
|
+
elementToClose &&
|
|
2665
|
+
(elementToClose.type === 'Element' || elementToClose.type === 'Tsrx')
|
|
2666
|
+
) {
|
|
2667
|
+
const elementToCloseName =
|
|
2668
|
+
elementToClose.type === 'Tsrx'
|
|
2669
|
+
? 'tsrx'
|
|
2670
|
+
: /** @type {AST.Element} */ (elementToClose).id
|
|
2671
|
+
? this.getElementName(/** @type {AST.Element} */ (elementToClose).id)
|
|
2672
|
+
: null;
|
|
2568
2673
|
if (elementToCloseName === closingTagName) {
|
|
2569
|
-
/** @type {AST.Element} */ (elementToClose).closingElement =
|
|
2674
|
+
/** @type {AST.Element | AST.Tsrx} */ (elementToClose).closingElement =
|
|
2675
|
+
closingElement;
|
|
2570
2676
|
}
|
|
2571
2677
|
}
|
|
2572
2678
|
|
|
@@ -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
|
+
}
|
package/src/scope.js
CHANGED
|
@@ -127,6 +127,13 @@ export function create_scopes(ast, root, parent, error_options) {
|
|
|
127
127
|
next({ scope });
|
|
128
128
|
},
|
|
129
129
|
|
|
130
|
+
Tsrx(node, { state, next }) {
|
|
131
|
+
const scope = state.scope.child();
|
|
132
|
+
scopes.set(node, scope);
|
|
133
|
+
|
|
134
|
+
next({ scope });
|
|
135
|
+
},
|
|
136
|
+
|
|
130
137
|
TSModuleDeclaration(node, { state, next }) {
|
|
131
138
|
const is_submodule = node.metadata?.module_keyword === 'module';
|
|
132
139
|
if (is_submodule && node.id?.type === 'Identifier') {
|