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