Update javascript_natives/index.js script
This change also includes a manual run of the script to update
NativeFunctions.js.
Change-Id: I9ce1c4979674d9513cb7259e3e311e30b13de9ed
Bug: 1255619
Doc: https://goo.gle/devtools-js-argument-hints
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3399837
Reviewed-by: Mathias Bynens <mathias@chromium.org>
Commit-Queue: Johan Bay <jobay@chromium.org>
diff --git a/scripts/javascript_natives/index.js b/scripts/javascript_natives/index.js
index 4bbfd9a..26dc00e 100644
--- a/scripts/javascript_natives/index.js
+++ b/scripts/javascript_natives/index.js
@@ -2,19 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-const WebIDL2 = require('webidl2');
-const fs = require('fs');
-const path = require('path');
-const ts = require('typescript');
-const glob = require('glob');
-const methods = {
- __proto__: null
-};
+import * as fs from 'fs';
+import glob from 'glob';
+import * as path from 'path';
+import ts from 'typescript';
+import * as WebIDL2 from 'webidl2';
+
+import {parseTSFunction, postProcess, walkRoot} from './helpers.js';
+
const program = ts.createProgram(
[
- path.join(__dirname, 'node_modules', 'typescript', 'lib', 'lib.esnext.d.ts'),
+ new URL('node_modules/typescript/lib/lib.esnext.d.ts', import.meta.url).pathname,
],
- {noLib: true});
+ {noLib: false, types: []});
+
for (const file of program.getSourceFiles()) {
ts.forEachChild(file, node => {
if (node.kind === ts.SyntaxKind.InterfaceDeclaration) {
@@ -31,201 +32,63 @@
});
}
-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);
-}
-
// Assume the DevTools front-end repository is at
// `devtools/devtools-frontend`, where `devtools` is on the same level
// as `chromium`. This matches `scripts/npm_test.js`.
-glob(
- '../../../../chromium/src/third_party/blink/renderer/+(core|modules)/**/*.idl', {cwd: process.env.PWD},
- function(er, files) {
- for (const file of files) {
- if (file.includes('testing')) {
- continue;
- }
- const data = fs.readFileSync(path.join(process.env.PWD, file), 'utf8');
- const lines = data.split('\n');
- const newLines = [];
- for (const line of lines) {
- if (!line.includes(' attribute ')) {
- newLines.push(line);
- }
- }
+const files =
+ glob.sync('../../../../chromium/src/third_party/blink/renderer/+(core|modules)/**/*.idl', {cwd: process.env.PWD});
- 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));
+for (const file of files) {
+ if (file.includes('testing')) {
+ continue;
+ }
+ const data = fs.readFileSync(path.join(process.env.PWD, file), 'utf8');
+ const lines = data.split('\n');
+ const newLines = [];
+ for (const line of lines) {
+ if (!line.includes(' attribute ')) {
+ newLines.push(line);
}
}
- if (thing.type.includes('operation')) {
- storeMethod(thing.static ? (parent.name + 'Constructor') : parent.name, thing.name, thing.arguments.map(argName));
- return;
+
+ try {
+ WebIDL2.parse(newLines.join('\n')).forEach(walkRoot);
+ } catch (e) {
+ // console.error(file);
}
- if (thing.members) {
- for (const member of thing.members) {
- walk(member, thing);
- }
- }
+
+ // Source for Console spec: https://console.spec.whatwg.org/#idl-index
+ WebIDL2
+ .parse(`
+[Exposed=(Window,Worker,Worklet)]
+namespace console { // but see namespace object requirements below
+ // Logging
+ undefined assert(optional boolean condition = false, any... data);
+ undefined clear();
+ undefined debug(any... data);
+ undefined error(any... data);
+ undefined info(any... data);
+ undefined log(any... data);
+ undefined table(optional any tabularData, optional sequence<DOMString> properties);
+ undefined trace(any... data);
+ undefined warn(any... data);
+ undefined dir(optional any item, optional object? options);
+ undefined dirxml(any... data);
+
+ // Counting
+ undefined count(optional DOMString label = "default");
+ undefined countReset(optional DOMString label = "default");
+
+ // Grouping
+ undefined group(any... data);
+ undefined groupCollapsed(any... data);
+ undefined groupEnd();
+
+ // Timing
+ undefined time(optional DOMString label = "default");
+ undefined timeLog(optional DOMString label = "default", any... data);
+ undefined timeEnd(optional DOMString label = "default");
+};
+`).forEach(walkRoot);
}
-
-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', 'models', 'javascript_metadata', 'NativeFunctions.js'),
- `// 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(__dirname, '..', '..'), __filename)}
-
-export const NativeFunctions = ${JSON.stringify(functions, null, 2)};
-`);
-}
+postProcess();