This commit is contained in:
152
src/declaration-generator.ts
Normal file
152
src/declaration-generator.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
TypeNode,
|
||||
PrimitiveType,
|
||||
ArrayType,
|
||||
ObjectType,
|
||||
UnionType,
|
||||
LiteralType,
|
||||
OptionalType,
|
||||
TypeDefinition,
|
||||
} from './types.js';
|
||||
|
||||
export class DeclarationGenerator {
|
||||
private indent: string;
|
||||
private newLine: string;
|
||||
|
||||
constructor(indentSize: number = 2) {
|
||||
this.indent = ' '.repeat(indentSize);
|
||||
this.newLine = '\n';
|
||||
}
|
||||
|
||||
generate(types: TypeDefinition[]): string {
|
||||
if (types.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const declarations = types.map((t) => this.generateTypeDeclaration(t));
|
||||
|
||||
return declarations.join(this.newLine + this.newLine);
|
||||
}
|
||||
|
||||
generateTypeDeclaration(typeDef: TypeDefinition): string {
|
||||
const declaration = this.generateTypeNode(typeDef.type, 0, new Set());
|
||||
|
||||
if (typeDef.type.kind === 'object') {
|
||||
return `export interface ${typeDef.name} ${this.generateObjectBody(typeDef.type, 0)}`;
|
||||
}
|
||||
|
||||
return `export type ${typeDef.name} = ${declaration};`;
|
||||
}
|
||||
|
||||
generateTypeNode(type: TypeNode, depth: number, _visited: Set<string>): string {
|
||||
switch (type.kind) {
|
||||
case 'primitive':
|
||||
return this.formatPrimitiveType(type);
|
||||
case 'literal':
|
||||
return this.formatLiteralType(type);
|
||||
case 'array':
|
||||
return this.formatArrayType(type, depth);
|
||||
case 'object':
|
||||
return this.formatObjectType(type, depth);
|
||||
case 'union':
|
||||
return this.formatUnionType(type, depth);
|
||||
case 'optional':
|
||||
return this.formatOptionalType(type, depth);
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
private formatPrimitiveType(type: PrimitiveType): string {
|
||||
switch (type.type) {
|
||||
case 'string':
|
||||
return 'string';
|
||||
case 'number':
|
||||
return 'number';
|
||||
case 'boolean':
|
||||
return 'boolean';
|
||||
case 'null':
|
||||
return 'null';
|
||||
case 'any':
|
||||
return 'any';
|
||||
case 'unknown':
|
||||
return 'unknown';
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
private formatLiteralType(type: LiteralType): string {
|
||||
if (typeof type.value === 'string') {
|
||||
return `'${type.value}'`;
|
||||
}
|
||||
return String(type.value);
|
||||
}
|
||||
|
||||
private formatArrayType(type: ArrayType, depth: number): string {
|
||||
const elementType = this.generateTypeNode(type.elementType, depth, new Set());
|
||||
|
||||
if (this.needsParens(type.elementType)) {
|
||||
return `(${elementType})[]`;
|
||||
}
|
||||
return `${elementType}[]`;
|
||||
}
|
||||
|
||||
private formatObjectType(type: ObjectType, depth: number): string {
|
||||
return this.generateObjectBody(type, depth);
|
||||
}
|
||||
|
||||
private generateObjectBody(type: ObjectType, depth: number): string {
|
||||
if (type.properties.length === 0) {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
const currentIndent = this.indent.repeat(depth);
|
||||
const innerIndent = this.indent.repeat(depth + 1);
|
||||
|
||||
const propertyLines = type.properties.map((prop) => {
|
||||
const optionalMarker = prop.optional ? '?' : '';
|
||||
const propType = this.generateTypeNode(prop.type, depth + 1, new Set());
|
||||
return `${innerIndent}${prop.name}${optionalMarker}: ${propType};`;
|
||||
});
|
||||
|
||||
return `{${this.newLine}${propertyLines.join(this.newLine)}${this.newLine}${currentIndent}}`;
|
||||
}
|
||||
|
||||
private formatUnionType(type: UnionType, depth: number): string {
|
||||
if (type.types.length === 0) {
|
||||
return 'never';
|
||||
}
|
||||
|
||||
if (type.types.length === 1) {
|
||||
return this.generateTypeNode(type.types[0], depth, new Set());
|
||||
}
|
||||
|
||||
const formattedTypes = type.types.map((t) => {
|
||||
const inner = this.generateTypeNode(t, depth, new Set());
|
||||
return this.needsParens(t) ? `(${inner})` : inner;
|
||||
});
|
||||
|
||||
const separator = this.newLine + this.indent.repeat(depth) + '| ';
|
||||
|
||||
if (depth === 0) {
|
||||
return formattedTypes.join(' | ');
|
||||
}
|
||||
|
||||
return formattedTypes.join(separator);
|
||||
}
|
||||
|
||||
private formatOptionalType(type: OptionalType, depth: number): string {
|
||||
const inner = this.generateTypeNode(type.type, depth, new Set());
|
||||
return `${inner} | undefined`;
|
||||
}
|
||||
|
||||
private needsParens(type: TypeNode): boolean {
|
||||
return type.kind === 'union' || type.kind === 'optional';
|
||||
}
|
||||
|
||||
generateStandalone(types: TypeDefinition[]): string {
|
||||
const declarations = types.map((t) => this.generateTypeDeclaration(t));
|
||||
return declarations.join(this.newLine + this.newLine);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user