Skip to content

Commit 499d66b

Browse files
authored
Merge branch 'main' into copilot/change-objects-to-maps
2 parents f0437a1 + 414c82e commit 499d66b

File tree

12 files changed

+542
-28
lines changed

12 files changed

+542
-28
lines changed

.changeset/add-jsdoc-generation.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"counterfact": minor
3+
---
4+
5+
Add JSDoc comment generation for types from OpenAPI metadata
6+
7+
Generated TypeScript types now include inline JSDoc comments derived from the OpenAPI spec:
8+
9+
- Schema-level JSDoc from `description`/`summary`
10+
- Property-level JSDoc including `description`, `@example`, `@default`, `@format`, and `@deprecated`
11+
- Operation-level JSDoc from operation `summary`/`description`
12+
13+
This improves IDE hover documentation and AI-assisted development workflows.

.github/workflows/create-issues.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616

1717
steps:
1818
- name: Check out repository
19-
uses: actions/checkout@v4
19+
uses: actions/checkout@v6
2020

2121
- name: Install Python dependencies
2222
run: pip install pyyaml

src/typescript-generator/coder.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export class Coder {
2020
return "";
2121
}
2222

23+
public jsdoc(): string {
24+
return "";
25+
}
26+
2327
public write(script: Script): string {
2428
if (this.requirement.isReference) {
2529
return script.import(this);
@@ -83,6 +87,7 @@ export interface ExportStatement {
8387
id: string;
8488
isDefault: boolean;
8589
isType: boolean;
90+
jsdoc: string;
8691
typeDeclaration: string;
8792
name?: string;
8893
code?: string | { raw: string };

src/typescript-generator/jsdoc.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Builds a JSDoc comment string from OpenAPI schema metadata.
3+
* Returns an empty string if there is no relevant metadata.
4+
*/
5+
export function buildJsDoc(data: Record<string, unknown>): string {
6+
const lines: string[] = [];
7+
8+
const description = data["description"] as string | undefined;
9+
const summary = data["summary"] as string | undefined;
10+
const example = data["example"];
11+
const examples = data["examples"] as
12+
| Record<string, { value?: unknown }>
13+
| undefined;
14+
const defaultValue = data["default"];
15+
const format = data["format"] as string | undefined;
16+
const deprecated = data["deprecated"] as boolean | undefined;
17+
18+
const mainText = description ?? summary;
19+
20+
if (mainText) {
21+
// Escape */ to prevent prematurely closing the JSDoc block
22+
const escaped = String(mainText).replace(/\*\//gu, "* /");
23+
const textLines = escaped.split("\n");
24+
25+
for (const line of textLines) {
26+
lines.push(` * ${line}`);
27+
}
28+
}
29+
30+
if (format !== undefined) {
31+
lines.push(` * @format ${format}`);
32+
}
33+
34+
if (defaultValue !== undefined) {
35+
lines.push(` * @default ${JSON.stringify(defaultValue)}`);
36+
}
37+
38+
// Use scalar `example`, or fall back to the first value from `examples`
39+
const exampleValue =
40+
example !== undefined
41+
? example
42+
: examples !== undefined
43+
? Object.values(examples)[0]?.value
44+
: undefined;
45+
46+
if (exampleValue !== undefined) {
47+
lines.push(` * @example ${JSON.stringify(exampleValue)}`);
48+
}
49+
50+
if (deprecated === true) {
51+
lines.push(` * @deprecated`);
52+
}
53+
54+
if (lines.length === 0) {
55+
return "";
56+
}
57+
58+
return `/**\n${lines.join("\n")}\n */\n`;
59+
}

src/typescript-generator/operation-type-coder.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import nodePath from "node:path";
22

33
import { CONTEXT_FILE_TOKEN } from "./context-file-token.js";
4+
import { buildJsDoc } from "./jsdoc.js";
45
import { ParameterExportTypeCoder } from "./parameter-export-type-coder.js";
56
import { ParametersTypeCoder } from "./parameters-type-coder.js";
67
import { READ_ONLY_COMMENTS } from "./read-only-comments.js";
@@ -117,6 +118,10 @@ export class OperationTypeCoder extends TypeCoder {
117118
: `HTTP_${this.requestMethod.toUpperCase()}`;
118119
}
119120

121+
public override jsdoc(): string {
122+
return buildJsDoc(this.requirement.data);
123+
}
124+
120125
public override names(): Generator<string> {
121126
return super.names(this.getOperationBaseName());
122127
}

src/typescript-generator/parameters-type-coder.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import nodePath from "node:path";
22

3+
import { buildJsDoc } from "./jsdoc.js";
34
import { SchemaTypeCoder } from "./schema-type-coder.js";
45
import { TypeCoder } from "./type-coder.js";
56
import type { Requirement } from "./requirement.js";
@@ -39,9 +40,12 @@ export class ParametersTypeCoder extends TypeCoder {
3940
? parameter.get("schema")!
4041
: parameter;
4142

42-
return `"${name}"${optionalFlag}: ${new SchemaTypeCoder(schema).write(
43-
script,
44-
)}`;
43+
const comment = buildJsDoc(parameter.data);
44+
const commentPrefix = comment ? `\n${comment}` : "";
45+
46+
const typeString = new SchemaTypeCoder(schema).write(script);
47+
48+
return `${commentPrefix}"${name}"${optionalFlag}: ${typeString}`;
4549
});
4650

4751
if (typeDefinitions.length === 0) {

src/typescript-generator/schema-type-coder.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { buildJsDoc } from "./jsdoc.js";
12
import { TypeCoder } from "./type-coder.js";
23
import type { Requirement } from "./requirement.js";
34
import type { Script } from "./script.js";
@@ -9,6 +10,10 @@ export class SchemaTypeCoder extends TypeCoder {
910
);
1011
}
1112

13+
public override jsdoc(): string {
14+
return buildJsDoc(this.requirement.data);
15+
}
16+
1217
public additionalPropertiesType(script: Script): string {
1318
const { additionalProperties, properties } = this.requirement.data as {
1419
additionalProperties: { type?: string };
@@ -51,9 +56,12 @@ export class SchemaTypeCoder extends TypeCoder {
5156
typedData.required?.includes(name) || propertyData.required === true;
5257
const optionalFlag = isRequired ? "" : "?";
5358

54-
return `"${name}"${optionalFlag}: ${new SchemaTypeCoder(property).write(
55-
script,
56-
)}`;
59+
const comment = buildJsDoc(property.data);
60+
const commentPrefix = comment ? `\n${comment}` : "";
61+
62+
return `${commentPrefix}"${name}"${optionalFlag}: ${new SchemaTypeCoder(
63+
property,
64+
).write(script)}`;
5765
});
5866

5967
if (typedData.additionalProperties) {

src/typescript-generator/script.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class Script {
7878
id: coder.id,
7979
isDefault,
8080
isType,
81+
jsdoc: "",
8182
typeDeclaration: coder.typeDeclaration(this.exports, this),
8283
};
8384

@@ -87,6 +88,7 @@ export class Script {
8788
.then((availableCoder) => {
8889
exportStatement.name = name;
8990
exportStatement.code = availableCoder.write(this);
91+
exportStatement.jsdoc = availableCoder.jsdoc();
9092

9193
return availableCoder;
9294
})
@@ -227,13 +229,21 @@ export class Script {
227229
public exportStatements(): string[] {
228230
return Array.from(
229231
this.exports.values(),
230-
({ beforeExport, code, isDefault, isType, name, typeDeclaration }) => {
232+
({
233+
beforeExport,
234+
code,
235+
isDefault,
236+
isType,
237+
jsdoc,
238+
name,
239+
typeDeclaration,
240+
}) => {
231241
if (typeof code === "object" && code !== null && "raw" in code) {
232242
return code.raw;
233243
}
234244

235245
if (isDefault) {
236-
return `${beforeExport}export default ${code as string};`;
246+
return `${jsdoc}${beforeExport}export default ${code as string};`;
237247
}
238248

239249
const keyword = isType ? "type" : "const";
@@ -242,7 +252,7 @@ export class Script {
242252
? ""
243253
: `:${typeDeclaration ?? ""}`;
244254

245-
return `${beforeExport}export ${keyword} ${name ?? ""}${typeAnnotation} = ${code as string};`;
255+
return `${jsdoc}${beforeExport}export ${keyword} ${name ?? ""}${typeAnnotation} = ${code as string};`;
246256
},
247257
);
248258
}

0 commit comments

Comments
 (0)