ripple 0.2.92 → 0.2.94

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 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.92",
6
+ "version": "0.2.94",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -39,10 +39,11 @@ export function compile(source, filename, options = {}) {
39
39
  * @returns {object} Volar mappings object
40
40
  */
41
41
  export function compile_to_volar_mappings(source, filename) {
42
- // Parse and transform to get the esrap sourcemap
42
+ // Parse and transform
43
43
  const ast = parse_module(source);
44
44
  const analysis = analyze(ast, filename);
45
45
  const transformed = transform_client(filename, source, analysis, true);
46
46
 
47
- return convert_source_map_to_mappings(transformed.js.map, source, transformed.js.code);
47
+ // 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);
48
49
  }
@@ -1404,12 +1404,7 @@ function transform_ts_child(node, context) {
1404
1404
  if (is_dom_element) {
1405
1405
  children.push(b.jsx_expression_container(b.call(thunk)));
1406
1406
  } else {
1407
- const children_name = context.state.scope.generate('component');
1408
- const children_id = b.id(children_name);
1409
- const jsx_id = b.jsx_id('children');
1410
- jsx_id.loc = node.id.loc;
1411
- state.init.push(b.const(children_id, thunk));
1412
- attributes.push(b.jsx_attribute(jsx_id, b.jsx_expression_container(children_id)));
1407
+ attributes.push(b.jsx_attribute(b.jsx_id('children'), b.jsx_expression_container(thunk)));
1413
1408
  }
1414
1409
  }
1415
1410
 
@@ -1463,12 +1458,14 @@ function transform_ts_child(node, context) {
1463
1458
  state.init.push(b.if(visit(node.test), consequent, alternate));
1464
1459
  } else if (node.type === 'ForOfStatement') {
1465
1460
  const body_scope = context.state.scopes.get(node.body);
1466
- const body = b.block(
1467
- transform_body(node.body.body, {
1468
- ...context,
1469
- state: { ...context.state, scope: body_scope },
1470
- }),
1471
- );
1461
+ const block_body = transform_body(node.body.body, {
1462
+ ...context,
1463
+ state: { ...context.state, scope: body_scope },
1464
+ });
1465
+ if (node.index) {
1466
+ block_body.unshift(b.let(visit(node.index), b.literal(0)));
1467
+ }
1468
+ const body = b.block(block_body);
1472
1469
 
1473
1470
  state.init.push(b.for_of(visit(node.left), visit(node.right), body, node.await));
1474
1471
  } else if (node.type === 'TryStatement') {
@@ -1,4 +1,4 @@
1
- import { decode } from '@jridgewell/sourcemap-codec';
1
+ import { walk } from 'zimmerframe';
2
2
 
3
3
  export const mapping_data = {
4
4
  verification: true,
@@ -8,306 +8,483 @@ export const mapping_data = {
8
8
  };
9
9
 
10
10
  /**
11
- * Helper to find a meaningful token boundary by looking for word boundaries,
12
- * punctuation, or whitespace
13
- * @param {string} text
14
- * @param {number} start
15
- * @param {number} direction
16
- */
17
- function findTokenBoundary(text, start, direction = 1) {
18
- if (start < 0 || start >= text.length) return start;
19
-
20
- let pos = start;
21
- /** @param {string} c */
22
- const isAlphaNum = (c) => /[a-zA-Z0-9_$]/.test(c);
23
-
24
- // If we're at whitespace or punctuation, find the next meaningful character
25
- while (pos >= 0 && pos < text.length && /\s/.test(text[pos])) {
26
- pos += direction;
27
- }
28
-
29
- if (pos < 0 || pos >= text.length) return start;
30
-
31
- // If we're in the middle of a word/identifier, find the boundary
32
- if (isAlphaNum(text[pos])) {
33
- if (direction > 0) {
34
- while (pos < text.length && isAlphaNum(text[pos])) pos++;
35
- } else {
36
- while (pos >= 0 && isAlphaNum(text[pos])) pos--;
37
- pos++; // Adjust back to start of token
38
- }
39
- } else {
40
- // For punctuation, just move one character in the given direction
41
- pos += direction;
42
- }
43
-
44
- return Math.max(0, Math.min(text.length, pos));
45
- }
46
-
47
- /**
48
- * Check if source and generated content are meaningfully similar
49
- * @param {string} sourceContent
50
- * @param {string} generatedContent
51
- */
52
- function isValidMapping(sourceContent, generatedContent) {
53
- // Remove whitespace for comparison
54
- const cleanSource = sourceContent.replace(/\s+/g, '');
55
- const cleanGenerated = generatedContent.replace(/\s+/g, '');
56
-
57
- // If either is empty, skip
58
- if (!cleanSource || !cleanGenerated) return false;
59
-
60
- // Skip obvious template transformations that don't make sense to map
61
- const templateTransforms = [
62
- /^\{.*\}$/, // Curly brace expressions
63
- /^<.*>$/, // HTML tags
64
- /^\(\(\)\s*=>\s*\{$/, // Generated function wrappers
65
- /^\}\)\(\)\}$/, // Generated function closures
66
- ];
67
-
68
- for (const transform of templateTransforms) {
69
- if (transform.test(cleanSource) || transform.test(cleanGenerated)) {
70
- return false;
71
- }
72
- }
73
-
74
- // Check if content is similar (exact match, or generated contains source)
75
- if (cleanSource === cleanGenerated) return true;
76
- if (cleanGenerated.includes(cleanSource)) return true;
77
- if (cleanSource.includes(cleanGenerated) && cleanGenerated.length > 2) return true;
78
-
79
- // Special handling for ref callback parameters and types in createRefKey context
80
- if (sourceContent.match(/\w+:\s*\w+/) && generatedContent.match(/\w+:\s*\w+/)) {
81
- // This looks like a parameter with type annotation, allow mapping
82
- return true;
83
- }
84
-
85
- // Allow mapping of identifiers that appear in both source and generated
86
- if (sourceContent.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/) &&
87
- generatedContent.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/) &&
88
- sourceContent === generatedContent) {
89
- return true;
90
- }
91
-
92
- return false;
93
- }
94
-
95
- /**
96
- * Convert esrap SourceMap to Volar mappings
97
- * @param {{ mappings: string }} source_map
98
- * @param {string} source
99
- * @param {string} generated_code
11
+ * Create Volar mappings by walking the transformed AST
12
+ * @param {any} ast - The transformed AST
13
+ * @param {string} source - Original source code
14
+ * @param {string} generated_code - Generated code from esrap
100
15
  * @returns {object}
101
16
  */
102
- export function convert_source_map_to_mappings(source_map, source, generated_code) {
17
+ export function convert_source_map_to_mappings(ast, source, generated_code) {
103
18
  /** @type {Array<{sourceOffsets: number[], generatedOffsets: number[], lengths: number[], data: any}>} */
104
19
  const mappings = [];
105
20
 
106
- // Decode the VLQ mappings from esrap
107
- const decoded_mappings = decode(source_map.mappings);
108
-
109
- let generated_offset = 0;
110
- const generated_lines = generated_code.split('\n');
111
- const source_lines = source.split('\n');
21
+ // Maintain indices that walk through source and generated code
22
+ let sourceIndex = 0;
23
+ let generatedIndex = 0;
112
24
 
113
- // Process each line of generated code
114
- for (let generated_line = 0; generated_line < generated_lines.length; generated_line++) {
115
- const line = generated_lines[generated_line];
116
- const line_mappings = decoded_mappings[generated_line] || [];
117
-
118
- // Process mappings for this line
119
- for (const mapping of line_mappings) {
120
- const [generated_column, source_file_index, source_line, source_column] = mapping;
25
+ /**
26
+ * Check if character is a word boundary (not alphanumeric or underscore)
27
+ * @param {string} char
28
+ * @returns {boolean}
29
+ */
30
+ const isWordBoundary = (char) => {
31
+ return char === undefined || !/[a-zA-Z0-9_$]/.test(char);
32
+ };
121
33
 
122
- // Skip mappings without source information
123
- if (source_file_index == null || source_line == null || source_column == null) {
124
- continue;
34
+ /**
35
+ * Find text in source string, searching character by character from sourceIndex
36
+ * @param {string} text - Text to find
37
+ * @returns {number|null} - Source position or null
38
+ */
39
+ const findInSource = (text) => {
40
+ for (let i = sourceIndex; i <= source.length - text.length; i++) {
41
+ let match = true;
42
+ for (let j = 0; j < text.length; j++) {
43
+ if (source[i + j] !== text[j]) {
44
+ match = false;
45
+ break;
46
+ }
125
47
  }
126
-
127
- // Calculate source offset
128
- let source_offset = 0;
129
- for (let i = 0; i < Math.min(source_line, source_lines.length - 1); i++) {
130
- source_offset += source_lines[i].length + 1; // +1 for newline
48
+ if (match) {
49
+ // Check word boundaries for identifier-like tokens
50
+ const isIdentifierLike = /^[a-zA-Z_$]/.test(text);
51
+ if (isIdentifierLike) {
52
+ const charBefore = source[i - 1];
53
+ const charAfter = source[i + text.length];
54
+ if (!isWordBoundary(charBefore) || !isWordBoundary(charAfter)) {
55
+ continue; // Not a whole word match, keep searching
56
+ }
57
+ }
58
+
59
+ sourceIndex = i + text.length;
60
+ return i;
131
61
  }
132
- source_offset += source_column;
133
-
134
- // Calculate generated offset
135
- const current_generated_offset = generated_offset + generated_column;
62
+ }
63
+ return null;
64
+ };
136
65
 
137
- // Find meaningful token boundaries for source content
138
- const source_token_end = findTokenBoundary(source, source_offset, 1);
139
- const source_token_start = findTokenBoundary(source, source_offset, -1);
140
-
141
- // Find meaningful token boundaries for generated content
142
- const generated_token_end = findTokenBoundary(generated_code, current_generated_offset, 1);
143
- const generated_token_start = findTokenBoundary(generated_code, current_generated_offset, -1);
144
-
145
- // Extract potential source content (prefer forward boundary but try both directions)
146
- let best_source_content = source.substring(source_offset, source_token_end);
147
- let best_generated_content = generated_code.substring(current_generated_offset, generated_token_end);
148
-
149
- // Try different segment boundaries to find the best match
150
- const candidates = [
151
- // Forward boundaries
152
- {
153
- source: source.substring(source_offset, source_token_end),
154
- generated: generated_code.substring(current_generated_offset, generated_token_end)
155
- },
156
- // Backward boundaries
157
- {
158
- source: source.substring(source_token_start, source_offset + 1),
159
- generated: generated_code.substring(generated_token_start, current_generated_offset + 1)
160
- },
161
- // Single character
162
- {
163
- source: source.charAt(source_offset),
164
- generated: generated_code.charAt(current_generated_offset)
165
- },
166
- // Try to find exact matches in nearby content
167
- ];
168
-
169
- // Look for the best candidate match
170
- let best_match = null;
171
- for (const candidate of candidates) {
172
- if (isValidMapping(candidate.source, candidate.generated)) {
173
- best_match = candidate;
66
+ /**
67
+ * Find text in generated code, searching character by character from generatedIndex
68
+ * @param {string} text - Text to find
69
+ * @returns {number|null} - Generated position or null
70
+ */
71
+ const findInGenerated = (text) => {
72
+ for (let i = generatedIndex; i <= generated_code.length - text.length; i++) {
73
+ let match = true;
74
+ for (let j = 0; j < text.length; j++) {
75
+ if (generated_code[i + j] !== text[j]) {
76
+ match = false;
174
77
  break;
175
78
  }
176
79
  }
177
-
178
- // If no good match found, try extracting identifiers/keywords
179
- if (!best_match) {
180
- const sourceIdMatch = source.substring(source_offset).match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/);
181
- const generatedIdMatch = generated_code.substring(current_generated_offset).match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/);
182
-
183
- if (sourceIdMatch && generatedIdMatch && sourceIdMatch[0] === generatedIdMatch[0]) {
184
- best_match = {
185
- source: sourceIdMatch[0],
186
- generated: generatedIdMatch[0]
187
- };
80
+ if (match) {
81
+ // Check word boundaries for identifier-like tokens
82
+ const isIdentifierLike = /^[a-zA-Z_$]/.test(text);
83
+ if (isIdentifierLike) {
84
+ const charBefore = generated_code[i - 1];
85
+ const charAfter = generated_code[i + text.length];
86
+ if (!isWordBoundary(charBefore) || !isWordBoundary(charAfter)) {
87
+ continue; // Not a whole word match, keep searching
88
+ }
188
89
  }
189
- }
190
-
191
- // Special handling for type annotations in ref callbacks
192
- if (!best_match) {
193
- // Look for type annotations like "HTMLButtonElement"
194
- const sourceTypeMatch = source.substring(source_offset).match(/^[A-Z][a-zA-Z0-9]*(?:Element|Type|Interface)?/);
195
- const generatedTypeMatch = generated_code.substring(current_generated_offset).match(/^[A-Z][a-zA-Z0-9]*(?:Element|Type|Interface)?/);
196
90
 
197
- if (sourceTypeMatch && generatedTypeMatch && sourceTypeMatch[0] === generatedTypeMatch[0]) {
198
- best_match = {
199
- source: sourceTypeMatch[0],
200
- generated: generatedTypeMatch[0]
201
- };
202
- }
91
+ generatedIndex = i + text.length;
92
+ return i;
203
93
  }
204
-
205
- // Handle special cases for Ripple keywords that might not have generated equivalents
206
- if (!best_match || best_match.source.length === 0) {
207
- continue;
208
- }
209
-
210
- // Special handling for Ripple-specific syntax that may be omitted in generated code
211
- const sourceAtOffset = source.substring(source_offset, source_offset + 10);
212
- if (sourceAtOffset.includes('index ')) {
213
- // For the 'index' keyword, create a mapping even if there's no generated equivalent
214
- const indexMatch = sourceAtOffset.match(/index\s+/);
215
- if (indexMatch) {
216
- best_match = {
217
- source: indexMatch[0].trim(),
218
- generated: '' // Empty generated content for keywords that are transformed away
219
- };
220
- }
221
- }
222
-
223
- // Skip if we still don't have a valid source match
224
- if (!best_match || best_match.source.length === 0) {
225
- continue;
226
94
  }
227
-
228
- // Handle special ref syntax mapping for createRefKey() pattern
229
- const sourceAtRefOffset = source.substring(Math.max(0, source_offset - 20), source_offset + 20);
230
- const generatedAtRefOffset = generated_code.substring(Math.max(0, current_generated_offset - 20), current_generated_offset + 20);
231
-
232
- // Check if we're dealing with ref callback syntax in source and createRefKey in generated
233
- if (sourceAtRefOffset.includes('{ref ') && generatedAtRefOffset.includes('createRefKey')) {
234
- // Look for the ref callback pattern in source: {ref (param: Type) => { ... }}
235
- const refMatch = source.substring(source_offset - 50, source_offset + 50).match(/\{ref\s*\(([^)]+)\)\s*=>/);
236
- if (refMatch) {
237
- const paramMatch = refMatch[1].match(/(\w+):\s*(\w+)/);
238
- if (paramMatch) {
239
- const paramName = paramMatch[1];
240
- const typeName = paramMatch[2];
241
-
242
- // Map the parameter name to the generated callback parameter
243
- if (best_match.source === paramName || best_match.source.includes(paramName)) {
244
- // This is a ref callback parameter, allow the mapping
95
+ return null;
96
+ };
97
+
98
+ // Collect text tokens from AST nodes
99
+ /** @type {string[]} */
100
+ const tokens = [];
101
+
102
+ walk(ast, null, {
103
+ _(node, { next, visit }) {
104
+ // Collect key node types: Identifiers, Literals, and JSX Elements
105
+ if (node.type === 'Identifier' && node.name) {
106
+ tokens.push(node.name);
107
+ } else if (node.type === 'JSXIdentifier' && node.name) {
108
+ tokens.push(node.name);
109
+ } else if (node.type === 'Literal' && node.raw) {
110
+ tokens.push(node.raw);
111
+ } else if (node.type === 'ImportDeclaration') {
112
+ // Visit specifiers in source order
113
+ if (node.specifiers) {
114
+ for (const specifier of node.specifiers) {
115
+ visit(specifier);
116
+ }
117
+ }
118
+ // Skip source (just a string literal)
119
+ return;
120
+ } else if (node.type === 'ImportSpecifier') {
121
+ // If local and imported are the same, only visit local to avoid duplicates
122
+ // Otherwise visit both in order
123
+ if (node.imported && node.local && node.imported.name !== node.local.name) {
124
+ visit(node.imported);
125
+ visit(node.local);
126
+ } else if (node.local) {
127
+ visit(node.local);
128
+ }
129
+ return;
130
+ } else if (node.type === 'ImportDefaultSpecifier' || node.type === 'ImportNamespaceSpecifier') {
131
+ // Just visit local
132
+ if (node.local) {
133
+ visit(node.local);
134
+ }
135
+ return;
136
+ } else if (node.type === 'ExportNamedDeclaration') {
137
+ // Visit in source order: declaration, specifiers
138
+ if (node.declaration) {
139
+ visit(node.declaration);
140
+ }
141
+ if (node.specifiers) {
142
+ for (const specifier of node.specifiers) {
143
+ visit(specifier);
245
144
  }
246
- // Map the type annotation
247
- else if (best_match.source === typeName || best_match.source.includes(typeName)) {
248
- // This is a type annotation in ref callback, allow the mapping
145
+ }
146
+ return;
147
+ } else if (node.type === 'ExportDefaultDeclaration') {
148
+ // Visit the declaration
149
+ if (node.declaration) {
150
+ visit(node.declaration);
151
+ }
152
+ return;
153
+ } else if (node.type === 'ExportAllDeclaration') {
154
+ // Nothing to visit (just source string)
155
+ return;
156
+ } else if (node.type === 'JSXElement') {
157
+ // Manually visit in source order: opening element, children, closing element
158
+
159
+ // 1. Visit opening element (name and attributes)
160
+ if (node.openingElement) {
161
+ visit(node.openingElement);
162
+ }
163
+
164
+ // 2. Visit children in order
165
+ if (node.children) {
166
+ for (const child of node.children) {
167
+ visit(child);
249
168
  }
250
169
  }
170
+
171
+ // 3. Push closing tag name (not visited by AST walker)
172
+ if (!node.openingElement?.selfClosing && node.closingElement?.name?.type === 'JSXIdentifier') {
173
+ tokens.push(node.closingElement.name.name);
174
+ }
175
+
176
+ return;
177
+ } else if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
178
+ // Visit in source order: id, params, body
179
+ if (node.id) {
180
+ visit(node.id);
181
+ }
182
+ if (node.params) {
183
+ for (const param of node.params) {
184
+ visit(param);
185
+ }
186
+ }
187
+ if (node.body) {
188
+ visit(node.body);
189
+ }
190
+ return;
191
+ } else if (node.type === 'VariableDeclaration') {
192
+ // Visit declarators in order
193
+ if (node.declarations) {
194
+ for (const declarator of node.declarations) {
195
+ visit(declarator);
196
+ }
197
+ }
198
+ return;
199
+ } else if (node.type === 'VariableDeclarator') {
200
+ // Visit in source order: id, init
201
+ if (node.id) {
202
+ visit(node.id);
203
+ }
204
+ if (node.init) {
205
+ visit(node.init);
206
+ }
207
+ return;
208
+ } else if (node.type === 'IfStatement') {
209
+ // Visit in source order: test, consequent, alternate
210
+ if (node.test) {
211
+ visit(node.test);
212
+ }
213
+ if (node.consequent) {
214
+ visit(node.consequent);
215
+ }
216
+ if (node.alternate) {
217
+ visit(node.alternate);
218
+ }
219
+ return;
220
+ } else if (node.type === 'ForStatement') {
221
+ // Visit in source order: init, test, update, body
222
+ if (node.init) {
223
+ visit(node.init);
224
+ }
225
+ if (node.test) {
226
+ visit(node.test);
227
+ }
228
+ if (node.update) {
229
+ visit(node.update);
230
+ }
231
+ if (node.body) {
232
+ visit(node.body);
233
+ }
234
+ return;
235
+ } else if (node.type === 'ForOfStatement' || node.type === 'ForInStatement') {
236
+ // Visit in source order: left, right, index (Ripple-specific), body
237
+ if (node.left) {
238
+ visit(node.left);
239
+ }
240
+ if (node.right) {
241
+ visit(node.right);
242
+ }
243
+ // Ripple-specific: index variable
244
+ if (node.index) {
245
+ visit(node.index);
246
+ }
247
+ if (node.body) {
248
+ visit(node.body);
249
+ }
250
+ return;
251
+ } else if (node.type === 'WhileStatement' || node.type === 'DoWhileStatement') {
252
+ // Visit in source order: test, body (while) or body, test (do-while)
253
+ if (node.type === 'WhileStatement') {
254
+ if (node.test) {
255
+ visit(node.test);
256
+ }
257
+ if (node.body) {
258
+ visit(node.body);
259
+ }
260
+ } else {
261
+ if (node.body) {
262
+ visit(node.body);
263
+ }
264
+ if (node.test) {
265
+ visit(node.test);
266
+ }
267
+ }
268
+ return;
269
+ } else if (node.type === 'TryStatement') {
270
+ // Visit in source order: block, handler, finalizer
271
+ if (node.block) {
272
+ visit(node.block);
273
+ }
274
+ if (node.handler) {
275
+ visit(node.handler);
276
+ }
277
+ if (node.finalizer) {
278
+ visit(node.finalizer);
279
+ }
280
+ return;
281
+ } else if (node.type === 'CatchClause') {
282
+ // Visit in source order: param, body
283
+ if (node.param) {
284
+ visit(node.param);
285
+ }
286
+ if (node.body) {
287
+ visit(node.body);
288
+ }
289
+ return;
290
+ } else if (node.type === 'CallExpression' || node.type === 'NewExpression') {
291
+ // Visit in source order: callee, arguments
292
+ if (node.callee) {
293
+ visit(node.callee);
294
+ }
295
+ if (node.arguments) {
296
+ for (const arg of node.arguments) {
297
+ visit(arg);
298
+ }
299
+ }
300
+ return;
301
+ } else if (node.type === 'LogicalExpression' || node.type === 'BinaryExpression') {
302
+ // Visit in source order: left, right
303
+ if (node.left) {
304
+ visit(node.left);
305
+ }
306
+ if (node.right) {
307
+ visit(node.right);
308
+ }
309
+ return;
310
+ } else if (node.type === 'MemberExpression') {
311
+ // Visit in source order: object, property
312
+ if (node.object) {
313
+ visit(node.object);
314
+ }
315
+ if (!node.computed && node.property) {
316
+ visit(node.property);
317
+ }
318
+ return;
319
+ } else if (node.type === 'AssignmentExpression' || node.type === 'AssignmentPattern') {
320
+ // Visit in source order: left, right
321
+ if (node.left) {
322
+ visit(node.left);
323
+ }
324
+ if (node.right) {
325
+ visit(node.right);
326
+ }
327
+ return;
328
+ } else if (node.type === 'ObjectExpression' || node.type === 'ObjectPattern') {
329
+ // Visit properties in order
330
+ if (node.properties) {
331
+ for (const prop of node.properties) {
332
+ visit(prop);
333
+ }
334
+ }
335
+ return;
336
+ } else if (node.type === 'Property') {
337
+ // Visit in source order: key, value
338
+ if (node.key) {
339
+ visit(node.key);
340
+ }
341
+ if (node.value) {
342
+ visit(node.value);
343
+ }
344
+ return;
345
+ } else if (node.type === 'ArrayExpression' || node.type === 'ArrayPattern') {
346
+ // Visit elements in order
347
+ if (node.elements) {
348
+ for (const element of node.elements) {
349
+ if (element) visit(element);
350
+ }
351
+ }
352
+ return;
353
+ } else if (node.type === 'ConditionalExpression') {
354
+ // Visit in source order: test, consequent, alternate
355
+ if (node.test) {
356
+ visit(node.test);
357
+ }
358
+ if (node.consequent) {
359
+ visit(node.consequent);
360
+ }
361
+ if (node.alternate) {
362
+ visit(node.alternate);
363
+ }
364
+ return;
365
+ } else if (node.type === 'UnaryExpression' || node.type === 'UpdateExpression') {
366
+ // Visit argument
367
+ if (node.argument) {
368
+ visit(node.argument);
369
+ }
370
+ return;
371
+ } else if (node.type === 'TemplateLiteral') {
372
+ // Visit quasis and expressions in order
373
+ for (let i = 0; i < node.quasis.length; i++) {
374
+ if (node.quasis[i]) {
375
+ visit(node.quasis[i]);
376
+ }
377
+ if (i < node.expressions.length && node.expressions[i]) {
378
+ visit(node.expressions[i]);
379
+ }
380
+ }
381
+ return;
382
+ } else if (node.type === 'TaggedTemplateExpression') {
383
+ // Visit in source order: tag, quasi
384
+ if (node.tag) {
385
+ visit(node.tag);
386
+ }
387
+ if (node.quasi) {
388
+ visit(node.quasi);
389
+ }
390
+ return;
391
+ } else if (node.type === 'ReturnStatement' || node.type === 'ThrowStatement') {
392
+ // Visit argument
393
+ if (node.argument) {
394
+ visit(node.argument);
395
+ }
396
+ return;
397
+ } else if (node.type === 'ExpressionStatement') {
398
+ // Visit expression
399
+ if (node.expression) {
400
+ visit(node.expression);
401
+ }
402
+ return;
403
+ } else if (node.type === 'BlockStatement' || node.type === 'Program') {
404
+ // Visit body statements in order
405
+ if (node.body) {
406
+ for (const statement of node.body) {
407
+ visit(statement);
408
+ }
409
+ }
410
+ return;
411
+ } else if (node.type === 'SwitchStatement') {
412
+ // Visit in source order: discriminant, cases
413
+ if (node.discriminant) {
414
+ visit(node.discriminant);
415
+ }
416
+ if (node.cases) {
417
+ for (const caseNode of node.cases) {
418
+ visit(caseNode);
419
+ }
420
+ }
421
+ return;
422
+ } else if (node.type === 'SwitchCase') {
423
+ // Visit in source order: test, consequent
424
+ if (node.test) {
425
+ visit(node.test);
426
+ }
427
+ if (node.consequent) {
428
+ for (const statement of node.consequent) {
429
+ visit(statement);
430
+ }
431
+ }
432
+ return;
433
+ } else if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
434
+ // Visit in source order: id, superClass, body
435
+ if (node.id) {
436
+ visit(node.id);
437
+ }
438
+ if (node.superClass) {
439
+ visit(node.superClass);
440
+ }
441
+ if (node.body) {
442
+ visit(node.body);
443
+ }
444
+ return;
445
+ } else if (node.type === 'ClassBody') {
446
+ // Visit body in order
447
+ if (node.body) {
448
+ for (const member of node.body) {
449
+ visit(member);
450
+ }
451
+ }
452
+ return;
453
+ } else if (node.type === 'MethodDefinition') {
454
+ // Visit in source order: key, value
455
+ if (node.key) {
456
+ visit(node.key);
457
+ }
458
+ if (node.value) {
459
+ visit(node.value);
460
+ }
461
+ return;
251
462
  }
463
+
464
+ next();
252
465
  }
253
-
254
- // Skip mappings for complex RefAttribute syntax to avoid overlapping sourcemaps,
255
- // but allow mappings that are part of the createRefKey pattern
256
- if (best_match.source.includes('{ref ') && best_match.source.length > 10 &&
257
- !generatedAtRefOffset.includes('createRefKey')) {
258
- // Skip complex ref expressions like '{ref (node) => { ... }}' only if not using createRefKey
259
- continue;
260
- }
261
-
262
- // Allow simple 'ref' keyword mappings for IntelliSense
263
- if (best_match.source.trim() === 'ref' && best_match.generated.length === 0) {
264
- // This is just the ref keyword, allow it for syntax support
265
- // but map it to current position since there's no generated equivalent
266
- }
267
-
268
- // Calculate actual offsets and lengths for the best match
269
- let actual_source_offset, actual_generated_offset;
270
-
271
- if (best_match.generated.length > 0) {
272
- actual_source_offset = source.indexOf(best_match.source, source_offset - best_match.source.length);
273
- actual_generated_offset = generated_code.indexOf(best_match.generated, current_generated_offset - best_match.generated.length);
274
- } else {
275
- // For keywords with no generated equivalent, use the exact source position
276
- actual_source_offset = source_offset;
277
- actual_generated_offset = current_generated_offset; // Map to current position in generated code
278
- }
279
-
280
- // Use the match we found, but fall back to original positions if indexOf fails
281
- const final_source_offset = actual_source_offset !== -1 ? actual_source_offset : source_offset;
282
- const final_generated_offset = actual_generated_offset !== -1 ? actual_generated_offset : current_generated_offset; // Avoid duplicate mappings by checking if we already have this exact mapping
283
- const isDuplicate = mappings.some(existing =>
284
- existing.sourceOffsets[0] === final_source_offset &&
285
- existing.generatedOffsets[0] === final_generated_offset &&
286
- existing.lengths[0] === best_match.source.length
287
- );
288
-
289
- if (!isDuplicate) {
290
- mappings.push({
291
- sourceOffsets: [final_source_offset],
292
- generatedOffsets: [final_generated_offset],
293
- lengths: [best_match.source.length],
294
- data: mapping_data,
295
- });
296
- }
297
- }
466
+ });
298
467
 
299
- // Add line length + 1 for newline (except for last line)
300
- generated_offset += line.length;
301
- if (generated_line < generated_lines.length - 1) {
302
- generated_offset += 1; // newline character
468
+ // Process each token in order
469
+ for (const text of tokens) {
470
+ const sourcePos = findInSource(text);
471
+ const genPos = findInGenerated(text);
472
+
473
+ if (sourcePos !== null && genPos !== null) {
474
+ mappings.push({
475
+ sourceOffsets: [sourcePos],
476
+ generatedOffsets: [genPos],
477
+ lengths: [text.length],
478
+ data: mapping_data,
479
+ });
303
480
  }
304
481
  }
305
482
 
306
- // Sort mappings by source offset for better organization
483
+ // Sort mappings by source offset
307
484
  mappings.sort((a, b) => a.sourceOffsets[0] - b.sourceOffsets[0]);
308
485
 
309
486
  return {
310
487
  code: generated_code,
311
488
  mappings,
312
489
  };
313
- }
490
+ }
package/types/index.d.ts CHANGED
@@ -168,3 +168,5 @@ export class SvelteDate extends Date {
168
168
  constructor(...params: any[]);
169
169
  #private;
170
170
  }
171
+
172
+ export function Portal<V = HTMLElement>({ target, children: Component }: { target: V }): void;