rip-lang 3.13.136 → 3.14.0

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.
@@ -364,3 +364,158 @@ export function mapToSourcePos(entry, offset) {
364
364
  }
365
365
  return { line: srcLine, col: srcCol };
366
366
  }
367
+
368
+ // Map a Rip source (line, col) to a TypeScript virtual file byte offset.
369
+ // This is the forward direction: source → generated (used for hover, definition, etc.)
370
+ //
371
+ // `entry` must have: tsContent, source, srcToGen, srcColToGen (optional)
372
+ // Returns undefined if no mapping can be established.
373
+ export function srcToOffset(entry, line, col) {
374
+ if (!entry) return undefined;
375
+ let genLine = entry.srcToGen.get(line);
376
+ let genColHint = -1;
377
+ let bestSrcCol = -1;
378
+
379
+ // Column-aware lookup
380
+ if (entry.srcColToGen) {
381
+ const colEntries = entry.srcColToGen.get(line);
382
+ if (colEntries && colEntries.length > 0) {
383
+ let best = colEntries[0];
384
+ for (const e of colEntries) {
385
+ if (e.srcCol <= col && (best.srcCol > col || e.srcCol > best.srcCol)) best = e;
386
+ }
387
+ if (best.srcCol > col) {
388
+ for (const e of colEntries) {
389
+ if (Math.abs(e.srcCol - col) < Math.abs(best.srcCol - col)) best = e;
390
+ }
391
+ }
392
+ genLine = best.genLine;
393
+ genColHint = best.genCol;
394
+ bestSrcCol = best.srcCol;
395
+ }
396
+ }
397
+
398
+ if (genLine === undefined) {
399
+ let best = -1;
400
+ for (const [s] of entry.srcToGen) if (s <= line && s > best) best = s;
401
+ if (best < 0) return undefined;
402
+ genLine = entry.srcToGen.get(best);
403
+ }
404
+
405
+ const srcLines = entry.source.split('\n');
406
+ const genLines = entry.tsContent.split('\n');
407
+ const KEYWORDS = new Set(['interface', 'type', 'enum', 'class', 'export', 'declare', 'extends', 'implements', 'import', 'from', 'def', 'const', 'let', 'var']);
408
+
409
+ if (srcLines[line] != null && genLines[genLine] != null) {
410
+ const srcText = srcLines[line];
411
+ const genText = genLines[genLine];
412
+ const leftPart = srcText.substring(0, col).match(/\w*$/)?.[0] || '';
413
+ const rightPart = srcText.substring(col).match(/^\w*/)?.[0] || '';
414
+ let wordMatch = (leftPart + rightPart) ? [leftPart + rightPart] : null;
415
+ if (wordMatch && KEYWORDS.has(wordMatch[0])) {
416
+ const after = srcText.substring(col + wordMatch[0].length).match(/\s+(\w+)/);
417
+ if (after) wordMatch = [after[1]];
418
+ }
419
+ if (wordMatch) {
420
+ const word = wordMatch[0];
421
+ const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
422
+ const wordStart = col - leftPart.length;
423
+ const useHint = genColHint >= 0 && bestSrcCol >= wordStart && bestSrcCol < wordStart + word.length;
424
+
425
+ // Prefer the overload signature line (genLine-1) when it exists and
426
+ // contains the same identifier — overloads carry typed parameters.
427
+ let targetLine = genLine;
428
+ let targetText = genText;
429
+ if (genLine > 0) {
430
+ const prevText = genLines[genLine - 1] || '';
431
+ if (/^(?:export\s+)?function\s+\w+\(.*\).*;\s*$/.test(prevText)) {
432
+ const re0 = new RegExp('\\b' + escaped + '\\b');
433
+ if (re0.test(prevText)) { targetLine = genLine - 1; targetText = prevText; }
434
+ }
435
+ }
436
+
437
+ const re = new RegExp('\\b' + escaped + '\\b', 'g');
438
+ let m, bestCol = -1, bestDist = Infinity;
439
+ // When the word doesn't fall exactly on a mapped srcCol, extrapolate
440
+ // the expected gen column from the nearest mapping entry. This avoids
441
+ // picking a same-named word inside a string literal that happens to be
442
+ // closer to the raw source column.
443
+ const expectedGenCol = useHint ? genColHint
444
+ : genColHint >= 0 ? genColHint + (col - bestSrcCol) : col;
445
+ while ((m = re.exec(targetText)) !== null) {
446
+ const dist = Math.abs(m.index - expectedGenCol);
447
+ if (dist < bestDist) { bestDist = dist; bestCol = m.index; }
448
+ }
449
+ if (bestCol >= 0) return lineColToOffset(entry.tsContent, targetLine, bestCol);
450
+
451
+ // Fall back to original genLine if overload didn't match
452
+ if (targetLine !== genLine) {
453
+ const re1b = new RegExp('\\b' + escaped + '\\b', 'g');
454
+ let m1b, bestCol1b = -1, bestDist1b = Infinity;
455
+ while ((m1b = re1b.exec(genText)) !== null) {
456
+ const dist = Math.abs(m1b.index - expectedGenCol);
457
+ if (dist < bestDist1b) { bestDist1b = dist; bestCol1b = m1b.index; }
458
+ }
459
+ if (bestCol1b >= 0) return lineColToOffset(entry.tsContent, genLine, bestCol1b);
460
+ }
461
+
462
+ // Word not on mapped line — search nearby generated lines
463
+ for (let delta = 1; delta <= 5; delta++) {
464
+ for (const tryLine of [genLine + delta, genLine - delta]) {
465
+ if (tryLine < 0 || tryLine >= genLines.length) continue;
466
+ const tryText = genLines[tryLine];
467
+ const re2 = new RegExp('\\b' + escaped + '\\b', 'g');
468
+ let m2, best2 = -1, bestDist2 = Infinity;
469
+ while ((m2 = re2.exec(tryText)) !== null) {
470
+ const dist2 = Math.abs(m2.index - col);
471
+ if (dist2 < bestDist2) { bestDist2 = dist2; best2 = m2.index; }
472
+ }
473
+ if (best2 >= 0) return lineColToOffset(entry.tsContent, tryLine, best2);
474
+ }
475
+ }
476
+
477
+ // Neighbor-line fallback: when the word isn't on the mapped gen line or
478
+ // nearby ±5 lines, check neighboring source lines for srcColToGen entries
479
+ // that point to gen lines containing the word. Handles multi-line
480
+ // expressions collapsed to one gen line, bodiless overload signatures
481
+ // mapped to wrong gen lines, etc.
482
+ if (entry.srcColToGen) {
483
+ const candidateGenLines = new Set();
484
+ for (let d = 0; d <= 10; d++) {
485
+ for (const sl of d === 0 ? [line] : [line - d, line + d]) {
486
+ if (sl < 0) continue;
487
+ const ce = entry.srcColToGen.get(sl);
488
+ if (ce) {
489
+ for (const e of ce) candidateGenLines.add(e.genLine);
490
+ }
491
+ }
492
+ }
493
+ // Also try gen lines near those candidates (overload signatures are
494
+ // typically on the line just before the function body)
495
+ const expanded = new Set(candidateGenLines);
496
+ for (const gl of candidateGenLines) {
497
+ for (let d = 1; d <= 3; d++) {
498
+ expanded.add(gl - d);
499
+ expanded.add(gl + d);
500
+ }
501
+ }
502
+ let bestAlt = -1, bestAltCol = -1, bestAltDist = Infinity;
503
+ for (const gl of expanded) {
504
+ if (gl < 0 || gl >= genLines.length) continue;
505
+ const altText = genLines[gl] || '';
506
+ const re3 = new RegExp('\\b' + escaped + '\\b', 'g');
507
+ let m3;
508
+ while ((m3 = re3.exec(altText)) !== null) {
509
+ const dist3 = Math.abs(m3.index - expectedGenCol);
510
+ if (dist3 < bestAltDist) { bestAltDist = dist3; bestAltCol = m3.index; bestAlt = gl; }
511
+ }
512
+ }
513
+ if (bestAltCol >= 0) return lineColToOffset(entry.tsContent, bestAlt, bestAltCol);
514
+ }
515
+ }
516
+ }
517
+
518
+ const genText = entry.tsContent.split('\n')[genLine] || '';
519
+ if (col < genText.length) return lineColToOffset(entry.tsContent, genLine, col);
520
+ return lineColToOffset(entry.tsContent, genLine, 0);
521
+ }