ripple 0.2.90 → 0.2.92

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.
@@ -7,14 +7,100 @@ export const mapping_data = {
7
7
  navigation: true,
8
8
  };
9
9
 
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
+
10
95
  /**
11
96
  * Convert esrap SourceMap to Volar mappings
12
- * @param {object} source_map
97
+ * @param {{ mappings: string }} source_map
13
98
  * @param {string} source
14
99
  * @param {string} generated_code
15
100
  * @returns {object}
16
101
  */
17
102
  export function convert_source_map_to_mappings(source_map, source, generated_code) {
103
+ /** @type {Array<{sourceOffsets: number[], generatedOffsets: number[], lengths: number[], data: any}>} */
18
104
  const mappings = [];
19
105
 
20
106
  // Decode the VLQ mappings from esrap
@@ -22,6 +108,7 @@ export function convert_source_map_to_mappings(source_map, source, generated_cod
22
108
 
23
109
  let generated_offset = 0;
24
110
  const generated_lines = generated_code.split('\n');
111
+ const source_lines = source.split('\n');
25
112
 
26
113
  // Process each line of generated code
27
114
  for (let generated_line = 0; generated_line < generated_lines.length; generated_line++) {
@@ -38,7 +125,6 @@ export function convert_source_map_to_mappings(source_map, source, generated_cod
38
125
  }
39
126
 
40
127
  // Calculate source offset
41
- const source_lines = source.split('\n');
42
128
  let source_offset = 0;
43
129
  for (let i = 0; i < Math.min(source_line, source_lines.length - 1); i++) {
44
130
  source_offset += source_lines[i].length + 1; // +1 for newline
@@ -48,41 +134,166 @@ export function convert_source_map_to_mappings(source_map, source, generated_cod
48
134
  // Calculate generated offset
49
135
  const current_generated_offset = generated_offset + generated_column;
50
136
 
51
- // Determine segment length (look ahead to next mapping or end of line)
52
- const next_mapping = line_mappings[line_mappings.indexOf(mapping) + 1];
53
- let segment_length = next_mapping
54
- ? next_mapping[0] - generated_column
55
- : Math.max(1, line.length - generated_column);
56
-
57
- // Determine the actual segment content
58
- const generated_content = generated_code.substring(
59
- current_generated_offset,
60
- current_generated_offset + segment_length,
61
- );
62
- const source_content = source.substring(source_offset, source_offset + segment_length);
63
-
64
- // Skip mappings for RefAttribute syntax to avoid overlapping sourcemaps
65
- if (source_content.includes('{ref ') || source_content.match(/\{\s*ref\s+/)) {
66
- continue;
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;
174
+ break;
175
+ }
67
176
  }
68
-
69
- // Fix for children mapping: when generated content is "children",
70
- // it should only map to the component name in the source, not include attributes
71
- if (generated_content === 'children') {
72
- // Look for the component name in the source content
73
- const component_name_match = source_content.match(/^(\w+)/);
74
- if (component_name_match) {
75
- const component_name = component_name_match[1];
76
- segment_length = component_name.length;
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
+ };
77
188
  }
78
189
  }
79
-
80
- mappings.push({
81
- sourceOffsets: [source_offset],
82
- generatedOffsets: [current_generated_offset],
83
- lengths: [segment_length],
84
- data: mapping_data,
85
- });
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
+
197
+ if (sourceTypeMatch && generatedTypeMatch && sourceTypeMatch[0] === generatedTypeMatch[0]) {
198
+ best_match = {
199
+ source: sourceTypeMatch[0],
200
+ generated: generatedTypeMatch[0]
201
+ };
202
+ }
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
245
+ }
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
249
+ }
250
+ }
251
+ }
252
+ }
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
+ }
86
297
  }
87
298
 
88
299
  // Add line length + 1 for newline (except for last line)
@@ -92,6 +303,9 @@ export function convert_source_map_to_mappings(source_map, source, generated_cod
92
303
  }
93
304
  }
94
305
 
306
+ // Sort mappings by source offset for better organization
307
+ mappings.sort((a, b) => a.sourceOffsets[0] - b.sourceOffsets[0]);
308
+
95
309
  return {
96
310
  code: generated_code,
97
311
  mappings,