2121'use strict' ;
2222import fs from 'node:fs/promises' ;
2323import { parse } from 'node-html-parser' ;
24+ import * as idl from 'webidl2' ;
2425
2526// --------------------------------------------------
2627// Process options
@@ -102,6 +103,19 @@ for (const element of root.querySelectorAll('script, style, .index')) {
102103const html = root . innerHTML ;
103104const text = root . innerText ;
104105
106+ // node-html-parser leaves some entities in innerText; use this to translate
107+ // where it matters, e.g. IDL fragments.
108+ function innerText ( element ) {
109+ return element . innerText . replaceAll ( / & a m p ; / g, '&' )
110+ . replaceAll ( / & l t ; / g, '<' )
111+ . replaceAll ( / & g t ; / g, '>' ) ;
112+ }
113+
114+ // Parse the WebIDL Index. This will throw if errors are found, but Bikeshed
115+ // should fail to build the spec if the WebIDL is invalid.
116+ const idl_text = innerText ( root . querySelector ( '#idl-index + pre' ) ) ;
117+ const idl_ast = idl . parse ( idl_text ) ;
118+
105119let exitCode = 0 ;
106120function error ( message ) {
107121 console . error ( message ) ;
@@ -148,11 +162,17 @@ const ALGORITHM_STEP_SELECTOR = '.algorithm li > p:not(.issue)';
148162// * `html` - HTML source, with style/script removed
149163// * `text` - rendered text content
150164// * `root.querySelectorAll()` - operate on DOM-like nodes
165+ // * `idl_ast` - WebIDL AST (see https://github.com/w3c/webidl2.js)
151166
152167// Checks are marked with one of these tags:
153168// * [Generic] - could apply to any spec
154169// * [WebNN] - very specific to the WebNN spec
155170
171+ // [Generic] Report warnings found when parsing the WebIDL.
172+ for ( const err of idl . validate ( idl_ast ) ) {
173+ error ( `WebIDL: ${ err . message } ` ) ;
174+ }
175+
156176// [Generic] Look for merge markers
157177for ( const match of source . matchAll ( / < { 7 } | > { 7 } | ^ = { 7 } $ / mg) ) {
158178 error ( `Merge conflict marker: ${ format ( match ) } ` ) ;
@@ -171,8 +191,7 @@ for (const match of html.matchAll(/(?:^|\s)(\w+) \1(?:$|\s)/ig)) {
171191// [Generic] Verify IDL lines wrap to avoid horizontal scrollbars
172192const MAX_IDL_WIDTH = 88 ;
173193for ( const idl of root . querySelectorAll ( 'pre.idl' ) ) {
174- idl . innerText . split ( / \n / ) . forEach ( line => {
175- line = line . replace ( / & l t ; / g, '<' ) ; // parser's notion of "innerText" is weird
194+ innerText ( idl ) . split ( / \n / ) . forEach ( line => {
176195 if ( line . length > MAX_IDL_WIDTH ) {
177196 error ( `Overlong IDL: ${ line } ` ) ;
178197 }
@@ -309,9 +328,7 @@ for (const term of root.querySelectorAll('#normative + dl > dt')) {
309328
310329// [Generic] Detect syntax errors in JS.
311330for ( const pre of root . querySelectorAll ( 'pre.highlight:not(.idl)' ) ) {
312- const script = pre . innerText . replaceAll ( / & a m p ; / g, '&' )
313- . replaceAll ( / & l t ; / g, '<' )
314- . replaceAll ( / & g t ; / g, '>' ) ;
331+ const script = innerText ( pre ) ;
315332 try {
316333 const f = AsyncFunction ( [ ] , '"use strict";' + script ) ;
317334 } catch ( ex ) {
@@ -405,8 +422,8 @@ for (const table of root.querySelectorAll('table.data').filter(e => e.id.startsW
405422 }
406423}
407424
408- // TODO: Generate this from the IDL itself.
409- const dictionaryTypes = [ 'MLOperandDescriptor' , 'MLContextLostInfo' ] ;
425+ const dictionaryTypes =
426+ idl_ast . filter ( o => o . type === 'dictionary' ) . map ( o => o . name ) ;
410427
411428// [Generic] Ensure JS objects are created with explicit realm
412429for ( const match of text . matchAll ( / a n e w p r o m i s e \b (? ! i n r e a l m ) / g) ) {
0 commit comments