From d39408a8e11c5df4bf8e8c3d8e56150a79ee27d4 Mon Sep 17 00:00:00 2001 From: Rainer Date: Thu, 20 Aug 2020 14:30:41 +0200 Subject: [PATCH 1/4] Implement nested schemas support "$ref" property: - the type of the property gets linked to the property in the definitions section - all property in the definitions section get written in there own comment sections support for the "allOf" property: - "$ref" : + if only one "$ref" is available the type gets linked + with multiple "$ref" all properties of the referenced definitions get copied into the current property and the type is set to "object" - all properties of sub objects get copied into the current property --- index.js | 96 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index f937561..8f0db68 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ 'use strict' const json = require('json-pointer') +const NewBlockIndicator = 1 function getDefaultPropertyType ({ propertyNameAsType, capitalizeProperty, defaultPropertyType @@ -68,6 +69,8 @@ function generate (schema, options = {}) { jsdoc.push(...processItems(schema, schema, null, config)) } + jsdoc.push(...processDefinitions(schema, schema, null, config)) + return format(config.outerIndent, jsdoc) } @@ -121,22 +124,85 @@ function processProperties (schema, rootSchema, base, config) { const root = base ? `${base}.` : '' const prefixedProperty = root + property const defaultValue = props[property].default - if (prop.type === 'object' && prop.properties) { - result.push(...writeProperty('object', prefixedProperty, prop.description, true, defaultValue, config)) - result.push(...processProperties(prop, rootSchema, prefixedProperty, config)) - } else if (prop.type === 'array' && prop.items) { - result.push(...writeProperty('array', prefixedProperty, prop.description, true, defaultValue, config)) - result.push(...processItems(prop, rootSchema, prefixedProperty, config)) - } else { - const optional = !required.includes(property) - const type = getType(prop, rootSchema) || getDefaultPropertyType(config, property) - result.push(...writeProperty(type, prefixedProperty, prop.description, optional, defaultValue, config)) + + if(prop.allOf){ + let refs = [] + let entrys = [] + + for(let e of prop.allOf){ + if(json.has(e, '/$ref')){ + refs.push(e) + }else{ + entrys.push(e) + } + } + + if(refs.length ==0){ + result.push(...writeProperty('object', prefixedProperty, prop.description, true, defaultValue, config)) + }else if(refs.length == 1){ + const optional = !required.includes(property) + let type = getType(refs[0], rootSchema) || getDefaultPropertyType(config, property) + result.push(...writeProperty(type, prefixedProperty, prop.description, optional, defaultValue, config)) + }else{ + result.push(...writeProperty('object', prefixedProperty, prop.description, true, defaultValue, config)) + for(let e of refs){ + result.push(...processProperties(json.get(rootSchema,e['$ref'].replace(/^#/gm,"")), rootSchema, prefixedProperty, config)) + } + } + + for(let e of entrys){ + const type = getType(e, rootSchema) || getDefaultPropertyType(config, property) + if(type === 'object') result.push(...processProperties(e, rootSchema, prefixedProperty, config)) + } + + + }else{ + + if (prop.type === 'object' && prop.properties) { + result.push(...writeProperty('object', prefixedProperty, prop.description, true, defaultValue, config)) + result.push(...processProperties(prop, rootSchema, prefixedProperty, config)) + } else if (prop.type === 'array' && prop.items) { + result.push(...writeProperty('array', prefixedProperty, prop.description, true, defaultValue, config)) + result.push(...processItems(prop, rootSchema, prefixedProperty, config)) + } else { + const optional = !required.includes(property) + const type = getType(prop, rootSchema) || getDefaultPropertyType(config, property) + result.push(...writeProperty(type, prefixedProperty, prop.description, optional, defaultValue, config)) + } } } } return result } +function processDefinitions(schema, rootSchema, base, config){ + let defs + if(json.has(schema, '/definitions')){ + defs = json.get(schema, '/definitions') + }else if(json.has(schema, '/$defs')){ + defs = json.get(schema, '/$defs') + }else{ + return [] + } + + const result = [] + + for(let def in defs){ + result.push(NewBlockIndicator) + result.push(...writeDescription(defs[def], config)) + if (json.has(defs[def], '/properties')) { + result.push(...processProperties(defs[def], rootSchema, null, config)) + } + if (json.has(defs[def], '/items')) { + result.push(...processItems(defs[def], rootSchema, null, config)) + } + } + + return result + + +} + function writeDescription (schema, config) { const result = [] const { objectTagName = 'typedef' } = config @@ -190,7 +256,7 @@ function writeProperty (type, field, description = '', optional, defaultValue, c function getType (schema, rootSchema) { if (schema.$ref) { const ref = json.get(rootSchema, schema.$ref.slice(1)) - return getType(ref, rootSchema) + return ref.title } if (schema.enum) { @@ -225,7 +291,13 @@ function generateDescription (title, type) { function format (outerIndent, lines) { const result = [`${outerIndent}/**`] - result.push(...lines.map(line => line ? `${outerIndent} * ${line}` : ' *')) + result.push(...lines.map(line =>{ + if(line == NewBlockIndicator){ + return ' */\n\n/**' + }else{ + return line ? `${outerIndent} * ${line}` : ' *' + } + })) result.push(`${outerIndent} */\n`) return result.join('\n') From d3ea090c94e28b6c8921e17f80fd4459da8e6aa1 Mon Sep 17 00:00:00 2001 From: Rainer Date: Fri, 11 Sep 2020 15:31:05 +0200 Subject: [PATCH 2/4] implement allOf and anyOf functionality implemented nested schemas with the anyOf and allOf keyword. --- index.js | 225 +++++++++++++------- test.js | 628 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 754 insertions(+), 99 deletions(-) diff --git a/index.js b/index.js index 6859b14..9d10165 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const json = require('json-pointer') const NewBlockIndicator = 1 +let generatedDefs = [] function getDefaultPropertyType ({ propertyNameAsType, capitalizeProperty, defaultPropertyType @@ -60,17 +61,18 @@ function generate (schema, options = {}) { const config = parseOptions(options) - jsdoc.push(...writeDescription(schema, config)) - - if (json.has(schema, '/properties')) { - jsdoc.push(...processProperties(schema, schema, null, config)) - } - if (json.has(schema, '/items')) { - jsdoc.push(...processItems(schema, schema, null, config)) + let defs + if (json.has(schema, '/definitions')) { + defs = json.get(schema, '/definitions') + } else if (json.has(schema, '/$defs')) { + defs = json.get(schema, '/$defs') } - jsdoc.push(...processDefinitions(schema, schema, null, config)) + const required = json.has(schema, '/required') ? json.get(schema, '/required') : [] + jsdoc.push(...writeDescription(schema, schema, config)) + jsdoc.push(...processObject(schema, '', '', required, schema, config, true)) + jsdoc.push(...processDefinitions(defs, schema, null, config)) return format(config.outerIndent, jsdoc) } @@ -112,6 +114,7 @@ function processItems (schema, rootSchema, base, config) { } function processProperties (schema, rootSchema, base, config) { + if (!json.has(schema, '/properties')) return [] const props = json.get(schema, '/properties') const required = json.has(schema, '/required') ? json.get(schema, '/required') : [] const result = [] @@ -120,90 +123,156 @@ function processProperties (schema, rootSchema, base, config) { if (Array.isArray(config.ignore) && config.ignore.includes(property)) { continue } else { - const prop = props[property] const root = base ? `${base}.` : '' const prefixedProperty = root + property - const defaultValue = props[property].default + result.push(...processObject(props[property], property, prefixedProperty, required, rootSchema, config)) + } + } + return result +} - if(prop.allOf){ - let refs = [] - let entrys = [] +function processObject (obj, objName, prefixedProperty, required, rootSchema, config, rootElement) { + const defaultValue = obj.default + const result = [] - for(let e of prop.allOf){ - if(json.has(e, '/$ref')){ - refs.push(e) - }else{ - entrys.push(e) - } + if (obj.allOf || obj.anyOf) { + const refs = [] + const refsIsA = [] + const refsAnyOf = [] + const entries = [] + const container = obj.allOf ? obj.allOf : obj.anyOf + const separator = obj.allOf ? '&' : '|' + + for (const e of container) { + if (json.has(e, '/$ref')) { + if (e.classRelation && e.classRelation === 'is-a') { + refsIsA.push(e) + } else { + refs.push(e) } + } else { + entries.push(e) + } + } - if(refs.length ==0){ - result.push(...writeProperty('object', prefixedProperty, prop.description, true, defaultValue, config)) - }else if(refs.length == 1){ - const optional = !required.includes(property) - let type = getType(refs[0], rootSchema) || getDefaultPropertyType(config, property) - result.push(...writeProperty(type, prefixedProperty, prop.description, optional, defaultValue, config)) - }else{ - result.push(...writeProperty('object', prefixedProperty, prop.description, true, defaultValue, config)) - for(let e of refs){ - result.push(...processProperties(json.get(rootSchema,e['$ref'].replace(/^#/gm,"")), rootSchema, prefixedProperty, config)) - } + if (obj.anyOf) { + let count = 0 + for (const e of refs) { + const refElement = json.get(rootSchema, e.$ref.replace(/^#/gm, '')) + if (refElement.title) { + e.title = `${obj.title}_${refElement.title}` + } else { + e.title = `${obj.title}_subtype${count}` + count++ } - - for(let e of entrys){ - const type = getType(e, rootSchema) || getDefaultPropertyType(config, property) - if(type === 'object') result.push(...processProperties(e, rootSchema, prefixedProperty, config)) + refsAnyOf.push(e) + generatedDefs.push(e) + } + for (const e of entries) { + const type = getType(e, rootSchema) || getDefaultPropertyType(config, objName) + if (type === 'object') { + if (e.title) { + e.title = `${obj.title}_${e.title}` + } else { + e.title = `${obj.title}_subtype${count}` + count++ + } + refsAnyOf.push(e) + generatedDefs.push(e) } + } + } + if (refsIsA.length === 0 && refsAnyOf.length === 0) { + if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, true, defaultValue, config)) + } else { + const optional = !required.includes(objName) + let type = '' + for (let i = 0; i < refsIsA.length; i++) { + if (type !== '') type += separator + type += getType(refsIsA[i], rootSchema) + } + for (let i = 0; i < refsAnyOf.length; i++) { + if (type !== '') type += separator + type += refsAnyOf[i].title + } + if (type === '') type = getDefaultPropertyType(config, objName) + if (!rootElement) result.push(...writeProperty(type, prefixedProperty, obj.description, optional, defaultValue, config)) + } - }else{ - - if (prop.type === 'object' && prop.properties) { - result.push(...writeProperty('object', prefixedProperty, prop.description, true, defaultValue, config)) - result.push(...processProperties(prop, rootSchema, prefixedProperty, config)) - } else if (prop.type === 'array' && prop.items) { - result.push(...writeProperty('array', prefixedProperty, prop.description, true, defaultValue, config)) - result.push(...processItems(prop, rootSchema, prefixedProperty, config)) - } else { - const optional = !required.includes(property) - const type = getType(prop, rootSchema) || getDefaultPropertyType(config, property) - result.push(...writeProperty(type, prefixedProperty, prop.description, optional, defaultValue, config)) - } + if (obj.allOf) { + for (const e of refs) { + result.push(...processProperties(json.get(rootSchema, e.$ref.replace(/^#/gm, '')), rootSchema, prefixedProperty, config)) + } + for (const e of entries) { + result.push(...processProperties(e, rootSchema, prefixedProperty, config)) + } + } + } else { + if (obj.$ref) { + if (obj.classRelation && obj.classRelation === 'is-a') { + const optional = !required.includes(objName) + const type = getType(obj, rootSchema) || getDefaultPropertyType(config, objName) + if (!rootElement) result.push(...writeProperty(type, prefixedProperty, obj.description, optional, defaultValue, config)) + } else { + if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, true, defaultValue, config)) + result.push(...processProperties(json.get(rootSchema, obj.$ref.slice(1)), rootSchema, prefixedProperty, config)) + } + } else { + if (obj.type === 'object' && obj.properties) { + if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, true, defaultValue, config)) + result.push(...processProperties(obj, rootSchema, prefixedProperty, config)) + } else if (obj.type === 'array' && obj.items) { + if (!rootElement) result.push(...writeProperty('array', prefixedProperty, obj.description, true, defaultValue, config)) + result.push(...processItems(obj, rootSchema, prefixedProperty, config)) + } else { + const optional = !required.includes(objName) + const type = getType(obj, rootSchema) || getDefaultPropertyType(config, objName) + if (!rootElement) result.push(...writeProperty(type, prefixedProperty, obj.description, optional, defaultValue, config)) } } } + return result } -function processDefinitions(schema, rootSchema, base, config){ - let defs - if(json.has(schema, '/definitions')){ - defs = json.get(schema, '/definitions') - }else if(json.has(schema, '/$defs')){ - defs = json.get(schema, '/$defs') - }else{ - return [] - } - - const result = [] - - for(let def in defs){ - result.push(NewBlockIndicator) - result.push(...writeDescription(defs[def], config)) - if (json.has(defs[def], '/properties')) { - result.push(...processProperties(defs[def], rootSchema, null, config)) - } - if (json.has(defs[def], '/items')) { - result.push(...processItems(defs[def], rootSchema, null, config)) - } - } +function processDefinitions (defs, rootSchema, base, config) { + const result = [] - return result + for (const def in defs) { + result.push(...writeDefinition(defs[def], rootSchema, config)) + } + for (const def of generatedDefs) { + if (def.$ref) { + const edit = { ...json.get(rootSchema, def.$ref.replace(/^#/gm, '')) } + edit.title = def.title + result.push(...writeDefinition(edit, rootSchema, config)) + } else { + result.push(...writeDefinition(def, rootSchema, config)) + } + } + + generatedDefs = [] + return result +} +function writeDefinition (def, rootSchema, config) { + const result = [] + result.push(NewBlockIndicator) + result.push(...writeDescription(def, rootSchema, config)) + if (def.type === 'object') { + if (json.has(def, '/properties')) { + result.push(...processProperties(def, rootSchema, null, config)) + } + if (json.has(def, '/items')) { + result.push(...processItems(def, rootSchema, null, config)) + } + } + return result } -function writeDescription (schema, config) { +function writeDescription (schema, rootSchema, config) { const result = [] const { objectTagName = 'typedef' } = config let { description } = schema @@ -219,7 +288,7 @@ function writeDescription (schema, config) { type = ` {${ typeMatch === '' ? '' - : typeMatch || getType(schema, schema) + : typeMatch || getType(schema, rootSchema) }}` } @@ -256,7 +325,11 @@ function writeProperty (type, field, description = '', optional, defaultValue, c function getType (schema, rootSchema) { if (schema.$ref) { const ref = json.get(rootSchema, schema.$ref.slice(1)) - return ref.title + if (schema.classRelation && schema.classRelation === 'is-a') { + return ref.title + } else { + return ref.type + } } if (schema.enum) { @@ -294,10 +367,10 @@ function generateDescription (title, type) { function format (outerIndent, lines) { const result = [`${outerIndent}/**`] - result.push(...lines.map(line =>{ - if(line == NewBlockIndicator){ + result.push(...lines.map(line => { + if (line === NewBlockIndicator) { return ' */\n\n/**' - }else{ + } else { return line ? `${outerIndent} * ${line}` : ' *' } })) diff --git a/test.js b/test.js index 398d239..8d7a2d7 100644 --- a/test.js +++ b/test.js @@ -92,28 +92,6 @@ describe('Simple schemas', () => { }) describe('Schemas with properties', () => { - it('Schema with `$ref` (object)', function () { - const schema = { - $defs: { // New name for `definitions` - definitionType: { - type: 'number' - } - }, - type: 'object', - properties: { - aNumberProp: { - $ref: '#/$defs/definitionType' - } - } - } - const expected = `/** - * @typedef {object} - * @property {number} [aNumberProp] - */ -` - expect(generate(schema)).toEqual(expected) - }) - it('Object with properties', function () { const schema = { type: 'object', @@ -260,10 +238,11 @@ describe('Schemas with properties', () => { }) describe('Schemas with items', function () { - it('Schema with `$ref` (array with items array)', function () { + it('Schema with `$ref` (array with items array), default', function () { const schema = { $defs: { // New name for `definitions` definitionType: { + title: 'customNumber', type: 'number' } }, @@ -276,6 +255,36 @@ describe('Schemas with items', function () { * @typedef {array} * @property {number} 0 */ + +/** + * @typedef {number} customNumber + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('Schema with `$ref` (array with items array), is-a', function () { + const schema = { + $defs: { // New name for `definitions` + definitionType: { + title: 'customNumber', + type: 'number' + } + }, + type: 'array', + items: [{ + $ref: '#/$defs/definitionType', + classRelation: 'is-a' + }] + } + const expected = `/** + * @typedef {array} + * @property {customNumber} 0 + */ + +/** + * @typedef {number} customNumber + */ ` expect(generate(schema)).toEqual(expected) }) @@ -284,6 +293,7 @@ describe('Schemas with items', function () { const schema = { $defs: { // New name for `definitions` definitionType: { + title: 'customNumber', type: 'number' } }, @@ -295,6 +305,10 @@ describe('Schemas with items', function () { const expected = `/** * @typedef {array} */ + +/** + * @typedef {number} customNumber + */ ` expect(generate(schema)).toEqual(expected) }) @@ -395,6 +409,574 @@ describe('Schemas with items', function () { }) }) +describe('Nested schemas', () => { + it('Simple Object reference default, first element', () => { + const schema = { + $defs: { + Person: { + title: 'Person', + type: 'object', + properties: { + name: { type: 'string', description: "A person's name" }, + age: { type: 'integer', description: "A person's age" } + } + } + }, + title: 'Laborer', + $ref: '#/$defs/Person' + } + const expected = `/** + * @typedef {object} Laborer + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ + +/** + * @typedef {object} Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('Simple Object reference default, nested element', () => { + const schema = { + $defs: { + Person: { + title: 'Person', + type: 'object', + properties: { + name: { type: 'string', description: "A person's name" }, + age: { type: 'integer', description: "A person's age" } + } + } + }, + title: 'LaborerList', + type: 'object', + properties: { + Laborer: { + title: 'Laborer', + $ref: '#/$defs/Person' + } + } + } + const expected = `/** + * @typedef {object} LaborerList + * @property {object} [Laborer] + * @property {string} [Laborer.name] A person's name + * @property {integer} [Laborer.age] A person's age + */ + +/** + * @typedef {object} Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('Simple Object reference is-a, first element', () => { + const schema = { + $defs: { + Person: { + title: 'Person', + type: 'object', + properties: { + name: { type: 'string', description: "A person's name" }, + age: { type: 'integer', description: "A person's age" } + } + } + }, + title: 'Laborer', + $ref: '#/$defs/Person', + classRelation: 'is-a' + } + const expected = `/** + * @typedef {Person} Laborer + */ + +/** + * @typedef {object} Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('Simple Object reference is-a, nested element', () => { + const schema = { + $defs: { + Person: { + title: 'Person', + type: 'object', + properties: { + name: { type: 'string', description: "A person's name" }, + age: { type: 'integer', description: "A person's age" } + } + } + }, + title: 'LaborerList', + type: 'object', + properties: { + Laborer: { + title: 'Laborer', + $ref: '#/$defs/Person', + classRelation: 'is-a' + } + } + } + const expected = `/** + * @typedef {object} LaborerList + * @property {Person} [Laborer] + */ + +/** + * @typedef {object} Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('allOf reference default', () => { + const schema = { + $defs: { + Person: { + title: 'Person', + type: 'object', + properties: { + name: { type: 'string', description: "A person's name" }, + age: { type: 'integer', description: "A person's age" } + } + }, + Address: { + title: 'Address', + type: 'object', + properties: { + town: { type: 'string', description: 'The town' }, + street: { type: 'string', description: 'The street' }, + number: { type: 'integer', description: 'The number' } + } + } + }, + title: 'List', + type: 'object', + properties: { + Laborer: { + title: 'Laborer', + allOf: [ + { $ref: '#/$defs/Person' }, + { $ref: '#/$defs/Address' } + ] + } + } + } + const expected = `/** + * @typedef {object} List + * @property {object} [Laborer] + * @property {string} [Laborer.name] A person's name + * @property {integer} [Laborer.age] A person's age + * @property {string} [Laborer.town] The town + * @property {string} [Laborer.street] The street + * @property {integer} [Laborer.number] The number + */ + +/** + * @typedef {object} Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ + +/** + * @typedef {object} Address + * @property {string} [town] The town + * @property {string} [street] The street + * @property {integer} [number] The number + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('allOf reference is-a', () => { + const schema = { + $defs: { + Person: { + title: 'Person', + type: 'object', + properties: { + name: { type: 'string', description: "A person's name" }, + age: { type: 'integer', description: "A person's age" } + } + }, + Address: { + title: 'Address', + type: 'object', + properties: { + town: { type: 'string', description: 'The town' }, + street: { type: 'string', description: 'The street' }, + number: { type: 'integer', description: 'The number' } + } + } + }, + title: 'List', + type: 'object', + properties: { + Laborer: { + title: 'Laborer', + allOf: [ + { $ref: '#/$defs/Person', classRelation: 'is-a' }, + { $ref: '#/$defs/Address', classRelation: 'is-a' } + ] + } + } + } + const expected = `/** + * @typedef {object} List + * @property {Person&Address} [Laborer] + */ + +/** + * @typedef {object} Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ + +/** + * @typedef {object} Address + * @property {string} [town] The town + * @property {string} [street] The street + * @property {integer} [number] The number + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('allOf reference nested object', () => { + const schema = { + title: 'List', + type: 'object', + properties: { + Laborer: { + title: 'Laborer', + allOf: [ + { + type: 'object', + properties: { + Number: { type: 'number', description: 'A number' }, + String: { type: 'string', description: 'A String' } + } + } + ] + } + } + } + const expected = `/** + * @typedef {object} List + * @property {object} [Laborer] + * @property {number} [Laborer.Number] A number + * @property {string} [Laborer.String] A String + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('allOf reference is-a, default and nested Object', () => { + const schema = { + $defs: { + Person: { + title: 'Person', + type: 'object', + properties: { + name: { type: 'string', description: "A person's name" }, + age: { type: 'integer', description: "A person's age" } + } + }, + Address: { + title: 'Address', + type: 'object', + properties: { + town: { type: 'string', description: 'The town' }, + street: { type: 'string', description: 'The street' }, + number: { type: 'integer', description: 'The number' } + } + } + }, + title: 'List', + type: 'object', + properties: { + Laborer: { + title: 'Laborer', + allOf: [ + { $ref: '#/$defs/Person' }, + { $ref: '#/$defs/Address', classRelation: 'is-a' }, + { + type: 'object', + properties: { + Number: { type: 'number', description: 'A number' }, + String: { type: 'string', description: 'A String' } + } + } + ] + } + } + } + const expected = `/** + * @typedef {object} List + * @property {Address} [Laborer] + * @property {string} [Laborer.name] A person's name + * @property {integer} [Laborer.age] A person's age + * @property {number} [Laborer.Number] A number + * @property {string} [Laborer.String] A String + */ + +/** + * @typedef {object} Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ + +/** + * @typedef {object} Address + * @property {string} [town] The town + * @property {string} [street] The street + * @property {integer} [number] The number + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('anyOf reference default', () => { + const schema = { + $defs: { + Person: { + title: 'Person', + type: 'object', + properties: { + name: { type: 'string', description: "A person's name" }, + age: { type: 'integer', description: "A person's age" } + } + }, + Address: { + title: 'Address', + type: 'object', + properties: { + town: { type: 'string', description: 'The town' }, + street: { type: 'string', description: 'The street' }, + number: { type: 'integer', description: 'The number' } + } + } + }, + title: 'List', + type: 'object', + properties: { + Laborer: { + title: 'Laborer', + anyOf: [ + { $ref: '#/$defs/Person' }, + { $ref: '#/$defs/Address' } + ] + } + } + } + const expected = `/** + * @typedef {object} List + * @property {Laborer_Person|Laborer_Address} [Laborer] + */ + +/** + * @typedef {object} Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ + +/** + * @typedef {object} Address + * @property {string} [town] The town + * @property {string} [street] The street + * @property {integer} [number] The number + */ + +/** + * @typedef {object} Laborer_Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ + +/** + * @typedef {object} Laborer_Address + * @property {string} [town] The town + * @property {string} [street] The street + * @property {integer} [number] The number + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('anyOf reference is-a', () => { + const schema = { + $defs: { + Person: { + title: 'Person', + type: 'object', + properties: { + name: { type: 'string', description: "A person's name" }, + age: { type: 'integer', description: "A person's age" } + } + }, + Address: { + title: 'Address', + type: 'object', + properties: { + town: { type: 'string', description: 'The town' }, + street: { type: 'string', description: 'The street' }, + number: { type: 'integer', description: 'The number' } + } + } + }, + title: 'List', + type: 'object', + properties: { + Laborer: { + title: 'Laborer', + anyOf: [ + { $ref: '#/$defs/Person', classRelation: 'is-a' }, + { $ref: '#/$defs/Address', classRelation: 'is-a' } + ] + } + } + } + const expected = `/** + * @typedef {object} List + * @property {Person|Address} [Laborer] + */ + +/** + * @typedef {object} Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ + +/** + * @typedef {object} Address + * @property {string} [town] The town + * @property {string} [street] The street + * @property {integer} [number] The number + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('anyOf reference nested object', () => { + const schema = { + title: 'List', + type: 'object', + properties: { + Laborer: { + title: 'Laborer', + anyOf: [ + { + type: 'object', + properties: { + Number: { type: 'number', description: 'A number' }, + String: { type: 'string', description: 'A String' } + } + } + ] + } + } + } + const expected = `/** + * @typedef {object} List + * @property {Laborer_subtype0} [Laborer] + */ + +/** + * @typedef {object} Laborer_subtype0 + * @property {number} [Number] A number + * @property {string} [String] A String + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('anyOf reference is-a, default and nested Object', () => { + const schema = { + $defs: { + Person: { + title: 'Person', + type: 'object', + properties: { + name: { type: 'string', description: "A person's name" }, + age: { type: 'integer', description: "A person's age" } + } + }, + Address: { + title: 'Address', + type: 'object', + properties: { + town: { type: 'string', description: 'The town' }, + street: { type: 'string', description: 'The street' }, + number: { type: 'integer', description: 'The number' } + } + } + }, + title: 'List', + type: 'object', + properties: { + Laborer: { + title: 'Laborer', + anyOf: [ + { $ref: '#/$defs/Person' }, + { $ref: '#/$defs/Address', classRelation: 'is-a' }, + { + type: 'object', + properties: { + Number: { type: 'number', description: 'A number' }, + String: { type: 'string', description: 'A String' } + } + } + ] + } + } + } + const expected = `/** + * @typedef {object} List + * @property {Address|Laborer_Person|Laborer_subtype0} [Laborer] + */ + +/** + * @typedef {object} Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ + +/** + * @typedef {object} Address + * @property {string} [town] The town + * @property {string} [street] The street + * @property {integer} [number] The number + */ + +/** + * @typedef {object} Laborer_Person + * @property {string} [name] A person's name + * @property {integer} [age] A person's age + */ + +/** + * @typedef {object} Laborer_subtype0 + * @property {number} [Number] A number + * @property {string} [String] A String + */ +` + expect(generate(schema)).toEqual(expected) + }) +}) + describe('option: `autoDescribe`', function () { it('Simple object with `autoDescribe`: true', function () { const schema = { From e2258077d7d10a4ec69eaa28f0e16551c656abce Mon Sep 17 00:00:00 2001 From: Rainer Date: Fri, 18 Sep 2020 12:41:19 +0200 Subject: [PATCH 3/4] Fix merge errors --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 2febffb..7868695 100644 --- a/index.js +++ b/index.js @@ -185,7 +185,7 @@ function processObject (obj, objName, prefixedProperty, required, rootSchema, co } if (refsIsA.length === 0 && refsAnyOf.length === 0) { - if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, true, defaultValue, config)) + if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, optional, defaultValue, config)) } else { const optional = !required.includes(objName) let type = '' From ed3b69c2c75468371be5f806946503ff00adde8f Mon Sep 17 00:00:00 2001 From: Rainer Date: Fri, 9 Oct 2020 12:58:13 +0200 Subject: [PATCH 4/4] Fix merging errors --- index.js | 38 +++++++++++++++++++------------------- test.js | 28 +++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/index.js b/index.js index 445bddc..37d028a 100644 --- a/index.js +++ b/index.js @@ -101,14 +101,14 @@ function processItems (schema, rootSchema, base, config) { const defaultValue = item.default const optional = !schema.minItems || i >= schema.minItems if (item.type === 'array' && item.items) { - result.push(...writeProperty('array', prefixedProperty, item.description, optional, defaultValue, schema, config)) + result.push(...writeProperty('array', prefixedProperty, item.description, optional, defaultValue, schema, rootSchema, config)) result.push(...processItems(item, rootSchema, prefixedProperty, config)) } else if (item.type === 'object' && item.properties) { - result.push(...writeProperty('object', prefixedProperty, item.description, optional, defaultValue, schema, config)) + result.push(...writeProperty('object', prefixedProperty, item.description, optional, defaultValue, schema, rootSchema, config)) result.push(...processProperties(item, rootSchema, prefixedProperty, config)) } else { const type = getSchemaType(item, rootSchema) || getDefaultPropertyType(config) - result.push(...writeProperty(type, prefixedProperty, item.description, optional, defaultValue, item, config)) + result.push(...writeProperty(type, prefixedProperty, item.description, optional, defaultValue, item, rootSchema, config)) } }) return result @@ -171,7 +171,7 @@ function processObject (obj, objName, prefixedProperty, required, rootSchema, co generatedDefs.push(e) } for (const e of entries) { - const type = getType(e, rootSchema) || getDefaultPropertyType(config, objName) + const type = getSchemaType(e, rootSchema, config) || getDefaultPropertyType(config, objName) if (type === 'object') { if (e.title) { e.title = `${obj.title}_${e.title}` @@ -186,20 +186,20 @@ function processObject (obj, objName, prefixedProperty, required, rootSchema, co } if (refsIsA.length === 0 && refsAnyOf.length === 0) { - if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, optional, defaultValue, config)) + if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, optional, defaultValue, obj, rootSchema, config)) } else { const optional = !required.includes(objName) let type = '' for (let i = 0; i < refsIsA.length; i++) { if (type !== '') type += separator - type += getType(refsIsA[i], rootSchema) + type += getSchemaType(refsIsA[i], rootSchema) } for (let i = 0; i < refsAnyOf.length; i++) { if (type !== '') type += separator type += refsAnyOf[i].title } if (type === '') type = getDefaultPropertyType(config, objName) - if (!rootElement) result.push(...writeProperty(type, prefixedProperty, obj.description, optional, defaultValue, config)) + if (!rootElement) result.push(...writeProperty(type, prefixedProperty, obj.description, optional, defaultValue, obj, rootSchema, config)) } if (obj.allOf) { @@ -213,22 +213,22 @@ function processObject (obj, objName, prefixedProperty, required, rootSchema, co } else { if (obj.$ref) { if (obj.classRelation && obj.classRelation === 'is-a') { - const type = getType(obj, rootSchema) || getDefaultPropertyType(config, objName) - if (!rootElement) result.push(...writeProperty(type, prefixedProperty, obj.description, optional, defaultValue, config)) + const type = getSchemaType(obj, rootSchema) || getDefaultPropertyType(config, objName) + if (!rootElement) result.push(...writeProperty(type, prefixedProperty, obj.description, optional, defaultValue, obj, rootSchema, config)) } else { - if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, optional, defaultValue, config)) + if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, optional, defaultValue, obj, rootSchema, config)) result.push(...processProperties(json.get(rootSchema, obj.$ref.slice(1)), rootSchema, prefixedProperty, config)) } } else { if (obj.type === 'object' && obj.properties) { - if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, optional, defaultValue, config)) + if (!rootElement) result.push(...writeProperty('object', prefixedProperty, obj.description, optional, defaultValue, obj, rootSchema, config)) result.push(...processProperties(obj, rootSchema, prefixedProperty, config)) } else if (obj.type === 'array' && obj.items) { - if (!rootElement) result.push(...writeProperty('array', prefixedProperty, obj.description, optional, defaultValue, config)) + if (!rootElement) result.push(...writeProperty('array', prefixedProperty, obj.description, optional, defaultValue, obj, rootSchema, config)) result.push(...processItems(obj, rootSchema, prefixedProperty, config)) } else { - const type = getType(obj, rootSchema) || getDefaultPropertyType(config, objName) - if (!rootElement) result.push(...writeProperty(type, prefixedProperty, obj.description, optional, defaultValue, config)) + const type = getSchemaType(obj, rootSchema) || getDefaultPropertyType(config, objName) + if (!rootElement) result.push(...writeProperty(type, prefixedProperty, obj.description, optional, defaultValue, obj, rootSchema, config)) } } } @@ -270,7 +270,7 @@ function getSchemaType (schema, rootSchema) { return schema.type } -function getType (schema, config, type) { +function getType (schema, rootSchema, config, type) { const typeCheck = type || schema.type let typeMatch if (schema.format) { @@ -295,7 +295,7 @@ function getType (schema, config, type) { typeStr = ` {${ typeMatch === '' ? '' - : typeMatch || type || getSchemaType(schema, schema) + : typeMatch || type || getSchemaType(schema, rootSchema) }}` } @@ -346,7 +346,7 @@ function writeDescription (schema, rootSchema, config) { description = config.autoDescribe ? generateDescription(schema.title, schema.type) : '' } - const type = getType(schema, config) + const type = getType(schema, rootSchema, config) if (description || config.addDescriptionLineBreak) { result.push(...wrapDescription(config, description)) @@ -358,8 +358,8 @@ function writeDescription (schema, rootSchema, config) { return result } -function writeProperty (type, field, description = '', optional, defaultValue, schema, config) { - const typeExpression = getType(schema, config, type) +function writeProperty (type, field, description = '', optional, defaultValue, schema, rootSchema, config) { + const typeExpression = getType(schema, rootSchema, config, type) let fieldTemplate = ' ' if (optional) { diff --git a/test.js b/test.js index 5ed9c86..9e7e952 100644 --- a/test.js +++ b/test.js @@ -363,6 +363,7 @@ describe('Schemas with items', function () { } }, type: 'array', + minItems: 1, items: [{ $ref: '#/$defs/definitionType', classRelation: 'is-a' @@ -380,7 +381,7 @@ describe('Schemas with items', function () { expect(generate(schema)).toEqual(expected) }) - it('Schema with `$ref` (array with items object)', function () { + it('Schema with `$ref` (array with items object) ,default', function () { const schema = { $defs: { // New name for `definitions` definitionType: { @@ -397,6 +398,31 @@ describe('Schemas with items', function () { * @typedef {array} */ +/** + * @typedef {number} customNumber + */ +` + expect(generate(schema)).toEqual(expected) + }) + + it('Schema with `$ref` (array with items object), is-a', function () { + const schema = { + $defs: { // New name for `definitions` + definitionType: { + title: 'customNumber', + type: 'number' + } + }, + type: 'array', + items: { + $ref: '#/$defs/definitionType', + classRelation: 'is-a' + } + } + const expected = `/** + * @typedef {array} + */ + /** * @typedef {number} customNumber */