webidl2js
Version:
Auto-generates class structures for WebIDL specifications
181 lines (151 loc) • 4.41 kB
JavaScript
;
const { extname } = require("path");
const keywords = require("./keywords.js");
function getDefault(dflt) {
switch (dflt.type) {
case "boolean":
case "string":
return JSON.stringify(dflt.value);
case "number":
return dflt.value;
case "null":
case "NaN":
return dflt.type;
case "Infinity":
return `${dflt.negative ? "-" : ""}Infinity`;
case "sequence":
return "[]";
}
throw new Error(`Unexpected default type: ${dflt.type}`);
}
function getExtAttr(attrs, name) {
for (let i = 0; i < attrs.length; ++i) {
if (attrs[i].name === name) {
return attrs[i];
}
}
return null;
}
function isGlobal(idl) {
return Boolean(getExtAttr(idl.extAttrs, "Global"));
}
function hasCEReactions(idl) {
return Boolean(getExtAttr(idl.extAttrs, "CEReactions"));
}
function isOnInstance(memberIDL, interfaceIDL) {
return memberIDL.special !== "static" && isGlobal(interfaceIDL);
}
function symbolName(symbol) {
const desc = String(symbol).replace(/^Symbol\((.*)\)$/, "$1");
if (!desc.startsWith("Symbol.")) {
throw new Error(`Internal error: Unsupported property name ${String(symbol)}`);
}
return desc;
}
function propertyName(name) {
// All Web IDL identifiers are valid JavaScript PropertyNames, other than those with '-'.
const isJSIdentifier = !name.includes("-");
if (isJSIdentifier) {
return name;
}
return JSON.stringify(name);
}
function stringifyPropertyKey(prop) {
return typeof prop === "symbol" ? `[${symbolName(prop)}]` : propertyName(prop);
}
function stringifyPropertyName(prop) {
return typeof prop === "symbol" ? symbolName(prop) : JSON.stringify(propertyName(prop));
}
// type can be "accessor" or "regular"
function getPropertyDescriptorModifier(currentDesc, targetDesc, type, value = undefined) {
const changes = [];
if (value !== undefined) {
changes.push(`value: ${value}`);
}
if (currentDesc.configurable !== targetDesc.configurable) {
changes.push(`configurable: ${targetDesc.configurable}`);
}
if (currentDesc.enumerable !== targetDesc.enumerable) {
changes.push(`enumerable: ${targetDesc.enumerable}`);
}
if (type !== "accessor" && currentDesc.writable !== targetDesc.writable) {
changes.push(`writable: ${targetDesc.writable}`);
}
if (changes.length === 0) {
return undefined;
}
return `{ ${changes.join(", ")} }`;
}
const defaultDefinePropertyDescriptor = {
configurable: false,
enumerable: false,
writable: false
};
function formatArgs(args) {
return args
.filter(name => name !== null && name !== undefined && name !== "")
.map(name => name + (keywords.has(name) ? "_" : ""))
.join(", ");
}
function toKey(type, func = "") {
return String(func + type).replace(/[./-]+/g, " ").trim().replace(/ /g, "_");
}
const PACKAGE_NAME_REGEX = /^(?:@([^/]+?)[/])?([^/]+?)$/u;
class RequiresMap extends Map {
constructor(ctx) {
super();
this.ctx = ctx;
}
add(name, func = "") {
const key = toKey(name, func);
// If `name` is a package name or has a file extension, then use it as-is,
// otherwise append the `.js` file extension:
const importPath = PACKAGE_NAME_REGEX.test(name) || extname(name) ? name : `${name}.js`;
let req = `require(${JSON.stringify(importPath)})`;
if (func) {
req += `.${func}`;
}
this.addRaw(key, req);
return key;
}
addRelative(type, func = "") {
const key = toKey(type, func);
const path = type.startsWith(".") ? type : `./${type}`;
let req = `require("${path}.js")`;
if (func) {
req += `.${func}`;
}
this.addRaw(key, req);
return key;
}
addRaw(key, expr) {
if (this.has(key) && this.get(key) !== expr) {
throw new Error(`Internal error: Variable name clash: ${key}; was ${this.get(key)}, adding: ${expr}`);
}
super.set(key, expr);
}
merge(src) {
if (!src || !(src instanceof RequiresMap)) {
return;
}
for (const [key, val] of src) {
this.addRaw(key, val);
}
}
generate() {
return [...this.keys()].map(key => `const ${key} = ${this.get(key)};`).join("\n");
}
}
module.exports = {
getDefault,
getExtAttr,
isGlobal,
hasCEReactions,
isOnInstance,
stringifyPropertyKey,
stringifyPropertyName,
getPropertyDescriptorModifier,
defaultDefinePropertyDescriptor,
formatArgs,
RequiresMap
};