From 60459f8fe7651b4f8d3bf01662abaeb71504d935 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Tue, 30 Aug 2016 12:24:32 -0700 Subject: [PATCH] Bug 1265371: Refactor schema parsing to reduce cyclomatic complexity. r=bsilverberg MozReview-Commit-ID: HzVrf4yG7hZ --HG-- extra : rebase_source : 3e7892f8e9b6ce9d5362fd4b0ea877a5cfa235f9 --- .../components/extensions/schemas/tabs.json | 2 +- toolkit/components/extensions/Schemas.jsm | 717 +++++++++++------- 2 files changed, 427 insertions(+), 292 deletions(-) diff --git a/browser/components/extensions/schemas/tabs.json b/browser/components/extensions/schemas/tabs.json index e99a04ced97b..0f94671bbe48 100644 --- a/browser/components/extensions/schemas/tabs.json +++ b/browser/components/extensions/schemas/tabs.json @@ -834,7 +834,7 @@ "name": "result", "optional": true, "type": "array", - "items": {"type": "any", "minimum": 0}, + "items": {"type": "any"}, "description": "The result of the script in every injected frame." } ] diff --git a/toolkit/components/extensions/Schemas.jsm b/toolkit/components/extensions/Schemas.jsm index a1fd066c026b..d538363b1552 100644 --- a/toolkit/components/extensions/Schemas.jsm +++ b/toolkit/components/extensions/Schemas.jsm @@ -548,6 +548,69 @@ class Entry { // Corresponds either to a type declared in the "types" section of the // schema or else to any type object used throughout the schema. class Type extends Entry { + /** + * @property {Array} EXTRA_PROPERTIES + * An array of extra properties which may be present for + * schemas of this type. + */ + static get EXTRA_PROPERTIES() { + return ["description", "deprecated", "preprocess", "restrictions"]; + } + + /** + * Parses the given schema object and returns an instance of this + * class which corresponds to its properties. + * + * @param {object} schema + * A JSON schema object which corresponds to a definition of + * this type. + * @param {Array} path + * The path to this schema object from the root schema, + * corresponding to the property names and array indices + * traversed during parsing in order to arrive at this schema + * object. + * @param {Array} [extraProperties] + * An array of extra property names which are valid for this + * schema in the current context. + * @returns {Type} + * An instance of this type which corresponds to the given + * schema object. + * @static + */ + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + return new this(schema); + } + + /** + * Checks that all of the properties present in the given schema + * object are valid properties for this type, and throws if invalid. + * + * @param {object} schema + * A JSON schema object. + * @param {Array} path + * The path to this schema object from the root schema, + * corresponding to the property names and array indices + * traversed during parsing in order to arrive at this schema + * object. + * @param {Array} [extra] + * An array of extra property names which are valid for this + * schema in the current context. + * @throws {Error} + * An error describing the first invalid property found in the + * schema object. + */ + static checkSchemaProperties(schema, path, extra = []) { + let allowedSet = new Set([...this.EXTRA_PROPERTIES, ...extra]); + + for (let prop of Object.keys(schema)) { + if (!allowedSet.has(prop)) { + throw new Error(`Internal error: Namespace ${path.join(".")} has invalid type property "${prop}" in type "${schema.id || JSON.stringify(schema)}"`); + } + } + } + // Takes a value, checks that it has the correct type, and returns a // "normalized" version of the value. The normalized version will // include "nulls" in place of omitted optional properties. The @@ -600,6 +663,17 @@ class AnyType extends Type { // An untagged union type. class ChoiceType extends Type { + static get EXTRA_PROPERTIES() { + return ["choices", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + let choices = schema.choices.map(t => Schemas.parseSchema(t, path)); + return new this(schema, choices); + } + constructor(schema, choices) { super(schema); this.choices = choices; @@ -648,6 +722,21 @@ class ChoiceType extends Type { // This is a reference to another type--essentially a typedef. class RefType extends Type { + static get EXTRA_PROPERTIES() { + return ["$ref", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + let ref = schema.$ref; + let ns = path[0]; + if (ref.includes(".")) { + [ns, ref] = ref.split("."); + } + return new this(schema, ns, ref); + } + // For a reference to a type named T declared in namespace NS, // namespaceName will be NS and reference will be T. constructor(schema, namespaceName, reference) { @@ -676,6 +765,50 @@ class RefType extends Type { } class StringType extends Type { + static get EXTRA_PROPERTIES() { + return ["enum", "minLength", "maxLength", "pattern", "format", + ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + let enumeration = schema.enum || null; + if (enumeration) { + // The "enum" property is either a list of strings that are + // valid values or else a list of {name, description} objects, + // where the .name values are the valid values. + enumeration = enumeration.map(e => { + if (typeof(e) == "object") { + return e.name; + } + return e; + }); + } + + let pattern = null; + if (schema.pattern) { + try { + pattern = parsePattern(schema.pattern); + } catch (e) { + throw new Error(`Internal error: Invalid pattern ${JSON.stringify(schema.pattern)}`); + } + } + + let format = null; + if (schema.format) { + if (!(schema.format in FORMATS)) { + throw new Error(`Internal error: Invalid string format ${schema.format}`); + } + format = FORMATS[schema.format]; + } + return new this(schema, enumeration, + schema.minLength || 0, + schema.maxLength || Infinity, + pattern, + format); + } + constructor(schema, enumeration, minLength, maxLength, pattern, format) { super(schema); this.enumeration = enumeration; @@ -743,7 +876,64 @@ class StringType extends Type { } } +let SubModuleType; class ObjectType extends Type { + static get EXTRA_PROPERTIES() { + return ["properties", "patternProperties", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + if ("functions" in schema) { + return SubModuleType.parseSchema(schema, path, extraProperties); + } + + if (!("$extend" in schema)) { + // Only allow extending "properties" and "patternProperties". + extraProperties = ["additionalProperties", "isInstanceOf", ...extraProperties]; + } + this.checkSchemaProperties(schema, path, extraProperties); + + let parseProperty = (schema, extraProps = []) => { + return { + type: Schemas.parseSchema(schema, path, + ["unsupported", "onError", "permissions", ...extraProps]), + optional: schema.optional || false, + unsupported: schema.unsupported || false, + onError: schema.onError || null, + }; + }; + + // Parse explicit "properties" object. + let properties = Object.create(null); + for (let propName of Object.keys(schema.properties || {})) { + properties[propName] = parseProperty(schema.properties[propName], ["optional"]); + } + + // Parse regexp properties from "patternProperties" object. + let patternProperties = []; + for (let propName of Object.keys(schema.patternProperties || {})) { + let pattern; + try { + pattern = parsePattern(propName); + } catch (e) { + throw new Error(`Internal error: Invalid property pattern ${JSON.stringify(propName)}`); + } + + patternProperties.push({ + pattern, + type: parseProperty(schema.patternProperties[propName]), + }); + } + + // Parse "additionalProperties" schema. + let additionalProperties = null; + if (schema.additionalProperties) { + additionalProperties = Schemas.parseSchema(schema.additionalProperties, path); + } + + return new this(schema, properties, additionalProperties, patternProperties, schema.isInstanceOf || null); + } + constructor(schema, properties, additionalProperties, patternProperties, isInstanceOf) { super(schema); this.properties = properties; @@ -769,31 +959,18 @@ class ObjectType extends Type { return baseType == "object"; } - // FIXME: Bug 1265371 - Refactor normalize and parseType in Schemas.jsm to reduce complexity - normalize(value, context) { // eslint-disable-line complexity - let v = this.normalizeBase("object", value, context); - if (v.error) { - return v; - } - value = v.value; - - if (this.isInstanceOf) { - if (Object.keys(this.properties).length || - this.patternProperties.length || - !(this.additionalProperties instanceof AnyType)) { - throw new Error("InternalError: isInstanceOf can only be used with objects that are otherwise unrestricted"); - } - - if (!instanceOf(value, this.isInstanceOf)) { - return context.error(`Object must be an instance of ${this.isInstanceOf}`, - `be an instance of ${this.isInstanceOf}`); - } - - // This is kind of a hack, but we can't normalize things that - // aren't JSON, so we just return them. - return {value}; - } - + /** + * Extracts the enumerable properties of the given object, including + * function properties which would normally be omitted by X-ray + * wrappers. + * + * @param {object} value + * @param {Context} context + * The current parse context. + * @returns {object} + * An object with an `error` or `value` property. + */ + extractProperties(value, context) { // |value| should be a JS Xray wrapping an object in the // extension compartment. This works well except when we need to // access callable properties on |value| since JS Xrays don't @@ -804,115 +981,161 @@ class ObjectType extends Type { let klass = Cu.getClassName(value, true); if (klass != "Object") { - return context.error(`Expected a plain JavaScript object, got a ${klass}`, - `be a plain JavaScript object`); + throw context.error(`Expected a plain JavaScript object, got a ${klass}`, + `be a plain JavaScript object`); } let properties = Object.create(null); - { - // |waived| is scoped locally to avoid accessing it when we - // don't mean to. - let waived = Cu.waiveXrays(value); - for (let prop of Object.getOwnPropertyNames(waived)) { - let desc = Object.getOwnPropertyDescriptor(waived, prop); - if (desc.get || desc.set) { - return context.error("Objects cannot have getters or setters on properties", - "contain no getter or setter properties"); - } - if (!desc.enumerable) { - // Chrome ignores non-enumerable properties. - continue; - } + + let waived = Cu.waiveXrays(value); + for (let prop of Object.getOwnPropertyNames(waived)) { + let desc = Object.getOwnPropertyDescriptor(waived, prop); + if (desc.get || desc.set) { + throw context.error("Objects cannot have getters or setters on properties", + "contain no getter or setter properties"); + } + // Chrome ignores non-enumerable properties. + if (desc.enumerable) { properties[prop] = Cu.unwaiveXrays(desc.value); } } - let remainingProps = new Set(Object.keys(properties)); + return properties; + } - let checkProperty = (prop, propType, result) => { - let {type, optional, unsupported} = propType; - if (unsupported) { - if (prop in properties) { - return context.error(`Property "${prop}" is unsupported by Firefox`, - `not contain an unsupported "${prop}" property`); - } - } else if (prop in properties) { - if (optional && (properties[prop] === null || properties[prop] === undefined)) { - result[prop] = null; + checkProperty(context, prop, propType, result, properties, remainingProps) { + let {type, optional, unsupported, onError} = propType; + let error = null; + + if (unsupported) { + if (prop in properties) { + error = context.error(`Property "${prop}" is unsupported by Firefox`, + `not contain an unsupported "${prop}" property`); + } + } else if (prop in properties) { + if (optional && (properties[prop] === null || properties[prop] === undefined)) { + result[prop] = null; + } else { + let r = context.withPath(prop, () => type.normalize(properties[prop], context)); + if (r.error) { + error = r; } else { + result[prop] = r.value; + properties[prop] = r.value; + } + } + remainingProps.delete(prop); + } else if (!optional) { + error = context.error(`Property "${prop}" is required`, + `contain the required "${prop}" property`); + } else { + result[prop] = null; + } + + if (error) { + if (onError == "warn") { + context.logError(error.error); + } else if (onError != "ignore") { + throw error; + } + + result[prop] = null; + } + } + + normalize(value, context) { + try { + let v = this.normalizeBase("object", value, context); + if (v.error) { + return v; + } + value = v.value; + + if (this.isInstanceOf) { + if (Object.keys(this.properties).length || + this.patternProperties.length || + !(this.additionalProperties instanceof AnyType)) { + throw new Error("InternalError: isInstanceOf can only be used with objects that are otherwise unrestricted"); + } + + if (!instanceOf(value, this.isInstanceOf)) { + return context.error(`Object must be an instance of ${this.isInstanceOf}`, + `be an instance of ${this.isInstanceOf}`); + } + + // This is kind of a hack, but we can't normalize things that + // aren't JSON, so we just return them. + return {value}; + } + + let properties = this.extractProperties(value, context); + let remainingProps = new Set(Object.keys(properties)); + + let result = {}; + for (let prop of Object.keys(this.properties)) { + this.checkProperty(context, prop, this.properties[prop], result, + properties, remainingProps); + } + + for (let prop of Object.keys(properties)) { + for (let {pattern, type} of this.patternProperties) { + if (pattern.test(prop)) { + this.checkProperty(context, prop, type, result, + properties, remainingProps); + } + } + } + + if (this.additionalProperties) { + for (let prop of remainingProps) { + let type = this.additionalProperties; let r = context.withPath(prop, () => type.normalize(properties[prop], context)); if (r.error) { return r; } result[prop] = r.value; - properties[prop] = r.value; } - remainingProps.delete(prop); - } else if (!optional) { - return context.error(`Property "${prop}" is required`, - `contain the required "${prop}" property`); - } else { - result[prop] = null; + } else if (remainingProps.size == 1) { + return context.error(`Unexpected property "${[...remainingProps]}"`, + `not contain an unexpected "${[...remainingProps]}" property`); + } else if (remainingProps.size) { + let props = [...remainingProps].sort().join(", "); + return context.error(`Unexpected properties: ${props}`, + `not contain the unexpected properties [${props}]`); } - }; - let result = {}; - for (let prop of Object.keys(this.properties)) { - let error = checkProperty(prop, this.properties[prop], result); - if (error) { - let {onError} = this.properties[prop]; - if (onError == "warn") { - context.logError(error.error); - } else if (onError != "ignore") { - return error; - } - - result[prop] = null; - remainingProps.delete(prop); + return {value: result}; + } catch (e) { + if (e.error) { + return e; } + throw e; } - - for (let prop of Object.keys(properties)) { - for (let {pattern, type} of this.patternProperties) { - if (pattern.test(prop)) { - let error = checkProperty(prop, type, result); - if (error) { - return error; - } - } - } - } - - if (this.additionalProperties) { - for (let prop of remainingProps) { - let type = this.additionalProperties; - let r = context.withPath(prop, () => type.normalize(properties[prop], context)); - if (r.error) { - return r; - } - result[prop] = r.value; - } - } else if (remainingProps.size == 1) { - return context.error(`Unexpected property "${[...remainingProps]}"`, - `not contain an unexpected "${[...remainingProps]}" property`); - } else if (remainingProps.size) { - let props = [...remainingProps].sort().join(", "); - return context.error(`Unexpected properties: ${props}`, - `not contain the unexpected properties [${props}]`); - } - - return {value: result}; } } // This type is just a placeholder to be referred to by // SubModuleProperty. No value is ever expected to have this type. -class SubModuleType extends Type { +SubModuleType = class SubModuleType extends Type { + static get EXTRA_PROPERTIES() { + return ["functions", "events", "properties", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + // The path we pass in here is only used for error messages. + path = [...path, schema.id]; + let functions = schema.functions.map(fun => Schemas.parseFunction(path, fun)); + + return new this(functions); + } + constructor(functions) { super(); this.functions = functions; } -} +}; class NumberType extends Type { normalize(value, context) { @@ -935,6 +1158,16 @@ class NumberType extends Type { } class IntegerType extends Type { + static get EXTRA_PROPERTIES() { + return ["minimum", "maximum", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + return new this(schema, schema.minimum || -Infinity, schema.maximum || Infinity); + } + constructor(schema, minimum, maximum) { super(schema); this.minimum = minimum; @@ -982,6 +1215,18 @@ class BooleanType extends Type { } class ArrayType extends Type { + static get EXTRA_PROPERTIES() { + return ["items", "minItems", "maxItems", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + let items = Schemas.parseSchema(schema.items, path); + + return new this(schema, items, schema.minItems || 0, schema.maxItems || Infinity); + } + constructor(schema, itemType, minItems, maxItems) { super(schema); this.itemType = itemType; @@ -1024,6 +1269,46 @@ class ArrayType extends Type { } class FunctionType extends Type { + static get EXTRA_PROPERTIES() { + return ["parameters", "async", "returns", ...super.EXTRA_PROPERTIES]; + } + + static parseSchema(schema, path, extraProperties = []) { + this.checkSchemaProperties(schema, path, extraProperties); + + let isAsync = !!schema.async; + let parameters = null; + if ("parameters" in schema) { + parameters = []; + for (let param of schema.parameters) { + // Callbacks default to optional for now, because of promise + // handling. + let isCallback = isAsync && param.name == schema.async; + + parameters.push({ + type: Schemas.parseSchema(param, path, ["name", "optional"]), + name: param.name, + optional: param.optional == null ? isCallback : param.optional, + }); + } + } + + let hasAsyncCallback = false; + if (isAsync) { + if (parameters && parameters.length && parameters[parameters.length - 1].name == schema.async) { + hasAsyncCallback = true; + } + if (schema.returns) { + throw new Error("Internal error: Async functions must not have return values."); + } + if (schema.allowAmbiguousOptionalArguments && !hasAsyncCallback) { + throw new Error("Internal error: Async functions with ambiguous arguments must declare the callback as the last parameter"); + } + } + + return new this(schema, parameters, isAsync, hasAsyncCallback); + } + constructor(schema, parameters, isAsync, hasAsyncCallback) { super(schema); this.parameters = parameters; @@ -1343,6 +1628,17 @@ class Event extends CallEntry { } } +const TYPES = Object.freeze(Object.assign(Object.create(null), { + any: AnyType, + array: ArrayType, + boolean: BooleanType, + function: FunctionType, + integer: IntegerType, + number: NumberType, + object: ObjectType, + string: StringType, +})); + this.Schemas = { initialized: false, @@ -1366,195 +1662,34 @@ this.Schemas = { ns.set(symbol, value); }, - // FIXME: Bug 1265371 - Refactor normalize and parseType in Schemas.jsm to reduce complexity - parseType(path, type, extraProperties = []) { // eslint-disable-line complexity + parseSchema(schema, path, extraProperties = []) { let allowedProperties = new Set(extraProperties); - // Do some simple validation of our own schemas. - function checkTypeProperties(...extra) { - let allowedSet = new Set([...allowedProperties, ...extra, "description", "deprecated", "preprocess", "restrictions"]); - for (let prop of Object.keys(type)) { - if (!allowedSet.has(prop)) { - throw new Error(`Internal error: Namespace ${path.join(".")} has invalid type property "${prop}" in type "${type.id || JSON.stringify(type)}"`); - } - } + if ("choices" in schema) { + return ChoiceType.parseSchema(schema, path, allowedProperties); + } else if ("$ref" in schema) { + return RefType.parseSchema(schema, path, allowedProperties); } - if ("choices" in type) { - checkTypeProperties("choices"); - - let choices = type.choices.map(t => this.parseType(path, t)); - return new ChoiceType(type, choices); - } else if ("$ref" in type) { - checkTypeProperties("$ref"); - let ref = type.$ref; - let ns = path[0]; - if (ref.includes(".")) { - [ns, ref] = ref.split("."); - } - return new RefType(type, ns, ref); - } - - if (!("type" in type)) { - throw new Error(`Unexpected value for type: ${JSON.stringify(type)}`); + if (!("type" in schema)) { + throw new Error(`Unexpected value for type: ${JSON.stringify(schema)}`); } allowedProperties.add("type"); - // Otherwise it's a normal type... - if (type.type == "string") { - checkTypeProperties("enum", "minLength", "maxLength", "pattern", "format"); - - let enumeration = type.enum || null; - if (enumeration) { - // The "enum" property is either a list of strings that are - // valid values or else a list of {name, description} objects, - // where the .name values are the valid values. - enumeration = enumeration.map(e => { - if (typeof(e) == "object") { - return e.name; - } - return e; - }); - } - - let pattern = null; - if (type.pattern) { - try { - pattern = parsePattern(type.pattern); - } catch (e) { - throw new Error(`Internal error: Invalid pattern ${JSON.stringify(type.pattern)}`); - } - } - - let format = null; - if (type.format) { - if (!(type.format in FORMATS)) { - throw new Error(`Internal error: Invalid string format ${type.format}`); - } - format = FORMATS[type.format]; - } - return new StringType(type, enumeration, - type.minLength || 0, - type.maxLength || Infinity, - pattern, - format); - } else if (type.type == "object" && "functions" in type) { - // NOTE: "events" and "properties" are currently ignored, because they are used - // in the DevTools schema files, but they are not currently used by anything in the - // initial set of supported DevTools APIs. See Bug 1290901 for rationale. - // Introducing a complete support of "events" and "properties" in the SubModuleType - // will be re-evaluated as part of Bug 1293298 and Bug 1293301. - - checkTypeProperties("functions", "events", "properties"); - - // The path we pass in here is only used for error messages. - let functions = type.functions.map(fun => this.parseFunction(path.concat(type.id), fun)); - - return new SubModuleType(functions); - } else if (type.type == "object") { - let parseProperty = (type, extraProps = []) => { - return { - type: this.parseType(path, type, - ["unsupported", "onError", "permissions", ...extraProps]), - optional: type.optional || false, - unsupported: type.unsupported || false, - onError: type.onError || null, - }; - }; - - let properties = Object.create(null); - for (let propName of Object.keys(type.properties || {})) { - properties[propName] = parseProperty(type.properties[propName], ["optional"]); - } - - let patternProperties = []; - for (let propName of Object.keys(type.patternProperties || {})) { - let pattern; - try { - pattern = parsePattern(propName); - } catch (e) { - throw new Error(`Internal error: Invalid property pattern ${JSON.stringify(propName)}`); - } - - patternProperties.push({ - pattern, - type: parseProperty(type.patternProperties[propName]), - }); - } - - let additionalProperties = null; - if (type.additionalProperties) { - additionalProperties = this.parseType(path, type.additionalProperties); - } - - if ("$extend" in type) { - // Only allow extending "properties" and "patternProperties". - checkTypeProperties("properties", "patternProperties"); - } else { - checkTypeProperties("properties", "additionalProperties", "patternProperties", "isInstanceOf"); - } - return new ObjectType(type, properties, additionalProperties, patternProperties, type.isInstanceOf || null); - } else if (type.type == "array") { - checkTypeProperties("items", "minItems", "maxItems"); - return new ArrayType(type, this.parseType(path, type.items), - type.minItems || 0, type.maxItems || Infinity); - } else if (type.type == "number") { - checkTypeProperties(); - return new NumberType(type); - } else if (type.type == "integer") { - checkTypeProperties("minimum", "maximum"); - return new IntegerType(type, type.minimum || -Infinity, type.maximum || Infinity); - } else if (type.type == "boolean") { - checkTypeProperties(); - return new BooleanType(type); - } else if (type.type == "function") { - let isAsync = !!type.async; - let parameters = null; - if ("parameters" in type) { - parameters = []; - for (let param of type.parameters) { - // Callbacks default to optional for now, because of promise - // handling. - let isCallback = isAsync && param.name == type.async; - - parameters.push({ - type: this.parseType(path, param, ["name", "optional"]), - name: param.name, - optional: param.optional == null ? isCallback : param.optional, - }); - } - } - - let hasAsyncCallback = false; - if (isAsync) { - if (parameters && parameters.length && parameters[parameters.length - 1].name == type.async) { - hasAsyncCallback = true; - } - if (type.returns) { - throw new Error("Internal error: Async functions must not have return values."); - } - if (type.allowAmbiguousOptionalArguments && !hasAsyncCallback) { - throw new Error("Internal error: Async functions with ambiguous arguments must declare the callback as the last parameter"); - } - } - - checkTypeProperties("parameters", "async", "returns"); - return new FunctionType(type, parameters, isAsync, hasAsyncCallback); - } else if (type.type == "any") { - // Need to see what minimum and maximum are supposed to do here. - checkTypeProperties("minimum", "maximum"); - return new AnyType(type); + let type = TYPES[schema.type]; + if (!type) { + throw new Error(`Unexpected type ${schema.type}`); } - throw new Error(`Unexpected type ${type.type}`); + return type.parseSchema(schema, path, allowedProperties); }, parseFunction(path, fun) { let f = new FunctionEntry(fun, path, fun.name, - this.parseType(path, fun, - ["name", "unsupported", "returns", - "permissions", - "allowAmbiguousOptionalArguments"]), + this.parseSchema(fun, path, + ["name", "unsupported", "returns", + "permissions", + "allowAmbiguousOptionalArguments"]), fun.unsupported || false, fun.allowAmbiguousOptionalArguments || false, fun.returns || null, @@ -1566,7 +1701,7 @@ this.Schemas = { if ("$extend" in type) { this.extendType(namespaceName, type); } else { - this.register(namespaceName, type.id, this.parseType([namespaceName], type, ["id"])); + this.register(namespaceName, type.id, this.parseSchema(type, [namespaceName], ["id"])); } }, @@ -1583,7 +1718,7 @@ this.Schemas = { throw new Error(`Internal error: Attempt to extend a non-extensible type ${type.$extend}`); } - let parsed = this.parseType([namespaceName], type, ["$extend"]); + let parsed = this.parseSchema(type, [namespaceName], ["$extend"]); if (parsed.constructor !== targetType.constructor) { throw new Error(`Internal error: Bad attempt to extend ${type.$extend}`); } @@ -1602,7 +1737,7 @@ this.Schemas = { } else { // We ignore the "optional" attribute on properties since we // don't inject anything here anyway. - let type = this.parseType([namespaceName], prop, ["optional", "writable"]); + let type = this.parseSchema(prop, [namespaceName], ["optional", "writable"]); this.register(namespaceName, name, new TypeProperty(prop, namespaceName, name, type, prop.writable || false)); } }, @@ -1616,7 +1751,7 @@ this.Schemas = { let extras = event.extraParameters || []; extras = extras.map(param => { return { - type: this.parseType([namespaceName], param, ["name", "optional"]), + type: this.parseSchema(param, [namespaceName], ["name", "optional"]), name: param.name, optional: param.optional || false, }; @@ -1628,9 +1763,9 @@ this.Schemas = { let filters = event.filters; /* eslint-enable no-unused-vars */ - let type = this.parseType([namespaceName], event, - ["name", "unsupported", "permissions", - "extraParameters", "returns", "filters"]); + let type = this.parseSchema(event, [namespaceName], + ["name", "unsupported", "permissions", + "extraParameters", "returns", "filters"]); let e = new Event(event, [namespaceName], event.name, type, extras, event.unsupported || false, @@ -1683,13 +1818,13 @@ this.Schemas = { }); for (let json of this.schemaJSON.values()) { - this.parseSchema(json); + this.loadSchema(json); } return this.namespaces; }, - parseSchema(json) { + loadSchema(json) { for (let namespace of json) { let name = namespace.namespace;