1- import { parse , type Value , type Node } from "kdljs" ;
1+ import { parse , type Value , type Node , Document } from "kdljs" ;
22import type {
33 Enum ,
44 Event ,
@@ -76,33 +76,34 @@ function handleTypeParameters(value: Value) {
7676 } ;
7777}
7878
79+ function undefinedIfEmpty ( object : object , output : object ) {
80+ return Object . entries ( object ) . length ? output : undefined ;
81+ }
82+
7983/**
80- * Converts patch files in KDL to match the [types](types.d.ts).
84+ * Converts parsed KDL Document nodes to match the [types](types.d.ts).
8185 */
82- function parseKDL ( kdlText : string ) : DeepPartial < WebIdl > {
83- const { output, errors } = parse ( kdlText ) ;
84-
85- if ( errors . length ) {
86- throw new Error ( "KDL parse errors" , { cause : errors } ) ;
87- }
88-
89- const nodes = output ! ;
86+ function convertKDLNodes ( nodes : Node [ ] ) : DeepPartial < WebIdl > {
9087 const enums : Record < string , Enum > = { } ;
9188 const mixin : Record < string , DeepPartial < Interface > > = { } ;
9289 const interfaces : Record < string , DeepPartial < Interface > > = { } ;
9390 const dictionary : Record < string , DeepPartial < Dictionary > > = { } ;
9491
9592 for ( const node of nodes ) {
93+ // Note: no "removals" handling here; caller is responsible for splitting
9694 const name = string ( node . values [ 0 ] ) ;
9795 switch ( node . name ) {
9896 case "enum" :
9997 enums [ name ] = handleEnum ( node ) ;
10098 break ;
10199 case "interface-mixin" :
102- mixin [ name ] = handleMixinandInterfaces ( node , "mixin" ) ;
100+ mixin [ name ] = merge (
101+ mixin [ name ] ,
102+ handleMixinAndInterfaces ( node , "mixin" ) ,
103+ ) ;
103104 break ;
104105 case "interface" :
105- interfaces [ name ] = handleMixinandInterfaces ( node , "interface" ) ;
106+ interfaces [ name ] = handleMixinAndInterfaces ( node , "interface" ) ;
106107 break ;
107108 case "dictionary" :
108109 dictionary [ name ] = handleDictionary ( node ) ;
@@ -113,10 +114,10 @@ function parseKDL(kdlText: string): DeepPartial<WebIdl> {
113114 }
114115
115116 return {
116- enums : { enum : enums } ,
117- mixins : { mixin } ,
118- interfaces : { interface : interfaces } ,
119- dictionaries : { dictionary } ,
117+ enums : undefinedIfEmpty ( enums , { enum : enums } ) ,
118+ mixins : undefinedIfEmpty ( mixin , { mixin } ) ,
119+ interfaces : undefinedIfEmpty ( interfaces , { interface : interfaces } ) ,
120+ dictionaries : undefinedIfEmpty ( dictionary , { dictionary } ) ,
120121 } ;
121122}
122123
@@ -152,7 +153,7 @@ function handleEnum(node: Node): Enum {
152153 * @param node The mixin node to handle.
153154 * @param mixins The record of mixins to update.
154155 */
155- function handleMixinandInterfaces (
156+ function handleMixinAndInterfaces (
156157 node : Node ,
157158 type : "mixin" | "interface" ,
158159) : DeepPartial < Interface > {
@@ -368,21 +369,76 @@ async function getAllFileURLs(folder: URL): Promise<URL[]> {
368369}
369370
370371/**
371- * Read and parse a single KDL file.
372+ * Read and parse a single KDL file into its KDL Document structure .
372373 */
373- export async function readPatch ( fileUrl : URL ) : Promise < any > {
374+ async function readPatchDocument ( fileUrl : URL ) : Promise < Document > {
374375 const text = await readFile ( fileUrl , "utf8" ) ;
375- return parseKDL ( text ) ;
376+ const { output, errors } = parse ( text ) ;
377+ if ( errors . length ) {
378+ throw new Error ( `KDL parse errors in ${ fileUrl . toString ( ) } ` , {
379+ cause : errors ,
380+ } ) ;
381+ }
382+ return output ! ;
383+ }
384+ /**
385+ * Recursively remove all 'name' fields from the object and its children, and
386+ * replace any empty objects ({} or []) with null.
387+ */
388+ function convertForRemovals ( obj : unknown ) : unknown {
389+ if ( Array . isArray ( obj ) ) {
390+ const result = obj . map ( convertForRemovals ) . filter ( ( v ) => v !== undefined ) ;
391+ return result . length === 0 ? null : result ;
392+ }
393+ if ( obj && typeof obj === "object" ) {
394+ const newObj : Record < string , unknown > = { } ;
395+ for ( const [ key , value ] of Object . entries ( obj ) ) {
396+ if ( key !== "name" ) {
397+ const cleaned = convertForRemovals ( value ) ;
398+ if ( cleaned !== undefined ) {
399+ newObj [ key ] = cleaned ;
400+ }
401+ }
402+ }
403+ // Replace empty objects with null
404+ return Object . keys ( newObj ) . length === 0 ? null : newObj ;
405+ }
406+ return obj ;
376407}
377408
378409/**
379410 * Read, parse, and merge all KDL files under the input folder.
411+ * Splits the main patch content and the removals from each file for combined processing.
412+ *
413+ * Returns:
414+ * {
415+ * patches: merged patch contents (excluding removals),
416+ * removalPatches: merged removals, with names stripped
417+ * }
380418 */
381- export default async function readPatches ( ) : Promise < any > {
419+ export default async function readPatches ( ) : Promise < {
420+ patches : any ;
421+ removalPatches : any ;
422+ } > {
382423 const patchDirectory = new URL ( "../../inputfiles/patches/" , import . meta. url ) ;
383424 const fileUrls = await getAllFileURLs ( patchDirectory ) ;
384425
385- const parsedContents = await Promise . all ( fileUrls . map ( readPatch ) ) ;
426+ // Stage 1: Parse all file KDLs into Documents
427+ const documents = await Promise . all ( fileUrls . map ( readPatchDocument ) ) ;
428+
429+ // Stage 2: Group by patches or removals
430+ const merged = documents . flat ( ) ;
431+ const patchNodes = merged . filter ( ( node ) => node . name !== "removals" ) ;
432+ const removalNodes = merged
433+ . filter ( ( node ) => node . name === "removals" )
434+ . map ( ( node ) => node . children )
435+ . flat ( ) ;
436+
437+ // Stage 3: Convert the nodes for patches and removals respectively
438+ const patches = convertKDLNodes ( patchNodes ) ;
439+ const removalPatches = convertForRemovals (
440+ convertKDLNodes ( removalNodes ) ,
441+ ) as DeepPartial < WebIdl > ;
386442
387- return parsedContents . reduce ( ( acc , current ) => merge ( acc , current ) , { } ) ;
443+ return { patches , removalPatches } ;
388444}
0 commit comments