webidl2js
Advanced tools
Comparing version 16.1.0 to 16.2.0
@@ -32,3 +32,3 @@ "use strict"; | ||
if (!exports.is(esValue)) { | ||
throw new TypeError("Illegal invocation"); | ||
throw new TypeError("'$KEYWORD$ ${this.idl.name}' called on an object that is not a valid instance of ${this.interface.name}."); | ||
} | ||
@@ -82,3 +82,3 @@ `; | ||
const esValue = this !== null && this !== undefined ? this : globalObject; | ||
${brandCheck} | ||
${brandCheck.replace("$KEYWORD$", "get")} | ||
${getterBody} | ||
@@ -88,2 +88,4 @@ ${promiseHandlingAfter} | ||
brandCheck = brandCheck.replace("$KEYWORD$", "set"); | ||
if (!this.idl.readonly) { | ||
@@ -165,3 +167,3 @@ if (async) { | ||
if (!exports.is(esValue)) { | ||
throw new TypeError("Illegal invocation"); | ||
throw new TypeError("'toString' called on an object that is not a valid instance of ${this.interface.name}."); | ||
} | ||
@@ -168,0 +170,0 @@ |
@@ -7,2 +7,3 @@ "use strict"; | ||
const Iterable = require("./iterable"); | ||
const AsyncIterable = require("./async-iterable"); | ||
const Operation = require("./operation"); | ||
@@ -12,3 +13,2 @@ const Types = require("../types"); | ||
const Parameters = require("../parameters"); | ||
const keywords = require("../keywords"); | ||
@@ -22,6 +22,2 @@ function isNamed(idl) { | ||
function formatArgs(args) { | ||
return args.map(name => name + (keywords.has(name) ? "_" : "")).join(", "); | ||
} | ||
const defaultObjectLiteralDescriptor = { | ||
@@ -317,3 +313,5 @@ configurable: true, | ||
} | ||
this.iterable = new Iterable(this.ctx, this, member); | ||
this.iterable = member.async ? | ||
new AsyncIterable(this.ctx, this, member) : | ||
new Iterable(this.ctx, this, member); | ||
break; | ||
@@ -380,9 +378,11 @@ default: | ||
if (this.iterable) { | ||
if (this.iterable.isValue) { | ||
if (!this.supportsIndexedProperties) { | ||
throw new Error(`A value iterator cannot be declared on ${this.name} which does not support indexed ` + | ||
"properties"); | ||
if (!this.iterable.isAsync) { | ||
if (this.iterable.isValue) { | ||
if (!this.supportsIndexedProperties) { | ||
throw new Error(`A value iterator cannot be declared on ${this.name} which does not support indexed ` + | ||
"properties"); | ||
} | ||
} else if (this.iterable.isPair && this.supportsIndexedProperties) { | ||
throw new Error(`A pair iterator cannot be declared on ${this.name} which supports indexed properties`); | ||
} | ||
} else if (this.iterable.isPair && this.supportsIndexedProperties) { | ||
throw new Error(`A pair iterator cannot be declared on ${this.name} which supports indexed properties`); | ||
} | ||
@@ -429,8 +429,98 @@ for (const n of ["entries", "forEach", "keys", "values"]) { | ||
generateIterator() { | ||
if (this.iterable && this.iterable.isPair) { | ||
if (!this.iterable) { | ||
return; | ||
} | ||
if (this.iterable.isAsync) { | ||
this.str += ` | ||
const AsyncIteratorPrototype = Object.create(utils.AsyncIteratorPrototype, { | ||
[Symbol.toStringTag]: { | ||
value: "${this.name} AsyncIterator", | ||
configurable: true | ||
} | ||
}); | ||
Object.assign(AsyncIteratorPrototype, { | ||
next() { | ||
const internal = this && this[utils.iterInternalSymbol]; | ||
if (!internal) { | ||
return Promise.reject(new TypeError("next() called on a value that is not an async iterator prototype object")); | ||
} | ||
const nextSteps = () => { | ||
if (internal.isFinished) { | ||
return Promise.resolve({ value: undefined, done: true }); | ||
} | ||
const nextPromise = internal.target[implSymbol][utils.asyncIteratorNext](this); | ||
return nextPromise.then( | ||
next => { | ||
internal.ongoingPromise = null; | ||
if (next === utils.asyncIteratorEOI) { | ||
internal.isFinished = true; | ||
return { value: undefined, done: true }; | ||
}`; | ||
if (this.iterable.isPair) { | ||
this.str += ` | ||
return utils.iteratorResult(next.map(utils.tryWrapperForImpl), kind); | ||
`; | ||
} else { | ||
this.str += ` | ||
return { value: utils.tryWrapperForImpl(next), done: false }; | ||
`; | ||
} | ||
this.str += ` | ||
}, | ||
reason => { | ||
internal.ongoingPromise = null; | ||
internal.isFinished = true; | ||
throw reason; | ||
} | ||
); | ||
}; | ||
internal.ongoingPromise = internal.ongoingPromise ? | ||
internal.ongoingPromise.then(nextSteps, nextSteps) : | ||
nextSteps(); | ||
return internal.ongoingPromise; | ||
}, | ||
`; | ||
if (this.iterable.hasReturnSteps) { | ||
this.str += ` | ||
return(value) { | ||
const internal = this && this[utils.iterInternalSymbol]; | ||
if (!internal) { | ||
return Promise.reject(new TypeError("return() called on a value that is not an async iterator prototype object")); | ||
} | ||
const returnSteps = () => { | ||
if (internal.isFinished) { | ||
return Promise.resolve({ value, done: true }); | ||
} | ||
internal.isFinished = true; | ||
return internal.target[implSymbol][utils.asyncIteratorReturn](this, value); | ||
}; | ||
const returnPromise = internal.ongoingPromise ? | ||
internal.ongoingPromise.then(returnSteps, returnSteps) : | ||
returnSteps(); | ||
return returnPromise.then(() => ({ value, done: true })); | ||
} | ||
`; | ||
} | ||
this.str += ` | ||
}); | ||
`; | ||
} else if (this.iterable.isPair) { | ||
this.str += ` | ||
const IteratorPrototype = Object.create(utils.IteratorPrototype, { | ||
next: { | ||
value: function next() { | ||
const internal = this[utils.iterInternalSymbol]; | ||
const internal = this && this[utils.iterInternalSymbol]; | ||
if (!internal) { | ||
throw new TypeError("next() called on a value that is not an iterator prototype object"); | ||
} | ||
const { target, kind, index } = internal; | ||
@@ -445,17 +535,3 @@ const values = Array.from(target[implSymbol]); | ||
internal.index = index + 1; | ||
const [key, value] = pair.map(utils.tryWrapperForImpl); | ||
let result; | ||
switch (kind) { | ||
case "key": | ||
result = key; | ||
break; | ||
case "value": | ||
result = value; | ||
break; | ||
case "key+value": | ||
result = [key, value]; | ||
break; | ||
} | ||
return { value: result, done: false }; | ||
return utils.iteratorResult(pair.map(utils.tryWrapperForImpl), kind); | ||
}, | ||
@@ -547,13 +623,26 @@ writable: true, | ||
if (this.iterable && this.iterable.isPair) { | ||
this.str += ` | ||
exports.createDefaultIterator = (target, kind) => { | ||
const iterator = Object.create(IteratorPrototype); | ||
Object.defineProperty(iterator, utils.iterInternalSymbol, { | ||
value: { target, kind, index: 0 }, | ||
configurable: true | ||
}); | ||
return iterator; | ||
}; | ||
`; | ||
if (this.iterable) { | ||
if (this.iterable.isAsync) { | ||
this.str += ` | ||
exports.createDefaultAsyncIterator = (target, kind) => { | ||
const iterator = Object.create(AsyncIteratorPrototype); | ||
Object.defineProperty(iterator, utils.iterInternalSymbol, { | ||
value: { target, kind, ongoingPromise: null, isFinished: false }, | ||
configurable: true | ||
}); | ||
return iterator; | ||
}; | ||
`; | ||
} else if (this.iterable.isPair) { | ||
this.str += ` | ||
exports.createDefaultIterator = (target, kind) => { | ||
const iterator = Object.create(IteratorPrototype); | ||
Object.defineProperty(iterator, utils.iterInternalSymbol, { | ||
value: { target, kind, index: 0 }, | ||
configurable: true | ||
}); | ||
return iterator; | ||
}; | ||
`; | ||
} | ||
} | ||
@@ -857,3 +946,5 @@ } | ||
} | ||
if (target === receiver) { | ||
// The \`receiver\` argument refers to the Proxy exotic object or an object | ||
// that inherits from it, whereas \`target\` refers to the Proxy target: | ||
if (target[implSymbol][utils.wrapperSymbol] === receiver) { | ||
`; | ||
@@ -867,31 +958,17 @@ | ||
if (this.supportsIndexedProperties) { | ||
if (hasIndexedSetter) { | ||
this.str += ` | ||
if (utils.isArrayIndexPropName(P)) { | ||
${invokeIndexedSetter("target", "P", "V")} | ||
return true; | ||
} | ||
`; | ||
} else { | ||
// Side-effects | ||
this.str += ` | ||
utils.isArrayIndexPropName(P); | ||
`; | ||
} | ||
if (this.supportsIndexedProperties && hasIndexedSetter) { | ||
this.str += ` | ||
if (utils.isArrayIndexPropName(P)) { | ||
${invokeIndexedSetter("target", "P", "V")} | ||
return true; | ||
} | ||
`; | ||
} | ||
if (this.supportsNamedProperties) { | ||
if (hasNamedSetter) { | ||
this.str += ` | ||
if (typeof P === "string" && !utils.isArrayIndexPropName(P)) { | ||
${invokeNamedSetter("target", "P", "V")} | ||
return true; | ||
} | ||
`; | ||
} else { | ||
// Side-effects | ||
this.str += ` | ||
typeof P === "string" && !utils.isArrayIndexPropName(P); | ||
`; | ||
} | ||
if (this.supportsNamedProperties && hasNamedSetter) { | ||
this.str += ` | ||
if (typeof P === "string") { | ||
${invokeNamedSetter("target", "P", "V")} | ||
return true; | ||
} | ||
`; | ||
} | ||
@@ -1262,3 +1339,3 @@ | ||
${conversions.body} | ||
return exports.setup(${formatArgs(setupArgs)}); | ||
return exports.setup(${utils.formatArgs(setupArgs)}); | ||
`; | ||
@@ -1333,3 +1410,3 @@ } else { | ||
this.str += ` | ||
${name}(${formatArgs(args)}) {${body}} | ||
${name}(${utils.formatArgs(args)}) {${body}} | ||
`; | ||
@@ -1430,3 +1507,3 @@ }; | ||
methods.push(` | ||
${name}(${formatArgs(args)}) {${body}} | ||
${name}(${utils.formatArgs(args)}) {${body}} | ||
`); | ||
@@ -1433,0 +1510,0 @@ } |
@@ -21,6 +21,10 @@ "use strict"; | ||
get isAsync() { | ||
return false; | ||
} | ||
generateFunction(key, kind) { | ||
this.interface.addMethod(this.interface.defaultWhence, key, [], ` | ||
if (!this || !exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
if (!exports.is(this)) { | ||
throw new TypeError("'${key}' called on an object that is not a valid instance of ${this.interface.name}."); | ||
} | ||
@@ -41,4 +45,4 @@ return exports.createDefaultIterator(this, "${kind}"); | ||
this.interface.addMethod(whence, "forEach", ["callback"], ` | ||
if (!this || !exports.is(this)) { | ||
throw new TypeError("Illegal invocation"); | ||
if (!exports.is(this)) { | ||
throw new TypeError("'forEach' called on an object that is not a valid instance of ${this.interface.name}."); | ||
} | ||
@@ -45,0 +49,0 @@ if (arguments.length < 1) { |
@@ -46,2 +46,23 @@ "use strict"; | ||
hasCallWithGlobal() { | ||
const { idls } = this; | ||
const hasCallWithGlobal = Boolean(utils.getExtAttr(idls[0].extAttrs, "WebIDL2JSCallWithGlobal")); | ||
if (hasCallWithGlobal && !this.static) { | ||
throw new Error( | ||
`[WebIDL2JSCallWithGlobal] is only valid for static operations: "${this.name}" on ${this.interface.name}` | ||
); | ||
} | ||
for (let i = 1; i < idls.length; i++) { | ||
if (hasCallWithGlobal !== Boolean(utils.getExtAttr(idls[i].extAttrs, "WebIDL2JSCallWithGlobal"))) { | ||
throw new Error( | ||
`[WebIDL2JSCallWithGlobal] is not applied uniformly to operation "${this.name}" on ${this.interface.name}` | ||
); | ||
} | ||
} | ||
return hasCallWithGlobal; | ||
} | ||
fixUpArgsExtAttrs() { | ||
@@ -73,2 +94,3 @@ for (const idl of this.idls) { | ||
const promiseHandlingAfter = async ? `} catch (e) { return Promise.reject(e); }` : ``; | ||
const hasCallWithGlobal = this.hasCallWithGlobal(); | ||
@@ -90,3 +112,3 @@ const type = this.static ? "static operation" : "regular operation"; | ||
if (!exports.is(esValue)) { | ||
throw new TypeError("Illegal invocation"); | ||
throw new TypeError("'${this.name}' called on an object that is not a valid instance of ${this.interface.name}."); | ||
} | ||
@@ -103,14 +125,22 @@ `; | ||
this.ctx, type, this.name, this.interface, `Failed to execute '${this.name}' on '${this.interface.name}': `); | ||
const argsSpread = parameterConversions.hasArgs ? "...args" : ""; | ||
const args = []; | ||
requires.merge(parameterConversions.requires); | ||
str += parameterConversions.body; | ||
if (hasCallWithGlobal) { | ||
args.push("globalObject"); | ||
} | ||
if (parameterConversions.hasArgs) { | ||
args.push("...args"); | ||
} | ||
let invocation; | ||
if (overloads.every(overload => conversions[overload.operation.idlType.idlType])) { | ||
invocation = ` | ||
return ${callOn}.${implFunc}(${argsSpread}); | ||
return ${callOn}.${implFunc}(${utils.formatArgs(args)}); | ||
`; | ||
} else { | ||
invocation = ` | ||
return utils.tryWrapperForImpl(${callOn}.${implFunc}(${argsSpread})); | ||
return utils.tryWrapperForImpl(${callOn}.${implFunc}(${utils.formatArgs(args)})); | ||
`; | ||
@@ -117,0 +147,0 @@ } |
@@ -48,2 +48,3 @@ "use strict"; | ||
const IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())); | ||
const AsyncIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(async function* () {}).prototype); | ||
@@ -76,2 +77,18 @@ function isArrayIndexPropName(P) { | ||
function iteratorResult([key, value], kind) { | ||
let result; | ||
switch (kind) { | ||
case "key": | ||
result = key; | ||
break; | ||
case "value": | ||
result = value; | ||
break; | ||
case "key+value": | ||
result = [key, value]; | ||
break; | ||
} | ||
return { value: result, done: false }; | ||
} | ||
const supportsPropertyIndex = Symbol("supports property index"); | ||
@@ -89,2 +106,7 @@ const supportedPropertyIndices = Symbol("supported property indices"); | ||
const asyncIteratorNext = Symbol("async iterator get the next iteration result"); | ||
const asyncIteratorReturn = Symbol("async iterator return steps"); | ||
const asyncIteratorInit = Symbol("async iterator initialization steps"); | ||
const asyncIteratorEOI = Symbol("async iterator end of iteration"); | ||
module.exports = exports = { | ||
@@ -103,2 +125,3 @@ isObject, | ||
IteratorPrototype, | ||
AsyncIteratorPrototype, | ||
isArrayBuffer, | ||
@@ -116,3 +139,8 @@ isArrayIndexPropName, | ||
namedSetExisting, | ||
namedDelete | ||
namedDelete, | ||
asyncIteratorNext, | ||
asyncIteratorReturn, | ||
asyncIteratorInit, | ||
asyncIteratorEOI, | ||
iteratorResult | ||
}; |
@@ -17,3 +17,10 @@ "use strict"; | ||
// Always (try to) force-convert dictionaries | ||
const optional = overload.optionalityList[i] === "optional" && !ctx.dictionaries.has(idlType.idlType); | ||
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}];`; | ||
@@ -50,2 +57,52 @@ if (optional) { | ||
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) { | ||
@@ -52,0 +109,0 @@ const requires = new utils.RequiresMap(ctx); |
"use strict"; | ||
const { extname } = require("path"); | ||
const keywords = require("./keywords.js"); | ||
@@ -98,2 +99,9 @@ function getDefault(dflt) { | ||
function formatArgs(args) { | ||
return args | ||
.filter(name => name !== null && name !== undefined && name !== "") | ||
.map(name => name + (keywords.has(name) ? "_" : "")) | ||
.join(", "); | ||
} | ||
function toKey(type, func = "") { | ||
@@ -172,3 +180,4 @@ return String(func + type).replace(/[./-]+/g, " ").trim().replace(/ /g, "_"); | ||
defaultDefinePropertyDescriptor, | ||
formatArgs, | ||
RequiresMap | ||
}; |
{ | ||
"name": "webidl2js", | ||
"version": "16.1.0", | ||
"version": "16.2.0", | ||
"description": "Auto-generates class structures for WebIDL specifications", | ||
@@ -10,3 +10,3 @@ "main": "lib/transformer.js", | ||
"webidl-conversions": "^6.1.0", | ||
"webidl2": "^23.11.0" | ||
"webidl2": "^23.12.1" | ||
}, | ||
@@ -13,0 +13,0 @@ "devDependencies": { |
@@ -388,3 +388,3 @@ # JavaScript bindings generator for Web IDL | ||
The first is the getters, (optional) setters, and (optional) deleters operations. Much like stringifiers, getters, setters, and deleters can either be standalone or aliased to a named operation (though not an attribute). If an operation is standalone, then the implementation class must implement following symbol-named methods. The `utils` object below refers to the default export from the generated utilities file `utils.js`. | ||
The first is the getters, (optional) setters, and (optional) deleters operations. Much like stringifiers, getters, setters, and deleters can either be standalone or aliased to a named operation (though not an attribute). If an operation is standalone, then the implementation class must implement the following symbol-named methods. The `utils` object below refers to the default export from the generated utilities file `utils.js`. | ||
@@ -399,2 +399,16 @@ - Getters: `utils.indexedGet`, `utils.namedGet` | ||
### Iterables | ||
For synchronous value iterable declarations, there is no need to add implementation-class code: they will be automatically generated based on the indexed property getter and `length` property. | ||
For synchronous pair iterable declarations, the implementation class needs to implement the `[Symbol.iterator]()` property, returning an iterable of `[key, value]` pairs. These can be impls; the generated code will convert them into wrappers as necessary. | ||
### Async iterables | ||
[Asynchronous iterable declarations](https://heycam.github.io/webidl/#idl-async-iterable) require the implementation class to implement the following symbol-named methods, corresponding to algorithms from the Web IDL specification. The `utils` object below refers to the default export from the generated utilities file `utils.js`. | ||
- `utils.asyncIteratorNext`: corresponds to the [get the next iteration result](https://heycam.github.io/webidl/#dfn-get-the-next-iteration-result) algorithm, and receives a single argument containing an instance of the generated async iterator. For pair asynchronous iterables, the return value must be a `[key, value]` pair array, or `utils.asyncIteratorEOI` to signal the end of the iteration. For value asynchronous iterables, the return value must be the value, or `utils.asyncIteratorEOI` to signal the end of the iteration. | ||
- `utils.asyncIteratorInit`: corresponds to the [asynchronous iterator initialization steps](https://heycam.github.io/webidl/#asynchronous-iterator-initialization-steps), and receives two arguments: the instance of the generated async iterator, and an array containing the post-conversion arguments. This method is optional. | ||
- `utils.asyncIteratorReturn`: corresponds to the [asynchronous iterator return](https://heycam.github.io/webidl/#asynchronous-iterator-return) algorithm, and receives two arguments: the instance of the generated async iterator, and the argument passed to the `return()` method. This method is optional. Note that if you include it, you need to annote the async iterable declaration with [`[WebIDL2JSHasReturnSteps]`](#webidl2jshasreturnsteps). | ||
### Other, non-exposed data and functionality | ||
@@ -454,2 +468,3 @@ | ||
- `iterable<>` declarations | ||
- `async iterable<>` declarations | ||
- Class strings (with the semantics of [heycam/webidl#357](https://github.com/heycam/webidl/pull/357)) | ||
@@ -508,4 +523,14 @@ - Dictionary types | ||
One non-standard extended attribute is baked in to webidl2js: | ||
A couple of non-standard extended attributes are baked in to webidl2js: | ||
### `[WebIDL2JSCallWithGlobal]` | ||
When the `[WebIDL2JSCallWithGlobal]` extended attribute is specified on static IDL operations, the generated interface code passes the [current global object](https://html.spec.whatwg.org/multipage/webappapis.html#current-global-object) as the first parameter to the implementation code. All other parameters follow `globalObject` and are unchanged. This could be used to implement factory functions that create objects in the current realm. | ||
### `[WebIDL2JSHasReturnSteps]` | ||
This extended attribute can be applied to async iterable declarations. It declares that the implementation class will implement the `[idlUtils.asyncIteratorReturn]()` method. | ||
This is necessary because we need to figure out at code-generation time whether to generate a `return()` method on the async iterator prototype. At that point, only the Web IDL is available, not the implementation class properties. So, we need a signal in the Web IDL itself. | ||
### `[WebIDL2JSValueAsUnsupported=value]` | ||
@@ -512,0 +537,0 @@ |
181983
23
4181
539
Updatedwebidl2@^23.12.1