@tsrx/core 0.1.58 → 0.1.60
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 +4 -2
- package/src/analyze/prune.js +7 -1
- package/src/comment-utils.js +39 -4
- package/src/runtime/iterable.js +1 -112
- package/src/runtime/language-helpers.js +1 -139
- package/src/runtime/ref.js +1 -319
- package/src/transform/jsx/helpers.js +98 -13
- package/src/transform/jsx/index.js +89 -19
- package/src/transform/segments.js +131 -17
- package/types/index.d.ts +19 -0
- package/types/jsx-platform.d.ts +19 -1
- package/types/runtime/iterable.d.ts +1 -13
- package/types/runtime/language-helpers.d.ts +1 -30
- package/types/runtime/ref.d.ts +1 -88
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
/** @import * as ESRap from 'esrap' */
|
|
3
3
|
|
|
4
4
|
import tsx from 'esrap/languages/tsx';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
should_preserve_comment,
|
|
7
|
+
should_preserve_jsx_tooling_comment,
|
|
8
|
+
is_file_level_pragma,
|
|
9
|
+
format_comment,
|
|
10
|
+
} from '../../comment-utils.js';
|
|
11
|
+
import { has_location } from '../../utils/ast.js';
|
|
6
12
|
import { with_deferred_imports } from '../imports.js';
|
|
7
13
|
|
|
8
14
|
/**
|
|
@@ -69,25 +75,64 @@ export function set_node_path_metadata(node, path) {
|
|
|
69
75
|
* (structural tokens carry one-character source locations). typeOnly/volar
|
|
70
76
|
* prints opt in — their maps are consumed positionally by the language
|
|
71
77
|
* tooling and never shipped; build prints stay sparse.
|
|
72
|
-
* @param {AST.CommentWithLocation[]} [comments] Source comments
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
78
|
+
* @param {AST.CommentWithLocation[]} [comments] Source comments. In type-only
|
|
79
|
+
* output, file-wide pragmas lead the program while documentation and scoped
|
|
80
|
+
* annotations stay with their declarations, members, or statements. A sparse
|
|
81
|
+
* print with explicitly supplied comments retains its existing leading-pragma
|
|
82
|
+
* behavior. Ordinary build callers supply no comments.
|
|
77
83
|
*/
|
|
78
84
|
export function tsx_with_ts_locations(boundary_tokens = false, comments = undefined) {
|
|
79
85
|
const base = with_deferred_imports(tsx({ boundaryTokens: boundary_tokens }));
|
|
80
86
|
const { _: base_visitor, ...base_visitors } = base;
|
|
87
|
+
const preserve_comments = comments !== undefined;
|
|
88
|
+
const preserve_owner_comments = boundary_tokens && preserve_comments;
|
|
89
|
+
/** @type {Set<string> | null} */
|
|
90
|
+
const emitted_comments = preserve_comments ? new Set() : null;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {AST.CommentWithLocation} comment
|
|
94
|
+
* @param {ESRap.Context} context
|
|
95
|
+
*/
|
|
96
|
+
const write_preserved_comment = (comment, context) => {
|
|
97
|
+
if (
|
|
98
|
+
!emitted_comments ||
|
|
99
|
+
!(preserve_owner_comments
|
|
100
|
+
? should_preserve_jsx_tooling_comment(comment)
|
|
101
|
+
: should_preserve_comment(comment))
|
|
102
|
+
) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const key = `${comment.start}:${comment.end}:${comment.type}:${comment.value}`;
|
|
106
|
+
if (emitted_comments.has(key)) return;
|
|
107
|
+
emitted_comments.add(key);
|
|
108
|
+
if (comment.loc) context.location(comment.loc.start.line, comment.loc.start.column);
|
|
109
|
+
context.write(format_comment(comment));
|
|
110
|
+
if (comment.loc) context.location(comment.loc.end.line, comment.loc.end.column);
|
|
111
|
+
context.newline();
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {AST.Node} node
|
|
116
|
+
* @param {ESRap.Context} context
|
|
117
|
+
*/
|
|
118
|
+
const write_leading_comments = (node, context) => {
|
|
119
|
+
if (!node.leadingComments || !is_comment_owner(node)) return;
|
|
120
|
+
for (const comment of node.leadingComments) {
|
|
121
|
+
if (has_location(comment)) write_preserved_comment(comment, context);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
81
124
|
|
|
82
125
|
const leading_preserved = (/** @type {AST.Program} */ program) => {
|
|
83
|
-
if (!comments?.length) return [];
|
|
126
|
+
if (!preserve_comments || !comments?.length) return [];
|
|
84
127
|
// Injected statements (dynamic-import/try-import prepends) carry no
|
|
85
128
|
// loc; anchor "leading" on the first statement that maps to source,
|
|
86
129
|
// else every preserved comment in the file would hoist to the top.
|
|
87
130
|
const first = program.body.find((node) => node.loc);
|
|
88
131
|
return comments.filter(
|
|
89
132
|
(comment) =>
|
|
90
|
-
|
|
133
|
+
(preserve_owner_comments
|
|
134
|
+
? is_file_level_pragma(comment)
|
|
135
|
+
: should_preserve_comment(comment)) &&
|
|
91
136
|
(first?.loc == null ||
|
|
92
137
|
(comment.loc &&
|
|
93
138
|
(comment.loc.end.line < first.loc.start.line ||
|
|
@@ -100,10 +145,7 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
|
|
|
100
145
|
const wrappers = {
|
|
101
146
|
Program: (node, context) => {
|
|
102
147
|
for (const comment of leading_preserved(node)) {
|
|
103
|
-
|
|
104
|
-
context.write(format_comment(comment));
|
|
105
|
-
if (comment.loc) context.location(comment.loc.end.line, comment.loc.end.column);
|
|
106
|
-
context.newline();
|
|
148
|
+
write_preserved_comment(comment, context);
|
|
107
149
|
}
|
|
108
150
|
/** @type {NonNullable<typeof base.Program>} */ (base.Program)(node, context);
|
|
109
151
|
},
|
|
@@ -206,8 +248,13 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
|
|
|
206
248
|
context.visit(node.body);
|
|
207
249
|
},
|
|
208
250
|
_(node, context, visit) {
|
|
251
|
+
if (preserve_owner_comments) write_leading_comments(node, context);
|
|
209
252
|
const visit_with_locations = () => {
|
|
210
|
-
if (
|
|
253
|
+
if (
|
|
254
|
+
!node.loc ||
|
|
255
|
+
(!LOCATION_WRAPPED_NODE_TYPES.has(node.type) &&
|
|
256
|
+
!(boundary_tokens && TOOLING_LOCATION_WRAPPED_NODE_TYPES.has(node.type)))
|
|
257
|
+
) {
|
|
211
258
|
visit(node);
|
|
212
259
|
return;
|
|
213
260
|
}
|
|
@@ -226,6 +273,44 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
|
|
|
226
273
|
return { ...base_visitors, ...wrappers };
|
|
227
274
|
}
|
|
228
275
|
|
|
276
|
+
/**
|
|
277
|
+
* A newline is safe before a statement/declaration or a member, but not before
|
|
278
|
+
* an arbitrary expression: `return /** @type {number} *\/ 1` must not become a
|
|
279
|
+
* bare return, and `throw` forbids a line terminator before its argument.
|
|
280
|
+
* @param {AST.Node} node
|
|
281
|
+
* @returns {boolean}
|
|
282
|
+
*/
|
|
283
|
+
function is_comment_owner(node) {
|
|
284
|
+
return (
|
|
285
|
+
node.type.endsWith('Declaration') ||
|
|
286
|
+
node.type.endsWith('Statement') ||
|
|
287
|
+
COMMENT_OWNER_NODE_TYPES.has(node.type)
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const COMMENT_OWNER_NODE_TYPES = new Set([
|
|
292
|
+
'VariableDeclarator',
|
|
293
|
+
'Property',
|
|
294
|
+
'PropertyDefinition',
|
|
295
|
+
'AccessorProperty',
|
|
296
|
+
'MethodDefinition',
|
|
297
|
+
'TSAbstractPropertyDefinition',
|
|
298
|
+
'TSAbstractAccessorProperty',
|
|
299
|
+
'TSAbstractMethodDefinition',
|
|
300
|
+
'TSPropertySignature',
|
|
301
|
+
'TSMethodSignature',
|
|
302
|
+
'TSIndexSignature',
|
|
303
|
+
'TSEnumMember',
|
|
304
|
+
'TSExportAssignment',
|
|
305
|
+
]);
|
|
306
|
+
|
|
307
|
+
const TOOLING_LOCATION_WRAPPED_NODE_TYPES = new Set([
|
|
308
|
+
'ExportNamedDeclaration',
|
|
309
|
+
'ExportDefaultDeclaration',
|
|
310
|
+
'ExportAllDeclaration',
|
|
311
|
+
'TSPropertySignature',
|
|
312
|
+
]);
|
|
313
|
+
|
|
229
314
|
// Be careful when adding visitors that are already defined in `wrappers`.
|
|
230
315
|
// JSXOpeningElement is intentionally in both places: its custom printer still
|
|
231
316
|
// needs a location marker around the whole node.
|
|
@@ -561,7 +561,17 @@ export function createJsxTransform(platform) {
|
|
|
561
561
|
* @returns {JsxTransformResult}
|
|
562
562
|
*/
|
|
563
563
|
function transform(ast, source, filename, options) {
|
|
564
|
-
const
|
|
564
|
+
const effective_platform =
|
|
565
|
+
options?.runtimeImports === 'direct' && platform.directRuntimeImports
|
|
566
|
+
? {
|
|
567
|
+
...platform,
|
|
568
|
+
imports: {
|
|
569
|
+
...platform.imports,
|
|
570
|
+
...platform.directRuntimeImports,
|
|
571
|
+
},
|
|
572
|
+
}
|
|
573
|
+
: platform;
|
|
574
|
+
const suspense_source = options?.suspenseSource ?? effective_platform.imports.suspense;
|
|
565
575
|
const collect = !!(options?.collect || options?.loose);
|
|
566
576
|
/** @type {AST.CSS.StyleSheet[]} */
|
|
567
577
|
const stylesheets = [];
|
|
@@ -570,7 +580,7 @@ export function createJsxTransform(platform) {
|
|
|
570
580
|
|
|
571
581
|
/** @type {TransformContext} */
|
|
572
582
|
const transform_context = {
|
|
573
|
-
platform,
|
|
583
|
+
platform: effective_platform,
|
|
574
584
|
local_statement_component_index: 0,
|
|
575
585
|
needs_error_boundary: false,
|
|
576
586
|
needs_suspense: false,
|
|
@@ -718,9 +728,24 @@ export function createJsxTransform(platform) {
|
|
|
718
728
|
// hooks can inspect the original JSX child shape.
|
|
719
729
|
const raw_children = node_children(node).map((child) => ({ ...child }));
|
|
720
730
|
const inner = /** @type {AST.TSRXJSXElement} */ (next() ?? node);
|
|
731
|
+
const in_jsx_child = in_jsx_child_context(path);
|
|
721
732
|
const hook = platform.hooks?.transformElement;
|
|
722
|
-
|
|
723
|
-
|
|
733
|
+
const produced = hook
|
|
734
|
+
? hook(inner, state, raw_children)
|
|
735
|
+
: to_jsx_element(inner, state, raw_children, in_jsx_child);
|
|
736
|
+
// A host element carrying `ref` plus a spread lowers to a generated
|
|
737
|
+
// `let X = __normalize_spread_props_for_ref_attr(…)` that rides on the
|
|
738
|
+
// element's metadata for a later pass to hoist. Only the render-block
|
|
739
|
+
// statement builder and the native-directive path hoist it, so an
|
|
740
|
+
// element in plain-JS expression position — a ternary arm, a concise
|
|
741
|
+
// arrow body, a declarator init, a callback body, an attribute value, an
|
|
742
|
+
// array element — reaches neither: the declaration is dropped while the
|
|
743
|
+
// rewritten attributes still reference the name, and the type-only print
|
|
744
|
+
// carries an undefined identifier (TS2304). Wrap it in the same IIFE the
|
|
745
|
+
// native-directive path already uses.
|
|
746
|
+
return state.typeOnly && produced.type !== 'JSXSpreadChild' && produced.type !== 'JSXText'
|
|
747
|
+
? wrap_jsx_setup_declarations(produced, in_jsx_child)
|
|
748
|
+
: produced;
|
|
724
749
|
},
|
|
725
750
|
|
|
726
751
|
JSXExpressionContainer(node, { next, state }) {
|
|
@@ -781,7 +806,7 @@ export function createJsxTransform(platform) {
|
|
|
781
806
|
return visited;
|
|
782
807
|
}
|
|
783
808
|
const is_component = is_component_like_jsx_name(visited.name);
|
|
784
|
-
|
|
809
|
+
const lowered = b.jsx_opening_element(
|
|
785
810
|
visited.name,
|
|
786
811
|
merge_duplicate_refs(
|
|
787
812
|
normalize_host_ref_spreads(visited.attributes || [], !is_component, transform_context),
|
|
@@ -791,6 +816,17 @@ export function createJsxTransform(platform) {
|
|
|
791
816
|
visited.typeArguments,
|
|
792
817
|
has_location(visited) ? visited : undefined,
|
|
793
818
|
);
|
|
819
|
+
// `normalize_host_ref_spreads` is NOT idempotent: run twice it reads the
|
|
820
|
+
// `ref={[authored, __spread_props1.ref]}` array it just produced as an
|
|
821
|
+
// AUTHORED ref and lowers again, emitting a second helper binding and a
|
|
822
|
+
// nested ref array. The attribute list cannot answer "already lowered"
|
|
823
|
+
// on its own — `merge_duplicate_refs` rebuilds the merged `ref` without
|
|
824
|
+
// the `synthetic_ref` marker — so record it on the element instead.
|
|
825
|
+
lowered.metadata = {
|
|
826
|
+
...(lowered.metadata || {}),
|
|
827
|
+
host_ref_spread_lowered: true,
|
|
828
|
+
};
|
|
829
|
+
return lowered;
|
|
794
830
|
},
|
|
795
831
|
});
|
|
796
832
|
|
|
@@ -811,7 +847,7 @@ export function createJsxTransform(platform) {
|
|
|
811
847
|
if (platform.hooks?.injectImports) {
|
|
812
848
|
platform.hooks.injectImports(expanded, transform_context, suspense_source);
|
|
813
849
|
} else {
|
|
814
|
-
inject_try_imports(expanded, transform_context,
|
|
850
|
+
inject_try_imports(expanded, transform_context, effective_platform, suspense_source);
|
|
815
851
|
}
|
|
816
852
|
|
|
817
853
|
// Lower any `@{ … }` code blocks left in generated helper bodies before the
|
|
@@ -1055,6 +1091,7 @@ function inject_dynamic_import(program, transform_context) {
|
|
|
1055
1091
|
* @param {AST.CSS.StyleSheet} css
|
|
1056
1092
|
* @param {TransformContext} transform_context
|
|
1057
1093
|
* @param {boolean} [export_top_scoped_classes]
|
|
1094
|
+
* @param {string} [region_hash]
|
|
1058
1095
|
* @returns {void}
|
|
1059
1096
|
*/
|
|
1060
1097
|
function apply_css_definition_metadata(
|
|
@@ -1062,6 +1099,7 @@ function apply_css_definition_metadata(
|
|
|
1062
1099
|
css,
|
|
1063
1100
|
transform_context,
|
|
1064
1101
|
export_top_scoped_classes = false,
|
|
1102
|
+
region_hash = css.hash,
|
|
1065
1103
|
) {
|
|
1066
1104
|
analyze_css(css);
|
|
1067
1105
|
|
|
@@ -1072,7 +1110,7 @@ function apply_css_definition_metadata(
|
|
|
1072
1110
|
|
|
1073
1111
|
const prune = () => {
|
|
1074
1112
|
for (const element of elements) {
|
|
1075
|
-
prune_css(css, element, style_classes, top_scoped_classes);
|
|
1113
|
+
prune_css(css, element, style_classes, top_scoped_classes, region_hash);
|
|
1076
1114
|
}
|
|
1077
1115
|
};
|
|
1078
1116
|
|
|
@@ -2281,19 +2319,20 @@ function node_contains_native_tsrx_template(node) {
|
|
|
2281
2319
|
|
|
2282
2320
|
/**
|
|
2283
2321
|
* @param {AST.NativeTSRXNode} node
|
|
2284
|
-
* @
|
|
2322
|
+
* @param {boolean} allow_multiple
|
|
2323
|
+
* @returns {AST.CSS.StyleSheet[] | null}
|
|
2285
2324
|
*/
|
|
2286
|
-
function
|
|
2325
|
+
function collect_tsrx_stylesheets(node, allow_multiple) {
|
|
2287
2326
|
/** @type {AST.CSS.StyleSheet[]} */
|
|
2288
2327
|
const styles = [];
|
|
2289
2328
|
collect_style_elements(node_children(node), styles);
|
|
2290
2329
|
|
|
2291
2330
|
if (styles.length === 0) return null;
|
|
2292
|
-
if (styles.length > 1) {
|
|
2331
|
+
if (styles.length > 1 && !allow_multiple) {
|
|
2293
2332
|
throw new Error('TSRX fragments can only have one style tag');
|
|
2294
2333
|
}
|
|
2295
2334
|
|
|
2296
|
-
return styles
|
|
2335
|
+
return styles;
|
|
2297
2336
|
}
|
|
2298
2337
|
|
|
2299
2338
|
/**
|
|
@@ -2302,14 +2341,37 @@ function collect_tsrx_stylesheet(node) {
|
|
|
2302
2341
|
* @returns {JsxStyleContext | null}
|
|
2303
2342
|
*/
|
|
2304
2343
|
function prepare_tsrx_fragment_styles(node, transform_context) {
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2344
|
+
// Type-only output must stay analyzable: a throw here makes language hosts
|
|
2345
|
+
// (tsrx-tsc, editors) fall back to presenting the RAW source as the virtual
|
|
2346
|
+
// TSX, so every CSS brace in the file becomes a TSX parse error. Platforms
|
|
2347
|
+
// whose runtime dialect allows one scope to split its CSS across several
|
|
2348
|
+
// `<style>` tags are valid input here, and platforms that forbid it still
|
|
2349
|
+
// report the rule through their own runtime compiler.
|
|
2350
|
+
const sheets = collect_tsrx_stylesheets(node, transform_context.typeOnly);
|
|
2351
|
+
if (!sheets) return null;
|
|
2352
|
+
|
|
2353
|
+
const css = sheets[0];
|
|
2308
2354
|
const style_refs = collect_style_ref_attributes(node);
|
|
2309
|
-
//
|
|
2310
|
-
//
|
|
2311
|
-
|
|
2312
|
-
|
|
2355
|
+
// A component scope keeps ONE hash even when its CSS is split across
|
|
2356
|
+
// several `<style>` tags: rebase every sheet onto the first sheet's hash
|
|
2357
|
+
// before scoping, so selector rewriting and the DOM hash annotation below
|
|
2358
|
+
// agree. `apply_css_definition_metadata` accumulates into the component's
|
|
2359
|
+
// `styleClasses`/`topScopedClasses` metadata, so per-sheet calls compose
|
|
2360
|
+
// into one class map for style refs.
|
|
2361
|
+
for (const sheet of sheets) {
|
|
2362
|
+
const region_hash = sheet.hash;
|
|
2363
|
+
sheet.hash = css.hash;
|
|
2364
|
+
// `prune_css` inside marks the matching selectors as used/scoped; selectors
|
|
2365
|
+
// that match no element render commented out, like the Ripple target.
|
|
2366
|
+
apply_css_definition_metadata(
|
|
2367
|
+
node,
|
|
2368
|
+
sheet,
|
|
2369
|
+
transform_context,
|
|
2370
|
+
style_refs.length > 0,
|
|
2371
|
+
region_hash,
|
|
2372
|
+
);
|
|
2373
|
+
transform_context.stylesheets.push(sheet);
|
|
2374
|
+
}
|
|
2313
2375
|
const fragment = annotate_tsrx_with_hash(
|
|
2314
2376
|
node,
|
|
2315
2377
|
css.hash,
|
|
@@ -6110,8 +6172,16 @@ function transform_element_attributes_dispatch(attrs, transform_context, element
|
|
|
6110
6172
|
}
|
|
6111
6173
|
const hook = transform_context.platform.hooks?.transformElementAttributes;
|
|
6112
6174
|
const result = hook ? hook(attrs, transform_context, element) : attrs;
|
|
6175
|
+
// An element in plain-JS expression position reaches BOTH lowering sites —
|
|
6176
|
+
// the JSXOpeningElement visitor above and this dispatch — so without the
|
|
6177
|
+
// marker its host ref/spread is lowered twice. Scoped to the type-only
|
|
6178
|
+
// print: runtime emit for the other platforms sharing this transform keeps
|
|
6179
|
+
// its existing output.
|
|
6180
|
+
const already_lowered =
|
|
6181
|
+
transform_context.typeOnly &&
|
|
6182
|
+
element?.openingElement?.metadata?.host_ref_spread_lowered === true;
|
|
6113
6183
|
return merge_duplicate_refs(
|
|
6114
|
-
normalize_host_ref_spreads(result, !is_component, transform_context),
|
|
6184
|
+
already_lowered ? result : normalize_host_ref_spreads(result, !is_component, transform_context),
|
|
6115
6185
|
transform_context,
|
|
6116
6186
|
);
|
|
6117
6187
|
}
|
|
@@ -34,11 +34,19 @@ import {
|
|
|
34
34
|
build_line_offsets,
|
|
35
35
|
get_mapping_from_node,
|
|
36
36
|
} from '../source-map-utils.js';
|
|
37
|
-
import {
|
|
37
|
+
import { should_preserve_jsx_tooling_comment, format_comment } from '../comment-utils.js';
|
|
38
38
|
import { has_location } from '../utils/ast.js';
|
|
39
39
|
|
|
40
40
|
const LAZY_PARAM_IDENTIFIER_REGEX = /^__lazy\d+$/;
|
|
41
41
|
const RETURN_KEYWORD = 'return';
|
|
42
|
+
const EXPORT_KEYWORD = 'export';
|
|
43
|
+
const BLOCK_DECLARATION_TYPES = new Set([
|
|
44
|
+
'FunctionDeclaration',
|
|
45
|
+
'ClassDeclaration',
|
|
46
|
+
'TSInterfaceDeclaration',
|
|
47
|
+
'TSEnumDeclaration',
|
|
48
|
+
'TSModuleDeclaration',
|
|
49
|
+
]);
|
|
42
50
|
|
|
43
51
|
/**
|
|
44
52
|
* @param {string} value
|
|
@@ -418,6 +426,88 @@ export function convert_source_map_to_mappings(
|
|
|
418
426
|
return candidates[Math.min(index, candidates.length - 1)];
|
|
419
427
|
}
|
|
420
428
|
|
|
429
|
+
/**
|
|
430
|
+
* A comment's end can share a source coordinate with the next declaration,
|
|
431
|
+
* and synthetic file pragmas can share offset zero with the first export.
|
|
432
|
+
* Select the position that actually prints this node's opening text instead
|
|
433
|
+
* of treating the first source-map entry as an unambiguous boundary.
|
|
434
|
+
* @param {AST.Position} position
|
|
435
|
+
* @param {string} text
|
|
436
|
+
* @returns {number | undefined}
|
|
437
|
+
*/
|
|
438
|
+
function generated_offset_for_text(position, text) {
|
|
439
|
+
const positions = src_to_gen_map.get(`${position.line}:${position.column}`);
|
|
440
|
+
for (const generated of positions ?? []) {
|
|
441
|
+
const offset = loc_to_offset(generated.line, generated.column, gen_line_offsets);
|
|
442
|
+
if (generated_code.startsWith(text, offset)) return offset;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* @param {AST.NodeWithLocation} node
|
|
448
|
+
* @param {string} start_text
|
|
449
|
+
* @returns {CodeMapping | undefined}
|
|
450
|
+
*/
|
|
451
|
+
function declaration_mapping(node, start_text) {
|
|
452
|
+
const start = generated_offset_for_text(node.loc.start, start_text);
|
|
453
|
+
if (start === undefined) return;
|
|
454
|
+
const positions = src_to_gen_map.get(`${node.loc.end.line}:${node.loc.end.column}`);
|
|
455
|
+
for (const generated of positions ?? []) {
|
|
456
|
+
const end = loc_to_offset(generated.line, generated.column, gen_line_offsets);
|
|
457
|
+
if (end < start) continue;
|
|
458
|
+
return {
|
|
459
|
+
sourceOffsets: [node.start],
|
|
460
|
+
lengths: [node.end - node.start],
|
|
461
|
+
generatedOffsets: [start],
|
|
462
|
+
generatedLengths: [end - start],
|
|
463
|
+
data: { ...mapping_data_verify_only, customData: {} },
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/** @param {AST.ExportNamedDeclaration | AST.ExportDefaultDeclaration | AST.ExportAllDeclaration} node */
|
|
469
|
+
function add_export_mapping(node) {
|
|
470
|
+
if (!has_location(node)) return;
|
|
471
|
+
const mapping = declaration_mapping(node, EXPORT_KEYWORD);
|
|
472
|
+
if (mapping) {
|
|
473
|
+
const declaration = node.type === 'ExportAllDeclaration' ? null : node.declaration;
|
|
474
|
+
const end = mapping.generatedOffsets[0] + mapping.generatedLengths[0];
|
|
475
|
+
// A semicolon-free source declaration shares its end with its last
|
|
476
|
+
// expression. The first map entry then precedes the statement's emitted
|
|
477
|
+
// semicolon. Block declarations are different: a following `;` is an
|
|
478
|
+
// empty statement, not part of TypeScript's declaration range.
|
|
479
|
+
if (
|
|
480
|
+
generated_code[end] === ';' &&
|
|
481
|
+
(!declaration || !BLOCK_DECLARATION_TYPES.has(declaration.type))
|
|
482
|
+
) {
|
|
483
|
+
mapping.generatedLengths[0]++;
|
|
484
|
+
}
|
|
485
|
+
// Full declaration queries need both endpoints in the same Volar
|
|
486
|
+
// mapping, not a linear claim over the generated body. A transformed
|
|
487
|
+
// component can contain synthetic imports, tags, and helper calls that
|
|
488
|
+
// must not acquire source locations merely because it is exported.
|
|
489
|
+
const generated_start = mapping.generatedOffsets[0];
|
|
490
|
+
const generated_end = generated_start + mapping.generatedLengths[0];
|
|
491
|
+
mapping.sourceOffsets = [node.start, node.end];
|
|
492
|
+
mapping.generatedOffsets = [generated_start, generated_end];
|
|
493
|
+
mapping.lengths = [0, 0];
|
|
494
|
+
mapping.generatedLengths = [0, 0];
|
|
495
|
+
mappings.push(mapping);
|
|
496
|
+
}
|
|
497
|
+
tokens.push({
|
|
498
|
+
source: EXPORT_KEYWORD,
|
|
499
|
+
generated: EXPORT_KEYWORD,
|
|
500
|
+
loc: {
|
|
501
|
+
start: node.loc.start,
|
|
502
|
+
end: {
|
|
503
|
+
line: node.loc.start.line,
|
|
504
|
+
column: node.loc.start.column + EXPORT_KEYWORD.length,
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
metadata: {},
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
421
511
|
/**
|
|
422
512
|
* Needed for a mapping that includes the computed brackets for diagnostics
|
|
423
513
|
* @param {AST.MethodDefinition | AST.Property} node
|
|
@@ -464,25 +554,24 @@ export function convert_source_map_to_mappings(
|
|
|
464
554
|
if (!Array.isArray(comments)) continue;
|
|
465
555
|
|
|
466
556
|
for (const comment of comments) {
|
|
467
|
-
if (!has_location(comment) || !
|
|
557
|
+
if (!has_location(comment) || !should_preserve_jsx_tooling_comment(comment)) continue;
|
|
468
558
|
|
|
469
559
|
const comment_key = `${comment.start}:${comment.end}`;
|
|
470
560
|
if (mapped_comments.has(comment_key)) continue;
|
|
471
561
|
mapped_comments.add(comment_key);
|
|
472
562
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
563
|
+
const text = format_comment(comment);
|
|
564
|
+
const start = generated_offset_for_text(comment.loc.start, text);
|
|
565
|
+
// A stripped or moved comment must not claim another node's shared
|
|
566
|
+
// source coordinate. The printer writes this exact formatted text.
|
|
567
|
+
if (start === undefined) continue;
|
|
568
|
+
mappings.push({
|
|
569
|
+
sourceOffsets: [comment.start],
|
|
570
|
+
lengths: [comment.end - comment.start],
|
|
571
|
+
generatedOffsets: [start],
|
|
572
|
+
generatedLengths: [text.length],
|
|
573
|
+
data: { ...mapping_data_verify_only, customData: {} },
|
|
574
|
+
});
|
|
486
575
|
}
|
|
487
576
|
}
|
|
488
577
|
}
|
|
@@ -704,6 +793,7 @@ export function convert_source_map_to_mappings(
|
|
|
704
793
|
}
|
|
705
794
|
return;
|
|
706
795
|
} else if (node.type === 'ExportNamedDeclaration') {
|
|
796
|
+
add_export_mapping(node);
|
|
707
797
|
if (node.specifiers && node.specifiers.length > 0) {
|
|
708
798
|
for (const specifier of node.specifiers) {
|
|
709
799
|
visit(specifier);
|
|
@@ -715,12 +805,14 @@ export function convert_source_map_to_mappings(
|
|
|
715
805
|
}
|
|
716
806
|
return;
|
|
717
807
|
} else if (node.type === 'ExportDefaultDeclaration') {
|
|
808
|
+
add_export_mapping(node);
|
|
718
809
|
// Visit the declaration
|
|
719
810
|
if (node.declaration) {
|
|
720
811
|
visit(/** @type {AST.Node} */ (node.declaration));
|
|
721
812
|
}
|
|
722
813
|
return;
|
|
723
814
|
} else if (node.type === 'ExportAllDeclaration') {
|
|
815
|
+
add_export_mapping(node);
|
|
724
816
|
// Nothing to visit (just source string)
|
|
725
817
|
return;
|
|
726
818
|
} else if (node.type === 'JSXOpeningElement') {
|
|
@@ -793,7 +885,7 @@ export function convert_source_map_to_mappings(
|
|
|
793
885
|
definition: {
|
|
794
886
|
description: `CSS class selector for '.${name}'`,
|
|
795
887
|
location: {
|
|
796
|
-
embeddedId: get_style_region_id(css.hash),
|
|
888
|
+
embeddedId: get_style_region_id(cssLocation.regionHash ?? css.hash),
|
|
797
889
|
start: cssLocation.start,
|
|
798
890
|
end: cssLocation.end,
|
|
799
891
|
},
|
|
@@ -1908,6 +2000,24 @@ export function convert_source_map_to_mappings(
|
|
|
1908
2000
|
}
|
|
1909
2001
|
return;
|
|
1910
2002
|
} else if (node.type === 'TSPropertySignature') {
|
|
2003
|
+
if (has_location(node)) {
|
|
2004
|
+
const start_text = node.readonly
|
|
2005
|
+
? 'readonly'
|
|
2006
|
+
: node.computed
|
|
2007
|
+
? '['
|
|
2008
|
+
: node.key.type === 'Identifier'
|
|
2009
|
+
? node.key.name
|
|
2010
|
+
: source.slice(node.start, node.key.end);
|
|
2011
|
+
const mapping = declaration_mapping(node, start_text);
|
|
2012
|
+
if (mapping) {
|
|
2013
|
+
const end = mapping.generatedOffsets[0] + mapping.generatedLengths[0];
|
|
2014
|
+
// esrap's containing type/interface prints member separators
|
|
2015
|
+
// after the property's own end marker. TS includes that `;`
|
|
2016
|
+
// in its PropertySignature range, even when it was not authored.
|
|
2017
|
+
if (generated_code[end] === ';') mapping.generatedLengths[0]++;
|
|
2018
|
+
mappings.push(mapping);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
1911
2021
|
// Property signature in type
|
|
1912
2022
|
if (node.key) {
|
|
1913
2023
|
visit(node.key);
|
|
@@ -2354,7 +2464,11 @@ export function convert_source_map_to_mappings(
|
|
|
2354
2464
|
|
|
2355
2465
|
// Add a mapping for the very beginning of the file to handle import additions
|
|
2356
2466
|
// This ensures that code actions adding imports at the top work correctly
|
|
2357
|
-
if (
|
|
2467
|
+
if (
|
|
2468
|
+
!isImportDeclarationPresent &&
|
|
2469
|
+
mappings.length > 0 &&
|
|
2470
|
+
(mappings[0].sourceOffsets[0] > 0 || mappings[0].generatedOffsets[0] > 0)
|
|
2471
|
+
) {
|
|
2358
2472
|
mappings.unshift({
|
|
2359
2473
|
sourceOffsets: [0],
|
|
2360
2474
|
generatedOffsets: [0],
|
package/types/index.d.ts
CHANGED
|
@@ -989,6 +989,8 @@ declare module 'estree-jsx' {
|
|
|
989
989
|
interface JSXOpeningElement {
|
|
990
990
|
metadata: BaseNodeMetaData & {
|
|
991
991
|
native_tsrx_pretransformed?: boolean;
|
|
992
|
+
/** Type-only host ref/spread normalization has already run for this element. */
|
|
993
|
+
host_ref_spread_lowered?: boolean;
|
|
992
994
|
};
|
|
993
995
|
}
|
|
994
996
|
|
|
@@ -2090,6 +2092,8 @@ export type TopScopedClasses = Map<
|
|
|
2090
2092
|
start: number;
|
|
2091
2093
|
end: number;
|
|
2092
2094
|
selector: AST.CSS.ClassSelector;
|
|
2095
|
+
/** Source `<style>` region for editor definition navigation. */
|
|
2096
|
+
regionHash?: string;
|
|
2093
2097
|
}
|
|
2094
2098
|
>;
|
|
2095
2099
|
|
|
@@ -2218,6 +2222,14 @@ export interface VolarCompileOptions extends Omit<ParseOptions, 'errors' | 'comm
|
|
|
2218
2222
|
dev?: boolean;
|
|
2219
2223
|
}
|
|
2220
2224
|
|
|
2225
|
+
/**
|
|
2226
|
+
* Selects where generated runtime helper imports resolve from. Direct mode
|
|
2227
|
+
* emits bare imports from the target's standalone runtime package; the package
|
|
2228
|
+
* that owns the generated modules must declare that runtime as a direct
|
|
2229
|
+
* production dependency.
|
|
2230
|
+
*/
|
|
2231
|
+
export type RuntimeImportMode = 'compiler' | 'direct';
|
|
2232
|
+
|
|
2221
2233
|
/**
|
|
2222
2234
|
* Common base options accepted by every TSRX target's `compile` entry point.
|
|
2223
2235
|
* Targets that need extra knobs (e.g. ripple's `mode`/`dev`/`hmr`, preact's
|
|
@@ -2227,6 +2239,13 @@ export interface VolarCompileOptions extends Omit<ParseOptions, 'errors' | 'comm
|
|
|
2227
2239
|
export interface BaseCompileOptions {
|
|
2228
2240
|
collect?: boolean;
|
|
2229
2241
|
loose?: boolean;
|
|
2242
|
+
/**
|
|
2243
|
+
* Selects where generated runtime helper imports resolve from. The default
|
|
2244
|
+
* `'compiler'` mode preserves compiler-package compatibility subpaths;
|
|
2245
|
+
* `'direct'` targets the renderer's small runtime package, which the package
|
|
2246
|
+
* owning the generated modules must declare as a direct production dependency.
|
|
2247
|
+
*/
|
|
2248
|
+
runtimeImports?: RuntimeImportMode;
|
|
2230
2249
|
}
|
|
2231
2250
|
|
|
2232
2251
|
/**
|
package/types/jsx-platform.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as AST from 'estree';
|
|
2
2
|
import type * as ESTreeJSX from 'estree-jsx';
|
|
3
3
|
import type { RawSourceMap } from 'source-map';
|
|
4
|
-
import type { CompileError, JsxHelperComponent, JsxHelperState } from './index';
|
|
4
|
+
import type { CompileError, JsxHelperComponent, JsxHelperState, RuntimeImportMode } from './index';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Result returned by a JSX platform transform (React, Preact, Solid).
|
|
@@ -82,6 +82,13 @@ export interface JsxTransformContext {
|
|
|
82
82
|
* Optional per-call compile options passed to a created JSX transform.
|
|
83
83
|
*/
|
|
84
84
|
export interface JsxTransformOptions {
|
|
85
|
+
/**
|
|
86
|
+
* Selects compiler-package compatibility imports or direct renderer runtime
|
|
87
|
+
* package imports. Defaults to `'compiler'`. Direct mode requires the package
|
|
88
|
+
* owning generated modules to declare the target runtime as a direct
|
|
89
|
+
* production dependency.
|
|
90
|
+
*/
|
|
91
|
+
runtimeImports?: RuntimeImportMode;
|
|
85
92
|
/**
|
|
86
93
|
* Override the import source used for `Suspense` in try-block transforms.
|
|
87
94
|
* Falls back to `platform.imports.suspense`. Preact uses this to let the
|
|
@@ -412,6 +419,17 @@ export interface JsxPlatform {
|
|
|
412
419
|
forOfIterableHelper?: string;
|
|
413
420
|
};
|
|
414
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Runtime-helper import sources used when a compile call opts into direct
|
|
424
|
+
* runtime imports. Fields omitted here continue using `imports`.
|
|
425
|
+
*/
|
|
426
|
+
directRuntimeImports?: {
|
|
427
|
+
errorBoundary?: string;
|
|
428
|
+
mergeRefs?: string;
|
|
429
|
+
refProp?: string;
|
|
430
|
+
forOfIterableHelper?: string;
|
|
431
|
+
};
|
|
432
|
+
|
|
415
433
|
jsx: {
|
|
416
434
|
/**
|
|
417
435
|
* Rewrite Ripple's `class` attribute to `className` for legacy targets
|
|
@@ -1,13 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// example: IterationValue<typeof something>
|
|
3
|
-
export type IterationValue<T> = T extends readonly unknown[]
|
|
4
|
-
? T[number]
|
|
5
|
-
: T extends Iterable<infer U>
|
|
6
|
-
? U
|
|
7
|
-
: never;
|
|
8
|
-
|
|
9
|
-
export function map_iterable<T, U>(
|
|
10
|
-
value: Iterable<T>,
|
|
11
|
-
fn: (item: T, index: number, is_last: boolean) => U,
|
|
12
|
-
tail?: () => U | U[],
|
|
13
|
-
): U[];
|
|
1
|
+
export * from '@tsrx/runtime/iterable';
|