Initial upload with CI/CD workflow
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-01-31 12:25:57 +00:00
parent 12a1cb8da1
commit 8a36547ec3

View 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);
}
}