11import { parse , type Value , type Node } from "kdljs" ;
2- import type { Enum , Event , Property , Interface , WebIdl } from "./types.js" ;
2+ import type {
3+ Enum ,
4+ Event ,
5+ Property ,
6+ Interface ,
7+ WebIdl ,
8+ Method ,
9+ Typed ,
10+ } from "./types.js" ;
311import { readdir , readFile } from "fs/promises" ;
412import { merge } from "./helpers.js" ;
513
@@ -25,6 +33,28 @@ function optionalMember<const T>(prop: string, type: T, value?: Value) {
2533 } ;
2634}
2735
36+ function string ( arg : unknown ) : string {
37+ if ( typeof arg !== "string" ) {
38+ throw new Error ( `Expected a string but found ${ typeof arg } ` ) ;
39+ }
40+ return arg ;
41+ }
42+
43+ function handleTyped ( type : Node ) : Typed {
44+ const isTyped = type . name == "type" ;
45+ if ( ! isTyped ) {
46+ throw new Error ( "Expected a type node" ) ;
47+ }
48+ const name = string ( type . values [ 0 ] ) ;
49+ const subType =
50+ type . children . length > 0 ? handleTyped ( type . children [ 0 ] ) : undefined ;
51+ return {
52+ type : name ,
53+ subtype : subType ,
54+ ...optionalMember ( "nullable" , "boolean" , type . properties ?. nullable ) ,
55+ } ;
56+ }
57+
2858/**
2959 * Converts patch files in KDL to match the [types](types.d.ts).
3060 */
@@ -40,10 +70,7 @@ function parseKDL(kdlText: string): DeepPartial<WebIdl> {
4070 const mixin : Record < string , DeepPartial < Interface > > = { } ;
4171
4272 for ( const node of nodes ) {
43- const name = node . values [ 0 ] ;
44- if ( typeof name !== "string" ) {
45- throw new Error ( `Missing name for ${ node . name } ` ) ;
46- }
73+ const name = string ( node . values [ 0 ] ) ;
4774 switch ( node . name ) {
4875 case "enum" :
4976 enums [ name ] = handleEnum ( node ) ;
@@ -66,10 +93,7 @@ function parseKDL(kdlText: string): DeepPartial<WebIdl> {
6693 * @param enums The record of enums to update.
6794 */
6895function handleEnum ( node : Node ) : Enum {
69- const name = node . properties ?. name || node . values [ 0 ] ;
70- if ( typeof name !== "string" ) {
71- throw new Error ( "Missing enum name" ) ;
72- }
96+ const name = string ( node . properties ?. name || node . values [ 0 ] ) ;
7397 const values : string [ ] = [ ] ;
7498
7599 for ( const child of node . children ) {
@@ -96,23 +120,26 @@ function handleEnum(node: Node): Enum {
96120 */
97121function handleMixin ( node : Node ) : DeepPartial < Interface > {
98122 const name = node . values [ 0 ] ;
99- if ( typeof name !== "string" ) {
100- throw new Error ( "Missing mixin name" ) ;
101- }
102123
103124 const event : Event [ ] = [ ] ;
104125 const property : Record < string , Partial < Property > > = { } ;
126+ const method : Record < string , Partial < Method > > = { } ;
105127
106128 for ( const child of node . children ) {
107129 switch ( child . name ) {
108130 case "event" :
109131 event . push ( handleEvent ( child ) ) ;
110132 break ;
111133 case "property" : {
112- const propName = child . values [ 0 ] as string ;
134+ const propName = string ( child . values [ 0 ] ) ;
113135 property [ propName ] = handleProperty ( child ) ;
114136 break ;
115137 }
138+ case "method" : {
139+ const methodName = string ( child . values [ 0 ] ) ;
140+ method [ methodName ] = handleMethod ( child ) ;
141+ break ;
142+ }
116143 default :
117144 throw new Error ( `Unknown node name: ${ child . name } ` ) ;
118145 }
@@ -122,6 +149,7 @@ function handleMixin(node: Node): DeepPartial<Interface> {
122149 name,
123150 events : { event } ,
124151 properties : { property } ,
152+ methods : { method } ,
125153 ...optionalMember ( "extends" , "string" , node . properties ?. extends ) ,
126154 } as DeepPartial < Interface > ;
127155}
@@ -132,8 +160,8 @@ function handleMixin(node: Node): DeepPartial<Interface> {
132160 */
133161function handleEvent ( child : Node ) : Event {
134162 return {
135- name : child . values [ 0 ] as string ,
136- type : child . properties . type as string ,
163+ name : string ( child . values [ 0 ] ) ,
164+ type : string ( child . properties . type ) ,
137165 } ;
138166}
139167
@@ -143,13 +171,57 @@ function handleEvent(child: Node): Event {
143171 */
144172function handleProperty ( child : Node ) : Partial < Property > {
145173 return {
146- name : child . values [ 0 ] as string ,
174+ name : string ( child . values [ 0 ] ) ,
147175 ...optionalMember ( "exposed" , "string" , child . properties ?. exposed ) ,
148176 ...optionalMember ( "optional" , "boolean" , child . properties ?. optional ) ,
149177 ...optionalMember ( "overrideType" , "string" , child . properties ?. overrideType ) ,
150178 } ;
151179}
152180
181+ /**
182+ * Handles a child node of type "method" and adds it to the method object.
183+ * @param child The child node to handle.
184+ */
185+ function handleMethod ( child : Node ) : Partial < Method > {
186+ const name = string ( child . values [ 0 ] ) ;
187+
188+ let typeNode : Node | undefined ;
189+ const params : { name : string ; type : string } [ ] = [ ] ;
190+
191+ for ( const c of child . children ) {
192+ switch ( c . name ) {
193+ case "type" :
194+ if ( typeNode ) {
195+ throw new Error ( `Method "${ name } " has multiple type nodes (invalid)` ) ;
196+ }
197+ typeNode = c ;
198+ break ;
199+
200+ case "param" :
201+ params . push ( {
202+ name : string ( c . values [ 0 ] ) ,
203+ type : string ( c . properties . type ) ,
204+ } ) ;
205+ break ;
206+
207+ default :
208+ throw new Error ( `Unexpected child "${ c . name } " in method "${ name } "` ) ;
209+ }
210+ }
211+
212+ if ( ! typeNode ) {
213+ throw new Error ( `Method "${ name } " is missing a return type` ) ;
214+ }
215+
216+ const signature : Method [ "signature" ] = [
217+ {
218+ param : params ,
219+ ...handleTyped ( typeNode ) ,
220+ } ,
221+ ] ;
222+ return { name, signature } ;
223+ }
224+
153225/**
154226 * Collect all file URLs in a directory.
155227 */
0 commit comments