ripple 0.2.91 → 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.
@@ -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,252 +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
- return false;
80
- }
81
-
82
- /**
83
- * Convert esrap SourceMap to Volar mappings
84
- * @param {{ mappings: string }} source_map
85
- * @param {string} source
86
- * @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
87
15
  * @returns {object}
88
16
  */
89
- export function convert_source_map_to_mappings(source_map, source, generated_code) {
17
+ export function convert_source_map_to_mappings(ast, source, generated_code) {
90
18
  /** @type {Array<{sourceOffsets: number[], generatedOffsets: number[], lengths: number[], data: any}>} */
91
19
  const mappings = [];
92
20
 
93
- // Decode the VLQ mappings from esrap
94
- const decoded_mappings = decode(source_map.mappings);
95
-
96
- let generated_offset = 0;
97
- const generated_lines = generated_code.split('\n');
98
- const source_lines = source.split('\n');
21
+ // Maintain indices that walk through source and generated code
22
+ let sourceIndex = 0;
23
+ let generatedIndex = 0;
99
24
 
100
- // Process each line of generated code
101
- for (let generated_line = 0; generated_line < generated_lines.length; generated_line++) {
102
- const line = generated_lines[generated_line];
103
- const line_mappings = decoded_mappings[generated_line] || [];
104
-
105
- // Process mappings for this line
106
- for (const mapping of line_mappings) {
107
- const [generated_column, source_file_index, source_line, source_column] = mapping;
108
-
109
- // Skip mappings without source information
110
- if (source_file_index == null || source_line == null || source_column == null) {
111
- 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
+ }
112
38
  }
113
-
114
- // Calculate source offset
115
- let source_offset = 0;
116
- for (let i = 0; i < Math.min(source_line, source_lines.length - 1); i++) {
117
- source_offset += source_lines[i].length + 1; // +1 for newline
39
+ if (match) {
40
+ sourceIndex = i + text.length;
41
+ return i;
118
42
  }
119
- source_offset += source_column;
120
-
121
- // Calculate generated offset
122
- const current_generated_offset = generated_offset + generated_column;
43
+ }
44
+ return null;
45
+ };
123
46
 
124
- // Find meaningful token boundaries for source content
125
- const source_token_end = findTokenBoundary(source, source_offset, 1);
126
- const source_token_start = findTokenBoundary(source, source_offset, -1);
127
-
128
- // Find meaningful token boundaries for generated content
129
- const generated_token_end = findTokenBoundary(generated_code, current_generated_offset, 1);
130
- const generated_token_start = findTokenBoundary(generated_code, current_generated_offset, -1);
131
-
132
- // Extract potential source content (prefer forward boundary but try both directions)
133
- let best_source_content = source.substring(source_offset, source_token_end);
134
- let best_generated_content = generated_code.substring(current_generated_offset, generated_token_end);
135
-
136
- // Try different segment boundaries to find the best match
137
- const candidates = [
138
- // Forward boundaries
139
- {
140
- source: source.substring(source_offset, source_token_end),
141
- generated: generated_code.substring(current_generated_offset, generated_token_end)
142
- },
143
- // Backward boundaries
144
- {
145
- source: source.substring(source_token_start, source_offset + 1),
146
- generated: generated_code.substring(generated_token_start, current_generated_offset + 1)
147
- },
148
- // Single character
149
- {
150
- source: source.charAt(source_offset),
151
- generated: generated_code.charAt(current_generated_offset)
152
- },
153
- // Try to find exact matches in nearby content
154
- ];
155
-
156
- // Look for the best candidate match
157
- let best_match = null;
158
- for (const candidate of candidates) {
159
- if (isValidMapping(candidate.source, candidate.generated)) {
160
- 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;
161
58
  break;
162
59
  }
163
60
  }
164
-
165
- // If no good match found, try extracting identifiers/keywords
166
- if (!best_match) {
167
- const sourceIdMatch = source.substring(source_offset).match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/);
168
- const generatedIdMatch = generated_code.substring(current_generated_offset).match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/);
169
-
170
- if (sourceIdMatch && generatedIdMatch && sourceIdMatch[0] === generatedIdMatch[0]) {
171
- best_match = {
172
- source: sourceIdMatch[0],
173
- generated: generatedIdMatch[0]
174
- };
175
- }
176
- }
177
-
178
- // Handle special cases for Ripple keywords that might not have generated equivalents
179
- if (!best_match || best_match.source.length === 0) {
180
- continue;
181
- }
182
-
183
- // Special handling for Ripple-specific syntax that may be omitted in generated code
184
- const sourceAtOffset = source.substring(source_offset, source_offset + 10);
185
- if (sourceAtOffset.includes('index ')) {
186
- // For the 'index' keyword, create a mapping even if there's no generated equivalent
187
- const indexMatch = sourceAtOffset.match(/index\s+/);
188
- if (indexMatch) {
189
- best_match = {
190
- source: indexMatch[0].trim(),
191
- generated: '' // Empty generated content for keywords that are transformed away
192
- };
61
+ if (match) {
62
+ generatedIndex = i + text.length;
63
+ return i;
193
64
  }
194
65
  }
195
-
196
- // Skip if we still don't have a valid source match
197
- if (!best_match || best_match.source.length === 0) {
198
- continue;
199
- }
200
-
201
- // Skip mappings for complex RefAttribute syntax to avoid overlapping sourcemaps,
202
- // but allow simple 'ref' keyword mappings for IntelliSense
203
- if (best_match.source.includes('{ref ') && best_match.source.length > 10) {
204
- // Skip complex ref expressions like '{ref (node) => { ... }}'
205
- continue;
206
- }
207
-
208
- // Allow simple 'ref' keyword mappings for IntelliSense
209
- if (best_match.source.trim() === 'ref' && best_match.generated.length === 0) {
210
- // This is just the ref keyword, allow it for syntax support
211
- // but map it to current position since there's no generated equivalent
212
- }
213
-
214
- // Calculate actual offsets and lengths for the best match
215
- let actual_source_offset, actual_generated_offset;
216
-
217
- if (best_match.generated.length > 0) {
218
- actual_source_offset = source.indexOf(best_match.source, source_offset - best_match.source.length);
219
- actual_generated_offset = generated_code.indexOf(best_match.generated, current_generated_offset - best_match.generated.length);
220
- } else {
221
- // For keywords with no generated equivalent, use the exact source position
222
- actual_source_offset = source_offset;
223
- actual_generated_offset = current_generated_offset; // Map to current position in generated code
224
- }
225
-
226
- // Use the match we found, but fall back to original positions if indexOf fails
227
- const final_source_offset = actual_source_offset !== -1 ? actual_source_offset : source_offset;
228
- 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
229
- const isDuplicate = mappings.some(existing =>
230
- existing.sourceOffsets[0] === final_source_offset &&
231
- existing.generatedOffsets[0] === final_generated_offset &&
232
- existing.lengths[0] === best_match.source.length
233
- );
234
-
235
- if (!isDuplicate) {
236
- mappings.push({
237
- sourceOffsets: [final_source_offset],
238
- generatedOffsets: [final_generated_offset],
239
- lengths: [best_match.source.length],
240
- data: mapping_data,
241
- });
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
129
+
130
+ // 1. Visit opening element (name and attributes)
131
+ if (node.openingElement) {
132
+ visit(node.openingElement);
133
+ }
134
+
135
+ // 2. Visit children in order
136
+ if (node.children) {
137
+ for (const child of node.children) {
138
+ visit(child);
139
+ }
140
+ }
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);
156
+ }
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);
167
+ }
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;
242
429
  }
430
+
431
+ next();
243
432
  }
433
+ });
244
434
 
245
- // Add line length + 1 for newline (except for last line)
246
- generated_offset += line.length;
247
- if (generated_line < generated_lines.length - 1) {
248
- 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
+ });
249
447
  }
250
448
  }
251
449
 
252
- // Sort mappings by source offset for better organization
450
+ // Sort mappings by source offset
253
451
  mappings.sort((a, b) => a.sourceOffsets[0] - b.sourceOffsets[0]);
254
452
 
255
453
  return {
256
454
  code: generated_code,
257
455
  mappings,
258
456
  };
259
- }
457
+ }