├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .husky
└── pre-commit
├── .npmignore
├── LICENSE-ISC.txt
├── example.js
├── index.js
├── package-lock.json
├── package.json
├── readme.md
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; EditorConfig file: https://EditorConfig.org
2 | ; Install the "EditorConfig" plugin into your editor to use
3 |
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | end_of_line = lf
9 | insert_final_newline = true
10 | indent_style = space
11 | indent_size = 2
12 | trim_trailing_whitespace = true
13 |
14 | [*.md]
15 | indent_size = 4
16 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | !*.js
2 | out
3 | node_modules
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | extends: ['eslint:recommended', 'standard'],
5 | env: {
6 | jest: true,
7 | node: true
8 | },
9 | parserOptions: {
10 | ecmaVersion: 2018
11 | },
12 | rules: {
13 | 'no-var': ['error'],
14 | 'prefer-destructuring': ['error'],
15 | 'object-shorthand': ['error'],
16 | 'prefer-template': ['error']
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [20.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v3
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: npm ci
30 | - run: npm test
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
3 | # Generated files
4 | out/
5 | docs.js
6 | coverage
7 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npx eslint --report-unused-disable-directives --fix .
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | coverage
2 | out
3 | .nyc_output
4 |
--------------------------------------------------------------------------------
/LICENSE-ISC.txt:
--------------------------------------------------------------------------------
1 | ISC License (ISC)
2 |
3 | Copyright 2017-2020, Francis Nepomuceno
4 | Copyright 2020, Brett Zamir
5 |
6 | Permission to use, copy, modify, and/or distribute this software for any
7 | purpose with or without fee is hereby granted, provided that the above
8 | copyright notice and this permission notice appear in all copies.
9 |
10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
13 | SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
14 | RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
15 | NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
16 | USE OR PERFORMANCE OF THIS SOFTWARE.
17 |
--------------------------------------------------------------------------------
/example.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const generate = require('./index')
3 | const fs = require('fs')
4 |
5 | const schema = {
6 | id: 'Person',
7 | type: 'object',
8 | properties: {
9 | name: { type: 'string', description: "A person's name" },
10 | age: { type: 'integer', description: "A person's age" }
11 | },
12 | required: ['name']
13 | }
14 |
15 | fs.writeFileSync('docs.js', generate(schema))
16 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const json = require('json-pointer')
4 |
5 | function getDefaultPropertyType ({
6 | propertyNameAsType, capitalizeProperty, defaultPropertyType
7 | }, property) {
8 | const fallbackPropertyType = '*'
9 |
10 | if (property !== undefined && propertyNameAsType) {
11 | return capitalizeProperty ? upperFirst(property) : property
12 | }
13 | if (defaultPropertyType === null || defaultPropertyType === '') {
14 | return defaultPropertyType
15 | }
16 | return defaultPropertyType || fallbackPropertyType
17 | }
18 |
19 | function wrapDescription (config, description) {
20 | const { maxLength } = config
21 | if (!maxLength) {
22 | return [description]
23 | }
24 | const result = []
25 |
26 | while (true) {
27 | const excess = description.length - config.indentMaxDelta
28 | if (excess <= 0) {
29 | if (description) {
30 | result.push(description)
31 | }
32 | break
33 | }
34 | const maxLine = description.slice(0, config.indentMaxDelta)
35 | const wsIndex = maxLine.search(/\s\S*$/)
36 | let safeString
37 | if (wsIndex === -1) {
38 | // With this being all non-whitespace, e.g., a long link, we
39 | // let it go on without wrapping until whitespace is reached
40 | const remainder = description.slice(config.indentMaxDelta).match(/^\S+/)
41 | safeString = maxLine + (remainder || '')
42 | } else {
43 | safeString = maxLine.slice(0, wsIndex)
44 | }
45 | result.push(safeString)
46 | description = description.slice(safeString.length + 1)
47 | }
48 | return result
49 | }
50 |
51 | module.exports = generate
52 |
53 | function generate (schema, options = {}) {
54 | const jsdoc = []
55 |
56 | if (!schema || Object.keys(schema).length === 0) {
57 | return ''
58 | }
59 |
60 | const config = parseOptions(options)
61 |
62 | jsdoc.push(...writeDescription(schema, config))
63 |
64 | if (json.has(schema, '/properties')) {
65 | jsdoc.push(...processProperties(schema, schema, null, config))
66 | }
67 | if (json.has(schema, '/items')) {
68 | jsdoc.push(...processItems(schema, schema, null, config))
69 | }
70 |
71 | return format(config.outerIndent, jsdoc)
72 | }
73 |
74 | function parseOptions (options) {
75 | const asteriskAndWhitespaceLength = 3 // ' * '
76 | const outerIndent = (options.indentChar || ' ').repeat(options.indent || 0)
77 | const indentMaxDelta = options.maxLength - outerIndent.length -
78 | asteriskAndWhitespaceLength
79 |
80 | return {
81 | ...options,
82 | outerIndent,
83 | indentMaxDelta
84 | }
85 | }
86 |
87 | function processItems (schema, rootSchema, base, config) {
88 | const items = json.get(schema, '/items')
89 | if (!Array.isArray(items)) {
90 | return []
91 | }
92 | const result = []
93 | items.forEach((item, i) => {
94 | const root = base ? `${base}.` : ''
95 | const prefixedProperty = root + i
96 | const defaultValue = item.default
97 | const optional = !schema.minItems || i >= schema.minItems
98 | if (item.type === 'array' && item.items) {
99 | result.push(...writeProperty('array', prefixedProperty, item.description, optional, defaultValue, schema, config))
100 | result.push(...processItems(item, rootSchema, prefixedProperty, config))
101 | } else if (item.type === 'object' && item.properties) {
102 | result.push(...writeProperty('object', prefixedProperty, item.description, optional, defaultValue, schema, config))
103 | result.push(...processProperties(item, rootSchema, prefixedProperty, config))
104 | } else {
105 | const type = getSchemaType(item, rootSchema) || getDefaultPropertyType(config)
106 | result.push(...writeProperty(type, prefixedProperty, item.description, optional, defaultValue, item, config))
107 | }
108 | })
109 | return result
110 | }
111 |
112 | function processProperties (schema, rootSchema, base, config) {
113 | const props = json.get(schema, '/properties')
114 | const required = json.has(schema, '/required') ? json.get(schema, '/required') : []
115 | const result = []
116 |
117 | for (const property in props) {
118 | if (Array.isArray(config.ignore) && config.ignore.includes(property)) {
119 | continue
120 | } else {
121 | const prop = props[property]
122 | const root = base ? `${base}.` : ''
123 | const prefixedProperty = root + property
124 | const defaultValue = prop.default
125 | const optional = !required.includes(property)
126 | if (prop.type === 'object' && prop.properties) {
127 | result.push(...writeProperty('object', prefixedProperty, prop.description, optional, defaultValue, schema, config))
128 | result.push(...processProperties(prop, rootSchema, prefixedProperty, config))
129 | } else if (prop.type === 'array' && prop.items) {
130 | result.push(...writeProperty('array', prefixedProperty, prop.description, optional, defaultValue, schema, config))
131 | result.push(...processItems(prop, rootSchema, prefixedProperty, config))
132 | } else {
133 | const type = getSchemaType(prop, rootSchema) || getDefaultPropertyType(config, property)
134 | result.push(...writeProperty(type, prefixedProperty, prop.description, optional, defaultValue, prop, config))
135 | }
136 | }
137 | }
138 | return result
139 | }
140 |
141 | function getSchemaType (schema, rootSchema) {
142 | if (schema.$ref) {
143 | const ref = json.get(rootSchema, schema.$ref.slice(1))
144 | return getSchemaType(ref, rootSchema)
145 | }
146 |
147 | if (schema.enum) {
148 | if (schema.type === 'string') {
149 | return `"${schema.enum.join('"|"')}"`
150 | }
151 | if (
152 | schema.type === 'number' || schema.type === 'integer' ||
153 | schema.type === 'boolean'
154 | ) {
155 | return `${schema.enum.join('|')}`
156 | }
157 | // Enum can represent more complex types such as array or object
158 | // It can also include a mixture of different types
159 | // Currently, these scenarios are not handled
160 | return schema.type === 'null' ? 'null' : 'enum'
161 | }
162 |
163 | if (schema.const !== undefined) {
164 | if (schema.type === 'string') {
165 | return `"${schema.const}"`
166 | }
167 | if (
168 | schema.type === 'number' || schema.type === 'integer' ||
169 | schema.type === 'boolean'
170 | ) {
171 | return `${schema.const}`
172 | }
173 | // Const can also be of more complex types like arrays or objects
174 | // As of now, these cases are not addressed
175 | return schema.type === 'null' ? 'null' : 'const'
176 | }
177 |
178 | if (Array.isArray(schema.type)) {
179 | if (schema.type.includes('null')) {
180 | return `?${schema.type[0]}`
181 | } else {
182 | return schema.type.join('|')
183 | }
184 | }
185 |
186 | return schema.type
187 | }
188 |
189 | function getType (schema, config, type) {
190 | const typeCheck = type || schema.type
191 | let typeMatch
192 | if (schema.format) {
193 | typeMatch = config.formats && config.formats[schema.format] &&
194 | config.formats[schema.format][typeCheck]
195 | }
196 | if (typeMatch === undefined || typeMatch === null) {
197 | typeMatch = config.types && config.types[typeCheck]
198 | }
199 |
200 | let typeStr
201 | if (config.types === null || config.formats === null ||
202 | (config.formats && (
203 | (config.formats[schema.format] === null) ||
204 | (config.formats[schema.format] &&
205 | config.formats[schema.format][typeCheck] === null)
206 | )) ||
207 | (typeMatch !== '' && !typeMatch && (type === null || type === ''))
208 | ) {
209 | typeStr = ''
210 | } else {
211 | typeStr = ` {${
212 | typeMatch === ''
213 | ? ''
214 | : typeMatch || type || getSchemaType(schema, schema)
215 | }}`
216 | }
217 |
218 | return typeStr
219 | }
220 |
221 | function writeDescription (schema, config) {
222 | const result = []
223 | const { objectTagName = 'typedef' } = config
224 | let { description } = schema
225 | if (description === undefined) {
226 | description = config.autoDescribe ? generateDescription(schema.title, schema.type) : ''
227 | }
228 |
229 | const type = getType(schema, config)
230 |
231 | if (description || config.addDescriptionLineBreak) {
232 | result.push(...wrapDescription(config, description))
233 | }
234 |
235 | const namepath = schema.title ? ` ${config.capitalizeTitle ? upperFirst(schema.title) : schema.title}` : ''
236 | result.push(`@${objectTagName}${type}${namepath}`)
237 |
238 | return result
239 | }
240 |
241 | function writeProperty (type, field, description = '', optional, defaultValue, schema, config) {
242 | const typeExpression = getType(schema, config, type)
243 |
244 | let fieldTemplate = ' '
245 | if (optional) {
246 | fieldTemplate += `[${field}${defaultValue === undefined ? '' : `=${JSON.stringify(defaultValue)}`}]`
247 | } else {
248 | fieldTemplate += field
249 | }
250 |
251 | let desc
252 | if (!description && !config.descriptionPlaceholder) {
253 | desc = ''
254 | } else if (config.hyphenatedDescriptions) {
255 | desc = ` - ${description}`
256 | } else {
257 | desc = ` ${description}`
258 | }
259 | return wrapDescription(config, `@property${typeExpression}${fieldTemplate}${desc}`)
260 | }
261 |
262 | function upperFirst (str) {
263 | return str.slice(0, 1).toUpperCase() + str.slice(1)
264 | }
265 |
266 | function generateDescription (title, type) {
267 | const noun = title ? `${title} ${type}` : type
268 | const article = `a${'aeiou'.split('').includes(noun.charAt()) ? 'n' : ''}`
269 |
270 | return `Represents ${article} ${noun}`
271 | }
272 |
273 | function format (outerIndent, lines) {
274 | const result = [`${outerIndent}/**`]
275 |
276 | result.push(...lines.map(line => line ? `${outerIndent} * ${line}` : ' *'))
277 | result.push(`${outerIndent} */\n`)
278 |
279 | return result.join('\n')
280 | }
281 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "json-schema-to-jsdoc",
3 | "version": "1.1.1",
4 | "description": "JSON Schema to JSDoc generator",
5 | "main": "index.js",
6 | "scripts": {
7 | "lint": "eslint --report-unused-disable-directives .",
8 | "test": "jest --coverage",
9 | "example": "node example.js",
10 | "postexample": "jsdoc docs.js"
11 | },
12 | "nyc": {
13 | "check-coverage": true,
14 | "branches": 100,
15 | "lines": 100,
16 | "functions": 100,
17 | "statements": 100
18 | },
19 | "keywords": [
20 | "JSON",
21 | "schema",
22 | "jsdoc",
23 | "jsonschema",
24 | "generator"
25 | ],
26 | "author": "Francis Nepomuceno",
27 | "contributors": [
28 | "Brett Zamir"
29 | ],
30 | "license": "ISC",
31 | "repository": {
32 | "type": "git",
33 | "url": "git+https://github.com/n3ps/json-schema-to-jsdoc.git"
34 | },
35 | "bugs": "https://github.com/n3ps/json-schema-to-jsdoc/issues",
36 | "homepage": "https://github.com/n3ps/json-schema-to-jsdoc",
37 | "engines": {
38 | "node": ">=6.0.0"
39 | },
40 | "dependencies": {
41 | "json-pointer": "^0.6.2"
42 | },
43 | "devDependencies": {
44 | "eslint": "^8.57.0",
45 | "eslint-config-standard": "^17.1.0",
46 | "eslint-plugin-import": "^2.29.1",
47 | "eslint-plugin-node": "^11.1.0",
48 | "eslint-plugin-promise": "^6.1.1",
49 | "eslint-plugin-standard": "^5.0.0",
50 | "husky": "^8.0.3",
51 | "jest": "^29.7.0",
52 | "jsdoc": "^4.0.2"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/json-schema-to-jsdoc)
2 | [](github.com/n3ps/json-schema-to-jsdoc/actions/workflows/node.js.yml/badge.svg)
3 |
4 | [](https://snyk.io/test/github/n3ps/json-schema-to-jsdoc)
5 | [](https://lgtm.com/projects/g/n3ps/json-schema-to-jsdoc/alerts)
6 | [](https://lgtm.com/projects/g/n3ps/json-schema-to-jsdoc/context:javascript)
7 |
8 | # JSON Schema to JSDoc
9 |
10 | Useful when you already have a JSON Schema and want to document the types you want to validate. Works with subschema definitions.
11 |
12 | ## Usage
13 |
14 | ```js
15 | const jsdoc = require('json-schema-to-jsdoc')
16 |
17 | const schema = {
18 | "title": "Person",
19 | "type": "object",
20 | "properties": {
21 | "name": {"type": "string", "description": "A person's name"},
22 | "age": {"type": "integer", "description": "A person's age"}
23 | },
24 | "required": ["name"]
25 | }
26 |
27 | jsdoc(schema /* , optionsObject */)
28 | ```
29 |
30 | ### Output
31 |
32 | ```js
33 | /**
34 | * @typedef {object} Person
35 | * @property {string} name A person's name
36 | * @property {integer} [age] A person's age
37 | */
38 | ```
39 |
40 | ## Examples
41 |
42 | #### `hyphenatedDescriptions`
43 |
44 | ```js
45 | jsdoc(schema, {
46 | hyphenatedDescriptions: true
47 | })
48 | ```
49 |
50 | ```js
51 | /**
52 | * @typedef {object} Person
53 | * @property {string} name - A person's name
54 | * @property {integer} [age] - A person's age
55 | */
56 | ```
57 |
58 | #### `autoDescribe`
59 |
60 | ```js
61 | jsdoc(schema, {
62 | autoDescribe: true
63 | })
64 | ```
65 |
66 | ```js
67 | /**
68 | * Represents a Person object
69 | * @typedef {object} Person
70 | * @property {string} name A person's name
71 | * @property {integer} [age] A person's age
72 | */
73 | ```
74 |
75 | #### `types`
76 |
77 | ```js
78 | jsdoc(schema, {
79 | types: {
80 | object: 'PlainObject'
81 | }
82 | })
83 | ```
84 |
85 | ```js
86 | /**
87 | * @typedef {PlainObject} Person
88 | * @property {string} name A person's name
89 | * @property {integer} [age] A person's age
90 | */
91 | ```
92 |
93 | #### `formats`
94 |
95 | ```js
96 | const schema = {
97 | title: 'Info',
98 | type: 'object',
99 | properties: {
100 | code: {
101 | type: 'string', format: 'html', description: 'The HTML source'
102 | }
103 | },
104 | required: ['code']
105 | }
106 |
107 | jsdoc(schema, {
108 | formats: {
109 | html: {
110 | string: 'HTML'
111 | }
112 | }
113 | })
114 | ```
115 |
116 | ```js
117 | /**
118 | * @typedef {object} Info
119 | * @property {HTML} code The HTML source
120 | */
121 | ```
122 |
123 | ## Options
124 |
125 | `addDescriptionLineBreak`: boolean
126 | Inserts an empty line when `autoDescribe` is `false` and the schema
127 | `description` is empty. Defaults to `false`.
128 |
129 | `autoDescribe`: boolean
130 | Adds a description (`"Represents a/n [
]"`) when the
131 | schema has no `description`. Defaults to `false`.
132 |
133 | `capitalizeProperty`: boolean
134 | When `propertyNameAsType` is `true`, capitalizes the property-as-type,
135 | i.e., `MyTitle` in `@property {MyTitle} myTitle`. Defaults to `false.`
136 |
137 | `capitalizeTitle`: boolean
138 | If a schema `title` is present, capitalizes the schema's `title` in the
139 | output of `@typedef {myType} title`. Defaults to `false`.
140 |
141 | `defaultPropertyType`: null | string
142 | Used when no schema type is present. Defaults to `"*"`.
143 | - `string`: If set to a string, that string will be used (e.g.,
144 | "any", "JSON", "external:JSON"). Note that jsdoc recommends `*` for
145 | any, while TypeScript uses "any". If one defines one's own "JSON"
146 | type, one could use that to clarify that only JSON types are used.
147 | - `null`: Will avoid any type brackets or type being added.
148 |
149 | `descriptionPlaceholder`: boolean
150 | If `false` and there is no `description` for the object `@property`,
151 | this will avoid a hyphen or even a space for `{description}` within
152 | `@property {name}{description}`. Defaults to `false`.
153 |
154 | `hyphenatedDescriptions`: boolean
155 | Inserts a hyphen + space in the `{description}` portion of
156 | `@property {name}{description}` (will add a space, however, unless
157 | `descriptionPlaceholder` is `false`). Defaults to `false`.
158 |
159 | `ignore`: string[]
160 | Property names to ignore adding to output. Defaults to empty array.
161 |
162 | `indent`: number
163 | How many of `indentChar` to precede each line. Defaults to `0` (no
164 | indent). Note that a single space will be added in addition to the
165 | indent for every line of the document block after the first.
166 |
167 | `indentChar`: string
168 | Character to use when `indent` is set (e.g., a tab or space).
169 | Defaults to a space.
170 |
171 | `maxLength`: number | boolean
172 | - `number`: Enforce a maximum length in `@typedef` and `@property`
173 | descriptions (taking into account `indent`/`indentChar`).
174 | - `false`: Prevent wrapping entirely. Defaults to `false`.
175 |
176 | `objectTagName`: string
177 | Tag name to use for objects. Defaults to `typedef`.
178 |
179 | `propertyNameAsType`: boolean
180 | Indicates that the property name (for objects) should be used as the
181 | type name (optionally capitalized with `capitalizeProperty`). Defaults
182 | to `false`.
183 |
184 | `types`: null | {[schemaType: string]: string}
185 | Used to determine output of curly-bracketed type content within
186 | `@typedef {...}`.
187 | If `null` no curly brackets or type content will be shown with the
188 | `@typedef` at all. If the schema `type` matches a property in the object map, and it maps to the empty string, an empty `{}` will result. Otherwise, if there is a `type` match, that string will be used as the curly bracketed type, or if there is no match, the schema's `type` will be used for the bracketed content. Defaults to an empty object map (will always just use the schema's `type`). This property may be used to change the likes of `@typedef {object}` to `@typedef {PlainObject}`.
189 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const generate = require('./index')
4 | const jsdoc = generate
5 |
6 | const trailingSpace = ' '
7 |
8 | const schema = {
9 | title: 'Person',
10 | type: 'object',
11 | properties: {
12 | name: { type: 'string', description: "A person's name" },
13 | age: { type: 'integer', description: "A person's age" }
14 | },
15 | required: ['name']
16 | }
17 |
18 | describe('Simple schemas', () => {
19 | it('Guards', function () {
20 | const inputs = [null, {}, undefined]
21 | inputs.forEach(input => {
22 | expect(generate(input)).toEqual('')
23 | })
24 | })
25 |
26 | it('Simple string', function () {
27 | const schema = { type: 'string' }
28 | const expected = `/**
29 | * @typedef {string}
30 | */
31 | `
32 | expect(generate(schema)).toEqual(expected)
33 | })
34 |
35 | it('Simple string with description', function () {
36 | const schema = { type: 'string', description: 'String description' }
37 | const expected = `/**
38 | * String description
39 | * @typedef {string}
40 | */
41 | `
42 | expect(generate(schema)).toEqual(expected)
43 | })
44 |
45 | it('Simple object with title', function () {
46 | const schema = {
47 | title: 'special',
48 | type: 'object'
49 | }
50 | const expected = `/**
51 | * @typedef {object} special
52 | */
53 | `
54 | expect(generate(schema)).toEqual(expected)
55 | })
56 |
57 | it('String with enum', function () {
58 | const schema = {
59 | type: 'string',
60 | enum: ['some', 'different', 'types']
61 | }
62 | const expected = `/**
63 | * @typedef {"some"|"different"|"types"}
64 | */
65 | `
66 | expect(generate(schema)).toEqual(expected)
67 | })
68 |
69 | it('Number with enum', function () {
70 | const schema = {
71 | type: 'number',
72 | enum: [12, 34.5, 6789]
73 | }
74 | const expected = `/**
75 | * @typedef {12|34.5|6789}
76 | */
77 | `
78 | expect(generate(schema)).toEqual(expected)
79 | })
80 |
81 | it('Integer with enum', function () {
82 | const schema = {
83 | type: 'integer',
84 | enum: [12, 345, 6789]
85 | }
86 | const expected = `/**
87 | * @typedef {12|345|6789}
88 | */
89 | `
90 | expect(generate(schema)).toEqual(expected)
91 | })
92 |
93 | it('Boolean with enum', function () {
94 | const schema = {
95 | type: 'boolean',
96 | enum: [false, true, false]
97 | }
98 | const expected = `/**
99 | * @typedef {false|true|false}
100 | */
101 | `
102 | expect(generate(schema)).toEqual(expected)
103 | })
104 |
105 | it('null with enum', function () {
106 | const schema = {
107 | type: 'null',
108 | enum: [null]
109 | }
110 | const expected = `/**
111 | * @typedef {null}
112 | */
113 | `
114 | expect(generate(schema)).toEqual(expected)
115 | })
116 |
117 | it('String with const', function () {
118 | const schema = {
119 | type: 'string',
120 | const: 'value'
121 | }
122 | const expected = `/**
123 | * @typedef {"value"}
124 | */
125 | `
126 | expect(generate(schema)).toEqual(expected)
127 | })
128 |
129 | it('Number with const', function () {
130 | const schema = {
131 | type: 'number',
132 | const: 23.3
133 | }
134 | const expected = `/**
135 | * @typedef {23.3}
136 | */
137 | `
138 | expect(generate(schema)).toEqual(expected)
139 | })
140 |
141 | it('Integer with const', function () {
142 | const schema = {
143 | type: 'integer',
144 | const: 42
145 | }
146 | const expected = `/**
147 | * @typedef {42}
148 | */
149 | `
150 | expect(generate(schema)).toEqual(expected)
151 | })
152 |
153 | it('Boolean with const', function () {
154 | const schema = {
155 | type: 'boolean',
156 | const: true
157 | }
158 | const expected = `/**
159 | * @typedef {true}
160 | */
161 | `
162 | expect(generate(schema)).toEqual(expected)
163 | })
164 |
165 | it('null with const', function () {
166 | const schema = {
167 | type: 'null',
168 | const: null
169 | }
170 | const expected = `/**
171 | * @typedef {null}
172 | */
173 | `
174 | expect(generate(schema)).toEqual(expected)
175 | })
176 |
177 | it('null with const complex type', function () {
178 | const schema = {
179 | type: 'array',
180 | const: ['red', 'green']
181 | }
182 | const expected = `/**
183 | * @typedef {const}
184 | */
185 | `
186 | expect(generate(schema)).toEqual(expected)
187 | })
188 |
189 | it('Simple array with title', function () {
190 | const schema = {
191 | title: 'special',
192 | type: 'array'
193 | }
194 | const expected = `/**
195 | * @typedef {array} special
196 | */
197 | `
198 | expect(generate(schema)).toEqual(expected)
199 | })
200 | })
201 |
202 | describe('Schemas with properties', () => {
203 | it('Schema with `$ref` (object)', function () {
204 | const schema = {
205 | $defs: { // New name for `definitions`
206 | definitionType: {
207 | type: 'number'
208 | }
209 | },
210 | type: 'object',
211 | properties: {
212 | aNumberProp: {
213 | $ref: '#/$defs/definitionType'
214 | }
215 | }
216 | }
217 | const expected = `/**
218 | * @typedef {object}
219 | * @property {number} [aNumberProp]
220 | */
221 | `
222 | expect(generate(schema)).toEqual(expected)
223 | })
224 |
225 | it('Object with properties', function () {
226 | const schema = {
227 | type: 'object',
228 | properties: {
229 | aStringProp: {
230 | type: 'string'
231 | },
232 | anObjectProp: {
233 | type: 'object',
234 | properties: {
235 | aNestedProp: {
236 | description: 'Boolean desc.',
237 | type: 'boolean'
238 | },
239 | aNestedArrayProp: {
240 | description: 'Array desc.',
241 | type: 'array',
242 | minItems: 1,
243 | items: [
244 | {
245 | type: 'number'
246 | }
247 | ]
248 | }
249 | }
250 | },
251 | nullableType: {
252 | type: ['string', 'null']
253 | },
254 | multipleTypes: {
255 | type: ['string', 'number']
256 | },
257 | enumProp: {
258 | enum: ['hello', 'world']
259 | },
260 | enumStringProp: {
261 | type: 'string',
262 | enum: ['hello', 'there', 'world']
263 | }
264 | }
265 | }
266 | const expected = `/**
267 | * @typedef {object}
268 | * @property {string} [aStringProp]
269 | * @property {object} [anObjectProp]
270 | * @property {boolean} [anObjectProp.aNestedProp] Boolean desc.
271 | * @property {array} [anObjectProp.aNestedArrayProp] Array desc.
272 | * @property {number} anObjectProp.aNestedArrayProp.0
273 | * @property {?string} [nullableType]
274 | * @property {string|number} [multipleTypes]
275 | * @property {enum} [enumProp]
276 | * @property {"hello"|"there"|"world"} [enumStringProp]
277 | */
278 | `
279 | expect(generate(schema)).toEqual(expected)
280 | })
281 |
282 | it('Object with properties and `required`', function () {
283 | const schema = {
284 | type: 'object',
285 | properties: {
286 | anObjectProp: {
287 | type: 'object',
288 | required: ['aNestedProp'],
289 | properties: {
290 | aNestedProp: {
291 | type: 'boolean'
292 | },
293 | anotherNestedProp: {
294 | type: 'number'
295 | }
296 | }
297 | },
298 | propWithDefault: {
299 | type: 'string',
300 | default: 'hello'
301 | }
302 | }
303 | }
304 | const expected = `/**
305 | * @typedef {object}
306 | * @property {object} [anObjectProp]
307 | * @property {boolean} anObjectProp.aNestedProp
308 | * @property {number} [anObjectProp.anotherNestedProp]
309 | * @property {string} [propWithDefault="hello"]
310 | */
311 | `
312 | expect(generate(schema)).toEqual(expected)
313 | })
314 |
315 | it('Required object', function () {
316 | const schema = {
317 | type: 'object',
318 | title: 'NestedType',
319 | properties: {
320 | cfg: {
321 | type: 'object',
322 | properties: {
323 | }
324 | }
325 | },
326 | required: [
327 | 'cfg'
328 | ]
329 | }
330 |
331 | const expected = `/**
332 | * @typedef {PlainObject} NestedType
333 | * @property {PlainObject} cfg
334 | */
335 | `
336 |
337 | expect(generate(schema, {
338 | types: {
339 | object: 'PlainObject'
340 | }
341 | })).toEqual(expected)
342 | })
343 |
344 | it('Required array', function () {
345 | const schema = {
346 | type: 'object',
347 | title: 'NestedType',
348 | properties: {
349 | cfg: {
350 | type: 'array',
351 | items: []
352 | }
353 | },
354 | required: [
355 | 'cfg'
356 | ]
357 | }
358 |
359 | const expected = `/**
360 | * @typedef {object} NestedType
361 | * @property {array} cfg
362 | */
363 | `
364 |
365 | expect(generate(schema)).toEqual(expected)
366 | })
367 |
368 | it('Object with untyped property', function () {
369 | const schema = {
370 | type: 'object',
371 | properties: {
372 | anObjectProp: {
373 | type: 'object',
374 | properties: {
375 | aNestedProp: {
376 | },
377 | anotherNestedProp: {
378 | type: 'number'
379 | }
380 | }
381 | }
382 | }
383 | }
384 | const expected = `/**
385 | * @typedef {object}
386 | * @property {object} [anObjectProp]
387 | * @property {*} [anObjectProp.aNestedProp]
388 | * @property {number} [anObjectProp.anotherNestedProp]
389 | */
390 | `
391 | expect(generate(schema)).toEqual(expected)
392 | })
393 |
394 | it('Object with deep nesting', function () {
395 | const schema = {
396 | type: 'object',
397 | properties: {
398 | anObjectProp: {
399 | type: 'object',
400 | properties: {
401 | aNestedProp: {
402 | type: 'object',
403 | properties: {
404 | aDeeplyNestedProp: {
405 | type: 'number'
406 | }
407 | }
408 | }
409 | }
410 | }
411 | }
412 | }
413 | const expected = `/**
414 | * @typedef {object}
415 | * @property {object} [anObjectProp]
416 | * @property {object} [anObjectProp.aNestedProp]
417 | * @property {number} [anObjectProp.aNestedProp.aDeeplyNestedProp]
418 | */
419 | `
420 | expect(generate(schema)).toEqual(expected)
421 | })
422 | })
423 |
424 | describe('Schemas with items', function () {
425 | it('Schema with `$ref` (array with items array)', function () {
426 | const schema = {
427 | $defs: { // New name for `definitions`
428 | definitionType: {
429 | type: 'number'
430 | }
431 | },
432 | type: 'array',
433 | minItems: 1,
434 | items: [{
435 | $ref: '#/$defs/definitionType'
436 | }]
437 | }
438 | const expected = `/**
439 | * @typedef {array}
440 | * @property {number} 0
441 | */
442 | `
443 | expect(generate(schema)).toEqual(expected)
444 | })
445 |
446 | it('Schema with `$ref` (array with items object)', function () {
447 | const schema = {
448 | $defs: { // New name for `definitions`
449 | definitionType: {
450 | type: 'number'
451 | }
452 | },
453 | type: 'array',
454 | items: {
455 | $ref: '#/$defs/definitionType'
456 | }
457 | }
458 | const expected = `/**
459 | * @typedef {array}
460 | */
461 | `
462 | expect(generate(schema)).toEqual(expected)
463 | })
464 |
465 | it('Array with items', function () {
466 | const schema = {
467 | type: 'array',
468 | minItems: 3,
469 | items: [
470 | {
471 | type: 'string'
472 | },
473 | {
474 | type: 'object',
475 | properties: {
476 | aNestedProp: {
477 | description: 'Boolean desc.',
478 | type: 'boolean'
479 | }
480 | }
481 | },
482 | {
483 | type: ['string', 'null']
484 | },
485 | {
486 | type: ['string', 'number']
487 | },
488 | {
489 | enum: ['hello', 'world']
490 | },
491 | {
492 | type: 'string',
493 | default: 'hello'
494 | }
495 | ]
496 | }
497 | const expected = `/**
498 | * @typedef {array}
499 | * @property {string} 0
500 | * @property {object} 1
501 | * @property {boolean} [1.aNestedProp] Boolean desc.
502 | * @property {?string} 2
503 | * @property {string|number} [3]
504 | * @property {enum} [4]
505 | * @property {string} [5="hello"]
506 | */
507 | `
508 | expect(generate(schema)).toEqual(expected)
509 | })
510 |
511 | it('Array with untyped property', function () {
512 | const schema = {
513 | type: 'array',
514 | minItems: 1,
515 | items: [
516 | {
517 | type: 'array',
518 | minItems: 2,
519 | items: [
520 | {
521 | },
522 | {
523 | type: 'number'
524 | }
525 | ]
526 | }
527 | ]
528 | }
529 | const expected = `/**
530 | * @typedef {array}
531 | * @property {array} 0
532 | * @property {*} 0.0
533 | * @property {number} 0.1
534 | */
535 | `
536 | expect(generate(schema)).toEqual(expected)
537 | })
538 |
539 | it('Object with deep nesting', function () {
540 | const schema = {
541 | type: 'object',
542 | properties: {
543 | anObjectProp: {
544 | type: 'object',
545 | properties: {
546 | aNestedProp: {
547 | type: 'object',
548 | properties: {
549 | aDeeplyNestedProp: {
550 | type: 'number'
551 | }
552 | }
553 | }
554 | }
555 | }
556 | }
557 | }
558 | const expected = `/**
559 | * @typedef {object}
560 | * @property {object} [anObjectProp]
561 | * @property {object} [anObjectProp.aNestedProp]
562 | * @property {number} [anObjectProp.aNestedProp.aDeeplyNestedProp]
563 | */
564 | `
565 | expect(generate(schema)).toEqual(expected)
566 | })
567 | })
568 |
569 | describe('option: `autoDescribe`', function () {
570 | it('Simple object with `autoDescribe`: true', function () {
571 | const schema = {
572 | type: 'object'
573 | }
574 | const expected = `/**
575 | * Represents an object
576 | * @typedef {object}
577 | */
578 | `
579 | expect(generate(schema, {
580 | autoDescribe: true
581 | })).toEqual(expected)
582 | })
583 |
584 | it('Object with `title` and `autoDescribe`: true', function () {
585 | const schema = {
586 | type: 'object',
587 | title: 'Title'
588 | }
589 | const expected = `/**
590 | * Represents a Title object
591 | * @typedef {object} Title
592 | */
593 | `
594 | expect(generate(schema, {
595 | autoDescribe: true
596 | })).toEqual(expected)
597 | })
598 | })
599 |
600 | describe('option: `autoDescriptionLineBreak`', () => {
601 | it('Simple object with `addDescriptionLineBreak`: true', function () {
602 | const schema = {
603 | type: 'object'
604 | }
605 | const expected = `/**
606 | *
607 | * @typedef {object}
608 | */
609 | `
610 | expect(generate(schema, {
611 | addDescriptionLineBreak: true
612 | })).toEqual(expected)
613 | })
614 | })
615 |
616 | describe('option: `types`', () => {
617 | it('Simple object with `types`: null', function () {
618 | const schema = {
619 | type: 'object'
620 | }
621 | const expected = `/**
622 | * @typedef
623 | */
624 | `
625 | expect(generate(schema, {
626 | types: null
627 | })).toEqual(expected)
628 | })
629 | it('Simple object with empty string `types`', function () {
630 | const schema = {
631 | type: 'object'
632 | }
633 | const expected = `/**
634 | * @typedef {}
635 | */
636 | `
637 | expect(generate(schema, {
638 | types: {
639 | object: ''
640 | }
641 | })).toEqual(expected)
642 | })
643 | it('Simple object with `types`', function () {
644 | const schema = {
645 | type: 'object'
646 | }
647 | const expected = `/**
648 | * @typedef {PlainObject}
649 | */
650 | `
651 | expect(generate(schema, {
652 | types: {
653 | object: 'PlainObject'
654 | }
655 | })).toEqual(expected)
656 | })
657 | })
658 |
659 | describe('option: `formats`', () => {
660 | it('Simple object with `formats`: null', function () {
661 | const schema = {
662 | type: 'object',
663 | format: 'special'
664 | }
665 | const expected = `/**
666 | * @typedef
667 | */
668 | `
669 | expect(generate(schema, {
670 | formats: null
671 | })).toEqual(expected)
672 | })
673 |
674 | it('Simple object with `formats`: null for format', function () {
675 | const schema = {
676 | type: 'object',
677 | format: 'special'
678 | }
679 | const expected = `/**
680 | * @typedef
681 | */
682 | `
683 | expect(generate(schema, {
684 | formats: {
685 | special: null
686 | }
687 | })).toEqual(expected)
688 | })
689 |
690 | it('Simple object with `formats`: null for type and format', function () {
691 | const schema = {
692 | type: 'object',
693 | format: 'special'
694 | }
695 | const expected = `/**
696 | * @typedef
697 | */
698 | `
699 | expect(generate(schema, {
700 | formats: {
701 | special: {
702 | object: null
703 | }
704 | }
705 | })).toEqual(expected)
706 | })
707 |
708 | it('Simple object with empty string `formats`', function () {
709 | const schema = {
710 | type: 'object',
711 | format: 'special'
712 | }
713 | const expected = `/**
714 | * @typedef {}
715 | */
716 | `
717 | expect(generate(schema, {
718 | formats: {
719 | special: {
720 | object: ''
721 | }
722 | }
723 | })).toEqual(expected)
724 | })
725 | it('Simple object with `formats`', function () {
726 | const schema = {
727 | type: 'object',
728 | format: 'special'
729 | }
730 | const expected = `/**
731 | * @typedef {PlainObject}
732 | */
733 | `
734 | expect(generate(schema, {
735 | formats: {
736 | special: {
737 | object: 'PlainObject'
738 | }
739 | }
740 | })).toEqual(expected)
741 | })
742 |
743 | it('Object with properties using `formats`', function () {
744 | const schema = {
745 | type: 'object',
746 | properties: {
747 | anHTMLProp: {
748 | type: 'string',
749 | format: 'html'
750 | }
751 | }
752 | }
753 | const expected = `/**
754 | * @typedef {object}
755 | * @property {HTML} [anHTMLProp]
756 | */
757 | `
758 | expect(generate(schema, {
759 | formats: {
760 | html: {
761 | string: 'HTML'
762 | }
763 | }
764 | })).toEqual(expected)
765 | })
766 | })
767 |
768 | describe('option: `propertyNameAsType`', function () {
769 | it('Object with untyped property', function () {
770 | const schema = {
771 | type: 'object',
772 | properties: {
773 | anObjectProp: {
774 | type: 'object',
775 | properties: {
776 | aNestedProp: {
777 | },
778 | anotherNestedProp: {
779 | type: 'number'
780 | }
781 | }
782 | }
783 | }
784 | }
785 | const expected = `/**
786 | * @typedef {object}
787 | * @property {object} [anObjectProp]
788 | * @property {aNestedProp} [anObjectProp.aNestedProp]
789 | * @property {number} [anObjectProp.anotherNestedProp]
790 | */
791 | `
792 | expect(generate(schema, {
793 | propertyNameAsType: true
794 | })).toEqual(expected)
795 | })
796 | })
797 |
798 | describe('option: `capitalizeProperty`', function () {
799 | it('Object with untyped property', function () {
800 | const schema = {
801 | type: 'object',
802 | properties: {
803 | anObjectProp: {
804 | type: 'object',
805 | properties: {
806 | aNestedProp: {
807 | },
808 | anotherNestedProp: {
809 | type: 'number'
810 | }
811 | }
812 | }
813 | }
814 | }
815 | const expected = `/**
816 | * @typedef {object}
817 | * @property {object} [anObjectProp]
818 | * @property {ANestedProp} [anObjectProp.aNestedProp]
819 | * @property {number} [anObjectProp.anotherNestedProp]
820 | */
821 | `
822 | expect(generate(schema, {
823 | propertyNameAsType: true,
824 | capitalizeProperty: true
825 | })).toEqual(expected)
826 | })
827 | })
828 |
829 | describe('option: `capitalizeTitle`', () => {
830 | it('Simple object with title and `capitalizeTitle`: true', function () {
831 | const schema = {
832 | title: 'special',
833 | type: 'object'
834 | }
835 | const expected = `/**
836 | * @typedef {object} Special
837 | */
838 | `
839 | expect(generate(schema, {
840 | capitalizeTitle: true
841 | })).toEqual(expected)
842 | })
843 | })
844 |
845 | describe('option: `indent`', () => {
846 | it('Object with properties with space indent', function () {
847 | const schema = {
848 | type: 'object',
849 | properties: {
850 | aStringProp: {
851 | type: 'string'
852 | },
853 | anObjectProp: {
854 | type: 'object',
855 | properties: {
856 | aNestedProp: {
857 | description: 'Boolean desc.',
858 | type: 'boolean'
859 | }
860 | }
861 | },
862 | nullableType: {
863 | type: ['string', 'null']
864 | },
865 | multipleTypes: {
866 | type: ['string', 'number']
867 | },
868 | enumProp: {
869 | enum: ['hello', 'world']
870 | }
871 | }
872 | }
873 | const spaces = ' '
874 | const expected = `${spaces}/**
875 | ${spaces} * @typedef {object}
876 | ${spaces} * @property {string} [aStringProp]
877 | ${spaces} * @property {object} [anObjectProp]
878 | ${spaces} * @property {boolean} [anObjectProp.aNestedProp] Boolean desc.
879 | ${spaces} * @property {?string} [nullableType]
880 | ${spaces} * @property {string|number} [multipleTypes]
881 | ${spaces} * @property {enum} [enumProp]
882 | ${spaces} */
883 | `
884 | expect(generate(schema, {
885 | indent: 3
886 | })).toEqual(expected)
887 | })
888 |
889 | it('Object with properties with tab indent', function () {
890 | const schema = {
891 | type: 'object',
892 | properties: {
893 | aStringProp: {
894 | type: 'string'
895 | },
896 | anObjectProp: {
897 | type: 'object',
898 | properties: {
899 | aNestedProp: {
900 | description: 'Boolean desc.',
901 | type: 'boolean'
902 | }
903 | }
904 | },
905 | nullableType: {
906 | type: ['string', 'null']
907 | },
908 | multipleTypes: {
909 | type: ['string', 'number']
910 | },
911 | enumProp: {
912 | enum: ['hello', 'world']
913 | }
914 | }
915 | }
916 | const tabs = '\t\t\t'
917 | const expected = `${tabs}/**
918 | ${tabs} * @typedef {object}
919 | ${tabs} * @property {string} [aStringProp]
920 | ${tabs} * @property {object} [anObjectProp]
921 | ${tabs} * @property {boolean} [anObjectProp.aNestedProp] Boolean desc.
922 | ${tabs} * @property {?string} [nullableType]
923 | ${tabs} * @property {string|number} [multipleTypes]
924 | ${tabs} * @property {enum} [enumProp]
925 | ${tabs} */
926 | `
927 | expect(generate(schema, {
928 | indentChar: '\t',
929 | indent: 3
930 | })).toEqual(expected)
931 | })
932 | })
933 |
934 | describe('option: `descriptionPlaceholder`', () => {
935 | it('Object with properties (with true `descriptionPlaceholder`)', function () {
936 | const schema = {
937 | type: 'object',
938 | properties: {
939 | aStringProp: {
940 | type: 'string'
941 | },
942 | anObjectProp: {
943 | type: 'object',
944 | properties: {
945 | aNestedProp: {
946 | description: 'Boolean desc.',
947 | type: 'boolean'
948 | }
949 | }
950 | },
951 | nullableType: {
952 | type: ['string', 'null']
953 | },
954 | multipleTypes: {
955 | type: ['string', 'number']
956 | },
957 | enumProp: {
958 | enum: ['hello', 'world']
959 | }
960 | }
961 | }
962 | const expected = `/**
963 | * @typedef {object}
964 | * @property {string} [aStringProp]${trailingSpace}
965 | * @property {object} [anObjectProp]${trailingSpace}
966 | * @property {boolean} [anObjectProp.aNestedProp] Boolean desc.
967 | * @property {?string} [nullableType]${trailingSpace}
968 | * @property {string|number} [multipleTypes]${trailingSpace}
969 | * @property {enum} [enumProp]${trailingSpace}
970 | */
971 | `
972 | expect(generate(schema, {
973 | descriptionPlaceholder: true
974 | })).toEqual(expected)
975 | })
976 | })
977 |
978 | describe('option: `hyphenatedDescriptions`', () => {
979 | it('Object with properties (with true `hyphenatedDescriptions`)', function () {
980 | const schema = {
981 | type: 'object',
982 | properties: {
983 | aStringProp: {
984 | type: 'string'
985 | },
986 | anObjectProp: {
987 | type: 'object',
988 | properties: {
989 | aNestedProp: {
990 | description: 'Boolean desc.',
991 | type: 'boolean'
992 | }
993 | }
994 | },
995 | nullableType: {
996 | type: ['string', 'null']
997 | },
998 | multipleTypes: {
999 | type: ['string', 'number']
1000 | },
1001 | enumProp: {
1002 | enum: ['hello', 'world']
1003 | }
1004 | }
1005 | }
1006 | const expected = `/**
1007 | * @typedef {object}
1008 | * @property {string} [aStringProp]
1009 | * @property {object} [anObjectProp]
1010 | * @property {boolean} [anObjectProp.aNestedProp] - Boolean desc.
1011 | * @property {?string} [nullableType]
1012 | * @property {string|number} [multipleTypes]
1013 | * @property {enum} [enumProp]
1014 | */
1015 | `
1016 | expect(generate(schema, {
1017 | hyphenatedDescriptions: true
1018 | })).toEqual(expected)
1019 | })
1020 | })
1021 |
1022 | describe('option: `ignore`', () => {
1023 | it('Object with properties and `ignore` option', function () {
1024 | const schema = {
1025 | type: 'object',
1026 | properties: {
1027 | aStringProp: {
1028 | type: 'string'
1029 | },
1030 | anObjectProp: {
1031 | type: 'object',
1032 | properties: {
1033 | aNestedProp: {
1034 | type: 'boolean'
1035 | }
1036 | }
1037 | }
1038 | }
1039 | }
1040 | const expected = `/**
1041 | * @typedef {object}
1042 | * @property {string} [aStringProp]
1043 | */
1044 | `
1045 | expect(generate(schema, {
1046 | ignore: ['anObjectProp']
1047 | })).toEqual(expected)
1048 | })
1049 | })
1050 |
1051 | describe('option `defaultPropertyType`', function () {
1052 | it('Object with untyped property and "JSON" `defaultPropertyType`', function () {
1053 | const schema = {
1054 | type: 'object',
1055 | properties: {
1056 | anObjectProp: {
1057 | type: 'object',
1058 | properties: {
1059 | aNestedProp: {
1060 | },
1061 | anotherNestedProp: {
1062 | type: 'number'
1063 | }
1064 | }
1065 | }
1066 | }
1067 | }
1068 | const expected = `/**
1069 | * @typedef {object}
1070 | * @property {object} [anObjectProp]
1071 | * @property {JSON} [anObjectProp.aNestedProp]
1072 | * @property {number} [anObjectProp.anotherNestedProp]
1073 | */
1074 | `
1075 | expect(generate(schema, {
1076 | defaultPropertyType: 'JSON'
1077 | })).toEqual(expected)
1078 | })
1079 |
1080 | it('Object with untyped property and `null` `defaultPropertyType`', function () {
1081 | const schema = {
1082 | type: 'object',
1083 | properties: {
1084 | anObjectProp: {
1085 | type: 'object',
1086 | properties: {
1087 | aNestedProp: {
1088 | },
1089 | anotherNestedProp: {
1090 | type: 'number'
1091 | }
1092 | }
1093 | }
1094 | }
1095 | }
1096 | const expected = `/**
1097 | * @typedef {object}
1098 | * @property {object} [anObjectProp]
1099 | * @property [anObjectProp.aNestedProp]
1100 | * @property {number} [anObjectProp.anotherNestedProp]
1101 | */
1102 | `
1103 | expect(generate(schema, {
1104 | defaultPropertyType: null
1105 | })).toEqual(expected)
1106 | })
1107 | })
1108 |
1109 | describe('option: `maxLength`', () => {
1110 | it('Simple object with description and `maxLength`', function () {
1111 | const schema = {
1112 | type: 'object',
1113 | properties: {
1114 | aShortStringProp: {
1115 | description: 'A short description',
1116 | type: 'string'
1117 | },
1118 | aStringProp: {
1119 | description: 'This is a very, very, very, very, very, very, very, very, very, very, very long description on the property.',
1120 | type: 'string'
1121 | },
1122 | aNonBreakingStringProp: {
1123 | description: 'https://example.com/a/very/very/very/very/very/very/very/very/long/nonbreaking/string',
1124 | type: 'string'
1125 | },
1126 | aLongStringBreakingAtEnd: {
1127 | description: 'https://example.com/another/very/very/very/very/very/very/very/lng/string breaking at end',
1128 | type: 'string'
1129 | }
1130 | },
1131 | description: 'This is a very, very, very, very, very, very, very, very, very, very, very long description.'
1132 | }
1133 | const indent = ' '
1134 | const expected = `${indent}/**
1135 | ${indent} * This is a very, very, very, very, very, very, very, very, very, very,
1136 | ${indent} * very long description.
1137 | ${indent} * @typedef {object}
1138 | ${indent} * @property {string} [aShortStringProp] A short description
1139 | ${indent} * @property {string} [aStringProp] This is a very, very, very, very, very,
1140 | ${indent} * very, very, very, very, very, very long description on the property.
1141 | ${indent} * @property {string} [aNonBreakingStringProp]
1142 | ${indent} * https://example.com/a/very/very/very/very/very/very/very/very/long/nonbreaking/string
1143 | ${indent} * @property {string} [aLongStringBreakingAtEnd]
1144 | ${indent} * https://example.com/another/very/very/very/very/very/very/very/lng/string
1145 | ${indent} * breaking at end
1146 | ${indent} */
1147 | `
1148 | expect(generate(schema, {
1149 | indent: 4,
1150 | maxLength: 80
1151 | })).toEqual(expected)
1152 |
1153 | const expectedNowrapping = `${indent}/**
1154 | ${indent} * This is a very, very, very, very, very, very, very, very, very, very, very long description.
1155 | ${indent} * @typedef {object}
1156 | ${indent} * @property {string} [aShortStringProp] A short description
1157 | ${indent} * @property {string} [aStringProp] This is a very, very, very, very, very, very, very, very, very, very, very long description on the property.
1158 | ${indent} * @property {string} [aNonBreakingStringProp] https://example.com/a/very/very/very/very/very/very/very/very/long/nonbreaking/string
1159 | ${indent} * @property {string} [aLongStringBreakingAtEnd] https://example.com/another/very/very/very/very/very/very/very/lng/string breaking at end
1160 | ${indent} */
1161 | `
1162 |
1163 | expect(generate(schema, {
1164 | indent: 4
1165 | })).toEqual(expectedNowrapping)
1166 | })
1167 | })
1168 |
1169 | describe('Examples', () => {
1170 | it('No options', () => {
1171 | const expected = `/**
1172 | * @typedef {object} Person
1173 | * @property {string} name A person's name
1174 | * @property {integer} [age] A person's age
1175 | */
1176 | `
1177 | const result = jsdoc(schema)
1178 | expect(result).toEqual(expected)
1179 | })
1180 |
1181 | it('`hyphenatedDescriptions`', () => {
1182 | const expected = `/**
1183 | * @typedef {object} Person
1184 | * @property {string} name - A person's name
1185 | * @property {integer} [age] - A person's age
1186 | */
1187 | `
1188 | const result = jsdoc(schema, {
1189 | hyphenatedDescriptions: true
1190 | })
1191 | expect(result).toEqual(expected)
1192 | })
1193 |
1194 | it('`autoDescribe`', () => {
1195 | const expected = `/**
1196 | * Represents a Person object
1197 | * @typedef {object} Person
1198 | * @property {string} name A person's name
1199 | * @property {integer} [age] A person's age
1200 | */
1201 | `
1202 | const result = jsdoc(schema, {
1203 | autoDescribe: true
1204 | })
1205 | expect(result).toEqual(expected)
1206 | })
1207 |
1208 | it('`types`', () => {
1209 | const expected = `/**
1210 | * @typedef {PlainObject} Person
1211 | * @property {string} name A person's name
1212 | * @property {integer} [age] A person's age
1213 | */
1214 | `
1215 | const result = jsdoc(schema, {
1216 | types: {
1217 | object: 'PlainObject'
1218 | }
1219 | })
1220 | expect(result).toEqual(expected)
1221 | })
1222 |
1223 | it('`formats`', () => {
1224 | const schema = {
1225 | title: 'Info',
1226 | type: 'object',
1227 | properties: {
1228 | code: {
1229 | type: 'string', format: 'html', description: 'The HTML source'
1230 | }
1231 | },
1232 | required: ['code']
1233 | }
1234 |
1235 | const expected = `/**
1236 | * @typedef {object} Info
1237 | * @property {HTML} code The HTML source
1238 | */
1239 | `
1240 | const result = jsdoc(schema, {
1241 | formats: {
1242 | html: {
1243 | string: 'HTML'
1244 | }
1245 | }
1246 | })
1247 | expect(result).toEqual(expected)
1248 | })
1249 | })
1250 |
--------------------------------------------------------------------------------