ripple 0.2.115 → 0.2.118
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 +16 -16
- package/src/compiler/index.js +20 -1
- package/src/compiler/phases/1-parse/index.js +79 -0
- package/src/compiler/phases/3-transform/client/index.js +54 -8
- package/src/compiler/phases/3-transform/segments.js +107 -60
- package/src/compiler/phases/3-transform/server/index.js +21 -11
- package/src/compiler/types/index.d.ts +16 -0
- package/src/runtime/index-client.js +19 -185
- package/src/runtime/index-server.js +24 -0
- package/src/runtime/internal/client/bindings.js +443 -0
- package/src/runtime/internal/client/index.js +4 -0
- package/src/runtime/internal/client/runtime.js +10 -0
- package/src/runtime/internal/client/utils.js +0 -8
- package/src/runtime/map.js +11 -1
- package/src/runtime/set.js +11 -1
- package/tests/client/__snapshots__/for.test.ripple.snap +80 -0
- package/tests/client/_etc.test.ripple +5 -0
- package/tests/client/array/array.copy-within.test.ripple +120 -0
- package/tests/client/array/array.derived.test.ripple +495 -0
- package/tests/client/array/array.iteration.test.ripple +115 -0
- package/tests/client/array/array.mutations.test.ripple +385 -0
- package/tests/client/array/array.static.test.ripple +237 -0
- package/tests/client/array/array.to-methods.test.ripple +93 -0
- package/tests/client/basic/__snapshots__/basic.attributes.test.ripple.snap +60 -0
- package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +106 -0
- package/tests/client/basic/__snapshots__/basic.text.test.ripple.snap +49 -0
- package/tests/client/basic/basic.attributes.test.ripple +474 -0
- package/tests/client/basic/basic.collections.test.ripple +94 -0
- package/tests/client/basic/basic.components.test.ripple +225 -0
- package/tests/client/basic/basic.errors.test.ripple +126 -0
- package/tests/client/basic/basic.events.test.ripple +222 -0
- package/tests/client/basic/basic.reactivity.test.ripple +476 -0
- package/tests/client/basic/basic.rendering.test.ripple +204 -0
- package/tests/client/basic/basic.styling.test.ripple +63 -0
- package/tests/client/basic/basic.utilities.test.ripple +25 -0
- package/tests/client/boundaries.test.ripple +2 -21
- package/tests/client/compiler/__snapshots__/compiler.assignments.test.ripple.snap +12 -0
- package/tests/client/compiler/__snapshots__/compiler.typescript.test.ripple.snap +22 -0
- package/tests/client/compiler/compiler.assignments.test.ripple +112 -0
- package/tests/client/compiler/compiler.attributes.test.ripple +95 -0
- package/tests/client/compiler/compiler.basic.test.ripple +203 -0
- package/tests/client/compiler/compiler.regex.test.ripple +87 -0
- package/tests/client/compiler/compiler.typescript.test.ripple +29 -0
- package/tests/client/{__snapshots__/composite.test.ripple.snap → composite/__snapshots__/composite.render.test.ripple.snap} +2 -2
- package/tests/client/composite/composite.dynamic-components.test.ripple +100 -0
- package/tests/client/composite/composite.generics.test.ripple +211 -0
- package/tests/client/composite/composite.props.test.ripple +106 -0
- package/tests/client/composite/composite.reactivity.test.ripple +184 -0
- package/tests/client/composite/composite.render.test.ripple +84 -0
- package/tests/client/computed-properties.test.ripple +2 -21
- package/tests/client/context.test.ripple +5 -22
- package/tests/client/date.test.ripple +1 -20
- package/tests/client/dynamic-elements.test.ripple +16 -24
- package/tests/client/for.test.ripple +4 -23
- package/tests/client/head.test.ripple +11 -23
- package/tests/client/html.test.ripple +1 -20
- package/tests/client/input-value.test.ripple +11 -31
- package/tests/client/map.test.ripple +82 -20
- package/tests/client/media-query.test.ripple +10 -23
- package/tests/client/object.test.ripple +5 -24
- package/tests/client/portal.test.ripple +2 -19
- package/tests/client/ref.test.ripple +8 -26
- package/tests/client/set.test.ripple +84 -22
- package/tests/client/svg.test.ripple +1 -22
- package/tests/client/switch.test.ripple +6 -25
- package/tests/client/tracked-expression.test.ripple +2 -21
- package/tests/client/typescript-generics.test.ripple +0 -21
- package/tests/client/url/url.derived.test.ripple +83 -0
- package/tests/client/url/url.parsing.test.ripple +165 -0
- package/tests/client/url/url.partial-removal.test.ripple +198 -0
- package/tests/client/url/url.reactivity.test.ripple +449 -0
- package/tests/client/url/url.serialization.test.ripple +50 -0
- package/tests/client/url-search-params/url-search-params.derived.test.ripple +84 -0
- package/tests/client/url-search-params/url-search-params.initialization.test.ripple +61 -0
- package/tests/client/url-search-params/url-search-params.iteration.test.ripple +153 -0
- package/tests/client/url-search-params/url-search-params.mutation.test.ripple +343 -0
- package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +160 -0
- package/tests/client/url-search-params/url-search-params.serialization.test.ripple +53 -0
- package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +55 -0
- package/tests/client.d.ts +12 -0
- package/tests/server/if.test.ripple +66 -0
- package/tests/setup-client.js +28 -0
- package/tsconfig.json +4 -2
- package/types/index.d.ts +92 -46
- package/LICENSE +0 -21
- package/tests/client/__snapshots__/basic.test.ripple.snap +0 -117
- package/tests/client/__snapshots__/compiler.test.ripple.snap +0 -33
- package/tests/client/array.test.ripple +0 -1455
- package/tests/client/basic.test.ripple +0 -1892
- package/tests/client/compiler.test.ripple +0 -541
- package/tests/client/composite.test.ripple +0 -692
- package/tests/client/url-search-params.test.ripple +0 -912
- package/tests/client/url.test.ripple +0 -954
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Ripple is an elegant TypeScript UI framework",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.118",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -63,21 +63,21 @@
|
|
|
63
63
|
"#public": "./types/index.d.ts"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@jridgewell/sourcemap-codec": "
|
|
67
|
-
"@sveltejs/acorn-typescript": "
|
|
68
|
-
"acorn": "
|
|
69
|
-
"clsx": "
|
|
70
|
-
"devalue": "
|
|
71
|
-
"esm-env": "
|
|
72
|
-
"esrap": "
|
|
73
|
-
"is-reference": "
|
|
74
|
-
"magic-string": "
|
|
75
|
-
"muggle-string": "
|
|
76
|
-
"zimmerframe": "
|
|
66
|
+
"@jridgewell/sourcemap-codec": "catalog:default",
|
|
67
|
+
"@sveltejs/acorn-typescript": "catalog:default",
|
|
68
|
+
"acorn": "catalog:default",
|
|
69
|
+
"clsx": "catalog:default",
|
|
70
|
+
"devalue": "catalog:default",
|
|
71
|
+
"esm-env": "catalog:default",
|
|
72
|
+
"esrap": "catalog:default",
|
|
73
|
+
"is-reference": "catalog:default",
|
|
74
|
+
"magic-string": "catalog:default",
|
|
75
|
+
"muggle-string": "catalog:default",
|
|
76
|
+
"zimmerframe": "catalog:default"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@types/estree": "
|
|
80
|
-
"@types/node": "
|
|
81
|
-
"typescript": "
|
|
79
|
+
"@types/estree": "catalog:default",
|
|
80
|
+
"@types/node": "catalog:default",
|
|
81
|
+
"typescript": "catalog:default"
|
|
82
82
|
}
|
|
83
|
-
}
|
|
83
|
+
}
|
package/src/compiler/index.js
CHANGED
|
@@ -41,9 +41,28 @@ export function compile(source, filename, options = {}) {
|
|
|
41
41
|
export function compile_to_volar_mappings(source, filename) {
|
|
42
42
|
// Parse and transform
|
|
43
43
|
const ast = parse_module(source);
|
|
44
|
+
|
|
45
|
+
// Add unique IDs to import declarations before transformation
|
|
46
|
+
// This allows us to match source imports with generated imports reliably
|
|
47
|
+
// This strategy can potentially be used for other node types in the future
|
|
48
|
+
let gen_id = 0;
|
|
49
|
+
const source_import_map = new Map();
|
|
50
|
+
for (const node of ast.body) {
|
|
51
|
+
if (node.type === 'ImportDeclaration') {
|
|
52
|
+
const start = /** @type {any} */ (node).start;
|
|
53
|
+
const end = /** @type {any} */ (node).end;
|
|
54
|
+
if (start !== undefined && end !== undefined) {
|
|
55
|
+
// Add a unique ID as a string property that will be copied during transformation
|
|
56
|
+
const id = `__volar_import_${gen_id++}__`;
|
|
57
|
+
/** @type {any} */ (node).__volar_id = id;
|
|
58
|
+
source_import_map.set(id, { start, end });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
44
63
|
const analysis = analyze(ast, filename);
|
|
45
64
|
const transformed = transform_client(filename, source, analysis, true);
|
|
46
65
|
|
|
47
66
|
// Create volar mappings directly from the AST instead of relying on esrap's sourcemap
|
|
48
|
-
return convert_source_map_to_mappings(transformed.ast, source, transformed.js.code);
|
|
67
|
+
return convert_source_map_to_mappings(transformed.ast, source, transformed.js.code, source_import_map);
|
|
49
68
|
}
|
|
@@ -167,6 +167,22 @@ function RipplePlugin(config) {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// Check if this is #Map or #Set
|
|
171
|
+
if (this.input.slice(this.pos, this.pos + 4) === '#Map') {
|
|
172
|
+
const charAfter = this.pos + 4 < this.input.length ? this.input.charCodeAt(this.pos + 4) : -1;
|
|
173
|
+
if (charAfter === 40) { // ( character
|
|
174
|
+
this.pos += 4; // consume '#Map'
|
|
175
|
+
return this.finishToken(tt.name, '#Map');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (this.input.slice(this.pos, this.pos + 4) === '#Set') {
|
|
179
|
+
const charAfter = this.pos + 4 < this.input.length ? this.input.charCodeAt(this.pos + 4) : -1;
|
|
180
|
+
if (charAfter === 40) { // ( character
|
|
181
|
+
this.pos += 4; // consume '#Set'
|
|
182
|
+
return this.finishToken(tt.name, '#Set');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
170
186
|
// Check if this is #server
|
|
171
187
|
if (this.input.slice(this.pos, this.pos + 7) === '#server') {
|
|
172
188
|
// Check that next char after 'server' is whitespace, {, . (dot), or EOF
|
|
@@ -186,6 +202,27 @@ function RipplePlugin(config) {
|
|
|
186
202
|
return this.finishToken(tt.name, '#server');
|
|
187
203
|
}
|
|
188
204
|
}
|
|
205
|
+
|
|
206
|
+
// Check if this is an invalid #Identifier pattern
|
|
207
|
+
// Valid patterns: #[, #{, #Map(, #Set(, #server
|
|
208
|
+
// If we see # followed by an uppercase letter that isn't Map or Set, it's an error
|
|
209
|
+
if (nextChar >= 65 && nextChar <= 90) { // A-Z
|
|
210
|
+
// Extract the identifier name
|
|
211
|
+
let identEnd = this.pos + 1;
|
|
212
|
+
while (identEnd < this.input.length) {
|
|
213
|
+
const ch = this.input.charCodeAt(identEnd);
|
|
214
|
+
if ((ch >= 65 && ch <= 90) || (ch >= 97 && ch <= 122) || (ch >= 48 && ch <= 57) || ch === 95) {
|
|
215
|
+
// A-Z, a-z, 0-9, _
|
|
216
|
+
identEnd++;
|
|
217
|
+
} else {
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const identName = this.input.slice(this.pos + 1, identEnd);
|
|
222
|
+
if (identName !== 'Map' && identName !== 'Set') {
|
|
223
|
+
this.raise(this.pos, `Invalid tracked syntax '#${identName}'. Only #Map and #Set are currently supported using shorthand tracked syntax.`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
189
226
|
}
|
|
190
227
|
}
|
|
191
228
|
if (code === 64) {
|
|
@@ -391,6 +428,12 @@ function RipplePlugin(config) {
|
|
|
391
428
|
return this.finishNode(node, 'ServerIdentifier');
|
|
392
429
|
}
|
|
393
430
|
|
|
431
|
+
// Check if this is #Map( or #Set(
|
|
432
|
+
if (this.type === tt.name && (this.value === '#Map' || this.value === '#Set')) {
|
|
433
|
+
const type = this.value === '#Map' ? 'TrackedMapExpression' : 'TrackedSetExpression';
|
|
434
|
+
return this.parseTrackedCollectionExpression(type);
|
|
435
|
+
}
|
|
436
|
+
|
|
394
437
|
// Check if this is a tuple literal starting with #[
|
|
395
438
|
if (this.type === tt.bracketL && this.value === '#[') {
|
|
396
439
|
return this.parseTrackedArrayExpression();
|
|
@@ -449,6 +492,42 @@ function RipplePlugin(config) {
|
|
|
449
492
|
return this.finishNode(node, 'ServerBlock');
|
|
450
493
|
}
|
|
451
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Parse `#Map(...)` or `#Set(...)` syntax for tracked collections
|
|
497
|
+
* Creates a TrackedMap or TrackedSet node with the arguments property
|
|
498
|
+
* @param {string} type - Either 'TrackedMap' or 'TrackedSet'
|
|
499
|
+
* @returns {any} TrackedMap or TrackedSet node
|
|
500
|
+
*/
|
|
501
|
+
parseTrackedCollectionExpression(type) {
|
|
502
|
+
const node = this.startNode();
|
|
503
|
+
this.next(); // consume '#Map' or '#Set'
|
|
504
|
+
this.expect(tt.parenL); // expect '('
|
|
505
|
+
|
|
506
|
+
node.arguments = [];
|
|
507
|
+
|
|
508
|
+
// Parse arguments similar to function call arguments
|
|
509
|
+
let first = true;
|
|
510
|
+
while (!this.eat(tt.parenR)) {
|
|
511
|
+
if (!first) {
|
|
512
|
+
this.expect(tt.comma);
|
|
513
|
+
if (this.afterTrailingComma(tt.parenR)) break;
|
|
514
|
+
} else {
|
|
515
|
+
first = false;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (this.type === tt.ellipsis) {
|
|
519
|
+
// Spread argument
|
|
520
|
+
const arg = this.parseSpread();
|
|
521
|
+
node.arguments.push(arg);
|
|
522
|
+
} else {
|
|
523
|
+
// Regular argument
|
|
524
|
+
node.arguments.push(this.parseMaybeAssign(false));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return this.finishNode(node, type);
|
|
529
|
+
}
|
|
530
|
+
|
|
452
531
|
parseTrackedArrayExpression() {
|
|
453
532
|
const node = this.startNode();
|
|
454
533
|
this.next(); // consume the '#['
|
|
@@ -350,11 +350,9 @@ const visitors = {
|
|
|
350
350
|
context.state.imports.add(`import { TrackedArray } from 'ripple'`);
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
return b.
|
|
354
|
-
b.
|
|
355
|
-
|
|
356
|
-
node.elements.map((el) => context.visit(el)),
|
|
357
|
-
),
|
|
353
|
+
return b.call(
|
|
354
|
+
b.member(b.id('TrackedArray'), b.id('from')),
|
|
355
|
+
...node.elements.map((el) => context.visit(el)),
|
|
358
356
|
);
|
|
359
357
|
}
|
|
360
358
|
|
|
@@ -384,6 +382,48 @@ const visitors = {
|
|
|
384
382
|
);
|
|
385
383
|
},
|
|
386
384
|
|
|
385
|
+
TrackedMapExpression(node, context) {
|
|
386
|
+
if (context.state.to_ts) {
|
|
387
|
+
if (!context.state.imports.has(`import { TrackedMap } from 'ripple'`)) {
|
|
388
|
+
context.state.imports.add(`import { TrackedMap } from 'ripple'`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const calleeId = b.id('TrackedMap');
|
|
392
|
+
// Preserve location from original node for Volar mapping
|
|
393
|
+
calleeId.loc = node.loc;
|
|
394
|
+
// Add metadata for Volar mapping - map "TrackedMap" identifier to "#Map" in source
|
|
395
|
+
calleeId.metadata = { tracked_shorthand: '#Map' };
|
|
396
|
+
return b.new(calleeId, ...node.arguments.map((arg) => context.visit(arg)));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return b.call(
|
|
400
|
+
'_$_.tracked_map',
|
|
401
|
+
b.id('__block'),
|
|
402
|
+
...node.arguments.map((arg) => context.visit(arg)),
|
|
403
|
+
);
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
TrackedSetExpression(node, context) {
|
|
407
|
+
if (context.state.to_ts) {
|
|
408
|
+
if (!context.state.imports.has(`import { TrackedSet } from 'ripple'`)) {
|
|
409
|
+
context.state.imports.add(`import { TrackedSet } from 'ripple'`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const calleeId = b.id('TrackedSet');
|
|
413
|
+
// Preserve location from original node for Volar mapping
|
|
414
|
+
calleeId.loc = node.loc;
|
|
415
|
+
// Add metadata for Volar mapping - map "TrackedSet" identifier to "#Set" in source
|
|
416
|
+
calleeId.metadata = { tracked_shorthand: '#Set' };
|
|
417
|
+
return b.new(calleeId, ...node.arguments.map((arg) => context.visit(arg)));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return b.call(
|
|
421
|
+
'_$_.tracked_set',
|
|
422
|
+
b.id('__block'),
|
|
423
|
+
...node.arguments.map((arg) => context.visit(arg)),
|
|
424
|
+
);
|
|
425
|
+
},
|
|
426
|
+
|
|
387
427
|
TrackedExpression(node, context) {
|
|
388
428
|
return b.call('_$_.get', context.visit(node.argument));
|
|
389
429
|
},
|
|
@@ -459,7 +499,7 @@ const visitors = {
|
|
|
459
499
|
return {
|
|
460
500
|
...node,
|
|
461
501
|
id: { ...node.id, name: capitalizedName },
|
|
462
|
-
init: node.init ? context.visit(node.init) : null
|
|
502
|
+
init: node.init ? context.visit(node.init) : null,
|
|
463
503
|
};
|
|
464
504
|
}
|
|
465
505
|
}
|
|
@@ -1517,12 +1557,18 @@ function transform_ts_child(node, context) {
|
|
|
1517
1557
|
};
|
|
1518
1558
|
}
|
|
1519
1559
|
|
|
1520
|
-
const jsxElement = b.jsx_element(
|
|
1560
|
+
const jsxElement = b.jsx_element(
|
|
1561
|
+
opening_type,
|
|
1562
|
+
attributes,
|
|
1563
|
+
children,
|
|
1564
|
+
node.selfClosing,
|
|
1565
|
+
closing_type,
|
|
1566
|
+
);
|
|
1521
1567
|
// Preserve metadata from Element node for mapping purposes
|
|
1522
1568
|
if (node.metadata && (node.metadata.ts_name || node.metadata.original_name)) {
|
|
1523
1569
|
jsxElement.metadata = {
|
|
1524
1570
|
ts_name: node.metadata.ts_name,
|
|
1525
|
-
original_name: node.metadata.original_name
|
|
1571
|
+
original_name: node.metadata.original_name,
|
|
1526
1572
|
};
|
|
1527
1573
|
}
|
|
1528
1574
|
state.init.push(b.stmt(jsxElement));
|
|
@@ -20,22 +20,23 @@ export const mapping_data = {
|
|
|
20
20
|
* @param {any} ast - The transformed AST
|
|
21
21
|
* @param {string} source - Original source code
|
|
22
22
|
* @param {string} generated_code - Generated code from esrap
|
|
23
|
+
* @param {Map<string, {start: number, end: number}>} [source_import_map] - Map of __volar_id strings to source positions
|
|
23
24
|
* @returns {object}
|
|
24
25
|
*/
|
|
25
|
-
export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
26
|
+
export function convert_source_map_to_mappings(ast, source, generated_code, source_import_map) {
|
|
26
27
|
/** @type {Array<{sourceOffsets: number[], generatedOffsets: number[], lengths: number[], data: any}>} */
|
|
27
28
|
const mappings = [];
|
|
28
29
|
|
|
29
30
|
// Maintain indices that walk through source and generated code
|
|
30
|
-
let
|
|
31
|
-
let
|
|
31
|
+
let source_index = 0;
|
|
32
|
+
let generated_index = 0;
|
|
32
33
|
|
|
33
34
|
// Map to track capitalized names: original name -> capitalized name
|
|
34
35
|
/** @type {Map<string, string>} */
|
|
35
|
-
const
|
|
36
|
+
const capitalized_names = new Map();
|
|
36
37
|
// Reverse map: capitalized name -> original name
|
|
37
38
|
/** @type {Map<string, string>} */
|
|
38
|
-
const
|
|
39
|
+
const reverse_capitalized_names = new Map();
|
|
39
40
|
|
|
40
41
|
// Pre-walk to collect capitalized names from JSXElement nodes (transformed AST)
|
|
41
42
|
// These are identifiers that are used as dynamic components/elements
|
|
@@ -43,8 +44,8 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
43
44
|
_(node, { next }) {
|
|
44
45
|
// Check JSXElement nodes with metadata (preserved from Element nodes)
|
|
45
46
|
if (node.type === 'JSXElement' && node.metadata?.ts_name && node.metadata?.original_name) {
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
capitalized_names.set(node.metadata.original_name, node.metadata.ts_name);
|
|
48
|
+
reverse_capitalized_names.set(node.metadata.ts_name, node.metadata.original_name);
|
|
48
49
|
}
|
|
49
50
|
next();
|
|
50
51
|
}
|
|
@@ -55,7 +56,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
55
56
|
* @param {string} char
|
|
56
57
|
* @returns {boolean}
|
|
57
58
|
*/
|
|
58
|
-
const
|
|
59
|
+
const is_word_boundary = (char) => {
|
|
59
60
|
return char === undefined || !/[a-zA-Z0-9_$]/.test(char);
|
|
60
61
|
};
|
|
61
62
|
|
|
@@ -64,7 +65,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
64
65
|
* @param {number} pos - Position to check
|
|
65
66
|
* @returns {boolean}
|
|
66
67
|
*/
|
|
67
|
-
const
|
|
68
|
+
const is_in_comment = (pos) => {
|
|
68
69
|
// Check for single-line comment: find start of line and check if there's // before this position
|
|
69
70
|
let lineStart = source.lastIndexOf('\n', pos - 1) + 1;
|
|
70
71
|
const lineBeforePos = source.substring(lineStart, pos);
|
|
@@ -87,8 +88,8 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
87
88
|
* @param {string} text - Text to find
|
|
88
89
|
* @returns {number|null} - Source position or null
|
|
89
90
|
*/
|
|
90
|
-
const
|
|
91
|
-
for (let i =
|
|
91
|
+
const find_in_source = (text) => {
|
|
92
|
+
for (let i = source_index; i <= source.length - text.length; i++) {
|
|
92
93
|
let match = true;
|
|
93
94
|
for (let j = 0; j < text.length; j++) {
|
|
94
95
|
if (source[i + j] !== text[j]) {
|
|
@@ -98,7 +99,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
98
99
|
}
|
|
99
100
|
if (match) {
|
|
100
101
|
// Skip if this match is inside a comment
|
|
101
|
-
if (
|
|
102
|
+
if (is_in_comment(i)) {
|
|
102
103
|
continue;
|
|
103
104
|
}
|
|
104
105
|
|
|
@@ -107,12 +108,12 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
107
108
|
if (isIdentifierLike) {
|
|
108
109
|
const charBefore = source[i - 1];
|
|
109
110
|
const charAfter = source[i + text.length];
|
|
110
|
-
if (!
|
|
111
|
+
if (!is_word_boundary(charBefore) || !is_word_boundary(charAfter)) {
|
|
111
112
|
continue; // Not a whole word match, keep searching
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
source_index = i + text.length;
|
|
116
117
|
return i;
|
|
117
118
|
}
|
|
118
119
|
}
|
|
@@ -120,12 +121,12 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
120
121
|
};
|
|
121
122
|
|
|
122
123
|
/**
|
|
123
|
-
* Find text in generated code, searching character by character from
|
|
124
|
+
* Find text in generated code, searching character by character from generated_index
|
|
124
125
|
* @param {string} text - Text to find
|
|
125
126
|
* @returns {number|null} - Generated position or null
|
|
126
127
|
*/
|
|
127
|
-
const
|
|
128
|
-
for (let i =
|
|
128
|
+
const find_in_generated = (text) => {
|
|
129
|
+
for (let i = generated_index; i <= generated_code.length - text.length; i++) {
|
|
129
130
|
let match = true;
|
|
130
131
|
for (let j = 0; j < text.length; j++) {
|
|
131
132
|
if (generated_code[i + j] !== text[j]) {
|
|
@@ -139,12 +140,12 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
139
140
|
if (isIdentifierLike) {
|
|
140
141
|
const charBefore = generated_code[i - 1];
|
|
141
142
|
const charAfter = generated_code[i + text.length];
|
|
142
|
-
if (!
|
|
143
|
+
if (!is_word_boundary(charBefore) || !is_word_boundary(charAfter)) {
|
|
143
144
|
continue; // Not a whole word match, keep searching
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
|
|
148
|
+
generated_index = i + text.length;
|
|
148
149
|
return i;
|
|
149
150
|
}
|
|
150
151
|
}
|
|
@@ -157,8 +158,8 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
157
158
|
const tokens = [];
|
|
158
159
|
|
|
159
160
|
// Collect import declarations for full-statement mappings
|
|
160
|
-
/** @type {Array<{
|
|
161
|
-
const
|
|
161
|
+
/** @type {Array<{id: string, node: any}>} */
|
|
162
|
+
const import_declarations = [];
|
|
162
163
|
|
|
163
164
|
// We have to visit everything in generated order to maintain correct indices
|
|
164
165
|
walk(ast, null, {
|
|
@@ -167,18 +168,29 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
167
168
|
// Only collect tokens from nodes with .loc (skip synthesized nodes like children attribute)
|
|
168
169
|
if (node.type === 'Identifier' && node.name) {
|
|
169
170
|
if (node.loc) {
|
|
170
|
-
// Check if this identifier
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// This is a capitalized name in generated code, map to lowercase in source
|
|
174
|
-
tokens.push({ source: originalName, generated: node.name });
|
|
171
|
+
// Check if this identifier has tracked_shorthand metadata (e.g., TrackedMap -> #Map)
|
|
172
|
+
if (node.metadata?.tracked_shorthand) {
|
|
173
|
+
tokens.push({ source: node.metadata.tracked_shorthand, generated: node.name });
|
|
175
174
|
} else {
|
|
176
|
-
// Check if this identifier
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
179
|
-
|
|
175
|
+
// Check if this identifier was capitalized (reverse lookup)
|
|
176
|
+
const original_name = reverse_capitalized_names.get(node.name);
|
|
177
|
+
if (original_name) {
|
|
178
|
+
// This is a capitalized name in generated code, map to lowercase in source
|
|
179
|
+
tokens.push({ source: original_name, generated: node.name });
|
|
180
180
|
} else {
|
|
181
|
-
|
|
181
|
+
// Check if this identifier should be capitalized (forward lookup)
|
|
182
|
+
const cap_name = capitalized_names.get(node.name);
|
|
183
|
+
if (cap_name) {
|
|
184
|
+
tokens.push({ source: node.name, generated: cap_name });
|
|
185
|
+
} else {
|
|
186
|
+
// Check if this identifier should be capitalized (forward lookup)
|
|
187
|
+
const cap_name = capitalized_names.get(node.name);
|
|
188
|
+
if (cap_name) {
|
|
189
|
+
tokens.push({ source: node.name, generated: cap_name });
|
|
190
|
+
} else {
|
|
191
|
+
tokens.push(node.name);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
182
194
|
}
|
|
183
195
|
}
|
|
184
196
|
}
|
|
@@ -186,12 +198,12 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
186
198
|
} else if (node.type === 'JSXIdentifier' && node.name) {
|
|
187
199
|
if (node.loc) {
|
|
188
200
|
// Check if this was capitalized (reverse lookup)
|
|
189
|
-
const originalName =
|
|
201
|
+
const originalName = reverse_capitalized_names.get(node.name);
|
|
190
202
|
if (originalName) {
|
|
191
203
|
tokens.push({ source: originalName, generated: node.name });
|
|
192
204
|
} else {
|
|
193
205
|
// Check if this should be capitalized (forward lookup)
|
|
194
|
-
const capitalizedName =
|
|
206
|
+
const capitalizedName = capitalized_names.get(node.name);
|
|
195
207
|
if (capitalizedName) {
|
|
196
208
|
tokens.push({ source: node.name, generated: capitalizedName });
|
|
197
209
|
} else {
|
|
@@ -206,10 +218,16 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
206
218
|
}
|
|
207
219
|
return; // Leaf node, don't traverse further
|
|
208
220
|
} else if (node.type === 'ImportDeclaration') {
|
|
209
|
-
// Collect import declaration
|
|
221
|
+
// Collect import declaration for full-statement mapping
|
|
210
222
|
// TypeScript reports unused imports with diagnostics covering the entire statement
|
|
211
|
-
|
|
212
|
-
|
|
223
|
+
// Store the __volar_id - we'll find the generated position later by searching
|
|
224
|
+
const volar_id = /** @type {any} */ (node).__volar_id;
|
|
225
|
+
if (volar_id) {
|
|
226
|
+
import_declarations.push({
|
|
227
|
+
id: volar_id,
|
|
228
|
+
// We'll calculate genStart/genEnd later by searching in generated code
|
|
229
|
+
node: node
|
|
230
|
+
});
|
|
213
231
|
}
|
|
214
232
|
|
|
215
233
|
// Visit specifiers in source order
|
|
@@ -310,12 +328,12 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
310
328
|
if (!node.openingElement?.selfClosing && node.closingElement?.name?.type === 'JSXIdentifier') {
|
|
311
329
|
const closingName = node.closingElement.name.name;
|
|
312
330
|
// Check if this was capitalized (reverse lookup)
|
|
313
|
-
const originalName =
|
|
331
|
+
const originalName = reverse_capitalized_names.get(closingName);
|
|
314
332
|
if (originalName) {
|
|
315
333
|
tokens.push({ source: originalName, generated: closingName });
|
|
316
334
|
} else {
|
|
317
335
|
// Check if this should be capitalized (forward lookup)
|
|
318
|
-
const capitalizedName =
|
|
336
|
+
const capitalizedName = capitalized_names.get(closingName);
|
|
319
337
|
if (capitalizedName) {
|
|
320
338
|
tokens.push({ source: closingName, generated: capitalizedName });
|
|
321
339
|
} else {
|
|
@@ -1101,25 +1119,25 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
1101
1119
|
|
|
1102
1120
|
// Process each token in order
|
|
1103
1121
|
for (const token of tokens) {
|
|
1104
|
-
let
|
|
1122
|
+
let source_text, generated_text;
|
|
1105
1123
|
|
|
1106
1124
|
if (typeof token === 'string') {
|
|
1107
|
-
|
|
1108
|
-
|
|
1125
|
+
source_text = token;
|
|
1126
|
+
generated_text = token;
|
|
1109
1127
|
} else {
|
|
1110
1128
|
// Token with different source and generated names
|
|
1111
|
-
|
|
1112
|
-
|
|
1129
|
+
source_text = token.source;
|
|
1130
|
+
generated_text = token.generated;
|
|
1113
1131
|
}
|
|
1114
1132
|
|
|
1115
|
-
const
|
|
1116
|
-
const
|
|
1133
|
+
const source_pos = find_in_source(source_text);
|
|
1134
|
+
const gen_pos = find_in_generated(generated_text);
|
|
1117
1135
|
|
|
1118
|
-
if (
|
|
1136
|
+
if (source_pos !== null && gen_pos !== null) {
|
|
1119
1137
|
mappings.push({
|
|
1120
|
-
sourceOffsets: [
|
|
1121
|
-
generatedOffsets: [
|
|
1122
|
-
lengths: [
|
|
1138
|
+
sourceOffsets: [source_pos],
|
|
1139
|
+
generatedOffsets: [gen_pos],
|
|
1140
|
+
lengths: [source_text.length],
|
|
1123
1141
|
data: mapping_data,
|
|
1124
1142
|
});
|
|
1125
1143
|
}
|
|
@@ -1128,17 +1146,46 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
1128
1146
|
// Add full-statement mappings for import declarations
|
|
1129
1147
|
// TypeScript reports unused import diagnostics covering the entire import statement
|
|
1130
1148
|
// Use verification-only mapping to avoid duplicate hover/completion
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1149
|
+
|
|
1150
|
+
// Use the source import map from the original AST (before transformation)
|
|
1151
|
+
// The __volar_id property is preserved through transformation via object spread
|
|
1152
|
+
if (source_import_map && import_declarations.length > 0) {
|
|
1153
|
+
// We need to find where each import appears in the generated code
|
|
1154
|
+
// Search for "import" keywords and match them to our collected imports
|
|
1155
|
+
let gen_search_index = 0;
|
|
1156
|
+
|
|
1157
|
+
for (const import_decl of import_declarations) {
|
|
1158
|
+
// Look up the source position using the __volar_id
|
|
1159
|
+
const source_range = source_import_map.get(import_decl.id);
|
|
1160
|
+
if (!source_range) continue; // Skip if we don't have source info for this ID
|
|
1161
|
+
|
|
1162
|
+
// Find this import statement in the generated code
|
|
1163
|
+
// Search for "import " starting from our last position
|
|
1164
|
+
const import_keyword_index = generated_code.indexOf('import ', gen_search_index);
|
|
1165
|
+
if (import_keyword_index === -1) continue; // Couldn't find it
|
|
1166
|
+
|
|
1167
|
+
// Find the semicolon or end of line for this import
|
|
1168
|
+
let gen_end = generated_code.indexOf(';', import_keyword_index);
|
|
1169
|
+
if (gen_end === -1) gen_end = generated_code.indexOf('\n', import_keyword_index);
|
|
1170
|
+
if (gen_end === -1) gen_end = generated_code.length;
|
|
1171
|
+
else gen_end += 1; // Include the semicolon
|
|
1172
|
+
|
|
1173
|
+
const get_start = import_keyword_index;
|
|
1174
|
+
gen_search_index = gen_end; // Next search starts after this import
|
|
1175
|
+
|
|
1176
|
+
const source_length = source_range.end - source_range.start;
|
|
1177
|
+
const get_length = gen_end - get_start;
|
|
1178
|
+
|
|
1179
|
+
mappings.push({
|
|
1180
|
+
sourceOffsets: [source_range.start],
|
|
1181
|
+
generatedOffsets: [get_start],
|
|
1182
|
+
lengths: [Math.min(source_length, get_length)],
|
|
1183
|
+
data: {
|
|
1184
|
+
// only verification (diagnostics) to avoid duplicate hover/completion
|
|
1185
|
+
verification: true
|
|
1186
|
+
},
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1142
1189
|
}
|
|
1143
1190
|
|
|
1144
1191
|
// Sort mappings by source offset
|
|
@@ -525,18 +525,28 @@ const visitors = {
|
|
|
525
525
|
return;
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
context.
|
|
532
|
-
|
|
533
|
-
transform_body(node.consequent.body, {
|
|
534
|
-
...context,
|
|
535
|
-
state: { ...context.state, scope: context.state.scopes.get(node.consequent) },
|
|
536
|
-
}),
|
|
537
|
-
),
|
|
538
|
-
),
|
|
528
|
+
const consequent = b.block(
|
|
529
|
+
transform_body(node.consequent.body, {
|
|
530
|
+
...context,
|
|
531
|
+
state: { ...context.state, scope: context.state.scopes.get(node.consequent) },
|
|
532
|
+
}),
|
|
539
533
|
);
|
|
534
|
+
|
|
535
|
+
let alternate = null;
|
|
536
|
+
if (node.alternate) {
|
|
537
|
+
const alternate_scope = context.state.scopes.get(node.alternate) || context.state.scope;
|
|
538
|
+
const alternate_body_nodes =
|
|
539
|
+
node.alternate.type === 'IfStatement' ? [node.alternate] : node.alternate.body;
|
|
540
|
+
|
|
541
|
+
alternate = b.block(
|
|
542
|
+
transform_body(alternate_body_nodes, {
|
|
543
|
+
...context,
|
|
544
|
+
state: { ...context.state, scope: alternate_scope },
|
|
545
|
+
}),
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
context.state.init.push(b.if(context.visit(node.test), consequent, alternate));
|
|
540
550
|
},
|
|
541
551
|
|
|
542
552
|
Identifier(node, context) {
|
|
@@ -78,6 +78,22 @@ export interface TrackedObjectExpression extends Omit<ObjectExpression, 'type'>
|
|
|
78
78
|
properties: (Property | SpreadElement)[];
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Tracked Map expression node
|
|
83
|
+
*/
|
|
84
|
+
export interface TrackedMapExpression extends Omit<Node, 'type'> {
|
|
85
|
+
type: 'TrackedMapExpression';
|
|
86
|
+
arguments: (Expression | SpreadElement)[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Tracked Set expression node
|
|
91
|
+
*/
|
|
92
|
+
export interface TrackedSetExpression extends Omit<Node, 'type'> {
|
|
93
|
+
type: 'TrackedSetExpression';
|
|
94
|
+
arguments: (Expression | SpreadElement)[];
|
|
95
|
+
}
|
|
96
|
+
|
|
81
97
|
/**
|
|
82
98
|
* Ripple component node
|
|
83
99
|
*/
|