rip-lang 3.16.0 → 3.16.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/types.js CHANGED
@@ -316,6 +316,7 @@ export function installTypeSupport(Lexer) {
316
316
  function collectTypeExpression(tokens, j) {
317
317
  let typeTokens = [];
318
318
  let depth = 0;
319
+ let bracketStack = []; // tracks innermost open bracket: '{', '[', '(', '<'
319
320
  let startJ = j;
320
321
 
321
322
  while (j < tokens.length) {
@@ -333,6 +334,8 @@ function collectTypeExpression(tokens, j) {
333
334
  // Handle >> as two > closes (nested generics: Map<string, Set<number>>)
334
335
  if (tTag === 'SHIFT' && t[1] === '>>' && depth >= 2) {
335
336
  depth -= 2;
337
+ if (bracketStack[bracketStack.length - 1] === '<') bracketStack.pop();
338
+ if (bracketStack[bracketStack.length - 1] === '<') bracketStack.pop();
336
339
  typeTokens.push(t);
337
340
  j++;
338
341
  continue;
@@ -340,6 +343,11 @@ function collectTypeExpression(tokens, j) {
340
343
 
341
344
  if (isOpen) {
342
345
  depth++;
346
+ let kind = (tTag === '{') ? '{'
347
+ : (tTag === '[' || tTag === 'INDEX_START') ? '['
348
+ : (tTag === 'COMPARE' && t[1] === '<') ? '<'
349
+ : '(';
350
+ bracketStack.push(kind);
343
351
  typeTokens.push(t);
344
352
  j++;
345
353
  continue;
@@ -347,6 +355,7 @@ function collectTypeExpression(tokens, j) {
347
355
  if (isClose) {
348
356
  if (depth > 0) {
349
357
  depth--;
358
+ bracketStack.pop();
350
359
  typeTokens.push(t);
351
360
  j++;
352
361
  continue;
@@ -376,6 +385,38 @@ function collectTypeExpression(tokens, j) {
376
385
  }
377
386
  }
378
387
 
388
+ // Inside a bracketed type expression, INDENT/OUTDENT/TERMINATOR are
389
+ // pure layout tokens (multi-line type literal `{ \n field: T \n }`).
390
+ // They carry no semantic meaning and would otherwise leak their raw
391
+ // `[1]` value (e.g. an indent level integer like `2`) into the
392
+ // type string. INDENT/OUTDENT are dropped silently; TERMINATOR
393
+ // separates fields and is replaced with a synthetic `;` so the
394
+ // emitted type literal is valid TS (`{ a: T; b: U }`).
395
+ if (depth > 0 && (tTag === 'INDENT' || tTag === 'OUTDENT')) {
396
+ j++;
397
+ continue;
398
+ }
399
+ if (depth > 0 && tTag === 'TERMINATOR') {
400
+ typeTokens.push(['', ';']);
401
+ j++;
402
+ continue;
403
+ }
404
+
405
+ // Inside `{ ... }` the Rip rewriter sometimes drops TERMINATOR
406
+ // between fields (e.g. after `Record<string, string[]>` because `>`
407
+ // looks like a binary operator wanting a RHS). Detect a new field
408
+ // by seeing a PROPERTY token at the top of a `{` and inject `;` if
409
+ // the previously emitted token isn't already a separator/opener.
410
+ if (tTag === 'PROPERTY' &&
411
+ bracketStack[bracketStack.length - 1] === '{') {
412
+ let prev = typeTokens[typeTokens.length - 1];
413
+ let prevTag = prev?.[0];
414
+ let prevVal = prev?.[1];
415
+ let needsSep = prev && prevTag !== '{' && prevTag !== ',' &&
416
+ !(prevTag === '' && prevVal === ';');
417
+ if (needsSep) typeTokens.push(['', ';']);
418
+ }
419
+
379
420
  // => at depth 0: function type arrow, continue collecting
380
421
  // -> at depth 0: code arrow, handled as delimiter above
381
422
  typeTokens.push(t);
@@ -392,7 +433,46 @@ function buildTypeString(typeTokens) {
392
433
  if (typeTokens.length === 0) return '';
393
434
  // Bare => (no params) means () => — add empty parens
394
435
  if (typeTokens[0]?.[0] === '=>') typeTokens.unshift(['', '()']);
395
- let typeStr = typeTokens.map(t => t[1]).join(' ').replace(/\s+/g, ' ').trim();
436
+ // Validation: `::` inside `{ ... }` in type position is illegal.
437
+ // `::` binds a name to a type (params, var decls, return types).
438
+ // Inside a structural type literal `{ ... }`, fields are key→type
439
+ // pairs and use `:` (TS-style), the same way TS type literals do.
440
+ // `::` has no role inside a type literal — every `:` there is
441
+ // already unambiguously a type separator.
442
+ {
443
+ let curlyDepth = 0;
444
+ for (let t of typeTokens) {
445
+ let tag = t[0];
446
+ if (tag === '{') curlyDepth++;
447
+ else if (tag === '}') curlyDepth--;
448
+ else if (tag === 'TYPE_ANNOTATION' && curlyDepth > 0) {
449
+ let loc = t.loc;
450
+ let where = loc ? ` (line ${loc.r}, col ${loc.c})` : '';
451
+ let err = new Error(
452
+ `Use \`:\` (not \`::\`) inside a structural type literal${where}. ` +
453
+ `\`::\` binds a name to a type; inside \`{ ... }\` in type ` +
454
+ `position, fields use \`:\` (TS-style).`
455
+ );
456
+ err.loc = loc;
457
+ throw err;
458
+ }
459
+ }
460
+ }
461
+ // Inline structural / function-param property-name optional marker:
462
+ // an IDENTIFIER carrying `.data.optional` and followed by TYPE_ANNOTATION
463
+ // gets a trailing `?` appended to its emitted name. The lexer stripped
464
+ // the trailing `?` from the token text but flagged it on `.data.optional`.
465
+ let parts = typeTokens.map((t, i) => {
466
+ let next = typeTokens[i + 1];
467
+ // Re-attach the trailing `?` for optional property names. The next
468
+ // separator is `::` (TYPE_ANNOTATION) in function param lists, or
469
+ // `:` inside an inline structural type literal `{ x?: T }`.
470
+ if (t.data?.optional && next && (next[0] === 'TYPE_ANNOTATION' || next[0] === ':')) {
471
+ return `${t[1]}?`;
472
+ }
473
+ return t[1];
474
+ });
475
+ let typeStr = parts.join(' ').replace(/\s+/g, ' ').trim();
396
476
  typeStr = typeStr
397
477
  .replace(/\s*<\s*/g, '<').replace(/\s*>\s*/g, '>')
398
478
  .replace(/\s*\[\s*/g, '[').replace(/\s*\]\s*/g, ']')
@@ -485,13 +565,13 @@ function collectStructuralType(tokens, indentIdx) {
485
565
  (/^[a-zA-Z_$]/.test(tokens[j][1]) && tokens[j + 1]?.[0] === 'TYPE_ANNOTATION'))) {
486
566
  readonly = true;
487
567
  propName = tokens[j][1];
488
- // Carry predicate flag through
489
- if (tokens[j].data?.predicate) optional = true;
568
+ // Carry optional flag through
569
+ if (tokens[j].data?.optional) optional = true;
490
570
  j++;
491
571
  }
492
572
 
493
- // Check for ? (optional property) — lexer stores as .data.predicate
494
- if (t.data?.predicate) optional = true;
573
+ // Check for ? (optional property) — lexer stores as .data.optional
574
+ if (t.data?.optional) optional = true;
495
575
  // Also check for standalone ? token
496
576
  if (tokens[j]?.[1] === '?' && !tokens[j]?.spaced) {
497
577
  optional = true;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes