summaryrefslogtreecommitdiffstats
path: root/tools/ts/build_xpcom.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/ts/build_xpcom.js')
-rw-r--r--tools/ts/build_xpcom.js139
1 files changed, 139 insertions, 0 deletions
diff --git a/tools/ts/build_xpcom.js b/tools/ts/build_xpcom.js
new file mode 100644
index 0000000000..d09b18fb69
--- /dev/null
+++ b/tools/ts/build_xpcom.js
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/**
+ * Build: <objdir>/dist/@types/lib.gecko.xpcom.d.ts,
+ *
+ * from: <objdir>/config/makefiles/xpidl/*.d.json type definition files,
+ * generated by a previous build step.
+ */
+
+const fs = require("fs");
+const URL = "https://searchfox.org/mozilla-central/source/";
+
+const HEADER = `/**
+ * NOTE: Do not modify this file by hand.
+ * Content was generated from source XPCOM .idl files.
+ */
+`;
+
+// Emit a typescript interface, along with any related enums.
+function ts_interface(iface) {
+ let lines = [];
+ let base = iface.base;
+ iface.class = iface.id;
+
+ // Make QueryInterface optional, enable plain objects to pass as nsISupports.
+ let partial = iface.id === "nsISupports" ? "?" : "";
+
+ let enums = iface.enums.map(e => `typeof ${iface.id}.${e.id}`).join(" & ");
+ if (enums) {
+ base += `, Enums<${enums}>`;
+ iface.class += `, ${enums}`;
+
+ // Close the global scope, avoid polluting it with the namespace value.
+ lines.push("} // global\n");
+ lines.push(`declare namespace ${iface.id} {\n`);
+
+ for (let e of iface.enums) {
+ lines.push(`enum ${e.id} {`);
+ for (let v of e.variants) {
+ lines.push(` ${v.name} = ${v.value},`);
+ }
+ lines.push("}\n");
+ }
+
+ lines.push("}\n");
+ lines.push("declare global {\n");
+ }
+
+ // Handle [function] interfaces.
+ if (iface.callable) {
+ lines.push(`type ${iface.id} = Callable<{`);
+ } else {
+ lines.push(`interface ${iface.id} ${base ? `extends ${base} ` : ""}{`);
+ }
+
+ for (let c of iface.consts) {
+ lines.push(` readonly ${c.name}: ${c.value};`);
+ }
+
+ if (iface.consts.length && iface.members.length) {
+ // For style points.
+ lines.push("");
+ }
+
+ for (let m of iface.members) {
+ if (!m.args) {
+ lines.push(` ${m.readonly ? "readonly " : ""}${m.name}: ${m.type};`);
+ } else {
+ let args = [];
+ for (let arg of m.args) {
+ // If this is the generic parameter, adjust its type.
+ let type = arg.name === m.iid_is ? "T" : arg.type;
+ args.push(`${arg.name}${arg.optional ? "?" : ""}: ${type}`);
+ }
+ let type = `(${args.join(", ")}): ${m.type}`;
+ // Adjust signature if this is a generic method.
+ let signature = m.iid_is ? `<T extends nsIID>${type}<T>` : type;
+ lines.push(` ${m.name}${partial}${signature};`);
+ }
+ }
+
+ lines.push(iface.callable ? "}>\n" : "}\n");
+ return lines;
+}
+
+// Link all generated .d.json files into a self-contained ts typelib.
+function ts_link(dir, files) {
+ let lines = [HEADER, "declare global {\n"];
+ let typedefs = {};
+ let iids = [];
+
+ for (let djson of files) {
+ let modules = JSON.parse(fs.readFileSync(`${dir}/${djson}`, "utf8"));
+
+ for (let mod of modules) {
+ lines.push(`// ${URL}${mod.path}\n`);
+ Object.assign(typedefs, Object.fromEntries(mod.typedefs ?? []));
+
+ for (let iface of mod.interfaces ?? []) {
+ if (iface.id !== "nsIXPCComponents_Interfaces") {
+ lines = lines.concat(ts_interface(iface));
+ iids.push(` ${iface.id}: nsJSIID<${iface.class}>;`);
+ }
+ }
+ }
+ }
+
+ lines.push("interface nsIXPCComponents_Interfaces {");
+ lines = lines.concat(iids);
+ lines.push("}\n");
+
+ lines.push("} // global\n");
+
+ lines.push("// Typedefs from xpidl.");
+ for (let [id, type] of Object.entries(typedefs).sort()) {
+ lines.push(`type ${id} = ${type};`);
+ }
+ lines.push("");
+
+ // Include xpcom builtins.
+ lines.push(fs.readFileSync(`${__dirname}/fixtures/intrinsics.d.ts`, "utf8"));
+ return lines;
+}
+
+// For testing.
+module.exports = { ts_link };
+
+function main(lib_dts, djson_dir, ...djson_files) {
+ let dts = ts_link(djson_dir, djson_files).join("\n");
+ console.log(`[INFO] ${lib_dts} (${dts.length.toLocaleString()} bytes)`);
+ fs.writeFileSync(lib_dts, dts);
+}
+
+if (require.main === module) {
+ main(...process.argv.slice(2));
+}