From a0cbdf0f9af4123a7ba409b2431d203560478a7a Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sun, 5 Feb 2017 21:02:58 -0800 Subject: [PATCH 1/9] Add support for record, sequence, Promise, FrozenArray types --- lib/constructs/dictionary.js | 33 +---- lib/parameters.js | 37 +----- lib/types.js | 170 ++++++++++++++++++++++++ package.json | 2 +- test/cases/SeqAndRec.idl | 7 + test/cases/URL.idl | 2 + test/cases/URLSearchParams.idl | 5 +- test/implementations/SeqAndRec.js | 29 ++++ test/implementations/URL.js | 7 +- test/implementations/URLSearchParams.js | 7 +- 10 files changed, 239 insertions(+), 60 deletions(-) create mode 100644 lib/types.js create mode 100644 test/cases/SeqAndRec.idl create mode 100644 test/implementations/SeqAndRec.js diff --git a/lib/constructs/dictionary.js b/lib/constructs/dictionary.js index 72540e02..b5ed9af7 100644 --- a/lib/constructs/dictionary.js +++ b/lib/constructs/dictionary.js @@ -1,6 +1,6 @@ "use strict"; -const conversions = require("webidl-conversions"); +const Types = require("../types"); const utils = require("../utils"); class Dictionary { @@ -32,34 +32,15 @@ class Dictionary { this.str += ` if (value !== undefined) {`; const argAttrs = field.extAttrs; - const enforceRange = utils.getExtAttr(argAttrs, "EnforceRange"); - const clamp = utils.getExtAttr(argAttrs, "Clamp"); - let optString = ""; - if (clamp) { - optString = `, { clamp: true }`; - } else if (enforceRange) { - optString = `, { enforceRange: true }`; - } - - let conversionFn = ""; - if (conversions[typeConversion.idlType]) { - conversionFn = `conversions["${typeConversion.idlType}"]`; - } else if (this.opts.customTypes.has(typeConversion.idlType)) { - this.str = `const convert${typeConversion.idlType} = require("./${typeConversion.idlType}");\n` + this.str; - conversionFn = `convert${typeConversion.idlType}`; + const conv = Types.generateTypeConversion("value", typeConversion, argAttrs, this.opts.customTypes); + for (let key in conv.requires) { + this.str = `const ${key} = ${conv.requires[key]};\n` + this.str; } + this.str += conv.body; + this.str += ` + ret[key] = value;` - if (typeConversion.array) { - this.str += ` - ret[key] = []; - for (let i = 0; i < value.length; ++i) { - ret[key][i] = ${conversionFn}(value[i]${optString}); - }`; - } else { - this.str += ` - ret[key] = ${conversionFn}(value${optString});`; - } if (field.required) { this.str += ` } else { diff --git a/lib/parameters.js b/lib/parameters.js index 9b8fd715..26781a05 100644 --- a/lib/parameters.js +++ b/lib/parameters.js @@ -1,7 +1,7 @@ "use strict"; -const conversions = require("webidl-conversions"); const Overloads = require("./overloads"); +const Types = require("./types"); const utils = require("./utils"); @@ -10,46 +10,23 @@ module.exports.generateVarConversion = function (name, conversion, argAttrs, cus let str = ""; const idlType = conversion.type; - if (idlType.nullable) { str += ` if (${name} === null || ${name} === undefined) { ${name} = null; } else {`; } - if (conversions[idlType.idlType] || customTypes.has(idlType.idlType)) { - const enforceRange = utils.getExtAttr(argAttrs, "EnforceRange"); - const clamp = utils.getExtAttr(argAttrs, "Clamp"); - const treatNullAs = utils.getExtAttr(argAttrs, "TreatNullAs"); - let optString = ""; - if (clamp) { - optString = `, { clamp: true }`; - } else if (enforceRange) { - optString = `, { enforceRange: true }`; - } else if (treatNullAs && treatNullAs.rhs.value === "EmptyString") { - optString = `, { treatNullAsEmptyString: true }`; - } - let conversionFn = null; - if (conversions[idlType.idlType]) { - conversionFn = `conversions["${idlType.idlType}"]`; - } else { - requires[`convert${idlType.idlType}`] = `require("./${idlType.idlType}").convert`; - conversionFn = `convert${idlType.idlType}`; - } + if (Types.canHandleType(idlType, customTypes)) { if (conversion.optional && !customTypes.has(idlType.idlType)) { // always (try to) force-convert dictionaries str += ` if (${name} !== undefined) {`; } - if (idlType.array) { - str += ` - for (let i = 0; i < ${name}.length; ++i) { - ${name}[i] = ${conversionFn}(${name}[i]${optString}); - }`; - } else { - str += ` - ${name} = ${conversionFn}(${name}${optString});`; - } + + const conv = Types.generateTypeConversion(name, idlType, argAttrs, customTypes); + Object.assign(requires, conv.requires); + str += conv.body; + if (conversion.optional && !customTypes.has(idlType.idlType)) { str += ` }`; diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 00000000..102f4e74 --- /dev/null +++ b/lib/types.js @@ -0,0 +1,170 @@ +"use strict"; + +const conversions = require("webidl-conversions"); + +const utils = require("./utils"); + +function generateTypeConversion(name, idlType, argAttrs, customTypes, opts) { + opts = opts || {}; + const requires = {}; + const handleNullable = Boolean(opts.handleNullable); + let str = ""; + + + if (handleNullable && idlType.nullable) { + str += ` + if (${name} === null || ${name} === undefined) { + ${name} = null; + } else {`; + } + + if (idlType.generic === "sequence") { + // sequence type + generateSequence(); + } else if (idlType.generic === "record") { + // record type + generateRecord(); + } else if (idlType.generic === "Promise") { + // Promise type + generatePromise(); + } else if (idlType.generic === "FrozenArray") { + // frozen array type + generateFrozenArray(); + } else if (conversions[idlType.idlType]) { + // string or number type compatible with webidl-conversions + generateGeneric(`conversions["${idlType.idlType}"]`); + } else if (customTypes.has(idlType.idlType)) { + // dictionaries or interfaces + const varName = `convert${idlType.idlType}`; + requires[varName] = `require("./${idlType.idlType}").convert`; + generateGeneric(varName); + } else { + // unknown + // Try to get the impl anyway. + str += ` + ${name} = utils.tryImplForWrapper(${name});`; + } + + if (handleNullable && idlType.nullable) { + str += ` + }`; + } + + return { + requires, + body: str + }; + + function generateSequence() { + str += ` + if (typeof ${name} !== "object") { + throw new TypeError("The value provided is not an iterable object"); + } else { + const V = []; + const tmp = ${name}; + for (let nextItem of tmp) {`; + + const conv = generateTypeConversion("nextItem", idlType.idlType, [], customTypes, { + handleNullable: true + }); + Object.assign(requires, conv.requires); + str += conv.body; + + str += ` + V.push(nextItem); + } + ${name} = V; + }`; + } + + function generateRecord() { + if (!handleNullable || !idlType.nullable) { + str += ` + if (${name} == null) { + ${name} = Object.create(null); + } else {`; + } + + str += ` + if (typeof ${name} !== "object") { + throw new TypeError("The value provided is not an object"); + } else { + const result = Object.create(null); + const keys = Object.getOwnPropertyNames(${name}); + for (let key of keys) { + const desc = Object.getOwnPropertyDescriptor(${name}, key); + if (desc && desc.enumerable) { + let typedKey = key; + let typedValue = ${name}[key];`; + + str += generateTypeConversion("typedKey", idlType.idlType[0], [], customTypes).body; + + const conv = generateTypeConversion("typedValue", idlType.idlType[1], [], customTypes, { + handleNullable: true + }); + Object.assign(requires, conv.requires); + str += conv.body; + + str += ` + result[typedKey] = typedValue; + } + } + ${name} = result; + }`; + + if (!handleNullable || !idlType.nullable) { + str += ` + }`; + } + } + + function generatePromise() { + str += ` + ${name} = Promise.resolve(${name});`; + } + + function generateFrozenArray() { + generateSequence(); + str += ` + ${name} = Object.freeze(${name});`; + } + + function generateGeneric(conversionFn) { + const enforceRange = utils.getExtAttr(argAttrs, "EnforceRange"); + const clamp = utils.getExtAttr(argAttrs, "Clamp"); + const treatNullAs = utils.getExtAttr(argAttrs, "TreatNullAs"); + + let optString = ""; + if (clamp) { + optString = `, { clamp: true }`; + } else if (enforceRange) { + optString = `, { enforceRange: true }`; + } else if (treatNullAs && treatNullAs.rhs.value === "EmptyString") { + optString = `, { treatNullAsEmptyString: true }`; + } + if (conversions[idlType.idlType]) { + conversionFn = `conversions["${idlType.idlType}"]`; + } else { + requires[`convert${idlType.idlType}`] = `require("./${idlType.idlType}").convert`; + conversionFn = `convert${idlType.idlType}`; + } + if (idlType.array) { + str += ` + for (let i = 0; i < ${name}.length; ++i) { + ${name}[i] = ${conversionFn}(${name}[i]${optString}); + }`; + } else { + str += ` + ${name} = ${conversionFn}(${name}${optString});`; + } + } +} + +function canHandleType(idlType, customTypes) { + return idlType.generic === "sequence" || idlType.generic === "record" || idlType.idlType in conversions || customTypes.has(idlType.idlType); +} + +module.exports = { + generateTypeConversion, + canHandleType +}; diff --git a/package.json b/package.json index 15b46b2d..a305ad3d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "pn": "^1.0.0", "prettier": "^1.3.1", "webidl-conversions": "^4.0.0", - "webidl2": "^2.0.11" + "webidl2": "^2.2.2" }, "devDependencies": { "jest": "^20.0.0" diff --git a/test/cases/SeqAndRec.idl b/test/cases/SeqAndRec.idl new file mode 100644 index 00000000..cd416c96 --- /dev/null +++ b/test/cases/SeqAndRec.idl @@ -0,0 +1,7 @@ +[Constructor] +interface SeqAndRec { + void recordConsumer(record rec); + void recordConsumer2(record rec); + void sequenceConsumer(sequence seq); + void sequenceConsumer2(sequence seq); +}; diff --git a/test/cases/URL.idl b/test/cases/URL.idl index 4e8de98f..eb1efa84 100644 --- a/test/cases/URL.idl +++ b/test/cases/URL.idl @@ -13,4 +13,6 @@ interface URL { attribute USVString search; [SameObject] readonly attribute URLSearchParams searchParams; attribute USVString hash; + + USVString toJSON(); }; diff --git a/test/cases/URLSearchParams.idl b/test/cases/URLSearchParams.idl index 914d3b37..7fe4ce83 100644 --- a/test/cases/URLSearchParams.idl +++ b/test/cases/URLSearchParams.idl @@ -1,4 +1,4 @@ -[Constructor(optional (USVString or URLSearchParams) init = ""), +[Constructor(optional (sequence> or record or USVString) init = ""), Exposed=(Window,Worker)] interface URLSearchParams { void append(USVString name, USVString value); @@ -7,6 +7,9 @@ interface URLSearchParams { sequence getAll(USVString name); boolean has(USVString name); void set(USVString name, USVString value); + + void sort(); + iterable; stringifier; }; diff --git a/test/implementations/SeqAndRec.js b/test/implementations/SeqAndRec.js new file mode 100644 index 00000000..031e7d9e --- /dev/null +++ b/test/implementations/SeqAndRec.js @@ -0,0 +1,29 @@ +"use strict"; + +class SeqAndRecImpl { + constructor(args) {} + + recordConsumer(record) { + console.assert(Object.getPrototypeOf(record) === null); + for (const name in record) + console.log(name, JSON.stringify(record[name])); + } + + recordConsumer2(record) { + return this.recordConsumer(record); + } + + sequenceConsumer(arr) { + console.assert(Array.isArray(arr)); + for (const item of arr) + console.log(JSON.stringify(item)); + } + + sequenceConsumer2(arr) { + return this.sequenceConsumer(arr); + } +} + +module.exports = { + implementation: SeqAndRecImpl +}; diff --git a/test/implementations/URL.js b/test/implementations/URL.js index ecb60f68..520d21e9 100644 --- a/test/implementations/URL.js +++ b/test/implementations/URL.js @@ -1,2 +1,7 @@ "use strict"; -exports.implementation = class {}; + +class URLImpl {} + +module.exports = { + implementation: URLImpl +}; diff --git a/test/implementations/URLSearchParams.js b/test/implementations/URLSearchParams.js index ecb60f68..4989b4b1 100644 --- a/test/implementations/URLSearchParams.js +++ b/test/implementations/URLSearchParams.js @@ -1,2 +1,7 @@ "use strict"; -exports.implementation = class {}; + +class URLSearchParamsImpl {} + +module.exports = { + implementation: URLSearchParamsImpl +}; From 806a98d79865a0e766c79440873866c1525f7114 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Mon, 1 May 2017 23:30:28 -0700 Subject: [PATCH 2/9] Various cleanups --- lib/constructs/attribute.js | 7 +++-- lib/constructs/interface.js | 4 +-- lib/overloads.js | 3 +- lib/parameters.js | 59 +++++++++++++++++++------------------ lib/types.js | 28 +++++------------- 5 files changed, 47 insertions(+), 54 deletions(-) diff --git a/lib/constructs/attribute.js b/lib/constructs/attribute.js index 84e8fd38..aac47c4c 100644 --- a/lib/constructs/attribute.js +++ b/lib/constructs/attribute.js @@ -4,12 +4,13 @@ const conversions = require("webidl-conversions"); const utils = require("../utils"); const reflector = require("../reflector"); -const Parameters = require("../parameters"); +const Types = require("../types"); -function Attribute(obj, I, idl) { +function Attribute(obj, I, idl, opts) { this.obj = obj; this.interface = I; this.idl = idl; + this.opts = opts; } Attribute.prototype.generate = function () { @@ -54,7 +55,7 @@ Attribute.prototype.generate = function () { ${getterBody} },`; if (!this.idl.readonly) { - const conv = Parameters.generateVarConversion("V", { type: this.idl.idlType, optional: false }, this.idl.extAttrs, new Set()); + const conv = Types.generateTypeConversion("V", this.idl.idlType, this.idl.extAttrs, this.opts.customTypes); Object.assign(requires, conv.requires); let conversion = conv.body.replace(/\n/g, "\n "); str += ` diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index f5ba3b33..2553ebeb 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -252,7 +252,7 @@ Interface.prototype.generateIface = function () { for (let i = 0; i < this.idl.members.length; ++i) { const memberIdl = this.idl.members[i]; if (memberIdl.type === "attribute" && (utils.getExtAttr(memberIdl.extAttrs, "Unforgeable") || utils.isGlobal(this.idl))) { - const member = new Attribute(this, this.idl, memberIdl); + const member = new Attribute(this, this.idl, memberIdl, this.opts); this.str += "\n " + member.generate().body.replace(/\n/g, '\n '); } } @@ -345,7 +345,7 @@ Interface.prototype.generateAttributes = function () { if (utils.getExtAttr(memberIdl.extAttrs, "Unforgeable") || utils.isGlobal(this.idl)) { break; } - member = new Attribute(this, this.idl, memberIdl); + member = new Attribute(this, this.idl, memberIdl, this.opts); break; case "const": member = new Constant(this, this.idl, memberIdl); diff --git a/lib/overloads.js b/lib/overloads.js index 4e4f9f12..d2739fb4 100644 --- a/lib/overloads.js +++ b/lib/overloads.js @@ -93,6 +93,7 @@ module.exports.proveSimiliarity = function (overloads) { if (maybeType.type.idlType !== thisType.idlType || maybeType.type.array !== thisType.array || maybeType.default !== overloads[j].operation.arguments[i].default) { maybeType = null; + break; } } @@ -100,4 +101,4 @@ module.exports.proveSimiliarity = function (overloads) { } return typeConversions; -}; \ No newline at end of file +}; diff --git a/lib/parameters.js b/lib/parameters.js index 26781a05..40b066bb 100644 --- a/lib/parameters.js +++ b/lib/parameters.js @@ -5,42 +5,45 @@ const Types = require("./types"); const utils = require("./utils"); -module.exports.generateVarConversion = function (name, conversion, argAttrs, customTypes) { +function getDefault(dflt) { + switch (dflt.type) { + case "boolean": + case "number": + case "string": + return JSON.stringify(dflt.value); + case "null": + case "NaN": + return dflt.type; + case "Infinity": + return `${dflt.negative ? "-" : ""}Infinity`; + case "sequence": + return "[]"; + } + throw new Error("Unexpected default type: " + dflt.type); +} + +function generateVarConversion(name, conversion, argAttrs, customTypes) { const requires = {}; let str = ""; const idlType = conversion.type; - if (idlType.nullable) { + if (conversion.optional && !customTypes.has(idlType.idlType)) { // always (try to) force-convert dictionaries str += ` - if (${name} === null || ${name} === undefined) { - ${name} = null; - } else {`; +if (${name} !== undefined) {`; } - if (Types.canHandleType(idlType, customTypes)) { - if (conversion.optional && !customTypes.has(idlType.idlType)) { // always (try to) force-convert dictionaries - str += ` - if (${name} !== undefined) {`; - } - - const conv = Types.generateTypeConversion(name, idlType, argAttrs, customTypes); - Object.assign(requires, conv.requires); - str += conv.body; - - if (conversion.optional && !customTypes.has(idlType.idlType)) { - str += ` - }`; - if (conversion.default) { - str += ` else { - ${name} = ${JSON.stringify(conversion.default.value)}; - }`; - } - } - } + const conv = Types.generateTypeConversion(name, idlType, argAttrs, customTypes); + Object.assign(requires, conv.requires); + str += conv.body; - if (idlType.nullable) { + if (conversion.optional && !customTypes.has(idlType.idlType)) { str += ` - }`; +}`; + if (conversion.default) { + str += ` else { + ${name} = ${getDefault(conversion.default)}; +}`; + } } return { @@ -84,7 +87,7 @@ module.exports.generateOverloadConversions = function (overloads, customTypes) { continue; } - const conv = module.exports.generateVarConversion(`args[${i}]`, typeConversions[i], maxConstructor.operation.arguments[i].extAttrs, customTypes); + const conv = generateVarConversion(`args[${i}]`, typeConversions[i], maxConstructor.operation.arguments[i].extAttrs, customTypes); Object.assign(requires, conv.requires); str += conv.body; } diff --git a/lib/types.js b/lib/types.js index 102f4e74..2ab45e8a 100644 --- a/lib/types.js +++ b/lib/types.js @@ -4,14 +4,11 @@ const conversions = require("webidl-conversions"); const utils = require("./utils"); -function generateTypeConversion(name, idlType, argAttrs, customTypes, opts) { - opts = opts || {}; +function generateTypeConversion(name, idlType, argAttrs, customTypes) { const requires = {}; - const handleNullable = Boolean(opts.handleNullable); let str = ""; - - if (handleNullable && idlType.nullable) { + if (idlType.nullable) { str += ` if (${name} === null || ${name} === undefined) { ${name} = null; @@ -45,7 +42,7 @@ function generateTypeConversion(name, idlType, argAttrs, customTypes, opts) { ${name} = utils.tryImplForWrapper(${name});`; } - if (handleNullable && idlType.nullable) { + if (idlType.nullable) { str += ` }`; } @@ -64,9 +61,7 @@ function generateTypeConversion(name, idlType, argAttrs, customTypes, opts) { const tmp = ${name}; for (let nextItem of tmp) {`; - const conv = generateTypeConversion("nextItem", idlType.idlType, [], customTypes, { - handleNullable: true - }); + const conv = generateTypeConversion("nextItem", idlType.idlType, [], customTypes); Object.assign(requires, conv.requires); str += conv.body; @@ -78,7 +73,7 @@ function generateTypeConversion(name, idlType, argAttrs, customTypes, opts) { } function generateRecord() { - if (!handleNullable || !idlType.nullable) { + if (!idlType.nullable) { str += ` if (${name} == null) { ${name} = Object.create(null); @@ -99,9 +94,7 @@ function generateTypeConversion(name, idlType, argAttrs, customTypes, opts) { str += generateTypeConversion("typedKey", idlType.idlType[0], [], customTypes).body; - const conv = generateTypeConversion("typedValue", idlType.idlType[1], [], customTypes, { - handleNullable: true - }); + const conv = generateTypeConversion("typedValue", idlType.idlType[1], [], customTypes); Object.assign(requires, conv.requires); str += conv.body; @@ -112,7 +105,7 @@ function generateTypeConversion(name, idlType, argAttrs, customTypes, opts) { ${name} = result; }`; - if (!handleNullable || !idlType.nullable) { + if (!idlType.nullable) { str += ` }`; } @@ -160,11 +153,6 @@ function generateTypeConversion(name, idlType, argAttrs, customTypes, opts) { } } -function canHandleType(idlType, customTypes) { - return idlType.generic === "sequence" || idlType.generic === "record" || idlType.idlType in conversions || customTypes.has(idlType.idlType); -} - module.exports = { - generateTypeConversion, - canHandleType + generateTypeConversion }; From 8d25d1e59639a36749f56fccff0762c4f314941f Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Tue, 2 May 2017 00:08:56 -0700 Subject: [PATCH 3/9] Condense transformer-global data into a Context class --- lib/constructs/attribute.js | 6 ++--- lib/constructs/constant.js | 3 ++- lib/constructs/dictionary.js | 6 ++--- lib/constructs/interface.js | 17 +++++++------- lib/constructs/iterable.js | 4 ++-- lib/constructs/operation.js | 5 +++-- lib/context.js | 16 ++++++++++++++ lib/overloads.js | 2 +- lib/parameters.js | 12 +++++----- lib/transformer.js | 43 ++++++++++++++++++------------------ lib/types.js | 9 ++++---- 11 files changed, 72 insertions(+), 51 deletions(-) create mode 100644 lib/context.js diff --git a/lib/constructs/attribute.js b/lib/constructs/attribute.js index aac47c4c..cae0b56a 100644 --- a/lib/constructs/attribute.js +++ b/lib/constructs/attribute.js @@ -6,11 +6,11 @@ const utils = require("../utils"); const reflector = require("../reflector"); const Types = require("../types"); -function Attribute(obj, I, idl, opts) { +function Attribute(ctx, obj, I, idl) { + this.ctx = ctx; this.obj = obj; this.interface = I; this.idl = idl; - this.opts = opts; } Attribute.prototype.generate = function () { @@ -55,7 +55,7 @@ Attribute.prototype.generate = function () { ${getterBody} },`; if (!this.idl.readonly) { - const conv = Types.generateTypeConversion("V", this.idl.idlType, this.idl.extAttrs, this.opts.customTypes); + const conv = Types.generateTypeConversion(this.ctx, "V", this.idl.idlType, this.idl.extAttrs); Object.assign(requires, conv.requires); let conversion = conv.body.replace(/\n/g, "\n "); str += ` diff --git a/lib/constructs/constant.js b/lib/constructs/constant.js index ba618804..41537691 100644 --- a/lib/constructs/constant.js +++ b/lib/constructs/constant.js @@ -1,6 +1,7 @@ "use strict"; -function Constant(obj, I, idl) { +function Constant(ctx, obj, I, idl) { + this.ctx = ctx; this.obj = obj; this.interface = I; this.idl = idl; diff --git a/lib/constructs/dictionary.js b/lib/constructs/dictionary.js index b5ed9af7..b4e70aad 100644 --- a/lib/constructs/dictionary.js +++ b/lib/constructs/dictionary.js @@ -4,10 +4,10 @@ const Types = require("../types"); const utils = require("../utils"); class Dictionary { - constructor(idl, opts) { + constructor(ctx, idl) { + this.ctx = ctx; this.idl = idl; this.name = idl.name; - this.opts = opts; } _generateConversions() { @@ -33,7 +33,7 @@ class Dictionary { if (value !== undefined) {`; const argAttrs = field.extAttrs; - const conv = Types.generateTypeConversion("value", typeConversion, argAttrs, this.opts.customTypes); + const conv = Types.generateTypeConversion(this.ctx, "value", typeConversion, argAttrs); for (let key in conv.requires) { this.str = `const ${key} = ${conv.requires[key]};\n` + this.str; } diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index 2553ebeb..e162f5c7 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -11,7 +11,8 @@ const Overloads = require("../overloads"); const Parameters = require("../parameters"); const keywords = require("../keywords"); -function Interface(idl, opts) { +function Interface(ctx, idl, opts) { + this.ctx = ctx; this.idl = idl; this.name = idl.name; this.factory = !!utils.getExtAttr(this.idl.extAttrs, "WebIDL2JSFactory"); @@ -89,7 +90,7 @@ Interface.prototype.generateConstructor = function () { } } - const conversions = Parameters.generateOverloadConversions(overloads, this.opts.customTypes); + const conversions = Parameters.generateOverloadConversions(this.ctx, overloads); Object.assign(this.requires, conversions.requires); minConstructor.nameList = minConstructor.nameList.map((name) => (keywords.has(name) ? "_" : "") + name); @@ -252,7 +253,7 @@ Interface.prototype.generateIface = function () { for (let i = 0; i < this.idl.members.length; ++i) { const memberIdl = this.idl.members[i]; if (memberIdl.type === "attribute" && (utils.getExtAttr(memberIdl.extAttrs, "Unforgeable") || utils.isGlobal(this.idl))) { - const member = new Attribute(this, this.idl, memberIdl, this.opts); + const member = new Attribute(this.ctx, this, this.idl, memberIdl); this.str += "\n " + member.generate().body.replace(/\n/g, '\n '); } } @@ -284,7 +285,7 @@ Interface.prototype.generateIface = function () { this._internalSetup(obj);\n`; - const implClass = require(this.opts.implDir + "/" + this.name + this.opts.implSuffix); + const implClass = require(this.opts.implDir + "/" + this.name + this.ctx.implSuffix); this.str += ` Object.defineProperty(obj, impl, { value: new Impl.implementation(constructorArgs, privateData), @@ -313,14 +314,14 @@ Interface.prototype.generateOperations = function () { switch (memberIdl.type) { case "operation": - member = new Operation(this, this.idl, memberIdl, { customTypes: this.opts.customTypes }); + member = new Operation(this.ctx, this, this.idl, memberIdl); if (done[member.name]) { continue; } done[member.name] = true; break; case "iterable": - member = new Iterable(this, this.idl, memberIdl, { customTypes: this.opts.customTypes }); + member = new Iterable(this.ctx, this, this.idl, memberIdl); break; default: //throw new Error("Can't handle member of type '" + memberIdl.type + "'"); @@ -345,10 +346,10 @@ Interface.prototype.generateAttributes = function () { if (utils.getExtAttr(memberIdl.extAttrs, "Unforgeable") || utils.isGlobal(this.idl)) { break; } - member = new Attribute(this, this.idl, memberIdl, this.opts); + member = new Attribute(this.ctx, this, this.idl, memberIdl); break; case "const": - member = new Constant(this, this.idl, memberIdl); + member = new Constant(this.ctx, this, this.idl, memberIdl); break; default: //throw new Error("Can't handle member of type '" + memberIdl.type + "'"); diff --git a/lib/constructs/iterable.js b/lib/constructs/iterable.js index 1ddf2eda..f849fc3f 100644 --- a/lib/constructs/iterable.js +++ b/lib/constructs/iterable.js @@ -4,12 +4,12 @@ const conversions = require("webidl-conversions"); const keywords = require("../keywords"); const utils = require("../utils"); -function Iterable(obj, I, idl, opts) { +function Iterable(ctx, obj, I, idl) { + this.ctx = ctx; this.obj = obj; this.interface = I; this.idl = idl; this.name = idl.type; - this.opts = opts; } Iterable.prototype.generateFunction = function (key, kind, keyExpr, fnName) { diff --git a/lib/constructs/operation.js b/lib/constructs/operation.js index b9da03fe..ec683a78 100644 --- a/lib/constructs/operation.js +++ b/lib/constructs/operation.js @@ -5,7 +5,8 @@ const Overloads = require("../overloads"); const Parameters = require("../parameters"); const keywords = require("../keywords"); -function Operation(obj, I, idl) { +function Operation(ctx, obj, I, idl) { + this.ctx = ctx; this.obj = obj; this.interface = I; this.idl = idl; @@ -52,7 +53,7 @@ Operation.prototype.generate = function () { const callOn = this.idl.static ? "Impl" : "this[impl]"; - const parameterConversions = Parameters.generateOverloadConversions(overloads, this.obj.opts.customTypes); + const parameterConversions = Parameters.generateOverloadConversions(this.ctx, overloads); const argsSpread = parameterConversions.hasArgs ? "...args" : ""; Object.assign(requires, parameterConversions.requires); str += parameterConversions.body; diff --git a/lib/context.js b/lib/context.js new file mode 100644 index 00000000..b38f5240 --- /dev/null +++ b/lib/context.js @@ -0,0 +1,16 @@ +"use strict"; + +class Context { + constructor({ implSuffix = "" } = {}) { + this.implSuffix = implSuffix; + this.initialize(); + } + + initialize() { + this.customTypes = new Set(); + this.interfaces = Object.create(null); + this.dictionaries = Object.create(null); + } +} + +module.exports = Context; diff --git a/lib/overloads.js b/lib/overloads.js index d2739fb4..46a4cd1c 100644 --- a/lib/overloads.js +++ b/lib/overloads.js @@ -65,7 +65,7 @@ module.exports.getEffectiveOverloads = function (A, N, I, C) { return S; }; -module.exports.proveSimiliarity = function (overloads) { +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) { diff --git a/lib/parameters.js b/lib/parameters.js index 40b066bb..3887bbc1 100644 --- a/lib/parameters.js +++ b/lib/parameters.js @@ -22,7 +22,8 @@ function getDefault(dflt) { throw new Error("Unexpected default type: " + dflt.type); } -function generateVarConversion(name, conversion, argAttrs, customTypes) { +function generateVarConversion(ctx, name, conversion, argAttrs) { + const { customTypes } = ctx; const requires = {}; let str = ""; const idlType = conversion.type; @@ -32,7 +33,7 @@ function generateVarConversion(name, conversion, argAttrs, customTypes) { if (${name} !== undefined) {`; } - const conv = Types.generateTypeConversion(name, idlType, argAttrs, customTypes); + const conv = Types.generateTypeConversion(ctx, name, idlType, argAttrs); Object.assign(requires, conv.requires); str += conv.body; @@ -52,7 +53,8 @@ if (${name} !== undefined) {`; }; }; -module.exports.generateOverloadConversions = function (overloads, customTypes) { +module.exports.generateOverloadConversions = function (ctx, overloads) { + const { customTypes } = ctx; const requires = {}; let str = ``; let maxConstructor = overloads[0]; @@ -71,7 +73,7 @@ module.exports.generateOverloadConversions = function (overloads, customTypes) { } } - const typeConversions = Overloads.proveSimiliarity(overloads); + const typeConversions = Overloads.proveSimiliarity(ctx, overloads); const isAlwaysZeroArgs = !isVariadic && maxArguments === 0; if (!isAlwaysZeroArgs) { @@ -87,7 +89,7 @@ module.exports.generateOverloadConversions = function (overloads, customTypes) { continue; } - const conv = generateVarConversion(`args[${i}]`, typeConversions[i], maxConstructor.operation.arguments[i].extAttrs, customTypes); + const conv = generateVarConversion(ctx, `args[${i}]`, typeConversions[i], maxConstructor.operation.arguments[i].extAttrs); Object.assign(requires, conv.requires); str += conv.body; } diff --git a/lib/transformer.js b/lib/transformer.js index 5c84ad98..0d35aebd 100644 --- a/lib/transformer.js +++ b/lib/transformer.js @@ -7,20 +7,20 @@ const fs = require("pn/fs"); const webidl = require("webidl2"); const prettier = require("prettier"); +const Context = require("./context"); const Interface = require("./constructs/interface"); const Dictionary = require("./constructs/dictionary"); class Transformer { - constructor(opts) { + constructor(opts = {}) { + this.ctx = new Context({ + implSuffix: opts.implSuffix + }); this.options = Object.assign({ - implSuffix: "", suppressErrors: false }, opts); this.sources = []; - this._interfaces = null; - this._dictionaries = null; - this._customTypes = null; } addSource(idl, impl) { @@ -76,9 +76,8 @@ class Transformer { impl: content.impl })); - const interfaces = this._interfaces = Object.create(null); - const dictionaries = this._dictionaries = Object.create(null); - const customTypes = this._customTypes = new Set(); + this.ctx.initialize(); + const { interfaces, dictionaries, customTypes } = this.ctx; // first we're gathering all full interfaces and ignore partial ones for (const file of parsed) { @@ -90,10 +89,8 @@ class Transformer { break; } - obj = new Interface(instruction, { - implDir: path.resolve(outputDir, file.impl), - implSuffix: this.options.implSuffix, - customTypes + obj = new Interface(this.ctx, instruction, { + implDir: path.resolve(outputDir, file.impl) }); interfaces[obj.name] = obj; break; @@ -104,7 +101,7 @@ class Transformer { break; } - obj = new Dictionary(instruction, { customTypes }); + obj = new Dictionary(this.ctx, instruction); dictionaries[obj.name] = obj; customTypes.add(instruction.name); break; @@ -162,23 +159,25 @@ class Transformer { let utilsText = yield fs.readFile(__dirname + "/output/utils.js"); yield fs.writeFile(this.options.utilPath, utilsText); + const { interfaces, dictionaries } = this.ctx; + let interfaceStub = yield fs.readFile(__dirname + "/output/interfaceStub.js"); - let keys = Object.keys(this._interfaces).concat(Object.keys(this._dictionaries)); + let keys = Object.keys(interfaces).concat(Object.keys(dictionaries)); for (let key of keys) { - if (this._interfaces[key]) { - yield fs.writeFile(path.join(outputDir, this._interfaces[key].name + ".js"), interfaceStub); + if (interfaces[key]) { + yield fs.writeFile(path.join(outputDir, interfaces[key].name + ".js"), interfaceStub); } else { - yield fs.writeFile(path.join(outputDir, this._dictionaries[key].name + ".js"), ""); + yield fs.writeFile(path.join(outputDir, dictionaries[key].name + ".js"), ""); } } - keys = Object.keys(this._interfaces); + keys = Object.keys(interfaces); for (let i = 0; i < keys.length; ++i) { - const obj = this._interfaces[keys[i]]; + const obj = interfaces[keys[i]]; let source = obj.toString(); const implDir = path.relative(outputDir, obj.opts.implDir).replace(/\\/g, "/"); // fix windows file paths - let implFile = implDir + "/" + obj.name + this.options.implSuffix; + let implFile = implDir + "/" + obj.name + this.ctx.implSuffix; if (implFile[0] !== ".") { implFile = "./" + implFile; } @@ -199,9 +198,9 @@ const Impl = require("${implFile}.js");\n`; yield fs.writeFile(path.join(outputDir, obj.name + ".js"), source); } - keys = Object.keys(this._dictionaries); + keys = Object.keys(dictionaries); for (let i = 0; i < keys.length; ++i) { - const obj = this._dictionaries[keys[i]]; + const obj = dictionaries[keys[i]]; let source = obj.toString(); let relativeUtils = path.relative(outputDir, this.options.utilPath).replace(/\\/g, '/'); diff --git a/lib/types.js b/lib/types.js index 2ab45e8a..491333e6 100644 --- a/lib/types.js +++ b/lib/types.js @@ -4,7 +4,8 @@ const conversions = require("webidl-conversions"); const utils = require("./utils"); -function generateTypeConversion(name, idlType, argAttrs, customTypes) { +function generateTypeConversion(ctx, name, idlType, argAttrs) { + const { customTypes } = ctx; const requires = {}; let str = ""; @@ -61,7 +62,7 @@ function generateTypeConversion(name, idlType, argAttrs, customTypes) { const tmp = ${name}; for (let nextItem of tmp) {`; - const conv = generateTypeConversion("nextItem", idlType.idlType, [], customTypes); + const conv = generateTypeConversion(ctx, "nextItem", idlType.idlType, []); Object.assign(requires, conv.requires); str += conv.body; @@ -92,9 +93,9 @@ function generateTypeConversion(name, idlType, argAttrs, customTypes) { let typedKey = key; let typedValue = ${name}[key];`; - str += generateTypeConversion("typedKey", idlType.idlType[0], [], customTypes).body; + str += generateTypeConversion(ctx, "typedKey", idlType.idlType[0], []).body; - const conv = generateTypeConversion("typedValue", idlType.idlType[1], [], customTypes); + const conv = generateTypeConversion(ctx, "typedValue", idlType.idlType[1], []); Object.assign(requires, conv.requires); str += conv.body; From 03c53a3bf3cd7ec1d70c49abad7abc622c42e267 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Tue, 2 May 2017 01:10:21 -0700 Subject: [PATCH 4/9] Treat interfaces as first-class types Throw a TypeError when the object that is supposed to implement an interface does not. --- lib/constructs/attribute.js | 5 ++--- lib/constructs/dictionary.js | 2 +- lib/constructs/interface.js | 12 +++++++++++- lib/constructs/operation.js | 2 +- lib/context.js | 2 +- lib/parameters.js | 12 ++++++------ lib/transformer.js | 3 ++- lib/types.js | 26 +++++++++++++------------- 8 files changed, 37 insertions(+), 27 deletions(-) diff --git a/lib/constructs/attribute.js b/lib/constructs/attribute.js index cae0b56a..84485412 100644 --- a/lib/constructs/attribute.js +++ b/lib/constructs/attribute.js @@ -28,10 +28,9 @@ Attribute.prototype.generate = function () { definedOn = `obj`; } let getterBody = `return utils.tryWrapperForImpl(${objName}[impl].${this.idl.name});`; - let setterBody = `${objName}[impl].${this.idl.name} = utils.tryImplForWrapper(V);`; + let setterBody = `${objName}[impl].${this.idl.name} = V;`; if (conversions[this.idl.idlType.idlType]) { getterBody = `return ${objName}[impl].${this.idl.name};`; - setterBody = `${objName}[impl].${this.idl.name} = V;`; } if (this.idl.static) { @@ -55,7 +54,7 @@ Attribute.prototype.generate = function () { ${getterBody} },`; if (!this.idl.readonly) { - const conv = Types.generateTypeConversion(this.ctx, "V", this.idl.idlType, this.idl.extAttrs); + const conv = Types.generateTypeConversion(this.ctx, "V", this.idl.idlType, this.idl.extAttrs, this.interface.name); Object.assign(requires, conv.requires); let conversion = conv.body.replace(/\n/g, "\n "); str += ` diff --git a/lib/constructs/dictionary.js b/lib/constructs/dictionary.js index b4e70aad..f5cb773f 100644 --- a/lib/constructs/dictionary.js +++ b/lib/constructs/dictionary.js @@ -33,7 +33,7 @@ class Dictionary { if (value !== undefined) {`; const argAttrs = field.extAttrs; - const conv = Types.generateTypeConversion(this.ctx, "value", typeConversion, argAttrs); + const conv = Types.generateTypeConversion(this.ctx, "value", typeConversion, argAttrs, this.name); for (let key in conv.requires) { this.str = `const ${key} = ${conv.requires[key]};\n` + this.str; } diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index e162f5c7..d0e8a70e 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -90,7 +90,7 @@ Interface.prototype.generateConstructor = function () { } } - const conversions = Parameters.generateOverloadConversions(this.ctx, overloads); + const conversions = Parameters.generateOverloadConversions(this.ctx, overloads, this.name); Object.assign(this.requires, conversions.requires); minConstructor.nameList = minConstructor.nameList.map((name) => (keywords.has(name) ? "_" : "") + name); @@ -185,6 +185,16 @@ Interface.prototype.generateExport = function () { } } return false; + }, + convert(obj) { + // One day... one day... we'll be able to get rid of this branch... + if (module.exports.isImpl(obj)) { + return obj; + } + if (module.exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError("The value provided is not a ${this.name}"); },`; if (this.hasPairIterator) { diff --git a/lib/constructs/operation.js b/lib/constructs/operation.js index ec683a78..9949ed24 100644 --- a/lib/constructs/operation.js +++ b/lib/constructs/operation.js @@ -53,7 +53,7 @@ Operation.prototype.generate = function () { const callOn = this.idl.static ? "Impl" : "this[impl]"; - const parameterConversions = Parameters.generateOverloadConversions(this.ctx, overloads); + const parameterConversions = Parameters.generateOverloadConversions(this.ctx, overloads, this.interface.name); const argsSpread = parameterConversions.hasArgs ? "...args" : ""; Object.assign(requires, parameterConversions.requires); str += parameterConversions.body; diff --git a/lib/context.js b/lib/context.js index b38f5240..56993d73 100644 --- a/lib/context.js +++ b/lib/context.js @@ -7,7 +7,7 @@ class Context { } initialize() { - this.customTypes = new Set(); + this.customTypes = new Map(); this.interfaces = Object.create(null); this.dictionaries = Object.create(null); } diff --git a/lib/parameters.js b/lib/parameters.js index 3887bbc1..110c6b41 100644 --- a/lib/parameters.js +++ b/lib/parameters.js @@ -22,22 +22,22 @@ function getDefault(dflt) { throw new Error("Unexpected default type: " + dflt.type); } -function generateVarConversion(ctx, name, conversion, argAttrs) { +function generateVarConversion(ctx, name, conversion, argAttrs, parentName) { const { customTypes } = ctx; const requires = {}; let str = ""; const idlType = conversion.type; - if (conversion.optional && !customTypes.has(idlType.idlType)) { // always (try to) force-convert dictionaries + if (conversion.optional && customTypes.get(idlType.idlType) !== "dictionary") { // always (try to) force-convert dictionaries str += ` if (${name} !== undefined) {`; } - const conv = Types.generateTypeConversion(ctx, name, idlType, argAttrs); + const conv = Types.generateTypeConversion(ctx, name, idlType, argAttrs, parentName); Object.assign(requires, conv.requires); str += conv.body; - if (conversion.optional && !customTypes.has(idlType.idlType)) { + if (conversion.optional && customTypes.get(idlType.idlType) !== "dictionary") { str += ` }`; if (conversion.default) { @@ -53,7 +53,7 @@ if (${name} !== undefined) {`; }; }; -module.exports.generateOverloadConversions = function (ctx, overloads) { +module.exports.generateOverloadConversions = function (ctx, overloads, parentName) { const { customTypes } = ctx; const requires = {}; let str = ``; @@ -89,7 +89,7 @@ module.exports.generateOverloadConversions = function (ctx, overloads) { continue; } - const conv = generateVarConversion(ctx, `args[${i}]`, typeConversions[i], maxConstructor.operation.arguments[i].extAttrs); + const conv = generateVarConversion(ctx, `args[${i}]`, typeConversions[i], maxConstructor.operation.arguments[i].extAttrs, parentName); Object.assign(requires, conv.requires); str += conv.body; } diff --git a/lib/transformer.js b/lib/transformer.js index 0d35aebd..0dd516c9 100644 --- a/lib/transformer.js +++ b/lib/transformer.js @@ -93,6 +93,7 @@ class Transformer { implDir: path.resolve(outputDir, file.impl) }); interfaces[obj.name] = obj; + customTypes.set(obj.name, "interface"); break; case "implements": break; // handled later @@ -103,7 +104,7 @@ class Transformer { obj = new Dictionary(this.ctx, instruction); dictionaries[obj.name] = obj; - customTypes.add(instruction.name); + customTypes.set(obj.name, "dictionary"); break; default: if (!this.options.suppressErrors) { diff --git a/lib/types.js b/lib/types.js index 491333e6..8d38c706 100644 --- a/lib/types.js +++ b/lib/types.js @@ -4,7 +4,7 @@ const conversions = require("webidl-conversions"); const utils = require("./utils"); -function generateTypeConversion(ctx, name, idlType, argAttrs) { +function generateTypeConversion(ctx, name, idlType, argAttrs, parentName) { const { customTypes } = ctx; const requires = {}; let str = ""; @@ -33,9 +33,15 @@ function generateTypeConversion(ctx, name, idlType, argAttrs) { generateGeneric(`conversions["${idlType.idlType}"]`); } else if (customTypes.has(idlType.idlType)) { // dictionaries or interfaces - const varName = `convert${idlType.idlType}`; - requires[varName] = `require("./${idlType.idlType}").convert`; - generateGeneric(varName); + let fn; + // Avoid requiring the interface itself + if (idlType.idlType !== parentName) { + fn = `convert${idlType.idlType}`; + requires[fn] = `require("./${idlType.idlType}").convert`; + } else { + fn = `module.exports.convert`; + } + generateGeneric(fn); } else { // unknown // Try to get the impl anyway. @@ -62,7 +68,7 @@ function generateTypeConversion(ctx, name, idlType, argAttrs) { const tmp = ${name}; for (let nextItem of tmp) {`; - const conv = generateTypeConversion(ctx, "nextItem", idlType.idlType, []); + const conv = generateTypeConversion(ctx, "nextItem", idlType.idlType, [], parentName); Object.assign(requires, conv.requires); str += conv.body; @@ -93,9 +99,9 @@ function generateTypeConversion(ctx, name, idlType, argAttrs) { let typedKey = key; let typedValue = ${name}[key];`; - str += generateTypeConversion(ctx, "typedKey", idlType.idlType[0], []).body; + str += generateTypeConversion(ctx, "typedKey", idlType.idlType[0], [], parentName).body; - const conv = generateTypeConversion(ctx, "typedValue", idlType.idlType[1], []); + const conv = generateTypeConversion(ctx, "typedValue", idlType.idlType[1], [], parentName); Object.assign(requires, conv.requires); str += conv.body; @@ -136,12 +142,6 @@ function generateTypeConversion(ctx, name, idlType, argAttrs) { } else if (treatNullAs && treatNullAs.rhs.value === "EmptyString") { optString = `, { treatNullAsEmptyString: true }`; } - if (conversions[idlType.idlType]) { - conversionFn = `conversions["${idlType.idlType}"]`; - } else { - requires[`convert${idlType.idlType}`] = `require("./${idlType.idlType}").convert`; - conversionFn = `convert${idlType.idlType}`; - } if (idlType.array) { str += ` for (let i = 0; i < ${name}.length; ++i) { From 3b1cdf884ecad15358de77e10aba2c8366b148f2 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Tue, 2 May 2017 02:05:03 -0700 Subject: [PATCH 5/9] Only unwrap arguments when necessary --- lib/constructs/interface.js | 4 ---- lib/parameters.js | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index d0e8a70e..55742725 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -187,10 +187,6 @@ Interface.prototype.generateExport = function () { return false; }, convert(obj) { - // One day... one day... we'll be able to get rid of this branch... - if (module.exports.isImpl(obj)) { - return obj; - } if (module.exports.is(obj)) { return utils.implForWrapper(obj); } diff --git a/lib/parameters.js b/lib/parameters.js index 110c6b41..42762cd3 100644 --- a/lib/parameters.js +++ b/lib/parameters.js @@ -81,7 +81,7 @@ module.exports.generateOverloadConversions = function (ctx, overloads, parentNam str += `\n const args = []; for (let i = 0; i < arguments.length${extraClause}; ++i) { - args[i] = utils.tryImplForWrapper(arguments[i]); + args[i] = arguments[i]; }`; for (let i = 0; i < typeConversions.length; ++i) { From 820df70d8768eadda9a8cd86cfb24e7a74ef84c4 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Tue, 2 May 2017 03:34:15 -0700 Subject: [PATCH 6/9] Add error context Also eliminate outdated dictionary error for Date and RegExp. --- lib/constructs/attribute.js | 2 +- lib/constructs/dictionary.js | 15 ++++++--------- lib/constructs/interface.js | 6 +++--- lib/constructs/operation.js | 2 +- lib/parameters.js | 8 ++++---- lib/types.js | 21 +++++++++++---------- 6 files changed, 26 insertions(+), 28 deletions(-) diff --git a/lib/constructs/attribute.js b/lib/constructs/attribute.js index 84485412..83cd4c05 100644 --- a/lib/constructs/attribute.js +++ b/lib/constructs/attribute.js @@ -54,7 +54,7 @@ Attribute.prototype.generate = function () { ${getterBody} },`; if (!this.idl.readonly) { - const conv = Types.generateTypeConversion(this.ctx, "V", this.idl.idlType, this.idl.extAttrs, this.interface.name); + const conv = Types.generateTypeConversion(this.ctx, "V", this.idl.idlType, this.idl.extAttrs, this.interface.name, `"Failed to set the '${this.idl.name}' property on '${this.interface.name}': The provided value"`); Object.assign(requires, conv.requires); let conversion = conv.body.replace(/\n/g, "\n "); str += ` diff --git a/lib/constructs/dictionary.js b/lib/constructs/dictionary.js index f5cb773f..1aa94624 100644 --- a/lib/constructs/dictionary.js +++ b/lib/constructs/dictionary.js @@ -33,7 +33,7 @@ class Dictionary { if (value !== undefined) {`; const argAttrs = field.extAttrs; - const conv = Types.generateTypeConversion(this.ctx, "value", typeConversion, argAttrs, this.name); + const conv = Types.generateTypeConversion(this.ctx, "value", typeConversion, argAttrs, this.name, `\`\${context} has member ${field.name} that\``); for (let key in conv.requires) { this.str = `const ${key} = ${conv.requires[key]};\n` + this.str; } @@ -63,26 +63,23 @@ class Dictionary { generate() { this.str += ` module.exports = { - convertInherit(obj, ret) {`; + convertInherit(obj, ret, { context = "The provided value" } = {}) {`; if (this.idl.inheritance) { this.str = `const ${this.idl.inheritance} = require("./${this.idl.inheritance}");\n` + this.str; this.str += ` - ${this.idl.inheritance}.convertInherit(obj, ret);`; + ${this.idl.inheritance}.convertInherit(obj, ret, { context });`; } this._generateConversions(); this.str += ` }, - convert(obj) { + convert(obj, { context = "The provided value" } = {}) { if (obj !== undefined && typeof obj !== "object") { - throw new TypeError("Dictionary has to be an object"); - } - if (obj instanceof Date || obj instanceof RegExp) { - throw new TypeError("Dictionary may not be a Date or RegExp object"); + throw new TypeError(\`\${context} is not an object.\`); } const ret = Object.create(null); - module.exports.convertInherit(obj, ret); + module.exports.convertInherit(obj, ret, { context }); return ret; } };`; diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index 55742725..d97f647b 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -90,7 +90,7 @@ Interface.prototype.generateConstructor = function () { } } - const conversions = Parameters.generateOverloadConversions(this.ctx, overloads, this.name); + const conversions = Parameters.generateOverloadConversions(this.ctx, overloads, this.name, `Failed to construct '${this.name}': `); Object.assign(this.requires, conversions.requires); minConstructor.nameList = minConstructor.nameList.map((name) => (keywords.has(name) ? "_" : "") + name); @@ -186,11 +186,11 @@ Interface.prototype.generateExport = function () { } return false; }, - convert(obj) { + convert(obj, { context = "The provided value" } = {}) { if (module.exports.is(obj)) { return utils.implForWrapper(obj); } - throw new TypeError("The value provided is not a ${this.name}"); + throw new TypeError(\`\${context} is not of type '${this.name}'.\`); },`; if (this.hasPairIterator) { diff --git a/lib/constructs/operation.js b/lib/constructs/operation.js index 9949ed24..db19e3ad 100644 --- a/lib/constructs/operation.js +++ b/lib/constructs/operation.js @@ -53,7 +53,7 @@ Operation.prototype.generate = function () { const callOn = this.idl.static ? "Impl" : "this[impl]"; - const parameterConversions = Parameters.generateOverloadConversions(this.ctx, overloads, this.interface.name); + const parameterConversions = Parameters.generateOverloadConversions(this.ctx, overloads, this.interface.name, `Failed to execute '${name}' on '${this.obj.name}': `); const argsSpread = parameterConversions.hasArgs ? "...args" : ""; Object.assign(requires, parameterConversions.requires); str += parameterConversions.body; diff --git a/lib/parameters.js b/lib/parameters.js index 42762cd3..31817a9b 100644 --- a/lib/parameters.js +++ b/lib/parameters.js @@ -22,7 +22,7 @@ function getDefault(dflt) { throw new Error("Unexpected default type: " + dflt.type); } -function generateVarConversion(ctx, name, conversion, argAttrs, parentName) { +function generateVarConversion(ctx, name, conversion, argAttrs, ...typeArgs) { const { customTypes } = ctx; const requires = {}; let str = ""; @@ -33,7 +33,7 @@ function generateVarConversion(ctx, name, conversion, argAttrs, parentName) { if (${name} !== undefined) {`; } - const conv = Types.generateTypeConversion(ctx, name, idlType, argAttrs, parentName); + const conv = Types.generateTypeConversion(ctx, name, idlType, argAttrs, ...typeArgs); Object.assign(requires, conv.requires); str += conv.body; @@ -53,7 +53,7 @@ if (${name} !== undefined) {`; }; }; -module.exports.generateOverloadConversions = function (ctx, overloads, parentName) { +module.exports.generateOverloadConversions = function (ctx, overloads, parentName, errPrefix) { const { customTypes } = ctx; const requires = {}; let str = ``; @@ -89,7 +89,7 @@ module.exports.generateOverloadConversions = function (ctx, overloads, parentNam continue; } - const conv = generateVarConversion(ctx, `args[${i}]`, typeConversions[i], maxConstructor.operation.arguments[i].extAttrs, parentName); + const conv = generateVarConversion(ctx, `args[${i}]`, typeConversions[i], maxConstructor.operation.arguments[i].extAttrs, parentName, `"${errPrefix}parameter ${i + 1}"`); Object.assign(requires, conv.requires); str += conv.body; } diff --git a/lib/types.js b/lib/types.js index 8d38c706..5c98367d 100644 --- a/lib/types.js +++ b/lib/types.js @@ -4,7 +4,7 @@ const conversions = require("webidl-conversions"); const utils = require("./utils"); -function generateTypeConversion(ctx, name, idlType, argAttrs, parentName) { +function generateTypeConversion(ctx, name, idlType, argAttrs, parentName, errPrefix = '"The provided value"') { const { customTypes } = ctx; const requires = {}; let str = ""; @@ -62,13 +62,13 @@ function generateTypeConversion(ctx, name, idlType, argAttrs, parentName) { function generateSequence() { str += ` if (typeof ${name} !== "object") { - throw new TypeError("The value provided is not an iterable object"); + throw new TypeError(${errPrefix} + " is not an iterable object."); } else { const V = []; const tmp = ${name}; for (let nextItem of tmp) {`; - const conv = generateTypeConversion(ctx, "nextItem", idlType.idlType, [], parentName); + const conv = generateTypeConversion(ctx, "nextItem", idlType.idlType, [], parentName, `${errPrefix} + "'s element"`); Object.assign(requires, conv.requires); str += conv.body; @@ -89,7 +89,7 @@ function generateTypeConversion(ctx, name, idlType, argAttrs, parentName) { str += ` if (typeof ${name} !== "object") { - throw new TypeError("The value provided is not an object"); + throw new TypeError(${errPrefix} + " is not an object."); } else { const result = Object.create(null); const keys = Object.getOwnPropertyNames(${name}); @@ -99,9 +99,9 @@ function generateTypeConversion(ctx, name, idlType, argAttrs, parentName) { let typedKey = key; let typedValue = ${name}[key];`; - str += generateTypeConversion(ctx, "typedKey", idlType.idlType[0], [], parentName).body; + str += generateTypeConversion(ctx, "typedKey", idlType.idlType[0], [], parentName, `${errPrefix} + "'s key"`).body; - const conv = generateTypeConversion(ctx, "typedValue", idlType.idlType[1], [], parentName); + const conv = generateTypeConversion(ctx, "typedValue", idlType.idlType[1], [], parentName, `${errPrefix} + "'s value"`); Object.assign(requires, conv.requires); str += conv.body; @@ -134,14 +134,15 @@ function generateTypeConversion(ctx, name, idlType, argAttrs, parentName) { const clamp = utils.getExtAttr(argAttrs, "Clamp"); const treatNullAs = utils.getExtAttr(argAttrs, "TreatNullAs"); - let optString = ""; + let optString = `, { context: ${errPrefix}`; if (clamp) { - optString = `, { clamp: true }`; + optString += ", clamp: true"; } else if (enforceRange) { - optString = `, { enforceRange: true }`; + optString += ", enforceRange: true"; } else if (treatNullAs && treatNullAs.rhs.value === "EmptyString") { - optString = `, { treatNullAsEmptyString: true }`; + optString += ", treatNullAsEmptyString: true"; } + optString += " }"; if (idlType.array) { str += ` for (let i = 0; i < ${name}.length; ++i) { From 2e9c5696279d83d1821d7687faab4653a6caac82 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sat, 6 May 2017 15:57:30 -0700 Subject: [PATCH 7/9] Better promise support --- lib/types.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/types.js b/lib/types.js index 5c98367d..6fcaf9e0 100644 --- a/lib/types.js +++ b/lib/types.js @@ -120,7 +120,18 @@ function generateTypeConversion(ctx, name, idlType, argAttrs, parentName, errPre function generatePromise() { str += ` - ${name} = Promise.resolve(${name});`; + ${name} = Promise.resolve(${name}).then(value => {` + if (idlType.idlType.idlType === "void") { + // Do nothing. + } else { + const conv = generateTypeConversion(ctx, "value", idlType.idlType, [], parentName, `${errPrefix} + " promise value"`); + Object.assign(requires, conv.requires); + str += conv.body; + str += ` + return value;`; + } + str += ` + }, reason => reason);`; } function generateFrozenArray() { From b6919354f0f6f882e6f04da3af245cb66990746e Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Mon, 8 May 2017 11:34:54 -0700 Subject: [PATCH 8/9] Add and update test snapshots --- test/__snapshots__/test.js.snap | 515 +++++++++++++++++++++++++-- test/cases/PromiseTypes.idl | 4 + test/cases/SeqAndRec.idl | 5 +- test/implementations/PromiseTypes.js | 3 + 4 files changed, 497 insertions(+), 30 deletions(-) create mode 100644 test/cases/PromiseTypes.idl create mode 100644 test/implementations/PromiseTypes.js diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 4e27f82a..17c24a69 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -1,5 +1,420 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`PromiseTypes.idl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); +const impl = utils.implSymbol; + +function PromiseTypes() { + throw new TypeError(\\"Illegal constructor\\"); +} + +PromiseTypes.prototype.voidPromiseConsumer = function voidPromiseConsumer(p) { + if (!this || !module.exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + if (arguments.length < 1) { + throw new TypeError( + \\"Failed to execute 'voidPromiseConsumer' on 'PromiseTypes': 1 argument required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + + const args = []; + for (let i = 0; i < arguments.length && i < 1; ++i) { + args[i] = arguments[i]; + } + args[0] = Promise.resolve(args[0]).then(value => {}, reason => reason); + return this[impl].voidPromiseConsumer(...args); +}; + +PromiseTypes.prototype.promiseConsumer = function promiseConsumer(p) { + if (!this || !module.exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + if (arguments.length < 1) { + throw new TypeError( + \\"Failed to execute 'promiseConsumer' on 'PromiseTypes': 1 argument required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + + const args = []; + for (let i = 0; i < arguments.length && i < 1; ++i) { + args[i] = arguments[i]; + } + args[0] = Promise.resolve(args[0]).then(value => { + value = conversions[\\"double\\"](value, { + context: \\"Failed to execute 'promiseConsumer' on 'PromiseTypes': parameter 1\\" + \\" promise value\\" + }); + return value; + }, reason => reason); + return this[impl].promiseConsumer(...args); +}; + +Object.defineProperty(PromiseTypes.prototype, Symbol.toStringTag, { + value: \\"PromiseTypes\\", + writable: false, + enumerable: false, + configurable: true +}); + +const iface = { + mixedInto: [], + is(obj) { + if (obj) { + if (obj[impl] instanceof Impl.implementation) { + return true; + } + for (let i = 0; i < module.exports.mixedInto.length; ++i) { + if (obj instanceof module.exports.mixedInto[i]) { + return true; + } + } + } + return false; + }, + isImpl(obj) { + if (obj) { + if (obj instanceof Impl.implementation) { + return true; + } + + const wrapper = utils.wrapperForImpl(obj); + for (let i = 0; i < module.exports.mixedInto.length; ++i) { + if (wrapper instanceof module.exports.mixedInto[i]) { + return true; + } + } + } + return false; + }, + convert(obj, { context = \\"The provided value\\" } = {}) { + if (module.exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError(\`\${context} is not of type 'PromiseTypes'.\`); + }, + create(constructorArgs, privateData) { + let obj = Object.create(PromiseTypes.prototype); + this.setup(obj, constructorArgs, privateData); + return obj; + }, + createImpl(constructorArgs, privateData) { + let obj = Object.create(PromiseTypes.prototype); + this.setup(obj, constructorArgs, privateData); + return utils.implForWrapper(obj); + }, + _internalSetup(obj) {}, + setup(obj, constructorArgs, privateData) { + if (!privateData) privateData = {}; + privateData.wrapper = obj; + + this._internalSetup(obj); + + Object.defineProperty(obj, impl, { + value: new Impl.implementation(constructorArgs, privateData), + writable: false, + enumerable: false, + configurable: true + }); + obj[impl][utils.wrapperSymbol] = obj; + }, + interface: PromiseTypes, + expose: { + Window: { PromiseTypes: PromiseTypes } + } +}; +module.exports = iface; + +const Impl = require(\\"../implementations/PromiseTypes.js\\"); +" +`; + +exports[`SeqAndRec.idl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); +const impl = utils.implSymbol; +const convertURL = require(\\"./URL\\").convert; + +function SeqAndRec() { + iface.setup(this); +} + +SeqAndRec.prototype.recordConsumer = function recordConsumer(rec) { + if (!this || !module.exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + if (arguments.length < 1) { + throw new TypeError( + \\"Failed to execute 'recordConsumer' on 'SeqAndRec': 1 argument required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + + const args = []; + for (let i = 0; i < arguments.length && i < 1; ++i) { + args[i] = arguments[i]; + } + if (args[0] == null) { + args[0] = Object.create(null); + } else { + if (typeof args[0] !== \\"object\\") { + throw new TypeError(\\"Failed to execute 'recordConsumer' on 'SeqAndRec': parameter 1\\" + \\" is not an object.\\"); + } else { + const result = Object.create(null); + const keys = Object.getOwnPropertyNames(args[0]); + for (let key of keys) { + const desc = Object.getOwnPropertyDescriptor(args[0], key); + if (desc && desc.enumerable) { + let typedKey = key; + let typedValue = args[0][key]; + typedKey = conversions[\\"USVString\\"](typedKey, { + context: \\"Failed to execute 'recordConsumer' on 'SeqAndRec': parameter 1\\" + \\"'s key\\" + }); + typedValue = conversions[\\"double\\"](typedValue, { + context: \\"Failed to execute 'recordConsumer' on 'SeqAndRec': parameter 1\\" + \\"'s value\\" + }); + result[typedKey] = typedValue; + } + } + args[0] = result; + } + } + return this[impl].recordConsumer(...args); +}; + +SeqAndRec.prototype.recordConsumer2 = function recordConsumer2(rec) { + if (!this || !module.exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + if (arguments.length < 1) { + throw new TypeError( + \\"Failed to execute 'recordConsumer2' on 'SeqAndRec': 1 argument required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + + const args = []; + for (let i = 0; i < arguments.length && i < 1; ++i) { + args[i] = arguments[i]; + } + if (args[0] == null) { + args[0] = Object.create(null); + } else { + if (typeof args[0] !== \\"object\\") { + throw new TypeError(\\"Failed to execute 'recordConsumer2' on 'SeqAndRec': parameter 1\\" + \\" is not an object.\\"); + } else { + const result = Object.create(null); + const keys = Object.getOwnPropertyNames(args[0]); + for (let key of keys) { + const desc = Object.getOwnPropertyDescriptor(args[0], key); + if (desc && desc.enumerable) { + let typedKey = key; + let typedValue = args[0][key]; + typedKey = conversions[\\"USVString\\"](typedKey, { + context: \\"Failed to execute 'recordConsumer2' on 'SeqAndRec': parameter 1\\" + \\"'s key\\" + }); + typedValue = convertURL(typedValue, { + context: \\"Failed to execute 'recordConsumer2' on 'SeqAndRec': parameter 1\\" + \\"'s value\\" + }); + result[typedKey] = typedValue; + } + } + args[0] = result; + } + } + return this[impl].recordConsumer2(...args); +}; + +SeqAndRec.prototype.sequenceConsumer = function sequenceConsumer(seq) { + if (!this || !module.exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + if (arguments.length < 1) { + throw new TypeError( + \\"Failed to execute 'sequenceConsumer' on 'SeqAndRec': 1 argument required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + + const args = []; + for (let i = 0; i < arguments.length && i < 1; ++i) { + args[i] = arguments[i]; + } + if (typeof args[0] !== \\"object\\") { + throw new TypeError( + \\"Failed to execute 'sequenceConsumer' on 'SeqAndRec': parameter 1\\" + \\" is not an iterable object.\\" + ); + } else { + const V = []; + const tmp = args[0]; + for (let nextItem of tmp) { + nextItem = conversions[\\"USVString\\"](nextItem, { + context: \\"Failed to execute 'sequenceConsumer' on 'SeqAndRec': parameter 1\\" + \\"'s element\\" + }); + V.push(nextItem); + } + args[0] = V; + } + return this[impl].sequenceConsumer(...args); +}; + +SeqAndRec.prototype.sequenceConsumer2 = function sequenceConsumer2(seq) { + if (!this || !module.exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + if (arguments.length < 1) { + throw new TypeError( + \\"Failed to execute 'sequenceConsumer2' on 'SeqAndRec': 1 argument required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + + const args = []; + for (let i = 0; i < arguments.length && i < 1; ++i) { + args[i] = arguments[i]; + } + if (typeof args[0] !== \\"object\\") { + throw new TypeError( + \\"Failed to execute 'sequenceConsumer2' on 'SeqAndRec': parameter 1\\" + \\" is not an iterable object.\\" + ); + } else { + const V = []; + const tmp = args[0]; + for (let nextItem of tmp) { + nextItem = utils.tryImplForWrapper(nextItem); + V.push(nextItem); + } + args[0] = V; + } + return this[impl].sequenceConsumer2(...args); +}; + +SeqAndRec.prototype.frozenArrayConsumer = function frozenArrayConsumer(arr) { + if (!this || !module.exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + if (arguments.length < 1) { + throw new TypeError( + \\"Failed to execute 'frozenArrayConsumer' on 'SeqAndRec': 1 argument required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + + const args = []; + for (let i = 0; i < arguments.length && i < 1; ++i) { + args[i] = arguments[i]; + } + if (typeof args[0] !== \\"object\\") { + throw new TypeError( + \\"Failed to execute 'frozenArrayConsumer' on 'SeqAndRec': parameter 1\\" + \\" is not an iterable object.\\" + ); + } else { + const V = []; + const tmp = args[0]; + for (let nextItem of tmp) { + nextItem = conversions[\\"double\\"](nextItem, { + context: \\"Failed to execute 'frozenArrayConsumer' on 'SeqAndRec': parameter 1\\" + \\"'s element\\" + }); + V.push(nextItem); + } + args[0] = V; + } + args[0] = Object.freeze(args[0]); + return this[impl].frozenArrayConsumer(...args); +}; + +Object.defineProperty(SeqAndRec.prototype, Symbol.toStringTag, { + value: \\"SeqAndRec\\", + writable: false, + enumerable: false, + configurable: true +}); + +const iface = { + mixedInto: [], + is(obj) { + if (obj) { + if (obj[impl] instanceof Impl.implementation) { + return true; + } + for (let i = 0; i < module.exports.mixedInto.length; ++i) { + if (obj instanceof module.exports.mixedInto[i]) { + return true; + } + } + } + return false; + }, + isImpl(obj) { + if (obj) { + if (obj instanceof Impl.implementation) { + return true; + } + + const wrapper = utils.wrapperForImpl(obj); + for (let i = 0; i < module.exports.mixedInto.length; ++i) { + if (wrapper instanceof module.exports.mixedInto[i]) { + return true; + } + } + } + return false; + }, + convert(obj, { context = \\"The provided value\\" } = {}) { + if (module.exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError(\`\${context} is not of type 'SeqAndRec'.\`); + }, + create(constructorArgs, privateData) { + let obj = Object.create(SeqAndRec.prototype); + this.setup(obj, constructorArgs, privateData); + return obj; + }, + createImpl(constructorArgs, privateData) { + let obj = Object.create(SeqAndRec.prototype); + this.setup(obj, constructorArgs, privateData); + return utils.implForWrapper(obj); + }, + _internalSetup(obj) {}, + setup(obj, constructorArgs, privateData) { + if (!privateData) privateData = {}; + privateData.wrapper = obj; + + this._internalSetup(obj); + + Object.defineProperty(obj, impl, { + value: new Impl.implementation(constructorArgs, privateData), + writable: false, + enumerable: false, + configurable: true + }); + obj[impl][utils.wrapperSymbol] = obj; + }, + interface: SeqAndRec, + expose: { + Window: { SeqAndRec: SeqAndRec } + } +}; +module.exports = iface; + +const Impl = require(\\"../implementations/SeqAndRec.js\\"); +" +`; + exports[`URL.idl 1`] = ` "\\"use strict\\"; @@ -19,22 +434,28 @@ function URL(url) { const args = []; for (let i = 0; i < arguments.length && i < 2; ++i) { - args[i] = utils.tryImplForWrapper(arguments[i]); + args[i] = arguments[i]; } - args[0] = conversions[\\"USVString\\"](args[0]); + args[0] = conversions[\\"USVString\\"](args[0], { context: \\"Failed to construct 'URL': parameter 1\\" }); if (args[1] !== undefined) { - args[1] = conversions[\\"USVString\\"](args[1]); + args[1] = conversions[\\"USVString\\"](args[1], { context: \\"Failed to construct 'URL': parameter 2\\" }); } iface.setup(this, args); } +URL.prototype.toJSON = function toJSON() { + if (!this || !module.exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + return this[impl].toJSON(); +}; Object.defineProperty(URL.prototype, \\"href\\", { get() { return this[impl].href; }, set(V) { - V = conversions[\\"USVString\\"](V); + V = conversions[\\"USVString\\"](V, { context: \\"Failed to set the 'href' property on 'URL': The provided value\\" }); this[impl].href = V; }, enumerable: true, @@ -61,7 +482,7 @@ Object.defineProperty(URL.prototype, \\"protocol\\", { return this[impl].protocol; }, set(V) { - V = conversions[\\"USVString\\"](V); + V = conversions[\\"USVString\\"](V, { context: \\"Failed to set the 'protocol' property on 'URL': The provided value\\" }); this[impl].protocol = V; }, enumerable: true, @@ -73,7 +494,7 @@ Object.defineProperty(URL.prototype, \\"username\\", { return this[impl].username; }, set(V) { - V = conversions[\\"USVString\\"](V); + V = conversions[\\"USVString\\"](V, { context: \\"Failed to set the 'username' property on 'URL': The provided value\\" }); this[impl].username = V; }, enumerable: true, @@ -85,7 +506,7 @@ Object.defineProperty(URL.prototype, \\"password\\", { return this[impl].password; }, set(V) { - V = conversions[\\"USVString\\"](V); + V = conversions[\\"USVString\\"](V, { context: \\"Failed to set the 'password' property on 'URL': The provided value\\" }); this[impl].password = V; }, enumerable: true, @@ -97,7 +518,7 @@ Object.defineProperty(URL.prototype, \\"host\\", { return this[impl].host; }, set(V) { - V = conversions[\\"USVString\\"](V); + V = conversions[\\"USVString\\"](V, { context: \\"Failed to set the 'host' property on 'URL': The provided value\\" }); this[impl].host = V; }, enumerable: true, @@ -109,7 +530,7 @@ Object.defineProperty(URL.prototype, \\"hostname\\", { return this[impl].hostname; }, set(V) { - V = conversions[\\"USVString\\"](V); + V = conversions[\\"USVString\\"](V, { context: \\"Failed to set the 'hostname' property on 'URL': The provided value\\" }); this[impl].hostname = V; }, enumerable: true, @@ -121,7 +542,7 @@ Object.defineProperty(URL.prototype, \\"port\\", { return this[impl].port; }, set(V) { - V = conversions[\\"USVString\\"](V); + V = conversions[\\"USVString\\"](V, { context: \\"Failed to set the 'port' property on 'URL': The provided value\\" }); this[impl].port = V; }, enumerable: true, @@ -133,7 +554,7 @@ Object.defineProperty(URL.prototype, \\"pathname\\", { return this[impl].pathname; }, set(V) { - V = conversions[\\"USVString\\"](V); + V = conversions[\\"USVString\\"](V, { context: \\"Failed to set the 'pathname' property on 'URL': The provided value\\" }); this[impl].pathname = V; }, enumerable: true, @@ -145,7 +566,7 @@ Object.defineProperty(URL.prototype, \\"search\\", { return this[impl].search; }, set(V) { - V = conversions[\\"USVString\\"](V); + V = conversions[\\"USVString\\"](V, { context: \\"Failed to set the 'search' property on 'URL': The provided value\\" }); this[impl].search = V; }, enumerable: true, @@ -167,7 +588,7 @@ Object.defineProperty(URL.prototype, \\"hash\\", { return this[impl].hash; }, set(V) { - V = conversions[\\"USVString\\"](V); + V = conversions[\\"USVString\\"](V, { context: \\"Failed to set the 'hash' property on 'URL': The provided value\\" }); this[impl].hash = V; }, enumerable: true, @@ -211,6 +632,12 @@ const iface = { } return false; }, + convert(obj, { context = \\"The provided value\\" } = {}) { + if (module.exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError(\`\${context} is not of type 'URL'.\`); + }, create(constructorArgs, privateData) { let obj = Object.create(URL.prototype); this.setup(obj, constructorArgs, privateData); @@ -299,7 +726,12 @@ const IteratorPrototype = Object.create(utils.IteratorPrototype, { function URLSearchParams() { const args = []; for (let i = 0; i < arguments.length && i < 1; ++i) { - args[i] = utils.tryImplForWrapper(arguments[i]); + args[i] = arguments[i]; + } + if (args[0] !== undefined) { + args[0] = utils.tryImplForWrapper(args[0]); + } else { + args[0] = \\"\\"; } iface.setup(this, args); @@ -319,10 +751,14 @@ URLSearchParams.prototype.append = function append(name, value) { const args = []; for (let i = 0; i < arguments.length && i < 2; ++i) { - args[i] = utils.tryImplForWrapper(arguments[i]); + args[i] = arguments[i]; } - args[0] = conversions[\\"USVString\\"](args[0]); - args[1] = conversions[\\"USVString\\"](args[1]); + args[0] = conversions[\\"USVString\\"](args[0], { + context: \\"Failed to execute 'append' on 'URLSearchParams': parameter 1\\" + }); + args[1] = conversions[\\"USVString\\"](args[1], { + context: \\"Failed to execute 'append' on 'URLSearchParams': parameter 2\\" + }); return this[impl].append(...args); }; @@ -338,9 +774,11 @@ URLSearchParams.prototype.delete = function _(name) { const args = []; for (let i = 0; i < arguments.length && i < 1; ++i) { - args[i] = utils.tryImplForWrapper(arguments[i]); + args[i] = arguments[i]; } - args[0] = conversions[\\"USVString\\"](args[0]); + args[0] = conversions[\\"USVString\\"](args[0], { + context: \\"Failed to execute 'delete' on 'URLSearchParams': parameter 1\\" + }); return this[impl].delete(...args); }; @@ -356,9 +794,9 @@ URLSearchParams.prototype.get = function get(name) { const args = []; for (let i = 0; i < arguments.length && i < 1; ++i) { - args[i] = utils.tryImplForWrapper(arguments[i]); + args[i] = arguments[i]; } - args[0] = conversions[\\"USVString\\"](args[0]); + args[0] = conversions[\\"USVString\\"](args[0], { context: \\"Failed to execute 'get' on 'URLSearchParams': parameter 1\\" }); return this[impl].get(...args); }; @@ -374,9 +812,11 @@ URLSearchParams.prototype.getAll = function getAll(name) { const args = []; for (let i = 0; i < arguments.length && i < 1; ++i) { - args[i] = utils.tryImplForWrapper(arguments[i]); + args[i] = arguments[i]; } - args[0] = conversions[\\"USVString\\"](args[0]); + args[0] = conversions[\\"USVString\\"](args[0], { + context: \\"Failed to execute 'getAll' on 'URLSearchParams': parameter 1\\" + }); return utils.tryWrapperForImpl(this[impl].getAll(...args)); }; @@ -392,9 +832,9 @@ URLSearchParams.prototype.has = function has(name) { const args = []; for (let i = 0; i < arguments.length && i < 1; ++i) { - args[i] = utils.tryImplForWrapper(arguments[i]); + args[i] = arguments[i]; } - args[0] = conversions[\\"USVString\\"](args[0]); + args[0] = conversions[\\"USVString\\"](args[0], { context: \\"Failed to execute 'has' on 'URLSearchParams': parameter 1\\" }); return this[impl].has(...args); }; @@ -410,13 +850,20 @@ URLSearchParams.prototype.set = function set(name, value) { const args = []; for (let i = 0; i < arguments.length && i < 2; ++i) { - args[i] = utils.tryImplForWrapper(arguments[i]); + args[i] = arguments[i]; } - args[0] = conversions[\\"USVString\\"](args[0]); - args[1] = conversions[\\"USVString\\"](args[1]); + args[0] = conversions[\\"USVString\\"](args[0], { context: \\"Failed to execute 'set' on 'URLSearchParams': parameter 1\\" }); + args[1] = conversions[\\"USVString\\"](args[1], { context: \\"Failed to execute 'set' on 'URLSearchParams': parameter 2\\" }); return this[impl].set(...args); }; +URLSearchParams.prototype.sort = function sort() { + if (!this || !module.exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + return this[impl].sort(); +}; + URLSearchParams.prototype[Symbol.iterator] = function entries() { if (!this || !module.exports.is(this)) { throw new TypeError(\\"Illegal invocation\\"); @@ -502,6 +949,12 @@ const iface = { } return false; }, + convert(obj, { context = \\"The provided value\\" } = {}) { + if (module.exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError(\`\${context} is not of type 'URLSearchParams'.\`); + }, createDefaultIterator(target, kind) { const iterator = Object.create(IteratorPrototype); Object.defineProperty(iterator, utils.iterInternalSymbol, { @@ -597,6 +1050,12 @@ const iface = { } return false; }, + convert(obj, { context = \\"The provided value\\" } = {}) { + if (module.exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError(\`\${context} is not of type 'ZeroArgConstructor'.\`); + }, create(constructorArgs, privateData) { let obj = Object.create(ZeroArgConstructor.prototype); this.setup(obj, constructorArgs, privateData); diff --git a/test/cases/PromiseTypes.idl b/test/cases/PromiseTypes.idl new file mode 100644 index 00000000..abb2b866 --- /dev/null +++ b/test/cases/PromiseTypes.idl @@ -0,0 +1,4 @@ +interface PromiseTypes { + void voidPromiseConsumer(Promise p); + void promiseConsumer(Promise p); +}; diff --git a/test/cases/SeqAndRec.idl b/test/cases/SeqAndRec.idl index cd416c96..42cb98a3 100644 --- a/test/cases/SeqAndRec.idl +++ b/test/cases/SeqAndRec.idl @@ -1,7 +1,8 @@ [Constructor] interface SeqAndRec { void recordConsumer(record rec); - void recordConsumer2(record rec); + void recordConsumer2(record rec); void sequenceConsumer(sequence seq); - void sequenceConsumer2(sequence seq); + void sequenceConsumer2(sequence seq); + void frozenArrayConsumer(FrozenArray arr); }; diff --git a/test/implementations/PromiseTypes.js b/test/implementations/PromiseTypes.js new file mode 100644 index 00000000..a1da3fdf --- /dev/null +++ b/test/implementations/PromiseTypes.js @@ -0,0 +1,3 @@ +"use strict"; + +exports.implementation = class {}; From 7a07506a70e138ad09e7b9ad57c6f8d8dbb216ba Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Mon, 8 May 2017 13:50:39 -0700 Subject: [PATCH 9/9] Fixup sequence, record, dictionary Type() Object check See https://github.com/heycam/webidl/pull/305 --- lib/constructs/dictionary.js | 2 +- lib/types.js | 16 +------ test/__snapshots__/test.js.snap | 82 +++++++++++++++------------------ 3 files changed, 40 insertions(+), 60 deletions(-) diff --git a/lib/constructs/dictionary.js b/lib/constructs/dictionary.js index 1aa94624..a9a92dda 100644 --- a/lib/constructs/dictionary.js +++ b/lib/constructs/dictionary.js @@ -74,7 +74,7 @@ module.exports = { }, convert(obj, { context = "The provided value" } = {}) { - if (obj !== undefined && typeof obj !== "object") { + if (obj !== undefined && typeof obj !== "object" && typeof obj !== "function") { throw new TypeError(\`\${context} is not an object.\`); } diff --git a/lib/types.js b/lib/types.js index 6fcaf9e0..897e20e9 100644 --- a/lib/types.js +++ b/lib/types.js @@ -61,7 +61,7 @@ function generateTypeConversion(ctx, name, idlType, argAttrs, parentName, errPre function generateSequence() { str += ` - if (typeof ${name} !== "object") { + if (${name} === null || typeof ${name} !== "object" && typeof ${name} !== "function") { throw new TypeError(${errPrefix} + " is not an iterable object."); } else { const V = []; @@ -80,15 +80,8 @@ function generateTypeConversion(ctx, name, idlType, argAttrs, parentName, errPre } function generateRecord() { - if (!idlType.nullable) { - str += ` - if (${name} == null) { - ${name} = Object.create(null); - } else {`; - } - str += ` - if (typeof ${name} !== "object") { + if (${name} === null || typeof ${name} !== "object" && typeof ${name} !== "function") { throw new TypeError(${errPrefix} + " is not an object."); } else { const result = Object.create(null); @@ -111,11 +104,6 @@ function generateTypeConversion(ctx, name, idlType, argAttrs, parentName, errPre } ${name} = result; }`; - - if (!idlType.nullable) { - str += ` - }`; - } } function generatePromise() { diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 17c24a69..f4cea415 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -163,30 +163,26 @@ SeqAndRec.prototype.recordConsumer = function recordConsumer(rec) { for (let i = 0; i < arguments.length && i < 1; ++i) { args[i] = arguments[i]; } - if (args[0] == null) { - args[0] = Object.create(null); + if (args[0] === null || (typeof args[0] !== \\"object\\" && typeof args[0] !== \\"function\\")) { + throw new TypeError(\\"Failed to execute 'recordConsumer' on 'SeqAndRec': parameter 1\\" + \\" is not an object.\\"); } else { - if (typeof args[0] !== \\"object\\") { - throw new TypeError(\\"Failed to execute 'recordConsumer' on 'SeqAndRec': parameter 1\\" + \\" is not an object.\\"); - } else { - const result = Object.create(null); - const keys = Object.getOwnPropertyNames(args[0]); - for (let key of keys) { - const desc = Object.getOwnPropertyDescriptor(args[0], key); - if (desc && desc.enumerable) { - let typedKey = key; - let typedValue = args[0][key]; - typedKey = conversions[\\"USVString\\"](typedKey, { - context: \\"Failed to execute 'recordConsumer' on 'SeqAndRec': parameter 1\\" + \\"'s key\\" - }); - typedValue = conversions[\\"double\\"](typedValue, { - context: \\"Failed to execute 'recordConsumer' on 'SeqAndRec': parameter 1\\" + \\"'s value\\" - }); - result[typedKey] = typedValue; - } + const result = Object.create(null); + const keys = Object.getOwnPropertyNames(args[0]); + for (let key of keys) { + const desc = Object.getOwnPropertyDescriptor(args[0], key); + if (desc && desc.enumerable) { + let typedKey = key; + let typedValue = args[0][key]; + typedKey = conversions[\\"USVString\\"](typedKey, { + context: \\"Failed to execute 'recordConsumer' on 'SeqAndRec': parameter 1\\" + \\"'s key\\" + }); + typedValue = conversions[\\"double\\"](typedValue, { + context: \\"Failed to execute 'recordConsumer' on 'SeqAndRec': parameter 1\\" + \\"'s value\\" + }); + result[typedKey] = typedValue; } - args[0] = result; } + args[0] = result; } return this[impl].recordConsumer(...args); }; @@ -207,30 +203,26 @@ SeqAndRec.prototype.recordConsumer2 = function recordConsumer2(rec) { for (let i = 0; i < arguments.length && i < 1; ++i) { args[i] = arguments[i]; } - if (args[0] == null) { - args[0] = Object.create(null); + if (args[0] === null || (typeof args[0] !== \\"object\\" && typeof args[0] !== \\"function\\")) { + throw new TypeError(\\"Failed to execute 'recordConsumer2' on 'SeqAndRec': parameter 1\\" + \\" is not an object.\\"); } else { - if (typeof args[0] !== \\"object\\") { - throw new TypeError(\\"Failed to execute 'recordConsumer2' on 'SeqAndRec': parameter 1\\" + \\" is not an object.\\"); - } else { - const result = Object.create(null); - const keys = Object.getOwnPropertyNames(args[0]); - for (let key of keys) { - const desc = Object.getOwnPropertyDescriptor(args[0], key); - if (desc && desc.enumerable) { - let typedKey = key; - let typedValue = args[0][key]; - typedKey = conversions[\\"USVString\\"](typedKey, { - context: \\"Failed to execute 'recordConsumer2' on 'SeqAndRec': parameter 1\\" + \\"'s key\\" - }); - typedValue = convertURL(typedValue, { - context: \\"Failed to execute 'recordConsumer2' on 'SeqAndRec': parameter 1\\" + \\"'s value\\" - }); - result[typedKey] = typedValue; - } + const result = Object.create(null); + const keys = Object.getOwnPropertyNames(args[0]); + for (let key of keys) { + const desc = Object.getOwnPropertyDescriptor(args[0], key); + if (desc && desc.enumerable) { + let typedKey = key; + let typedValue = args[0][key]; + typedKey = conversions[\\"USVString\\"](typedKey, { + context: \\"Failed to execute 'recordConsumer2' on 'SeqAndRec': parameter 1\\" + \\"'s key\\" + }); + typedValue = convertURL(typedValue, { + context: \\"Failed to execute 'recordConsumer2' on 'SeqAndRec': parameter 1\\" + \\"'s value\\" + }); + result[typedKey] = typedValue; } - args[0] = result; } + args[0] = result; } return this[impl].recordConsumer2(...args); }; @@ -251,7 +243,7 @@ SeqAndRec.prototype.sequenceConsumer = function sequenceConsumer(seq) { for (let i = 0; i < arguments.length && i < 1; ++i) { args[i] = arguments[i]; } - if (typeof args[0] !== \\"object\\") { + if (args[0] === null || (typeof args[0] !== \\"object\\" && typeof args[0] !== \\"function\\")) { throw new TypeError( \\"Failed to execute 'sequenceConsumer' on 'SeqAndRec': parameter 1\\" + \\" is not an iterable object.\\" ); @@ -285,7 +277,7 @@ SeqAndRec.prototype.sequenceConsumer2 = function sequenceConsumer2(seq) { for (let i = 0; i < arguments.length && i < 1; ++i) { args[i] = arguments[i]; } - if (typeof args[0] !== \\"object\\") { + if (args[0] === null || (typeof args[0] !== \\"object\\" && typeof args[0] !== \\"function\\")) { throw new TypeError( \\"Failed to execute 'sequenceConsumer2' on 'SeqAndRec': parameter 1\\" + \\" is not an iterable object.\\" ); @@ -317,7 +309,7 @@ SeqAndRec.prototype.frozenArrayConsumer = function frozenArrayConsumer(arr) { for (let i = 0; i < arguments.length && i < 1; ++i) { args[i] = arguments[i]; } - if (typeof args[0] !== \\"object\\") { + if (args[0] === null || (typeof args[0] !== \\"object\\" && typeof args[0] !== \\"function\\")) { throw new TypeError( \\"Failed to execute 'frozenArrayConsumer' on 'SeqAndRec': parameter 1\\" + \\" is not an iterable object.\\" );