@tsrx/core 0.1.31 → 0.1.33
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 +1 -1
- package/src/constants.js +4 -0
- package/src/index.js +4 -1
- package/src/plugin.js +152 -88
- package/src/transform/jsx/helpers.js +14 -39
- package/src/transform/jsx/index.js +408 -47
- package/src/transform/lazy.js +95 -46
- package/src/utils/builders.js +20 -0
- package/types/index.d.ts +6 -0
package/src/transform/lazy.js
CHANGED
|
@@ -99,38 +99,6 @@ function get_lazy_pattern_mapping_range(pattern) {
|
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
/**
|
|
103
|
-
* Synthesize an object-shaped annotation for untyped lazy object params so the
|
|
104
|
-
* virtual TSX can expose prop names to TypeScript completions.
|
|
105
|
-
*
|
|
106
|
-
* @param {any} pattern
|
|
107
|
-
* @returns {any | null}
|
|
108
|
-
*/
|
|
109
|
-
function create_lazy_object_type_annotation(pattern) {
|
|
110
|
-
if (pattern.type !== 'ObjectPattern') return null;
|
|
111
|
-
|
|
112
|
-
const members = [];
|
|
113
|
-
for (const prop of pattern.properties || []) {
|
|
114
|
-
if (prop.type === 'RestElement' || prop.computed) continue;
|
|
115
|
-
|
|
116
|
-
const key = prop.key;
|
|
117
|
-
if (key.type !== 'Identifier' && key.type !== 'Literal') continue;
|
|
118
|
-
|
|
119
|
-
const member_key =
|
|
120
|
-
key.type === 'Identifier'
|
|
121
|
-
? create_generated_identifier(key.name, key)
|
|
122
|
-
: set_source_location({ ...key, metadata: { path: [] } }, key);
|
|
123
|
-
|
|
124
|
-
members.push(
|
|
125
|
-
b.ts_property_signature(member_key, b.ts_type_annotation(b.ts_keyword_type('any'))),
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (members.length === 0) return null;
|
|
130
|
-
|
|
131
|
-
return b.ts_type_annotation(b.ts_type_literal(members));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
102
|
/**
|
|
135
103
|
* @param {any} node
|
|
136
104
|
* @returns {string | null}
|
|
@@ -348,12 +316,14 @@ function visit_topmost_lazy_patterns(pattern, visit) {
|
|
|
348
316
|
|
|
349
317
|
/**
|
|
350
318
|
* Build the replacement identifier for a lazy pattern. When `is_top` is true
|
|
351
|
-
* (the pattern is itself a function parameter) we
|
|
352
|
-
* `typeAnnotation
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
319
|
+
* (the pattern is itself a function parameter) we carry over the author's
|
|
320
|
+
* `typeAnnotation` if one was written and register source-mapping info. An
|
|
321
|
+
* untyped lazy object param gets NO synthesized annotation: since the source
|
|
322
|
+
* specified no type, the generated param is left implicitly `any` rather than
|
|
323
|
+
* being given a fabricated `{ … : any }` object type. Nested replacements
|
|
324
|
+
* (inside a non-lazy outer destructure) can't carry an inline type annotation —
|
|
325
|
+
* that's not valid syntax — so they get a plain identifier with just source-range
|
|
326
|
+
* info.
|
|
357
327
|
*
|
|
358
328
|
* @param {any} pattern
|
|
359
329
|
* @param {boolean} is_top
|
|
@@ -372,9 +342,6 @@ function build_lazy_id_for_pattern(pattern, is_top) {
|
|
|
372
342
|
if (!is_top) return lazy_id;
|
|
373
343
|
if (pattern.typeAnnotation) {
|
|
374
344
|
lazy_id.typeAnnotation = pattern.typeAnnotation;
|
|
375
|
-
} else {
|
|
376
|
-
const type_annotation = create_lazy_object_type_annotation(pattern);
|
|
377
|
-
if (type_annotation) lazy_id.typeAnnotation = type_annotation;
|
|
378
345
|
}
|
|
379
346
|
set_lazy_param_binding_mappings(lazy_id, pattern);
|
|
380
347
|
return lazy_id;
|
|
@@ -511,6 +478,53 @@ export function preallocate_lazy_ids(root, context) {
|
|
|
511
478
|
visit(root);
|
|
512
479
|
}
|
|
513
480
|
|
|
481
|
+
/**
|
|
482
|
+
* Convert an ESTree member-access chain (`__lazy0.Item`, `__lazy0.a.b`) into the
|
|
483
|
+
* equivalent JSX element-name node (a `JSXIdentifier` or `JSXMemberExpression`),
|
|
484
|
+
* so a lazy binding used as a component/element name (`<Item>`) can be rewritten
|
|
485
|
+
* to `<__lazy0.Item>`. Returns null for a computed access (`__lazy0[0]`, from an
|
|
486
|
+
* array destructure), which has no JSX-name form.
|
|
487
|
+
*
|
|
488
|
+
* @param {any} expr
|
|
489
|
+
* @param {any} [source]
|
|
490
|
+
* @returns {any | null}
|
|
491
|
+
*/
|
|
492
|
+
function estree_member_to_jsx_name(expr, source) {
|
|
493
|
+
if (expr.type === 'Identifier') {
|
|
494
|
+
return set_source_location(b.jsx_id(expr.name), source);
|
|
495
|
+
}
|
|
496
|
+
if (expr.type === 'MemberExpression' && !expr.computed && expr.property?.type === 'Identifier') {
|
|
497
|
+
const object = estree_member_to_jsx_name(expr.object, source);
|
|
498
|
+
if (!object) return null;
|
|
499
|
+
return set_source_location(b.jsx_member(object, b.jsx_id(expr.property.name)), source);
|
|
500
|
+
}
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* If a JSX element/component name references a lazy binding, return the rewritten
|
|
506
|
+
* JSX name (`<Item>` → `<__lazy0.Item>`, `<Item.Sub>` → `<__lazy0.Item.Sub>`).
|
|
507
|
+
* Returns null when the name does not reference a lazy binding (or the access has
|
|
508
|
+
* no JSX-name form), so the caller leaves the original name untouched.
|
|
509
|
+
*
|
|
510
|
+
* @param {any} name
|
|
511
|
+
* @param {Map<string, LazyBinding>} lazy_bindings
|
|
512
|
+
* @returns {any | null}
|
|
513
|
+
*/
|
|
514
|
+
function rewrite_lazy_jsx_name(name, lazy_bindings) {
|
|
515
|
+
if (!name) return null;
|
|
516
|
+
if (name.type === 'JSXIdentifier') {
|
|
517
|
+
const binding = lazy_bindings.get(name.name);
|
|
518
|
+
if (!binding) return null;
|
|
519
|
+
return estree_member_to_jsx_name(binding.read(name), name);
|
|
520
|
+
}
|
|
521
|
+
if (name.type === 'JSXMemberExpression') {
|
|
522
|
+
const new_object = rewrite_lazy_jsx_name(name.object, lazy_bindings);
|
|
523
|
+
return new_object ? { ...name, object: new_object } : null;
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
|
|
514
528
|
/**
|
|
515
529
|
* Recursively rewrite lazy-binding references in `node`.
|
|
516
530
|
*
|
|
@@ -669,16 +683,36 @@ export function apply_lazy_transforms(node, lazy_bindings) {
|
|
|
669
683
|
}
|
|
670
684
|
|
|
671
685
|
if (node.type === 'SwitchStatement') {
|
|
686
|
+
// All case consequents share one lexical block scope, and `@switch`
|
|
687
|
+
// case bodies are flattened (the `{ … }` wrapper is dropped), so a
|
|
688
|
+
// `let &[x] = …` / `let &{ … } = …` declared in a case body is scoped to
|
|
689
|
+
// the whole switch block. Collect names and lazy bindings across every
|
|
690
|
+
// consequent — like the BlockStatement handler does for a block body — so
|
|
691
|
+
// references in any case resolve to the generated id, and an inner
|
|
692
|
+
// declaration shadowing an outer lazy name is dropped.
|
|
693
|
+
const all_consequents = node.cases.flatMap(
|
|
694
|
+
(/** @type {any} */ switch_case) => switch_case.consequent,
|
|
695
|
+
);
|
|
696
|
+
const block_shadowed = collect_block_shadowed_names(all_consequents, lazy_bindings);
|
|
697
|
+
const after_shadow =
|
|
698
|
+
block_shadowed.size > 0 ? remove_shadowed(lazy_bindings, block_shadowed) : lazy_bindings;
|
|
699
|
+
|
|
700
|
+
/** @type {Map<string, LazyBinding>} */
|
|
701
|
+
const block_lazy = new Map();
|
|
702
|
+
collect_lazy_bindings_from_statements(all_consequents, block_lazy);
|
|
703
|
+
const effective_bindings =
|
|
704
|
+
block_lazy.size > 0 ? new Map([...after_shadow, ...block_lazy]) : after_shadow;
|
|
705
|
+
|
|
672
706
|
let changed = false;
|
|
673
|
-
|
|
707
|
+
// The discriminant is evaluated before any case body runs, so it sees the
|
|
708
|
+
// bindings visible at the switch (outer, minus inner shadows), not a lazy
|
|
709
|
+
// binding declared inside a case body.
|
|
710
|
+
const new_discriminant = apply_lazy_transforms(node.discriminant, after_shadow);
|
|
674
711
|
if (new_discriminant !== node.discriminant) changed = true;
|
|
675
712
|
const new_cases = node.cases.map((/** @type {any} */ switch_case) => {
|
|
676
|
-
const case_bindings = collect_block_shadowed_names(switch_case.consequent, lazy_bindings);
|
|
677
|
-
const effective_bindings =
|
|
678
|
-
case_bindings.size > 0 ? remove_shadowed(lazy_bindings, case_bindings) : lazy_bindings;
|
|
679
713
|
let case_changed = false;
|
|
680
714
|
const new_test = switch_case.test
|
|
681
|
-
? apply_lazy_transforms(switch_case.test,
|
|
715
|
+
? apply_lazy_transforms(switch_case.test, effective_bindings)
|
|
682
716
|
: null;
|
|
683
717
|
if (new_test !== switch_case.test) case_changed = true;
|
|
684
718
|
const new_consequent = switch_case.consequent.map((/** @type {any} */ stmt) => {
|
|
@@ -823,6 +857,21 @@ export function apply_lazy_transforms(node, lazy_bindings) {
|
|
|
823
857
|
// Skip VariableDeclarator id (already handled above).
|
|
824
858
|
if (key === 'id' && node.type === 'VariableDeclarator') continue;
|
|
825
859
|
|
|
860
|
+
// A JSX element/component name that resolves to a lazy binding becomes a
|
|
861
|
+
// JSX member expression (`<Item>` → `<__lazy0.Item>`): the bound name is no
|
|
862
|
+
// longer a local, it is a property of the synthesized lazy source.
|
|
863
|
+
if (
|
|
864
|
+
key === 'name' &&
|
|
865
|
+
(node.type === 'JSXOpeningElement' || node.type === 'JSXClosingElement')
|
|
866
|
+
) {
|
|
867
|
+
const jsx_name = rewrite_lazy_jsx_name(node[key], lazy_bindings);
|
|
868
|
+
if (jsx_name) {
|
|
869
|
+
clone[key] = jsx_name;
|
|
870
|
+
changed = true;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
826
875
|
const new_value = apply_lazy_transforms(node[key], lazy_bindings);
|
|
827
876
|
if (new_value !== node[key]) {
|
|
828
877
|
clone[key] = new_value;
|
package/src/utils/builders.js
CHANGED
|
@@ -368,6 +368,26 @@ export function literal(value, raw, loc_info) {
|
|
|
368
368
|
return set_location(node, loc_info);
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Ripple template `Text` node wrapping a string literal of `value`. Used by the
|
|
373
|
+
* Ripple normalizer when lowering a JSX text child to a template Text node.
|
|
374
|
+
*
|
|
375
|
+
* Remove once Ripple transformers move to the parser's JSX AST
|
|
376
|
+
*
|
|
377
|
+
* @param {string} value
|
|
378
|
+
* @param {AST.NodeWithLocation} [loc_info]
|
|
379
|
+
* @returns {AST.Text}
|
|
380
|
+
*/
|
|
381
|
+
export function text(value, loc_info) {
|
|
382
|
+
const node = /** @type {AST.Text} */ ({
|
|
383
|
+
type: 'Text',
|
|
384
|
+
expression: literal(value, JSON.stringify(value), loc_info),
|
|
385
|
+
metadata: { path: [] },
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
return set_location(node, loc_info);
|
|
389
|
+
}
|
|
390
|
+
|
|
371
391
|
/**
|
|
372
392
|
* @param {AST.Expression | AST.Super} object
|
|
373
393
|
* @param {string | AST.Expression | AST.PrivateIdentifier} property
|
package/types/index.d.ts
CHANGED
|
@@ -83,6 +83,7 @@ interface BaseNodeMetaData {
|
|
|
83
83
|
commentContainerId?: number;
|
|
84
84
|
parenthesized?: boolean;
|
|
85
85
|
native_tsrx?: boolean;
|
|
86
|
+
tsrx_generated_wrapper?: boolean;
|
|
86
87
|
native_tsrx_template_block?: boolean;
|
|
87
88
|
dynamicElement?: boolean;
|
|
88
89
|
templateMode?: 'script' | 'template';
|
|
@@ -137,6 +138,7 @@ type AcornTSNode<T> = Omit<T, 'parent' | 'loc' | 'range' | 'expression'> & {
|
|
|
137
138
|
|
|
138
139
|
leadingComments?: AST.Comment[] | undefined;
|
|
139
140
|
trailingComments?: AST.Comment[] | undefined;
|
|
141
|
+
append_into?: AST.Identifier;
|
|
140
142
|
};
|
|
141
143
|
|
|
142
144
|
interface FunctionLikeTS {
|
|
@@ -295,6 +297,8 @@ declare module 'estree' {
|
|
|
295
297
|
TSAsExpression: TSAsExpression;
|
|
296
298
|
}
|
|
297
299
|
|
|
300
|
+
type TraversableAstNode = AST.Node & Record<string, unknown>;
|
|
301
|
+
|
|
298
302
|
// Ripple-normalized template node shapes. The core parser emits JSX-shaped
|
|
299
303
|
// TSRX nodes; @tsrx/ripple creates these during its normalization pass.
|
|
300
304
|
interface Attribute extends AST.BaseNode {
|
|
@@ -516,6 +520,8 @@ declare module 'estree' {
|
|
|
516
520
|
metadata: BaseNodeMetaData;
|
|
517
521
|
|
|
518
522
|
comments?: Comment[];
|
|
523
|
+
|
|
524
|
+
append_into?: AST.Identifier;
|
|
519
525
|
}
|
|
520
526
|
|
|
521
527
|
interface NodeWithLocation {
|