Comparing version 8.0.0 to 9.0.0
@@ -17,7 +17,7 @@ "use strict"; | ||
Object.defineProperty(${this.interface.name}, "${this.idl.name}", { | ||
value: ${JSON.stringify(this.idl.value.value)}, | ||
value: ${utils.getDefault(this.idl.value)}, | ||
enumerable: true | ||
}); | ||
Object.defineProperty(${this.interface.name}.prototype, "${this.idl.name}", { | ||
value: ${JSON.stringify(this.idl.value.value)}, | ||
value: ${utils.getDefault(this.idl.value)}, | ||
enumerable: true | ||
@@ -24,0 +24,0 @@ }); |
@@ -12,3 +12,3 @@ "use strict"; | ||
generate() { | ||
const values = new Set(this.idl.values); | ||
const values = new Set(this.idl.values.map(val => val.value)); | ||
if (values.size !== this.idl.values.length) { | ||
@@ -15,0 +15,0 @@ throw new Error(`Duplicates found in ${this.name}'s enumeration values`); |
@@ -317,3 +317,2 @@ "use strict"; | ||
let minConstructor = overloads[0]; | ||
for (let i = 1; i < overloads.length; ++i) { | ||
@@ -325,23 +324,14 @@ if (overloads[i].nameList.length < minConstructor.nameList.length) { | ||
const conversions = | ||
Parameters.generateOverloadConversions(this.ctx, overloads, this.name, `Failed to construct '${this.name}': `); | ||
const conversions = Parameters.generateOverloadConversions( | ||
this.ctx, "constructor", this.name, this, `Failed to construct '${this.name}': `); | ||
this.requires.merge(conversions.requires); | ||
minConstructor.nameList = minConstructor.nameList.map(name => (keywords.has(name) ? "_" : "") + name); | ||
const argNames = minConstructor.nameList.map(name => (keywords.has(name) ? "_" : "") + name); | ||
this.str += ` | ||
function ${this.name}(${minConstructor.nameList.join(", ")}) { | ||
`; | ||
if (minConstructor.nameList.length !== 0) { | ||
const plural = minConstructor.nameList.length > 1 ? "s" : ""; | ||
this.str += ` | ||
if (!new.target) { | ||
function ${this.name}(${argNames.join(", ")}) { | ||
if (new.target === undefined) { | ||
throw new TypeError("Failed to construct '${this.name}'. Please use the 'new' operator; this constructor " + | ||
"cannot be called as a function."); | ||
} | ||
if (arguments.length < ${minConstructor.nameList.length}) { | ||
throw new TypeError("Failed to construct '${this.name}': ${minConstructor.nameList.length} " + | ||
"argument${plural} required, but only " + arguments.length + " present."); | ||
} | ||
`; | ||
} | ||
`; | ||
this.str += conversions.body + "\n"; | ||
@@ -1236,3 +1226,3 @@ | ||
for (const member of this.idl.members) { | ||
if (utils.getExtAttr(member.extAttrs, "Unscopeable")) { | ||
if (utils.getExtAttr(member.extAttrs, "Unscopable")) { | ||
unscopables[member.name] = true; | ||
@@ -1239,0 +1229,0 @@ } |
@@ -29,4 +29,18 @@ "use strict"; | ||
fixUpArgsExtAttrs() { | ||
for (const idl of this.idls) { | ||
for (const arg of idl.arguments) { | ||
if (arg.extAttrs) { | ||
if (!arg.idlType.extAttrs) { | ||
arg.idlType.extAttrs = []; | ||
} | ||
arg.idlType.extAttrs.push(...arg.extAttrs); | ||
} | ||
} | ||
} | ||
} | ||
generate() { | ||
const requires = new utils.RequiresMap(this.ctx); | ||
this.fixUpArgsExtAttrs(); | ||
let str = ""; | ||
@@ -45,15 +59,14 @@ | ||
const overloads = Overloads.getEffectiveOverloads(type, this.name, 0, this.interface); | ||
let minConstructor = overloads[0]; | ||
let minOp = overloads[0]; | ||
for (let i = 1; i < overloads.length; ++i) { | ||
if (overloads[i].nameList.length < minConstructor.nameList.length) { | ||
minConstructor = overloads[i]; | ||
if (overloads[i].nameList.length < minOp.nameList.length) { | ||
minOp = overloads[i]; | ||
} | ||
} | ||
const fnName = keywords.has(this.name) ? "_" : this.name; | ||
minConstructor.nameList = minConstructor.nameList.map(n => (keywords.has(n) ? "_" : "") + n); | ||
const fnName = (keywords.has(this.name) ? "_" : "") + this.name; | ||
const argNames = minOp.nameList.map(name => (keywords.has(name) ? "_" : "") + name); | ||
str += ` | ||
${targetObj}.${this.name} = function ${fnName}(${minConstructor.nameList.join(", ")}) { | ||
${targetObj}.${this.name} = function ${fnName}(${argNames.join(", ")}) { | ||
`; | ||
@@ -68,13 +81,2 @@ if (!this.static) { | ||
if (minConstructor.nameList.length !== 0) { | ||
const plural = minConstructor.nameList.length > 1 ? "s" : ""; | ||
str += ` | ||
if (arguments.length < ${minConstructor.nameList.length}) { | ||
throw new TypeError("Failed to execute '${this.name}' on '${this.interface.name}': " + | ||
"${minConstructor.nameList.length} argument${plural} required, but only " + | ||
arguments.length + " present."); | ||
} | ||
`; | ||
} | ||
const callOn = this.static ? "Impl.implementation" : "this[impl]"; | ||
@@ -86,3 +88,3 @@ // In case of stringifiers, use the named implementation function rather than hardcoded "toString". | ||
const parameterConversions = Parameters.generateOverloadConversions( | ||
this.ctx, overloads, this.interface.name, `Failed to execute '${this.name}' on '${this.interface.name}': `); | ||
this.ctx, type, this.name, this.interface, `Failed to execute '${this.name}' on '${this.interface.name}': `); | ||
const argsSpread = parameterConversions.hasArgs ? "...args" : ""; | ||
@@ -89,0 +91,0 @@ requires.merge(parameterConversions.requires); |
@@ -21,3 +21,2 @@ "use strict"; | ||
initialize() { | ||
this.customTypes = new Map(); | ||
this.typedefs = new Map(); | ||
@@ -32,4 +31,20 @@ this.interfaces = new Map(); | ||
} | ||
typeOf(name) { | ||
if (this.typedefs.has(name)) { | ||
return "typedef"; | ||
} | ||
if (this.interfaces.has(name)) { | ||
return "interface"; | ||
} | ||
if (this.dictionaries.has(name)) { | ||
return "dictionary"; | ||
} | ||
if (this.enumerations.has(name)) { | ||
return "enumeration"; | ||
} | ||
return undefined; | ||
} | ||
} | ||
module.exports = Context; |
@@ -12,17 +12,2 @@ "use strict"; | ||
function getReferenceToBytes(bufferSource) { | ||
// Node.js' Buffer does not allow subclassing for now, so we can get away with a prototype object check for perf. | ||
if (Object.getPrototypeOf(bufferSource) === Buffer.prototype) { | ||
return bufferSource; | ||
} | ||
if (bufferSource instanceof ArrayBuffer) { | ||
return Buffer.from(bufferSource); | ||
} | ||
return Buffer.from(bufferSource.buffer, bufferSource.byteOffset, bufferSource.byteLength); | ||
} | ||
function getCopyToBytes(bufferSource) { | ||
return Buffer.from(getReferenceToBytes(bufferSource)); | ||
} | ||
const wrapperSymbol = Symbol("wrapper"); | ||
@@ -96,4 +81,2 @@ const implSymbol = Symbol("impl"); | ||
hasOwn, | ||
getReferenceToBytes, | ||
getCopyToBytes, | ||
wrapperSymbol, | ||
@@ -100,0 +83,0 @@ implSymbol, |
"use strict"; | ||
module.exports.getEffectiveOverloads = function (type, A, N, I) { | ||
const S = []; | ||
let F = null; | ||
const { areDistinguishable, sameType } = require("./types"); | ||
function getOperations(type, A, I) { | ||
switch (type) { | ||
case "regular operation": | ||
F = I.operations.get(A).idls; | ||
break; | ||
return I.operations.get(A).idls; | ||
case "static operation": | ||
F = I.staticOperations.get(A).idls; | ||
break; | ||
case "constructor": | ||
F = I.idl.extAttrs.filter(a => a.name === "Constructor"); | ||
for (const c of F) { | ||
return I.staticOperations.get(A).idls; | ||
case "constructor": { | ||
const ctor = I.idl.extAttrs.filter(a => a.name === "Constructor"); | ||
for (const c of ctor) { | ||
if (!c.arguments) { | ||
@@ -21,7 +18,12 @@ c.arguments = []; | ||
} | ||
break; | ||
default: | ||
throw new RangeError(`${type}s are not yet supported`); | ||
return ctor; | ||
} | ||
} | ||
throw new RangeError(`${type}s are not yet supported`); | ||
} | ||
module.exports.getOperations = getOperations; | ||
module.exports.getEffectiveOverloads = function (type, A, N, I) { | ||
const S = []; | ||
const F = getOperations(type, A, I); | ||
let maxArgs = 0; | ||
@@ -34,7 +36,7 @@ for (const X of F) { | ||
const m = Math.max(maxArgs, N); | ||
const max = Math.max(maxArgs, N); | ||
for (const X of F) { | ||
const n = X.arguments.length; | ||
const nameList = X.arguments.map(arg => arg.name); | ||
const typeList = X.arguments.map(arg => arg.idlType.idlType); | ||
const typeList = X.arguments.map(arg => arg.idlType); | ||
const optionalityList = X.arguments.map(arg => { | ||
@@ -58,8 +60,16 @@ if (arg.optional) { | ||
if (optionalityList[optionalityList.length - 1] === "variadic") { | ||
for (let i = n; i <= m - 1; i++) { | ||
for (let i = n; i <= max - 1; i++) { | ||
const variadicNames = nameList.slice(0, n); | ||
const variadicTypes = typeList.slice(0, n); | ||
const variadicOptionalityValues = optionalityList.slice(0, n); | ||
for (let j = n; j <= i; j++) { | ||
variadicNames.push(nameList[n - 1]); | ||
variadicTypes.push(typeList[n - 1]); | ||
variadicOptionalityValues.push("variadic"); | ||
} | ||
S.push({ | ||
operation: X, | ||
nameList: nameList.slice(0, i + 1), | ||
typeList: typeList.slice(0, i + 1), | ||
optionalityList: optionalityList.slice(0, i + 1) | ||
nameList: variadicNames, | ||
typeList: variadicTypes, | ||
optionalityList: variadicOptionalityValues | ||
}); | ||
@@ -85,38 +95,26 @@ } | ||
module.exports.proveSimiliarity = function (ctx, overloads) { | ||
let maxArguments = overloads[0].nameList.length; | ||
for (let i = 1; i < overloads.length; ++i) { | ||
if (overloads[i].nameList.length > maxArguments) { | ||
maxArguments = overloads[i].nameList.length; | ||
module.exports.distinguishingArgumentIndex = function (ctx, S) { | ||
for (let i = 0; i < S[0].typeList.length; i++) { | ||
let distinguishable = true; | ||
for (let j = 0; j < S.length - 1; j++) { | ||
for (let k = j + 1; k < S.length; k++) { | ||
if (!areDistinguishable(ctx, S[j].typeList[i], S[k].typeList[i])) { | ||
distinguishable = false; | ||
} | ||
} | ||
} | ||
} | ||
const typeConversions = []; | ||
for (let i = 0; i < maxArguments; ++i) { | ||
if (overloads[0].operation.arguments.length <= i) { | ||
break; | ||
if (distinguishable) { | ||
return i; | ||
} | ||
let maybeType = { | ||
type: overloads[0].operation.arguments[i].idlType, | ||
optional: overloads[0].optionalityList[i] !== "required", | ||
default: overloads[0].operation.arguments[i].default | ||
}; | ||
for (let j = 1; j < overloads.length; ++j) { | ||
if (overloads[j].optionalityList[i] !== "required") { | ||
maybeType.optional = true; | ||
for (let j = 0; j < S.length - 1; j++) { | ||
for (let k = j + 1; k < S.length; k++) { | ||
if (!sameType(ctx, S[j].typeList[i], S[k].typeList[i])) { | ||
throw new Error(`Different but indistinguishable types at index ${i}`); | ||
} | ||
} | ||
const thisType = overloads[j].operation.arguments[i].idlType; | ||
if (maybeType.type.idlType !== thisType.idlType || maybeType.type.array !== thisType.array || | ||
maybeType.default !== overloads[j].operation.arguments[i].default) { | ||
maybeType = null; | ||
break; | ||
} | ||
} | ||
typeConversions.push(maybeType); | ||
} | ||
return typeConversions; | ||
return -1; | ||
}; |
@@ -8,26 +8,31 @@ "use strict"; | ||
function generateVarConversion(ctx, name, conversion, argAttrs, ...typeArgs) { | ||
const { customTypes } = ctx; | ||
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); | ||
let str = ""; | ||
const idlType = conversion.type; | ||
const idlType = overload.typeList[i]; | ||
// Always (try to) force-convert dictionaries | ||
const optional = conversion.optional && customTypes.get(idlType.idlType) !== "dictionary"; | ||
const optional = overload.optionalityList[i] === "optional" && !ctx.dictionaries.has(idlType.idlType); | ||
let str = `{ let curArg = arguments[${targetIdx}];`; | ||
if (optional) { | ||
str += ` | ||
if (${name} !== undefined) { | ||
if (curArg !== undefined) { | ||
`; | ||
} | ||
const conv = Types.generateTypeConversion(ctx, name, idlType, argAttrs, ...typeArgs); | ||
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 += "}"; | ||
if (conversion.default) { | ||
const defaultValue = overload.operation.arguments[i].default; | ||
if (defaultValue) { | ||
str += ` | ||
else { | ||
${name} = ${utils.getDefault(conversion.default)}; | ||
curArg = ${utils.getDefault(defaultValue)}; | ||
} | ||
@@ -37,3 +42,4 @@ `; | ||
} | ||
str += "args.push(curArg);"; | ||
str += "}"; | ||
return { | ||
@@ -45,80 +51,270 @@ requires, | ||
module.exports.generateOverloadConversions = function (ctx, overloads, parentName, errPrefix) { | ||
module.exports.generateOverloadConversions = function (ctx, typeOfOp, name, parent, errPrefix) { | ||
const requires = new utils.RequiresMap(ctx); | ||
let str = ``; | ||
let maxConstructor = overloads[0]; | ||
let maxArguments = overloads[0].nameList.length; | ||
let isVariadic = overloads[0].optionalityList.indexOf("variadic") !== -1; | ||
for (let i = 1; i < overloads.length; ++i) { | ||
if (overloads[i].nameList.length > maxConstructor.nameList.length) { | ||
maxConstructor = overloads[i]; | ||
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 TypeError("${errPrefix}${minArgs} argument${plural} required, but only " + arguments.length + | ||
" present."); | ||
} | ||
`; | ||
} | ||
if (overloads[i].nameList.length > maxArguments) { | ||
maxArguments = overloads[i].nameList.length; | ||
} | ||
if (overloads[i].optionalityList.indexOf("variadic") !== -1) { | ||
isVariadic = true; | ||
} | ||
} | ||
const typeConversions = Overloads.proveSimiliarity(ctx, overloads); | ||
const isAlwaysZeroArgs = !isVariadic && maxArguments === 0; | ||
if (!isAlwaysZeroArgs) { | ||
const extraClause = !isVariadic ? ` && i < ${maxArguments}` : ``; | ||
str += ` | ||
const args = []; | ||
for (let i = 0; i < arguments.length${extraClause}; ++i) { | ||
args[i] = arguments[i]; | ||
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 TypeError("${errPrefix}only " + arguments.length + " arguments present."); | ||
`); | ||
continue; | ||
} | ||
`; | ||
for (let i = 0; i < typeConversions.length; ++i) { | ||
if (typeConversions[i] === null) { | ||
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 = []; | ||
// For variadic operations, we only try to fully support non-overloaded variadic operations right now. It is a | ||
// TODO to support overloaded variadic functions. The decision to only support the simplest cases of variadic | ||
// operations is made, because overload resolution for overloaded variadic operations is non-trivial but hardly | ||
// used in the Web platform (or at least the jsdom-supported subset of it), and because the entire overload | ||
// resolution apparatus will require a rewrite for https://github.com/jsdom/webidl2js/issues/29. | ||
// | ||
// To determine that the current argument we are processing is the variadic one, the following three-fold | ||
// algorithm is used: | ||
// | ||
// 1. The current argument must the last one. | ||
// 2. The effective overload set must have a length of two (one with the final variadic argument, one without). | ||
// 3. The current argument must have an optionality value of "variadic" in either one of the overloads. | ||
const curIsVariadic = i + 1 === maxArguments && overloads.length === 2 && | ||
(overloads[0].optionalityList[i] === "variadic" || | ||
overloads[1].optionalityList[i] === "variadic"); | ||
let msg = `"${errPrefix}parameter ${i + 1}"`; | ||
let lvalue = `args[${i}]`; | ||
const optionals = S.filter(o => o.optionalityList[d] === "optional"); | ||
if (optionals.length) { | ||
possibilities.push(` | ||
if (curArg === undefined) { | ||
${continued(optionals[0], i)} | ||
} | ||
`); | ||
} | ||
if (curIsVariadic) { | ||
msg = `"${errPrefix}parameter " + (i + 1)`; | ||
lvalue = `args[i]`; | ||
str += `for (let i = ${i}; i < arguments.length; ++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)} | ||
} | ||
`); | ||
} | ||
// The object will not be re-used outside of this function, so mutating it is fine here. | ||
typeConversions[i].optional = false; | ||
} | ||
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 = `is${iface}`; | ||
requires.add(iface, "is"); | ||
} else { | ||
fn = "module.exports.is"; | ||
} | ||
possibilities.push(` | ||
if (${fn}(curArg)) { | ||
${continued(overload, i)} | ||
} | ||
`); | ||
} | ||
const conv = generateVarConversion( | ||
ctx, lvalue, typeConversions[i], maxConstructor.operation.arguments[i].extAttrs, parentName, msg); | ||
requires.merge(conv.requires); | ||
str += conv.body; | ||
// Handle Error and DOMException the same way | ||
const exceptions = S.filter(o => { | ||
return isOrIncludes(ctx, o.typeList[d], t => Types.exceptionTypes.has(t.idlType)); | ||
}); | ||
if (exceptions.length) { | ||
possibilities.push(` | ||
if (curArg instanceof Error) { | ||
${continued(exceptions[0], i)} | ||
} | ||
`); | ||
} | ||
if (curIsVariadic) { | ||
str += "}"; | ||
const arrayBuffers = S.filter(o => isOrIncludes(ctx, o.typeList[d], t => t.idlType === "ArrayBuffer")); | ||
if (arrayBuffers.length) { | ||
possibilities.push(` | ||
if (curArg instanceof ArrayBuffer || | ||
typeof SharedArrayBuffer !== "undefined" && curArg instanceof SharedArrayBuffer) { | ||
${continued(arrayBuffers[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 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: !isAlwaysZeroArgs | ||
hasArgs: maxArgs > 0 | ||
}; | ||
}; |
@@ -25,3 +25,3 @@ "use strict"; | ||
this.sources = []; | ||
this.sources = []; // Absolute paths to the IDL and Impl directories. | ||
this.utilPath = null; | ||
@@ -37,3 +37,3 @@ } | ||
} | ||
this.sources.push({ idlPath: idl, impl }); | ||
this.sources.push({ idlPath: path.resolve(idl), impl: path.resolve(impl) }); | ||
return this; | ||
@@ -85,3 +85,3 @@ } | ||
this.ctx.initialize(); | ||
const { interfaces, dictionaries, enumerations, typedefs, customTypes } = this.ctx; | ||
const { interfaces, dictionaries, enumerations, typedefs } = this.ctx; | ||
@@ -99,6 +99,5 @@ // first we're gathering all full interfaces and ignore partial ones | ||
obj = new Interface(this.ctx, instruction, { | ||
implDir: path.resolve(outputDir, file.impl) | ||
implDir: file.impl | ||
}); | ||
interfaces.set(obj.name, obj); | ||
customTypes.set(obj.name, "interface"); | ||
break; | ||
@@ -114,3 +113,2 @@ case "implements": | ||
dictionaries.set(obj.name, obj); | ||
customTypes.set(obj.name, "dictionary"); | ||
break; | ||
@@ -120,3 +118,2 @@ case "enum": | ||
enumerations.set(obj.name, obj); | ||
customTypes.set(obj.name, "enumeration"); | ||
break; | ||
@@ -186,4 +183,4 @@ case "typedef": | ||
const implDir = path.relative(outputDir, obj.opts.implDir).replace(/\\/g, "/"); // fix windows file paths | ||
let implFile = implDir + "/" + obj.name + this.ctx.implSuffix; | ||
let implFile = path.relative(outputDir, path.resolve(obj.opts.implDir, obj.name + this.ctx.implSuffix)); | ||
implFile = implFile.replace(/\\/g, "/"); // fix windows file paths | ||
if (implFile[0] !== ".") { | ||
@@ -190,0 +187,0 @@ implFile = "./" + implFile; |
253
lib/types.js
@@ -7,6 +7,8 @@ "use strict"; | ||
const arrayBufferViewTypes = new Set([ | ||
const typedArrayTypes = new Set([ | ||
"Int8Array", "Int16Array", "Int32Array", "Uint8Array", "Uint16Array", "Uint32Array", | ||
"Uint8ClampedArray", "Float32Array", "Float64Array", "DataView" | ||
"Uint8ClampedArray", "Float32Array", "Float64Array" | ||
]); | ||
const arrayBufferViewTypes = new Set([...typedArrayTypes, "DataView"]); | ||
const bufferSourceTypes = new Set([...arrayBufferViewTypes, "ArrayBuffer"]); | ||
const stringTypes = new Set(["DOMString", "ByteString", "USVString"]); | ||
@@ -20,3 +22,6 @@ const integerTypes = new Set([ | ||
]); | ||
const exceptionTypes = new Set(["Error", "DOMException"]); | ||
const resolvedMap = new WeakMap(); | ||
function mergeExtAttrs(a = [], b = []) { | ||
@@ -26,5 +31,13 @@ return [...a, ...b]; | ||
// Types of types that generate an output file. | ||
const resolvedTypes = new Set(["dictionary", "enumeration", "interface"]); | ||
function resolveType(ctx, idlType, stack = []) { | ||
if (resolvedMap.has(idlType)) { | ||
return resolvedMap.get(idlType); | ||
} | ||
const original = idlType; | ||
idlType = deepClone(idlType); | ||
const { customTypes, typedefs } = ctx; | ||
resolvedMap.set(original, idlType); | ||
if (idlType.union) { | ||
@@ -54,7 +67,8 @@ const types = []; | ||
return idlType; | ||
} else if (customTypes.has(idlType.idlType)) { | ||
} else if (resolvedTypes.has(ctx.typeOf(idlType.idlType))) { | ||
// already resolved | ||
return idlType; | ||
} else if (typedefs.has(idlType.idlType)) { | ||
const out = deepClone(typedefs.get(idlType.idlType).resolve(stack)); | ||
} else if (ctx.typedefs.has(idlType.idlType)) { | ||
const out = deepClone(ctx.typedefs.get(idlType.idlType).resolve(stack)); | ||
resolvedMap.set(original, out); | ||
out.nullable = out.nullable || idlType.nullable; | ||
@@ -81,3 +95,2 @@ out.extAttrs = deepClone(mergeExtAttrs(out.extAttrs, idlType.extAttrs)); | ||
function generateTypeConversion(ctx, name, idlType, argAttrs = [], parentName, errPrefix = '"The provided value"') { | ||
const { customTypes } = ctx; | ||
const requires = new utils.RequiresMap(ctx); | ||
@@ -115,4 +128,4 @@ let str = ""; | ||
generateGeneric(`conversions["${idlType.idlType}"]`); | ||
} else if (customTypes.has(idlType.idlType)) { | ||
// dictionaries or interfaces | ||
} else if (resolvedTypes.has(ctx.typeOf(idlType.idlType))) { | ||
// dictionaries, enumerations, and interfaces | ||
let fn; | ||
@@ -381,3 +394,2 @@ // Avoid requiring the interface itself | ||
function extractUnionInfo(ctx, idlType, errPrefix) { | ||
const { customTypes, enumerations } = ctx; | ||
const seen = { | ||
@@ -435,3 +447,3 @@ sequenceLike: null, | ||
seen.ArrayBufferViews.add(item.idlType); | ||
} else if (stringTypes.has(item.idlType) || enumerations.has(item.idlType)) { | ||
} else if (stringTypes.has(item.idlType) || ctx.enumerations.has(item.idlType)) { | ||
if (seen.string) { | ||
@@ -460,3 +472,3 @@ error("There can only be one string type in a union type"); | ||
seen.object = true; | ||
} else if (item.idlType === "DOMException" || item.idlType === "Error") { | ||
} else if (exceptionTypes.has(item.idlType)) { | ||
if (seen.object) { | ||
@@ -480,24 +492,18 @@ error("Exception types are not distinguishable with object type"); | ||
seen.callback = true; | ||
} else if (customTypes.has(item.idlType)) { | ||
// Enumerations were handled with the string types. | ||
const type = customTypes.get(item.idlType); | ||
if (type === "dictionary") { | ||
if (seen.object) { | ||
error("Dictionary-like types are not distinguishable with object type"); | ||
} | ||
if (seen.callback) { | ||
error("Dictionary-like types are not distinguishable with callback functions"); | ||
} | ||
if (seen.dictionaryLike) { | ||
error("There can only be one dictionary-like type in a union type"); | ||
} | ||
seen.dictionary = item; | ||
} else if (type === "interface") { | ||
if (seen.object) { | ||
error("Interface types are not distinguishable with object type"); | ||
} | ||
seen.interfaces.add(item.idlType); | ||
} else { | ||
error(`Unknown custom type ${type}`); | ||
} else if (ctx.dictionaries.has(item.idlType)) { | ||
if (seen.object) { | ||
error("Dictionary-like types are not distinguishable with object type"); | ||
} | ||
if (seen.callback) { | ||
error("Dictionary-like types are not distinguishable with callback functions"); | ||
} | ||
if (seen.dictionaryLike) { | ||
error("There can only be one dictionary-like type in a union type"); | ||
} | ||
seen.dictionary = item; | ||
} else if (ctx.interfaces.has(item.idlType)) { | ||
if (seen.object) { | ||
error("Interface types are not distinguishable with object type"); | ||
} | ||
seen.interfaces.add(item.idlType); | ||
} else { | ||
@@ -514,5 +520,184 @@ seen.unknown = true; | ||
// https://heycam.github.io/webidl/#dfn-includes-a-nullable-type | ||
function includesNullableType(ctx, idlType) { | ||
idlType = resolveType(ctx, idlType); | ||
if (idlType.nullable) { | ||
return true; | ||
} | ||
if (!idlType.union) { | ||
return false; | ||
} | ||
for (const type of idlType.idlType) { | ||
if (type.nullable) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function includesDictionaryType(ctx, idlType) { | ||
idlType = resolveType(ctx, idlType); | ||
if (typeof idlType.idlType === "string" && ctx.dictionaries.has(idlType.idlType)) { | ||
return true; | ||
} | ||
if (!idlType.union) { | ||
return false; | ||
} | ||
for (const type of idlType.idlType) { | ||
if (includesDictionaryType(ctx, type)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
function sameType(ctx, type1, type2) { | ||
if (type1 === type2) { | ||
return true; | ||
} | ||
type1 = resolveType(ctx, type1); | ||
type2 = resolveType(ctx, type2); | ||
if (type1.generic !== type2.generic) { | ||
return false; | ||
} | ||
if (type1.union !== type2.union) { | ||
return false; | ||
} | ||
if (includesNullableType(ctx, type1) !== includesNullableType(ctx, type2)) { | ||
return false; | ||
} | ||
// TODO: check extended attributes | ||
if (typeof type1.idlType === "string" || typeof type2.idlType === "string") { | ||
return type1.idlType === type2.idlType; | ||
} | ||
if (type1.generic === "sequence" || type1.generic === "FrozenArray") { | ||
return sameType(ctx, type1.idlType, type2.idlType); | ||
} | ||
if (type1.generic === "record") { | ||
return sameType(ctx, type1.idlType[0], type2.idlType[0]) && | ||
sameType(ctx, type2.idlType[1], type2.idlType[1]); | ||
} | ||
if (!type1.union) { | ||
// This branch should never be taken. | ||
return false; | ||
} | ||
const extracted1 = extractUnionInfo(ctx, type1, `""`); | ||
const extracted2 = extractUnionInfo(ctx, type2, `""`); | ||
return sameType(ctx, extracted1.sequenceLike, extracted2.sequenceLike) && | ||
sameType(ctx, extracted1.record, extracted2.record) && | ||
extracted1.ArrayBuffer !== extracted2.ArrayBuffer && | ||
JSON.stringify([...extracted1.ArrayBufferViews].sort()) === | ||
JSON.stringify([...extracted2.ArrayBufferViews].sort()) && | ||
extracted1.object === extracted2.object && | ||
sameType(ctx, extracted1.exception, extracted2.exception) && | ||
sameType(ctx, extracted1.string, extracted2.string) && | ||
sameType(ctx, extracted1.numeric, extracted2.numeric) && | ||
sameType(ctx, extracted1.boolean, extracted2.boolean) && | ||
extracted1.callback === extracted2.callback && | ||
sameType(ctx, extracted1.dictionary, extracted2.dictionary) && | ||
JSON.stringify([...extracted1.interfaces].sort()) === | ||
JSON.stringify([...extracted2.interfaces].sort()) && | ||
extracted1.unknown === extracted2.unknown; | ||
} | ||
function areDistinguishable(ctx, type1, type2) { | ||
const resolved1 = resolveType(ctx, type1); | ||
const resolved2 = resolveType(ctx, type2); | ||
const effectivelyNullable1 = includesNullableType(ctx, resolved1) || includesDictionaryType(ctx, resolved1); | ||
const effectivelyNullable2 = includesNullableType(ctx, resolved2) || includesDictionaryType(ctx, resolved2); | ||
if (includesNullableType(ctx, resolved1) && effectivelyNullable2 || | ||
effectivelyNullable1 && includesNullableType(ctx, resolved2)) { | ||
return false; | ||
} | ||
if (resolved1.union && resolved2.union) { | ||
for (const i of resolved1.idlType) { | ||
for (const j of resolved2.idlType) { | ||
if (!areDistinguishable(ctx, i, j)) { | ||
return false; | ||
} | ||
} | ||
} | ||
return true; | ||
} | ||
function inner(inner1, inner2) { | ||
if (inner1.union) { | ||
for (const i of inner1.idlType) { | ||
if (!areDistinguishable(ctx, i, inner2)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
if (inner1.idlType === "boolean") { | ||
return inner2.idlType !== "boolean"; | ||
} | ||
if (numericTypes.has(inner1.idlType)) { | ||
return !numericTypes.has(inner2.idlType); | ||
} | ||
if (stringTypes.has(inner1.idlType) || ctx.enumerations.has(inner1.idlType)) { | ||
return !stringTypes.has(inner2.idlType) && !ctx.enumerations.has(inner2.idlType); | ||
} | ||
const isInterfaceLike1 = ctx.interfaces.has(inner1.idlType) || exceptionTypes.has(inner1.idlType) || | ||
bufferSourceTypes.has(inner1.idlType); | ||
const isInterfaceLike2 = ctx.interfaces.has(inner2.idlType) || exceptionTypes.has(inner2.idlType) || | ||
bufferSourceTypes.has(inner2.idlType); | ||
const isDictionaryLike1 = ctx.dictionaries.has(inner1.idlType) || inner1.generic === "record"; | ||
const isDictionaryLike2 = ctx.dictionaries.has(inner2.idlType) || inner2.generic === "record"; | ||
const isSequenceLike1 = inner1.generic === "sequence" || inner1.generic === "FrozenArray"; | ||
const isSequenceLike2 = inner2.generic === "sequence" || inner2.generic === "FrozenArray"; | ||
if (inner1.idlType === "object") { | ||
return inner2.idlType !== "object" && | ||
!isInterfaceLike2 && | ||
!isDictionaryLike2 && | ||
!isSequenceLike2; | ||
} | ||
if (inner1.idlType === "symbol") { | ||
return inner2.idlType !== "symbol"; | ||
} | ||
if (isInterfaceLike1) { | ||
return inner2.idlType !== "object" && | ||
(!isInterfaceLike2 || | ||
!(inner2.idlType === "Error" && inner1.idlType === "DOMException") && | ||
(!ctx.interfaces.has(inner2.idlType) || | ||
!new Set(ctx.interfaces.get(inner2.idlType).allInterfaces()).has(inner1.idlType))); | ||
} | ||
if (isDictionaryLike1) { | ||
return inner2.idlType !== "object" && !isDictionaryLike2; | ||
} | ||
if (isSequenceLike1) { | ||
return inner2.idlType !== "object" && !isSequenceLike2; | ||
} | ||
return true; | ||
} | ||
return inner(resolved1, resolved2) && inner(resolved2, resolved1); | ||
} | ||
module.exports = { | ||
arrayBufferViewTypes, | ||
stringTypes, | ||
numericTypes, | ||
exceptionTypes, | ||
generateTypeConversion, | ||
resolveType | ||
resolveType, | ||
includesNullableType, | ||
includesDictionaryType, | ||
areDistinguishable, | ||
sameType | ||
}; |
@@ -6,5 +6,6 @@ "use strict"; | ||
case "boolean": | ||
case "number": | ||
case "string": | ||
return JSON.stringify(dflt.value); | ||
case "number": | ||
return dflt.value; | ||
case "null": | ||
@@ -11,0 +12,0 @@ case "NaN": |
{ | ||
"name": "webidl2js", | ||
"version": "8.0.0", | ||
"version": "9.0.0", | ||
"description": "Auto-generates class structures for WebIDL specifications", | ||
@@ -11,7 +11,7 @@ "main": "lib/transformer.js", | ||
"webidl-conversions": "^4.0.0", | ||
"webidl2": "^4.1.0" | ||
"webidl2": "^8.1.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^4.3.0", | ||
"jest": "^20.0.4" | ||
"jest": "^21.2.1" | ||
}, | ||
@@ -18,0 +18,0 @@ "scripts": { |
@@ -231,46 +231,8 @@ # JavaScript bindings generator for Web IDL | ||
Variadic operations are fully supported by webidl2js. | ||
#### Overloaded operations | ||
One other subtlety here is overloads: if the IDL file defines overloads for a given operation, webidl2js is not always as helpful as it could be. | ||
In the case of overloaded operations, webidl2js is not always as helpful as it could be. Due to the fact that JavaScript does not have a C++-like method overloading system, all overloads of a method are dispatched to the same implementation method, which means that the implementation class would need to do some secondary overload resolution to determine which overload is actually called. | ||
1. webidl2js does not yet implement the [overload resolution algorithm](https://heycam.github.io/webidl/#dfn-overload-resolution-algorithm) fully. Take the `overload1()` operations below: | ||
```webidl | ||
void overload1(DOMString first, long second); | ||
void overload1(DOMString first, long second, DOMString third); | ||
``` | ||
webidl2js fully handles type conversion for both `first` and `second` arguments, but does not try to convert the third argument even if it is provided. | ||
Similarly, consider the following `overload2()` operations: | ||
```webidl | ||
void overload2(Blob first, long second); | ||
void overload2(long first, long second); | ||
``` | ||
webidl2js only converts `second` argument because its type is unambiguous, and calls the implementation method with `first` unconverted. In particular, this means that webidl2js will not try to unwrap the `first` argument into a `Blob` implementation class, even if it is a `Blob`. | ||
2. webidl2js does not dispatch overloaded operations to separate implementation class methods, but instead performs type conversions if possible and then sends the result to the same backing method. | ||
We're hoping to fix both of these problems in [#29](https://github.com/jsdom/webidl2js/issues/29). But in the meantime, properly implementing overloads requires doing some extra type-checking (often using appropriate [`is()`](#isvalue) functions) to determine which case of the overload you ended up in, and manual conversion with [webidl-conversions][] and/or unwrapping. | ||
#### Variadic operations | ||
Variadic operations are fully supported by webidl2js, but only if the particular operation is not overloaded. So while type conversions for the `simple1` and `simple2` operations below are fully implemented, webidl2js will not provide any variadic semantics for `overloaded1()` or `overloaded2()`. | ||
```webidl | ||
void simple1(DOMString... strings); | ||
void simple2(DOMString first, URL... urls); | ||
void overloaded1(DOMString... strings); | ||
void overloaded1(unsigned long... numbers); | ||
void overloaded2(DOMString first, DOMString... strings); | ||
void overloaded2(unsigned long first, DOMString... strings); | ||
``` | ||
We hope to fix this problem in the overload resolution overhaul ([#29](https://github.com/jsdom/webidl2js/issues/29)). Right now, however, manual conversions will be needed for overloaded variadic operations. | ||
#### Static operations | ||
@@ -379,3 +341,3 @@ | ||
- Overload resolution (although [tricky cases are not easy on the implementation class](#overloaded-operations)) | ||
- Variadic arguments ([only non-overloaded operations](#variadic-operations) are supported though) | ||
- Variadic arguments | ||
- `[Clamp]` | ||
@@ -393,3 +355,3 @@ - `[Constructor]` | ||
- `[Unforgeable]` | ||
- `[Unscopeable]` | ||
- `[Unscopable]` | ||
@@ -396,0 +358,0 @@ Notable missing features include: |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
237394
24
3405
397
+ Addedwebidl2@8.1.0(transitive)
- Removedwebidl2@4.2.0(transitive)
Updatedwebidl2@^8.1.0