rip-lang 3.8.10 → 3.9.1

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/src/lexer.js CHANGED
@@ -39,7 +39,6 @@
39
39
  //
40
40
  // ==========================================================================
41
41
 
42
- import { TEMPLATE_TAGS } from './tags.js';
43
42
  import { installTypeSupport } from './types.js';
44
43
 
45
44
  // ==========================================================================
@@ -759,6 +758,16 @@ export class Lexer {
759
758
  if (this.inRenderBlock && LINE_CONTINUER_RE.test(this.chunk) && /^\s*\./.test(this.chunk)) {
760
759
  return false;
761
760
  }
761
+ // Inside render blocks, a bare . at line start is div shorthand, not continuation
762
+ if (this.inRenderBlock && this.prevTag() === '.') {
763
+ let len = this.tokens.length;
764
+ if (len >= 2) {
765
+ let beforeDot = this.tokens[len - 2][0];
766
+ if (beforeDot === 'INDENT' || beforeDot === 'TERMINATOR' || beforeDot === 'OUTDENT') {
767
+ return false;
768
+ }
769
+ }
770
+ }
762
771
  return LINE_CONTINUER_RE.test(this.chunk) || UNFINISHED.has(this.prevTag());
763
772
  }
764
773
 
@@ -1263,7 +1272,7 @@ export class Lexer {
1263
1272
  }
1264
1273
 
1265
1274
  // ==========================================================================
1266
- // Rewriter — 8 passes
1275
+ // Rewriter
1267
1276
  // ==========================================================================
1268
1277
 
1269
1278
  rewrite(tokens) {
@@ -1272,7 +1281,7 @@ export class Lexer {
1272
1281
  this.closeOpenCalls();
1273
1282
  this.closeOpenIndexes();
1274
1283
  this.normalizeLines();
1275
- this.rewriteRender();
1284
+ this.rewriteRender?.();
1276
1285
  this.rewriteTypes();
1277
1286
  this.tagPostfixConditionals();
1278
1287
  this.addImplicitBracesAndParens();
@@ -1383,278 +1392,6 @@ export class Lexer {
1383
1392
  });
1384
1393
  }
1385
1394
 
1386
- // =========================================================================
1387
- // Render block rewriter
1388
- // =========================================================================
1389
- // Transforms template syntax inside render blocks:
1390
- // - Implicit div for class-only selectors: .card → div.card
1391
- // - Combine #id selectors: div # main → div#main
1392
- // - Two-way binding: value <=> username → __bind_value__: username
1393
- // - Event modifiers: @click.prevent: → [@click.prevent]:
1394
- // - Dynamic classes: div.('card', x && 'active') → div.__clsx(...)
1395
- // - Implicit nesting: inject -> before INDENT for template elements
1396
- // - Hyphenated attributes: data-foo: "x" → "data-foo": "x"
1397
- // =========================================================================
1398
- rewriteRender() {
1399
- let inRender = false;
1400
- let renderIndentLevel = 0;
1401
- let currentIndent = 0;
1402
- let pendingCallEnds = [];
1403
-
1404
- let isHtmlTag = (name) => {
1405
- let tagPart = name.split('#')[0];
1406
- return TEMPLATE_TAGS.has(tagPart);
1407
- };
1408
-
1409
- let isComponent = (name) => {
1410
- if (!name || typeof name !== 'string') return false;
1411
- return /^[A-Z]/.test(name);
1412
- };
1413
-
1414
- let isTemplateTag = (name) => {
1415
- return isHtmlTag(name) || isComponent(name);
1416
- };
1417
-
1418
- let startsWithTag = (tokens, i) => {
1419
- let j = i;
1420
- while (j > 0) {
1421
- let pt = tokens[j - 1][0];
1422
- if (pt === 'INDENT' || pt === 'OUTDENT' || pt === 'TERMINATOR' || pt === 'RENDER' || pt === 'CALL_END' || pt === ')') {
1423
- break;
1424
- }
1425
- j--;
1426
- }
1427
- return tokens[j] && tokens[j][0] === 'IDENTIFIER' && isTemplateTag(tokens[j][1]);
1428
- };
1429
-
1430
- this.scanTokens(function(token, i, tokens) {
1431
- let tag = token[0];
1432
- let nextToken = i < tokens.length - 1 ? tokens[i + 1] : null;
1433
-
1434
- // Track entering render blocks
1435
- if (tag === 'RENDER') {
1436
- inRender = true;
1437
- renderIndentLevel = currentIndent + 1;
1438
- return 1;
1439
- }
1440
-
1441
- // Track indentation
1442
- if (tag === 'INDENT') {
1443
- currentIndent++;
1444
- return 1;
1445
- }
1446
-
1447
- if (tag === 'OUTDENT') {
1448
- currentIndent--;
1449
-
1450
- // Insert pending CALL_END(s) after this OUTDENT
1451
- let inserted = 0;
1452
- while (pendingCallEnds.length > 0 && pendingCallEnds[pendingCallEnds.length - 1] > currentIndent) {
1453
- let callEndToken = gen('CALL_END', ')', token);
1454
- tokens.splice(i + 1 + inserted, 0, callEndToken);
1455
- pendingCallEnds.pop();
1456
- inserted++;
1457
- }
1458
-
1459
- // Exit render block when we outdent past where it started
1460
- if (inRender && currentIndent < renderIndentLevel) {
1461
- inRender = false;
1462
- }
1463
- return 1 + inserted;
1464
- }
1465
-
1466
- // Only process if we're inside a render block
1467
- if (!inRender) return 1;
1468
-
1469
- // ─────────────────────────────────────────────────────────────────────
1470
- // Hyphenated attributes
1471
- // data-lucide: "search" → "data-lucide": "search"
1472
- // ─────────────────────────────────────────────────────────────────────
1473
- if (tag === 'IDENTIFIER' && !token.spaced) {
1474
- let parts = [token[1]];
1475
- let j = i + 1;
1476
- while (j + 1 < tokens.length) {
1477
- let hyphen = tokens[j];
1478
- let nextPart = tokens[j + 1];
1479
- if (hyphen[0] === '-' && !hyphen.spaced &&
1480
- (nextPart[0] === 'IDENTIFIER' || nextPart[0] === 'PROPERTY')) {
1481
- parts.push(nextPart[1]);
1482
- j += 2;
1483
- if (nextPart[0] === 'PROPERTY') break;
1484
- } else {
1485
- break;
1486
- }
1487
- }
1488
- if (parts.length > 1 && j > i + 1 && tokens[j - 1][0] === 'PROPERTY') {
1489
- token[0] = 'STRING';
1490
- token[1] = `"${parts.join('-')}"`;
1491
- tokens.splice(i + 1, j - i - 1);
1492
- return 1;
1493
- }
1494
- }
1495
-
1496
- // ─────────────────────────────────────────────────────────────────────
1497
- // Implicit div for class-only selectors
1498
- // .card → div.card
1499
- // ─────────────────────────────────────────────────────────────────────
1500
- if (tag === '.') {
1501
- let prevToken = i > 0 ? tokens[i - 1] : null;
1502
- let prevTag = prevToken ? prevToken[0] : null;
1503
- if (prevTag === 'INDENT' || prevTag === 'TERMINATOR') {
1504
- if (nextToken && nextToken[0] === 'PROPERTY') {
1505
- let divToken = gen('IDENTIFIER', 'div', token);
1506
- tokens.splice(i, 0, divToken);
1507
- return 2;
1508
- }
1509
- }
1510
- }
1511
-
1512
- // ─────────────────────────────────────────────────────────────────────
1513
- // Combine #id selectors
1514
- // div # main → div#main
1515
- // ─────────────────────────────────────────────────────────────────────
1516
- if (tag === 'IDENTIFIER' || tag === 'PROPERTY') {
1517
- let next = tokens[i + 1];
1518
- let nextNext = tokens[i + 2];
1519
- if (next && next[0] === '#' && nextNext && (nextNext[0] === 'PROPERTY' || nextNext[0] === 'IDENTIFIER')) {
1520
- token[1] = token[1] + '#' + nextNext[1];
1521
- if (nextNext.spaced) token.spaced = true;
1522
- tokens.splice(i + 1, 2);
1523
- return 1;
1524
- }
1525
- }
1526
-
1527
- // ─────────────────────────────────────────────────────────────────────
1528
- // Two-way binding
1529
- // value <=> username → __bind_value__: username
1530
- // ─────────────────────────────────────────────────────────────────────
1531
- if (tag === 'BIND') {
1532
- let prevToken = i > 0 ? tokens[i - 1] : null;
1533
- let nextBindToken = tokens[i + 1];
1534
- if (prevToken && (prevToken[0] === 'IDENTIFIER' || prevToken[0] === 'PROPERTY') &&
1535
- nextBindToken && nextBindToken[0] === 'IDENTIFIER') {
1536
- prevToken[1] = `__bind_${prevToken[1]}__`;
1537
- token[0] = ':';
1538
- token[1] = ':';
1539
- return 1;
1540
- }
1541
- }
1542
-
1543
- // ─────────────────────────────────────────────────────────────────────
1544
- // Event modifiers
1545
- // @click.prevent: handler → [@click.prevent]: handler
1546
- // ─────────────────────────────────────────────────────────────────────
1547
- if (tag === '@') {
1548
- let j = i + 1;
1549
- if (j < tokens.length && tokens[j][0] === 'PROPERTY') {
1550
- j++;
1551
- while (j + 1 < tokens.length && tokens[j][0] === '.' && tokens[j + 1][0] === 'PROPERTY') {
1552
- j += 2;
1553
- }
1554
- if (j > i + 2 && j < tokens.length && tokens[j][0] === ':') {
1555
- let openBracket = gen('[', '[', token);
1556
- tokens.splice(i, 0, openBracket);
1557
- let closeBracket = gen(']', ']', tokens[j + 1]);
1558
- tokens.splice(j + 1, 0, closeBracket);
1559
- return 2;
1560
- }
1561
- }
1562
- }
1563
-
1564
- // ─────────────────────────────────────────────────────────────────────
1565
- // Dynamic classes
1566
- // div.('card', x && 'active') → div.__clsx('card', x && 'active')
1567
- // .('card') → div.__clsx('card')
1568
- // ─────────────────────────────────────────────────────────────────────
1569
- if (tag === '.' && nextToken && nextToken[0] === '(') {
1570
- let prevToken = i > 0 ? tokens[i - 1] : null;
1571
- let prevTag = prevToken ? prevToken[0] : null;
1572
- let atLineStart = prevTag === 'INDENT' || prevTag === 'TERMINATOR';
1573
-
1574
- let cxToken = gen('PROPERTY', '__clsx', token);
1575
- nextToken[0] = 'CALL_START';
1576
- let depth = 1;
1577
- for (let j = i + 2; j < tokens.length && depth > 0; j++) {
1578
- if (tokens[j][0] === '(' || tokens[j][0] === 'CALL_START') depth++;
1579
- else if (tokens[j][0] === ')') {
1580
- depth--;
1581
- if (depth === 0) tokens[j][0] = 'CALL_END';
1582
- } else if (tokens[j][0] === 'CALL_END') depth--;
1583
- }
1584
-
1585
- if (atLineStart) {
1586
- let divToken = gen('IDENTIFIER', 'div', token);
1587
- tokens.splice(i, 0, divToken);
1588
- tokens.splice(i + 2, 0, cxToken);
1589
- return 3;
1590
- } else {
1591
- tokens.splice(i + 1, 0, cxToken);
1592
- return 2;
1593
- }
1594
- }
1595
-
1596
- // ─────────────────────────────────────────────────────────────────────
1597
- // Implicit nesting (inject -> before INDENT)
1598
- // ─────────────────────────────────────────────────────────────────────
1599
- if (nextToken && nextToken[0] === 'INDENT') {
1600
- if (tag === '->' || tag === '=>' || tag === 'CALL_START' || tag === '(') {
1601
- return 1;
1602
- }
1603
-
1604
- let isTemplateElement = false;
1605
- let prevTag = i > 0 ? tokens[i - 1][0] : null;
1606
- let isAfterControlFlow = prevTag === 'IF' || prevTag === 'UNLESS' || prevTag === 'WHILE' || prevTag === 'UNTIL' || prevTag === 'WHEN';
1607
-
1608
- if (tag === 'IDENTIFIER' && isTemplateTag(token[1]) && !isAfterControlFlow) {
1609
- isTemplateElement = true;
1610
- } else if (tag === 'PROPERTY' || tag === 'STRING' || tag === 'CALL_END' || tag === ')') {
1611
- isTemplateElement = startsWithTag(tokens, i);
1612
- }
1613
- else if (tag === 'IDENTIFIER' && i > 1 && tokens[i - 1][0] === '...') {
1614
- if (startsWithTag(tokens, i)) {
1615
- let commaToken = gen(',', ',', token);
1616
- let arrowToken = gen('->', '->', token);
1617
- arrowToken.newLine = true;
1618
- tokens.splice(i + 1, 0, commaToken, arrowToken);
1619
- return 3;
1620
- }
1621
- }
1622
-
1623
- if (isTemplateElement) {
1624
- let isClassOrIdTail = tag === 'PROPERTY' && i > 0 && (tokens[i - 1][0] === '.' || tokens[i - 1][0] === '#');
1625
- if ((tag === 'IDENTIFIER' && isTemplateTag(token[1])) || isClassOrIdTail) {
1626
- // Bare tag or tag.class/tag#id (no other args): inject CALL_START -> and manage CALL_END
1627
- let callStartToken = gen('CALL_START', '(', token);
1628
- let arrowToken = gen('->', '->', token);
1629
- arrowToken.newLine = true;
1630
- tokens.splice(i + 1, 0, callStartToken, arrowToken);
1631
- pendingCallEnds.push(currentIndent + 1);
1632
- return 3;
1633
- } else {
1634
- // Tag with args: inject , -> (call wrapping handled by addImplicitBracesAndParens)
1635
- let commaToken = gen(',', ',', token);
1636
- let arrowToken = gen('->', '->', token);
1637
- arrowToken.newLine = true;
1638
- tokens.splice(i + 1, 0, commaToken, arrowToken);
1639
- return 3;
1640
- }
1641
- }
1642
- }
1643
-
1644
- // ─────────────────────────────────────────────────────────────────────
1645
- // Bare component reference (PascalCase, no children, no args)
1646
- // Counter → Counter() so it gets treated as a component instantiation
1647
- // ─────────────────────────────────────────────────────────────────────
1648
- if (tag === 'IDENTIFIER' && isComponent(token[1]) &&
1649
- nextToken && (nextToken[0] === 'OUTDENT' || nextToken[0] === 'TERMINATOR')) {
1650
- tokens.splice(i + 1, 0, gen('CALL_START', '(', token), gen('CALL_END', ')', token));
1651
- return 3;
1652
- }
1653
-
1654
- return 1;
1655
- });
1656
- }
1657
-
1658
1395
  tagPostfixConditionals() {
1659
1396
  let original = null;
1660
1397