Skip to content

Commit df5912c

Browse files
authored
fix: Deal with forbidden chars in description (#272)
When a description includes `*/`, this is causing a broken generated multiline comment, let’s strip them in a smart way to avoid this issue.
1 parent c270a54 commit df5912c

File tree

2 files changed

+93
-17
lines changed

2 files changed

+93
-17
lines changed

plugins/typescript/src/core/schemaToTypeAliasDeclaration.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,64 @@ describe("schemaToTypeAliasDeclaration", () => {
176176
`);
177177
});
178178

179+
it("should pick the first line of the description if the body contains */", () => {
180+
const schema: SchemaObject = {
181+
title: "Deployment branch policy name pattern",
182+
required: ["name"],
183+
type: "object",
184+
properties: {
185+
name: {
186+
type: "string",
187+
description: `The name pattern that branches must match in order to deploy to the environment.
188+
189+
Wildcard characters will not match \`/\`. For example, to match branches that begin with \`release/\` and contain an additional single slash, use \`release/*/*\`.
190+
For more information about pattern matching syntax, see the [Ruby File.fnmatch documentation](https://ruby-doc.org/core-2.5.1/File.html#method-c-fnmatch).`,
191+
example: "release/*",
192+
},
193+
},
194+
};
195+
196+
expect(printSchema(schema)).toMatchInlineSnapshot(`
197+
"export type Test = {
198+
/**
199+
* The name pattern that branches must match in order to deploy to the environment.
200+
*
201+
* [see original specs]
202+
*
203+
* @example release/*
204+
*/
205+
name: string;
206+
};"
207+
`);
208+
});
209+
210+
it("should skip the description if the first line contains */", () => {
211+
const schema: SchemaObject = {
212+
title: "Deployment branch policy name pattern",
213+
required: ["name"],
214+
type: "object",
215+
properties: {
216+
name: {
217+
type: "string",
218+
description: `Wildcard characters will not match \`/\`. For example, to match branches that begin with \`release/\` and contain an additional single slash, use \`release/*/*\`.
219+
For more information about pattern matching syntax, see the [Ruby File.fnmatch documentation](https://ruby-doc.org/core-2.5.1/File.html#method-c-fnmatch).`,
220+
example: "release/*",
221+
},
222+
},
223+
};
224+
225+
expect(printSchema(schema)).toMatchInlineSnapshot(`
226+
"export type Test = {
227+
/**
228+
* [see original specs]
229+
*
230+
* @example release/*
231+
*/
232+
name: string;
233+
};"
234+
`);
235+
});
236+
179237
it("should generate top-level documentation", () => {
180238
const schema: SchemaObject = {
181239
type: "null",

plugins/typescript/src/core/schemaToTypeAliasDeclaration.ts

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ type RemoveIndex<T> = {
1818
[P in keyof T as string extends P
1919
? never
2020
: number extends P
21-
? never
22-
: P]: T[P];
21+
? never
22+
: P]: T[P];
2323
};
2424

2525
export type OpenAPIComponentType = Extract<
@@ -131,21 +131,18 @@ export const getType = (
131131
const adHocSchemas: Array<SchemaObject> = [];
132132
if (schema.properties) {
133133
adHocSchemas.push({
134-
type: 'object',
134+
type: "object",
135135
properties: schema.properties,
136-
required: schema.required
136+
required: schema.required,
137137
});
138138
}
139139
if (schema.additionalProperties) {
140140
adHocSchemas.push({
141-
type: 'object',
142-
additionalProperties: schema.additionalProperties
141+
type: "object",
142+
additionalProperties: schema.additionalProperties,
143143
});
144144
}
145-
return getAllOf([
146-
...schema.allOf,
147-
...adHocSchemas
148-
], context);
145+
return getAllOf([...schema.allOf, ...adHocSchemas], context);
149146
}
150147

151148
if (schema.enum) {
@@ -227,7 +224,10 @@ export const getType = (
227224
const members: ts.TypeElement[] = Object.entries(
228225
schema.properties || {}
229226
).map(([key, property]) => {
230-
const isEnum = typeof property === "object" && "enum" in property && useEnumsConfigBase;
227+
const isEnum =
228+
typeof property === "object" &&
229+
"enum" in property &&
230+
useEnumsConfigBase;
231231

232232
const propertyNode = f.createPropertySignature(
233233
undefined,
@@ -601,7 +601,7 @@ export const getJSDocComment = (
601601
return f.createIdentifier(value.toString());
602602
}
603603

604-
// Value is not stringifiable
604+
// Value is not stringifyable
605605
// See https://github.com/fabien0102/openapi-codegen/issues/36, https://github.com/fabien0102/openapi-codegen/issues/57
606606
return f.createIdentifier("[see original specs]");
607607
};
@@ -635,18 +635,36 @@ export const getJSDocComment = (
635635
}
636636
});
637637

638-
if (schemaWithAllOfResolved.description || propertyTags.length > 0) {
638+
const description = sanitizeDescription(schemaWithAllOfResolved.description);
639+
if (description || propertyTags.length > 0) {
639640
return f.createJSDocComment(
640-
schemaWithAllOfResolved.description
641-
? schemaWithAllOfResolved.description.trim() +
642-
(propertyTags.length ? "\n" : "")
643-
: undefined,
641+
description ? description + (propertyTags.length ? "\n" : "") : undefined,
644642
propertyTags
645643
);
646644
}
647645
return undefined;
648646
};
649647

648+
/**
649+
* Remove any unwanted chars from the description to avoid
650+
* unparsable multiline comment.
651+
*
652+
* @param description
653+
*/
654+
const sanitizeDescription = (description?: string) => {
655+
if (!description || description.trim().length === 0) return undefined;
656+
if (!description.includes("*/")) {
657+
return description.trim();
658+
}
659+
// Try to return first line, since it’s more likely than the `*/` is the body
660+
const [title] = description.trim().split("\n");
661+
if (title && !title.includes("*/")) {
662+
return `${title}\n\n[see original specs]`;
663+
}
664+
665+
return "[see original specs]";
666+
};
667+
650668
/**
651669
* Add js comment to a node (mutate the original node).
652670
*

0 commit comments

Comments
 (0)