From e9c2ed0a69bf42adc0fc87b93c85dbc35a6c947a Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Thu, 18 Jul 2019 15:26:12 -0600 Subject: [PATCH 1/2] Fix nested item naming --- .eslintrc.js | 1 + src/swagger-2.ts | 84 ++++++++++++++++++++--------------------- tests/swagger-2.test.ts | 54 +++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 45 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 499167c93..8adce0516 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,6 +4,7 @@ module.exports = { plugins: ['@typescript-eslint', 'prettier'], rules: { '@typescript-eslint/camelcase': 0, // This is perfectly acceptable + 'prettier/prettier': 'error', }, env: { jest: true, diff --git a/src/swagger-2.ts b/src/swagger-2.ts index 2d6e522da..bdf0e36fd 100644 --- a/src/swagger-2.ts +++ b/src/swagger-2.ts @@ -37,9 +37,8 @@ function capitalize(str: string): string { } function camelCase(name: string): string { - return name.replace( - /(-|_|\.|\s)+\w/g, - (letter): string => letter.toUpperCase().replace(/[^0-9a-z]/gi, '') + return name.replace(/(-|_|\.|\s)+\w/g, (letter): string => + letter.toUpperCase().replace(/[^0-9a-z]/gi, '') ); } @@ -68,6 +67,8 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string { function getType(definition: Swagger2Definition, nestedName: string): string { const { $ref, items, type, ...value } = definition; + const nextInterface = camelCase(nestedName); // if this becomes an interface, it’ll need to be camelCased + const DEFAULT_TYPE = 'any'; if ($ref) { @@ -90,8 +91,9 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string { if (TYPES[items.type]) { return `${TYPES[items.type]}[]`; } - queue.push([nestedName, items]); - return `${nestedName}[]`; + // If this is an array of items, let’s add it to the stack for later + queue.push([nextInterface, items]); + return `${nextInterface}[]`; } if (Array.isArray(value.oneOf)) { @@ -100,8 +102,8 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string { if (value.properties) { // If this is a nested object, let’s add it to the stack for later - queue.push([nestedName, { $ref, items, type, ...value }]); - return nestedName; + queue.push([nextInterface, { $ref, items, type, ...value }]); + return nextInterface; } if (type) { @@ -121,17 +123,15 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string { // Include allOf, if specified if (Array.isArray(allOf)) { - allOf.forEach( - (item): void => { - // Add “implements“ if this references other items - if (item.$ref) { - const [refName] = getRef(item.$ref); - includes.push(refName); - } else if (item.properties) { - allProperties = { ...allProperties, ...item.properties }; - } + allOf.forEach((item): void => { + // Add “implements“ if this references other items + if (item.$ref) { + const [refName] = getRef(item.$ref); + includes.push(refName); + } else if (item.properties) { + allProperties = { ...allProperties, ...item.properties }; } - ); + }); } // If nothing’s here, let’s skip this one. @@ -149,28 +149,26 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string { output.push(`export interface ${shouldCamelCase ? camelCase(ID) : ID}${isExtending} {`); // Populate interface - Object.entries(allProperties).forEach( - ([key, value]): void => { - const optional = !Array.isArray(required) || required.indexOf(key) === -1; - const formattedKey = shouldCamelCase ? camelCase(key) : key; - const name = `${sanitize(formattedKey)}${optional ? '?' : ''}`; - const newID = `${ID}${capitalize(formattedKey)}`; - const interfaceType = getType(value, newID); - - if (typeof value.description === 'string') { - // Print out descriptions as comments, but only if there’s something there (.*) - output.push(`// ${value.description.replace(/\n$/, '').replace(/\n/g, '\n// ')}`); - } - - // Handle enums in the same definition - if (Array.isArray(value.enum)) { - output.push(`${name}: ${value.enum.map(option => JSON.stringify(option)).join(' | ')};`); - return; - } + Object.entries(allProperties).forEach(([key, value]): void => { + const optional = !Array.isArray(required) || required.indexOf(key) === -1; + const formattedKey = shouldCamelCase ? camelCase(key) : key; + const name = `${sanitize(formattedKey)}${optional ? '?' : ''}`; + const newID = `${ID}${capitalize(formattedKey)}`; + const interfaceType = getType(value, newID); + + if (typeof value.description === 'string') { + // Print out descriptions as comments, but only if there’s something there (.*) + output.push(`// ${value.description.replace(/\n$/, '').replace(/\n/g, '\n// ')}`); + } - output.push(`${name}: ${interfaceType};`); + // Handle enums in the same definition + if (Array.isArray(value.enum)) { + output.push(`${name}: ${value.enum.map(option => JSON.stringify(option)).join(' | ')};`); + return; } - ); + + output.push(`${name}: ${interfaceType};`); + }); if (additionalProperties) { if ((additionalProperties as boolean) === true) { @@ -188,14 +186,12 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string { } // Begin parsing top-level entries - Object.entries(definitions).forEach( - (entry): void => { - // Ignore top-level array definitions - if (entry[1].type === 'object') { - queue.push(entry); - } + Object.entries(definitions).forEach((entry): void => { + // Ignore top-level array definitions + if (entry[1].type === 'object') { + queue.push(entry); } - ); + }); queue.sort((a, b) => a[0].localeCompare(b[0])); while (queue.length > 0) { buildNextInterface(); diff --git a/tests/swagger-2.test.ts b/tests/swagger-2.test.ts index 5a2d1fb04..c90ab46a5 100644 --- a/tests/swagger-2.test.ts +++ b/tests/swagger-2.test.ts @@ -116,7 +116,7 @@ describe('Swagger 2 spec', () => { expect(swaggerToTS(swagger)).toBe(ts); }); - it('handles arrays of complex items', () => { + it('handles arrays of references', () => { const swagger: Swagger2 = { definitions: { Team: { @@ -145,6 +145,58 @@ describe('Swagger 2 spec', () => { expect(swaggerToTS(swagger)).toBe(ts); }); + it('handles nested objects', () => { + const swagger: Swagger2 = { + definitions: { + User: { + properties: { + remote_id: { + type: 'object', + properties: { id: { type: 'string' } }, + }, + }, + type: 'object', + }, + }, + }; + + const ts = format(` + export interface User { + remote_id?: UserRemoteId; + } + export interface UserRemoteId { + id?: string; + }`); + + expect(swaggerToTS(swagger)).toBe(ts); + }); + + it('handles arrays of nested objects', () => { + const swagger: Swagger2 = { + definitions: { + User: { + properties: { + remote_ids: { + type: 'array', + items: { type: 'object', properties: { id: { type: 'string' } } }, + }, + }, + type: 'object', + }, + }, + }; + + const ts = format(` + export interface User { + remote_ids?: UserRemoteIds[]; + } + export interface UserRemoteIds { + id?: string; + }`); + + expect(swaggerToTS(swagger)).toBe(ts); + }); + it('handles allOf', () => { const swagger: Swagger2 = { definitions: { From 76009cfbd27d04c5cea0973b0064aa7a28a2b122 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Thu, 18 Jul 2019 15:32:26 -0600 Subject: [PATCH 2/2] Bump TypeScript --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 17c28271c..2710b78b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6674,9 +6674,9 @@ } }, "typescript": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", - "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index 44ba15160..4f0ada104 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,6 @@ "jest": "^24.8.0", "ts-jest": "^24.0.2", "tslib": "^1.10.0", - "typescript": "^3.5.1" + "typescript": "^3.5.3" } }