Skip to content

Commit 522dbd5

Browse files
Bashamegasaschanaz
andauthored
Feature: Add method handling to mixin processing in KDL (#2100)
Co-authored-by: Kagami Sascha Rosylight <[email protected]>
1 parent ba94904 commit 522dbd5

File tree

3 files changed

+104
-40
lines changed

3 files changed

+104
-40
lines changed

inputfiles/addedTypes.jsonc

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,4 @@
11
{
2-
"mixins": {
3-
"mixin": {
4-
"DocumentOrShadowRoot": {
5-
// Manually moved from Document
6-
// See https://github.com/w3c/csswg-drafts/issues/5886 and https://github.com/w3c/csswg-drafts/issues/556
7-
"methods": {
8-
"method": {
9-
"elementFromPoint": {
10-
"name": "elementFromPoint",
11-
"overrideSignatures": [
12-
"elementFromPoint(x: number, y: number): Element | null"
13-
]
14-
},
15-
"elementsFromPoint": {
16-
"name": "elementsFromPoint",
17-
"overrideSignatures": [
18-
"elementsFromPoint(x: number, y: number): Element[]"
19-
]
20-
}
21-
}
22-
}
23-
}
24-
}
25-
},
262
"interfaces": {
273
"interface": {
284
// ImportMeta is not a true DOM interface, but we are forced to declare it as one in order to emit method definitions.

inputfiles/patches/cssom-view.kdl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Manually moved from Document
2+
// See https://github.com/w3c/csswg-drafts/issues/5886 and https://github.com/w3c/csswg-drafts/issues/556
3+
interface-mixin DocumentOrShadowRoot {
4+
method elementFromPoint {
5+
type Element nullable=#true
6+
param x type=long
7+
param y type=long
8+
}
9+
method elementsFromPoint {
10+
type sequence {
11+
type Element
12+
}
13+
param x type=long
14+
param y type=long
15+
}
16+
}

src/build/patches.ts

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
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";
311
import { readdir, readFile } from "fs/promises";
412
import { 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
*/
6895
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]);
7397
const values: string[] = [];
7498

7599
for (const child of node.children) {
@@ -96,23 +120,26 @@ function handleEnum(node: Node): Enum {
96120
*/
97121
function 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
*/
133161
function 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
*/
144172
function 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

Comments
 (0)