Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
},
"files": {
"ignoreUnknown": true,
"includes": ["**", "!**/dist", "!coverage", "!examples", "!test/code"],
"includes": ["**", "!**/dist", "!coverage", "!examples", "!test/code", "!**/__snapshots__"],
"maxSize": 10485760
},
"javascript": {
Expand Down
3 changes: 3 additions & 0 deletions scripts/testCodeGenWithClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const main = () => {
Writer.generateTypedefWithTemplateCode("test/kubernetes/openapi-v1.18.5.json", "test/code/class/kubernetes/client-v1.18.5.ts", false, {
sync: false,
});
Writer.generateTypedefWithTemplateCode("test/kubernetes/openapi-v1.28.6.json", "test/code/class/kubernetes/client-v1.28.6.ts", false, {
sync: false,
});
Writer.generateTypedefWithTemplateCode("test/argo-rollout/index.json", "test/code/class/argo-rollout/client.ts", false, {
sync: false,
});
Expand Down
4 changes: 1 addition & 3 deletions src/code-templates/class-api-client/ApiClientClass/Method.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { EOL } from "node:os";

import type { TsGenerator } from "../../../api";
import type { CodeGenerator } from "../../../types";
import * as MethodBody from "../../_shared/MethodBody";
Expand Down Expand Up @@ -161,7 +159,7 @@ export const create = (factory: TsGenerator.Factory.Type, params: CodeGenerator.
comment: option.additionalMethodComment
? [params.operationParams.comment, `operationId: ${params.operationId}`, `Request URI: ${params.operationParams.requestUri}`]
.filter(t => !!t)
.join(EOL)
.join("\n")
: params.operationParams.comment,
deprecated: params.operationParams.deprecated,
type: returnType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { EOL } from "node:os";

import type { TsGenerator } from "../../../api";
import type { CodeGenerator } from "../../../types";
import type { Option } from "../../_shared/types";
Expand All @@ -11,7 +9,7 @@ export const create = (factory: TsGenerator.Factory.Type, list: CodeGenerator.Pa
comment: option.additionalMethodComment
? [params.operationParams.comment, `operationId: ${params.operationId}`, `Request URI: ${params.operationParams.requestUri}`]
.filter(t => !!t)
.join(EOL)
.join("\n")
: params.operationParams.comment,
modifiers: ["export"],
declarationList: factory.VariableDeclarationList.create({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { EOL } from "node:os";

import type { TsGenerator } from "../../../api";
import type { CodeGenerator } from "../../../types";
import type { Option } from "../../_shared/types";
Expand All @@ -13,7 +11,7 @@ export const create = (factory: TsGenerator.Factory.Type, list: CodeGenerator.Pa
comment: option.additionalMethodComment
? [params.operationParams.comment, `operationId: ${params.operationId}`, `Request URI: ${params.operationParams.requestUri}`]
.filter(t => !!t)
.join(EOL)
.join("\n")
: params.operationParams.comment,
});
});
Expand Down
6 changes: 2 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { EOL } from "node:os";

import * as Api from "./api";
import { generateValidRootSchema } from "./generateValidRootSchema";
import type * as Types from "./types";
Expand Down Expand Up @@ -63,7 +61,7 @@ export class CodeGenerator {
});
return statements;
};
return [Api.OpenApiTools.Comment.generateLeading(this.resolvedReferenceDocument), Api.TsGenerator.generate(create)].join(EOL + EOL + EOL);
return [Api.OpenApiTools.Comment.generateLeading(this.resolvedReferenceDocument), Api.TsGenerator.generate(create)].join("\n\n\n");
}

/**
Expand All @@ -79,7 +77,7 @@ export class CodeGenerator {
return generatorTemplate?.generator(payload, generatorTemplate.option);
});
};
return [Api.OpenApiTools.Comment.generateLeading(this.resolvedReferenceDocument), Api.TsGenerator.generate(create)].join(EOL + EOL + EOL);
return [Api.OpenApiTools.Comment.generateLeading(this.resolvedReferenceDocument), Api.TsGenerator.generate(create)].join("\n\n\n");
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/internal/OpenApiTools/Comment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { EOL } from "node:os";
import { Name, Version } from "../../meta";
import type { OpenApi } from "../../types";

Expand All @@ -22,5 +21,5 @@ export const generateLeading = (schema: OpenApi.Document): string => {
.map(message => {
return `// ${message}`;
})
.join(EOL);
.join("\n");
};
4 changes: 1 addition & 3 deletions src/internal/OpenApiTools/Walker/Operation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { EOL } from "node:os";

import type { CodeGenerator, OpenApi } from "../../../types";

const httpMethodList = ["get", "put", "post", "delete", "options", "head", "patch", "trace"] as const;
Expand Down Expand Up @@ -37,7 +35,7 @@ export const create = (rootSchema: OpenApi.Document): State => {
state[operation.operationId] = {
httpMethod,
requestUri,
comment: [operation.summary, operation.description].filter(Boolean).join(EOL),
comment: [operation.summary, operation.description].filter(Boolean).join("\n"),
deprecated: !!operation.deprecated,
requestBody: hasValidMediaType ? requestBody : undefined,
parameters: uniqParameters(parameters),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { EOL } from "node:os";

import type { OpenApi } from "../../../types";

export const addComment = (comment?: string, externalDocs?: OpenApi.ExternalDocumentation): string | undefined => {
if (!externalDocs) {
return comment;
}
return [comment, "", `@see ${externalDocs.url}`, externalDocs.description].filter(Boolean).join(EOL);
return [comment, "", `@see ${externalDocs.url}`, externalDocs.description].filter(Boolean).join("\n");
};
3 changes: 1 addition & 2 deletions src/internal/OpenApiTools/components/Operation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { EOL } from "node:os";
import * as path from "node:path";

import type { OpenApi } from "../../../types";
Expand Down Expand Up @@ -26,7 +25,7 @@ const generateComment = (operation: OpenApi.Operation): string => {
if (operation.tags) {
comments.push(`tags: ${operation.tags.join(", ")}`);
}
return comments.join(EOL);
return comments.join("\n");
};

export const generateNamespace = (
Expand Down
46 changes: 33 additions & 13 deletions src/internal/OpenApiTools/components/Parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,28 @@ export const generatePropertySignatureObject = (
};
};

/**
* パラメータの `in` プロパティを返す。
*
* inline パラメータはそのまま `in` を返す。
* `$ref` 参照の場合はローカル参照を解決して store から実体を取得する。
* store に存在しない場合(リモート参照など)は `undefined` を返す。
*/
const resolveParameterIn = (store: Walker.Store, parameter: OpenApi.Parameter | OpenApi.Reference): string | undefined => {
if (!Guard.isReference(parameter)) {
return parameter.in;
}
const localRef = Reference.generateLocalReference(parameter);
if (!localRef) {
return undefined;
}
try {
return store.getParameter(localRef.path).in;
} catch {
return undefined;
}
};

export const generatePropertySignatures = (
entryPoint: string,
currentPoint: string,
Expand All @@ -109,16 +131,11 @@ export const generatePropertySignatures = (
context: ToTypeNode.Context,
converterContext: ConverterContext.Types,
): string[] => {
// Path parameters must be processed last so they win over same-named query/header
// parameters when building the TypeScript interface (path params are always required).
const sorted = [...parameters].sort((a, b): number => {
const aIsPath = !Guard.isReference(a) && a.in === "path";
const bIsPath = !Guard.isReference(b) && b.in === "path";
if (aIsPath && !bIsPath) return 1;
if (!aIsPath && bIsPath) return -1;
return 0;
});
const typeElementMap = sorted.reduce<Record<string, string>>((all, parameter) => {
// 入力順を維持しながら、同名パラメータが存在する場合は path パラメータを優先する。
// Map はキーの挿入順を保持するため、既存キーへの set() は値のみ更新し順序は変わらない。
// これにより OpenAPI spec の記述順を崩さず、path パラメータの required が保たれる。
const typeElementMap = new Map<string, string>();
for (const parameter of parameters) {
const { name, typeElement } = generatePropertySignatureObject(
entryPoint,
currentPoint,
Expand All @@ -128,9 +145,12 @@ export const generatePropertySignatures = (
context,
converterContext,
);
return { ...all, [name]: typeElement };
}, {});
return Object.values(typeElementMap);
const isPath = resolveParameterIn(store, parameter) === "path";
if (!typeElementMap.has(name) || isPath) {
typeElementMap.set(name, typeElement);
}
}
return [...typeElementMap.values()];
};

export const generateInterface = (
Expand Down
4 changes: 1 addition & 3 deletions src/internal/OpenApiTools/components/Server.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { EOL } from "node:os";

import type { OpenApi } from "../../../types";

export const addComment = (comment?: string, server?: OpenApi.Server): string | undefined => {
if (!server) {
return comment;
}
return [comment, server.url, server.description].filter(Boolean).join(EOL);
return [comment, server.url, server.description].filter(Boolean).join("\n");
};
4 changes: 1 addition & 3 deletions src/internal/OpenApiTools/components/Servers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { EOL } from "node:os";

import type { OpenApi } from "../../../types";
import * as Server from "./Server";

export const addComment = (comments: (string | undefined)[], servers: OpenApi.Server[] = []): string | undefined => {
const comment = comments.filter(Boolean).join(EOL) as string | undefined;
const comment = comments.filter(Boolean).join("\n") as string | undefined;
return servers.reduce((newComment, server) => {
return Server.addComment(newComment, server);
}, comment);
Expand Down
9 changes: 4 additions & 5 deletions src/internal/TsGenerator/__tests__/factory.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { EOL } from "node:os";
import { describe, expect, it } from "vitest";
import * as Factory from "../factory";

Expand Down Expand Up @@ -50,17 +49,17 @@ describe("TsGenerator Factory Helpers", () => {

describe("buildComment", () => {
it("単一ラインのコメントを生成できること", () => {
expect(Factory.buildComment("hello")).toBe(`/** hello */${EOL}`);
expect(Factory.buildComment("hello")).toBe(`/** hello */${"\n"}`);
});

it("複数ラインのコメントを生成できること", () => {
const input = "line1\nline2";
const expected = `/**${EOL} * line1${EOL} * line2${EOL} */${EOL}`;
const expected = `/**${"\n"} * line1${"\n"} * line2${"\n"} */${"\n"}`;
expect(Factory.buildComment(input)).toBe(expected);
});

it("deprecated フラグがある場合に @deprecated タグを付与すること", () => {
const expected = `/**${EOL} * @deprecated${EOL} * old feature${EOL} */${EOL}`;
const expected = `/**${"\n"} * @deprecated${"\n"} * old feature${"\n"} */${"\n"}`;
expect(Factory.buildComment("old feature", true)).toBe(expected);
});

Expand All @@ -75,7 +74,7 @@ describe("TsGenerator Factory Helpers", () => {
const code = "const a = 1;";
const comment = "my variable";
const result = Factory.addComment(code, comment);
expect(result).toBe(`/** my variable */${EOL}${code}`);
expect(result).toBe(`/** my variable */${"\n"}${code}`);
});

it("コメントも deprecated もない場合は元のコードを返すこと", () => {
Expand Down
6 changes: 2 additions & 4 deletions src/internal/TsGenerator/factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { EOL } from "node:os";

// --- Private helpers ---

/**
Expand Down Expand Up @@ -93,9 +91,9 @@ export const buildComment = (comment: string, deprecated?: boolean): string => {
const lines = deprecated ? ["@deprecated", ...escaped.split(/\r?\n/)] : escaped.split(/\r?\n/);
const filtered = lines.filter((line, i) => !(i === lines.length - 1 && line === ""));
if (filtered.length === 1) {
return `/** ${filtered[0]} */${EOL}`;
return `/** ${filtered[0]} */\n`;
}
return `/**${EOL}${filtered.map(l => (l.trimEnd() ? ` * ${l.trimEnd()}` : " *")).join(EOL)}${EOL} */${EOL}`;
return `/**\n${filtered.map(l => (l.trimEnd() ? ` * ${l.trimEnd()}` : " *")).join("\n")}\n */\n`;
};

/**
Expand Down
Loading
Loading