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.
- package/README.md +46 -4
- package/docs/RIP-LANG.md +116 -11
- package/docs/RIP-SCHEMA.md +2390 -0
- package/docs/RIP-TYPES.md +21 -14
- package/docs/assets/rip-schema-logo-960w.png +0 -0
- package/docs/assets/rip-schema-social.png +0 -0
- package/docs/dist/rip.js +6817 -3670
- package/docs/dist/rip.min.js +1454 -211
- package/docs/dist/rip.min.js.br +0 -0
- package/package.json +10 -4
- package/src/AGENTS.md +130 -0
- package/src/compiler.js +65 -2
- package/src/components.js +19 -5
- package/src/grammar/grammar.rip +20 -1
- package/src/lexer.js +42 -0
- package/src/parser.js +222 -220
- package/src/schema.js +3298 -0
- package/src/sourcemap-utils.js +155 -0
- package/src/typecheck.js +395 -23
- package/src/types.js +25 -0
- package/src/ui.rip +203 -45
- package/CHANGELOG.md +0 -1500
package/src/sourcemap-utils.js
CHANGED
|
@@ -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
|
+
}
|