rip-lang 3.14.5 → 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.
package/src/types.js CHANGED
@@ -1,65 +1,16 @@
1
- import { SCHEMA_INTRINSIC_DECLS, emitSchemaTypes } from './schema.js';
2
-
3
- // Type System — Optional type annotations and .d.ts emission for Rip
4
- //
5
- // Architecture:
6
- // installTypeSupport(Lexer) — adds rewriteTypes() to the lexer prototype.
7
- // Strips type annotations from the token stream and stores them as
8
- // metadata on surviving tokens. The parser never sees types.
1
+ // Type System token-stream type stripping (browser-safe half).
9
2
  //
10
- // emitTypes(tokens, sexpr) generates .d.ts from annotated tokens and
11
- // the parsed s-expression tree. Called after parsing so it has access
12
- // to both token-level annotations (variables, functions, types) and
13
- // s-expression structures (components). One function, one output.
3
+ // This module exists for one purpose: install rewriteTypes() on the lexer
4
+ // so user-typed Rip source parses cleanly. Type annotations are stored as
5
+ // token metadata and the parser never sees them, so all of this code only
6
+ // runs when source contains `::` annotations or `type` / `interface` /
7
+ // `enum` declarations.
14
8
  //
15
- // emitEnum() the one CodeEmitter method for runtime enum output.
16
- // Enums cross into the grammar because they emit runtime JavaScript.
17
-
18
- // ============================================================================
19
- // Shared type declaration constants — single source of truth
20
- // ============================================================================
21
- // Used by emitTypes() for .d.ts emission and by compileForCheck() in
22
- // typecheck.js for virtual .ts injection. Keeping them here eliminates
23
- // divergence between what gets written to disk and what TS analyzes.
24
-
25
- export const INTRINSIC_TYPE_DECLS = [
26
- 'type __RipElementMap = HTMLElementTagNameMap & Omit<SVGElementTagNameMap, keyof HTMLElementTagNameMap>;',
27
- 'type __RipTag = keyof __RipElementMap;',
28
- "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; };",
29
- "type __RipDomEl<K extends __RipTag> = Omit<__RipElementMap[K], 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & __RipBrowserElement;",
30
- "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;",
31
- 'type __RipEvents = { [K in keyof HTMLElementEventMap as `@${K}`]?: ((event: HTMLElementEventMap[K]) => void) | null };',
32
- 'type __RipClassValue = string | boolean | null | undefined | Record<string, boolean> | __RipClassValue[];',
33
- '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 };',
34
- ];
35
-
36
- export const INTRINSIC_FN_DECL = 'declare function __ripEl<K extends __RipTag>(tag: K, props?: __RipProps<K>): void;';
37
-
38
- export const ARIA_TYPE_DECLS = [
39
- 'type __RipAriaNavHandlers = { next?: () => void; prev?: () => void; first?: () => void; last?: () => void; select?: () => void; dismiss?: () => void; tab?: () => void; char?: () => void; };',
40
- "declare const ARIA: {",
41
- " bindPopover(open: boolean, popover: () => Element | null | undefined, setOpen: (isOpen: boolean) => void, source?: (() => Element | null | undefined) | null): void;",
42
- " bindDialog(open: boolean, dialog: () => Element | null | undefined, setOpen: (isOpen: boolean) => void, dismissable?: boolean): void;",
43
- " popupDismiss(open: boolean, popup: () => Element | null | undefined, close: () => void, els?: Array<() => Element | null | undefined>, repos?: (() => void) | null): void;",
44
- " popupGuard(delay?: number): any;",
45
- " listNav(event: KeyboardEvent, handlers: __RipAriaNavHandlers): void;",
46
- " rovingNav(event: KeyboardEvent, handlers: __RipAriaNavHandlers, orientation?: 'vertical' | 'horizontal' | 'both'): void;",
47
- " positionBelow(trigger: Element | null | undefined, popup: Element | null | undefined, gap?: number, setVisible?: boolean): void;",
48
- " position(trigger: Element | null | undefined, floating: Element | null | undefined, opts?: any): void;",
49
- " trapFocus(panel: Element | null | undefined): void;",
50
- " wireAria(panel: Element, id: string): void;",
51
- " lockScroll(instance: any): void;",
52
- " unlockScroll(instance: any): void;",
53
- " hasAnchor: boolean;",
54
- " [key: string]: any;",
55
- "};",
56
- ];
57
-
58
- export const SIGNAL_INTERFACE = 'interface Signal<T> { value: T; read(): T; lock(): Signal<T>; free(): Signal<T>; kill(): T; }';
59
- export const SIGNAL_FN = 'declare function __state<T>(value: T | Signal<T>): Signal<T>;';
60
- export const COMPUTED_INTERFACE = 'interface Computed<T> { readonly value: T; read(): T; lock(): Computed<T>; free(): Computed<T>; kill(): T; }';
61
- export const COMPUTED_FN = 'declare function __computed<T>(fn: () => T): Computed<T>;';
62
- export const EFFECT_FN = 'declare function __effect(fn: () => void | (() => void)): () => void;';
9
+ // The .d.ts emission half (emitTypes, intrinsic decl tables, component-
10
+ // type emitter) lives in src/types-emit.js, which is reachable only from
11
+ // CLI entry points and typecheck.js. Runtime enum codegen lives in
12
+ // compiler.js (CodeEmitter.prototype.emitEnum) — that's real codegen,
13
+ // not type machinery.
63
14
 
64
15
  // ============================================================================
65
16
  // installTypeSupport — adds rewriteTypes() to Lexer.prototype
@@ -649,978 +600,3 @@ function collectBlockUnion(tokens, startIdx) {
649
600
  return { typeText: members.join(' | '), endIdx };
650
601
  }
651
602
 
652
- // ============================================================================
653
- // emitTypes — generate .d.ts from annotated tokens + s-expression tree
654
- // ============================================================================
655
-
656
- export function emitTypes(tokens, sexpr = null, source = '') {
657
- let lines = [];
658
- let indentLevel = 0;
659
- let indentStr = ' ';
660
- let indent = () => indentStr.repeat(indentLevel);
661
- let inClass = false;
662
- let classFields = new Set(); // Track emitted field names to avoid duplicates
663
- let usesSignal = false;
664
- let usesComputed = false;
665
- let usesRipIntrinsicProps = false;
666
- const sourceLines = typeof source === 'string' ? source.split('\n') : [];
667
-
668
- // Pre-scan: detect reactive operators regardless of type annotations.
669
- // This ensures the DTS preamble declares __state/__computed/__effect
670
- // so TypeScript can infer types for untyped reactive variables.
671
- for (let i = 0; i < tokens.length; i++) {
672
- const tag = tokens[i][0];
673
- if (tag === 'REACTIVE_ASSIGN') usesSignal = true;
674
- else if (tag === 'COMPUTED_ASSIGN') usesComputed = true;
675
- }
676
-
677
- // Format { prop; prop } into multi-line block. Only applies when the
678
- // body is a single { ... } object — not a union like { ... } | { ... }.
679
- let emitBlock = (prefix, body, suffix) => {
680
- if (body.startsWith('{ ') && body.endsWith(' }')) {
681
- let depth = 0, firstTopClose = -1;
682
- for (let c = 0; c < body.length; c++) {
683
- if (body[c] === '{') depth++;
684
- else if (body[c] === '}') { depth--; if (depth === 0) { firstTopClose = c; break; } }
685
- }
686
- if (firstTopClose === body.length - 1) {
687
- // Depth-aware split on '; ' at top level only
688
- let inner = body.slice(2, -2);
689
- let props = [], start = 0, d = 0;
690
- for (let c = 0; c < inner.length; c++) {
691
- if (inner[c] === '{') d++;
692
- else if (inner[c] === '}') d--;
693
- else if (d === 0 && inner[c] === ';' && inner[c + 1] === ' ') {
694
- props.push(inner.slice(start, c));
695
- start = c + 2;
696
- }
697
- }
698
- if (start < inner.length) props.push(inner.slice(start));
699
- props = props.filter(p => p.trim());
700
- if (props.length > 0) {
701
- lines.push(`${indent()}${prefix}{`);
702
- indentLevel++;
703
- for (let prop of props) lines.push(`${indent()}${prop};`);
704
- indentLevel--;
705
- lines.push(`${indent()}}${suffix}`);
706
- return;
707
- }
708
- }
709
- }
710
- lines.push(`${indent()}${prefix}${body}${suffix}`);
711
- };
712
-
713
- // Skip past a default value expression (= ...) returning the new j
714
- let skipDefault = (tokens, j) => {
715
- j++; // skip =
716
- let dd = 0;
717
- while (j < tokens.length) {
718
- let dt = tokens[j];
719
- if (dt[0] === '(' || dt[0] === '[' || dt[0] === '{') dd++;
720
- if (dt[0] === ')' || dt[0] === ']' || dt[0] === '}') {
721
- if (dd === 0) break; // closing bracket of enclosing structure
722
- dd--;
723
- }
724
- if (dd === 0 && dt[1] === ',') break;
725
- j++;
726
- }
727
- return j;
728
- };
729
-
730
- // Collect a destructured object { a, b: c, ...rest } recursively.
731
- // tokens[startJ] must be '{'. Returns { patternStr, typeStr, endJ, hasAnyType }.
732
- let collectDestructuredObj = (tokens, startJ) => {
733
- let props = [];
734
- let hasAnyType = false;
735
- let j = startJ + 1; // skip opening {
736
- let d = 1;
737
- while (j < tokens.length && d > 0) {
738
- if (tokens[j][0] === '{') d++;
739
- if (tokens[j][0] === '}') d--;
740
- if (d <= 0) { j++; break; }
741
-
742
- // Rest: ...name
743
- if (tokens[j][0] === '...' || tokens[j][0] === 'SPREAD') {
744
- j++;
745
- if (tokens[j]?.[0] === 'IDENTIFIER') {
746
- props.push({ kind: 'rest', propName: tokens[j][1] });
747
- j++;
748
- }
749
- continue;
750
- }
751
-
752
- // PROPERTY : ... (rename, nested object, or nested array)
753
- if (tokens[j][0] === 'PROPERTY' && tokens[j + 1]?.[0] === ':') {
754
- let propName = tokens[j][1];
755
- j += 2; // skip PROPERTY and :
756
- // Nested object
757
- if (tokens[j]?.[0] === '{') {
758
- let inner = collectDestructuredObj(tokens, j);
759
- if (inner.hasAnyType) hasAnyType = true;
760
- props.push({ kind: 'nested-obj', propName, inner });
761
- j = inner.endJ;
762
- continue;
763
- }
764
- // Nested array
765
- if (tokens[j]?.[0] === '[') {
766
- let inner = collectDestructuredArr(tokens, j);
767
- if (inner.hasAnyType) hasAnyType = true;
768
- props.push({ kind: 'nested-arr', propName, inner });
769
- j = inner.endJ;
770
- continue;
771
- }
772
- // Simple rename: PROPERTY : IDENTIFIER
773
- if (tokens[j]?.[0] === 'IDENTIFIER') {
774
- let localName = tokens[j][1];
775
- let type = tokens[j].data?.type;
776
- if (type) hasAnyType = true;
777
- let hasDefault = tokens[j + 1]?.[0] === '=';
778
- props.push({ kind: 'rename', propName, localName, type: type ? expandSuffixes(type) : null, hasDefault });
779
- j++;
780
- if (hasDefault) j = skipDefault(tokens, j);
781
- }
782
- continue;
783
- }
784
-
785
- // Simple: IDENTIFIER (possibly with default)
786
- if (tokens[j][0] === 'IDENTIFIER') {
787
- let name = tokens[j][1];
788
- let type = tokens[j].data?.type;
789
- if (type) hasAnyType = true;
790
- let hasDefault = tokens[j + 1]?.[0] === '=';
791
- props.push({ kind: 'simple', propName: name, type: type ? expandSuffixes(type) : null, hasDefault });
792
- j++;
793
- if (hasDefault) j = skipDefault(tokens, j);
794
- continue;
795
- }
796
-
797
- // Skip commas and other tokens
798
- j++;
799
- }
800
-
801
- // Build pattern and type strings
802
- let patternParts = [];
803
- let typeParts = [];
804
- for (let p of props) {
805
- if (p.kind === 'rest') {
806
- patternParts.push(`...${p.propName}`);
807
- typeParts.push(`[key: string]: unknown`);
808
- } else if (p.kind === 'nested-obj' || p.kind === 'nested-arr') {
809
- patternParts.push(`${p.propName}: ${p.inner.patternStr}`);
810
- typeParts.push(`${p.propName}: ${p.inner.typeStr}`);
811
- } else if (p.kind === 'rename') {
812
- patternParts.push(`${p.propName}: ${p.localName}`);
813
- typeParts.push(`${p.propName}${p.hasDefault ? '?' : ''}: ${p.type || 'any'}`);
814
- } else {
815
- patternParts.push(p.propName);
816
- typeParts.push(`${p.propName}${p.hasDefault ? '?' : ''}: ${p.type || 'any'}`);
817
- }
818
- }
819
- return {
820
- patternStr: `{${patternParts.join(', ')}}`,
821
- typeStr: `{${typeParts.join(', ')}}`,
822
- endJ: j,
823
- hasAnyType,
824
- };
825
- };
826
-
827
- // Collect a destructured array [ a, b ] from tokens.
828
- // tokens[startJ] must be '['. Returns { patternStr, typeStr, endJ, hasAnyType }.
829
- let collectDestructuredArr = (tokens, startJ) => {
830
- let names = [];
831
- let elemTypes = [];
832
- let hasAnyType = false;
833
- let j = startJ + 1; // skip opening [
834
- let d = 1;
835
- while (j < tokens.length && d > 0) {
836
- if (tokens[j][0] === '[') d++;
837
- if (tokens[j][0] === ']') d--;
838
- if (d > 0 && tokens[j][0] === 'IDENTIFIER') {
839
- let name = tokens[j][1];
840
- let type = tokens[j].data?.type;
841
- names.push(name);
842
- elemTypes.push(type ? expandSuffixes(type) : null);
843
- if (type) hasAnyType = true;
844
- }
845
- j++;
846
- }
847
- return {
848
- patternStr: `[${names.join(', ')}]`,
849
- typeStr: `[${elemTypes.map(t => t || 'any').join(', ')}]`,
850
- endJ: j,
851
- hasAnyType,
852
- };
853
- };
854
-
855
- // Collect function parameters (handles simple, destructured, rest, defaults)
856
- let collectParams = (tokens, startIdx) => {
857
- let params = [];
858
- let fields = []; // Track @param:: type for class field emission
859
- let j = startIdx;
860
- let openTag = tokens[j]?.[0];
861
- if (openTag !== 'CALL_START' && openTag !== 'PARAM_START') return { params, fields, endIdx: j };
862
- let closeTag = openTag === 'CALL_START' ? 'CALL_END' : 'PARAM_END';
863
- j++;
864
- let depth = 0;
865
-
866
- while (j < tokens.length && !(tokens[j][0] === closeTag && depth === 0)) {
867
- let tok = tokens[j];
868
-
869
- // Skip commas at depth 0
870
- if (tok[1] === ',' && depth === 0) { j++; continue; }
871
-
872
- // Track nesting
873
- if (tok[0] === '{' || tok[0] === '[' || tok[0] === 'CALL_START' ||
874
- tok[0] === 'PARAM_START' || tok[0] === 'INDEX_START') depth++;
875
- if (tok[0] === '}' || tok[0] === ']' || tok[0] === 'CALL_END' ||
876
- tok[0] === 'PARAM_END' || tok[0] === 'INDEX_END') { depth--; j++; continue; }
877
-
878
- // @ prefix (constructor shorthand: @name)
879
- if (tok[0] === '@') {
880
- j++;
881
- if (tokens[j]?.[0] === 'PROPERTY' || tokens[j]?.[0] === 'IDENTIFIER') {
882
- let name = tokens[j][1];
883
- let type = tokens[j].data?.type;
884
- params.push(type ? `${name}: ${expandSuffixes(type)}` : name);
885
- if (type) fields.push({ name, type: expandSuffixes(type) });
886
- j++;
887
- }
888
- continue;
889
- }
890
-
891
- // Rest parameter: ...name
892
- if (tok[0] === 'SPREAD' || tok[1] === '...') {
893
- j++;
894
- if (tokens[j]?.[0] === 'IDENTIFIER') {
895
- let name = tokens[j][1];
896
- let type = tokens[j].data?.type;
897
- params.push(type ? `...${name}: ${expandSuffixes(type)}` : `...${name}: any[]`);
898
- j++;
899
- }
900
- continue;
901
- }
902
-
903
- // Destructured object parameter: { a, b }
904
- if (tok[0] === '{') {
905
- depth--; // undo the depth++ from nesting tracker above
906
- let result = collectDestructuredObj(tokens, j);
907
- j = result.endJ;
908
- if (result.hasAnyType) {
909
- params.push(`${result.patternStr}: ${result.typeStr}`);
910
- } else {
911
- params.push(result.patternStr);
912
- }
913
- continue;
914
- }
915
-
916
- // Destructured array parameter: [ a, b ]
917
- if (tok[0] === '[') {
918
- depth--; // undo the depth++ from nesting tracker above
919
- let result = collectDestructuredArr(tokens, j);
920
- j = result.endJ;
921
- if (result.hasAnyType) {
922
- params.push(`${result.patternStr}: ${result.typeStr}`);
923
- } else {
924
- params.push(result.patternStr);
925
- }
926
- continue;
927
- }
928
-
929
- // Simple identifier parameter
930
- if (tok[0] === 'IDENTIFIER') {
931
- let paramName = tok[1];
932
- let paramType = tok.data?.type;
933
-
934
- // Check for default value (skip = and the default expression)
935
- let hasDefault = false;
936
- if (tokens[j + 1]?.[0] === '=') {
937
- hasDefault = true;
938
- }
939
-
940
- let isOptional = hasDefault || tok.data?.predicate;
941
- if (paramType) {
942
- params.push(`${paramName}${isOptional ? '?' : ''}: ${expandSuffixes(paramType)}`);
943
- } else {
944
- params.push(paramName);
945
- }
946
- j++;
947
-
948
- // Skip past default value expression
949
- if (hasDefault) {
950
- j++; // skip =
951
- let dd = 0;
952
- while (j < tokens.length) {
953
- let dt = tokens[j];
954
- if (dt[0] === '(' || dt[0] === '[' || dt[0] === '{') dd++;
955
- if (dt[0] === ')' || dt[0] === ']' || dt[0] === '}') dd--;
956
- if (dd === 0 && (dt[1] === ',' || dt[0] === 'CALL_END')) break;
957
- j++;
958
- }
959
- }
960
- continue;
961
- }
962
-
963
- j++;
964
- }
965
-
966
- return { params, fields, endIdx: j };
967
- };
968
-
969
- for (let i = 0; i < tokens.length; i++) {
970
- let t = tokens[i];
971
- let tag = t[0];
972
-
973
- // Track export flag
974
- let exported = false;
975
- if (tag === 'EXPORT') {
976
- exported = true;
977
- i++;
978
- if (i >= tokens.length) break;
979
- t = tokens[i];
980
- tag = t[0];
981
-
982
- // Export default
983
- if (tag === 'DEFAULT') {
984
- i++;
985
- if (i >= tokens.length) break;
986
- t = tokens[i];
987
- tag = t[0];
988
-
989
- // export default IDENTIFIER (re-export)
990
- if (tag === 'IDENTIFIER') {
991
- lines.push(`${indent()}export default ${t[1]};`);
992
- }
993
- // export default { ... } or other expressions — skip for now
994
- continue;
995
- }
996
- }
997
-
998
- // Import statements — pass through for type references
999
- if (tag === 'IMPORT') {
1000
- let importTokens = [];
1001
- let j = i + 1;
1002
- while (j < tokens.length && tokens[j][0] !== 'TERMINATOR') {
1003
- importTokens.push(tokens[j]);
1004
- j++;
1005
- }
1006
- // Reconstruct: join with spaces, then clean up spacing
1007
- let raw = 'import ' + importTokens.map(tk => tk[1]).join(' ');
1008
- raw = raw.replace(/\s+/g, ' ')
1009
- .replace(/\s*,\s*/g, ', ')
1010
- .replace(/\{\s*/g, '{ ').replace(/\s*\}/g, ' }')
1011
- .trim();
1012
- lines.push(`${indent()}${raw};`);
1013
- i = j;
1014
- continue;
1015
- }
1016
-
1017
- // TYPE_DECL marker — emit type alias or interface
1018
- if (tag === 'TYPE_DECL') {
1019
- let data = t.data;
1020
- if (!data) continue;
1021
- let exp = (exported || data.exported) ? 'export ' : '';
1022
- let params = data.typeParams || '';
1023
-
1024
- if (data.kind === 'overload') {
1025
- // Emit function overload signature from saved tokens
1026
- let ot = data.overloadTokens;
1027
- let nameToken = ot[1]; // DEF is [0], name is [1]
1028
- let { params: paramList } = collectParams(ot, 2);
1029
- let returnType = nameToken.data?.returnType;
1030
- let ret = returnType ? `: ${expandSuffixes(returnType)}` : '';
1031
- let declare = inClass ? '' : (exp ? '' : 'declare ');
1032
- let typeParams = data.typeParams || '';
1033
- if (inClass) {
1034
- lines.push(`${indent()}${data.name}${typeParams}(${paramList.join(', ')})${ret};`);
1035
- } else {
1036
- lines.push(`${indent()}${exp}${declare}function ${data.name}${typeParams}(${paramList.join(', ')})${ret};`);
1037
- }
1038
- } else if (data.kind === 'interface') {
1039
- let ext = data.extends ? ` extends ${data.extends}` : '';
1040
- emitBlock(`${exp}interface ${data.name}${params}${ext} `, data.typeText || '{}', '');
1041
- } else {
1042
- let typeText = expandSuffixes(data.typeText || '');
1043
- emitBlock(`${exp}type ${data.name}${params} = `, typeText, ';');
1044
- }
1045
- continue;
1046
- }
1047
-
1048
- // ENUM — emit enum declaration for .d.ts
1049
- if (tag === 'ENUM') {
1050
- let exp = exported ? 'export ' : '';
1051
- let nameToken = tokens[i + 1];
1052
- if (!nameToken) continue;
1053
- let enumName = nameToken[1];
1054
-
1055
- // Find INDENT ... OUTDENT for enum body
1056
- let j = i + 2;
1057
- if (tokens[j]?.[0] === 'INDENT') {
1058
- lines.push(`${indent()}${exp}enum ${enumName} {`);
1059
- indentLevel++;
1060
- j++;
1061
- let members = [];
1062
-
1063
- while (j < tokens.length && tokens[j][0] !== 'OUTDENT') {
1064
- if (tokens[j][0] === 'TERMINATOR') { j++; continue; }
1065
- if (tokens[j][0] === 'IDENTIFIER') {
1066
- let memberName = tokens[j][1];
1067
- j++;
1068
- if (tokens[j]?.[1] === '=') {
1069
- j++;
1070
- let val = tokens[j]?.[1];
1071
- members.push(`${memberName} = ${val}`);
1072
- j++;
1073
- } else {
1074
- members.push(memberName);
1075
- }
1076
- } else {
1077
- j++;
1078
- }
1079
- }
1080
-
1081
- for (let m = 0; m < members.length; m++) {
1082
- let comma = m < members.length - 1 ? ',' : '';
1083
- lines.push(`${indent()}${members[m]}${comma}`);
1084
- }
1085
-
1086
- indentLevel--;
1087
- lines.push(`${indent()}}`);
1088
- }
1089
- // Don't advance i — the parser still needs to see ENUM tokens
1090
- continue;
1091
- }
1092
-
1093
- // CLASS — emit class declaration
1094
- if (tag === 'CLASS') {
1095
- let exp = exported ? 'export ' : '';
1096
- let classNameToken = tokens[i + 1];
1097
- if (!classNameToken) continue;
1098
- let className = classNameToken[1];
1099
-
1100
- // Check for extends
1101
- let ext = '';
1102
- let j = i + 2;
1103
- if (tokens[j]?.[0] === 'EXTENDS') {
1104
- ext = ` extends ${tokens[j + 1]?.[1] || ''}`;
1105
- j += 2;
1106
- }
1107
-
1108
- // Only emit if there are typed members
1109
- if (tokens[j]?.[0] === 'INDENT') {
1110
- let hasTypedMembers = false;
1111
- let k = j + 1;
1112
- while (k < tokens.length && tokens[k][0] !== 'OUTDENT') {
1113
- if (tokens[k].data?.type || tokens[k].data?.returnType) {
1114
- hasTypedMembers = true;
1115
- break;
1116
- }
1117
- k++;
1118
- }
1119
- if (hasTypedMembers) {
1120
- lines.push(`${indent()}${exp}declare class ${className}${ext} {`);
1121
- inClass = true;
1122
- classFields.clear();
1123
- indentLevel++;
1124
- }
1125
- }
1126
- continue;
1127
- }
1128
-
1129
- // DEF — emit function or method declaration
1130
- if (tag === 'DEF') {
1131
- let nameToken = tokens[i + 1];
1132
- if (!nameToken) continue;
1133
- let fnName = nameToken[1];
1134
- let returnType = nameToken.data?.returnType;
1135
- if (!returnType && nameToken.data?.await === true) returnType = 'void';
1136
- let typeParams = nameToken.data?.typeParams || '';
1137
-
1138
- let { params, endIdx } = collectParams(tokens, i + 2);
1139
-
1140
- // Only emit if there are type annotations
1141
- if (returnType || params.some(p => p.includes(':'))) {
1142
- let exp = exported ? 'export ' : '';
1143
- let declare = inClass ? '' : (exported ? '' : 'declare ');
1144
- let ret = returnType ? `: ${expandSuffixes(returnType)}` : '';
1145
- let paramStr = params.join(', ');
1146
- if (inClass) {
1147
- lines.push(`${indent()}${fnName}${typeParams}(${paramStr})${ret};`);
1148
- } else {
1149
- lines.push(`${indent()}${exp}${declare}function ${fnName}${typeParams}(${paramStr})${ret};`);
1150
- }
1151
- }
1152
- i = endIdx; // advance past params to avoid leaking destructured typed identifiers
1153
- continue;
1154
- }
1155
-
1156
- // Class method block: { PROPERTY ... PROPERTY ... }
1157
- // Contains one or more methods separated by TERMINATOR
1158
- if (tag === '{' && inClass) {
1159
- let j = i + 1;
1160
- let braceDepth = 1;
1161
-
1162
- while (j < tokens.length && braceDepth > 0) {
1163
- let tok = tokens[j];
1164
-
1165
- if (tok[0] === '{') { braceDepth++; j++; continue; }
1166
- if (tok[0] === '}') { braceDepth--; j++; continue; }
1167
- if (tok[0] === 'TERMINATOR') { j++; continue; }
1168
-
1169
- // Found a method: PROPERTY "name" : PARAM_START ... PARAM_END -> body
1170
- if (tok[0] === 'PROPERTY' && braceDepth === 1) {
1171
- let methodName = tok[1];
1172
- let returnType = tok.data?.returnType;
1173
- j++;
1174
-
1175
- // Skip : separator
1176
- if (tokens[j]?.[1] === ':') j++;
1177
-
1178
- let params = [];
1179
- let fields = [];
1180
- if (tokens[j]?.[0] === 'PARAM_START') {
1181
- let result = collectParams(tokens, j);
1182
- params = result.params;
1183
- fields = result.fields;
1184
- j = result.endIdx + 1;
1185
- }
1186
-
1187
- // Skip -> and method body (INDENT ... OUTDENT)
1188
- if (tokens[j]?.[0] === '->' || tokens[j]?.[0] === '=>') j++;
1189
- if (tokens[j]?.[0] === 'INDENT') {
1190
- let d = 1;
1191
- j++;
1192
- while (j < tokens.length && d > 0) {
1193
- if (tokens[j][0] === 'INDENT') d++;
1194
- if (tokens[j][0] === 'OUTDENT') d--;
1195
- j++;
1196
- }
1197
- }
1198
-
1199
- if (returnType || params.some(p => p.includes(':'))) {
1200
- let ret = returnType ? `: ${expandSuffixes(returnType)}` : '';
1201
- let paramStr = params.join(', ');
1202
- // Emit field declarations for constructor @param:: type shorthand
1203
- if (methodName === 'constructor' && fields.length) {
1204
- for (let f of fields) {
1205
- if (!classFields.has(f.name)) {
1206
- lines.push(`${indent()}${f.name}: ${f.type};`);
1207
- classFields.add(f.name);
1208
- }
1209
- }
1210
- }
1211
- lines.push(`${indent()}${methodName}(${paramStr})${ret};`);
1212
- }
1213
- continue;
1214
- }
1215
-
1216
- j++;
1217
- }
1218
-
1219
- i = j - 1;
1220
- continue;
1221
- }
1222
-
1223
- // Track INDENT/OUTDENT for class body
1224
- if (tag === 'INDENT') {
1225
- continue;
1226
- }
1227
- if (tag === 'OUTDENT') {
1228
- if (inClass) {
1229
- indentLevel--;
1230
- lines.push(`${indent()}}`);
1231
- inClass = false;
1232
- }
1233
- continue;
1234
- }
1235
-
1236
- // Arrow function assignment: name = (params) -> body
1237
- if (tag === 'IDENTIFIER' && !inClass && tokens[i + 1]?.[0] === '=' &&
1238
- (tokens[i + 2]?.[0] === 'PARAM_START' || tokens[i + 2]?.[0] === '(')) {
1239
- let fnName = t[1];
1240
- let j = i + 2;
1241
-
1242
- let { params } = collectParams(tokens, j);
1243
-
1244
- // Find the -> or => token to get return type
1245
- let k = j;
1246
- let depth = 0;
1247
- while (k < tokens.length) {
1248
- if (tokens[k][0] === 'PARAM_START' || tokens[k][0] === '(') depth++;
1249
- if (tokens[k][0] === 'PARAM_END' || tokens[k][0] === ')') depth--;
1250
- if (depth === 0 && (tokens[k][0] === '->' || tokens[k][0] === '=>')) break;
1251
- k++;
1252
- }
1253
- let returnType = tokens[k]?.data?.returnType;
1254
-
1255
- if (returnType || params.some(p => p.includes(':'))) {
1256
- let exp = exported ? 'export ' : '';
1257
- let declare = exported ? '' : 'declare ';
1258
- let ret = returnType ? `: ${expandSuffixes(returnType)}` : '';
1259
- let paramStr = params.join(', ');
1260
- lines.push(`${indent()}${exp}${declare}function ${fnName}(${paramStr})${ret};`);
1261
- continue;
1262
- }
1263
- }
1264
-
1265
- // Variable assignments with type annotations
1266
- if (tag === 'IDENTIFIER' && t.data?.type) {
1267
- let varName = t[1];
1268
- let type = expandSuffixes(t.data.type);
1269
- let next = tokens[i + 1];
1270
-
1271
- if (next) {
1272
- let exp = exported ? 'export ' : '';
1273
- let declare = exported ? '' : 'declare ';
1274
-
1275
- if (next[0] === 'READONLY_ASSIGN') {
1276
- lines.push(`${indent()}${exp}${declare}const ${varName}: ${type};`);
1277
- } else if (next[0] === 'REACTIVE_ASSIGN') {
1278
- usesSignal = true;
1279
- lines.push(`${indent()}${exp}${declare}const ${varName}: Signal<${type}>;`);
1280
- } else if (next[0] === 'COMPUTED_ASSIGN') {
1281
- usesComputed = true;
1282
- lines.push(`${indent()}${exp}${declare}const ${varName}: Computed<${type}>;`);
1283
- } else if (next[0] === 'EFFECT') {
1284
- lines.push(`${indent()}${exp}${declare}const ${varName}: () => void;`);
1285
- } else if (next[0] === '=') {
1286
- // Check if RHS is an arrow function with return type
1287
- let arrowIdx = i + 2;
1288
- // Skip past PARAM_START ... PARAM_END if present
1289
- if (tokens[arrowIdx]?.[0] === 'PARAM_START') {
1290
- let d = 1, k = arrowIdx + 1;
1291
- while (k < tokens.length && d > 0) {
1292
- if (tokens[k][0] === 'PARAM_START') d++;
1293
- if (tokens[k][0] === 'PARAM_END') d--;
1294
- k++;
1295
- }
1296
- arrowIdx = k;
1297
- }
1298
- let arrowToken = tokens[arrowIdx];
1299
- if (arrowToken && (arrowToken[0] === '->' || arrowToken[0] === '=>') &&
1300
- arrowToken.data?.returnType) {
1301
- // Typed arrow function assignment
1302
- let returnType = expandSuffixes(arrowToken.data.returnType);
1303
- let { params } = collectParams(tokens, i + 2);
1304
- let paramStr = params.join(', ');
1305
- lines.push(`${indent()}${exp}${declare}function ${varName}(${paramStr}): ${returnType};`);
1306
- } else if (inClass) {
1307
- lines.push(`${indent()}${varName}: ${type};`);
1308
- classFields.add(varName);
1309
- } else {
1310
- lines.push(`${indent()}${exp}let ${varName}: ${type};`);
1311
- }
1312
- } else if (inClass) {
1313
- // Class property without assignment
1314
- lines.push(`${indent()}${varName}: ${type};`);
1315
- classFields.add(varName);
1316
- }
1317
- } else if (inClass) {
1318
- lines.push(`${indent()}${varName}: ${type};`);
1319
- }
1320
- }
1321
- }
1322
-
1323
- // Walk s-expression tree for component declarations
1324
- let componentVars = new Set();
1325
- let hasSchemaDecls = false;
1326
- if (sexpr) {
1327
- usesRipIntrinsicProps = emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, sourceLines) || usesRipIntrinsicProps;
1328
-
1329
- // Remove lines for variables that belong to components (emitted as class members)
1330
- if (componentVars.size > 0) {
1331
- for (let k = lines.length - 1; k >= 0; k--) {
1332
- let match = lines[k].match(/(?:declare |export )*(?:const|let) (\w+)/);
1333
- if (match && componentVars.has(match[1])) lines.splice(k, 1);
1334
- }
1335
- }
1336
-
1337
- // Schema declarations — strip any prior auto-emitted `declare let Foo`
1338
- // for the same bindings (they are re-emitted as typed Schema<T>).
1339
- let schemaLines = [];
1340
- hasSchemaDecls = emitSchemaTypes(sexpr, schemaLines);
1341
- if (hasSchemaDecls) {
1342
- let bindings = new Set();
1343
- for (let line of schemaLines) {
1344
- let m = line.match(/(?:declare |export )*const (\w+)/);
1345
- if (m) bindings.add(m[1]);
1346
- }
1347
- for (let k = lines.length - 1; k >= 0; k--) {
1348
- let m = lines[k].match(/(?:declare |export )*(?:const|let) (\w+)/);
1349
- if (m && bindings.has(m[1])) lines.splice(k, 1);
1350
- }
1351
- lines.push(...schemaLines);
1352
- }
1353
- }
1354
-
1355
- if (lines.length === 0) return null;
1356
-
1357
- // Prepend reactive type definitions if used
1358
- let preamble = [];
1359
- if (usesRipIntrinsicProps) {
1360
- preamble.push(...INTRINSIC_TYPE_DECLS);
1361
- }
1362
- if (/\bARIA\./.test(source)) {
1363
- preamble.push(...ARIA_TYPE_DECLS);
1364
- }
1365
- if (usesSignal) {
1366
- preamble.push(SIGNAL_INTERFACE);
1367
- preamble.push(SIGNAL_FN);
1368
- }
1369
- if (usesComputed) {
1370
- preamble.push(COMPUTED_INTERFACE);
1371
- preamble.push(COMPUTED_FN);
1372
- }
1373
- if (usesSignal || usesComputed) {
1374
- preamble.push(EFFECT_FN);
1375
- }
1376
- if (hasSchemaDecls) {
1377
- preamble.push(...SCHEMA_INTRINSIC_DECLS);
1378
- }
1379
- if (preamble.length > 0) {
1380
- preamble.push('');
1381
- }
1382
-
1383
- return preamble.concat(lines).join('\n') + '\n';
1384
- }
1385
-
1386
- // ============================================================================
1387
- // Suffix expansion — Rip type suffixes to TypeScript
1388
- // ============================================================================
1389
-
1390
- function expandSuffixes(typeStr) {
1391
- if (!typeStr) return typeStr;
1392
-
1393
- // Convert :: to : (annotation sigil to type separator)
1394
- typeStr = typeStr.replace(/::/g, ':');
1395
-
1396
- // T?? → T | null | undefined
1397
- typeStr = typeStr.replace(/(\w+(?:<[^>]+>)?)\?\?/g, '$1 | null | undefined');
1398
-
1399
- // T? → T | undefined (but not ?. or ?: which are different)
1400
- typeStr = typeStr.replace(/(\w+(?:<[^>]+>)?)\?(?![.:])/g, '$1 | undefined');
1401
-
1402
- // T! → NonNullable<T>
1403
- typeStr = typeStr.replace(/(\w+(?:<[^>]+>)?)\!/g, 'NonNullable<$1>');
1404
-
1405
- return typeStr;
1406
- }
1407
-
1408
- // ============================================================================
1409
- // Component type emission — walk s-expression for component declarations
1410
- // ============================================================================
1411
-
1412
- function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, sourceLines) {
1413
- if (!Array.isArray(sexpr)) return false;
1414
- let head = sexpr[0]?.valueOf?.() ?? sexpr[0];
1415
- let usesIntrinsicProps = false;
1416
-
1417
- const refMembers = new Map();
1418
- const isIntrinsicTag = (name) => typeof name === 'string' && /^[a-z]/.test(name);
1419
- const collectRefMembers = (node) => {
1420
- if (!Array.isArray(node)) return;
1421
- let nodeHead = node[0]?.valueOf?.() ?? node[0];
1422
- if (isIntrinsicTag(nodeHead)) {
1423
- for (let i = 1; i < node.length; i++) {
1424
- let child = node[i];
1425
- if (!Array.isArray(child)) continue;
1426
- let childHead = child[0]?.valueOf?.() ?? child[0];
1427
- if (childHead !== 'object') continue;
1428
- for (let j = 1; j < child.length; j++) {
1429
- let entry = child[j];
1430
- if (!Array.isArray(entry)) continue;
1431
- let key = entry[0]?.valueOf?.() ?? entry[0];
1432
- if (key !== 'ref') continue;
1433
- let refName = entry[1]?.valueOf?.() ?? entry[1];
1434
- if (typeof refName === 'string') refName = refName.replace(/^["']|["']$/g, '');
1435
- if (typeof refName === 'string' && !refMembers.has(refName)) {
1436
- refMembers.set(refName, `__RipDomEl<'${nodeHead}'> | null`);
1437
- }
1438
- }
1439
- }
1440
- }
1441
- for (let i = 1; i < node.length; i++) {
1442
- if (Array.isArray(node[i])) collectRefMembers(node[i]);
1443
- }
1444
- };
1445
-
1446
- // export Name = component ... → ["export", ["=", "Name", ["component", ...members]]]
1447
- // Name = component ... → ["=", "Name", ["component", ...members]]
1448
- let exported = false;
1449
- let name = null;
1450
- let compNode = null;
1451
- let typeParams = '';
1452
-
1453
- if (head === 'export' && Array.isArray(sexpr[1])) {
1454
- exported = true;
1455
- let inner = sexpr[1];
1456
- let innerHead = inner[0]?.valueOf?.() ?? inner[0];
1457
- if (innerHead === '=' && Array.isArray(inner[2]) &&
1458
- (inner[2][0]?.valueOf?.() ?? inner[2][0]) === 'component') {
1459
- typeParams = inner[1]?.typeParams || '';
1460
- name = inner[1]?.valueOf?.() ?? inner[1];
1461
- compNode = inner[2];
1462
- }
1463
- } else if (head === '=' && Array.isArray(sexpr[2]) &&
1464
- (sexpr[2][0]?.valueOf?.() ?? sexpr[2][0]) === 'component') {
1465
- typeParams = sexpr[1]?.typeParams || '';
1466
- name = sexpr[1]?.valueOf?.() ?? sexpr[1];
1467
- compNode = sexpr[2];
1468
- }
1469
-
1470
- if (name && compNode) {
1471
- let exp = exported ? 'export ' : '';
1472
- let inheritsTag = compNode[1]?.valueOf?.() ?? null;
1473
- let inheritedPropsType = inheritsTag ? `__RipProps<'${inheritsTag}'>` : null;
1474
- if (inheritedPropsType) usesIntrinsicProps = true;
1475
-
1476
- // Component structure: ["component", parent, ["block", ...members]]
1477
- let body = compNode[2];
1478
- let members = (Array.isArray(body) && (body[0]?.valueOf?.() ?? body[0]) === 'block')
1479
- ? body.slice(1) : (body ? [body] : []);
1480
-
1481
- let publicProps = [];
1482
- let bodyMembers = [];
1483
- let hasRequired = false;
1484
-
1485
- // Infer type from literal initializer when no explicit annotation
1486
- let inferLiteralType = (v) => {
1487
- let s = v?.valueOf?.() ?? v;
1488
- if (typeof s !== 'string') return null;
1489
- if (s === 'true' || s === 'false') return 'boolean';
1490
- if (/^-?\d+(\.\d+)?$/.test(s)) return 'number';
1491
- if (s.startsWith('"') || s.startsWith("'")) return 'string';
1492
- return null;
1493
- };
1494
-
1495
- for (let member of members) {
1496
- if (!Array.isArray(member)) continue;
1497
- let mHead = member[0]?.valueOf?.() ?? member[0];
1498
-
1499
- let target, propName, isProp, type, hasDefault;
1500
-
1501
- if (mHead === 'state' || mHead === 'readonly' || mHead === 'computed') {
1502
- target = member[1];
1503
- isProp = Array.isArray(target) && (target[0]?.valueOf?.() ?? target[0]) === '.' && (target[1]?.valueOf?.() ?? target[1]) === 'this';
1504
- propName = isProp ? (target[2]?.valueOf?.() ?? target[2]) : (target?.valueOf?.() ?? target);
1505
- type = isProp ? target[2]?.type : target?.type;
1506
- hasDefault = true;
1507
- if (!isProp) {
1508
- componentVars.add(propName);
1509
- let wrapper = (mHead === 'computed') ? 'Computed' : 'Signal';
1510
- let typeStr = type ? expandSuffixes(type) : (inferLiteralType(member[2]) || 'any');
1511
- bodyMembers.push(` ${propName}: ${wrapper}<${typeStr}>;`);
1512
- continue;
1513
- }
1514
- } else if (mHead === '.') {
1515
- isProp = (member[1]?.valueOf?.() ?? member[1]) === 'this';
1516
- propName = isProp ? (member[2]?.valueOf?.() ?? member[2]) : null;
1517
- type = isProp ? member[2]?.type : null;
1518
- hasDefault = false;
1519
- if (!isProp && propName) componentVars.add(propName);
1520
- } else if (mHead === 'object') {
1521
- // Method definitions: (object (: methodName (-> (params...) (block ...))))
1522
- for (let i = 1; i < member.length; i++) {
1523
- let entry = member[i];
1524
- if (!Array.isArray(entry) || entry.length < 3) continue;
1525
- let methName = entry[1]?.valueOf?.() ?? entry[1];
1526
- let funcDef = entry[2];
1527
- if (!Array.isArray(funcDef)) continue;
1528
- let fHead = funcDef[0]?.valueOf?.() ?? funcDef[0];
1529
- if (fHead !== '->' && fHead !== '=>') continue;
1530
- let params = funcDef[1];
1531
- if (!Array.isArray(params)) continue;
1532
- let hasTypedParams = params.some(p => p?.type);
1533
- if (!hasTypedParams) continue;
1534
- let paramStrs = [];
1535
- for (let p of params) {
1536
- let pName = p?.valueOf?.() ?? p;
1537
- let pType = p?.type ? expandSuffixes(p.type) : 'any';
1538
- paramStrs.push(`${pName}: ${pType}`);
1539
- }
1540
- bodyMembers.push(` ${methName}(${paramStrs.join(', ')}): void;`);
1541
- }
1542
- continue;
1543
- } else if (mHead === 'render') {
1544
- usesIntrinsicProps = true;
1545
- collectRefMembers(member[1]);
1546
- continue;
1547
- } else {
1548
- continue;
1549
- }
1550
-
1551
- if (!isProp || !propName) continue;
1552
-
1553
- let typeStr = type ? expandSuffixes(type) : 'any';
1554
- let opt = hasDefault ? '?' : '';
1555
- if (!hasDefault) hasRequired = true;
1556
- publicProps.push(` ${propName}${opt}: ${typeStr};`);
1557
- if (mHead === 'state') {
1558
- publicProps.push(` __bind_${propName}__?: Signal<${typeStr}>;`);
1559
- }
1560
- }
1561
-
1562
- lines.push(`${exp}declare class ${name}${typeParams} {`);
1563
- if (publicProps.length > 0 || inheritedPropsType) {
1564
- let propsOpt = hasRequired ? '' : '?';
1565
- if (publicProps.length > 0) {
1566
- lines.push(` constructor(props${propsOpt}: {`);
1567
- for (let p of publicProps) lines.push(p);
1568
- lines.push(inheritedPropsType ? ` } & ${inheritedPropsType});` : ' });');
1569
- } else {
1570
- lines.push(` constructor(props${propsOpt}: ${inheritedPropsType});`);
1571
- }
1572
- } else {
1573
- lines.push(` constructor(props?: {});`);
1574
- }
1575
- for (let [refName, refType] of refMembers) {
1576
- bodyMembers.push(` ${refName}: ${refType};`);
1577
- }
1578
- for (let m of bodyMembers) lines.push(m);
1579
- lines.push(`}`);
1580
- }
1581
-
1582
- // Recurse into child nodes
1583
- if (head === 'program' || head === 'block') {
1584
- for (let i = 1; i < sexpr.length; i++) {
1585
- if (Array.isArray(sexpr[i])) {
1586
- usesIntrinsicProps = emitComponentTypes(sexpr[i], lines, indent, indentLevel, componentVars, sourceLines) || usesIntrinsicProps;
1587
- }
1588
- }
1589
- }
1590
- // Also check inside export wrappers
1591
- if (head === 'export' && Array.isArray(sexpr[1]) && !compNode) {
1592
- usesIntrinsicProps = emitComponentTypes(sexpr[1], lines, indent, indentLevel, componentVars, sourceLines) || usesIntrinsicProps;
1593
- }
1594
-
1595
- return usesIntrinsicProps;
1596
- }
1597
-
1598
- // ============================================================================
1599
- // emitEnum — runtime JavaScript enum object (CodeEmitter method)
1600
- // ============================================================================
1601
-
1602
- export function emitEnum(head, rest, context) {
1603
- let [name, body] = rest;
1604
- let enumName = name?.valueOf?.() ?? name;
1605
-
1606
- // Parse enum body from s-expression
1607
- let pairs = [];
1608
- if (Array.isArray(body)) {
1609
- let items = body[0] === 'block' ? body.slice(1) : [body];
1610
- for (let item of items) {
1611
- if (Array.isArray(item)) {
1612
- if (item[0]?.valueOf?.() === '=') {
1613
- let key = item[1]?.valueOf?.() ?? item[1];
1614
- let val = item[2]?.valueOf?.() ?? item[2];
1615
- pairs.push([key, val]);
1616
- }
1617
- }
1618
- }
1619
- }
1620
-
1621
- if (pairs.length === 0) return `const ${enumName} = {}`;
1622
-
1623
- let forward = pairs.map(([k, v]) => `${k}: ${v}`).join(', ');
1624
- let reverse = pairs.map(([k, v]) => `${v}: "${k}"`).join(', ');
1625
- return `const ${enumName} = {${forward}, ${reverse}}`;
1626
- }