Skip to content

Commit 5daf9d7

Browse files
Tooling: Use WebIDL2 parser when linting to identify dictionaries (#831)
Dictionary types need special treatment in a lint rule verifying that realms are used correctly. This requires knowing which types are dictionaries vs. interfaces. This was previously hard-coded, which is a maintenance burden. Improve this by pulling in the webidl2.js library and using it to parse (and validate!) the spec's WebIDL, which gives us a list of dictionary types.
1 parent 4c8c75e commit 5daf9d7

File tree

2 files changed

+26
-8
lines changed

2 files changed

+26
-8
lines changed

tools/lint.mjs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
'use strict';
2222
import fs from 'node:fs/promises';
2323
import {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')) {
102103
const html = root.innerHTML;
103104
const 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(/&/g, '&')
110+
.replaceAll(/&lt;/g, '<')
111+
.replaceAll(/&gt;/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+
105119
let exitCode = 0;
106120
function 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
157177
for (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
172192
const MAX_IDL_WIDTH = 88;
173193
for (const idl of root.querySelectorAll('pre.idl')) {
174-
idl.innerText.split(/\n/).forEach(line => {
175-
line = line.replace(/&lt;/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.
311330
for (const pre of root.querySelectorAll('pre.highlight:not(.idl)')) {
312-
const script = pre.innerText.replaceAll(/&amp;/g, '&')
313-
.replaceAll(/&lt;/g, '<')
314-
.replaceAll(/&gt;/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
412429
for (const match of text.matchAll(/ a new promise\b(?! in realm)/g)) {

tools/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"dependencies": {
3-
"node-html-parser": "^6.1.13"
3+
"node-html-parser": "^6.1.13",
4+
"webidl2": "^24.4.1"
45
}
56
}

0 commit comments

Comments
 (0)