1
1
import { 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" ;
3
11
import { readdir , readFile } from "fs/promises" ;
4
12
import { merge } from "./helpers.js" ;
5
13
@@ -25,6 +33,28 @@ function optionalMember<const T>(prop: string, type: T, value?: Value) {
25
33
} ;
26
34
}
27
35
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
+
28
58
/**
29
59
* Converts patch files in KDL to match the [types](types.d.ts).
30
60
*/
@@ -40,10 +70,7 @@ function parseKDL(kdlText: string): DeepPartial<WebIdl> {
40
70
const mixin : Record < string , DeepPartial < Interface > > = { } ;
41
71
42
72
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 ] ) ;
47
74
switch ( node . name ) {
48
75
case "enum" :
49
76
enums [ name ] = handleEnum ( node ) ;
@@ -66,10 +93,7 @@ function parseKDL(kdlText: string): DeepPartial<WebIdl> {
66
93
* @param enums The record of enums to update.
67
94
*/
68
95
function 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 ] ) ;
73
97
const values : string [ ] = [ ] ;
74
98
75
99
for ( const child of node . children ) {
@@ -96,23 +120,26 @@ function handleEnum(node: Node): Enum {
96
120
*/
97
121
function handleMixin ( node : Node ) : DeepPartial < Interface > {
98
122
const name = node . values [ 0 ] ;
99
- if ( typeof name !== "string" ) {
100
- throw new Error ( "Missing mixin name" ) ;
101
- }
102
123
103
124
const event : Event [ ] = [ ] ;
104
125
const property : Record < string , Partial < Property > > = { } ;
126
+ const method : Record < string , Partial < Method > > = { } ;
105
127
106
128
for ( const child of node . children ) {
107
129
switch ( child . name ) {
108
130
case "event" :
109
131
event . push ( handleEvent ( child ) ) ;
110
132
break ;
111
133
case "property" : {
112
- const propName = child . values [ 0 ] as string ;
134
+ const propName = string ( child . values [ 0 ] ) ;
113
135
property [ propName ] = handleProperty ( child ) ;
114
136
break ;
115
137
}
138
+ case "method" : {
139
+ const methodName = string ( child . values [ 0 ] ) ;
140
+ method [ methodName ] = handleMethod ( child ) ;
141
+ break ;
142
+ }
116
143
default :
117
144
throw new Error ( `Unknown node name: ${ child . name } ` ) ;
118
145
}
@@ -122,6 +149,7 @@ function handleMixin(node: Node): DeepPartial<Interface> {
122
149
name,
123
150
events : { event } ,
124
151
properties : { property } ,
152
+ methods : { method } ,
125
153
...optionalMember ( "extends" , "string" , node . properties ?. extends ) ,
126
154
} as DeepPartial < Interface > ;
127
155
}
@@ -132,8 +160,8 @@ function handleMixin(node: Node): DeepPartial<Interface> {
132
160
*/
133
161
function handleEvent ( child : Node ) : Event {
134
162
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 ) ,
137
165
} ;
138
166
}
139
167
@@ -143,13 +171,57 @@ function handleEvent(child: Node): Event {
143
171
*/
144
172
function handleProperty ( child : Node ) : Partial < Property > {
145
173
return {
146
- name : child . values [ 0 ] as string ,
174
+ name : string ( child . values [ 0 ] ) ,
147
175
...optionalMember ( "exposed" , "string" , child . properties ?. exposed ) ,
148
176
...optionalMember ( "optional" , "boolean" , child . properties ?. optional ) ,
149
177
...optionalMember ( "overrideType" , "string" , child . properties ?. overrideType ) ,
150
178
} ;
151
179
}
152
180
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
+
153
225
/**
154
226
* Collect all file URLs in a directory.
155
227
*/
0 commit comments