Skip to content

Commit 40380ce

Browse files
authored
Fix underscores in interface names (#29)
* Fix nested item naming * Bump TypeScript
1 parent 52204f4 commit 40380ce

File tree

5 files changed

+98
-49
lines changed

5 files changed

+98
-49
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module.exports = {
44
plugins: ['@typescript-eslint', 'prettier'],
55
rules: {
66
'@typescript-eslint/camelcase': 0, // This is perfectly acceptable
7+
'prettier/prettier': 'error',
78
},
89
env: {
910
jest: true,

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,6 @@
7878
"jest": "^24.8.0",
7979
"ts-jest": "^24.0.2",
8080
"tslib": "^1.10.0",
81-
"typescript": "^3.5.1"
81+
"typescript": "^3.5.3"
8282
}
8383
}

src/swagger-2.ts

+40-44
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ function capitalize(str: string): string {
3737
}
3838

3939
function camelCase(name: string): string {
40-
return name.replace(
41-
/(-|_|\.|\s)+\w/g,
42-
(letter): string => letter.toUpperCase().replace(/[^0-9a-z]/gi, '')
40+
return name.replace(/(-|_|\.|\s)+\w/g, (letter): string =>
41+
letter.toUpperCase().replace(/[^0-9a-z]/gi, '')
4342
);
4443
}
4544

@@ -68,6 +67,8 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string {
6867
function getType(definition: Swagger2Definition, nestedName: string): string {
6968
const { $ref, items, type, ...value } = definition;
7069

70+
const nextInterface = camelCase(nestedName); // if this becomes an interface, it’ll need to be camelCased
71+
7172
const DEFAULT_TYPE = 'any';
7273

7374
if ($ref) {
@@ -90,8 +91,9 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string {
9091
if (TYPES[items.type]) {
9192
return `${TYPES[items.type]}[]`;
9293
}
93-
queue.push([nestedName, items]);
94-
return `${nestedName}[]`;
94+
// If this is an array of items, let’s add it to the stack for later
95+
queue.push([nextInterface, items]);
96+
return `${nextInterface}[]`;
9597
}
9698

9799
if (Array.isArray(value.oneOf)) {
@@ -100,8 +102,8 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string {
100102

101103
if (value.properties) {
102104
// If this is a nested object, let’s add it to the stack for later
103-
queue.push([nestedName, { $ref, items, type, ...value }]);
104-
return nestedName;
105+
queue.push([nextInterface, { $ref, items, type, ...value }]);
106+
return nextInterface;
105107
}
106108

107109
if (type) {
@@ -121,17 +123,15 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string {
121123

122124
// Include allOf, if specified
123125
if (Array.isArray(allOf)) {
124-
allOf.forEach(
125-
(item): void => {
126-
// Add “implements“ if this references other items
127-
if (item.$ref) {
128-
const [refName] = getRef(item.$ref);
129-
includes.push(refName);
130-
} else if (item.properties) {
131-
allProperties = { ...allProperties, ...item.properties };
132-
}
126+
allOf.forEach((item): void => {
127+
// Add “implements“ if this references other items
128+
if (item.$ref) {
129+
const [refName] = getRef(item.$ref);
130+
includes.push(refName);
131+
} else if (item.properties) {
132+
allProperties = { ...allProperties, ...item.properties };
133133
}
134-
);
134+
});
135135
}
136136

137137
// If nothing’s here, let’s skip this one.
@@ -149,28 +149,26 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string {
149149
output.push(`export interface ${shouldCamelCase ? camelCase(ID) : ID}${isExtending} {`);
150150

151151
// Populate interface
152-
Object.entries(allProperties).forEach(
153-
([key, value]): void => {
154-
const optional = !Array.isArray(required) || required.indexOf(key) === -1;
155-
const formattedKey = shouldCamelCase ? camelCase(key) : key;
156-
const name = `${sanitize(formattedKey)}${optional ? '?' : ''}`;
157-
const newID = `${ID}${capitalize(formattedKey)}`;
158-
const interfaceType = getType(value, newID);
159-
160-
if (typeof value.description === 'string') {
161-
// Print out descriptions as comments, but only if there’s something there (.*)
162-
output.push(`// ${value.description.replace(/\n$/, '').replace(/\n/g, '\n// ')}`);
163-
}
164-
165-
// Handle enums in the same definition
166-
if (Array.isArray(value.enum)) {
167-
output.push(`${name}: ${value.enum.map(option => JSON.stringify(option)).join(' | ')};`);
168-
return;
169-
}
152+
Object.entries(allProperties).forEach(([key, value]): void => {
153+
const optional = !Array.isArray(required) || required.indexOf(key) === -1;
154+
const formattedKey = shouldCamelCase ? camelCase(key) : key;
155+
const name = `${sanitize(formattedKey)}${optional ? '?' : ''}`;
156+
const newID = `${ID}${capitalize(formattedKey)}`;
157+
const interfaceType = getType(value, newID);
158+
159+
if (typeof value.description === 'string') {
160+
// Print out descriptions as comments, but only if there’s something there (.*)
161+
output.push(`// ${value.description.replace(/\n$/, '').replace(/\n/g, '\n// ')}`);
162+
}
170163

171-
output.push(`${name}: ${interfaceType};`);
164+
// Handle enums in the same definition
165+
if (Array.isArray(value.enum)) {
166+
output.push(`${name}: ${value.enum.map(option => JSON.stringify(option)).join(' | ')};`);
167+
return;
172168
}
173-
);
169+
170+
output.push(`${name}: ${interfaceType};`);
171+
});
174172

175173
if (additionalProperties) {
176174
if ((additionalProperties as boolean) === true) {
@@ -188,14 +186,12 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string {
188186
}
189187

190188
// Begin parsing top-level entries
191-
Object.entries(definitions).forEach(
192-
(entry): void => {
193-
// Ignore top-level array definitions
194-
if (entry[1].type === 'object') {
195-
queue.push(entry);
196-
}
189+
Object.entries(definitions).forEach((entry): void => {
190+
// Ignore top-level array definitions
191+
if (entry[1].type === 'object') {
192+
queue.push(entry);
197193
}
198-
);
194+
});
199195
queue.sort((a, b) => a[0].localeCompare(b[0]));
200196
while (queue.length > 0) {
201197
buildNextInterface();

tests/swagger-2.test.ts

+53-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ describe('Swagger 2 spec', () => {
116116
expect(swaggerToTS(swagger)).toBe(ts);
117117
});
118118

119-
it('handles arrays of complex items', () => {
119+
it('handles arrays of references', () => {
120120
const swagger: Swagger2 = {
121121
definitions: {
122122
Team: {
@@ -145,6 +145,58 @@ describe('Swagger 2 spec', () => {
145145
expect(swaggerToTS(swagger)).toBe(ts);
146146
});
147147

148+
it('handles nested objects', () => {
149+
const swagger: Swagger2 = {
150+
definitions: {
151+
User: {
152+
properties: {
153+
remote_id: {
154+
type: 'object',
155+
properties: { id: { type: 'string' } },
156+
},
157+
},
158+
type: 'object',
159+
},
160+
},
161+
};
162+
163+
const ts = format(`
164+
export interface User {
165+
remote_id?: UserRemoteId;
166+
}
167+
export interface UserRemoteId {
168+
id?: string;
169+
}`);
170+
171+
expect(swaggerToTS(swagger)).toBe(ts);
172+
});
173+
174+
it('handles arrays of nested objects', () => {
175+
const swagger: Swagger2 = {
176+
definitions: {
177+
User: {
178+
properties: {
179+
remote_ids: {
180+
type: 'array',
181+
items: { type: 'object', properties: { id: { type: 'string' } } },
182+
},
183+
},
184+
type: 'object',
185+
},
186+
},
187+
};
188+
189+
const ts = format(`
190+
export interface User {
191+
remote_ids?: UserRemoteIds[];
192+
}
193+
export interface UserRemoteIds {
194+
id?: string;
195+
}`);
196+
197+
expect(swaggerToTS(swagger)).toBe(ts);
198+
});
199+
148200
it('handles allOf', () => {
149201
const swagger: Swagger2 = {
150202
definitions: {

0 commit comments

Comments
 (0)