Skip to content

Commit 652c36f

Browse files
committed
feat: consolidate comments from nested levels of schemas
DX-592
1 parent 8ccc1a1 commit 652c36f

File tree

3 files changed

+470
-29
lines changed

3 files changed

+470
-29
lines changed

packages/openapi-generator/src/comments.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { parse as parseComment, Block } from 'comment-parser';
2+
import { Schema } from './ir';
23

34
export function leadingComment(
45
src: string,
@@ -42,3 +43,69 @@ export function leadingComment(
4243
const parsedComment = parseComment(commentString);
4344
return parsedComment;
4445
}
46+
47+
/**
48+
*
49+
* @param schema the schema to get all comments from
50+
* @returns an array of all comments in the schema
51+
*/
52+
export function getAllSchemaComments(schema: Schema): Block[] {
53+
const result = [];
54+
55+
/** Push the first comment */
56+
if (schema.comment) {
57+
result.push(schema.comment);
58+
}
59+
60+
/** Push the comments of the subschemas in CombinedTypes (union, intersection, etc) */
61+
if ('schemas' in schema) {
62+
// combined type
63+
for (const s of schema.schemas) {
64+
result.push(...getAllSchemaComments(s));
65+
}
66+
}
67+
68+
return result;
69+
}
70+
71+
/**
72+
*
73+
* @param schema the schema to combine comments from
74+
* @returns a combined comment from all comments in the schema
75+
*/
76+
export function combineComments(schema: Schema): Block | undefined {
77+
const comments = getAllSchemaComments(schema);
78+
79+
const tagSet = new Set<string>();
80+
81+
// Empty comment block where we will build the result
82+
const result: Block = {
83+
tags: [],
84+
description: '',
85+
problems: [],
86+
source: [],
87+
};
88+
89+
if (comments.length === 0) return undefined;
90+
91+
// Only use the first description if it exists, we don't wanna accidentally pull a description from a lower level schema
92+
if (comments[0]?.description && comments[0].description !== '') {
93+
result.description = comments[0].description;
94+
}
95+
96+
// Add all seen tags, problems, and source comments to the result
97+
for (const comment of comments) {
98+
for (const tag of comment.tags) {
99+
// Only add the tag if we haven't seen it before. Otherwise, the higher level tag is 'probably' the more relevant tag.
100+
if (!tagSet.has(tag.tag)) {
101+
result.tags.push(tag);
102+
tagSet.add(tag.tag);
103+
}
104+
}
105+
106+
result.problems.push(...comment.problems);
107+
result.source.push(...comment.source);
108+
}
109+
110+
return result;
111+
}

packages/openapi-generator/src/optimize.ts

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { combineComments } from './comments';
12
import { isPrimitive, type Primitive, type Schema } from './ir';
23

34
export type OptimizeFn = (schema: Schema) => Schema;
@@ -173,6 +174,15 @@ export function filterUndefinedUnion(schema: Schema): [boolean, Schema] {
173174
}
174175
}
175176

177+
// This function is a helper that adds back any comments that were removed during optimization
178+
function withComment(newSchema: Schema, oldSchema: Schema): Schema {
179+
if (oldSchema.comment) {
180+
newSchema.comment = combineComments(oldSchema);
181+
}
182+
183+
return newSchema;
184+
}
185+
176186
export function optimize(schema: Schema): Schema {
177187
if (schema.type === 'object') {
178188
const properties: Record<string, Schema> = {};
@@ -184,8 +194,8 @@ export function optimize(schema: Schema): Schema {
184194
}
185195
const [isOptional, filteredSchema] = filterUndefinedUnion(optimized);
186196

187-
if (prop.comment) {
188-
filteredSchema.comment = prop.comment;
197+
if (optimized.comment) {
198+
filteredSchema.comment = optimized.comment;
189199
}
190200

191201
properties[key] = filteredSchema;
@@ -197,48 +207,31 @@ export function optimize(schema: Schema): Schema {
197207

198208
const schemaObject: Schema = { type: 'object', properties, required };
199209

200-
// only add comment field if there is a comment
201-
if (schema.comment) {
202-
return { ...schemaObject, comment: schema.comment };
203-
}
204-
205-
return schemaObject;
210+
return withComment(schemaObject, schema);
206211
} else if (schema.type === 'intersection') {
207212
const newSchema = foldIntersection(schema, optimize);
208-
if (schema.comment) {
209-
return { ...newSchema, comment: schema.comment };
210-
}
211-
return newSchema;
213+
214+
return withComment(newSchema, schema);
212215
} else if (schema.type === 'union') {
213216
const consolidated = consolidateUnion(schema);
214217
const simplified = simplifyUnion(consolidated, optimize);
215218
const merged = mergeUnions(simplified);
216219

217-
if (schema.comment) {
218-
return { ...merged, comment: schema.comment };
219-
}
220-
221-
return merged;
220+
return withComment(merged, schema);
222221
} else if (schema.type === 'array') {
223222
const optimized = optimize(schema.items);
224-
if (schema.comment) {
225-
return { type: 'array', items: optimized, comment: schema.comment };
226-
}
227-
return { type: 'array', items: optimized };
223+
224+
return withComment({ type: 'array', items: optimized }, schema);
228225
} else if (schema.type === 'record') {
229-
return {
226+
return withComment({
230227
type: 'record',
231228
...(schema.domain ? { domain: optimize(schema.domain) } : {}),
232229
codomain: optimize(schema.codomain),
233-
...(schema.comment ? { comment: schema.comment } : {}),
234-
};
230+
}, schema)
235231
} else if (schema.type === 'tuple') {
236232
const schemas = schema.schemas.map(optimize);
237-
return { type: 'tuple', schemas };
233+
return withComment({ type: 'tuple', schemas }, schema);
238234
} else if (schema.type === 'ref') {
239-
if (schema.comment) {
240-
return { ...schema, comment: schema.comment };
241-
}
242235
return schema;
243236
} else {
244237
return schema;

0 commit comments

Comments
 (0)