const WebIDL2 = require('webidl2'); const fs = require('fs'); const path = require('path'); const ts = require('typescript'); const glob = require('glob'); const methods = { __proto__: null }; const methodsByName = { __proto__: null }; const program = ts.createProgram([path.join(__dirname, 'node_modules', 'typescript', 'lib', 'lib.esnext.d.ts')], {noLib: true}); for (const file of program.getSourceFiles()) { ts.forEachChild(file, node => { if (node.kind === ts.SyntaxKind.InterfaceDeclaration) { for (const member of node.members) { if (member.kind === ts.SyntaxKind.MethodSignature) parseTSFunction(member, node); } } if (node.kind === ts.SyntaxKind.FunctionDeclaration) parseTSFunction(node, {name: {text: 'Window'}}); }); } 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); } const files = glob('../../../+(core|modules)/**/*.idl', {cwd: __dirname}, function(er, files) { for (const file of files) { if (file.includes('testing')) continue; const data = fs.readFileSync(path.join(__dirname, file), 'utf8'); const lines = data.split('\n'); const newLines = []; for (line of lines) { if (!line.includes(' attribute ')) newLines.push(line); } try { WebIDL2.parse(newLines.join('\n')).forEach(walk); } catch (e) { // console.error(file); } } WebIDL2 .parse(` namespace console { void assert(optional boolean condition = false, any... data); void clear(); void count(optional DOMString label = "default"); void debug(any... data); void dir(any item, optional object? options); void dirxml(any... data); void error(any... data); void group(any... data); void groupCollapsed(any... data); void groupEnd(); void info(any... data); void log(any... data); void profile(optional DOMString title); void profileEnd(optional DOMString title); void table(any... tabularData); void time(optional DOMString label); void timeEnd(optional DOMString label); void timeStamp(optional DOMString name); void trace(any... data); void warn(any... data); }; `).forEach(walk); postProcess(); }); function walk(thing, parent) { if (thing.type === 'interface') { const constructor = thing.extAttrs.find(extAttr => extAttr.name === 'Constructor'); if (constructor && constructor.arguments && thing.extAttrs.find(extAttr => extAttr.name === 'Exposed')) storeMethod('Window', thing.name, constructor.arguments.map(argName)); const namedConstructor = thing.extAttrs.find(extAttr => extAttr.name === 'NamedConstructor'); if (namedConstructor && namedConstructor.arguments) storeMethod('Window', namedConstructor.rhs.value, namedConstructor.arguments.map(argName)); } if (thing.type.includes('operation')) { storeMethod(thing.static ? (parent.name + 'Constructor') : parent.name, thing.name, thing.arguments.map(argName)); return; } if (thing.members) { for (const member of thing.members) walk(member, thing); } } function argName(a) { let name = a.name; if (a.optional) name = '?' + name; if (a.variadic) name = '...' + name; return name; } function storeMethod(parent, name, args) { if (!methods[name]) methods[name] = {__proto__: null}; if (!methods[name][parent]) methods[name][parent] = []; methods[name][parent].push(args); } function postProcess() { for (const name in methods) { const jsonParents = new Set(); for (const parent in methods[name]) { const signatures = methods[name][parent]; 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[name][parent] = filteredSignatures; jsonParents.add(JSON.stringify(filteredSignatures)); } if (jsonParents.size === 1) { methods[name] = {'*': JSON.parse(jsonParents.values().next().value)}; } for (const parent in methods[name]) { const signatures = methods[name][parent]; if (signatures.length === 1 && !signatures[0].length) delete methods[name][parent]; } if (!Object.keys(methods[name]).length) delete methods[name]; } const functions = []; for (const name in methods) { if (methods[name]['*']) { functions.push({name, signatures: methods[name]['*']}); } else { for (const parent in methods[name]) { if (parent.endsWith('Constructor')) functions.push({name, signatures: methods[name][parent], static: true, receiver: parent.substring(0, parent.length - 'Constructor'.length)}); else functions.push({name, signatures: methods[name][parent], receiver: parent}); } } } fs.writeFileSync( path.join(__dirname, '..', '..', 'front_end', 'javascript_metadata', 'NativeFunctions.js'), `// Generated from ${path.relative(path.join(__dirname, '..', '..'), __filename)} JavaScriptMetadata.NativeFunctions = ${JSON.stringify(functions)};`); }