blob: 19c49acac6f67ce77d7fbaa0ecf813d2d033fa62 [file] [log] [blame]
Johan Bay49f681a2022-01-27 09:25:13 +00001// Copyright 2021 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// @ts-check
6
7import * as fs from 'fs';
8import * as path from 'path';
9import {fileURLToPath} from 'url';
10
11/** @type {Map<string, Map<string, string[][]>>} */
12const methods = new Map();
13
14export function clearState() {
15 methods.clear();
16}
17
18export function parseTSFunction(func, node) {
19 if (!func.name.escapedText) {
20 return;
21 }
22
23 const args = func.parameters
24 .map(p => {
25 let text = p.name.escapedText;
26 if (p.questionToken) {
27 text = '?' + text;
28 }
29 if (p.dotDotDotToken) {
30 text = '...' + text;
31 }
32 return text;
33 })
34 .filter(x => x !== 'this');
35 storeMethod(node.name.text, func.name.escapedText, args);
36}
37
38/**
39 * @param {WebIDL2.IDLRootType} thing
40 * */
41export function walkRoot(thing) {
42 switch (thing.type) {
43 case 'interface':
44 walkInterface(thing);
45 break;
46 case 'interface mixin':
47 case 'namespace':
48 walkMembers(thing);
49 break;
50 }
51}
52
53/**
54 * @param {WebIDL2.InterfaceType} thing
55 * */
56function walkInterface(thing) {
57 thing.members.forEach(member => {
58 switch (member.type) {
59 case 'constructor':
60 storeMethod('Window', thing.name, member.arguments.map(argName));
61 break;
62 case 'operation':
63 handleOperation(member);
64 }
65 });
66 const namedConstructor = thing.extAttrs.find(extAttr => extAttr.name === 'NamedConstructor');
67 if (namedConstructor && namedConstructor.arguments) {
68 storeMethod('Window', namedConstructor.rhs.value, namedConstructor.arguments.map(argName));
69 }
70}
71
72/**
73 * @param {WebIDL2.NamespaceType | WebIDL2.InterfaceMixinType} thing
74 * */
75function walkMembers(thing) {
76 thing.members.forEach(member => {
77 if (member.type === 'operation') {
78 handleOperation(member);
79 }
80 });
81}
82
83/**
84 * @param {WebIDL2.OperationMemberType} member
85 * */
86function handleOperation(member) {
Johan Bayebb70e32022-05-23 17:46:37 +020087 storeMethod(member.parent.name, member.name, member.arguments.map(argName));
Johan Bay49f681a2022-01-27 09:25:13 +000088}
89
90/**
91 * @param {WebIDL2.Argument} a
92 * */
93function argName(a) {
94 let name = a.name;
95 if (a.optional) {
96 name = '?' + name;
97 }
98 if (a.variadic) {
99 name = '...' + name;
100 }
101 return name;
102}
103
104/**
105 * @param {string} parent
106 * @param {string} name
107 * @param {Array<string>} args
108 * */
109function storeMethod(parent, name, args) {
110 if (!methods.has(name)) {
111 methods.set(name, new Map());
112 }
113 const method = methods.get(name);
114 if (!method.has(parent)) {
115 method.set(parent, []);
116 }
117 method.get(parent).push(args);
118}
119
120export function postProcess(dryRun = false) {
121 for (const name of methods.keys()) {
122 // We use the set jsonParents to track the set of different signatures across parent for this function name.
123 // If all signatures are identical, we leave out the parent and emit a single NativeFunction entry without receiver.
124 const jsonParents = new Set();
125 for (const [parent, signatures] of methods.get(name)) {
126 signatures.sort((a, b) => a.length - b.length);
127 const filteredSignatures = [];
128 for (const signature of signatures) {
129 const smallerIndex = filteredSignatures.findIndex(smaller => startsTheSame(smaller, signature));
130 if (smallerIndex !== -1) {
131 filteredSignatures[smallerIndex] = (signature.map((arg, index) => {
132 const otherArg = filteredSignatures[smallerIndex][index];
133 if (otherArg) {
134 return otherArg.length > arg.length ? otherArg : arg;
135 }
136 if (arg.startsWith('?') || arg.startsWith('...')) {
137 return arg;
138 }
139 return '?' + arg;
140 }));
141 } else {
142 filteredSignatures.push(signature);
143 }
144 }
145
146 function startsTheSame(smaller, bigger) {
147 for (let i = 0; i < smaller.length; i++) {
148 const withoutQuestion = str => /[\?]?(.*)/.exec(str)[1];
149 if (withoutQuestion(smaller[i]) !== withoutQuestion(bigger[i])) {
150 return false;
151 }
152 }
153 return true;
154 }
155
156 methods.get(name).set(parent, filteredSignatures);
157 jsonParents.add(JSON.stringify(filteredSignatures));
158 }
159 // If all parents had the same signature for this name, we put a `*` as parent for this entry.
160 if (jsonParents.size === 1) {
161 methods.set(name, new Map([['*', JSON.parse(jsonParents.values().next().value)]]));
162 }
163 for (const [parent, signatures] of methods.get(name)) {
164 if (signatures.length === 1 && !signatures[0].length) {
165 methods.get(name).delete(parent);
166 }
167 }
168 if (methods.get(name).size === 0) {
169 methods.delete(name);
170 }
171 }
172 const functions = [];
173 for (const [name, method] of methods) {
174 if (method.has('*')) {
175 // All parents had the same signature so we emit an entry without receiver.
176 functions.push({name, signatures: method.get('*')});
177 } else {
Johan Bayebb70e32022-05-23 17:46:37 +0200178 const receiversMap = new Map();
Johan Bay49f681a2022-01-27 09:25:13 +0000179 for (const [parent, signatures] of method) {
Johan Bayebb70e32022-05-23 17:46:37 +0200180 const receivers = receiversMap.get(JSON.stringify(signatures)) || [];
181 receivers.push(parent);
182 receiversMap.set(JSON.stringify(signatures), receivers);
183 }
184 for (const [signatures, receivers] of receiversMap) {
185 functions.push({name, signatures: JSON.parse(signatures), receivers});
Johan Bay49f681a2022-01-27 09:25:13 +0000186 }
187 }
188 }
189 const output = `export const NativeFunctions = [\n${
190 functions
191 .map(
192 entry =>
193 ` {\n${Object.entries(entry).map(kv => ` ${kv[0]}: ${JSON.stringify(kv[1])}`).join(',\n')}\n }`)
194 .join(',\n')}\n];`;
195
196 if (dryRun) {
197 return output;
198 }
199
200 fs.writeFileSync(
201 (new URL('../../front_end/models/javascript_metadata/NativeFunctions.js', import.meta.url)).pathname,
202 `// Copyright 2020 The Chromium Authors. All rights reserved.
203// Use of this source code is governed by a BSD-style license that can be
204// found in the LICENSE file.
205// Generated from ${
206 path.relative(path.join(fileURLToPath(import.meta.url), '..', '..'), fileURLToPath(import.meta.url))}
207
208${output}
209`);
210}