@tsrx/core 0.1.4 → 0.1.7
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 +39 -6
- package/src/diagnostics.js +1 -0
- package/src/index.js +8 -2
- package/src/plugin.js +31 -1
- package/src/runtime/events.js +10 -0
- package/src/runtime/hash.js +1 -0
- package/src/runtime/html.js +3 -0
- package/src/runtime/iterable.js +110 -0
- package/src/source-map-utils.js +19 -2
- package/src/transform/jsx/ast-builders.js +120 -0
- package/src/transform/jsx/index.js +632 -1415
- package/src/transform/lazy.js +301 -205
- package/src/transform/scoping.js +9 -45
- package/src/transform/segments.js +164 -11
- package/src/utils/ast.js +13 -0
- package/src/utils/builders.js +51 -13
- package/src/utils/dom.js +158 -0
- package/src/utils.js +6 -159
- package/types/index.d.ts +1 -0
- package/types/jsx-platform.d.ts +12 -0
- package/types/runtime/events.d.ts +11 -0
- package/types/runtime/hash.d.ts +2 -0
- package/types/runtime/html.d.ts +4 -0
- package/types/runtime/iterable.d.ts +13 -0
- package/types/runtime/language-helpers.d.ts +17 -0
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.1.
|
|
6
|
+
"version": "0.1.7",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -31,9 +31,31 @@
|
|
|
31
31
|
"types": "./types/runtime/ref.d.ts",
|
|
32
32
|
"default": "./src/runtime/ref.js"
|
|
33
33
|
},
|
|
34
|
-
"./runtime
|
|
34
|
+
"./runtime/events": {
|
|
35
|
+
"types": "./types/runtime/events.d.ts",
|
|
36
|
+
"default": "./src/runtime/events.js"
|
|
37
|
+
},
|
|
38
|
+
"./runtime/hash": {
|
|
39
|
+
"types": "./types/runtime/hash.d.ts",
|
|
40
|
+
"default": "./src/runtime/hash.js"
|
|
41
|
+
},
|
|
42
|
+
"./runtime/html": {
|
|
43
|
+
"types": "./types/runtime/html.d.ts",
|
|
44
|
+
"default": "./src/runtime/html.js"
|
|
45
|
+
},
|
|
46
|
+
"./runtime/language-helpers": {
|
|
47
|
+
"types": "./types/runtime/language-helpers.d.ts",
|
|
48
|
+
"default": "./src/runtime/language-helpers.js"
|
|
49
|
+
},
|
|
50
|
+
"./runtime/iterable": {
|
|
51
|
+
"types": "./types/runtime/iterable.d.ts",
|
|
52
|
+
"default": "./src/runtime/iterable.js"
|
|
53
|
+
},
|
|
35
54
|
"./test-harness/source-mappings": "./tests/shared/source-mappings.js",
|
|
36
|
-
"./test-harness/compile": "./tests/shared/compile.js"
|
|
55
|
+
"./test-harness/compile": "./tests/shared/compile.js",
|
|
56
|
+
"./test-harness/runtime/*": "./tests/shared/runtime/*.js",
|
|
57
|
+
"./test-harness/runtime/*.js": "./tests/shared/runtime/*.js",
|
|
58
|
+
"./test-harness/runtime/*.tsrx": "./tests/shared/runtime/*.tsrx"
|
|
37
59
|
},
|
|
38
60
|
"dependencies": {
|
|
39
61
|
"@jridgewell/sourcemap-codec": "^1.5.5",
|
|
@@ -42,17 +64,28 @@
|
|
|
42
64
|
"@types/estree-jsx": "^1.0.5",
|
|
43
65
|
"@types/estree": "^1.0.8",
|
|
44
66
|
"acorn": "^8.15.0",
|
|
45
|
-
"esrap": "^2.
|
|
67
|
+
"esrap": "^2.2.7",
|
|
46
68
|
"is-reference": "^3.0.3",
|
|
47
69
|
"magic-string": "^0.30.18",
|
|
48
70
|
"zimmerframe": "^1.1.2"
|
|
49
71
|
},
|
|
50
72
|
"devDependencies": {
|
|
73
|
+
"@solidjs/web": "2.0.0-beta.7",
|
|
51
74
|
"@types/node": "^24.3.0",
|
|
52
75
|
"@typescript-eslint/types": "^8.40.0",
|
|
53
|
-
"typescript": "^5.9.3",
|
|
54
76
|
"@volar/language-core": "~2.4.28",
|
|
55
|
-
"
|
|
77
|
+
"preact": "^10.27.0",
|
|
78
|
+
"react": "^19.2.0",
|
|
79
|
+
"react-dom": "^19.2.0",
|
|
80
|
+
"solid-js": "2.0.0-beta.7",
|
|
81
|
+
"typescript": "^5.9.3",
|
|
82
|
+
"vscode-languageserver-types": "^3.17.5",
|
|
83
|
+
"vue": "3.6.0-beta.10",
|
|
84
|
+
"vue-jsx-vapor": "^3.2.12",
|
|
85
|
+
"@tsrx/preact": "0.1.7",
|
|
86
|
+
"@tsrx/react": "0.2.7",
|
|
87
|
+
"@tsrx/solid": "0.1.7",
|
|
88
|
+
"@tsrx/vue": "0.1.7"
|
|
56
89
|
},
|
|
57
90
|
"files": [
|
|
58
91
|
"src",
|
package/src/diagnostics.js
CHANGED
|
@@ -4,4 +4,5 @@ export const DIAGNOSTIC_CODES = {
|
|
|
4
4
|
FUNCTION_COMPONENT_SYNTAX: 'tsrx-function-component-syntax',
|
|
5
5
|
UNCLOSED_TAG: 'tsrx-unclosed-tag',
|
|
6
6
|
MISMATCHED_CLOSING_TAG: 'tsrx-mismatched-closing-tag',
|
|
7
|
+
TEMPLATE_EXPRESSION_TRAILING_SEMICOLON: 'tsrx-template-expression-trailing-semicolon',
|
|
7
8
|
};
|
package/src/index.js
CHANGED
|
@@ -88,6 +88,7 @@ export {
|
|
|
88
88
|
is_class_node as isClassNode,
|
|
89
89
|
is_component_node as isComponentNode,
|
|
90
90
|
is_function_node as isFunctionNode,
|
|
91
|
+
is_function_or_component_node as isFunctionOrComponentNode,
|
|
91
92
|
is_inside_component as isInsideComponent,
|
|
92
93
|
} from './utils/ast.js';
|
|
93
94
|
|
|
@@ -142,6 +143,9 @@ export { escape, escape_script as escapeScript } from './utils/escaping.js';
|
|
|
142
143
|
// Transform
|
|
143
144
|
export {
|
|
144
145
|
add_jsx_setup_declaration as addJsxSetupDeclaration,
|
|
146
|
+
clone_switch_helper_invocation as cloneSwitchHelperInvocation,
|
|
147
|
+
collect_param_bindings as collectParamBindings,
|
|
148
|
+
collect_statement_bindings as collectStatementBindings,
|
|
145
149
|
createJsxTransform,
|
|
146
150
|
CREATE_REF_PROP_INTERNAL_NAME,
|
|
147
151
|
extract_jsx_setup_declarations as extractJsxSetupDeclarations,
|
|
@@ -149,6 +153,7 @@ export {
|
|
|
149
153
|
MERGE_REFS_INTERNAL_NAME,
|
|
150
154
|
merge_duplicate_refs as mergeDuplicateRefs,
|
|
151
155
|
NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
|
|
156
|
+
plan_switch_lift as planSwitchLift,
|
|
152
157
|
return_value_body_to_expression as returnValueBodyToExpression,
|
|
153
158
|
rewrite_loop_continues_to_bare_returns as rewriteLoopContinuesToBareReturns,
|
|
154
159
|
to_jsx_attribute as toJsxAttribute,
|
|
@@ -165,13 +170,16 @@ export {
|
|
|
165
170
|
clone_expression_node,
|
|
166
171
|
clone_identifier,
|
|
167
172
|
clone_jsx_name,
|
|
173
|
+
contains_component_jsx,
|
|
168
174
|
create_compile_error,
|
|
169
175
|
create_generated_identifier,
|
|
170
176
|
create_null_literal,
|
|
177
|
+
expand_switch_cases_for_fallthrough,
|
|
171
178
|
flatten_switch_consequent,
|
|
172
179
|
get_for_of_iteration_params,
|
|
173
180
|
identifier_to_jsx_name,
|
|
174
181
|
is_bare_render_expression,
|
|
182
|
+
is_component_jsx_name,
|
|
175
183
|
is_dynamic_element_id,
|
|
176
184
|
is_jsx_child,
|
|
177
185
|
set_loc,
|
|
@@ -198,11 +206,9 @@ export {
|
|
|
198
206
|
export {
|
|
199
207
|
create_lazy_context as createLazyContext,
|
|
200
208
|
collect_lazy_bindings as collectLazyBindings,
|
|
201
|
-
collect_lazy_bindings_from_component as collectLazyBindingsFromComponent,
|
|
202
209
|
collect_lazy_bindings_from_statements as collectLazyBindingsFromStatements,
|
|
203
210
|
preallocate_lazy_ids as preallocateLazyIds,
|
|
204
211
|
apply_lazy_transforms as applyLazyTransforms,
|
|
205
|
-
replace_lazy_params as replaceLazyParams,
|
|
206
212
|
} from './transform/lazy.js';
|
|
207
213
|
export {
|
|
208
214
|
find_first_top_level_await as findFirstTopLevelAwait,
|
package/src/plugin.js
CHANGED
|
@@ -224,6 +224,7 @@ export function TSRXPlugin(config) {
|
|
|
224
224
|
#filename = null;
|
|
225
225
|
#componentDepth = 0;
|
|
226
226
|
#functionBodyDepth = 0;
|
|
227
|
+
#allowExpressionContainerTrailingSemicolon = false;
|
|
227
228
|
|
|
228
229
|
/**
|
|
229
230
|
* @type {Parse.Parser['finishNode']}
|
|
@@ -352,7 +353,14 @@ export function TSRXPlugin(config) {
|
|
|
352
353
|
}
|
|
353
354
|
|
|
354
355
|
#parseNativeTemplateExpressionContainer() {
|
|
355
|
-
const
|
|
356
|
+
const allow_trailing_semicolon = this.#allowExpressionContainerTrailingSemicolon;
|
|
357
|
+
this.#allowExpressionContainerTrailingSemicolon = true;
|
|
358
|
+
let node;
|
|
359
|
+
try {
|
|
360
|
+
node = this.jsx_parseExpressionContainer();
|
|
361
|
+
} finally {
|
|
362
|
+
this.#allowExpressionContainerTrailingSemicolon = allow_trailing_semicolon;
|
|
363
|
+
}
|
|
356
364
|
// Keep JSXEmptyExpression as-is (for prettier to handle comments)
|
|
357
365
|
// but convert other expressions to native TSRX child nodes.
|
|
358
366
|
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
@@ -644,6 +652,18 @@ export function TSRXPlugin(config) {
|
|
|
644
652
|
ctx.length = ci - 1;
|
|
645
653
|
return;
|
|
646
654
|
}
|
|
655
|
+
// Statement-bodied `<tsrx>` attributes can leave the attribute's
|
|
656
|
+
// expression contexts above the still-open JSX tag context. Strip
|
|
657
|
+
// those so a following `/>` stays in JSX opening-tag mode.
|
|
658
|
+
if (
|
|
659
|
+
this.type === tt.braceR &&
|
|
660
|
+
top === tstc.tc_expr &&
|
|
661
|
+
second === b_expr &&
|
|
662
|
+
ctx[ci - 2] === tstc.tc_oTag
|
|
663
|
+
) {
|
|
664
|
+
ctx.length = ci - 1;
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
647
667
|
// Closing token after the template at expression position. For `}`
|
|
648
668
|
// only pop if it actually closes this `b_expr` — otherwise the
|
|
649
669
|
// brace targets an inner callback/object body that should pop it
|
|
@@ -1771,6 +1791,16 @@ export function TSRXPlugin(config) {
|
|
|
1771
1791
|
'"style" is a TSRX keyword and must be used in the form {style "class_name"}',
|
|
1772
1792
|
);
|
|
1773
1793
|
}
|
|
1794
|
+
if (this.#allowExpressionContainerTrailingSemicolon && this.type === tt.semi) {
|
|
1795
|
+
if (this.#collect) {
|
|
1796
|
+
this.#report_recoverable_error(
|
|
1797
|
+
this.start,
|
|
1798
|
+
'TSRX expression containers do not use semicolons. Remove this semicolon.',
|
|
1799
|
+
DIAGNOSTIC_CODES.TEMPLATE_EXPRESSION_TRAILING_SEMICOLON,
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
this.next();
|
|
1803
|
+
}
|
|
1774
1804
|
this.expect(tt.braceR);
|
|
1775
1805
|
|
|
1776
1806
|
return this.finishNode(node, 'JSXExpressionContainer');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { simple_hash, strong_hash } from '../utils/hashing.js';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template T
|
|
3
|
+
* @template U
|
|
4
|
+
* @param {Iterable<T> | Iterator<T>} iterable
|
|
5
|
+
* @param {(item: T, index: number, is_last: boolean) => U} fn
|
|
6
|
+
* @param {() => U | U[]} [tail]
|
|
7
|
+
* @returns {U[]}
|
|
8
|
+
*/
|
|
9
|
+
export function map_iterable(iterable, fn, tail) {
|
|
10
|
+
if (Array.isArray(iterable)) {
|
|
11
|
+
return map_array(iterable, fn, tail);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** @type {Iterator<T>} */
|
|
15
|
+
var iterator;
|
|
16
|
+
var iterable_prop = /** @type {Iterable<T>} */ (iterable)[Symbol.iterator];
|
|
17
|
+
|
|
18
|
+
if (typeof iterable_prop === 'function') {
|
|
19
|
+
iterator = iterable_prop.call(iterable);
|
|
20
|
+
} else if (typeof (/** @type {Iterator<T>} */ (iterable).next) === 'function') {
|
|
21
|
+
iterator = Iterator.from(iterable);
|
|
22
|
+
} else {
|
|
23
|
+
throw new TypeError('The loop target has to be an Iterable');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var current = iterator.next();
|
|
27
|
+
if (current.done) {
|
|
28
|
+
if (!tail) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
var tail_value = tail();
|
|
32
|
+
if (Array.isArray(tail_value)) {
|
|
33
|
+
return tail_value;
|
|
34
|
+
}
|
|
35
|
+
return [tail_value];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var index = 0;
|
|
39
|
+
var result = [];
|
|
40
|
+
while (true) {
|
|
41
|
+
var next = iterator.next();
|
|
42
|
+
var value = fn(current.value, index++, !!next.done);
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
for (var j = 0; j < value.length; j++) {
|
|
45
|
+
result.push(value[j]);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
result.push(value);
|
|
49
|
+
}
|
|
50
|
+
if (next.done) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
current = next;
|
|
54
|
+
}
|
|
55
|
+
if (tail) {
|
|
56
|
+
var tail_value = tail();
|
|
57
|
+
if (Array.isArray(tail_value)) {
|
|
58
|
+
for (var j = 0; j < tail_value.length; j++) {
|
|
59
|
+
result.push(tail_value[j]);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
result.push(tail_value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @template T
|
|
70
|
+
* @template U
|
|
71
|
+
* @param {Array<T>} array
|
|
72
|
+
* @param {(item: T, index: number, is_last: boolean) => U} fn
|
|
73
|
+
* @param {() => U | U[]} [tail]
|
|
74
|
+
* @returns {U[]}
|
|
75
|
+
*/
|
|
76
|
+
function map_array(array, fn, tail) {
|
|
77
|
+
var length = array.length;
|
|
78
|
+
if (length === 0) {
|
|
79
|
+
if (!tail) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
var tail_value = tail();
|
|
83
|
+
if (Array.isArray(tail_value)) {
|
|
84
|
+
return tail_value;
|
|
85
|
+
}
|
|
86
|
+
return [tail_value];
|
|
87
|
+
}
|
|
88
|
+
var result = [];
|
|
89
|
+
for (var i = 0; i < length; i++) {
|
|
90
|
+
var value = fn(array[i], i, i === length - 1);
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
for (var j = 0; j < value.length; j++) {
|
|
93
|
+
result.push(value[j]);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
result.push(value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (tail) {
|
|
100
|
+
var tail_value = tail();
|
|
101
|
+
if (Array.isArray(tail_value)) {
|
|
102
|
+
for (var j = 0; j < tail_value.length; j++) {
|
|
103
|
+
result.push(tail_value[j]);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
result.push(tail_value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
package/src/source-map-utils.js
CHANGED
|
@@ -17,10 +17,15 @@
|
|
|
17
17
|
* css?: AST.Element['metadata']['css']
|
|
18
18
|
* },
|
|
19
19
|
* }} CodePosition
|
|
20
|
+
* @typedef {{
|
|
21
|
+
* column: number,
|
|
22
|
+
* position: CodePosition,
|
|
23
|
+
* }} SourceLineGeneratedPosition
|
|
20
24
|
*/
|
|
21
25
|
|
|
22
26
|
/** @typedef {Map<string, CodePosition[]>} CodeToGeneratedMap */
|
|
23
27
|
/** @typedef {Map<string, {line: number, column: number}[]>} GeneratedToSourceMap */
|
|
28
|
+
/** @typedef {Map<number, SourceLineGeneratedPosition[]>} SourceLineGeneratedMap */
|
|
24
29
|
|
|
25
30
|
import { decode } from '@jridgewell/sourcemap-codec';
|
|
26
31
|
|
|
@@ -83,18 +88,22 @@ export const offset_to_line_col = (offset, line_offsets) => {
|
|
|
83
88
|
* @param {PostProcessingChanges} post_processing_changes - Optional post-processing changes to apply
|
|
84
89
|
* @param {LineOffsets} line_offsets - Pre-computed line offsets array
|
|
85
90
|
* @param {string} generated_code - The final generated code (after post-processing)
|
|
86
|
-
* @
|
|
91
|
+
* @param {boolean} [include_source_line_generated_map] - Whether to build the optional source-line predecessor lookup
|
|
92
|
+
* @returns {[CodeToGeneratedMap, GeneratedToSourceMap, SourceLineGeneratedMap | null]} Tuple of [source-to-generated map, generated-to-source map, source-line generated map]
|
|
87
93
|
*/
|
|
88
94
|
export function build_src_to_gen_map(
|
|
89
95
|
source_map,
|
|
90
96
|
post_processing_changes,
|
|
91
97
|
line_offsets,
|
|
92
98
|
generated_code,
|
|
99
|
+
include_source_line_generated_map = false,
|
|
93
100
|
) {
|
|
94
101
|
/** @type {CodeToGeneratedMap} */
|
|
95
102
|
const map = new Map();
|
|
96
103
|
/** @type {GeneratedToSourceMap} */
|
|
97
104
|
const reverse_map = new Map();
|
|
105
|
+
/** @type {SourceLineGeneratedMap | null} */
|
|
106
|
+
const source_line_generated_map = include_source_line_generated_map ? new Map() : null;
|
|
98
107
|
|
|
99
108
|
// Decode the VLQ-encoded mappings string
|
|
100
109
|
const decoded = decode(source_map.mappings);
|
|
@@ -192,6 +201,14 @@ export function build_src_to_gen_map(
|
|
|
192
201
|
map.set(key, []);
|
|
193
202
|
}
|
|
194
203
|
/** @type {CodePosition[]} */ (map.get(key)).push(gen_pos);
|
|
204
|
+
if (source_line_generated_map) {
|
|
205
|
+
if (!source_line_generated_map.has(segment.sourceLine)) {
|
|
206
|
+
source_line_generated_map.set(segment.sourceLine, []);
|
|
207
|
+
}
|
|
208
|
+
/** @type {SourceLineGeneratedPosition[]} */ (
|
|
209
|
+
source_line_generated_map.get(segment.sourceLine)
|
|
210
|
+
).push({ column: segment.sourceColumn, position: gen_pos });
|
|
211
|
+
}
|
|
195
212
|
|
|
196
213
|
// Store reverse mapping (generated to source)
|
|
197
214
|
const gen_key = `${gen_pos.line}:${gen_pos.column}`;
|
|
@@ -206,7 +223,7 @@ export function build_src_to_gen_map(
|
|
|
206
223
|
}
|
|
207
224
|
}
|
|
208
225
|
|
|
209
|
-
return [map, reverse_map];
|
|
226
|
+
return [map, reverse_map, source_line_generated_map];
|
|
210
227
|
}
|
|
211
228
|
|
|
212
229
|
/**
|
|
@@ -162,6 +162,74 @@ export function identifier_to_jsx_name(id) {
|
|
|
162
162
|
return id;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* A JSX tag name refers to a *component* (rather than a host/DOM tag) iff:
|
|
167
|
+
* - it's a `JSXIdentifier` whose first character is uppercase (the convention
|
|
168
|
+
* every framework's JSX runtime keys off — `<div>` is a host element,
|
|
169
|
+
* `<Foo>` is a component), or
|
|
170
|
+
* - it's a `JSXMemberExpression` (e.g. `<Icons.Button />`).
|
|
171
|
+
*
|
|
172
|
+
* Used by platforms that veto static-hoisting of component JSX (Vue, Solid)
|
|
173
|
+
* and by core's narrower bare-component-invocation predicate.
|
|
174
|
+
*
|
|
175
|
+
* @param {any} name
|
|
176
|
+
* @returns {boolean}
|
|
177
|
+
*/
|
|
178
|
+
export function is_component_jsx_name(name) {
|
|
179
|
+
if (!name || typeof name !== 'object') {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (name.type === 'JSXIdentifier') {
|
|
184
|
+
const first = name.name?.[0];
|
|
185
|
+
return first != null && first >= 'A' && first <= 'Z';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (name.type === 'JSXMemberExpression') {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Does this JSX subtree contain any component-shaped element (anywhere —
|
|
197
|
+
* including nested under host elements or inside expression containers)?
|
|
198
|
+
* Vue and Solid use this as their `canHoistStaticNode` predicate: hoisting a
|
|
199
|
+
* subtree that invokes a component into a module-level constant pins that
|
|
200
|
+
* component instance to module identity, which doesn't help either framework
|
|
201
|
+
* the way it helps React, so it's wasted output.
|
|
202
|
+
*
|
|
203
|
+
* @param {any} node
|
|
204
|
+
* @returns {boolean}
|
|
205
|
+
*/
|
|
206
|
+
export function contains_component_jsx(node) {
|
|
207
|
+
if (!node || typeof node !== 'object') {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (node.type === 'JSXElement') {
|
|
212
|
+
if (is_component_jsx_name(node.openingElement?.name)) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return node.children?.some(contains_component_jsx) ?? false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (node.type === 'JSXFragment') {
|
|
219
|
+
return node.children?.some(contains_component_jsx) ?? false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (node.type === 'JSXExpressionContainer') {
|
|
223
|
+
return contains_component_jsx(node.expression);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (Array.isArray(node)) {
|
|
227
|
+
return node.some(contains_component_jsx);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
165
233
|
/**
|
|
166
234
|
* @param {any} node
|
|
167
235
|
* @returns {boolean}
|
|
@@ -302,6 +370,58 @@ export function flatten_switch_consequent(consequent) {
|
|
|
302
370
|
return result;
|
|
303
371
|
}
|
|
304
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Compute fall-through expansions for each `case` in a `switch`. JavaScript
|
|
375
|
+
* `switch` semantics say that once a case body executes, execution continues
|
|
376
|
+
* into the bodies of subsequent cases until a `break` or terminal `return` is
|
|
377
|
+
* hit. We pre-compute, per case, the flat list of statements that should run
|
|
378
|
+
* when that case is the entry point — so downstream targets (which render each
|
|
379
|
+
* case independently rather than executing fall-through at runtime) still
|
|
380
|
+
* produce the right output.
|
|
381
|
+
*
|
|
382
|
+
* Walking right-to-left lets each case reuse the next case's already-expanded
|
|
383
|
+
* tail without recomputation. Downstream nodes are deep-cloned when absorbed
|
|
384
|
+
* so each case's expanded body owns its own AST subtree.
|
|
385
|
+
*
|
|
386
|
+
* @param {any[]} cases
|
|
387
|
+
* @returns {Array<{ test: any, body: any[], source: any }>}
|
|
388
|
+
*/
|
|
389
|
+
export function expand_switch_cases_for_fallthrough(cases) {
|
|
390
|
+
/** @type {Array<{ test: any, body: any[], source: any }>} */
|
|
391
|
+
const expanded = new Array(cases.length);
|
|
392
|
+
for (let i = cases.length - 1; i >= 0; i--) {
|
|
393
|
+
const consequent = flatten_switch_consequent(cases[i].consequent || []);
|
|
394
|
+
const body = [];
|
|
395
|
+
let has_terminal = false;
|
|
396
|
+
for (const child of consequent) {
|
|
397
|
+
if (child.type === 'BreakStatement') {
|
|
398
|
+
has_terminal = true;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
body.push(child);
|
|
402
|
+
if (child.type === 'ReturnStatement') {
|
|
403
|
+
has_terminal = true;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Strip locations from cloned downstream nodes. Only the original case
|
|
408
|
+
// (one entry up the chain) keeps `loc`/`start`/`end`; clones inlined
|
|
409
|
+
// into upstream cases would otherwise point editor IntelliSense at the
|
|
410
|
+
// same source range multiple times (one hover/go-to-definition per
|
|
411
|
+
// fall-through entry point), producing double/triple results in Volar.
|
|
412
|
+
const downstream =
|
|
413
|
+
!has_terminal && i + 1 < cases.length
|
|
414
|
+
? expanded[i + 1].body.map((n) => clone_expression_node(n, false))
|
|
415
|
+
: [];
|
|
416
|
+
expanded[i] = {
|
|
417
|
+
test: cases[i].test,
|
|
418
|
+
body: [...body, ...downstream],
|
|
419
|
+
source: cases[i],
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
return expanded;
|
|
423
|
+
}
|
|
424
|
+
|
|
305
425
|
/**
|
|
306
426
|
* @param {AST.Expression | null | undefined} expression
|
|
307
427
|
* @returns {boolean}
|