From 852a108c5382af2a0ccb18a52873ef386b564e78 Mon Sep 17 00:00:00 2001 From: hypercross Date: Wed, 15 Apr 2026 14:46:03 +0800 Subject: [PATCH] fix: update type generation --- src/csv-loader/loader.test.ts | 35 ++++++++++++++++++-------- src/csv-loader/loader.ts | 46 ++++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/csv-loader/loader.test.ts b/src/csv-loader/loader.test.ts index 1080c5b..9acdbee 100644 --- a/src/csv-loader/loader.test.ts +++ b/src/csv-loader/loader.test.ts @@ -846,15 +846,15 @@ describe('csvToModule - accessor-based output', () => { }); describe('csvToModule - circular reference support', () => { - it('should emit accessor for self-referencing table', () => { + it('should emit accessor for self-referencing table without self-import', () => { const csv = readFixture('self_ref.csv'); const result = csvToModule(csv, { emitTypes: false, currentFilePath: path.join(fixturesDir, 'self_ref.csv') }); - expect(result.js).toContain("import _self_ref from './self_ref.csv'"); + expect(result.js).not.toContain("import _self_ref from './self_ref.csv'"); expect(result.js).toContain('export default function getData()'); expect(result.js).toContain('_self_refLookup'); - expect(result.js).toContain('_resolved = _raw;'); + expect(result.js).toContain('_self_refLookup = new Map(_raw.map'); expect(result.js).toContain('parent: _self_refLookup.get(String(row.parent))'); }); @@ -878,14 +878,14 @@ describe('csvToModule - circular reference support', () => { const result = csvToModule(csv, { emitTypes: false, currentFilePath: path.join(fixturesDir, 'self_ref.csv') }); - expect(result.js).toContain("import _self_ref from './self_ref.csv'"); + expect(result.js).not.toContain("import _self_ref from './self_ref.csv'"); expect(result.js).toContain('_self_refLookup'); expect(result.js).toContain('export default function getData()'); expect(result.js).toContain('parent_info:'); expect(result.js).toContain('_self_refLookup.get(String(row.parent_info[0]))'); }); - it('should emit accessor for self-referencing table with nested reference in union', () => { + it('should emit accessor for self-referencing table with nested reference in union with fallback', () => { const csv = [ 'id,name,ref_or_val', 'string,string,@self_ref | string', @@ -895,10 +895,11 @@ describe('csvToModule - circular reference support', () => { const result = csvToModule(csv, { emitTypes: false, currentFilePath: path.join(fixturesDir, 'self_ref.csv') }); - expect(result.js).toContain("import _self_ref from './self_ref.csv'"); + expect(result.js).not.toContain("import _self_ref from './self_ref.csv'"); expect(result.js).toContain('_self_refLookup'); expect(result.js).toContain('export default function getData()'); expect(result.js).toContain('ref_or_val:'); + expect(result.js).toContain('_self_refLookup.get(String(row.ref_or_val)) ?? row.ref_or_val'); }); it('should emit accessor for self-referencing table with nested reference array in tuple', () => { @@ -910,20 +911,21 @@ describe('csvToModule - circular reference support', () => { const result = csvToModule(csv, { emitTypes: false, currentFilePath: path.join(fixturesDir, 'self_ref.csv') }); - expect(result.js).toContain("import _self_ref from './self_ref.csv'"); + expect(result.js).not.toContain("import _self_ref from './self_ref.csv'"); expect(result.js).toContain('_self_refLookup'); expect(result.js).toContain('export default function getData()'); expect(result.js).toContain('children:'); }); - it('should generate correct type definition for self-referencing table', () => { + it('should generate correct type definition for self-referencing table using local singular type', () => { const csv = readFixture('self_ref.csv'); const result = csvToModule(csv, { emitTypes: true, resourceName: 'nodes', currentFilePath: path.join(fixturesDir, 'self_ref.csv') }); expect(result.dts).toContain('declare function getData(): nodesTable'); - expect(result.dts).toContain('Self_ref'); - expect(result.dts).toContain("import type { Self_ref } from './self_ref.csv'"); + expect(result.dts).toContain('readonly parent: Nodes'); + expect(result.dts).not.toContain("import type { Self_ref } from './self_ref.csv'"); + expect(result.dts).not.toContain('Self_ref'); }); it('should emit accessor for cross-referencing tables with array references', () => { @@ -950,4 +952,17 @@ describe('csvToModule - circular reference support', () => { expect(result.js).toContain('_circular_bLookup'); expect(result.js).toContain('export default function getData()'); }); + + it('should generate union fallback with ?? for non-reference union members', () => { + const csv = [ + 'id,value', + 'string,@users | string', + '1,1', + '2,unknown', + ].join('\n'); + + const result = csvToModule(csv, { emitTypes: false }); + + expect(result.js).toContain('?? row.value'); + }); }); \ No newline at end of file diff --git a/src/csv-loader/loader.ts b/src/csv-loader/loader.ts index c360736..2b04e03 100644 --- a/src/csv-loader/loader.ts +++ b/src/csv-loader/loader.ts @@ -538,12 +538,22 @@ function generateTypeDefinition( hasRefs?: boolean ): string { const typeName = resourceName ? `${resourceName}Table` : 'Table'; + const currentTableName = currentFilePath + ? path.basename(currentFilePath, path.extname(currentFilePath)) + : undefined; + const singularType = resourceName + ? resourceName.charAt(0).toUpperCase() + resourceName.slice(1) + : `${typeName}[number]`; // Generate import statements for referenced tables const imports: string[] = []; const resourceNames = new Map(); references.forEach(tableName => { + if (tableName === currentTableName) { + resourceNames.set(tableName, singularType); + return; + } // Convert table name to type name by capitalizing const typeBase = tableName.charAt(0).toUpperCase() + tableName.slice(1); resourceNames.set(tableName, typeBase); @@ -551,10 +561,8 @@ function generateTypeDefinition( // Generate import path based on current file path let importPath: string; if (currentFilePath) { - // Both files are in the same directory, use relative path importPath = `./${tableName}.csv`; } else { - // Fallback for unknown path importPath = `../${tableName}.csv`; } imports.push(`import type { ${typeBase} } from '${importPath}';`); @@ -793,15 +801,17 @@ function generateSchemaResolutionCode( case 'union': { const refMembers = schema.members.filter(m => hasNestedReferences(m)); const nonRefMembers = schema.members.filter(m => !hasNestedReferences(m)); - const parts: string[] = []; + const resolveParts: string[] = []; for (const member of refMembers) { const resolveCode = generateSchemaResolutionCode(member, valueExpr, lookupVar, pkField); - parts.push(resolveCode); + resolveParts.push(resolveCode); } if (nonRefMembers.length > 0) { - parts.push(valueExpr); + resolveParts.push(valueExpr); } - return parts[0] || valueExpr; + if (resolveParts.length === 0) return valueExpr; + if (resolveParts.length === 1) return resolveParts[0]; + return `(${resolveParts.join(' ?? ')})`; } default: return valueExpr; @@ -834,14 +844,26 @@ export function csvToModule( const lookupInits: string[] = []; const lookupVarMap = new Map(); + const currentTableName = options.currentFilePath + ? path.basename(options.currentFilePath, path.extname(options.currentFilePath)) + : undefined; + const uniqueTables = new Set(result.referenceFields.map(f => f.tableName)); uniqueTables.forEach(tableName => { - const varName = `_${tableName}`; - lookupVarMap.set(tableName, `_${tableName}Lookup`); - imports.push(`import ${varName} from './${tableName}.csv';`); - lookupInits.push( - `const _${tableName}Lookup = new Map(${varName}().map(p => [String(p.${defaultPrimaryKey}), p]));` - ); + const lookupVar = `_${tableName}Lookup`; + lookupVarMap.set(tableName, lookupVar); + + if (tableName === currentTableName) { + lookupInits.push( + `const ${lookupVar} = new Map(_raw.map(p => [String(p.${defaultPrimaryKey}), p]));` + ); + } else { + const varName = `_${tableName}`; + imports.push(`import ${varName} from './${tableName}.csv';`); + lookupInits.push( + `const ${lookupVar} = new Map(${varName}().map(p => [String(p.${defaultPrimaryKey}), p]));` + ); + } }); const lookupVar = (tableName: string) => lookupVarMap.get(tableName)!;