forked from mirrors/misskey
deps(misskey-js): Update openapi-typescript to v7 (#15491)
* deps(misskey-js): Update openapi-typescript to v7 * update openapi-typescript to v7.7.3 * generate misskey-js types * bump openapi-typescript * enhance: 生成物からnever型を除去するように * regenerate api types * refactor: 処理共通化 --------- Co-authored-by: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
This commit is contained in:
@@ -3212,10 +3212,11 @@ type PinnedUsersResponse = operations['pinned-users']['responses']['200']['conte
|
||||
type PromoReadRequest = operations['promo___read']['requestBody']['content']['application/json'];
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "AllNullRecord" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "AllNullOrOptionalRecord" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "NonNullableRecord" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public (undocumented)
|
||||
type PureRenote = Omit<Note, 'renote' | 'renoteId' | 'reply' | 'replyId' | 'text' | 'cw' | 'files' | 'fileIds' | 'poll'> & AllNullRecord<Pick<Note, 'reply' | 'replyId' | 'text' | 'cw' | 'poll'>> & {
|
||||
type PureRenote = Omit<Note, 'renote' | 'renoteId' | 'reply' | 'replyId' | 'text' | 'cw' | 'files' | 'fileIds' | 'poll'> & AllNullRecord<Pick<Note, 'text'>> & AllNullOrOptionalRecord<Pick<Note, 'reply' | 'replyId' | 'cw' | 'poll'>> & {
|
||||
files: [];
|
||||
fileIds: [];
|
||||
} & NonNullableRecord<Pick<Note, 'renote' | 'renoteId'>>;
|
||||
@@ -3748,7 +3749,7 @@ type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['respons
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/entities.ts:50:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||
// src/entities.ts:54:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.ts:57:3 - (ae-forgotten-export) The symbol "ReconnectingWebSocket" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.types.ts:218:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.types.ts:228:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "8.32.1",
|
||||
"@typescript-eslint/parser": "8.32.1",
|
||||
"openapi-types": "12.1.3",
|
||||
"openapi-typescript": "6.7.6",
|
||||
"openapi-typescript": "7.8.0",
|
||||
"ts-case-convert": "2.1.0",
|
||||
"tsx": "4.19.4",
|
||||
"typescript": "5.8.3"
|
||||
|
||||
130
packages/misskey-js/generator/src/ast-transformer.ts
Normal file
130
packages/misskey-js/generator/src/ast-transformer.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
/**
|
||||
* TypeScript AST ノードから 'never' 型のプロパティを削除します。
|
||||
* この関数は、特に 'paths' という名前の TypeAliasDeclaration 内や
|
||||
* 'operations' という名前の InterfaceDeclaration 内を再帰的に探索し、
|
||||
* そこに含まれるすべての TypeLiteralNode/Interfaceから 'PropertySignature' で型が 'never' であるものを削除します。
|
||||
* さらに、すべてのプロパティが 'never' で除去された場合は、そのオブジェクト自体も削除します。
|
||||
*
|
||||
* @param astNodes 処理対象の ts.Node 配列 (例: `openapi-typescript` から出力されたもの)。
|
||||
* @returns 'never' 型プロパティが削除された新しい ts.Node 配列。
|
||||
*/
|
||||
export function removeNeverPropertiesFromAST(astNodes: readonly ts.Node[]): ts.Node[] {
|
||||
const factory = ts.factory;
|
||||
|
||||
/**
|
||||
* TypeLiteralNodeやInterfaceDeclarationのmembersからneverプロパティを除去し、必要なら型も再帰的に処理する共通関数
|
||||
*/
|
||||
function removeNeverPropertiesFromMembers(
|
||||
members: readonly ts.TypeElement[],
|
||||
visitType: (node: ts.Node) => ts.Node | undefined,
|
||||
): { newMembers: ts.TypeElement[]; hasChanged: boolean } {
|
||||
const newMembers: ts.TypeElement[] = [];
|
||||
let hasChanged = false;
|
||||
|
||||
for (const member of members) {
|
||||
if (ts.isPropertySignature(member)) {
|
||||
if (member.type && member.type.kind === ts.SyntaxKind.NeverKeyword) {
|
||||
hasChanged = true;
|
||||
continue;
|
||||
}
|
||||
let updatedPropertySignature = member;
|
||||
if (member.type) {
|
||||
const visitedMemberType = ts.visitNode(member.type, visitType);
|
||||
if (visitedMemberType && visitedMemberType !== member.type) {
|
||||
updatedPropertySignature = factory.updatePropertySignature(
|
||||
member,
|
||||
member.modifiers,
|
||||
member.name,
|
||||
member.questionToken,
|
||||
visitedMemberType as ts.TypeNode,
|
||||
);
|
||||
hasChanged = true;
|
||||
} else if (visitedMemberType === undefined) {
|
||||
// 子の型が消された場合、このプロパティも消す
|
||||
hasChanged = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
newMembers.push(updatedPropertySignature);
|
||||
} else {
|
||||
newMembers.push(member);
|
||||
}
|
||||
}
|
||||
return { newMembers, hasChanged };
|
||||
}
|
||||
|
||||
function typeNodeRecursiveVisitor(node: ts.Node): ts.Node | undefined {
|
||||
if (ts.isTypeLiteralNode(node)) {
|
||||
const { newMembers, hasChanged } = removeNeverPropertiesFromMembers(node.members, typeNodeRecursiveVisitor);
|
||||
|
||||
if (newMembers.length === 0) {
|
||||
// すべてのプロパティがneverで消された場合、このTypeLiteralNode自体も消す
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
return factory.updateTypeLiteralNode(node, factory.createNodeArray(newMembers));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
return ts.visitEachChild(node, typeNodeRecursiveVisitor, undefined);
|
||||
}
|
||||
|
||||
function interfaceRecursiveVisitor(node: ts.Node): ts.Node | undefined {
|
||||
if (ts.isInterfaceDeclaration(node)) {
|
||||
const { newMembers, hasChanged } = removeNeverPropertiesFromMembers(node.members, typeNodeRecursiveVisitor);
|
||||
|
||||
if (newMembers.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
return factory.updateInterfaceDeclaration(
|
||||
node,
|
||||
node.modifiers,
|
||||
node.name,
|
||||
node.typeParameters,
|
||||
node.heritageClauses,
|
||||
newMembers,
|
||||
);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
return ts.visitEachChild(node, interfaceRecursiveVisitor, undefined);
|
||||
}
|
||||
|
||||
function topLevelVisitor(node: ts.Node): ts.Node | undefined {
|
||||
if (ts.isTypeAliasDeclaration(node) && node.name.escapedText === 'paths') {
|
||||
const newType = ts.visitNode(node.type, typeNodeRecursiveVisitor);
|
||||
if (newType && newType !== node.type) {
|
||||
return factory.updateTypeAliasDeclaration(
|
||||
node,
|
||||
node.modifiers,
|
||||
node.name,
|
||||
node.typeParameters,
|
||||
newType as ts.TypeNode,
|
||||
);
|
||||
} else if (newType === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
if (ts.isInterfaceDeclaration(node) && node.name.escapedText === 'operations') {
|
||||
const result = interfaceRecursiveVisitor(node);
|
||||
return result;
|
||||
}
|
||||
return ts.visitEachChild(node, topLevelVisitor, undefined);
|
||||
}
|
||||
|
||||
const transformedNodes: ts.Node[] = [];
|
||||
for (const astNode of astNodes) {
|
||||
const resultNode = ts.visitNode(astNode, topLevelVisitor);
|
||||
if (resultNode) {
|
||||
transformedNodes.push(resultNode);
|
||||
}
|
||||
}
|
||||
|
||||
return transformedNodes;
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import assert from 'assert';
|
||||
import { mkdir, readFile, writeFile } from 'fs/promises';
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
import type { OpenAPIV3_1 } from 'openapi-types';
|
||||
import { toPascal } from 'ts-case-convert';
|
||||
import OpenAPIParser from '@readme/openapi-parser';
|
||||
import openapiTS, { OpenAPI3, OperationObject, PathItemObject } from 'openapi-typescript';
|
||||
import openapiTS, { astToString } from 'openapi-typescript';
|
||||
import type { OpenAPI3, OperationObject, PathItemObject } from 'openapi-typescript';
|
||||
import ts from 'typescript';
|
||||
import { removeNeverPropertiesFromAST } from './ast-transformer.js';
|
||||
|
||||
async function generateBaseTypes(
|
||||
openApiDocs: OpenAPIV3_1.Document,
|
||||
@@ -28,13 +31,6 @@ async function generateBaseTypes(
|
||||
assert('post' in item);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
openApi.paths![key] = {
|
||||
...('get' in item ? {
|
||||
get: {
|
||||
...item.get,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
operationId: ((item as PathItemObject).get as OperationObject).operationId!.replaceAll('get___', ''),
|
||||
},
|
||||
} : {}),
|
||||
post: {
|
||||
...item.post,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
@@ -43,15 +39,26 @@ async function generateBaseTypes(
|
||||
};
|
||||
}
|
||||
|
||||
const generatedTypes = await openapiTS(openApi, {
|
||||
const tsNullNode = ts.factory.createLiteralTypeNode(ts.factory.createNull());
|
||||
const tsBlobNode = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Blob'));
|
||||
|
||||
const generatedTypesAst = await openapiTS(openApi, {
|
||||
exportType: true,
|
||||
transform(schemaObject) {
|
||||
if ('format' in schemaObject && schemaObject.format === 'binary') {
|
||||
return schemaObject.nullable ? 'Blob | null' : 'Blob';
|
||||
if (schemaObject.nullable) {
|
||||
return ts.factory.createUnionTypeNode([tsBlobNode, tsNullNode]);
|
||||
} else {
|
||||
return tsBlobNode;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
lines.push(generatedTypes);
|
||||
|
||||
const filteredAst = removeNeverPropertiesFromAST(generatedTypesAst);
|
||||
|
||||
lines.push(astToString(filteredAst));
|
||||
|
||||
lines.push('');
|
||||
|
||||
await writeFile(typeFileName, lines.join('\n'));
|
||||
|
||||
@@ -77,7 +77,7 @@ export class APIClient {
|
||||
|
||||
if (mediaType === 'application/json') {
|
||||
payload = JSON.stringify({
|
||||
...params,
|
||||
...(this.assertIsRecord(params) ? params : {}),
|
||||
i: credential !== undefined ? credential : this.credential,
|
||||
});
|
||||
} else if (mediaType === 'multipart/form-data') {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,10 +24,14 @@ type NonNullableRecord<T> = {
|
||||
type AllNullRecord<T> = {
|
||||
[P in keyof T]: null;
|
||||
};
|
||||
type AllNullOrOptionalRecord<T> = {
|
||||
[P in keyof T]: never;
|
||||
};
|
||||
|
||||
export type PureRenote =
|
||||
Omit<Note, 'renote' | 'renoteId' | 'reply' | 'replyId' | 'text' | 'cw' | 'files' | 'fileIds' | 'poll'>
|
||||
& AllNullRecord<Pick<Note, 'reply' | 'replyId' | 'text' | 'cw' | 'poll'>>
|
||||
& AllNullRecord<Pick<Note, 'text'>>
|
||||
& AllNullOrOptionalRecord<Pick<Note, 'reply' | 'replyId' | 'cw' | 'poll'>>
|
||||
& { files: []; fileIds: []; }
|
||||
& NonNullableRecord<Pick<Note, 'renote' | 'renoteId'>>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user