rip-lang 3.14.4 → 3.15.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.
@@ -0,0 +1,1021 @@
1
+ import { SCHEMA_INTRINSIC_DECLS, emitSchemaTypes } from "@rip-lang/schema/dts-emit";
2
+ import { setTypesEmitter } from "./compiler.js";
3
+
4
+ // Type System — .d.ts emission for Rip (CLI / typecheck only).
5
+ //
6
+ // This module is a CLI/editor-only sidecar:
7
+ //
8
+ // emitTypes(tokens, sexpr, source) — generates .d.ts from annotated
9
+ // tokens and the parsed s-expression tree.
10
+ //
11
+ // INTRINSIC_TYPE_DECLS / INTRINSIC_FN_DECL / ARIA_TYPE_DECLS /
12
+ // SIGNAL_*, COMPUTED_*, EFFECT_* — declaration tables consumed by
13
+ // emitTypes() and by typecheck.js when building the virtual TS
14
+ // file. Browser code never references these.
15
+ //
16
+ // The browser bundle must NOT import this module — see
17
+ // scripts/check-bundle-graph.js. Token-level type stripping
18
+ // (installTypeSupport) and runtime enum codegen (emitEnum) live
19
+ // in types.js, which the browser does need.
20
+
21
+ // ============================================================================
22
+ // Shared type declaration constants — single source of truth
23
+ // ============================================================================
24
+ // Used by emitTypes() for .d.ts emission and by compileForCheck() in
25
+ // typecheck.js for virtual .ts injection. Keeping them here eliminates
26
+ // divergence between what gets written to disk and what TS analyzes.
27
+
28
+ export const INTRINSIC_TYPE_DECLS = [
29
+ 'type __RipElementMap = HTMLElementTagNameMap & Omit<SVGElementTagNameMap, keyof HTMLElementTagNameMap>;',
30
+ 'type __RipTag = keyof __RipElementMap;',
31
+ "type __RipBrowserElement = Omit<HTMLElement, 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & { hidden: boolean | 'until-found'; setAttribute(qualifiedName: string, value: any): void; querySelector(selectors: string): __RipBrowserElement | null; querySelectorAll(selectors: string): NodeListOf<__RipBrowserElement>; closest(selectors: string): __RipBrowserElement | null; };",
32
+ "type __RipDomEl<K extends __RipTag> = Omit<__RipElementMap[K], 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & __RipBrowserElement;",
33
+ "type __RipAttrKeys<T> = { [K in keyof T]-?: K extends 'style' | 'classList' | 'className' | 'nodeValue' | 'textContent' | 'innerHTML' | 'innerText' | 'outerHTML' | 'outerText' | 'scrollLeft' | 'scrollTop' ? never : K extends `on${string}` | `aria${string}Element` | `aria${string}Elements` ? never : T[K] extends (...args: any[]) => any ? never : (<V>() => V extends Pick<T, K> ? 1 : 2) extends (<V>() => V extends { -readonly [P in K]: T[P] } ? 1 : 2) ? K : never }[keyof T] & string;",
34
+ 'type __RipEvents = { [K in keyof HTMLElementEventMap as `@${K}`]?: ((event: HTMLElementEventMap[K]) => void) | null };',
35
+ 'type __RipClassValue = string | boolean | null | undefined | Record<string, boolean> | __RipClassValue[];',
36
+ 'type __RipProps<K extends __RipTag> = { [P in __RipAttrKeys<__RipElementMap[K]>]?: __RipElementMap[K][P] } & __RipEvents & { ref?: string; class?: __RipClassValue | __RipClassValue[]; style?: string; [k: `data-${string}`]: any; [k: `aria-${string}`]: any };',
37
+ ];
38
+
39
+ export const INTRINSIC_FN_DECL = 'declare function __ripEl<K extends __RipTag>(tag: K, props?: __RipProps<K>): void;';
40
+
41
+ export const ARIA_TYPE_DECLS = [
42
+ 'type __RipAriaNavHandlers = { next?: () => void; prev?: () => void; first?: () => void; last?: () => void; select?: () => void; dismiss?: () => void; tab?: () => void; char?: () => void; };',
43
+ "declare const ARIA: {",
44
+ " bindPopover(open: boolean, popover: () => Element | null | undefined, setOpen: (isOpen: boolean) => void, source?: (() => Element | null | undefined) | null): void;",
45
+ " bindDialog(open: boolean, dialog: () => Element | null | undefined, setOpen: (isOpen: boolean) => void, dismissable?: boolean): void;",
46
+ " popupDismiss(open: boolean, popup: () => Element | null | undefined, close: () => void, els?: Array<() => Element | null | undefined>, repos?: (() => void) | null): void;",
47
+ " popupGuard(delay?: number): any;",
48
+ " listNav(event: KeyboardEvent, handlers: __RipAriaNavHandlers): void;",
49
+ " rovingNav(event: KeyboardEvent, handlers: __RipAriaNavHandlers, orientation?: 'vertical' | 'horizontal' | 'both'): void;",
50
+ " positionBelow(trigger: Element | null | undefined, popup: Element | null | undefined, gap?: number, setVisible?: boolean): void;",
51
+ " position(trigger: Element | null | undefined, floating: Element | null | undefined, opts?: any): void;",
52
+ " trapFocus(panel: Element | null | undefined): void;",
53
+ " wireAria(panel: Element, id: string): void;",
54
+ " lockScroll(instance: any): void;",
55
+ " unlockScroll(instance: any): void;",
56
+ " hasAnchor: boolean;",
57
+ " [key: string]: any;",
58
+ "};",
59
+ ];
60
+
61
+ export const SIGNAL_INTERFACE = 'interface Signal<T> { value: T; read(): T; lock(): Signal<T>; free(): Signal<T>; kill(): T; }';
62
+ export const SIGNAL_FN = 'declare function __state<T>(value: T | Signal<T>): Signal<T>;';
63
+ export const COMPUTED_INTERFACE = 'interface Computed<T> { readonly value: T; read(): T; lock(): Computed<T>; free(): Computed<T>; kill(): T; }';
64
+ export const COMPUTED_FN = 'declare function __computed<T>(fn: () => T): Computed<T>;';
65
+ export const EFFECT_FN = 'declare function __effect(fn: () => void | (() => void)): () => void;';
66
+
67
+ // ============================================================================
68
+ // emitTypes — generate .d.ts from annotated tokens + s-expression tree
69
+ // ============================================================================
70
+
71
+ export function emitTypes(tokens, sexpr = null, source = '') {
72
+ let lines = [];
73
+ let indentLevel = 0;
74
+ let indentStr = ' ';
75
+ let indent = () => indentStr.repeat(indentLevel);
76
+ let inClass = false;
77
+ let classFields = new Set(); // Track emitted field names to avoid duplicates
78
+ let usesSignal = false;
79
+ let usesComputed = false;
80
+ let usesRipIntrinsicProps = false;
81
+ const sourceLines = typeof source === 'string' ? source.split('\n') : [];
82
+
83
+ // Pre-scan: detect reactive operators regardless of type annotations.
84
+ // This ensures the DTS preamble declares __state/__computed/__effect
85
+ // so TypeScript can infer types for untyped reactive variables.
86
+ for (let i = 0; i < tokens.length; i++) {
87
+ const tag = tokens[i][0];
88
+ if (tag === 'REACTIVE_ASSIGN') usesSignal = true;
89
+ else if (tag === 'COMPUTED_ASSIGN') usesComputed = true;
90
+ }
91
+
92
+ // Format { prop; prop } into multi-line block. Only applies when the
93
+ // body is a single { ... } object — not a union like { ... } | { ... }.
94
+ let emitBlock = (prefix, body, suffix) => {
95
+ if (body.startsWith('{ ') && body.endsWith(' }')) {
96
+ let depth = 0, firstTopClose = -1;
97
+ for (let c = 0; c < body.length; c++) {
98
+ if (body[c] === '{') depth++;
99
+ else if (body[c] === '}') { depth--; if (depth === 0) { firstTopClose = c; break; } }
100
+ }
101
+ if (firstTopClose === body.length - 1) {
102
+ // Depth-aware split on '; ' at top level only
103
+ let inner = body.slice(2, -2);
104
+ let props = [], start = 0, d = 0;
105
+ for (let c = 0; c < inner.length; c++) {
106
+ if (inner[c] === '{') d++;
107
+ else if (inner[c] === '}') d--;
108
+ else if (d === 0 && inner[c] === ';' && inner[c + 1] === ' ') {
109
+ props.push(inner.slice(start, c));
110
+ start = c + 2;
111
+ }
112
+ }
113
+ if (start < inner.length) props.push(inner.slice(start));
114
+ props = props.filter(p => p.trim());
115
+ if (props.length > 0) {
116
+ lines.push(`${indent()}${prefix}{`);
117
+ indentLevel++;
118
+ for (let prop of props) lines.push(`${indent()}${prop};`);
119
+ indentLevel--;
120
+ lines.push(`${indent()}}${suffix}`);
121
+ return;
122
+ }
123
+ }
124
+ }
125
+ lines.push(`${indent()}${prefix}${body}${suffix}`);
126
+ };
127
+
128
+ // Skip past a default value expression (= ...) returning the new j
129
+ let skipDefault = (tokens, j) => {
130
+ j++; // skip =
131
+ let dd = 0;
132
+ while (j < tokens.length) {
133
+ let dt = tokens[j];
134
+ if (dt[0] === '(' || dt[0] === '[' || dt[0] === '{') dd++;
135
+ if (dt[0] === ')' || dt[0] === ']' || dt[0] === '}') {
136
+ if (dd === 0) break; // closing bracket of enclosing structure
137
+ dd--;
138
+ }
139
+ if (dd === 0 && dt[1] === ',') break;
140
+ j++;
141
+ }
142
+ return j;
143
+ };
144
+
145
+ // Collect a destructured object { a, b: c, ...rest } recursively.
146
+ // tokens[startJ] must be '{'. Returns { patternStr, typeStr, endJ, hasAnyType }.
147
+ let collectDestructuredObj = (tokens, startJ) => {
148
+ let props = [];
149
+ let hasAnyType = false;
150
+ let j = startJ + 1; // skip opening {
151
+ let d = 1;
152
+ while (j < tokens.length && d > 0) {
153
+ if (tokens[j][0] === '{') d++;
154
+ if (tokens[j][0] === '}') d--;
155
+ if (d <= 0) { j++; break; }
156
+
157
+ // Rest: ...name
158
+ if (tokens[j][0] === '...' || tokens[j][0] === 'SPREAD') {
159
+ j++;
160
+ if (tokens[j]?.[0] === 'IDENTIFIER') {
161
+ props.push({ kind: 'rest', propName: tokens[j][1] });
162
+ j++;
163
+ }
164
+ continue;
165
+ }
166
+
167
+ // PROPERTY : ... (rename, nested object, or nested array)
168
+ if (tokens[j][0] === 'PROPERTY' && tokens[j + 1]?.[0] === ':') {
169
+ let propName = tokens[j][1];
170
+ j += 2; // skip PROPERTY and :
171
+ // Nested object
172
+ if (tokens[j]?.[0] === '{') {
173
+ let inner = collectDestructuredObj(tokens, j);
174
+ if (inner.hasAnyType) hasAnyType = true;
175
+ props.push({ kind: 'nested-obj', propName, inner });
176
+ j = inner.endJ;
177
+ continue;
178
+ }
179
+ // Nested array
180
+ if (tokens[j]?.[0] === '[') {
181
+ let inner = collectDestructuredArr(tokens, j);
182
+ if (inner.hasAnyType) hasAnyType = true;
183
+ props.push({ kind: 'nested-arr', propName, inner });
184
+ j = inner.endJ;
185
+ continue;
186
+ }
187
+ // Simple rename: PROPERTY : IDENTIFIER
188
+ if (tokens[j]?.[0] === 'IDENTIFIER') {
189
+ let localName = tokens[j][1];
190
+ let type = tokens[j].data?.type;
191
+ if (type) hasAnyType = true;
192
+ let hasDefault = tokens[j + 1]?.[0] === '=';
193
+ props.push({ kind: 'rename', propName, localName, type: type ? expandSuffixes(type) : null, hasDefault });
194
+ j++;
195
+ if (hasDefault) j = skipDefault(tokens, j);
196
+ }
197
+ continue;
198
+ }
199
+
200
+ // Simple: IDENTIFIER (possibly with default)
201
+ if (tokens[j][0] === 'IDENTIFIER') {
202
+ let name = tokens[j][1];
203
+ let type = tokens[j].data?.type;
204
+ if (type) hasAnyType = true;
205
+ let hasDefault = tokens[j + 1]?.[0] === '=';
206
+ props.push({ kind: 'simple', propName: name, type: type ? expandSuffixes(type) : null, hasDefault });
207
+ j++;
208
+ if (hasDefault) j = skipDefault(tokens, j);
209
+ continue;
210
+ }
211
+
212
+ // Skip commas and other tokens
213
+ j++;
214
+ }
215
+
216
+ // Build pattern and type strings
217
+ let patternParts = [];
218
+ let typeParts = [];
219
+ for (let p of props) {
220
+ if (p.kind === 'rest') {
221
+ patternParts.push(`...${p.propName}`);
222
+ typeParts.push(`[key: string]: unknown`);
223
+ } else if (p.kind === 'nested-obj' || p.kind === 'nested-arr') {
224
+ patternParts.push(`${p.propName}: ${p.inner.patternStr}`);
225
+ typeParts.push(`${p.propName}: ${p.inner.typeStr}`);
226
+ } else if (p.kind === 'rename') {
227
+ patternParts.push(`${p.propName}: ${p.localName}`);
228
+ typeParts.push(`${p.propName}${p.hasDefault ? '?' : ''}: ${p.type || 'any'}`);
229
+ } else {
230
+ patternParts.push(p.propName);
231
+ typeParts.push(`${p.propName}${p.hasDefault ? '?' : ''}: ${p.type || 'any'}`);
232
+ }
233
+ }
234
+ return {
235
+ patternStr: `{${patternParts.join(', ')}}`,
236
+ typeStr: `{${typeParts.join(', ')}}`,
237
+ endJ: j,
238
+ hasAnyType,
239
+ };
240
+ };
241
+
242
+ // Collect a destructured array [ a, b ] from tokens.
243
+ // tokens[startJ] must be '['. Returns { patternStr, typeStr, endJ, hasAnyType }.
244
+ let collectDestructuredArr = (tokens, startJ) => {
245
+ let names = [];
246
+ let elemTypes = [];
247
+ let hasAnyType = false;
248
+ let j = startJ + 1; // skip opening [
249
+ let d = 1;
250
+ while (j < tokens.length && d > 0) {
251
+ if (tokens[j][0] === '[') d++;
252
+ if (tokens[j][0] === ']') d--;
253
+ if (d > 0 && tokens[j][0] === 'IDENTIFIER') {
254
+ let name = tokens[j][1];
255
+ let type = tokens[j].data?.type;
256
+ names.push(name);
257
+ elemTypes.push(type ? expandSuffixes(type) : null);
258
+ if (type) hasAnyType = true;
259
+ }
260
+ j++;
261
+ }
262
+ return {
263
+ patternStr: `[${names.join(', ')}]`,
264
+ typeStr: `[${elemTypes.map(t => t || 'any').join(', ')}]`,
265
+ endJ: j,
266
+ hasAnyType,
267
+ };
268
+ };
269
+
270
+ // Collect function parameters (handles simple, destructured, rest, defaults)
271
+ let collectParams = (tokens, startIdx) => {
272
+ let params = [];
273
+ let fields = []; // Track @param:: type for class field emission
274
+ let j = startIdx;
275
+ let openTag = tokens[j]?.[0];
276
+ if (openTag !== 'CALL_START' && openTag !== 'PARAM_START') return { params, fields, endIdx: j };
277
+ let closeTag = openTag === 'CALL_START' ? 'CALL_END' : 'PARAM_END';
278
+ j++;
279
+ let depth = 0;
280
+
281
+ while (j < tokens.length && !(tokens[j][0] === closeTag && depth === 0)) {
282
+ let tok = tokens[j];
283
+
284
+ // Skip commas at depth 0
285
+ if (tok[1] === ',' && depth === 0) { j++; continue; }
286
+
287
+ // Track nesting
288
+ if (tok[0] === '{' || tok[0] === '[' || tok[0] === 'CALL_START' ||
289
+ tok[0] === 'PARAM_START' || tok[0] === 'INDEX_START') depth++;
290
+ if (tok[0] === '}' || tok[0] === ']' || tok[0] === 'CALL_END' ||
291
+ tok[0] === 'PARAM_END' || tok[0] === 'INDEX_END') { depth--; j++; continue; }
292
+
293
+ // @ prefix (constructor shorthand: @name)
294
+ if (tok[0] === '@') {
295
+ j++;
296
+ if (tokens[j]?.[0] === 'PROPERTY' || tokens[j]?.[0] === 'IDENTIFIER') {
297
+ let name = tokens[j][1];
298
+ let type = tokens[j].data?.type;
299
+ params.push(type ? `${name}: ${expandSuffixes(type)}` : name);
300
+ if (type) fields.push({ name, type: expandSuffixes(type) });
301
+ j++;
302
+ }
303
+ continue;
304
+ }
305
+
306
+ // Rest parameter: ...name
307
+ if (tok[0] === 'SPREAD' || tok[1] === '...') {
308
+ j++;
309
+ if (tokens[j]?.[0] === 'IDENTIFIER') {
310
+ let name = tokens[j][1];
311
+ let type = tokens[j].data?.type;
312
+ params.push(type ? `...${name}: ${expandSuffixes(type)}` : `...${name}: any[]`);
313
+ j++;
314
+ }
315
+ continue;
316
+ }
317
+
318
+ // Destructured object parameter: { a, b }
319
+ if (tok[0] === '{') {
320
+ depth--; // undo the depth++ from nesting tracker above
321
+ let result = collectDestructuredObj(tokens, j);
322
+ j = result.endJ;
323
+ if (result.hasAnyType) {
324
+ params.push(`${result.patternStr}: ${result.typeStr}`);
325
+ } else {
326
+ params.push(result.patternStr);
327
+ }
328
+ continue;
329
+ }
330
+
331
+ // Destructured array parameter: [ a, b ]
332
+ if (tok[0] === '[') {
333
+ depth--; // undo the depth++ from nesting tracker above
334
+ let result = collectDestructuredArr(tokens, j);
335
+ j = result.endJ;
336
+ if (result.hasAnyType) {
337
+ params.push(`${result.patternStr}: ${result.typeStr}`);
338
+ } else {
339
+ params.push(result.patternStr);
340
+ }
341
+ continue;
342
+ }
343
+
344
+ // Simple identifier parameter
345
+ if (tok[0] === 'IDENTIFIER') {
346
+ let paramName = tok[1];
347
+ let paramType = tok.data?.type;
348
+
349
+ // Check for default value (skip = and the default expression)
350
+ let hasDefault = false;
351
+ if (tokens[j + 1]?.[0] === '=') {
352
+ hasDefault = true;
353
+ }
354
+
355
+ let isOptional = hasDefault || tok.data?.predicate;
356
+ if (paramType) {
357
+ params.push(`${paramName}${isOptional ? '?' : ''}: ${expandSuffixes(paramType)}`);
358
+ } else {
359
+ params.push(paramName);
360
+ }
361
+ j++;
362
+
363
+ // Skip past default value expression
364
+ if (hasDefault) {
365
+ j++; // skip =
366
+ let dd = 0;
367
+ while (j < tokens.length) {
368
+ let dt = tokens[j];
369
+ if (dt[0] === '(' || dt[0] === '[' || dt[0] === '{') dd++;
370
+ if (dt[0] === ')' || dt[0] === ']' || dt[0] === '}') dd--;
371
+ if (dd === 0 && (dt[1] === ',' || dt[0] === 'CALL_END')) break;
372
+ j++;
373
+ }
374
+ }
375
+ continue;
376
+ }
377
+
378
+ j++;
379
+ }
380
+
381
+ return { params, fields, endIdx: j };
382
+ };
383
+
384
+ for (let i = 0; i < tokens.length; i++) {
385
+ let t = tokens[i];
386
+ let tag = t[0];
387
+
388
+ // Track export flag
389
+ let exported = false;
390
+ if (tag === 'EXPORT') {
391
+ exported = true;
392
+ i++;
393
+ if (i >= tokens.length) break;
394
+ t = tokens[i];
395
+ tag = t[0];
396
+
397
+ // Export default
398
+ if (tag === 'DEFAULT') {
399
+ i++;
400
+ if (i >= tokens.length) break;
401
+ t = tokens[i];
402
+ tag = t[0];
403
+
404
+ // export default IDENTIFIER (re-export)
405
+ if (tag === 'IDENTIFIER') {
406
+ lines.push(`${indent()}export default ${t[1]};`);
407
+ }
408
+ // export default { ... } or other expressions — skip for now
409
+ continue;
410
+ }
411
+ }
412
+
413
+ // Import statements — pass through for type references
414
+ if (tag === 'IMPORT') {
415
+ let importTokens = [];
416
+ let j = i + 1;
417
+ while (j < tokens.length && tokens[j][0] !== 'TERMINATOR') {
418
+ importTokens.push(tokens[j]);
419
+ j++;
420
+ }
421
+ // Reconstruct: join with spaces, then clean up spacing
422
+ let raw = 'import ' + importTokens.map(tk => tk[1]).join(' ');
423
+ raw = raw.replace(/\s+/g, ' ')
424
+ .replace(/\s*,\s*/g, ', ')
425
+ .replace(/\{\s*/g, '{ ').replace(/\s*\}/g, ' }')
426
+ .trim();
427
+ lines.push(`${indent()}${raw};`);
428
+ i = j;
429
+ continue;
430
+ }
431
+
432
+ // TYPE_DECL marker — emit type alias or interface
433
+ if (tag === 'TYPE_DECL') {
434
+ let data = t.data;
435
+ if (!data) continue;
436
+ let exp = (exported || data.exported) ? 'export ' : '';
437
+ let params = data.typeParams || '';
438
+
439
+ if (data.kind === 'overload') {
440
+ // Emit function overload signature from saved tokens
441
+ let ot = data.overloadTokens;
442
+ let nameToken = ot[1]; // DEF is [0], name is [1]
443
+ let { params: paramList } = collectParams(ot, 2);
444
+ let returnType = nameToken.data?.returnType;
445
+ let ret = returnType ? `: ${expandSuffixes(returnType)}` : '';
446
+ let declare = inClass ? '' : (exp ? '' : 'declare ');
447
+ let typeParams = data.typeParams || '';
448
+ if (inClass) {
449
+ lines.push(`${indent()}${data.name}${typeParams}(${paramList.join(', ')})${ret};`);
450
+ } else {
451
+ lines.push(`${indent()}${exp}${declare}function ${data.name}${typeParams}(${paramList.join(', ')})${ret};`);
452
+ }
453
+ } else if (data.kind === 'interface') {
454
+ let ext = data.extends ? ` extends ${data.extends}` : '';
455
+ emitBlock(`${exp}interface ${data.name}${params}${ext} `, data.typeText || '{}', '');
456
+ } else {
457
+ let typeText = expandSuffixes(data.typeText || '');
458
+ emitBlock(`${exp}type ${data.name}${params} = `, typeText, ';');
459
+ }
460
+ continue;
461
+ }
462
+
463
+ // ENUM — emit enum declaration for .d.ts
464
+ if (tag === 'ENUM') {
465
+ let exp = exported ? 'export ' : '';
466
+ let nameToken = tokens[i + 1];
467
+ if (!nameToken) continue;
468
+ let enumName = nameToken[1];
469
+
470
+ // Find INDENT ... OUTDENT for enum body
471
+ let j = i + 2;
472
+ if (tokens[j]?.[0] === 'INDENT') {
473
+ lines.push(`${indent()}${exp}enum ${enumName} {`);
474
+ indentLevel++;
475
+ j++;
476
+ let members = [];
477
+
478
+ while (j < tokens.length && tokens[j][0] !== 'OUTDENT') {
479
+ if (tokens[j][0] === 'TERMINATOR') { j++; continue; }
480
+ if (tokens[j][0] === 'IDENTIFIER') {
481
+ let memberName = tokens[j][1];
482
+ j++;
483
+ if (tokens[j]?.[1] === '=') {
484
+ j++;
485
+ let val = tokens[j]?.[1];
486
+ members.push(`${memberName} = ${val}`);
487
+ j++;
488
+ } else {
489
+ members.push(memberName);
490
+ }
491
+ } else {
492
+ j++;
493
+ }
494
+ }
495
+
496
+ for (let m = 0; m < members.length; m++) {
497
+ let comma = m < members.length - 1 ? ',' : '';
498
+ lines.push(`${indent()}${members[m]}${comma}`);
499
+ }
500
+
501
+ indentLevel--;
502
+ lines.push(`${indent()}}`);
503
+ }
504
+ // Don't advance i — the parser still needs to see ENUM tokens
505
+ continue;
506
+ }
507
+
508
+ // CLASS — emit class declaration
509
+ if (tag === 'CLASS') {
510
+ let exp = exported ? 'export ' : '';
511
+ let classNameToken = tokens[i + 1];
512
+ if (!classNameToken) continue;
513
+ let className = classNameToken[1];
514
+
515
+ // Check for extends
516
+ let ext = '';
517
+ let j = i + 2;
518
+ if (tokens[j]?.[0] === 'EXTENDS') {
519
+ ext = ` extends ${tokens[j + 1]?.[1] || ''}`;
520
+ j += 2;
521
+ }
522
+
523
+ // Only emit if there are typed members
524
+ if (tokens[j]?.[0] === 'INDENT') {
525
+ let hasTypedMembers = false;
526
+ let k = j + 1;
527
+ while (k < tokens.length && tokens[k][0] !== 'OUTDENT') {
528
+ if (tokens[k].data?.type || tokens[k].data?.returnType) {
529
+ hasTypedMembers = true;
530
+ break;
531
+ }
532
+ k++;
533
+ }
534
+ if (hasTypedMembers) {
535
+ lines.push(`${indent()}${exp}declare class ${className}${ext} {`);
536
+ inClass = true;
537
+ classFields.clear();
538
+ indentLevel++;
539
+ }
540
+ }
541
+ continue;
542
+ }
543
+
544
+ // DEF — emit function or method declaration
545
+ if (tag === 'DEF') {
546
+ let nameToken = tokens[i + 1];
547
+ if (!nameToken) continue;
548
+ let fnName = nameToken[1];
549
+ let returnType = nameToken.data?.returnType;
550
+ if (!returnType && nameToken.data?.await === true) returnType = 'void';
551
+ let typeParams = nameToken.data?.typeParams || '';
552
+
553
+ let { params, endIdx } = collectParams(tokens, i + 2);
554
+
555
+ // Only emit if there are type annotations
556
+ if (returnType || params.some(p => p.includes(':'))) {
557
+ let exp = exported ? 'export ' : '';
558
+ let declare = inClass ? '' : (exported ? '' : 'declare ');
559
+ let ret = returnType ? `: ${expandSuffixes(returnType)}` : '';
560
+ let paramStr = params.join(', ');
561
+ if (inClass) {
562
+ lines.push(`${indent()}${fnName}${typeParams}(${paramStr})${ret};`);
563
+ } else {
564
+ lines.push(`${indent()}${exp}${declare}function ${fnName}${typeParams}(${paramStr})${ret};`);
565
+ }
566
+ }
567
+ i = endIdx; // advance past params to avoid leaking destructured typed identifiers
568
+ continue;
569
+ }
570
+
571
+ // Class method block: { PROPERTY ... PROPERTY ... }
572
+ // Contains one or more methods separated by TERMINATOR
573
+ if (tag === '{' && inClass) {
574
+ let j = i + 1;
575
+ let braceDepth = 1;
576
+
577
+ while (j < tokens.length && braceDepth > 0) {
578
+ let tok = tokens[j];
579
+
580
+ if (tok[0] === '{') { braceDepth++; j++; continue; }
581
+ if (tok[0] === '}') { braceDepth--; j++; continue; }
582
+ if (tok[0] === 'TERMINATOR') { j++; continue; }
583
+
584
+ // Found a method: PROPERTY "name" : PARAM_START ... PARAM_END -> body
585
+ if (tok[0] === 'PROPERTY' && braceDepth === 1) {
586
+ let methodName = tok[1];
587
+ let returnType = tok.data?.returnType;
588
+ j++;
589
+
590
+ // Skip : separator
591
+ if (tokens[j]?.[1] === ':') j++;
592
+
593
+ let params = [];
594
+ let fields = [];
595
+ if (tokens[j]?.[0] === 'PARAM_START') {
596
+ let result = collectParams(tokens, j);
597
+ params = result.params;
598
+ fields = result.fields;
599
+ j = result.endIdx + 1;
600
+ }
601
+
602
+ // Skip -> and method body (INDENT ... OUTDENT)
603
+ if (tokens[j]?.[0] === '->' || tokens[j]?.[0] === '=>') j++;
604
+ if (tokens[j]?.[0] === 'INDENT') {
605
+ let d = 1;
606
+ j++;
607
+ while (j < tokens.length && d > 0) {
608
+ if (tokens[j][0] === 'INDENT') d++;
609
+ if (tokens[j][0] === 'OUTDENT') d--;
610
+ j++;
611
+ }
612
+ }
613
+
614
+ if (returnType || params.some(p => p.includes(':'))) {
615
+ let ret = returnType ? `: ${expandSuffixes(returnType)}` : '';
616
+ let paramStr = params.join(', ');
617
+ // Emit field declarations for constructor @param:: type shorthand
618
+ if (methodName === 'constructor' && fields.length) {
619
+ for (let f of fields) {
620
+ if (!classFields.has(f.name)) {
621
+ lines.push(`${indent()}${f.name}: ${f.type};`);
622
+ classFields.add(f.name);
623
+ }
624
+ }
625
+ }
626
+ lines.push(`${indent()}${methodName}(${paramStr})${ret};`);
627
+ }
628
+ continue;
629
+ }
630
+
631
+ j++;
632
+ }
633
+
634
+ i = j - 1;
635
+ continue;
636
+ }
637
+
638
+ // Track INDENT/OUTDENT for class body
639
+ if (tag === 'INDENT') {
640
+ continue;
641
+ }
642
+ if (tag === 'OUTDENT') {
643
+ if (inClass) {
644
+ indentLevel--;
645
+ lines.push(`${indent()}}`);
646
+ inClass = false;
647
+ }
648
+ continue;
649
+ }
650
+
651
+ // Arrow function assignment: name = (params) -> body
652
+ if (tag === 'IDENTIFIER' && !inClass && tokens[i + 1]?.[0] === '=' &&
653
+ (tokens[i + 2]?.[0] === 'PARAM_START' || tokens[i + 2]?.[0] === '(')) {
654
+ let fnName = t[1];
655
+ let j = i + 2;
656
+
657
+ let { params } = collectParams(tokens, j);
658
+
659
+ // Find the -> or => token to get return type
660
+ let k = j;
661
+ let depth = 0;
662
+ while (k < tokens.length) {
663
+ if (tokens[k][0] === 'PARAM_START' || tokens[k][0] === '(') depth++;
664
+ if (tokens[k][0] === 'PARAM_END' || tokens[k][0] === ')') depth--;
665
+ if (depth === 0 && (tokens[k][0] === '->' || tokens[k][0] === '=>')) break;
666
+ k++;
667
+ }
668
+ let returnType = tokens[k]?.data?.returnType;
669
+
670
+ if (returnType || params.some(p => p.includes(':'))) {
671
+ let exp = exported ? 'export ' : '';
672
+ let declare = exported ? '' : 'declare ';
673
+ let ret = returnType ? `: ${expandSuffixes(returnType)}` : '';
674
+ let paramStr = params.join(', ');
675
+ lines.push(`${indent()}${exp}${declare}function ${fnName}(${paramStr})${ret};`);
676
+ continue;
677
+ }
678
+ }
679
+
680
+ // Variable assignments with type annotations
681
+ if (tag === 'IDENTIFIER' && t.data?.type) {
682
+ let varName = t[1];
683
+ let type = expandSuffixes(t.data.type);
684
+ let next = tokens[i + 1];
685
+
686
+ if (next) {
687
+ let exp = exported ? 'export ' : '';
688
+ let declare = exported ? '' : 'declare ';
689
+
690
+ if (next[0] === 'READONLY_ASSIGN') {
691
+ lines.push(`${indent()}${exp}${declare}const ${varName}: ${type};`);
692
+ } else if (next[0] === 'REACTIVE_ASSIGN') {
693
+ usesSignal = true;
694
+ lines.push(`${indent()}${exp}${declare}const ${varName}: Signal<${type}>;`);
695
+ } else if (next[0] === 'COMPUTED_ASSIGN') {
696
+ usesComputed = true;
697
+ lines.push(`${indent()}${exp}${declare}const ${varName}: Computed<${type}>;`);
698
+ } else if (next[0] === 'EFFECT') {
699
+ lines.push(`${indent()}${exp}${declare}const ${varName}: () => void;`);
700
+ } else if (next[0] === '=') {
701
+ // Check if RHS is an arrow function with return type
702
+ let arrowIdx = i + 2;
703
+ // Skip past PARAM_START ... PARAM_END if present
704
+ if (tokens[arrowIdx]?.[0] === 'PARAM_START') {
705
+ let d = 1, k = arrowIdx + 1;
706
+ while (k < tokens.length && d > 0) {
707
+ if (tokens[k][0] === 'PARAM_START') d++;
708
+ if (tokens[k][0] === 'PARAM_END') d--;
709
+ k++;
710
+ }
711
+ arrowIdx = k;
712
+ }
713
+ let arrowToken = tokens[arrowIdx];
714
+ if (arrowToken && (arrowToken[0] === '->' || arrowToken[0] === '=>') &&
715
+ arrowToken.data?.returnType) {
716
+ // Typed arrow function assignment
717
+ let returnType = expandSuffixes(arrowToken.data.returnType);
718
+ let { params } = collectParams(tokens, i + 2);
719
+ let paramStr = params.join(', ');
720
+ lines.push(`${indent()}${exp}${declare}function ${varName}(${paramStr}): ${returnType};`);
721
+ } else if (inClass) {
722
+ lines.push(`${indent()}${varName}: ${type};`);
723
+ classFields.add(varName);
724
+ } else {
725
+ lines.push(`${indent()}${exp}let ${varName}: ${type};`);
726
+ }
727
+ } else if (inClass) {
728
+ // Class property without assignment
729
+ lines.push(`${indent()}${varName}: ${type};`);
730
+ classFields.add(varName);
731
+ }
732
+ } else if (inClass) {
733
+ lines.push(`${indent()}${varName}: ${type};`);
734
+ }
735
+ }
736
+ }
737
+
738
+ // Walk s-expression tree for component declarations
739
+ let componentVars = new Set();
740
+ let hasSchemaDecls = false;
741
+ if (sexpr) {
742
+ usesRipIntrinsicProps = emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, sourceLines) || usesRipIntrinsicProps;
743
+
744
+ // Remove lines for variables that belong to components (emitted as class members)
745
+ if (componentVars.size > 0) {
746
+ for (let k = lines.length - 1; k >= 0; k--) {
747
+ let match = lines[k].match(/(?:declare |export )*(?:const|let) (\w+)/);
748
+ if (match && componentVars.has(match[1])) lines.splice(k, 1);
749
+ }
750
+ }
751
+
752
+ // Schema declarations — strip any prior auto-emitted `declare let Foo`
753
+ // for the same bindings (they are re-emitted as typed Schema<T>).
754
+ let schemaLines = [];
755
+ hasSchemaDecls = emitSchemaTypes(sexpr, schemaLines);
756
+ if (hasSchemaDecls) {
757
+ let bindings = new Set();
758
+ for (let line of schemaLines) {
759
+ let m = line.match(/(?:declare |export )*const (\w+)/);
760
+ if (m) bindings.add(m[1]);
761
+ }
762
+ for (let k = lines.length - 1; k >= 0; k--) {
763
+ let m = lines[k].match(/(?:declare |export )*(?:const|let) (\w+)/);
764
+ if (m && bindings.has(m[1])) lines.splice(k, 1);
765
+ }
766
+ lines.push(...schemaLines);
767
+ }
768
+ }
769
+
770
+ if (lines.length === 0) return null;
771
+
772
+ // Prepend reactive type definitions if used
773
+ let preamble = [];
774
+ if (usesRipIntrinsicProps) {
775
+ preamble.push(...INTRINSIC_TYPE_DECLS);
776
+ }
777
+ if (/\bARIA\./.test(source)) {
778
+ preamble.push(...ARIA_TYPE_DECLS);
779
+ }
780
+ if (usesSignal) {
781
+ preamble.push(SIGNAL_INTERFACE);
782
+ preamble.push(SIGNAL_FN);
783
+ }
784
+ if (usesComputed) {
785
+ preamble.push(COMPUTED_INTERFACE);
786
+ preamble.push(COMPUTED_FN);
787
+ }
788
+ if (usesSignal || usesComputed) {
789
+ preamble.push(EFFECT_FN);
790
+ }
791
+ if (hasSchemaDecls) {
792
+ preamble.push(...SCHEMA_INTRINSIC_DECLS);
793
+ }
794
+ if (preamble.length > 0) {
795
+ preamble.push('');
796
+ }
797
+
798
+ return preamble.concat(lines).join('\n') + '\n';
799
+ }
800
+
801
+ // ============================================================================
802
+ // Suffix expansion — Rip type suffixes to TypeScript
803
+ // ============================================================================
804
+
805
+ function expandSuffixes(typeStr) {
806
+ if (!typeStr) return typeStr;
807
+
808
+ // Convert :: to : (annotation sigil to type separator)
809
+ typeStr = typeStr.replace(/::/g, ':');
810
+
811
+ // T?? → T | null | undefined
812
+ typeStr = typeStr.replace(/(\w+(?:<[^>]+>)?)\?\?/g, '$1 | null | undefined');
813
+
814
+ // T? → T | undefined (but not ?. or ?: which are different)
815
+ typeStr = typeStr.replace(/(\w+(?:<[^>]+>)?)\?(?![.:])/g, '$1 | undefined');
816
+
817
+ // T! → NonNullable<T>
818
+ typeStr = typeStr.replace(/(\w+(?:<[^>]+>)?)\!/g, 'NonNullable<$1>');
819
+
820
+ return typeStr;
821
+ }
822
+
823
+ // ============================================================================
824
+ // Component type emission — walk s-expression for component declarations
825
+ // ============================================================================
826
+
827
+ function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, sourceLines) {
828
+ if (!Array.isArray(sexpr)) return false;
829
+ let head = sexpr[0]?.valueOf?.() ?? sexpr[0];
830
+ let usesIntrinsicProps = false;
831
+
832
+ const refMembers = new Map();
833
+ const isIntrinsicTag = (name) => typeof name === 'string' && /^[a-z]/.test(name);
834
+ const collectRefMembers = (node) => {
835
+ if (!Array.isArray(node)) return;
836
+ let nodeHead = node[0]?.valueOf?.() ?? node[0];
837
+ if (isIntrinsicTag(nodeHead)) {
838
+ for (let i = 1; i < node.length; i++) {
839
+ let child = node[i];
840
+ if (!Array.isArray(child)) continue;
841
+ let childHead = child[0]?.valueOf?.() ?? child[0];
842
+ if (childHead !== 'object') continue;
843
+ for (let j = 1; j < child.length; j++) {
844
+ let entry = child[j];
845
+ if (!Array.isArray(entry)) continue;
846
+ let key = entry[0]?.valueOf?.() ?? entry[0];
847
+ if (key !== 'ref') continue;
848
+ let refName = entry[1]?.valueOf?.() ?? entry[1];
849
+ if (typeof refName === 'string') refName = refName.replace(/^["']|["']$/g, '');
850
+ if (typeof refName === 'string' && !refMembers.has(refName)) {
851
+ refMembers.set(refName, `__RipDomEl<'${nodeHead}'> | null`);
852
+ }
853
+ }
854
+ }
855
+ }
856
+ for (let i = 1; i < node.length; i++) {
857
+ if (Array.isArray(node[i])) collectRefMembers(node[i]);
858
+ }
859
+ };
860
+
861
+ // export Name = component ... → ["export", ["=", "Name", ["component", ...members]]]
862
+ // Name = component ... → ["=", "Name", ["component", ...members]]
863
+ let exported = false;
864
+ let name = null;
865
+ let compNode = null;
866
+ let typeParams = '';
867
+
868
+ if (head === 'export' && Array.isArray(sexpr[1])) {
869
+ exported = true;
870
+ let inner = sexpr[1];
871
+ let innerHead = inner[0]?.valueOf?.() ?? inner[0];
872
+ if (innerHead === '=' && Array.isArray(inner[2]) &&
873
+ (inner[2][0]?.valueOf?.() ?? inner[2][0]) === 'component') {
874
+ typeParams = inner[1]?.typeParams || '';
875
+ name = inner[1]?.valueOf?.() ?? inner[1];
876
+ compNode = inner[2];
877
+ }
878
+ } else if (head === '=' && Array.isArray(sexpr[2]) &&
879
+ (sexpr[2][0]?.valueOf?.() ?? sexpr[2][0]) === 'component') {
880
+ typeParams = sexpr[1]?.typeParams || '';
881
+ name = sexpr[1]?.valueOf?.() ?? sexpr[1];
882
+ compNode = sexpr[2];
883
+ }
884
+
885
+ if (name && compNode) {
886
+ let exp = exported ? 'export ' : '';
887
+ let inheritsTag = compNode[1]?.valueOf?.() ?? null;
888
+ let inheritedPropsType = inheritsTag ? `__RipProps<'${inheritsTag}'>` : null;
889
+ if (inheritedPropsType) usesIntrinsicProps = true;
890
+
891
+ // Component structure: ["component", parent, ["block", ...members]]
892
+ let body = compNode[2];
893
+ let members = (Array.isArray(body) && (body[0]?.valueOf?.() ?? body[0]) === 'block')
894
+ ? body.slice(1) : (body ? [body] : []);
895
+
896
+ let publicProps = [];
897
+ let bodyMembers = [];
898
+ let hasRequired = false;
899
+
900
+ // Infer type from literal initializer when no explicit annotation
901
+ let inferLiteralType = (v) => {
902
+ let s = v?.valueOf?.() ?? v;
903
+ if (typeof s !== 'string') return null;
904
+ if (s === 'true' || s === 'false') return 'boolean';
905
+ if (/^-?\d+(\.\d+)?$/.test(s)) return 'number';
906
+ if (s.startsWith('"') || s.startsWith("'")) return 'string';
907
+ return null;
908
+ };
909
+
910
+ for (let member of members) {
911
+ if (!Array.isArray(member)) continue;
912
+ let mHead = member[0]?.valueOf?.() ?? member[0];
913
+
914
+ let target, propName, isProp, type, hasDefault;
915
+
916
+ if (mHead === 'state' || mHead === 'readonly' || mHead === 'computed') {
917
+ target = member[1];
918
+ isProp = Array.isArray(target) && (target[0]?.valueOf?.() ?? target[0]) === '.' && (target[1]?.valueOf?.() ?? target[1]) === 'this';
919
+ propName = isProp ? (target[2]?.valueOf?.() ?? target[2]) : (target?.valueOf?.() ?? target);
920
+ type = isProp ? target[2]?.type : target?.type;
921
+ hasDefault = true;
922
+ if (!isProp) {
923
+ componentVars.add(propName);
924
+ let wrapper = (mHead === 'computed') ? 'Computed' : 'Signal';
925
+ let typeStr = type ? expandSuffixes(type) : (inferLiteralType(member[2]) || 'any');
926
+ bodyMembers.push(` ${propName}: ${wrapper}<${typeStr}>;`);
927
+ continue;
928
+ }
929
+ } else if (mHead === '.') {
930
+ isProp = (member[1]?.valueOf?.() ?? member[1]) === 'this';
931
+ propName = isProp ? (member[2]?.valueOf?.() ?? member[2]) : null;
932
+ type = isProp ? member[2]?.type : null;
933
+ hasDefault = false;
934
+ if (!isProp && propName) componentVars.add(propName);
935
+ } else if (mHead === 'object') {
936
+ // Method definitions: (object (: methodName (-> (params...) (block ...))))
937
+ for (let i = 1; i < member.length; i++) {
938
+ let entry = member[i];
939
+ if (!Array.isArray(entry) || entry.length < 3) continue;
940
+ let methName = entry[1]?.valueOf?.() ?? entry[1];
941
+ let funcDef = entry[2];
942
+ if (!Array.isArray(funcDef)) continue;
943
+ let fHead = funcDef[0]?.valueOf?.() ?? funcDef[0];
944
+ if (fHead !== '->' && fHead !== '=>') continue;
945
+ let params = funcDef[1];
946
+ if (!Array.isArray(params)) continue;
947
+ let hasTypedParams = params.some(p => p?.type);
948
+ if (!hasTypedParams) continue;
949
+ let paramStrs = [];
950
+ for (let p of params) {
951
+ let pName = p?.valueOf?.() ?? p;
952
+ let pType = p?.type ? expandSuffixes(p.type) : 'any';
953
+ paramStrs.push(`${pName}: ${pType}`);
954
+ }
955
+ bodyMembers.push(` ${methName}(${paramStrs.join(', ')}): void;`);
956
+ }
957
+ continue;
958
+ } else if (mHead === 'render') {
959
+ usesIntrinsicProps = true;
960
+ collectRefMembers(member[1]);
961
+ continue;
962
+ } else {
963
+ continue;
964
+ }
965
+
966
+ if (!isProp || !propName) continue;
967
+
968
+ let typeStr = type ? expandSuffixes(type) : 'any';
969
+ let opt = hasDefault ? '?' : '';
970
+ if (!hasDefault) hasRequired = true;
971
+ publicProps.push(` ${propName}${opt}: ${typeStr};`);
972
+ if (mHead === 'state') {
973
+ publicProps.push(` __bind_${propName}__?: Signal<${typeStr}>;`);
974
+ }
975
+ }
976
+
977
+ lines.push(`${exp}declare class ${name}${typeParams} {`);
978
+ if (publicProps.length > 0 || inheritedPropsType) {
979
+ let propsOpt = hasRequired ? '' : '?';
980
+ if (publicProps.length > 0) {
981
+ lines.push(` constructor(props${propsOpt}: {`);
982
+ for (let p of publicProps) lines.push(p);
983
+ lines.push(inheritedPropsType ? ` } & ${inheritedPropsType});` : ' });');
984
+ } else {
985
+ lines.push(` constructor(props${propsOpt}: ${inheritedPropsType});`);
986
+ }
987
+ } else {
988
+ lines.push(` constructor(props?: {});`);
989
+ }
990
+ for (let [refName, refType] of refMembers) {
991
+ bodyMembers.push(` ${refName}: ${refType};`);
992
+ }
993
+ for (let m of bodyMembers) lines.push(m);
994
+ lines.push(`}`);
995
+ }
996
+
997
+ // Recurse into child nodes
998
+ if (head === 'program' || head === 'block') {
999
+ for (let i = 1; i < sexpr.length; i++) {
1000
+ if (Array.isArray(sexpr[i])) {
1001
+ usesIntrinsicProps = emitComponentTypes(sexpr[i], lines, indent, indentLevel, componentVars, sourceLines) || usesIntrinsicProps;
1002
+ }
1003
+ }
1004
+ }
1005
+ // Also check inside export wrappers
1006
+ if (head === 'export' && Array.isArray(sexpr[1]) && !compNode) {
1007
+ usesIntrinsicProps = emitComponentTypes(sexpr[1], lines, indent, indentLevel, componentVars, sourceLines) || usesIntrinsicProps;
1008
+ }
1009
+
1010
+ return usesIntrinsicProps;
1011
+ }
1012
+
1013
+ // ============================================================================
1014
+ // Registration — install emitTypes into the compiler at module load.
1015
+ // ============================================================================
1016
+ // The compiler exposes setTypesEmitter() as a no-op-friendly hook. When
1017
+ // nothing imports types-emit.js (browser bundle), the emitter stays null
1018
+ // and compile()s .d.ts output is silently skipped. CLI entry points and
1019
+ // typecheck.js import this module specifically to install the emitter.
1020
+
1021
+ setTypesEmitter(emitTypes);