ripple 0.2.92 → 0.2.93

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.93",
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
 
@@ -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,450 @@ 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');
112
-
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] || [];
21
+ // Maintain indices that walk through source and generated code
22
+ let sourceIndex = 0;
23
+ let generatedIndex = 0;
117
24
 
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;
121
-
122
- // Skip mappings without source information
123
- if (source_file_index == null || source_line == null || source_column == null) {
124
- continue;
25
+ /**
26
+ * Find text in source string, searching character by character from sourceIndex
27
+ * @param {string} text - Text to find
28
+ * @returns {number|null} - Source position or null
29
+ */
30
+ const findInSource = (text) => {
31
+ for (let i = sourceIndex; i <= source.length - text.length; i++) {
32
+ let match = true;
33
+ for (let j = 0; j < text.length; j++) {
34
+ if (source[i + j] !== text[j]) {
35
+ match = false;
36
+ break;
37
+ }
125
38
  }
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
39
+ if (match) {
40
+ sourceIndex = i + text.length;
41
+ return i;
131
42
  }
132
- source_offset += source_column;
133
-
134
- // Calculate generated offset
135
- const current_generated_offset = generated_offset + generated_column;
43
+ }
44
+ return null;
45
+ };
136
46
 
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;
47
+ /**
48
+ * Find text in generated code, searching character by character from generatedIndex
49
+ * @param {string} text - Text to find
50
+ * @returns {number|null} - Generated position or null
51
+ */
52
+ const findInGenerated = (text) => {
53
+ for (let i = generatedIndex; i <= generated_code.length - text.length; i++) {
54
+ let match = true;
55
+ for (let j = 0; j < text.length; j++) {
56
+ if (generated_code[i + j] !== text[j]) {
57
+ match = false;
174
58
  break;
175
59
  }
176
60
  }
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_$]*/);
61
+ if (match) {
62
+ generatedIndex = i + text.length;
63
+ return i;
64
+ }
65
+ }
66
+ return null;
67
+ };
68
+
69
+ // Collect text tokens from AST nodes
70
+ /** @type {string[]} */
71
+ const tokens = [];
72
+
73
+ walk(ast, null, {
74
+ _(node, { next, visit }) {
75
+ // Collect key node types: Identifiers, Literals, and JSX Elements
76
+ if (node.type === 'Identifier' && node.name) {
77
+ tokens.push(node.name);
78
+ } else if (node.type === 'JSXIdentifier' && node.name) {
79
+ tokens.push(node.name);
80
+ } else if (node.type === 'Literal' && node.raw) {
81
+ tokens.push(node.raw);
82
+ } else if (node.type === 'ImportDeclaration') {
83
+ // Visit specifiers in source order
84
+ if (node.specifiers) {
85
+ for (const specifier of node.specifiers) {
86
+ visit(specifier);
87
+ }
88
+ }
89
+ // Skip source (just a string literal)
90
+ return;
91
+ } else if (node.type === 'ImportSpecifier') {
92
+ // If local and imported are the same, only visit local to avoid duplicates
93
+ // Otherwise visit both in order
94
+ if (node.imported && node.local && node.imported.name !== node.local.name) {
95
+ visit(node.imported);
96
+ visit(node.local);
97
+ } else if (node.local) {
98
+ visit(node.local);
99
+ }
100
+ return;
101
+ } else if (node.type === 'ImportDefaultSpecifier' || node.type === 'ImportNamespaceSpecifier') {
102
+ // Just visit local
103
+ if (node.local) {
104
+ visit(node.local);
105
+ }
106
+ return;
107
+ } else if (node.type === 'ExportNamedDeclaration') {
108
+ // Visit in source order: declaration, specifiers
109
+ if (node.declaration) {
110
+ visit(node.declaration);
111
+ }
112
+ if (node.specifiers) {
113
+ for (const specifier of node.specifiers) {
114
+ visit(specifier);
115
+ }
116
+ }
117
+ return;
118
+ } else if (node.type === 'ExportDefaultDeclaration') {
119
+ // Visit the declaration
120
+ if (node.declaration) {
121
+ visit(node.declaration);
122
+ }
123
+ return;
124
+ } else if (node.type === 'ExportAllDeclaration') {
125
+ // Nothing to visit (just source string)
126
+ return;
127
+ } else if (node.type === 'JSXElement') {
128
+ // Manually visit in source order: opening element, children, closing element
182
129
 
183
- if (sourceIdMatch && generatedIdMatch && sourceIdMatch[0] === generatedIdMatch[0]) {
184
- best_match = {
185
- source: sourceIdMatch[0],
186
- generated: generatedIdMatch[0]
187
- };
130
+ // 1. Visit opening element (name and attributes)
131
+ if (node.openingElement) {
132
+ visit(node.openingElement);
188
133
  }
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
134
 
197
- if (sourceTypeMatch && generatedTypeMatch && sourceTypeMatch[0] === generatedTypeMatch[0]) {
198
- best_match = {
199
- source: sourceTypeMatch[0],
200
- generated: generatedTypeMatch[0]
201
- };
135
+ // 2. Visit children in order
136
+ if (node.children) {
137
+ for (const child of node.children) {
138
+ visit(child);
139
+ }
202
140
  }
203
- }
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
- }
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
141
+
142
+ // 3. Push closing tag name (not visited by AST walker)
143
+ if (!node.openingElement?.selfClosing && node.closingElement?.name?.type === 'JSXIdentifier') {
144
+ tokens.push(node.closingElement.name.name);
145
+ }
146
+
147
+ return;
148
+ } else if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
149
+ // Visit in source order: id, params, body
150
+ if (node.id) {
151
+ visit(node.id);
152
+ }
153
+ if (node.params) {
154
+ for (const param of node.params) {
155
+ visit(param);
245
156
  }
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
157
+ }
158
+ if (node.body) {
159
+ visit(node.body);
160
+ }
161
+ return;
162
+ } else if (node.type === 'VariableDeclaration') {
163
+ // Visit declarators in order
164
+ if (node.declarations) {
165
+ for (const declarator of node.declarations) {
166
+ visit(declarator);
249
167
  }
250
168
  }
169
+ return;
170
+ } else if (node.type === 'VariableDeclarator') {
171
+ // Visit in source order: id, init
172
+ if (node.id) {
173
+ visit(node.id);
174
+ }
175
+ if (node.init) {
176
+ visit(node.init);
177
+ }
178
+ return;
179
+ } else if (node.type === 'IfStatement') {
180
+ // Visit in source order: test, consequent, alternate
181
+ if (node.test) {
182
+ visit(node.test);
183
+ }
184
+ if (node.consequent) {
185
+ visit(node.consequent);
186
+ }
187
+ if (node.alternate) {
188
+ visit(node.alternate);
189
+ }
190
+ return;
191
+ } else if (node.type === 'ForStatement') {
192
+ // Visit in source order: init, test, update, body
193
+ if (node.init) {
194
+ visit(node.init);
195
+ }
196
+ if (node.test) {
197
+ visit(node.test);
198
+ }
199
+ if (node.update) {
200
+ visit(node.update);
201
+ }
202
+ if (node.body) {
203
+ visit(node.body);
204
+ }
205
+ return;
206
+ } else if (node.type === 'ForOfStatement' || node.type === 'ForInStatement') {
207
+ // Visit in source order: left, right, body
208
+ if (node.left) {
209
+ visit(node.left);
210
+ }
211
+ if (node.right) {
212
+ visit(node.right);
213
+ }
214
+ if (node.body) {
215
+ visit(node.body);
216
+ }
217
+ return;
218
+ } else if (node.type === 'WhileStatement' || node.type === 'DoWhileStatement') {
219
+ // Visit in source order: test, body (while) or body, test (do-while)
220
+ if (node.type === 'WhileStatement') {
221
+ if (node.test) {
222
+ visit(node.test);
223
+ }
224
+ if (node.body) {
225
+ visit(node.body);
226
+ }
227
+ } else {
228
+ if (node.body) {
229
+ visit(node.body);
230
+ }
231
+ if (node.test) {
232
+ visit(node.test);
233
+ }
234
+ }
235
+ return;
236
+ } else if (node.type === 'TryStatement') {
237
+ // Visit in source order: block, handler, finalizer
238
+ if (node.block) {
239
+ visit(node.block);
240
+ }
241
+ if (node.handler) {
242
+ visit(node.handler);
243
+ }
244
+ if (node.finalizer) {
245
+ visit(node.finalizer);
246
+ }
247
+ return;
248
+ } else if (node.type === 'CatchClause') {
249
+ // Visit in source order: param, body
250
+ if (node.param) {
251
+ visit(node.param);
252
+ }
253
+ if (node.body) {
254
+ visit(node.body);
255
+ }
256
+ return;
257
+ } else if (node.type === 'CallExpression' || node.type === 'NewExpression') {
258
+ // Visit in source order: callee, arguments
259
+ if (node.callee) {
260
+ visit(node.callee);
261
+ }
262
+ if (node.arguments) {
263
+ for (const arg of node.arguments) {
264
+ visit(arg);
265
+ }
266
+ }
267
+ return;
268
+ } else if (node.type === 'LogicalExpression' || node.type === 'BinaryExpression') {
269
+ // Visit in source order: left, right
270
+ if (node.left) {
271
+ visit(node.left);
272
+ }
273
+ if (node.right) {
274
+ visit(node.right);
275
+ }
276
+ return;
277
+ } else if (node.type === 'MemberExpression') {
278
+ // Visit in source order: object, property
279
+ if (node.object) {
280
+ visit(node.object);
281
+ }
282
+ if (!node.computed && node.property) {
283
+ visit(node.property);
284
+ }
285
+ return;
286
+ } else if (node.type === 'AssignmentExpression' || node.type === 'AssignmentPattern') {
287
+ // Visit in source order: left, right
288
+ if (node.left) {
289
+ visit(node.left);
290
+ }
291
+ if (node.right) {
292
+ visit(node.right);
293
+ }
294
+ return;
295
+ } else if (node.type === 'ObjectExpression' || node.type === 'ObjectPattern') {
296
+ // Visit properties in order
297
+ if (node.properties) {
298
+ for (const prop of node.properties) {
299
+ visit(prop);
300
+ }
301
+ }
302
+ return;
303
+ } else if (node.type === 'Property') {
304
+ // Visit in source order: key, value
305
+ if (node.key) {
306
+ visit(node.key);
307
+ }
308
+ if (node.value) {
309
+ visit(node.value);
310
+ }
311
+ return;
312
+ } else if (node.type === 'ArrayExpression' || node.type === 'ArrayPattern') {
313
+ // Visit elements in order
314
+ if (node.elements) {
315
+ for (const element of node.elements) {
316
+ if (element) visit(element);
317
+ }
318
+ }
319
+ return;
320
+ } else if (node.type === 'ConditionalExpression') {
321
+ // Visit in source order: test, consequent, alternate
322
+ if (node.test) {
323
+ visit(node.test);
324
+ }
325
+ if (node.consequent) {
326
+ visit(node.consequent);
327
+ }
328
+ if (node.alternate) {
329
+ visit(node.alternate);
330
+ }
331
+ return;
332
+ } else if (node.type === 'UnaryExpression' || node.type === 'UpdateExpression') {
333
+ // Visit argument
334
+ if (node.argument) {
335
+ visit(node.argument);
336
+ }
337
+ return;
338
+ } else if (node.type === 'TemplateLiteral') {
339
+ // Visit quasis and expressions in order
340
+ for (let i = 0; i < node.quasis.length; i++) {
341
+ if (node.quasis[i]) {
342
+ visit(node.quasis[i]);
343
+ }
344
+ if (i < node.expressions.length && node.expressions[i]) {
345
+ visit(node.expressions[i]);
346
+ }
347
+ }
348
+ return;
349
+ } else if (node.type === 'TaggedTemplateExpression') {
350
+ // Visit in source order: tag, quasi
351
+ if (node.tag) {
352
+ visit(node.tag);
353
+ }
354
+ if (node.quasi) {
355
+ visit(node.quasi);
356
+ }
357
+ return;
358
+ } else if (node.type === 'ReturnStatement' || node.type === 'ThrowStatement') {
359
+ // Visit argument
360
+ if (node.argument) {
361
+ visit(node.argument);
362
+ }
363
+ return;
364
+ } else if (node.type === 'ExpressionStatement') {
365
+ // Visit expression
366
+ if (node.expression) {
367
+ visit(node.expression);
368
+ }
369
+ return;
370
+ } else if (node.type === 'BlockStatement' || node.type === 'Program') {
371
+ // Visit body statements in order
372
+ if (node.body) {
373
+ for (const statement of node.body) {
374
+ visit(statement);
375
+ }
376
+ }
377
+ return;
378
+ } else if (node.type === 'SwitchStatement') {
379
+ // Visit in source order: discriminant, cases
380
+ if (node.discriminant) {
381
+ visit(node.discriminant);
382
+ }
383
+ if (node.cases) {
384
+ for (const caseNode of node.cases) {
385
+ visit(caseNode);
386
+ }
387
+ }
388
+ return;
389
+ } else if (node.type === 'SwitchCase') {
390
+ // Visit in source order: test, consequent
391
+ if (node.test) {
392
+ visit(node.test);
393
+ }
394
+ if (node.consequent) {
395
+ for (const statement of node.consequent) {
396
+ visit(statement);
397
+ }
398
+ }
399
+ return;
400
+ } else if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
401
+ // Visit in source order: id, superClass, body
402
+ if (node.id) {
403
+ visit(node.id);
404
+ }
405
+ if (node.superClass) {
406
+ visit(node.superClass);
407
+ }
408
+ if (node.body) {
409
+ visit(node.body);
410
+ }
411
+ return;
412
+ } else if (node.type === 'ClassBody') {
413
+ // Visit body in order
414
+ if (node.body) {
415
+ for (const member of node.body) {
416
+ visit(member);
417
+ }
418
+ }
419
+ return;
420
+ } else if (node.type === 'MethodDefinition') {
421
+ // Visit in source order: key, value
422
+ if (node.key) {
423
+ visit(node.key);
424
+ }
425
+ if (node.value) {
426
+ visit(node.value);
427
+ }
428
+ return;
251
429
  }
430
+
431
+ next();
252
432
  }
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
- }
433
+ });
298
434
 
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
435
+ // Process each token in order
436
+ for (const text of tokens) {
437
+ const sourcePos = findInSource(text);
438
+ const genPos = findInGenerated(text);
439
+
440
+ if (sourcePos !== null && genPos !== null) {
441
+ mappings.push({
442
+ sourceOffsets: [sourcePos],
443
+ generatedOffsets: [genPos],
444
+ lengths: [text.length],
445
+ data: mapping_data,
446
+ });
303
447
  }
304
448
  }
305
449
 
306
- // Sort mappings by source offset for better organization
450
+ // Sort mappings by source offset
307
451
  mappings.sort((a, b) => a.sourceOffsets[0] - b.sourceOffsets[0]);
308
452
 
309
453
  return {
310
454
  code: generated_code,
311
455
  mappings,
312
456
  };
313
- }
457
+ }
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;