Comparing version 9.0.0 to 9.0.1
@@ -18,3 +18,2 @@ "use strict"; | ||
generate() { | ||
let str = ""; | ||
const requires = new utils.RequiresMap(this.ctx); | ||
@@ -26,7 +25,7 @@ | ||
const onInstance = utils.isOnInstance(this.idl, this.interface.idl); | ||
let objName = `this`; | ||
let definedOn = this.interface.name + (this.static ? "" : ".prototype"); | ||
if (utils.isOnInstance(this.idl, this.interface.idl)) { // we're in a setup method | ||
if (onInstance) { // we're in a setup method | ||
objName = `obj`; | ||
definedOn = `obj`; | ||
} | ||
@@ -44,2 +43,6 @@ let brandCheck = ` | ||
const addMethod = this.static ? | ||
this.interface.addStaticMethod.bind(this.interface) : | ||
this.interface.addMethod.bind(this.interface, onInstance ? "instance" : "prototype"); | ||
if (this.static) { | ||
@@ -66,9 +69,7 @@ brandCheck = ""; | ||
str += ` | ||
Object.defineProperty(${definedOn}, "${this.idl.name}", { | ||
get() { | ||
${brandCheck} | ||
${getterBody} | ||
}, | ||
`; | ||
addMethod(this.idl.name, [], ` | ||
${brandCheck} | ||
${getterBody} | ||
`, "get", { configurable }); | ||
if (!this.idl.readonly) { | ||
@@ -92,66 +93,35 @@ let idlConversion; | ||
} | ||
str += ` | ||
set(V) { | ||
${brandCheck} | ||
${idlConversion} | ||
${setterBody} | ||
}, | ||
`; | ||
addMethod(this.idl.name, ["V"], ` | ||
${brandCheck} | ||
${idlConversion} | ||
${setterBody} | ||
`, "set", { configurable }); | ||
} else if (utils.getExtAttr(this.idl.extAttrs, "PutForwards")) { | ||
str += ` | ||
set(V) { | ||
${brandCheck} | ||
this.${this.idl.name}.${utils.getExtAttr(this.idl.extAttrs, "PutForwards").rhs.value} = V; | ||
}, | ||
`; | ||
addMethod(this.idl.name, ["V"], ` | ||
${brandCheck} | ||
this.${this.idl.name}.${utils.getExtAttr(this.idl.extAttrs, "PutForwards").rhs.value} = V; | ||
`, "set", { configurable }); | ||
} else if (utils.getExtAttr(this.idl.extAttrs, "Replaceable")) { | ||
str += ` | ||
set(V) { | ||
${brandCheck} | ||
Object.defineProperty(this, "${this.idl.name}", { | ||
configurable: true, | ||
enumerable: true, | ||
value: V, | ||
writable: true | ||
}); | ||
}, | ||
`; | ||
addMethod(this.idl.name, ["V"], ` | ||
${brandCheck} | ||
Object.defineProperty(this, "${this.idl.name}", { | ||
configurable: true, | ||
enumerable: true, | ||
value: V, | ||
writable: true | ||
}); | ||
`, "set", { configurable }); | ||
} | ||
str += ` | ||
enumerable: true, | ||
configurable: ${JSON.stringify(configurable)} | ||
}); | ||
`; | ||
if (this.idl.stringifier) { | ||
const functionExpression = ` | ||
function toString() { | ||
if (!this || !module.exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
} | ||
${getterBody}; | ||
if (!this.static && this.idl.stringifier) { | ||
addMethod("toString", [], ` | ||
if (!this || !module.exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
} | ||
`; | ||
if (utils.getExtAttr(this.idl.extAttrs, "Unforgeable")) { | ||
str += ` | ||
Object.defineProperty(${definedOn}, "toString", { | ||
writable: false, | ||
enumerable: true, | ||
configurable: false, | ||
value: ${functionExpression} | ||
}); | ||
`; | ||
} else { | ||
str += ` | ||
${definedOn}.toString = ${functionExpression}; | ||
`; | ||
} | ||
str += "\n"; | ||
${getterBody}; | ||
`, "regular", { configurable, writable: configurable }); | ||
} | ||
return { | ||
requires, | ||
body: str | ||
}; | ||
return { requires }; | ||
} | ||
@@ -158,0 +128,0 @@ } |
@@ -15,16 +15,11 @@ "use strict"; | ||
generate() { | ||
const body = ` | ||
Object.defineProperty(${this.interface.name}, "${this.idl.name}", { | ||
value: ${utils.getDefault(this.idl.value)}, | ||
enumerable: true | ||
}); | ||
Object.defineProperty(${this.interface.name}.prototype, "${this.idl.name}", { | ||
value: ${utils.getDefault(this.idl.value)}, | ||
enumerable: true | ||
}); | ||
`; | ||
return { | ||
requires: new utils.RequiresMap(this.ctx), | ||
body | ||
}; | ||
this.interface.addStaticProperty(this.idl.name, utils.getDefault(this.idl.value), { | ||
configurable: false, | ||
writable: false | ||
}); | ||
this.interface.addProperty(this.interface.defaultWhence, this.idl.name, utils.getDefault(this.idl.value), { | ||
configurable: false, | ||
writable: false | ||
}); | ||
return { requires: new utils.RequiresMap(this.ctx) }; | ||
} | ||
@@ -31,0 +26,0 @@ } |
@@ -20,2 +20,46 @@ "use strict"; | ||
function formatArgs(args) { | ||
return args.map(name => name + (keywords.has(name) ? "_" : "")).join(", "); | ||
} | ||
const defaultDefinePropertyDescriptor = { | ||
configurable: false, | ||
enumerable: false, | ||
writable: false | ||
}; | ||
const defaultObjectLiteralDescriptor = { | ||
configurable: true, | ||
enumerable: true, | ||
writable: true | ||
}; | ||
const defaultClassMethodDescriptor = { | ||
configurable: true, | ||
enumerable: false, | ||
writable: true | ||
}; | ||
// 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(", ")} }`; | ||
} | ||
class Interface { | ||
@@ -51,4 +95,97 @@ constructor(ctx, idl, opts) { | ||
this._analyzed = false; | ||
this._outputMethods = new Map(); | ||
this._outputStaticMethods = new Map(); | ||
this._outputProperties = new Map(); | ||
this._outputStaticProperties = new Map(); | ||
const global = utils.getExtAttr(this.idl.extAttrs, "Global"); | ||
this.isGlobal = Boolean(global); | ||
if (global && (!global.rhs || global.arguments)) { | ||
throw new Error(`[Global] must take an identifier or an identifier list in interface ${this.name}`); | ||
} | ||
} | ||
// whence is either "instance" or "prototype" | ||
// type is either "regular", "get", or "set" | ||
addMethod(whence, propName, args, body, type = "regular", { | ||
configurable = true, | ||
enumerable = typeof propName === "string", | ||
writable = type === "regular" ? true : undefined | ||
} = {}) { | ||
if (whence !== "instance" && whence !== "prototype") { | ||
throw new Error(`Internal error: Invalid whence ${whence}`); | ||
} | ||
if (type !== "regular") { | ||
const existing = this._outputMethods.get(propName); | ||
if (existing !== undefined) { | ||
if (type === "get") { | ||
existing.body[0] = body; | ||
} else { | ||
existing.args = args; | ||
existing.body[1] = body; | ||
} | ||
return; | ||
} | ||
const pair = new Array(2); | ||
pair[type === "get" ? 0 : 1] = body; | ||
body = pair; | ||
type = "accessor"; | ||
} | ||
const descriptor = { configurable, enumerable, writable }; | ||
this._outputMethods.set(propName, { whence, type, args, body, descriptor }); | ||
} | ||
// type is either "regular", "get", or "set" | ||
addStaticMethod(propName, args, body, type = "regular", { | ||
configurable = true, | ||
enumerable = typeof propName === "string", | ||
writable = type === "regular" ? true : undefined | ||
} = {}) { | ||
if (type !== "regular") { | ||
const existing = this._outputStaticMethods.get(propName); | ||
if (existing !== undefined) { | ||
if (type === "get") { | ||
existing.body[0] = body; | ||
} else { | ||
existing.args = args; | ||
existing.body[1] = body; | ||
} | ||
return; | ||
} | ||
const pair = new Array(2); | ||
pair[type === "get" ? 0 : 1] = body; | ||
body = pair; | ||
type = "accessor"; | ||
} | ||
const descriptor = { configurable, enumerable, writable }; | ||
this._outputStaticMethods.set(propName, { type, args, body, descriptor }); | ||
} | ||
// whence is either "instance" or "prototype" | ||
addProperty(whence, propName, str, { | ||
configurable = true, | ||
enumerable = typeof propName === "string", | ||
writable = true | ||
} = {}) { | ||
if (whence !== "instance" && whence !== "prototype") { | ||
throw new Error(`Internal error: Invalid whence ${whence}`); | ||
} | ||
const descriptor = { configurable, enumerable, writable }; | ||
this._outputProperties.set(propName, { whence, body: str, descriptor }); | ||
} | ||
addStaticProperty(propName, str, { | ||
configurable = true, | ||
enumerable = typeof propName === "string", | ||
writable = true | ||
} = {}) { | ||
const descriptor = { configurable, enumerable, writable }; | ||
this._outputStaticProperties.set(propName, { body: str, descriptor }); | ||
} | ||
_analyzeMembers() { | ||
@@ -63,8 +200,7 @@ const handleSpecialOperations = member => { | ||
msg += ": "; | ||
if (member.arguments.length < 1 || | ||
(!this.ctx.options.suppressErrors && member.arguments.length !== 1)) { | ||
if (member.arguments.length !== 1) { | ||
throw new Error(msg + `1 argument should be present, found ${member.arguments.length}`); | ||
} | ||
if (isIndexed(member)) { | ||
if (!this.ctx.options.suppressErrors && this.indexedGetter) { | ||
if (this.indexedGetter) { | ||
throw new Error(msg + "duplicated indexed getter"); | ||
@@ -74,3 +210,3 @@ } | ||
} else if (isNamed(member)) { | ||
if (!this.ctx.options.suppressErrors && this.namedGetter) { | ||
if (this.namedGetter) { | ||
throw new Error(msg + "duplicated named getter"); | ||
@@ -90,8 +226,7 @@ } | ||
if (member.arguments.length < 2 || | ||
(!this.ctx.options.suppressErrors && member.arguments.length !== 2)) { | ||
if (member.arguments.length !== 2) { | ||
throw new Error(msg + `2 arguments should be present, found ${member.arguments.length}`); | ||
} | ||
if (isIndexed(member)) { | ||
if (!this.ctx.options.suppressErrors && this.indexedSetter) { | ||
if (this.indexedSetter) { | ||
throw new Error(msg + "duplicated indexed setter"); | ||
@@ -101,3 +236,3 @@ } | ||
} else if (isNamed(member)) { | ||
if (!this.ctx.options.suppressErrors && this.namedSetter) { | ||
if (this.namedSetter) { | ||
throw new Error(msg + "duplicated named setter"); | ||
@@ -117,8 +252,7 @@ } | ||
if (member.arguments.length < 1 || | ||
(!this.ctx.options.suppressErrors && member.arguments.length !== 1)) { | ||
if (member.arguments.length !== 1) { | ||
throw new Error(msg + `1 arguments should be present, found ${member.arguments.length}`); | ||
} | ||
if (isNamed(member)) { | ||
if (!this.ctx.options.suppressErrors && this.namedDeleter) { | ||
if (this.namedDeleter) { | ||
throw new Error(msg + "duplicated named deleter"); | ||
@@ -182,13 +316,11 @@ } | ||
} | ||
if (!this.ctx.options.suppressErrors) { | ||
if (member.arguments.length > 0) { | ||
throw new Error(msg + "takes more than zero arguments"); | ||
} | ||
if (member.idlType.idlType !== "DOMString" || member.idlType.nullable) { | ||
throw new Error(msg + "returns something other than a plain DOMString"); | ||
} | ||
if (this.stringifier) { | ||
throw new Error(msg + "duplicated stringifier"); | ||
} | ||
if (member.arguments.length > 0) { | ||
throw new Error(msg + "takes more than zero arguments"); | ||
} | ||
if (member.idlType.idlType !== "DOMString" || member.idlType.nullable) { | ||
throw new Error(msg + "returns something other than a plain DOMString"); | ||
} | ||
if (this.stringifier) { | ||
throw new Error(msg + "duplicated stringifier"); | ||
} | ||
const op = new Operation(this.ctx, this, member); | ||
@@ -201,7 +333,5 @@ op.name = "toString"; | ||
} | ||
if (!this.ctx.options.suppressErrors) { | ||
if (member.idlType.idlType !== "DOMString" && member.idlType.idlType !== "USVString" || | ||
member.idlType.nullable) { | ||
throw new Error(msg + "attribute can only be of type DOMString or USVString"); | ||
} | ||
if (member.idlType.idlType !== "DOMString" && member.idlType.idlType !== "USVString" || | ||
member.idlType.nullable) { | ||
throw new Error(msg + "attribute can only be of type DOMString or USVString"); | ||
} | ||
@@ -259,3 +389,3 @@ // Implemented in Attribute class. | ||
get isLegacyPlatformObj() { | ||
return !utils.isGlobal(this.idl) && (this.supportsIndexedProperties || this.supportsNamedProperties); | ||
return !this.isGlobal && (this.supportsIndexedProperties || this.supportsNamedProperties); | ||
} | ||
@@ -312,5 +442,3 @@ | ||
[Symbol.toStringTag]: { | ||
value: "${this.name}Iterator", | ||
writable: false, | ||
enumerable: false, | ||
value: "${this.name} Iterator", | ||
configurable: true | ||
@@ -323,61 +451,2 @@ } | ||
generateConstructor() { | ||
const overloads = Overloads.getEffectiveOverloads("constructor", this.name, 0, this); | ||
if (overloads.length !== 0) { | ||
let minConstructor = overloads[0]; | ||
for (let i = 1; i < overloads.length; ++i) { | ||
if (overloads[i].nameList.length < minConstructor.nameList.length) { | ||
minConstructor = overloads[i]; | ||
} | ||
} | ||
const conversions = Parameters.generateOverloadConversions( | ||
this.ctx, "constructor", this.name, this, `Failed to construct '${this.name}': `); | ||
this.requires.merge(conversions.requires); | ||
const argNames = minConstructor.nameList.map(name => (keywords.has(name) ? "_" : "") + name); | ||
this.str += ` | ||
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."); | ||
} | ||
`; | ||
this.str += conversions.body + "\n"; | ||
const passArgs = conversions.hasArgs ? ", args" : ""; | ||
this.str += ` | ||
iface.setup(this${passArgs}); | ||
} | ||
`; | ||
} else { | ||
this.str += ` | ||
function ${this.name}() { | ||
throw new TypeError("Illegal constructor"); | ||
} | ||
`; | ||
} | ||
if (this.idl.inheritance) { | ||
this.str += ` | ||
Object.setPrototypeOf(${this.name}.prototype, ${this.idl.inheritance}.interface.prototype); | ||
Object.setPrototypeOf(${this.name}, ${this.idl.inheritance}.interface); | ||
`; | ||
} else if (utils.getExtAttr(this.idl.extAttrs, "LegacyArrayClass")) { | ||
this.str += ` | ||
Object.setPrototypeOf(${this.name}.prototype, Array.prototype); | ||
`; | ||
} | ||
this.str += ` | ||
Object.defineProperty(${this.name}, "prototype", { | ||
value: ${this.name}.prototype, | ||
writable: false, | ||
enumerable: false, | ||
configurable: false | ||
}); | ||
`; | ||
} | ||
// https://heycam.github.io/webidl/#dfn-consequential-interfaces | ||
@@ -507,4 +576,2 @@ * consequentialInterfaces(seen = new Set([this.name]), root = this.name) { | ||
value: { target, kind, index: 0 }, | ||
writable: false, | ||
enumerable: false, | ||
configurable: true | ||
@@ -918,3 +985,3 @@ }); | ||
let needFallback = false; | ||
if (this.supportsNamedProperties && !utils.isGlobal(this.idl)) { | ||
if (this.supportsNamedProperties && !this.isGlobal) { | ||
const unforgeable = new Set(); | ||
@@ -965,3 +1032,3 @@ for (const m of this.allMembers()) { | ||
// Spec says to set configurable to true, but doing so will make Proxy's trap throw and also fail WPT. | ||
// if (!utils.isGlobal(this.idl)) { | ||
// if (!this.isGlobal) { | ||
// this.str += ` | ||
@@ -994,3 +1061,3 @@ // desc.configurable = true; | ||
} | ||
if (this.supportsNamedProperties && !utils.isGlobal(this.idl)) { | ||
if (this.supportsNamedProperties && !this.isGlobal) { | ||
this.str += ` | ||
@@ -1025,4 +1092,2 @@ if (${namedPropertyVisible("P", "target")}) { | ||
// TODO: Implement [[Call]] / legacycallers. | ||
// [[PreventExtensions]] | ||
@@ -1087,16 +1152,3 @@ this.str += ` | ||
for (const member of this.operations.values()) { | ||
if (member.isOnInstance()) { | ||
const data = member.generate(); | ||
this.requires.merge(data.requires); | ||
this.str += data.body; | ||
} | ||
} | ||
for (const member of this.attributes.values()) { | ||
if (utils.isOnInstance(member.idl, this.idl)) { | ||
const data = member.generate(); | ||
this.requires.merge(data.requires); | ||
this.str += data.body; | ||
} | ||
} | ||
this.generateOnInstance(); | ||
@@ -1125,4 +1177,2 @@ this.str += ` | ||
value: new Impl.implementation(constructorArgs, privateData), | ||
writable: false, | ||
enumerable: false, | ||
configurable: true | ||
@@ -1150,118 +1200,250 @@ }); | ||
generateOperations() { | ||
addConstructor() { | ||
const overloads = Overloads.getEffectiveOverloads("constructor", this.name, 0, this); | ||
let body; | ||
let argNames = []; | ||
if (overloads.length !== 0) { | ||
let minConstructor = overloads[0]; | ||
for (let i = 1; i < overloads.length; ++i) { | ||
if (overloads[i].nameList.length < minConstructor.nameList.length) { | ||
minConstructor = overloads[i]; | ||
} | ||
} | ||
argNames = minConstructor.nameList; | ||
const conversions = Parameters.generateOverloadConversions( | ||
this.ctx, "constructor", this.name, this, `Failed to construct '${this.name}': `); | ||
this.requires.merge(conversions.requires); | ||
const passArgs = conversions.hasArgs ? ", args" : ""; | ||
body = ` | ||
${conversions.body} | ||
return iface.setup(Object.create(new.target.prototype)${passArgs}); | ||
`; | ||
} else { | ||
body = ` | ||
throw new TypeError("Illegal constructor"); | ||
`; | ||
} | ||
this.addMethod("prototype", "constructor", argNames, body, "regular", { enumerable: false }); | ||
} | ||
get defaultWhence() { | ||
return this.isGlobal ? "instance" : "prototype"; | ||
} | ||
addIteratorMethod() { | ||
// TODO maplike setlike | ||
// Don't bother checking "length" attribute as interfaces that support indexed properties must implement one. | ||
// "Has value iterator" implies "supports indexed properties". | ||
if (this.supportsIndexedProperties || this.iterable && this.iterable.isPair) { | ||
let expr; | ||
if (this.supportsIndexedProperties) { | ||
this.addProperty(this.defaultWhence, Symbol.iterator, "Array.prototype[Symbol.iterator]"); | ||
} | ||
} | ||
if (this.supportsIndexedProperties) { | ||
expr = "Array.prototype[Symbol.iterator]"; | ||
addAllMethodsProperties() { | ||
this.addConstructor(); | ||
this.addProperty("prototype", Symbol.toStringTag, JSON.stringify(this.name), { | ||
writable: false | ||
}); | ||
const unscopables = Object.create(null); | ||
for (const member of this.idl.members) { | ||
if (utils.getExtAttr(member.extAttrs, "Unscopable")) { | ||
unscopables[member.name] = true; | ||
} | ||
} | ||
if (Object.keys(unscopables).length > 0) { | ||
this.addProperty("prototype", Symbol.unscopables, JSON.stringify(unscopables), { | ||
writable: false | ||
}); | ||
} | ||
for (const member of [...this.operations.values(), ...this.staticOperations.values()]) { | ||
const data = member.generate(); | ||
this.requires.merge(data.requires); | ||
} | ||
this.addIteratorMethod(); | ||
if (this.iterable) { | ||
const data = this.iterable.generate(); | ||
this.requires.merge(data.requires); | ||
} | ||
for (const member of [...this.attributes.values(), ...this.staticAttributes.values(), ...this.constants.values()]) { | ||
const data = member.generate(); | ||
this.requires.merge(data.requires); | ||
} | ||
} | ||
generateOffInstanceMethods() { | ||
const addOne = (name, args, body) => { | ||
this.str += ` | ||
${name}(${formatArgs(args)}) {${body}} | ||
`; | ||
}; | ||
for (const [name, { whence, type, args, body }] of this._outputMethods) { | ||
if (whence !== "prototype") { | ||
continue; | ||
} | ||
const propName = utils.stringifyPropertyName(name); | ||
if (type === "regular") { | ||
addOne(propName, args, body); | ||
} else { | ||
expr = ` | ||
function entries() { | ||
if (!this || !module.exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
} | ||
return module.exports.createDefaultIterator(this, "key+value"); | ||
} | ||
`; | ||
if (body[0] !== undefined) { | ||
addOne(`get ${propName}`, [], body[0]); | ||
} | ||
if (body[1] !== undefined) { | ||
addOne(`set ${propName}`, args, body[1]); | ||
} | ||
} | ||
} | ||
for (const [name, { type, args, body }] of this._outputStaticMethods) { | ||
const propName = utils.stringifyPropertyName(name); | ||
if (type === "regular") { | ||
addOne(`static ${propName}`, args, body); | ||
} else { | ||
if (body[0] !== undefined) { | ||
addOne(`static get ${propName}`, [], body[0]); | ||
} | ||
if (body[1] !== undefined) { | ||
addOne(`static set ${propName}`, args, body[0]); | ||
} | ||
} | ||
} | ||
} | ||
generateOffInstanceAfterClass() { | ||
// Inheritance is taken care of by "extends" clause in class declaration. | ||
if (utils.getExtAttr(this.idl.extAttrs, "LegacyArrayClass")) { | ||
if (this.idl.inheritance) { | ||
throw new Error(`Interface ${this.name} has [LegacyArrayClass] but inherits from ${this.idl.inheritance}`); | ||
} | ||
this.str += ` | ||
Object.defineProperty(${this.name}.prototype, Symbol.iterator, { | ||
writable: true, | ||
enumerable: false, | ||
configurable: true, | ||
value: ${expr} | ||
}); | ||
Object.setPrototypeOf(${this.name}.prototype, Array.prototype); | ||
`; | ||
} | ||
if (this.iterable) { | ||
let expr; | ||
const protoProps = new Map(); | ||
const classProps = new Map(); | ||
if (this.iterable.isValue) { | ||
expr = "Array.prototype.forEach"; | ||
} else { | ||
expr = ` | ||
function forEach(callback) { | ||
if (!this || !module.exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
} | ||
if (arguments.length < 1) { | ||
throw new TypeError("Failed to execute 'forEach' on '${this.name}': 1 argument required, " + | ||
"but only 0 present."); | ||
} | ||
if (typeof callback !== "function") { | ||
throw new TypeError("Failed to execute 'forEach' on '${this.name}': The callback provided " + | ||
"as parameter 1 is not a function."); | ||
} | ||
const thisArg = arguments[1]; | ||
let pairs = Array.from(this[impl]); | ||
let i = 0; | ||
while (i < pairs.length) { | ||
const [key, value] = pairs[i].map(utils.tryWrapperForImpl); | ||
callback.call(thisArg, value, key, this); | ||
pairs = Array.from(this[impl]); | ||
i++; | ||
} | ||
} | ||
`; | ||
for (const [name, { whence, type, descriptor }] of this._outputMethods) { | ||
if (whence !== "prototype") { | ||
continue; | ||
} | ||
this.str += `${this.name}.prototype.forEach = ${expr};`; | ||
const descriptorModifier = getPropertyDescriptorModifier(defaultClassMethodDescriptor, descriptor, type); | ||
if (descriptorModifier === undefined) { | ||
continue; | ||
} | ||
protoProps.set(utils.stringifyPropertyName(name), descriptorModifier); | ||
} | ||
for (const member of [...this.operations.values(), ...this.staticOperations.values()]) { | ||
if (!member.isOnInstance()) { | ||
const data = member.generate(); | ||
this.requires.merge(data.requires); | ||
this.str += data.body; | ||
for (const [name, { type, descriptor }] of this._outputStaticMethods) { | ||
const descriptorModifier = getPropertyDescriptorModifier(defaultClassMethodDescriptor, descriptor, type); | ||
if (descriptorModifier === undefined) { | ||
continue; | ||
} | ||
classProps.set(utils.stringifyPropertyName(name), descriptorModifier); | ||
} | ||
if (this.iterable) { | ||
const data = this.iterable.generate(); | ||
this.requires.merge(data.requires); | ||
this.str += data.body; | ||
for (const [name, { whence, body, descriptor }] of this._outputProperties) { | ||
if (whence !== "prototype") { | ||
continue; | ||
} | ||
const descriptorModifier = | ||
getPropertyDescriptorModifier(defaultDefinePropertyDescriptor, descriptor, "regular", body); | ||
protoProps.set(utils.stringifyPropertyName(name), descriptorModifier); | ||
} | ||
for (const [name, { body, descriptor }] of this._outputStaticProperties) { | ||
const descriptorModifier = | ||
getPropertyDescriptorModifier(defaultDefinePropertyDescriptor, descriptor, "regular", body); | ||
classProps.set(utils.stringifyPropertyName(name), descriptorModifier); | ||
} | ||
if (protoProps.size > 0) { | ||
const props = [...protoProps].map(([name, body]) => `${name}: ${body}`); | ||
this.str += `Object.defineProperties(${this.name}.prototype, { ${props.join(", ")} });`; | ||
} | ||
if (classProps.size > 0) { | ||
const props = [...classProps].map(([name, body]) => `${name}: ${body}`); | ||
this.str += `Object.defineProperties(${this.name}, { ${props.join(", ")} });`; | ||
} | ||
} | ||
generateAttributes() { | ||
for (const member of [...this.attributes.values(), ...this.staticAttributes.values(), ...this.constants.values()]) { | ||
if (member instanceof Attribute && utils.isOnInstance(member.idl, this.idl)) { | ||
generateOnInstance() { | ||
const methods = []; | ||
const props = new Map(); | ||
function addOne(name, args, body) { | ||
methods.push(` | ||
${name}(${formatArgs(args)}) {${body}} | ||
`); | ||
} | ||
for (const [name, { whence, type, args, body, descriptor }] of this._outputMethods) { | ||
if (whence !== "instance") { | ||
continue; | ||
} | ||
const data = member.generate(); | ||
this.requires.merge(data.requires); | ||
this.str += data.body; | ||
const propName = utils.stringifyPropertyName(name); | ||
if (type === "regular") { | ||
addOne(propName, args, body); | ||
} else { | ||
if (body[0] !== undefined) { | ||
addOne(`get ${propName}`, [], body[0]); | ||
} | ||
if (body[1] !== undefined) { | ||
addOne(`set ${propName}`, args, body[1]); | ||
} | ||
} | ||
const descriptorModifier = getPropertyDescriptorModifier(defaultObjectLiteralDescriptor, descriptor, type); | ||
if (descriptorModifier === undefined) { | ||
continue; | ||
} | ||
props.set(utils.stringifyPropertyName(name), descriptorModifier); | ||
} | ||
} | ||
generateSymbols() { | ||
const unscopables = Object.create(null); | ||
for (const member of this.idl.members) { | ||
if (utils.getExtAttr(member.extAttrs, "Unscopable")) { | ||
unscopables[member.name] = true; | ||
for (const [name, { whence, body, descriptor }] of this._outputProperties) { | ||
if (whence !== "instance") { | ||
continue; | ||
} | ||
const propName = utils.stringifyPropertyName(name); | ||
methods.push(`${propName}: ${body}`); | ||
const descriptorModifier = getPropertyDescriptorModifier(defaultObjectLiteralDescriptor, descriptor, "regular"); | ||
if (descriptorModifier === undefined) { | ||
continue; | ||
} | ||
props.set(propName, descriptorModifier); | ||
} | ||
if (Object.keys(unscopables).length) { | ||
const propStrs = [...props].map(([name, body]) => `${name}: ${body}`); | ||
if (methods.length > 0) { | ||
this.str += ` | ||
Object.defineProperty(${this.name}.prototype, Symbol.unscopables, { | ||
value: ${JSON.stringify(unscopables, null, " ")}, | ||
writable: false, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
Object.defineProperties( | ||
obj, | ||
utils.getOwnPropertyDescriptors({ ${methods.join(", ")} }) | ||
); | ||
`; | ||
} | ||
this.str += ` | ||
Object.defineProperty(${this.name}.prototype, Symbol.toStringTag, { | ||
value: "${this.name}", | ||
writable: false, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
`; | ||
if (propStrs.length > 0) { | ||
this.str += ` | ||
Object.defineProperties( | ||
obj, | ||
{ ${propStrs.join(", ")} } | ||
); | ||
`; | ||
} | ||
} | ||
@@ -1279,9 +1461,11 @@ | ||
this.generateConstructor(); | ||
const ext = this.idl.inheritance ? ` extends ${this.idl.inheritance}.interface` : ""; | ||
this.str += `class ${this.name}${ext} {`; | ||
this.generateOperations(); | ||
this.generateAttributes(); | ||
this.generateOffInstanceMethods(); | ||
this.generateSymbols(); | ||
this.str += "}"; | ||
this.generateOffInstanceAfterClass(); | ||
this.str += ` | ||
@@ -1321,2 +1505,3 @@ const iface = { | ||
} | ||
this.addAllMethodsProperties(); | ||
this.generate(); | ||
@@ -1323,0 +1508,0 @@ return this.str; |
"use strict"; | ||
const keywords = require("../keywords"); | ||
const utils = require("../utils"); | ||
@@ -15,50 +14,57 @@ | ||
get isValue() { | ||
return !Array.isArray(this.idl.idlType); | ||
return this.idl.idlType.length === 1; | ||
} | ||
get isPair() { | ||
return Array.isArray(this.idl.idlType) && this.idl.idlType.length === 2; | ||
return this.idl.idlType.length === 2; | ||
} | ||
generateFunction(key, kind, keyExpr, fnName) { | ||
if (fnName === undefined) { | ||
if (typeof key === "symbol") { | ||
fnName = ""; | ||
} else { | ||
fnName = keywords.has(key) ? "_" : key; | ||
generateFunction(key, kind) { | ||
this.interface.addMethod(this.interface.defaultWhence, key, [], ` | ||
if (!this || !module.exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
} | ||
} | ||
return module.exports.createDefaultIterator(this, "${kind}"); | ||
`); | ||
} | ||
const propExpr = typeof key === "symbol" ? `[${keyExpr}]` : `.${key}`; | ||
return ` | ||
${this.interface.name}.prototype${propExpr} = function ${fnName}() { | ||
generate() { | ||
const whence = this.interface.defaultWhence; | ||
if (this.isPair) { | ||
this.generateFunction("keys", "key"); | ||
this.generateFunction("values", "value"); | ||
this.generateFunction("entries", "key+value"); | ||
this.interface.addProperty(whence, Symbol.iterator, `${this.interface.name}.prototype.entries`); | ||
this.interface.addMethod(whence, "forEach", ["callback"], ` | ||
if (!this || !module.exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
} | ||
return module.exports.createDefaultIterator(this, "${kind}"); | ||
}; | ||
`; | ||
} | ||
generate() { | ||
let str = ""; | ||
if (this.isPair) { | ||
str += ` | ||
${this.interface.name}.prototype.entries = ${this.interface.name}.prototype[Symbol.iterator]; | ||
${this.generateFunction("keys", "key")} | ||
${this.generateFunction("values", "value")} | ||
`; | ||
if (arguments.length < 1) { | ||
throw new TypeError("Failed to execute 'forEach' on '${this.name}': 1 argument required, " + | ||
"but only 0 present."); | ||
} | ||
if (typeof callback !== "function") { | ||
throw new TypeError("Failed to execute 'forEach' on '${this.name}': The callback provided " + | ||
"as parameter 1 is not a function."); | ||
} | ||
const thisArg = arguments[1]; | ||
let pairs = Array.from(this[impl]); | ||
let i = 0; | ||
while (i < pairs.length) { | ||
const [key, value] = pairs[i].map(utils.tryWrapperForImpl); | ||
callback.call(thisArg, value, key, this); | ||
pairs = Array.from(this[impl]); | ||
i++; | ||
} | ||
`); | ||
} else { | ||
str += ` | ||
${this.interface.name}.prototype.entries = Array.prototype.entries; | ||
${this.interface.name}.prototype.keys = Array.prototype.keys; | ||
${this.interface.name}.prototype.values = Array.prototype[Symbol.iterator]; | ||
`; | ||
this.interface.addProperty(whence, "keys", "Array.prototype.keys"); | ||
this.interface.addProperty(whence, "values", "Array.prototype[Symbol.iterator]"); | ||
this.interface.addProperty(whence, "entries", "Array.prototype.entries"); | ||
this.interface.addProperty(whence, "forEach", "Array.prototype.forEach"); | ||
// @@iterator is added in Interface class. | ||
} | ||
return { | ||
requires: new utils.RequiresMap(this.ctx), | ||
body: str | ||
requires: new utils.RequiresMap(this.ctx) | ||
}; | ||
@@ -65,0 +71,0 @@ } |
@@ -8,3 +8,2 @@ "use strict"; | ||
const Parameters = require("../parameters"); | ||
const keywords = require("../keywords"); | ||
@@ -52,6 +51,3 @@ class Operation { | ||
let targetObj = this.interface.name + (this.static ? "" : ".prototype"); | ||
if (this.isOnInstance()) { | ||
targetObj = "obj"; | ||
} | ||
const onInstance = this.isOnInstance(); | ||
@@ -67,8 +63,4 @@ const type = this.static ? "static operation" : "regular operation"; | ||
const fnName = (keywords.has(this.name) ? "_" : "") + this.name; | ||
const argNames = minOp.nameList.map(name => (keywords.has(name) ? "_" : "") + name); | ||
const argNames = minOp.nameList; | ||
str += ` | ||
${targetObj}.${this.name} = function ${fnName}(${argNames.join(", ")}) { | ||
`; | ||
if (!this.static) { | ||
@@ -95,16 +87,25 @@ str += ` | ||
str += ` | ||
return ${callOn}.${implFunc}(${argsSpread}); | ||
}; | ||
return ${callOn}.${implFunc}(${argsSpread}); | ||
`; | ||
} else { | ||
str += ` | ||
return utils.tryWrapperForImpl(${callOn}.${implFunc}(${argsSpread})); | ||
}; | ||
return utils.tryWrapperForImpl(${callOn}.${implFunc}(${argsSpread})); | ||
`; | ||
} | ||
return { | ||
requires, | ||
body: str | ||
}; | ||
if (this.static) { | ||
this.interface.addStaticMethod(this.name, argNames, str); | ||
} else { | ||
const forgeable = !utils.getExtAttr(this.idls[0].extAttrs, "Unforgeable"); | ||
this.interface.addMethod( | ||
onInstance ? "instance" : "prototype", | ||
this.name, | ||
argNames, | ||
str, | ||
"regular", | ||
{ configurable: forgeable, writable: forgeable } | ||
); | ||
} | ||
return { requires }; | ||
} | ||
@@ -111,0 +112,0 @@ } |
@@ -12,2 +12,27 @@ "use strict"; | ||
const getOwnPropertyDescriptors = typeof Object.getOwnPropertyDescriptors === "function" ? | ||
Object.getOwnPropertyDescriptors : | ||
// Polyfill exists until we require Node.js v8.x | ||
// https://tc39.github.io/ecma262/#sec-object.getownpropertydescriptors | ||
obj => { | ||
if (obj === undefined || obj === null) { | ||
throw new TypeError("Cannot convert undefined or null to object"); | ||
} | ||
obj = Object(obj); | ||
const ownKeys = Reflect.ownKeys(obj); | ||
const descriptors = {}; | ||
for (const key of ownKeys) { | ||
const descriptor = Reflect.getOwnPropertyDescriptor(obj, key); | ||
if (descriptor !== undefined) { | ||
Reflect.defineProperty(descriptors, key, { | ||
value: descriptor, | ||
writable: true, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
} | ||
} | ||
return descriptors; | ||
}; | ||
const wrapperSymbol = Symbol("wrapper"); | ||
@@ -81,2 +106,3 @@ const implSymbol = Symbol("impl"); | ||
hasOwn, | ||
getOwnPropertyDescriptors, | ||
wrapperSymbol, | ||
@@ -83,0 +109,0 @@ implSymbol, |
@@ -235,3 +235,4 @@ "use strict"; | ||
return prettier.format(source, { | ||
printWidth: 120 | ||
printWidth: 120, | ||
parser: "babylon" | ||
}); | ||
@@ -238,0 +239,0 @@ } |
@@ -32,4 +32,3 @@ "use strict"; | ||
function isGlobal(idl) { | ||
return Boolean(getExtAttr(idl.extAttrs, "Global")) || | ||
Boolean(getExtAttr(idl.extAttrs, "PrimaryGlobal")); | ||
return Boolean(getExtAttr(idl.extAttrs, "Global")); | ||
} | ||
@@ -41,2 +40,19 @@ | ||
function stringifyPropertyName(propName) { | ||
if (typeof propName === "symbol") { | ||
const desc = String(propName).replace(/^Symbol\((.*)\)$/, "$1"); | ||
if (!desc.startsWith("Symbol.")) { | ||
throw new Error(`Internal error: Unsupported property name ${String(propName)}`); | ||
} | ||
return `[${desc}]`; | ||
} | ||
// All Web IDL identifiers are valid JavaScript PropertyNames, other than those with '-'. | ||
const isJSIdentifier = !propName.includes("-"); | ||
if (isJSIdentifier) { | ||
return propName; | ||
} | ||
return JSON.stringify(propName); | ||
} | ||
class RequiresMap extends Map { | ||
@@ -83,3 +99,4 @@ constructor(ctx) { | ||
isOnInstance, | ||
stringifyPropertyName, | ||
RequiresMap | ||
}; |
{ | ||
"name": "webidl2js", | ||
"version": "9.0.0", | ||
"version": "9.0.1", | ||
"description": "Auto-generates class structures for WebIDL specifications", | ||
"main": "lib/transformer.js", | ||
"repository": "github:jsdom/webidl2js", | ||
"dependencies": { | ||
"co": "^4.6.0", | ||
"pn": "^1.0.0", | ||
"prettier": "^1.5.3", | ||
"pn": "^1.1.0", | ||
"prettier": "^1.14.0", | ||
"webidl-conversions": "^4.0.0", | ||
"webidl2": "^8.1.0" | ||
"webidl2": "^9.0.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^4.3.0", | ||
"jest": "^21.2.1" | ||
"eslint": "^4.13.1", | ||
"jest": "^23.4.2" | ||
}, | ||
@@ -22,4 +23,7 @@ "scripts": { | ||
}, | ||
"jest": { | ||
"testEnvironment": "node" | ||
}, | ||
"author": "Sebastian Mayr <npm@smayr.name>", | ||
"license": "MIT" | ||
} |
@@ -12,3 +12,3 @@ # JavaScript bindings generator for Web IDL | ||
unsigned long long add(unsigned long x, unsigned long y); | ||
} | ||
}; | ||
``` | ||
@@ -19,3 +19,3 @@ | ||
```js | ||
exports.implementation = class { | ||
exports.implementation = class SomeInterfaceImpl { | ||
add(x, y) { | ||
@@ -35,34 +35,34 @@ return x + y; | ||
function SomeInterface() { | ||
throw new TypeError("Illegal constructor"); | ||
} | ||
SomeInterface.prototype.add = function add(x, y) { | ||
if (!exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
class SomeInterface { | ||
constructor() { | ||
throw new TypeError("Illegal constructor"); | ||
} | ||
if (arguments.length < 2) { | ||
throw new TypeError( | ||
"Failed to execute 'add' on 'SomeInterface': 2 arguments required, but only " + | ||
arguments.length + | ||
" present." | ||
); | ||
} | ||
const args = []; | ||
args[0] = conversions["unsigned long"](arguments[0], { | ||
context: "Failed to execute 'add' on 'SomeInterface': parameter 1" | ||
}); | ||
args[1] = conversions["unsigned long"](arguments[1], { | ||
context: "Failed to execute 'add' on 'SomeInterface': parameter 2" | ||
}); | ||
add(x, y) { | ||
if (!exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
} | ||
if (arguments.length < 2) { | ||
throw new TypeError( | ||
"Failed to execute 'add' on 'SomeInterface': 2 arguments required, but only " + | ||
arguments.length + | ||
" present." | ||
); | ||
} | ||
this[impl].add(...args); | ||
}; | ||
const args = []; | ||
args[0] = conversions["unsigned long"](arguments[0], { | ||
context: "Failed to execute 'add' on 'SomeInterface': parameter 1" | ||
}); | ||
args[1] = conversions["unsigned long"](arguments[1], { | ||
context: "Failed to execute 'add' on 'SomeInterface': parameter 2" | ||
}); | ||
Object.defineProperty(SomeInterface.prototype, Symbol.toStringTag, { | ||
value: "SomeInterface", | ||
writable: false, | ||
enumerable: false, | ||
configurable: true | ||
return this[impl].add(...args); | ||
} | ||
} | ||
Object.defineProperties(SomeInterface.prototype, { | ||
add: { enumerable: true }, | ||
[Symbol.toStringTag]: { value: "SomeInterface", configurable: true } | ||
}); | ||
@@ -88,2 +88,3 @@ | ||
- Argument conversion according to the rules specified in Web IDL for given argument types | ||
- Enumerability of operations | ||
- @@toStringTag semantics to make `Object.prototype.toString.call()` behave correctly | ||
@@ -291,2 +292,6 @@ - After performing Web IDL-related processing, webidl2js delegates to the implementation class for the non-boilerplate parts of `add()`. This allows you to focus on writing the interesting parts of the implementation without worrying about types, brand-checking, parameter-processing, etc. | ||
### Other requirements | ||
The generated interface wrapper files use modern JavaScript features such as `class` definitions and `Proxy`. They will not work on JavaScript runtimes that do not support the ECMAScript 2015 standard. | ||
## The generated utilities file | ||
@@ -293,0 +298,0 @@ |
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
402
138847
19
3336
2
+ Addedwebidl2@9.0.0(transitive)
- Removedwebidl2@8.1.0(transitive)
Updatedpn@^1.1.0
Updatedprettier@^1.14.0
Updatedwebidl2@^9.0.0