Skip to content

Commit 62d635f

Browse files
authored
Handle kebab-cased names (#15)
1 parent 7926ca7 commit 62d635f

File tree

2 files changed

+68
-33
lines changed

2 files changed

+68
-33
lines changed

src/swagger-2.ts

+46-33
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ function capitalize(str: string): string {
3737
}
3838

3939
function camelCase(name: string): string {
40-
return name.replace(/(-|_|\.|\s)+\w/g, letter => letter.toUpperCase().replace(/[^0-9a-z]/gi, ''));
40+
return name.replace(
41+
/(-|_|\.|\s)+\w/g,
42+
(letter): string => letter.toUpperCase().replace(/[^0-9a-z]/gi, '')
43+
);
44+
}
45+
46+
function sanitize(name: string): string {
47+
return name.includes('-') ? `'${name}'` : name;
4148
}
4249

4350
function parse(spec: Swagger2, options: Swagger2Options = {}): string {
@@ -88,7 +95,7 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string {
8895
}
8996

9097
if (Array.isArray(value.oneOf)) {
91-
return value.oneOf.map(def => getType(def, '')).join(' | ');
98+
return value.oneOf.map((def): string => getType(def, '')).join(' | ');
9299
}
93100

94101
if (value.properties) {
@@ -114,15 +121,17 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string {
114121

115122
// Include allOf, if specified
116123
if (Array.isArray(allOf)) {
117-
allOf.forEach(item => {
118-
// Add “implements“ if this references other items
119-
if (item.$ref) {
120-
const [refName] = getRef(item.$ref);
121-
includes.push(refName);
122-
} else if (item.properties) {
123-
allProperties = { ...allProperties, ...item.properties };
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+
}
124133
}
125-
});
134+
);
126135
}
127136

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

142151
// Populate interface
143-
Object.entries(allProperties).forEach(([key, value]) => {
144-
const optional = !Array.isArray(required) || required.indexOf(key) === -1;
145-
const formattedKey = shouldCamelCase ? camelCase(key) : key;
146-
const name = `${formattedKey}${optional ? '?' : ''}`;
147-
const newID = `${ID}${capitalize(formattedKey)}`;
148-
const interfaceType = getType(value, newID);
149-
150-
if (typeof value.description === 'string') {
151-
// Print out descriptions as comments, but only if there’s something there (.*)
152-
output.push(`// ${value.description.replace(/\n$/, '').replace(/\n/g, '\n// ')}`);
153-
}
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+
}
154164

155-
// Handle enums in the same definition
156-
if (Array.isArray(value.enum)) {
157-
output.push(`${name}: ${value.enum.map(option => JSON.stringify(option)).join(' | ')};`);
158-
return;
159-
}
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+
}
160170

161-
output.push(`${name}: ${interfaceType};`);
162-
});
171+
output.push(`${name}: ${interfaceType};`);
172+
}
173+
);
163174

164175
if (additionalProperties) {
165176
if ((additionalProperties as boolean) === true) {
@@ -177,12 +188,14 @@ function parse(spec: Swagger2, options: Swagger2Options = {}): string {
177188
}
178189

179190
// Begin parsing top-level entries
180-
Object.entries(definitions).forEach(entry => {
181-
// Ignore top-level array definitions
182-
if (entry[1].type === 'object') {
183-
queue.push(entry);
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+
}
184197
}
185-
});
198+
);
186199
queue.sort((a, b) => a[0].localeCompare(b[0]));
187200
while (queue.length > 0) {
188201
buildNextInterface();

tests/swagger-2.test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,28 @@ describe('Swagger 2 spec', () => {
268268

269269
expect(swaggerToTS(swagger, { camelcase: true })).toBe(ts);
270270
});
271+
272+
it('handles kebab-case property names', () => {
273+
const swagger: Swagger2 = {
274+
definitions: {
275+
User: {
276+
properties: {
277+
'profile-image': { type: 'string' },
278+
'address-line-1': { type: 'string' },
279+
},
280+
type: 'object',
281+
},
282+
},
283+
};
284+
285+
const ts = format(`
286+
export interface User {
287+
'profile-image'?: string;
288+
'address-line-1'?: string;
289+
}`);
290+
291+
expect(swaggerToTS(swagger)).toBe(ts);
292+
});
271293
});
272294

273295
describe('TS features', () => {

0 commit comments

Comments
 (0)