UNPKG

chrome-devtools-frontend

Version:
233 lines (213 loc) • 6.89 kB
// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // @ts-check import * as fs from 'fs'; import * as path from 'path'; import {fileURLToPath} from 'url'; /** @type {Map<string, Map<string, string[][]>>} */ const methods = new Map(); /** @type {Map<string, string[]>} */ const includes = new Map(); export function clearState() { methods.clear(); includes.clear(); } export function parseTSFunction(func, node) { if (!func.name.escapedText) { return; } const args = func.parameters .map(p => { let text = p.name.escapedText; if (p.questionToken) { text = '?' + text; } if (p.dotDotDotToken) { text = '...' + text; } return text; }) .filter(x => x !== 'this'); storeMethod(node.name.text, func.name.escapedText, args); } /** * @param {WebIDL2.IDLRootType} thing * */ export function walkRoot(thing) { switch (thing.type) { case 'interface': walkInterface(thing); break; case 'interface mixin': case 'namespace': walkMembers(thing); break; case 'includes': walkIncludes(thing); break; } } /** * @param {WebIDL2.IncludesType} thing * */ function walkIncludes(thing) { if (includes.has(thing.includes)) { includes.get(thing.includes).push(thing.target); } else { includes.set(thing.includes, [thing.target]); } } /** * @param {WebIDL2.InterfaceType} thing * */ function walkInterface(thing) { thing.members.forEach(member => { switch (member.type) { case 'constructor': storeMethod('Window', thing.name, member.arguments.map(argName)); break; case 'operation': handleOperation(member); } }); const namedConstructor = thing.extAttrs.find(extAttr => extAttr.name === 'NamedConstructor'); if (namedConstructor && namedConstructor.arguments) { storeMethod('Window', namedConstructor.rhs.value, namedConstructor.arguments.map(argName)); } } /** * @param {WebIDL2.NamespaceType | WebIDL2.InterfaceMixinType} thing * */ function walkMembers(thing) { thing.members.forEach(member => { if (member.type === 'operation') { handleOperation(member); } }); } /** * @param {WebIDL2.OperationMemberType} member * */ function handleOperation(member) { storeMethod(member.parent.name, member.name, member.arguments.map(argName)); } /** * @param {WebIDL2.Argument} a * */ function argName(a) { let name = a.name; if (a.optional) { name = '?' + name; } if (a.variadic) { name = '...' + name; } return name; } /** * @param {string} parent * @param {string} name * @param {Array<string>} args * */ function storeMethod(parent, name, args) { if (!methods.has(name)) { methods.set(name, new Map()); } const method = methods.get(name); if (!method.has(parent)) { method.set(parent, []); } method.get(parent).push(args); } export function postProcess(dryRun = false) { for (const name of methods.keys()) { // We use the set jsonParents to track the set of different signatures across parent for this function name. // If all signatures are identical, we leave out the parent and emit a single NativeFunction entry without receiver. const jsonParents = new Set(); for (const [parent, signatures] of methods.get(name)) { signatures.sort((a, b) => a.length - b.length); const filteredSignatures = []; for (const signature of signatures) { const smallerIndex = filteredSignatures.findIndex(smaller => startsTheSame(smaller, signature)); if (smallerIndex !== -1) { filteredSignatures[smallerIndex] = (signature.map((arg, index) => { const otherArg = filteredSignatures[smallerIndex][index]; if (otherArg) { return otherArg.length > arg.length ? otherArg : arg; } if (arg.startsWith('?') || arg.startsWith('...')) { return arg; } return '?' + arg; })); } else { filteredSignatures.push(signature); } } function startsTheSame(smaller, bigger) { for (let i = 0; i < smaller.length; i++) { const withoutQuestion = str => /[\?]?(.*)/.exec(str)[1]; if (withoutQuestion(smaller[i]) !== withoutQuestion(bigger[i])) { return false; } } return true; } methods.get(name).set(parent, filteredSignatures); jsonParents.add(JSON.stringify(filteredSignatures)); } // If all parents had the same signature for this name, we put a `*` as parent for this entry. if (jsonParents.size === 1) { methods.set(name, new Map([['*', JSON.parse(jsonParents.values().next().value)]])); } for (const [parent, signatures] of methods.get(name)) { if (signatures.length === 1 && !signatures[0].length) { methods.get(name).delete(parent); } } if (methods.get(name).size === 0) { methods.delete(name); } } const functions = []; for (const [name, method] of methods) { if (method.has('*')) { // All parents had the same signature so we emit an entry without receiver. functions.push({name, signatures: method.get('*')}); } else { const receiversMap = new Map(); for (const [parent, signatures] of method) { const receivers = receiversMap.get(JSON.stringify(signatures)) || new Set(); if (includes.has(parent)) { includes.get(parent).forEach(receiver => receivers.add(receiver)); } else { receivers.add(parent); } receiversMap.set(JSON.stringify(signatures), receivers); } for (const [signatures, receivers] of receiversMap) { functions.push({name, signatures: JSON.parse(signatures), receivers: Array.from(receivers)}); } } } const output = `export const NativeFunctions = [\n${ functions .map( entry => ` {\n${Object.entries(entry).map(kv => ` ${kv[0]}: ${JSON.stringify(kv[1])}`).join(',\n')}\n }`) .join(',\n')}\n];`; if (dryRun) { return output; } fs.writeFileSync( (new URL('../../front_end/models/javascript_metadata/NativeFunctions.js', import.meta.url)).pathname, `// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Generated from ${ path.relative(path.join(fileURLToPath(import.meta.url), '..', '..'), fileURLToPath(import.meta.url))} // clang-format off ${output} `); }