UNPKG

webidl2js

Version:

Auto-generates class structures for WebIDL specifications

370 lines (341 loc) 12.5 kB
"use strict"; const Overloads = require("./overloads"); const Types = require("./types"); const utils = require("./utils"); function isOrIncludes(ctx, parent, predicate) { parent = Types.resolveType(ctx, parent); return predicate(parent) || (parent.union && parent.idlType.some(predicate)); } function generateVarConversion(ctx, overload, i, parent, errPrefix, targetIdx = i) { const requires = new utils.RequiresMap(ctx); const idlType = overload.typeList[i]; // Always (try to) force-convert dictionaries const isDefaultedDictionary = overload.operation.arguments[i].default && overload.operation.arguments[i].default.type === "dictionary"; if (isDefaultedDictionary && !ctx.dictionaries.has(idlType.idlType)) { throw new Error(`The parameter ${overload.operation.arguments[i].name} was defaulted to {}, but no dictionary ` + `named ${idlType.idlType} exists`); } const optional = overload.optionalityList[i] === "optional" && !isDefaultedDictionary; let str = `{ let curArg = arguments[${targetIdx}];`; if (optional) { str += ` if (curArg !== undefined) { `; } const msg = typeof targetIdx === "string" ? `"${errPrefix}parameter " + (${targetIdx} + 1)` : `"${errPrefix}parameter ${i + 1}"`; const conv = Types.generateTypeConversion(ctx, "curArg", idlType, [], parent.name, msg); requires.merge(conv.requires); str += conv.body; if (optional) { str += "}"; const defaultValue = overload.operation.arguments[i].default; if (defaultValue) { str += ` else { curArg = ${utils.getDefault(defaultValue)}; } `; } } str += "args.push(curArg);"; str += "}"; return { requires, body: str }; } module.exports.generateAsyncIteratorArgConversions = (ctx, idl, parent, errPrefix) => { const requires = new utils.RequiresMap(ctx); let str = "const args = [];"; for (let i = 0; i < idl.arguments.length; ++i) { const idlArg = idl.arguments[i]; if (!idlArg.optional) { throw new Error("All async iterable arguments must be optional"); } const isDefaultedDictionary = idlArg.default && idlArg.default.type === "dictionary"; if (isDefaultedDictionary && !ctx.dictionaries.has(idlArg.idlType.idlType)) { throw new Error(`The dictionary ${idlArg.idlType.idlType} was referenced in an argument list, but doesn't exist`); } const msg = `"${errPrefix}parameter ${i + 1}"`; const conv = Types.generateTypeConversion(ctx, `args[${i}]`, idlArg.idlType, [], parent.name, msg); requires.merge(conv.requires); if (isDefaultedDictionary) { str += `args[${i}] = arguments[${i}];${conv.body}`; } else { str += ` if (arguments[${i}] !== undefined) { args[${i}] = arguments[${i}];${conv.body} } `; if (idlArg.default) { str += ` else { args[${i}] = ${utils.getDefault(idlArg.default)}; } `; } else { str += ` else { args[${i}] = undefined; } `; } } } return { requires, body: str }; }; module.exports.generateOverloadConversions = function (ctx, typeOfOp, name, parent, errPrefix) { const requires = new utils.RequiresMap(ctx); const ops = Overloads.getOperations(typeOfOp, name, parent); const argLengths = Overloads.getEffectiveOverloads(typeOfOp, name, 0, parent).map(o => o.typeList.length); const maxArgs = Math.max(...argLengths); let str = ""; if (maxArgs > 0) { const minArgs = Math.min(...argLengths); if (minArgs > 0) { const plural = minArgs > 1 ? "s" : ""; str += ` if (arguments.length < ${minArgs}) { throw new globalObject.TypeError(\`${errPrefix}${minArgs} argument${plural} required, but only \${arguments.length} present.\`); } `; } str += "const args = [];"; const switchCases = []; // Special case: when the operation isn't overloaded, always try to convert to the maximum number of args. for (let numArgs = ops.length === 1 ? maxArgs : minArgs; numArgs <= maxArgs; numArgs++) { // for (let numArgs = minArgs; numArgs <= maxArgs; numArgs++) { const S = Overloads.getEffectiveOverloads(typeOfOp, name, numArgs, parent) .filter(o => o.typeList.length === numArgs); if (S.length === 0) { switchCases.push(` throw new globalObject.TypeError(\`${errPrefix}only \${arguments.length} arguments present.\`); `); continue; } let d = -1; if (S.length > 1) { d = Overloads.distinguishingArgumentIndex(ctx, S); } let caseSrc = ""; let i = 0; for (; i < d; i++) { const conv = generateVarConversion(ctx, S[0], i, parent, errPrefix); requires.merge(conv.requires); caseSrc += conv.body; } if (i === d) { caseSrc += "{"; caseSrc += `let curArg = arguments[${d}];`; const possibilities = []; const optionals = S.filter(o => o.optionalityList[d] === "optional"); if (optionals.length) { possibilities.push(` if (curArg === undefined) { ${continued(optionals[0], i)} } `); } const nullables = S.filter(o => { return isOrIncludes(ctx, o.typeList[d], t => t.nullable || ctx.dictionaries.has(t.idlType)); }); if (nullables.length) { possibilities.push(` if (curArg === null || curArg === undefined) { ${continued(nullables[0], i)} } `); } const interfaceTypes = new Map(); for (const o of S) { const type = Types.resolveType(ctx, o.typeList[d]); if (ctx.interfaces.has(type.idlType)) { interfaceTypes.set(type.idlType, o); } else if (type.union) { for (const child of type.idlType) { if (ctx.interfaces.has(child.idlType)) { interfaceTypes.set(child.idlType, o); } } } } for (const [iface, overload] of interfaceTypes) { let fn; // Avoid requiring the interface itself if (iface !== parent.name) { fn = `${iface}.is`; requires.addRelative(iface); } else { fn = "exports.is"; } possibilities.push(` if (${fn}(curArg)) { ${continued(overload, i)} } `); } const arrayBuffers = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => t.idlType === "ArrayBuffer")); if (arrayBuffers.length) { possibilities.push(` if (utils.isArrayBuffer(curArg)) { ${continued(arrayBuffers[0], i)} } `); } const sharedArrayBuffers = S.filter(o => { return isOrIncludes(ctx, o.typeList[d], t => t.idlType === "SharedArrayBuffer"); }); if (sharedArrayBuffers.length) { possibilities.push(` if (utils.isSharedArrayBuffer(curArg)) { ${continued(sharedArrayBuffers[0], i)} } `); } const arrayBufferViews = new Map(); for (const o of S) { const type = Types.resolveType(ctx, o.typeList[d]); if (Types.arrayBufferViewTypes.has(type.idlType)) { arrayBufferViews.set(type.idlType, o); } else if (type.union) { for (const child of type.idlType) { if (Types.arrayBufferViewTypes.has(child.idlType)) { arrayBufferViews.set(child.idlType, o); } } } } if (arrayBufferViews.size) { // Special case for all ArrayBufferView types. if (arrayBufferViews.size === Types.arrayBufferViewTypes.size && new Set(arrayBufferViews.values()).size === 1) { possibilities.push(` if (ArrayBuffer.isView(curArg)) { ${continued(arrayBufferViews.get("Uint8Array"), i)} } `); } else { for (const [type, overload] of arrayBufferViews) { possibilities.push(` if (ArrayBuffer.isView(curArg) && curArg instanceof ${type}) { ${continued(overload, i)} } `); } } } const callables = S.filter(o => { return isOrIncludes(ctx, o.typeList[d], t => ["Function", "VoidFunction"].includes(t.idlType)); }); if (callables.length) { possibilities.push(` if (typeof curArg === "function") { ${continued(callables[0], i)} } `); } const iterables = S.filter(o => { return isOrIncludes(ctx, o.typeList[d], t => ["sequence", "FrozenArray"].includes(t.generic)); }); if (iterables.length) { possibilities.push(` if (utils.isObject(curArg) && typeof curArg[Symbol.iterator] === "function") { ${continued(iterables[0], i)} } `); } const objects = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => t.idlType === "object")); if (objects.length) { possibilities.push(` if (utils.isObject(curArg)) { ${continued(objects[0], i)} } `); } const booleans = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => t.idlType === "boolean")); if (booleans.length) { possibilities.push(` if (typeof curArg === "boolean") { ${continued(booleans[0], i)} } `); } const numerics = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => Types.numericTypes.has(t.idlType))); if (numerics.length) { possibilities.push(` if (typeof curArg === "number") { ${continued(numerics[0], i)} } `); } const strings = S.filter(o => { return isOrIncludes(ctx, o.typeList[d], t => { return Types.stringTypes.has(t.idlType) || ctx.enumerations.has(t.idlType); }); }); const any = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => t.idlType === "any")); if (strings.length) { possibilities.push(`{ ${continued(strings[0], i)} }`); } else if (numerics.length) { possibilities.push(`{ ${continued(numerics[0], i)} }`); } else if (booleans.length) { possibilities.push(`{ ${continued(booleans[0], i)} }`); } else if (any.length) { possibilities.push(`{ ${continued(any[0], i)} }`); } else { possibilities.push(`throw new globalObject.TypeError("${errPrefix}No such overload");`); } caseSrc += possibilities.join(" else "); caseSrc += "}"; } else { // Branch taken when S.length === 1. caseSrc += continued(S[0], i); } switchCases.push(caseSrc); function continued(overload, idx) { let continuedStr = ""; for (; idx < numArgs; idx++) { let targetIdx = idx; if (overload.optionalityList[idx] === "variadic" && numArgs === maxArgs && idx === numArgs - 1) { continuedStr += `for (let i = ${idx}; i < arguments.length; i++)`; targetIdx = "i"; } const conv = generateVarConversion(ctx, overload, idx, parent, errPrefix, targetIdx); requires.merge(conv.requires); continuedStr += conv.body; } return continuedStr; } } if (switchCases.length === 1) { str += switchCases[0]; } else { str += "switch (arguments.length) {"; let lastBody; for (let i = 0; i < switchCases.length - 1; i++) { if (lastBody !== undefined && switchCases[i] !== lastBody) { str += `${lastBody}break;`; } str += `case ${minArgs + i}:`; lastBody = switchCases[i]; } if (lastBody !== undefined && switchCases[switchCases.length - 1] !== lastBody) { str += `${lastBody}break;`; } str += "default:"; str += switchCases[switchCases.length - 1]; str += "}"; } } return { requires, body: str, hasArgs: maxArgs > 0 }; };