Files
react-native-devtools-frontend/scripts/javascript_natives/index.js
T
Joel Einbinder 705daf04ca DevTools: Deduplicate more function signatures for argument hints
Sometimes a function would have multiple signatures in the IDL files
with the difference just being some of the arguments are optional.
This patch improves the deduplication logic to correctly support
array.splice(start, ?deleteCount, ...items)

Change-Id: Ie9230d54dadd1c8c7cadbb92afc37fbfa87ea857
Reviewed-on: https://chromium-review.googlesource.com/1062885
Reviewed-by: Andrey Lushnikov <lushnikov@chromium.org>
Commit-Queue: Joel Einbinder <einbinder@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#559342}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: ddcb027a039f212e0701f7118ea7dd8b8cea65b4
2018-05-16 23:57:41 +00:00

193 lines
6.2 KiB
JavaScript

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: constructor});
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)};`);
}