enhance: 生成物からnever型を除去するように

This commit is contained in:
kakkokari-gtyih 2025-05-22 20:33:55 +09:00
parent 6a042e3f9e
commit 547d20ec50
3 changed files with 155 additions and 8168 deletions

View File

@ -0,0 +1,149 @@
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;
const typeNodeRecursiveVisitor = (node: ts.Node): ts.Node | undefined => {
if (ts.isTypeLiteralNode(node)) {
const newMembers: ts.TypeElement[] = [];
let hasTypeLiteralChanged = false;
for (const member of node.members) {
if (ts.isPropertySignature(member)) {
if (member.type && member.type.kind === ts.SyntaxKind.NeverKeyword) {
hasTypeLiteralChanged = true;
continue;
}
let updatedPropertySignature = member;
if (member.type) {
const visitedMemberType = ts.visitNode(member.type, typeNodeRecursiveVisitor);
if (visitedMemberType && visitedMemberType !== member.type) {
updatedPropertySignature = factory.updatePropertySignature(
member,
member.modifiers,
member.name,
member.questionToken,
visitedMemberType as ts.TypeNode
);
hasTypeLiteralChanged = true;
} else if (visitedMemberType === undefined) {
// 子の型が消された場合、このプロパティも消す
hasTypeLiteralChanged = true;
continue;
}
}
newMembers.push(updatedPropertySignature);
} else {
newMembers.push(member);
}
}
if (newMembers.length === 0) {
// すべてのプロパティがneverで消された場合、このTypeLiteralNode自体も消す
return undefined;
}
if (hasTypeLiteralChanged) {
return factory.updateTypeLiteralNode(node, factory.createNodeArray(newMembers));
}
return node;
}
return ts.visitEachChild(node, typeNodeRecursiveVisitor, undefined);
};
const interfaceRecursiveVisitor = (node: ts.Node): ts.Node | undefined => {
if (ts.isInterfaceDeclaration(node)) {
const newMembers: ts.TypeElement[] = [];
let hasChanged = false;
for (const member of node.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, typeNodeRecursiveVisitor);
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);
}
}
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);
};
const 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;
}

View File

@ -6,6 +6,7 @@ import OpenAPIParser from '@readme/openapi-parser';
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,
@ -53,7 +54,11 @@ async function generateBaseTypes(
}
},
});
lines.push(astToString(generatedTypesAst));
const filteredAst = removeNeverPropertiesFromAST(generatedTypesAst);
lines.push(astToString(filteredAst));
lines.push('');
await writeFile(typeFileName, lines.join('\n'));

File diff suppressed because it is too large Load Diff