fix: sanitize .NET-style schema names for valid TypeScript
- Handle fully qualified names (Namespace.Type -> Type) - Handle generics (Type\`1[Inner] -> Type_Inner) - Preserve original name in JSDoc comment - Tested with Moravia Symfonie API (185 schemas) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
409194781e
commit
c978f9c313
1 changed files with 44 additions and 6 deletions
|
|
@ -91,7 +91,43 @@ function generateTypeScript(schemas: Record<string, object>): string {
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize schema name for valid TypeScript identifier.
|
||||||
|
* Handles .NET-style names like "Microsoft.AspNetCore.Foo`1[Bar]"
|
||||||
|
*/
|
||||||
|
function sanitizeName(name: string): string {
|
||||||
|
// Extract just the type name from fully qualified .NET names
|
||||||
|
// e.g., "Moravia.Symfonie.Api.V5.ViewModels.UserViewModel" -> "UserViewModel"
|
||||||
|
let sanitized = name;
|
||||||
|
|
||||||
|
// Handle generic types like `1[TypeName] or `2[Type1,Type2]
|
||||||
|
const genericMatch = sanitized.match(/`\d+\[([^\]]+)\]/);
|
||||||
|
if (genericMatch) {
|
||||||
|
const innerTypes = genericMatch[1].split(',').map(t => {
|
||||||
|
const parts = t.trim().split('.');
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
});
|
||||||
|
const baseName = sanitized.split('`')[0].split('.').pop() || 'Unknown';
|
||||||
|
sanitized = `${baseName}_${innerTypes.join('_')}`;
|
||||||
|
} else if (sanitized.includes('.')) {
|
||||||
|
// Just take the last part of dotted name
|
||||||
|
const parts = sanitized.split('.');
|
||||||
|
sanitized = parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any remaining invalid characters
|
||||||
|
sanitized = sanitized.replace(/[^a-zA-Z0-9_]/g, '_');
|
||||||
|
|
||||||
|
// Ensure it doesn't start with a number
|
||||||
|
if (/^\d/.test(sanitized)) {
|
||||||
|
sanitized = '_' + sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
function schemaToTypeScript(name: string, schema: object): string {
|
function schemaToTypeScript(name: string, schema: object): string {
|
||||||
|
const safeName = sanitizeName(name);
|
||||||
const s = schema as {
|
const s = schema as {
|
||||||
type?: string;
|
type?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
@ -107,21 +143,23 @@ function schemaToTypeScript(name: string, schema: object): string {
|
||||||
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
|
||||||
// Add JSDoc comment if description exists
|
// Add JSDoc comment with original name if different
|
||||||
if (s.description) {
|
if (s.description) {
|
||||||
lines.push(`/** ${s.description} */`);
|
lines.push(`/** ${s.description} */`);
|
||||||
|
} else if (safeName !== name) {
|
||||||
|
lines.push(`/** Original: ${name} */`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle enums
|
// Handle enums
|
||||||
if (s.enum) {
|
if (s.enum) {
|
||||||
const values = s.enum.map(v => typeof v === 'string' ? `'${v}'` : v).join(' | ');
|
const values = s.enum.map(v => typeof v === 'string' ? `'${v}'` : v).join(' | ');
|
||||||
lines.push(`export type ${name} = ${values};`);
|
lines.push(`export type ${safeName} = ${values};`);
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle object types
|
// Handle object types
|
||||||
if (s.type === 'object' || s.properties) {
|
if (s.type === 'object' || s.properties) {
|
||||||
lines.push(`export interface ${name} {`);
|
lines.push(`export interface ${safeName} {`);
|
||||||
|
|
||||||
if (s.properties) {
|
if (s.properties) {
|
||||||
const required = new Set(s.required || []);
|
const required = new Set(s.required || []);
|
||||||
|
|
@ -146,13 +184,13 @@ function schemaToTypeScript(name: string, schema: object): string {
|
||||||
// Handle array types
|
// Handle array types
|
||||||
if (s.type === 'array' && s.items) {
|
if (s.type === 'array' && s.items) {
|
||||||
const itemType = schemaToType(s.items);
|
const itemType = schemaToType(s.items);
|
||||||
lines.push(`export type ${name} = ${itemType}[];`);
|
lines.push(`export type ${safeName} = ${itemType}[];`);
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle simple types
|
// Handle simple types
|
||||||
const simpleType = schemaToType(schema);
|
const simpleType = schemaToType(schema);
|
||||||
lines.push(`export type ${name} = ${simpleType};`);
|
lines.push(`export type ${safeName} = ${simpleType};`);
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,7 +211,7 @@ function schemaToType(schema: object): string {
|
||||||
|
|
||||||
// Handle $ref (after dereferencing, these should be resolved, but handle just in case)
|
// Handle $ref (after dereferencing, these should be resolved, but handle just in case)
|
||||||
if (s.$ref) {
|
if (s.$ref) {
|
||||||
const refName = s.$ref.split('/').pop() || 'unknown';
|
const refName = sanitizeName(s.$ref.split('/').pop() || 'unknown');
|
||||||
return refName;
|
return refName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue