diff --git a/formatters/format-ts.sh b/formatters/format-ts.sh index 44b7f246e..741209a70 100755 --- a/formatters/format-ts.sh +++ b/formatters/format-ts.sh @@ -1,18 +1,18 @@ #!/bin/bash set -e -# Format TypeScript files with Biome +# Format TypeScript files with tsfmt echo "Formatting TypeScript files..." dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -# Check if biome.json files match -if ! cmp -s ../spine-ts/biome.json ../tests/biome.json; then - echo -e "\033[1;31mERROR: spine-ts/biome.json and tests/biome.json differ!\033[0m" +# Check if tsfmt.json files match +if ! cmp -s ../spine-ts/tsfmt.json ../tests/tsfmt.json; then + echo -e "\033[1;31mERROR: spine-ts/tsfmt.json and tests/tsfmt.json differ!\033[0m" echo -e "\033[1;31mPlease sync them to ensure consistent formatting.\033[0m" exit 1 fi # Format TypeScript files -cd ../spine-ts && npx biome format --write . && cd ../formatters -cd ../tests && npx biome format --write --config-path ../spine-ts . && cd ../formatters \ No newline at end of file +cd ../spine-ts && npm run format && cd ../formatters +cd ../tests && npm run format -r && cd ../formatters \ No newline at end of file diff --git a/spine-ts/scripts/format.ts b/spine-ts/scripts/format.ts index 0f322ce1f..df79e71e0 100644 --- a/spine-ts/scripts/format.ts +++ b/spine-ts/scripts/format.ts @@ -2,37 +2,37 @@ import { execSync } from 'node:child_process'; import * as fs from 'node:fs'; import * as path from 'node:path'; -function findTypeScriptFiles(dir: string, files: string[] = []): string[] { - if (!fs.existsSync(dir)) return files; - - fs.readdirSync(dir).forEach(name => { - const filePath = path.join(dir, name); - const stat = fs.statSync(filePath); - - if (stat.isDirectory()) { - // Skip node_modules and dist directories - if (name !== 'node_modules' && name !== 'dist') { - findTypeScriptFiles(filePath, files); - } - } else if (name.endsWith('.ts') && !name.endsWith('.d.ts')) { - files.push(filePath); - } - }); - - return files; +function findTypeScriptFiles (dir: string, files: string[] = []): string[] { + if (!fs.existsSync(dir)) return files; + + fs.readdirSync(dir).forEach(name => { + const filePath = path.join(dir, name); + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + // Skip node_modules and dist directories + if (name !== 'node_modules' && name !== 'dist') { + findTypeScriptFiles(filePath, files); + } + } else if (name.endsWith('.ts') && !name.endsWith('.d.ts')) { + files.push(filePath); + } + }); + + return files; } // Find all TypeScript files in spine-* directories const allFiles: string[] = []; fs.readdirSync('.').forEach(name => { - if (name.startsWith('spine-') && fs.statSync(name).isDirectory()) { - findTypeScriptFiles(name, allFiles); - } + if (name.startsWith('spine-') && fs.statSync(name).isDirectory()) { + findTypeScriptFiles(name, allFiles); + } }); if (allFiles.length > 0) { - console.log(`Formatting ${allFiles.length} TypeScript files...`); - execSync(`npx tsfmt -r ${allFiles.join(' ')}`, { stdio: 'inherit' }); + console.log(`Formatting ${allFiles.length} TypeScript files...`); + execSync(`npx -y typescript-formatter -r ${allFiles.join(' ')}`, { stdio: 'inherit' }); } else { - console.log('No TypeScript files found to format.'); + console.log('No TypeScript files found to format.'); } \ No newline at end of file diff --git a/tests/package.json b/tests/package.json index 4f9bb19e9..28cfc853d 100644 --- a/tests/package.json +++ b/tests/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "compare": "tsx compare-with-reference-impl.ts", - "format": "npx tsfmt -r ./**/*.ts", + "format": "npx -y typescript-formatter -r ./**/*.ts", "lint": "npx biome lint ." }, "devDependencies": { diff --git a/tests/src/analyze-java-api.ts b/tests/src/analyze-java-api.ts index 429a235af..0a148ca5b 100755 --- a/tests/src/analyze-java-api.ts +++ b/tests/src/analyze-java-api.ts @@ -9,731 +9,731 @@ import type { LspCliResult, SymbolInfo } from '@mariozechner/lsp-cli'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -function ensureOutputDir(): string { - const outputDir = path.resolve(__dirname, '../output'); - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); - } - return outputDir; +function ensureOutputDir (): string { + const outputDir = path.resolve(__dirname, '../output'); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + return outputDir; } -function generateLspData(outputDir: string): string { - const outputFile = path.join(outputDir, 'spine-libgdx-symbols.json'); - const projectDir = path.resolve(__dirname, '../../spine-libgdx'); - const srcDir = path.join(projectDir, 'spine-libgdx/src'); +function generateLspData (outputDir: string): string { + const outputFile = path.join(outputDir, 'spine-libgdx-symbols.json'); + const projectDir = path.resolve(__dirname, '../../spine-libgdx'); + const srcDir = path.join(projectDir, 'spine-libgdx/src'); - // Check if we need to regenerate - let needsRegeneration = true; - if (fs.existsSync(outputFile)) { - const outputStats = fs.statSync(outputFile); - const outputTime = outputStats.mtime.getTime(); + // Check if we need to regenerate + let needsRegeneration = true; + if (fs.existsSync(outputFile)) { + const outputStats = fs.statSync(outputFile); + const outputTime = outputStats.mtime.getTime(); - // Find the newest source file - const newestSourceTime = execSync( - `find "${srcDir}" -name "*.java" -type f ! -name "SkeletonSerializer.java" -exec stat -f "%m" {} \\; | sort -nr | head -1`, - { encoding: 'utf8' } - ).trim(); + // Find the newest source file + const newestSourceTime = execSync( + `find "${srcDir}" -name "*.java" -type f ! -name "SkeletonSerializer.java" -exec stat -f "%m" {} \\; | sort -nr | head -1`, + { encoding: 'utf8' } + ).trim(); - if (newestSourceTime) { - const sourceTime = parseInt(newestSourceTime) * 1000; // Convert to milliseconds - needsRegeneration = sourceTime > outputTime; - } - } + if (newestSourceTime) { + const sourceTime = parseInt(newestSourceTime) * 1000; // Convert to milliseconds + needsRegeneration = sourceTime > outputTime; + } + } - if (needsRegeneration) { - console.error('Generating LSP data for spine-libgdx...'); - try { - execSync(`node ${path.join(__dirname, '../node_modules/@mariozechner/lsp-cli/dist/index.js')} "${projectDir}" java "${outputFile}"`, { - stdio: 'inherit' // Show all output - }); - console.error('LSP data generated successfully'); - } catch (error: any) { - console.error('Error generating LSP data:', error.message); - throw error; - } - } else { - console.error('Using existing LSP data (up to date)'); - } + if (needsRegeneration) { + console.error('Generating LSP data for spine-libgdx...'); + try { + execSync(`node ${path.join(__dirname, '../node_modules/@mariozechner/lsp-cli/dist/index.js')} "${projectDir}" java "${outputFile}"`, { + stdio: 'inherit' // Show all output + }); + console.error('LSP data generated successfully'); + } catch (error: any) { + console.error('Error generating LSP data:', error.message); + throw error; + } + } else { + console.error('Using existing LSP data (up to date)'); + } - return outputFile; + return outputFile; } -function analyzeClasses(symbols: SymbolInfo[]): Map { - const classMap = new Map(); - const srcPath = path.resolve(__dirname, '../../spine-libgdx/spine-libgdx/src/'); +function analyzeClasses (symbols: SymbolInfo[]): Map { + const classMap = new Map(); + const srcPath = path.resolve(__dirname, '../../spine-libgdx/spine-libgdx/src/'); - function processSymbol(symbol: SymbolInfo, parentName?: string) { - if (symbol.kind !== 'class' && symbol.kind !== 'enum' && symbol.kind !== 'interface') return; + function processSymbol (symbol: SymbolInfo, parentName?: string) { + if (symbol.kind !== 'class' && symbol.kind !== 'enum' && symbol.kind !== 'interface') return; - // Filter: only process symbols in spine-libgdx/src, excluding SkeletonSerializer - if (!symbol.file.startsWith(srcPath)) return; - if (symbol.file.endsWith('SkeletonSerializer.java')) return; + // Filter: only process symbols in spine-libgdx/src, excluding SkeletonSerializer + if (!symbol.file.startsWith(srcPath)) return; + if (symbol.file.endsWith('SkeletonSerializer.java')) return; - const className = parentName ? `${parentName}.${symbol.name}` : symbol.name; + const className = parentName ? `${parentName}.${symbol.name}` : symbol.name; - const classInfo: ClassInfo = { - className: className, - superTypes: (symbol.supertypes || []).map(st => st.name.replace('$', '.')), - superTypeDetails: symbol.supertypes, - file: symbol.file, - getters: [], - fields: [], - isAbstract: false, - isInterface: symbol.kind === 'interface', - isEnum: symbol.kind === 'enum', - typeParameters: symbol.typeParameters || [] - }; + const classInfo: ClassInfo = { + className: className, + superTypes: (symbol.supertypes || []).map(st => st.name.replace('$', '.')), + superTypeDetails: symbol.supertypes, + file: symbol.file, + getters: [], + fields: [], + isAbstract: false, + isInterface: symbol.kind === 'interface', + isEnum: symbol.kind === 'enum', + typeParameters: symbol.typeParameters || [] + }; - // Check if abstract class - if (symbol.preview && symbol.preview.includes('abstract ')) { - classInfo.isAbstract = true; - } + // Check if abstract class + if (symbol.preview && symbol.preview.includes('abstract ')) { + classInfo.isAbstract = true; + } - // Find all getter methods, public fields, inner classes, and enum values - if (symbol.children) { - for (const child of symbol.children) { - if (child.kind === 'class' || child.kind === 'enum' || child.kind === 'interface') { - // Process inner class - processSymbol(child, className); - } else if (child.kind === 'enumMember') { - // Collect enum values - if (!classInfo.enumValues) { - classInfo.enumValues = []; - } - classInfo.enumValues.push(child.name); - } else if (child.kind === 'field' && child.preview) { - // Check if it's a public field - if (child.preview.includes('public ')) { - // Extract field type from preview - // Examples: "public float offset;", "public final Array to = ..." - const fieldMatch = child.preview.match(/public\s+(final\s+)?(.+?)\s+(\w+)\s*[;=]/); - if (fieldMatch) { - const isFinal = !!fieldMatch[1]; - const fieldType = fieldMatch[2].trim(); - const fieldName = fieldMatch[3]; - classInfo.fields.push({ fieldName, fieldType, isFinal }); - } - } - } else if (child.kind === 'method' && - child.name.startsWith('get') && - child.name !== 'getClass()' && - child.name.endsWith('()')) { // Only parameterless getters + // Find all getter methods, public fields, inner classes, and enum values + if (symbol.children) { + for (const child of symbol.children) { + if (child.kind === 'class' || child.kind === 'enum' || child.kind === 'interface') { + // Process inner class + processSymbol(child, className); + } else if (child.kind === 'enumMember') { + // Collect enum values + if (!classInfo.enumValues) { + classInfo.enumValues = []; + } + classInfo.enumValues.push(child.name); + } else if (child.kind === 'field' && child.preview) { + // Check if it's a public field + if (child.preview.includes('public ')) { + // Extract field type from preview + // Examples: "public float offset;", "public final Array to = ..." + const fieldMatch = child.preview.match(/public\s+(final\s+)?(.+?)\s+(\w+)\s*[;=]/); + if (fieldMatch) { + const isFinal = !!fieldMatch[1]; + const fieldType = fieldMatch[2].trim(); + const fieldName = fieldMatch[3]; + classInfo.fields.push({ fieldName, fieldType, isFinal }); + } + } + } else if (child.kind === 'method' && + child.name.startsWith('get') && + child.name !== 'getClass()' && + child.name.endsWith('()')) { // Only parameterless getters - const methodName = child.name.slice(0, -2); // Remove () + const methodName = child.name.slice(0, -2); // Remove () - if (methodName.length > 3 && methodName[3] === methodName[3].toUpperCase()) { - // Extract return type from preview - let returnType = 'unknown'; - if (child.preview) { - const returnMatch = child.preview.match(/(?:public|protected|private)?\s*(.+?)\s+\w+\s*\(\s*\)/); - if (returnMatch) { - returnType = returnMatch[1].trim(); - } - } + if (methodName.length > 3 && methodName[3] === methodName[3].toUpperCase()) { + // Extract return type from preview + let returnType = 'unknown'; + if (child.preview) { + const returnMatch = child.preview.match(/(?:public|protected|private)?\s*(.+?)\s+\w+\s*\(\s*\)/); + if (returnMatch) { + returnType = returnMatch[1].trim(); + } + } - classInfo.getters.push({ methodName, returnType }); - } - } - } - } + classInfo.getters.push({ methodName, returnType }); + } + } + } + } - classMap.set(className, classInfo); - } + classMap.set(className, classInfo); + } - for (const symbol of symbols) { - processSymbol(symbol); - } + for (const symbol of symbols) { + processSymbol(symbol); + } - return classMap; + return classMap; } -function findAccessibleTypes( - classMap: Map, - startingTypes: string[] +function findAccessibleTypes ( + classMap: Map, + startingTypes: string[] ): Set { - const accessible = new Set(); - const toVisit = [...startingTypes]; - const visited = new Set(); + const accessible = new Set(); + const toVisit = [...startingTypes]; + const visited = new Set(); - // Helper to find all concrete subclasses of a type - function findConcreteSubclasses(typeName: string, addToQueue: boolean = true): string[] { - const concreteClasses: string[] = []; + // Helper to find all concrete subclasses of a type + function findConcreteSubclasses (typeName: string, addToQueue: boolean = true): string[] { + const concreteClasses: string[] = []; - if (!classMap.has(typeName)) return concreteClasses; + if (!classMap.has(typeName)) return concreteClasses; - const classInfo = classMap.get(typeName)!; + const classInfo = classMap.get(typeName)!; - // Add the type itself if it's concrete - if (!classInfo.isAbstract && !classInfo.isInterface && !classInfo.isEnum) { - concreteClasses.push(typeName); - } + // Add the type itself if it's concrete + if (!classInfo.isAbstract && !classInfo.isInterface && !classInfo.isEnum) { + concreteClasses.push(typeName); + } - // Find all subclasses recursively - for (const [className, info] of classMap) { - // Check if this class extends our target (handle both qualified and unqualified names) - const extendsTarget = info.superTypes.some(st => - st === typeName || - st === typeName.split('.').pop() || - (typeName.includes('.') && className.startsWith(typeName.split('.')[0] + '.') && st === typeName.split('.').pop()) - ); + // Find all subclasses recursively + for (const [className, info] of classMap) { + // Check if this class extends our target (handle both qualified and unqualified names) + const extendsTarget = info.superTypes.some(st => + st === typeName || + st === typeName.split('.').pop() || + (typeName.includes('.') && className.startsWith(typeName.split('.')[0] + '.') && st === typeName.split('.').pop()) + ); - if (extendsTarget) { - // Recursively find concrete subclasses - const subclasses = findConcreteSubclasses(className, false); - concreteClasses.push(...subclasses); + if (extendsTarget) { + // Recursively find concrete subclasses + const subclasses = findConcreteSubclasses(className, false); + concreteClasses.push(...subclasses); - if (addToQueue && !visited.has(className)) { - toVisit.push(className); - } - } - } + if (addToQueue && !visited.has(className)) { + toVisit.push(className); + } + } + } - return concreteClasses; - } + return concreteClasses; + } - while (toVisit.length > 0) { - const typeName = toVisit.pop()!; + while (toVisit.length > 0) { + const typeName = toVisit.pop()!; - if (visited.has(typeName)) continue; - visited.add(typeName); + if (visited.has(typeName)) continue; + visited.add(typeName); - if (!classMap.has(typeName)) { - console.error(`Type ${typeName} not found in classMap`); - continue; - } + if (!classMap.has(typeName)) { + console.error(`Type ${typeName} not found in classMap`); + continue; + } - const classInfo = classMap.get(typeName)!; + const classInfo = classMap.get(typeName)!; - // Add the type itself if it's concrete - if (!classInfo.isAbstract && !classInfo.isInterface && !classInfo.isEnum) { - accessible.add(typeName); - console.error(`Added concrete type: ${typeName}`); - } + // Add the type itself if it's concrete + if (!classInfo.isAbstract && !classInfo.isInterface && !classInfo.isEnum) { + accessible.add(typeName); + console.error(`Added concrete type: ${typeName}`); + } - // Find all concrete subclasses of this type - const concreteClasses = findConcreteSubclasses(typeName); - concreteClasses.forEach(c => accessible.add(c)); + // Find all concrete subclasses of this type + const concreteClasses = findConcreteSubclasses(typeName); + concreteClasses.forEach(c => accessible.add(c)); - // Add types from getter return types and field types - const allTypes = [ - ...classInfo.getters.map(g => g.returnType), - ...classInfo.fields.map(f => f.fieldType) - ]; + // Add types from getter return types and field types + const allTypes = [ + ...classInfo.getters.map(g => g.returnType), + ...classInfo.fields.map(f => f.fieldType) + ]; - for (const type of allTypes) { - const returnType = type - .replace(/@Null\s+/g, '') // Remove @Null annotations - .replace(/\s+/g, ' '); // Normalize whitespace + for (const type of allTypes) { + const returnType = type + .replace(/@Null\s+/g, '') // Remove @Null annotations + .replace(/\s+/g, ' '); // Normalize whitespace - // Extract types from Array, IntArray, FloatArray, etc. - const arrayMatch = returnType.match(/Array<(.+?)>/); - if (arrayMatch) { - const innerType = arrayMatch[1].trim(); - // Handle inner classes like AnimationState.TrackEntry - if (innerType.includes('.')) { - if (classMap.has(innerType) && !visited.has(innerType)) { - toVisit.push(innerType); - } - } else { - // Try both plain type and as inner class of current type - if (classMap.has(innerType) && !visited.has(innerType)) { - toVisit.push(innerType); - } - // Also try as inner class of the declaring type - const parts = typeName.split('.'); - for (let i = parts.length; i >= 1; i--) { - const parentPath = parts.slice(0, i).join('.'); - const innerClassPath = `${parentPath}.${innerType}`; - if (classMap.has(innerClassPath) && !visited.has(innerClassPath)) { - toVisit.push(innerClassPath); - break; - } - } - } - } + // Extract types from Array, IntArray, FloatArray, etc. + const arrayMatch = returnType.match(/Array<(.+?)>/); + if (arrayMatch) { + const innerType = arrayMatch[1].trim(); + // Handle inner classes like AnimationState.TrackEntry + if (innerType.includes('.')) { + if (classMap.has(innerType) && !visited.has(innerType)) { + toVisit.push(innerType); + } + } else { + // Try both plain type and as inner class of current type + if (classMap.has(innerType) && !visited.has(innerType)) { + toVisit.push(innerType); + } + // Also try as inner class of the declaring type + const parts = typeName.split('.'); + for (let i = parts.length; i >= 1; i--) { + const parentPath = parts.slice(0, i).join('.'); + const innerClassPath = `${parentPath}.${innerType}`; + if (classMap.has(innerClassPath) && !visited.has(innerClassPath)) { + toVisit.push(innerClassPath); + break; + } + } + } + } - // Extract all capitalized type names - const typeMatches = returnType.match(/\b([A-Z]\w+(?:\.[A-Z]\w+)*)\b/g); - if (typeMatches) { - for (const match of typeMatches) { - if (classMap.has(match) && !visited.has(match)) { - toVisit.push(match); - } - // For non-qualified names, also try as inner class - if (!match.includes('.')) { - // Try as inner class of current type and its parents - const parts = typeName.split('.'); - for (let i = parts.length; i >= 1; i--) { - const parentPath = parts.slice(0, i).join('.'); - const innerClassPath = `${parentPath}.${match}`; - if (classMap.has(innerClassPath) && !visited.has(innerClassPath)) { - toVisit.push(innerClassPath); - break; - } - } - } - } - } - } - } + // Extract all capitalized type names + const typeMatches = returnType.match(/\b([A-Z]\w+(?:\.[A-Z]\w+)*)\b/g); + if (typeMatches) { + for (const match of typeMatches) { + if (classMap.has(match) && !visited.has(match)) { + toVisit.push(match); + } + // For non-qualified names, also try as inner class + if (!match.includes('.')) { + // Try as inner class of current type and its parents + const parts = typeName.split('.'); + for (let i = parts.length; i >= 1; i--) { + const parentPath = parts.slice(0, i).join('.'); + const innerClassPath = `${parentPath}.${match}`; + if (classMap.has(innerClassPath) && !visited.has(innerClassPath)) { + toVisit.push(innerClassPath); + break; + } + } + } + } + } + } + } - console.error(`Found ${accessible.size} accessible types`); - return accessible; + console.error(`Found ${accessible.size} accessible types`); + return accessible; } -function loadExclusions(): { types: Set, methods: Map>, fields: Map> } { - const exclusionsPath = path.resolve(__dirname, '../java-exclusions.txt'); - const types = new Set(); - const methods = new Map>(); - const fields = new Map>(); +function loadExclusions (): { types: Set, methods: Map>, fields: Map> } { + const exclusionsPath = path.resolve(__dirname, '../java-exclusions.txt'); + const types = new Set(); + const methods = new Map>(); + const fields = new Map>(); - if (!fs.existsSync(exclusionsPath)) { - return { types, methods, fields }; - } + if (!fs.existsSync(exclusionsPath)) { + return { types, methods, fields }; + } - const content = fs.readFileSync(exclusionsPath, 'utf-8'); - const lines = content.split('\n'); + const content = fs.readFileSync(exclusionsPath, 'utf-8'); + const lines = content.split('\n'); - for (const line of lines) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith('#')) continue; + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; - const parts = trimmed.split(/\s+/); - if (parts.length < 2) continue; + const parts = trimmed.split(/\s+/); + if (parts.length < 2) continue; - const [type, className, property] = parts; + const [type, className, property] = parts; - switch (type) { - case 'type': - types.add(className); - break; - case 'method': - if (property) { - if (!methods.has(className)) { - methods.set(className, new Set()); - } - methods.get(className)!.add(property); - } - break; - case 'field': - if (property) { - if (!fields.has(className)) { - fields.set(className, new Set()); - } - fields.get(className)!.add(property); - } - break; - } - } + switch (type) { + case 'type': + types.add(className); + break; + case 'method': + if (property) { + if (!methods.has(className)) { + methods.set(className, new Set()); + } + methods.get(className)!.add(property); + } + break; + case 'field': + if (property) { + if (!fields.has(className)) { + fields.set(className, new Set()); + } + fields.get(className)!.add(property); + } + break; + } + } - return { types, methods, fields }; + return { types, methods, fields }; } -function isTypeExcluded(typeName: string, exclusions: ReturnType): boolean { - return exclusions.types.has(typeName); +function isTypeExcluded (typeName: string, exclusions: ReturnType): boolean { + return exclusions.types.has(typeName); } -function isPropertyExcluded(className: string, propertyName: string, isGetter: boolean, exclusions: ReturnType): boolean { - if (isGetter) { - return exclusions.methods.get(className)?.has(propertyName) || false; - } else { - return exclusions.fields.get(className)?.has(propertyName) || false; - } +function isPropertyExcluded (className: string, propertyName: string, isGetter: boolean, exclusions: ReturnType): boolean { + if (isGetter) { + return exclusions.methods.get(className)?.has(propertyName) || false; + } else { + return exclusions.fields.get(className)?.has(propertyName) || false; + } } -function getAllProperties(classMap: Map, className: string, symbolsFile: string, exclusions: ReturnType): PropertyInfo[] { - const allProperties: PropertyInfo[] = []; - const visited = new Set(); - const classInfo = classMap.get(className); - if (!classInfo) return []; +function getAllProperties (classMap: Map, className: string, symbolsFile: string, exclusions: ReturnType): PropertyInfo[] { + const allProperties: PropertyInfo[] = []; + const visited = new Set(); + const classInfo = classMap.get(className); + if (!classInfo) return []; - // Build type parameter mapping based on supertype details - const typeParamMap = new Map(); + // Build type parameter mapping based on supertype details + const typeParamMap = new Map(); - // Helper to build parameter mappings for a specific supertype - function buildTypeParamMapping(currentClass: string, targetSupertype: string): Map { - const mapping = new Map(); - const currentInfo = classMap.get(currentClass); - if (!currentInfo || !currentInfo.superTypeDetails) return mapping; + // Helper to build parameter mappings for a specific supertype + function buildTypeParamMapping (currentClass: string, targetSupertype: string): Map { + const mapping = new Map(); + const currentInfo = classMap.get(currentClass); + if (!currentInfo || !currentInfo.superTypeDetails) return mapping; - // Find the matching supertype - for (const supertype of currentInfo.superTypeDetails) { - if (supertype.name === targetSupertype && supertype.typeArguments) { - // Get the supertype's class info to know its type parameters - const supertypeInfo = classMap.get(targetSupertype); - if (supertypeInfo && supertypeInfo.typeParameters) { - // Map type parameters to arguments - for (let i = 0; i < Math.min(supertypeInfo.typeParameters.length, supertype.typeArguments.length); i++) { - mapping.set(supertypeInfo.typeParameters[i], supertype.typeArguments[i]); - } - } - break; - } - } - return mapping; - } + // Find the matching supertype + for (const supertype of currentInfo.superTypeDetails) { + if (supertype.name === targetSupertype && supertype.typeArguments) { + // Get the supertype's class info to know its type parameters + const supertypeInfo = classMap.get(targetSupertype); + if (supertypeInfo && supertypeInfo.typeParameters) { + // Map type parameters to arguments + for (let i = 0; i < Math.min(supertypeInfo.typeParameters.length, supertype.typeArguments.length); i++) { + mapping.set(supertypeInfo.typeParameters[i], supertype.typeArguments[i]); + } + } + break; + } + } + return mapping; + } - function resolveType(type: string, typeMap: Map = new Map()): string { - // Resolve generic type parameters - if (typeMap.has(type)) { - return typeMap.get(type)!; - } - // TODO: Handle complex types like Array, Map, etc. - return type; - } + function resolveType (type: string, typeMap: Map = new Map()): string { + // Resolve generic type parameters + if (typeMap.has(type)) { + return typeMap.get(type)!; + } + // TODO: Handle complex types like Array, Map, etc. + return type; + } - // Collect properties in inheritance order (most specific first) - function collectProperties(currentClass: string, inheritanceLevel: number = 0, currentTypeMap: Map = new Map()) { - if (visited.has(currentClass)) return; - visited.add(currentClass); + // Collect properties in inheritance order (most specific first) + function collectProperties (currentClass: string, inheritanceLevel: number = 0, currentTypeMap: Map = new Map()) { + if (visited.has(currentClass)) return; + visited.add(currentClass); - const classInfo = classMap.get(currentClass); - if (!classInfo) return; + const classInfo = classMap.get(currentClass); + if (!classInfo) return; - // Add this class's getters with resolved types - for (const getter of classInfo.getters) { - const propertyName = getter.methodName + '()'; - allProperties.push({ - name: propertyName, - type: resolveType(getter.returnType, currentTypeMap), - isGetter: true, - inheritedFrom: inheritanceLevel === 0 ? undefined : currentClass, - excluded: isPropertyExcluded(currentClass, propertyName, true, exclusions) - }); - } + // Add this class's getters with resolved types + for (const getter of classInfo.getters) { + const propertyName = getter.methodName + '()'; + allProperties.push({ + name: propertyName, + type: resolveType(getter.returnType, currentTypeMap), + isGetter: true, + inheritedFrom: inheritanceLevel === 0 ? undefined : currentClass, + excluded: isPropertyExcluded(currentClass, propertyName, true, exclusions) + }); + } - // Add this class's public fields - for (const field of classInfo.fields) { - allProperties.push({ - name: field.fieldName, - type: resolveType(field.fieldType, currentTypeMap), - isGetter: false, - inheritedFrom: inheritanceLevel === 0 ? undefined : currentClass, - excluded: isPropertyExcluded(currentClass, field.fieldName, false, exclusions) - }); - } + // Add this class's public fields + for (const field of classInfo.fields) { + allProperties.push({ + name: field.fieldName, + type: resolveType(field.fieldType, currentTypeMap), + isGetter: false, + inheritedFrom: inheritanceLevel === 0 ? undefined : currentClass, + excluded: isPropertyExcluded(currentClass, field.fieldName, false, exclusions) + }); + } - // Recursively collect from supertypes - for (const superType of classInfo.superTypes) { - // Build type parameter mapping for this supertype - const supertypeMapping = buildTypeParamMapping(currentClass, superType); + // Recursively collect from supertypes + for (const superType of classInfo.superTypes) { + // Build type parameter mapping for this supertype + const supertypeMapping = buildTypeParamMapping(currentClass, superType); - // Compose mappings - resolve type arguments through current mapping - const composedMapping = new Map(); - for (const [param, arg] of supertypeMapping) { - composedMapping.set(param, resolveType(arg, currentTypeMap)); - } + // Compose mappings - resolve type arguments through current mapping + const composedMapping = new Map(); + for (const [param, arg] of supertypeMapping) { + composedMapping.set(param, resolveType(arg, currentTypeMap)); + } - // Try to find the supertype - it might be unqualified - let superClassInfo = classMap.get(superType); + // Try to find the supertype - it might be unqualified + let superClassInfo = classMap.get(superType); - // If not found and it's unqualified, try to find it as an inner class - if (!superClassInfo && !superType.includes('.')) { - // Try as inner class of the same parent - if (currentClass.includes('.')) { - const parentPrefix = currentClass.substring(0, currentClass.lastIndexOf('.')); - const qualifiedSuper = `${parentPrefix}.${superType}`; - superClassInfo = classMap.get(qualifiedSuper); - if (superClassInfo) { - collectProperties(qualifiedSuper, inheritanceLevel + 1, composedMapping); - continue; - } - } + // If not found and it's unqualified, try to find it as an inner class + if (!superClassInfo && !superType.includes('.')) { + // Try as inner class of the same parent + if (currentClass.includes('.')) { + const parentPrefix = currentClass.substring(0, currentClass.lastIndexOf('.')); + const qualifiedSuper = `${parentPrefix}.${superType}`; + superClassInfo = classMap.get(qualifiedSuper); + if (superClassInfo) { + collectProperties(qualifiedSuper, inheritanceLevel + 1, composedMapping); + continue; + } + } - // Try as top-level class - for (const [name, info] of classMap) { - if (name === superType || name.endsWith(`.${superType}`)) { - collectProperties(name, inheritanceLevel + 1, composedMapping); - break; - } - } - } else if (superClassInfo) { - collectProperties(superType, inheritanceLevel + 1, composedMapping); - } - } - } + // Try as top-level class + for (const [name, info] of classMap) { + if (name === superType || name.endsWith(`.${superType}`)) { + collectProperties(name, inheritanceLevel + 1, composedMapping); + break; + } + } + } else if (superClassInfo) { + collectProperties(superType, inheritanceLevel + 1, composedMapping); + } + } + } - collectProperties(className); + collectProperties(className); - // Remove duplicates (overridden methods/shadowed fields), keeping the most specific one - const seen = new Map(); - for (const prop of allProperties) { - const key = prop.isGetter ? prop.name : `field:${prop.name}`; - if (!seen.has(key)) { - seen.set(key, prop); - } - } + // Remove duplicates (overridden methods/shadowed fields), keeping the most specific one + const seen = new Map(); + for (const prop of allProperties) { + const key = prop.isGetter ? prop.name : `field:${prop.name}`; + if (!seen.has(key)) { + seen.set(key, prop); + } + } - return Array.from(seen.values()); + return Array.from(seen.values()); } // Helper to find all implementations of a type (both concrete and abstract) -function findAllImplementations(classMap: Map, typeName: string, concreteOnly: boolean = false): string[] { - const implementations: string[] = []; - const visited = new Set(); +function findAllImplementations (classMap: Map, typeName: string, concreteOnly: boolean = false): string[] { + const implementations: string[] = []; + const visited = new Set(); - function findImplementations(currentType: string) { - if (visited.has(currentType)) return; - visited.add(currentType); + function findImplementations (currentType: string) { + if (visited.has(currentType)) return; + visited.add(currentType); - // Get the short name for comparison - const currentShortName = currentType.split('.').pop()!; - const currentPrefix = currentType.includes('.') ? currentType.split('.')[0] : ''; + // Get the short name for comparison + const currentShortName = currentType.split('.').pop()!; + const currentPrefix = currentType.includes('.') ? currentType.split('.')[0] : ''; - for (const [className, classInfo] of classMap) { - // Check if this class extends/implements the current type - let extendsType = false; + for (const [className, classInfo] of classMap) { + // Check if this class extends/implements the current type + let extendsType = false; - // For inner classes, we need to check if they're in the same outer class - if (currentPrefix && className.startsWith(currentPrefix + '.')) { - // Both are inner classes of the same outer class - extendsType = classInfo.superTypes.some(st => - st === currentShortName || st === currentType - ); - } else { - // Standard inheritance check - extendsType = classInfo.superTypes.some(st => - st === currentType || st === currentShortName - ); - } + // For inner classes, we need to check if they're in the same outer class + if (currentPrefix && className.startsWith(currentPrefix + '.')) { + // Both are inner classes of the same outer class + extendsType = classInfo.superTypes.some(st => + st === currentShortName || st === currentType + ); + } else { + // Standard inheritance check + extendsType = classInfo.superTypes.some(st => + st === currentType || st === currentShortName + ); + } - if (extendsType) { - if (!classInfo.isAbstract && !classInfo.isInterface && !classInfo.isEnum) { - // This is a concrete implementation - implementations.push(className); - } else { - // This is abstract/interface - if (!concreteOnly) { - // Include abstract types when getting all implementations - implementations.push(className); - } - // Always recurse to find further implementations - findImplementations(className); - } - } - } - } + if (extendsType) { + if (!classInfo.isAbstract && !classInfo.isInterface && !classInfo.isEnum) { + // This is a concrete implementation + implementations.push(className); + } else { + // This is abstract/interface + if (!concreteOnly) { + // Include abstract types when getting all implementations + implementations.push(className); + } + // Always recurse to find further implementations + findImplementations(className); + } + } + } + } - findImplementations(typeName); - return [...new Set(implementations)].sort(); // Remove duplicates and sort + findImplementations(typeName); + return [...new Set(implementations)].sort(); // Remove duplicates and sort } -function analyzeForSerialization(classMap: Map, symbolsFile: string): AnalysisResult { - const startingTypes = ['SkeletonData', 'Skeleton', 'AnimationState']; - const accessibleTypes = findAccessibleTypes(classMap, startingTypes); +function analyzeForSerialization (classMap: Map, symbolsFile: string): AnalysisResult { + const startingTypes = ['SkeletonData', 'Skeleton', 'AnimationState']; + const accessibleTypes = findAccessibleTypes(classMap, startingTypes); - // First pass: populate implementations for all abstract types - for (const [className, classInfo] of classMap) { - if (classInfo.isAbstract || classInfo.isInterface) { - // Get only concrete implementations - const concreteImplementations = findAllImplementations(classMap, className, true); - classInfo.concreteImplementations = concreteImplementations; + // First pass: populate implementations for all abstract types + for (const [className, classInfo] of classMap) { + if (classInfo.isAbstract || classInfo.isInterface) { + // Get only concrete implementations + const concreteImplementations = findAllImplementations(classMap, className, true); + classInfo.concreteImplementations = concreteImplementations; - // Get all implementations (including intermediate abstract types) - const allImplementations = findAllImplementations(classMap, className, false); - classInfo.allImplementations = allImplementations; - } - } + // Get all implementations (including intermediate abstract types) + const allImplementations = findAllImplementations(classMap, className, false); + classInfo.allImplementations = allImplementations; + } + } - // Collect abstract types and their implementations - const abstractTypes = new Map(); - const allTypesToGenerate = new Set(accessibleTypes); + // Collect abstract types and their implementations + const abstractTypes = new Map(); + const allTypesToGenerate = new Set(accessibleTypes); - // Find all abstract types referenced by accessible types - for (const typeName of accessibleTypes) { - const classInfo = classMap.get(typeName); - if (!classInfo) continue; + // Find all abstract types referenced by accessible types + for (const typeName of accessibleTypes) { + const classInfo = classMap.get(typeName); + if (!classInfo) continue; - // Check return types and field types for abstract classes - const allTypes = [ - ...classInfo.getters.map(g => g.returnType), - ...classInfo.fields.map(f => f.fieldType) - ]; + // Check return types and field types for abstract classes + const allTypes = [ + ...classInfo.getters.map(g => g.returnType), + ...classInfo.fields.map(f => f.fieldType) + ]; - for (const type of allTypes) { - const returnType = type - .replace(/@Null\s+/g, '') - .replace(/\s+/g, ' '); + for (const type of allTypes) { + const returnType = type + .replace(/@Null\s+/g, '') + .replace(/\s+/g, ' '); - // Extract types from Array - let checkTypes: string[] = []; - const arrayMatch = returnType.match(/Array<(.+?)>/); - if (arrayMatch) { - checkTypes.push(arrayMatch[1].trim()); - } else if (returnType.match(/^[A-Z]\w+$/)) { - checkTypes.push(returnType); - } + // Extract types from Array + let checkTypes: string[] = []; + const arrayMatch = returnType.match(/Array<(.+?)>/); + if (arrayMatch) { + checkTypes.push(arrayMatch[1].trim()); + } else if (returnType.match(/^[A-Z]\w+$/)) { + checkTypes.push(returnType); + } - // Also check for type names that might be inner classes - const typeMatches = returnType.match(/\b([A-Z]\w+)\b/g); - if (typeMatches) { - for (const match of typeMatches) { - // Try as inner class of current type - const parts = typeName.split('.'); - for (let i = parts.length; i >= 1; i--) { - const parentPath = parts.slice(0, i).join('.'); - const innerClassPath = `${parentPath}.${match}`; - if (classMap.has(innerClassPath)) { - checkTypes.push(innerClassPath); - break; - } - } - } - } + // Also check for type names that might be inner classes + const typeMatches = returnType.match(/\b([A-Z]\w+)\b/g); + if (typeMatches) { + for (const match of typeMatches) { + // Try as inner class of current type + const parts = typeName.split('.'); + for (let i = parts.length; i >= 1; i--) { + const parentPath = parts.slice(0, i).join('.'); + const innerClassPath = `${parentPath}.${match}`; + if (classMap.has(innerClassPath)) { + checkTypes.push(innerClassPath); + break; + } + } + } + } - for (const checkType of checkTypes) { - if (checkType && classMap.has(checkType)) { - const typeInfo = classMap.get(checkType)!; - if (typeInfo.isAbstract || typeInfo.isInterface) { - // Use the already populated concreteImplementations - const implementations = typeInfo.concreteImplementations || []; - abstractTypes.set(checkType, implementations); + for (const checkType of checkTypes) { + if (checkType && classMap.has(checkType)) { + const typeInfo = classMap.get(checkType)!; + if (typeInfo.isAbstract || typeInfo.isInterface) { + // Use the already populated concreteImplementations + const implementations = typeInfo.concreteImplementations || []; + abstractTypes.set(checkType, implementations); - // Add all concrete implementations to types to generate - implementations.forEach(impl => allTypesToGenerate.add(impl)); - } - } - } - } - } + // Add all concrete implementations to types to generate + implementations.forEach(impl => allTypesToGenerate.add(impl)); + } + } + } + } + } - // Load exclusions - const exclusions = loadExclusions(); + // Load exclusions + const exclusions = loadExclusions(); - // Filter out excluded types from allTypesToGenerate - const filteredTypesToGenerate = new Set(); - for (const typeName of allTypesToGenerate) { - if (!isTypeExcluded(typeName, exclusions)) { - filteredTypesToGenerate.add(typeName); - } else { - console.error(`Excluding type: ${typeName}`); - } - } + // Filter out excluded types from allTypesToGenerate + const filteredTypesToGenerate = new Set(); + for (const typeName of allTypesToGenerate) { + if (!isTypeExcluded(typeName, exclusions)) { + filteredTypesToGenerate.add(typeName); + } else { + console.error(`Excluding type: ${typeName}`); + } + } - // Update allTypesToGenerate to the filtered set - allTypesToGenerate.clear(); - filteredTypesToGenerate.forEach(type => allTypesToGenerate.add(type)); + // Update allTypesToGenerate to the filtered set + allTypesToGenerate.clear(); + filteredTypesToGenerate.forEach(type => allTypesToGenerate.add(type)); - // Collect all properties for each type (including inherited ones) - const typeProperties = new Map(); - for (const typeName of allTypesToGenerate) { - const props = getAllProperties(classMap, typeName, symbolsFile, exclusions); - typeProperties.set(typeName, props); - } + // Collect all properties for each type (including inherited ones) + const typeProperties = new Map(); + for (const typeName of allTypesToGenerate) { + const props = getAllProperties(classMap, typeName, symbolsFile, exclusions); + typeProperties.set(typeName, props); + } - // Also collect properties for abstract types (so we know what properties their implementations should have) - for (const abstractType of abstractTypes.keys()) { - if (!typeProperties.has(abstractType) && !isTypeExcluded(abstractType, exclusions)) { - const props = getAllProperties(classMap, abstractType, symbolsFile, exclusions); - typeProperties.set(abstractType, props); - } - } + // Also collect properties for abstract types (so we know what properties their implementations should have) + for (const abstractType of abstractTypes.keys()) { + if (!typeProperties.has(abstractType) && !isTypeExcluded(abstractType, exclusions)) { + const props = getAllProperties(classMap, abstractType, symbolsFile, exclusions); + typeProperties.set(abstractType, props); + } + } - // Second pass: find additional concrete types referenced in properties - const additionalTypes = new Set(); - for (const [typeName, props] of typeProperties) { - for (const prop of props) { - const propType = prop.type.replace(/@Null\s+/g, '').trim(); + // Second pass: find additional concrete types referenced in properties + const additionalTypes = new Set(); + for (const [typeName, props] of typeProperties) { + for (const prop of props) { + const propType = prop.type.replace(/@Null\s+/g, '').trim(); - // Check if it's a simple type name - const typeMatch = propType.match(/^([A-Z]\w+)$/); - if (typeMatch) { - const type = typeMatch[1]; - if (classMap.has(type)) { - const typeInfo = classMap.get(type)!; - if (!typeInfo.isAbstract && !typeInfo.isInterface && !typeInfo.isEnum) { - if (!allTypesToGenerate.has(type)) { - additionalTypes.add(type); - console.error(`Found additional type ${type} from property ${prop.name} of ${typeName}`); - } - } - } - } - } - } + // Check if it's a simple type name + const typeMatch = propType.match(/^([A-Z]\w+)$/); + if (typeMatch) { + const type = typeMatch[1]; + if (classMap.has(type)) { + const typeInfo = classMap.get(type)!; + if (!typeInfo.isAbstract && !typeInfo.isInterface && !typeInfo.isEnum) { + if (!allTypesToGenerate.has(type)) { + additionalTypes.add(type); + console.error(`Found additional type ${type} from property ${prop.name} of ${typeName}`); + } + } + } + } + } + } - // Add the additional types (filtered) - additionalTypes.forEach(type => { - if (!isTypeExcluded(type, exclusions)) { - allTypesToGenerate.add(type); - } else { - console.error(`Excluding additional type: ${type}`); - } - }); + // Add the additional types (filtered) + additionalTypes.forEach(type => { + if (!isTypeExcluded(type, exclusions)) { + allTypesToGenerate.add(type); + } else { + console.error(`Excluding additional type: ${type}`); + } + }); - // Get properties for the additional types too - for (const typeName of additionalTypes) { - if (!isTypeExcluded(typeName, exclusions)) { - const props = getAllProperties(classMap, typeName, symbolsFile, exclusions); - typeProperties.set(typeName, props); - } else { - console.error(`Excluding additional type: ${typeName}`); - } - } + // Get properties for the additional types too + for (const typeName of additionalTypes) { + if (!isTypeExcluded(typeName, exclusions)) { + const props = getAllProperties(classMap, typeName, symbolsFile, exclusions); + typeProperties.set(typeName, props); + } else { + console.error(`Excluding additional type: ${typeName}`); + } + } - return { - classMap, - accessibleTypes, - abstractTypes, - allTypesToGenerate, - typeProperties - }; + return { + classMap, + accessibleTypes, + abstractTypes, + allTypesToGenerate, + typeProperties + }; } -async function main() { - try { - // Ensure output directory exists - const outputDir = ensureOutputDir(); +async function main () { + try { + // Ensure output directory exists + const outputDir = ensureOutputDir(); - // Generate LSP data - const jsonFile = generateLspData(outputDir); + // Generate LSP data + const jsonFile = generateLspData(outputDir); - // Read and parse the JSON - const jsonContent = fs.readFileSync(jsonFile, 'utf8'); - const lspData: LspCliResult = JSON.parse(jsonContent); + // Read and parse the JSON + const jsonContent = fs.readFileSync(jsonFile, 'utf8'); + const lspData: LspCliResult = JSON.parse(jsonContent); - console.error(`Analyzing ${lspData.symbols.length} symbols...`); + console.error(`Analyzing ${lspData.symbols.length} symbols...`); - // Analyze all classes - const classMap = analyzeClasses(lspData.symbols); - console.error(`Found ${classMap.size} classes`); + // Analyze all classes + const classMap = analyzeClasses(lspData.symbols); + console.error(`Found ${classMap.size} classes`); - // Perform serialization analysis - const analysisResult = analyzeForSerialization(classMap, jsonFile); - console.error(`Found ${analysisResult.accessibleTypes.size} accessible types`); - console.error(`Found ${analysisResult.allTypesToGenerate.size} types to generate`); + // Perform serialization analysis + const analysisResult = analyzeForSerialization(classMap, jsonFile); + console.error(`Found ${analysisResult.accessibleTypes.size} accessible types`); + console.error(`Found ${analysisResult.allTypesToGenerate.size} types to generate`); - // Save analysis result to file - const analysisFile = path.join(outputDir, 'analysis-result.json'); + // Save analysis result to file + const analysisFile = path.join(outputDir, 'analysis-result.json'); - // Convert Maps to arrays and handle nested Maps in ClassInfo - const classMapArray: [string, any][] = []; - for (const [name, info] of analysisResult.classMap) { - const serializedInfo = { - ...info, - typeParameters: info.typeParameters ? Array.from(info.typeParameters.entries()) : undefined - }; - classMapArray.push([name, serializedInfo]); - } + // Convert Maps to arrays and handle nested Maps in ClassInfo + const classMapArray: [string, any][] = []; + for (const [name, info] of analysisResult.classMap) { + const serializedInfo = { + ...info, + typeParameters: info.typeParameters ? Array.from(info.typeParameters.entries()) : undefined + }; + classMapArray.push([name, serializedInfo]); + } - const resultToSave = { - ...analysisResult, - // Convert Maps and Sets to arrays for JSON serialization - classMap: classMapArray, - accessibleTypes: Array.from(analysisResult.accessibleTypes), - abstractTypes: Array.from(analysisResult.abstractTypes.entries()), - allTypesToGenerate: Array.from(analysisResult.allTypesToGenerate), - typeProperties: Array.from(analysisResult.typeProperties.entries()) - }; + const resultToSave = { + ...analysisResult, + // Convert Maps and Sets to arrays for JSON serialization + classMap: classMapArray, + accessibleTypes: Array.from(analysisResult.accessibleTypes), + abstractTypes: Array.from(analysisResult.abstractTypes.entries()), + allTypesToGenerate: Array.from(analysisResult.allTypesToGenerate), + typeProperties: Array.from(analysisResult.typeProperties.entries()) + }; - fs.writeFileSync(analysisFile, JSON.stringify(resultToSave, null, 2)); - console.log(`Analysis result written to: ${analysisFile}`); + fs.writeFileSync(analysisFile, JSON.stringify(resultToSave, null, 2)); + console.log(`Analysis result written to: ${analysisFile}`); - } catch (error: any) { - console.error('Error:', error.message); - process.exit(1); - } + } catch (error: any) { + console.error('Error:', error.message); + process.exit(1); + } } main(); \ No newline at end of file diff --git a/tests/src/generate-cpp-serializer.ts b/tests/src/generate-cpp-serializer.ts index c9daa7bd2..fc0789417 100644 --- a/tests/src/generate-cpp-serializer.ts +++ b/tests/src/generate-cpp-serializer.ts @@ -7,454 +7,454 @@ import type { Property, SerializerIR } from './generate-serializer-ir'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -function transformType(javaType: string): string { - // Remove package prefixes - const simpleName = javaType.includes('.') ? javaType.split('.').pop()! : javaType; +function transformType (javaType: string): string { + // Remove package prefixes + const simpleName = javaType.includes('.') ? javaType.split('.').pop()! : javaType; - // Handle primitive types - if (simpleName === 'String') return 'const String&'; - if (simpleName === 'int') return 'int'; - if (simpleName === 'float') return 'float'; - if (simpleName === 'boolean') return 'bool'; - if (simpleName === 'short') return 'short'; + // Handle primitive types + if (simpleName === 'String') return 'const String&'; + if (simpleName === 'int') return 'int'; + if (simpleName === 'float') return 'float'; + if (simpleName === 'boolean') return 'bool'; + if (simpleName === 'short') return 'short'; - // Handle arrays - if (simpleName.endsWith('[]')) { - const baseType = simpleName.slice(0, -2); - return `Array<${transformType(baseType)}>`; - } + // Handle arrays + if (simpleName.endsWith('[]')) { + const baseType = simpleName.slice(0, -2); + return `Array<${transformType(baseType)}>`; + } - // Object types become pointers - return simpleName; + // Object types become pointers + return simpleName; } -function generatePropertyCode(property: Property, indent: string, enumMappings: { [enumName: string]: { [javaValue: string]: string } }): string[] { - const lines: string[] = []; +function generatePropertyCode (property: Property, indent: string, enumMappings: { [enumName: string]: { [javaValue: string]: string } }): string[] { + const lines: string[] = []; - // Transform field access for C++: add _ prefix except for Color fields - let accessor = `obj->${property.getter}`; - if (!property.getter.includes('()')) { - // This is a field access, not a method call - const fieldName = property.getter; - // Color fields (r, g, b, a) don't get _ prefix, all others do - const isColorField = ['r', 'g', 'b', 'a'].includes(fieldName); - if (!isColorField) { - accessor = `obj->_${fieldName}`; - } else { - accessor = `obj->${fieldName}`; - } - } + // Transform field access for C++: add _ prefix except for Color fields + let accessor = `obj->${property.getter}`; + if (!property.getter.includes('()')) { + // This is a field access, not a method call + const fieldName = property.getter; + // Color fields (r, g, b, a) don't get _ prefix, all others do + const isColorField = ['r', 'g', 'b', 'a'].includes(fieldName); + if (!isColorField) { + accessor = `obj->_${fieldName}`; + } else { + accessor = `obj->${fieldName}`; + } + } - // C++-specific: darkColor specifically has hasDarkColor() method - const isDarkColor = property.kind === "object" && - property.valueType === "Color" && - property.getter === "getDarkColor()"; + // C++-specific: darkColor specifically has hasDarkColor() method + const isDarkColor = property.kind === "object" && + property.valueType === "Color" && + property.getter === "getDarkColor()"; - if (isDarkColor) { - const colorAccessor = `&${accessor}`; + if (isDarkColor) { + const colorAccessor = `&${accessor}`; - lines.push(`${indent}if (obj->hasDarkColor()) {`); - lines.push(`${indent} ${property.writeMethodCall}(${colorAccessor});`); - lines.push(`${indent}} else {`); - lines.push(`${indent} _json.writeNull();`); - lines.push(`${indent}}`); - return lines; - } + lines.push(`${indent}if (obj->hasDarkColor()) {`); + lines.push(`${indent} ${property.writeMethodCall}(${colorAccessor});`); + lines.push(`${indent}} else {`); + lines.push(`${indent} _json.writeNull();`); + lines.push(`${indent}}`); + return lines; + } - switch (property.kind) { - case "primitive": - lines.push(`${indent}_json.writeValue(${accessor});`); - break; + switch (property.kind) { + case "primitive": + lines.push(`${indent}_json.writeValue(${accessor});`); + break; - case "object": - if (property.isNullable) { - lines.push(`${indent}if (${accessor} == nullptr) {`); - lines.push(`${indent} _json.writeNull();`); - lines.push(`${indent}} else {`); - lines.push(`${indent} ${property.writeMethodCall}(${accessor});`); - lines.push(`${indent}}`); - } else { - lines.push(`${indent}${property.writeMethodCall}(${accessor});`); - } - break; + case "object": + if (property.isNullable) { + lines.push(`${indent}if (${accessor} == nullptr) {`); + lines.push(`${indent} _json.writeNull();`); + lines.push(`${indent}} else {`); + lines.push(`${indent} ${property.writeMethodCall}(${accessor});`); + lines.push(`${indent}}`); + } else { + lines.push(`${indent}${property.writeMethodCall}(${accessor});`); + } + break; - case "enum": - const enumName = property.enumName; - const enumMap = enumMappings[enumName]; + case "enum": + const enumName = property.enumName; + const enumMap = enumMappings[enumName]; - if (enumMap && Object.keys(enumMap).length > 0) { - // Generate switch statement for enum - lines.push(`${indent}_json.writeValue([&]() -> String {`); - lines.push(`${indent} switch(${accessor}) {`); + if (enumMap && Object.keys(enumMap).length > 0) { + // Generate switch statement for enum + lines.push(`${indent}_json.writeValue([&]() -> String {`); + lines.push(`${indent} switch(${accessor}) {`); - for (const [javaValue, cppValue] of Object.entries(enumMap)) { - lines.push(`${indent} case ${cppValue}: return "${javaValue}";`); - } + for (const [javaValue, cppValue] of Object.entries(enumMap)) { + lines.push(`${indent} case ${cppValue}: return "${javaValue}";`); + } - lines.push(`${indent} default: return "unknown";`); - lines.push(`${indent} }`); - lines.push(`${indent}}());`); - } else { - // Fallback if no enum mapping - lines.push(`${indent}_json.writeValue(String::valueOf((int)${accessor}));`); - } - break; + lines.push(`${indent} default: return "unknown";`); + lines.push(`${indent} }`); + lines.push(`${indent}}());`); + } else { + // Fallback if no enum mapping + lines.push(`${indent}_json.writeValue(String::valueOf((int)${accessor}));`); + } + break; - case "array": - // In C++, arrays are never null - empty arrays (size() == 0) are equivalent to Java null - lines.push(`${indent}_json.writeArrayStart();`); - lines.push(`${indent}for (size_t i = 0; i < ${accessor}.size(); i++) {`); - const elementAccess = `${accessor}[i]`; - if (property.elementKind === "primitive") { - lines.push(`${indent} _json.writeValue(${elementAccess});`); - } else { - lines.push(`${indent} ${property.writeMethodCall}(${elementAccess});`); - } - lines.push(`${indent}}`); - lines.push(`${indent}_json.writeArrayEnd();`); - break; + case "array": + // In C++, arrays are never null - empty arrays (size() == 0) are equivalent to Java null + lines.push(`${indent}_json.writeArrayStart();`); + lines.push(`${indent}for (size_t i = 0; i < ${accessor}.size(); i++) {`); + const elementAccess = `${accessor}[i]`; + if (property.elementKind === "primitive") { + lines.push(`${indent} _json.writeValue(${elementAccess});`); + } else { + lines.push(`${indent} ${property.writeMethodCall}(${elementAccess});`); + } + lines.push(`${indent}}`); + lines.push(`${indent}_json.writeArrayEnd();`); + break; - case "nestedArray": - // Nested arrays are always considered non-null in both Java and C++ - lines.push(`${indent}_json.writeArrayStart();`); - lines.push(`${indent}for (size_t i = 0; i < ${accessor}.size(); i++) {`); - lines.push(`${indent} Array<${property.elementType}>& nestedArray = ${accessor}[i];`); - lines.push(`${indent} _json.writeArrayStart();`); - lines.push(`${indent} for (size_t j = 0; j < nestedArray.size(); j++) {`); - lines.push(`${indent} _json.writeValue(nestedArray[j]);`); - lines.push(`${indent} }`); - lines.push(`${indent} _json.writeArrayEnd();`); - lines.push(`${indent}}`); - lines.push(`${indent}_json.writeArrayEnd();`); - break; - } + case "nestedArray": + // Nested arrays are always considered non-null in both Java and C++ + lines.push(`${indent}_json.writeArrayStart();`); + lines.push(`${indent}for (size_t i = 0; i < ${accessor}.size(); i++) {`); + lines.push(`${indent} Array<${property.elementType}>& nestedArray = ${accessor}[i];`); + lines.push(`${indent} _json.writeArrayStart();`); + lines.push(`${indent} for (size_t j = 0; j < nestedArray.size(); j++) {`); + lines.push(`${indent} _json.writeValue(nestedArray[j]);`); + lines.push(`${indent} }`); + lines.push(`${indent} _json.writeArrayEnd();`); + lines.push(`${indent}}`); + lines.push(`${indent}_json.writeArrayEnd();`); + break; + } - return lines; + return lines; } -function generateCppFromIR(ir: SerializerIR): string { - const cppOutput: string[] = []; +function generateCppFromIR (ir: SerializerIR): string { + const cppOutput: string[] = []; - // Generate C++ file header - cppOutput.push('#ifndef Spine_SkeletonSerializer_h'); - cppOutput.push('#define Spine_SkeletonSerializer_h'); - cppOutput.push(''); - cppOutput.push('#include '); - cppOutput.push('#include "JsonWriter.h"'); - cppOutput.push('#include '); - cppOutput.push('#include '); - cppOutput.push(''); - cppOutput.push('namespace spine {'); - cppOutput.push(''); - cppOutput.push('class SkeletonSerializer {'); - cppOutput.push('private:'); - cppOutput.push(' HashMap _visitedObjects;'); - cppOutput.push(' JsonWriter _json;'); - cppOutput.push(''); - cppOutput.push('public:'); - cppOutput.push(' SkeletonSerializer() {}'); - cppOutput.push(' ~SkeletonSerializer() {}'); - cppOutput.push(''); + // Generate C++ file header + cppOutput.push('#ifndef Spine_SkeletonSerializer_h'); + cppOutput.push('#define Spine_SkeletonSerializer_h'); + cppOutput.push(''); + cppOutput.push('#include '); + cppOutput.push('#include "JsonWriter.h"'); + cppOutput.push('#include '); + cppOutput.push('#include '); + cppOutput.push(''); + cppOutput.push('namespace spine {'); + cppOutput.push(''); + cppOutput.push('class SkeletonSerializer {'); + cppOutput.push('private:'); + cppOutput.push(' HashMap _visitedObjects;'); + cppOutput.push(' JsonWriter _json;'); + cppOutput.push(''); + cppOutput.push('public:'); + cppOutput.push(' SkeletonSerializer() {}'); + cppOutput.push(' ~SkeletonSerializer() {}'); + cppOutput.push(''); - // Generate public methods - for (const method of ir.publicMethods) { - const cppParamType = transformType(method.paramType); - cppOutput.push(` String ${method.name}(${cppParamType}* ${method.paramName}) {`); - cppOutput.push(' _visitedObjects.clear();'); - cppOutput.push(' _json = JsonWriter();'); - cppOutput.push(` ${method.writeMethodCall}(${method.paramName});`); - cppOutput.push(' return _json.getString();'); - cppOutput.push(' }'); - cppOutput.push(''); - } + // Generate public methods + for (const method of ir.publicMethods) { + const cppParamType = transformType(method.paramType); + cppOutput.push(` String ${method.name}(${cppParamType}* ${method.paramName}) {`); + cppOutput.push(' _visitedObjects.clear();'); + cppOutput.push(' _json = JsonWriter();'); + cppOutput.push(` ${method.writeMethodCall}(${method.paramName});`); + cppOutput.push(' return _json.getString();'); + cppOutput.push(' }'); + cppOutput.push(''); + } - cppOutput.push('private:'); + cppOutput.push('private:'); - // Generate write methods - for (const method of ir.writeMethods) { - const shortName = method.paramType.split('.').pop()!; - const cppType = transformType(method.paramType); + // Generate write methods + for (const method of ir.writeMethods) { + const shortName = method.paramType.split('.').pop()!; + const cppType = transformType(method.paramType); - // Custom writeSkin and writeSkinEntry implementations - if (method.name === 'writeSkin') { - cppOutput.push(' void writeSkin(Skin* obj) {'); - cppOutput.push(' if (_visitedObjects.containsKey(obj)) {'); - cppOutput.push(' _json.writeValue("");'); - cppOutput.push(' return;'); - cppOutput.push(' }'); - cppOutput.push(' _visitedObjects.put(obj, true);'); - cppOutput.push(''); - cppOutput.push(' _json.writeObjectStart();'); - cppOutput.push(' _json.writeName("type");'); - cppOutput.push(' _json.writeValue("Skin");'); - cppOutput.push(''); - cppOutput.push(' _json.writeName("attachments");'); - cppOutput.push(' _json.writeArrayStart();'); - cppOutput.push(' Skin::AttachmentMap::Entries entries = obj->getAttachments();'); - cppOutput.push(' while (entries.hasNext()) {'); - cppOutput.push(' Skin::AttachmentMap::Entry& entry = entries.next();'); - cppOutput.push(' writeSkinEntry(&entry);'); - cppOutput.push(' }'); - cppOutput.push(' _json.writeArrayEnd();'); - cppOutput.push(''); - cppOutput.push(' _json.writeName("bones");'); - cppOutput.push(' _json.writeArrayStart();'); - cppOutput.push(' for (size_t i = 0; i < obj->getBones().size(); i++) {'); - cppOutput.push(' BoneData* item = obj->getBones()[i];'); - cppOutput.push(' writeBoneData(item);'); - cppOutput.push(' }'); - cppOutput.push(' _json.writeArrayEnd();'); - cppOutput.push(''); - cppOutput.push(' _json.writeName("constraints");'); - cppOutput.push(' _json.writeArrayStart();'); - cppOutput.push(' for (size_t i = 0; i < obj->getConstraints().size(); i++) {'); - cppOutput.push(' ConstraintData* item = obj->getConstraints()[i];'); - cppOutput.push(' writeConstraintData(item);'); - cppOutput.push(' }'); - cppOutput.push(' _json.writeArrayEnd();'); - cppOutput.push(''); - cppOutput.push(' _json.writeName("name");'); - cppOutput.push(' _json.writeValue(obj->getName());'); - cppOutput.push(''); - cppOutput.push(' _json.writeName("color");'); - cppOutput.push(' writeColor(&obj->getColor());'); - cppOutput.push(''); - cppOutput.push(' _json.writeObjectEnd();'); - cppOutput.push(' }'); - cppOutput.push(''); - continue; - } + // Custom writeSkin and writeSkinEntry implementations + if (method.name === 'writeSkin') { + cppOutput.push(' void writeSkin(Skin* obj) {'); + cppOutput.push(' if (_visitedObjects.containsKey(obj)) {'); + cppOutput.push(' _json.writeValue("");'); + cppOutput.push(' return;'); + cppOutput.push(' }'); + cppOutput.push(' _visitedObjects.put(obj, true);'); + cppOutput.push(''); + cppOutput.push(' _json.writeObjectStart();'); + cppOutput.push(' _json.writeName("type");'); + cppOutput.push(' _json.writeValue("Skin");'); + cppOutput.push(''); + cppOutput.push(' _json.writeName("attachments");'); + cppOutput.push(' _json.writeArrayStart();'); + cppOutput.push(' Skin::AttachmentMap::Entries entries = obj->getAttachments();'); + cppOutput.push(' while (entries.hasNext()) {'); + cppOutput.push(' Skin::AttachmentMap::Entry& entry = entries.next();'); + cppOutput.push(' writeSkinEntry(&entry);'); + cppOutput.push(' }'); + cppOutput.push(' _json.writeArrayEnd();'); + cppOutput.push(''); + cppOutput.push(' _json.writeName("bones");'); + cppOutput.push(' _json.writeArrayStart();'); + cppOutput.push(' for (size_t i = 0; i < obj->getBones().size(); i++) {'); + cppOutput.push(' BoneData* item = obj->getBones()[i];'); + cppOutput.push(' writeBoneData(item);'); + cppOutput.push(' }'); + cppOutput.push(' _json.writeArrayEnd();'); + cppOutput.push(''); + cppOutput.push(' _json.writeName("constraints");'); + cppOutput.push(' _json.writeArrayStart();'); + cppOutput.push(' for (size_t i = 0; i < obj->getConstraints().size(); i++) {'); + cppOutput.push(' ConstraintData* item = obj->getConstraints()[i];'); + cppOutput.push(' writeConstraintData(item);'); + cppOutput.push(' }'); + cppOutput.push(' _json.writeArrayEnd();'); + cppOutput.push(''); + cppOutput.push(' _json.writeName("name");'); + cppOutput.push(' _json.writeValue(obj->getName());'); + cppOutput.push(''); + cppOutput.push(' _json.writeName("color");'); + cppOutput.push(' writeColor(&obj->getColor());'); + cppOutput.push(''); + cppOutput.push(' _json.writeObjectEnd();'); + cppOutput.push(' }'); + cppOutput.push(''); + continue; + } - // Custom writeSkinEntry - if (method.name === 'writeSkinEntry') { - // Custom writeSkinEntry implementation - cppOutput.push(' void writeSkinEntry(Skin::AttachmentMap::Entry* obj) {'); - cppOutput.push(' _json.writeObjectStart();'); - cppOutput.push(' _json.writeName("type");'); - cppOutput.push(' _json.writeValue("SkinEntry");'); - cppOutput.push(' _json.writeName("slotIndex");'); - cppOutput.push(' _json.writeValue((int)obj->_slotIndex);'); - cppOutput.push(' _json.writeName("name");'); - cppOutput.push(' _json.writeValue(obj->_name);'); - cppOutput.push(' _json.writeName("attachment");'); - cppOutput.push(' writeAttachment(obj->_attachment);'); - cppOutput.push(' _json.writeObjectEnd();'); - cppOutput.push(' }'); - cppOutput.push(''); - continue; - } + // Custom writeSkinEntry + if (method.name === 'writeSkinEntry') { + // Custom writeSkinEntry implementation + cppOutput.push(' void writeSkinEntry(Skin::AttachmentMap::Entry* obj) {'); + cppOutput.push(' _json.writeObjectStart();'); + cppOutput.push(' _json.writeName("type");'); + cppOutput.push(' _json.writeValue("SkinEntry");'); + cppOutput.push(' _json.writeName("slotIndex");'); + cppOutput.push(' _json.writeValue((int)obj->_slotIndex);'); + cppOutput.push(' _json.writeName("name");'); + cppOutput.push(' _json.writeValue(obj->_name);'); + cppOutput.push(' _json.writeName("attachment");'); + cppOutput.push(' writeAttachment(obj->_attachment);'); + cppOutput.push(' _json.writeObjectEnd();'); + cppOutput.push(' }'); + cppOutput.push(''); + continue; + } - cppOutput.push(` void ${method.name}(${cppType}* obj) {`); + cppOutput.push(` void ${method.name}(${cppType}* obj) {`); - if (method.isAbstractType) { - // Handle abstract types with instanceof chain - if (method.subtypeChecks && method.subtypeChecks.length > 0) { - let first = true; - for (const subtype of method.subtypeChecks) { - const subtypeShortName = subtype.typeName.split('.').pop()!; + if (method.isAbstractType) { + // Handle abstract types with instanceof chain + if (method.subtypeChecks && method.subtypeChecks.length > 0) { + let first = true; + for (const subtype of method.subtypeChecks) { + const subtypeShortName = subtype.typeName.split('.').pop()!; - if (first) { - cppOutput.push(` if (obj->getRTTI().instanceOf(${subtypeShortName}::rtti)) {`); - first = false; - } else { - cppOutput.push(` } else if (obj->getRTTI().instanceOf(${subtypeShortName}::rtti)) {`); - } - cppOutput.push(` ${subtype.writeMethodCall}((${subtypeShortName}*)obj);`); - } - cppOutput.push(' } else {'); - cppOutput.push(` fprintf(stderr, "Error: Unknown ${shortName} type\\n"); exit(1);`); - cppOutput.push(' }'); - } else { - cppOutput.push(' _json.writeNull(); // No concrete implementations after filtering exclusions'); - } - } else { - // Handle concrete types - // Add cycle detection - cppOutput.push(' if (_visitedObjects.containsKey(obj)) {'); - cppOutput.push(' _json.writeValue("");'); - cppOutput.push(' return;'); - cppOutput.push(' }'); - cppOutput.push(' _visitedObjects.put(obj, true);'); - cppOutput.push(''); + if (first) { + cppOutput.push(` if (obj->getRTTI().instanceOf(${subtypeShortName}::rtti)) {`); + first = false; + } else { + cppOutput.push(` } else if (obj->getRTTI().instanceOf(${subtypeShortName}::rtti)) {`); + } + cppOutput.push(` ${subtype.writeMethodCall}((${subtypeShortName}*)obj);`); + } + cppOutput.push(' } else {'); + cppOutput.push(` fprintf(stderr, "Error: Unknown ${shortName} type\\n"); exit(1);`); + cppOutput.push(' }'); + } else { + cppOutput.push(' _json.writeNull(); // No concrete implementations after filtering exclusions'); + } + } else { + // Handle concrete types + // Add cycle detection + cppOutput.push(' if (_visitedObjects.containsKey(obj)) {'); + cppOutput.push(' _json.writeValue("");'); + cppOutput.push(' return;'); + cppOutput.push(' }'); + cppOutput.push(' _visitedObjects.put(obj, true);'); + cppOutput.push(''); - cppOutput.push(' _json.writeObjectStart();'); + cppOutput.push(' _json.writeObjectStart();'); - // Write type field - cppOutput.push(' _json.writeName("type");'); - cppOutput.push(` _json.writeValue("${shortName}");`); + // Write type field + cppOutput.push(' _json.writeName("type");'); + cppOutput.push(` _json.writeValue("${shortName}");`); - // Write properties - for (const property of method.properties) { - cppOutput.push(''); - cppOutput.push(` _json.writeName("${property.name}");`); - const propertyLines = generatePropertyCode(property, ' ', ir.enumMappings); - cppOutput.push(...propertyLines); - } + // Write properties + for (const property of method.properties) { + cppOutput.push(''); + cppOutput.push(` _json.writeName("${property.name}");`); + const propertyLines = generatePropertyCode(property, ' ', ir.enumMappings); + cppOutput.push(...propertyLines); + } - cppOutput.push(''); - cppOutput.push(' _json.writeObjectEnd();'); - } + cppOutput.push(''); + cppOutput.push(' _json.writeObjectEnd();'); + } - cppOutput.push(' }'); - cppOutput.push(''); - } + cppOutput.push(' }'); + cppOutput.push(''); + } - // Add custom helper methods for special types - cppOutput.push(' // Custom helper methods'); - cppOutput.push(' void writeColor(Color* obj) {'); - cppOutput.push(' if (obj == nullptr) {'); - cppOutput.push(' _json.writeNull();'); - cppOutput.push(' } else {'); - cppOutput.push(' _json.writeObjectStart();'); - cppOutput.push(' _json.writeName("r");'); - cppOutput.push(' _json.writeValue(obj->r);'); - cppOutput.push(' _json.writeName("g");'); - cppOutput.push(' _json.writeValue(obj->g);'); - cppOutput.push(' _json.writeName("b");'); - cppOutput.push(' _json.writeValue(obj->b);'); - cppOutput.push(' _json.writeName("a");'); - cppOutput.push(' _json.writeValue(obj->a);'); - cppOutput.push(' _json.writeObjectEnd();'); - cppOutput.push(' }'); - cppOutput.push(' }'); - cppOutput.push(''); + // Add custom helper methods for special types + cppOutput.push(' // Custom helper methods'); + cppOutput.push(' void writeColor(Color* obj) {'); + cppOutput.push(' if (obj == nullptr) {'); + cppOutput.push(' _json.writeNull();'); + cppOutput.push(' } else {'); + cppOutput.push(' _json.writeObjectStart();'); + cppOutput.push(' _json.writeName("r");'); + cppOutput.push(' _json.writeValue(obj->r);'); + cppOutput.push(' _json.writeName("g");'); + cppOutput.push(' _json.writeValue(obj->g);'); + cppOutput.push(' _json.writeName("b");'); + cppOutput.push(' _json.writeValue(obj->b);'); + cppOutput.push(' _json.writeName("a");'); + cppOutput.push(' _json.writeValue(obj->a);'); + cppOutput.push(' _json.writeObjectEnd();'); + cppOutput.push(' }'); + cppOutput.push(' }'); + cppOutput.push(''); - cppOutput.push(' void writeColor(const Color& obj) {'); - cppOutput.push(' _json.writeObjectStart();'); - cppOutput.push(' _json.writeName("r");'); - cppOutput.push(' _json.writeValue(obj.r);'); - cppOutput.push(' _json.writeName("g");'); - cppOutput.push(' _json.writeValue(obj.g);'); - cppOutput.push(' _json.writeName("b");'); - cppOutput.push(' _json.writeValue(obj.b);'); - cppOutput.push(' _json.writeName("a");'); - cppOutput.push(' _json.writeValue(obj.a);'); - cppOutput.push(' _json.writeObjectEnd();'); - cppOutput.push(' }'); - cppOutput.push(''); + cppOutput.push(' void writeColor(const Color& obj) {'); + cppOutput.push(' _json.writeObjectStart();'); + cppOutput.push(' _json.writeName("r");'); + cppOutput.push(' _json.writeValue(obj.r);'); + cppOutput.push(' _json.writeName("g");'); + cppOutput.push(' _json.writeValue(obj.g);'); + cppOutput.push(' _json.writeName("b");'); + cppOutput.push(' _json.writeValue(obj.b);'); + cppOutput.push(' _json.writeName("a");'); + cppOutput.push(' _json.writeValue(obj.a);'); + cppOutput.push(' _json.writeObjectEnd();'); + cppOutput.push(' }'); + cppOutput.push(''); - cppOutput.push(' void writeTextureRegion(TextureRegion* obj) {'); - cppOutput.push(' if (obj == nullptr) {'); - cppOutput.push(' _json.writeNull();'); - cppOutput.push(' } else {'); - cppOutput.push(' _json.writeObjectStart();'); - cppOutput.push(' _json.writeName("u");'); - cppOutput.push(' _json.writeValue(obj->getU());'); - cppOutput.push(' _json.writeName("v");'); - cppOutput.push(' _json.writeValue(obj->getV());'); - cppOutput.push(' _json.writeName("u2");'); - cppOutput.push(' _json.writeValue(obj->getU2());'); - cppOutput.push(' _json.writeName("v2");'); - cppOutput.push(' _json.writeValue(obj->getV2());'); - cppOutput.push(' _json.writeName("width");'); - cppOutput.push(' _json.writeValue(obj->getRegionWidth());'); - cppOutput.push(' _json.writeName("height");'); - cppOutput.push(' _json.writeValue(obj->getRegionHeight());'); - cppOutput.push(' _json.writeObjectEnd();'); - cppOutput.push(' }'); - cppOutput.push(' }'); - cppOutput.push(''); + cppOutput.push(' void writeTextureRegion(TextureRegion* obj) {'); + cppOutput.push(' if (obj == nullptr) {'); + cppOutput.push(' _json.writeNull();'); + cppOutput.push(' } else {'); + cppOutput.push(' _json.writeObjectStart();'); + cppOutput.push(' _json.writeName("u");'); + cppOutput.push(' _json.writeValue(obj->getU());'); + cppOutput.push(' _json.writeName("v");'); + cppOutput.push(' _json.writeValue(obj->getV());'); + cppOutput.push(' _json.writeName("u2");'); + cppOutput.push(' _json.writeValue(obj->getU2());'); + cppOutput.push(' _json.writeName("v2");'); + cppOutput.push(' _json.writeValue(obj->getV2());'); + cppOutput.push(' _json.writeName("width");'); + cppOutput.push(' _json.writeValue(obj->getRegionWidth());'); + cppOutput.push(' _json.writeName("height");'); + cppOutput.push(' _json.writeValue(obj->getRegionHeight());'); + cppOutput.push(' _json.writeObjectEnd();'); + cppOutput.push(' }'); + cppOutput.push(' }'); + cppOutput.push(''); - cppOutput.push(' void writeTextureRegion(const TextureRegion& obj) {'); - cppOutput.push(' _json.writeObjectStart();'); - cppOutput.push(' _json.writeName("u");'); - cppOutput.push(' _json.writeValue(obj.getU());'); - cppOutput.push(' _json.writeName("v");'); - cppOutput.push(' _json.writeValue(obj.getV());'); - cppOutput.push(' _json.writeName("u2");'); - cppOutput.push(' _json.writeValue(obj.getU2());'); - cppOutput.push(' _json.writeName("v2");'); - cppOutput.push(' _json.writeValue(obj.getV2());'); - cppOutput.push(' _json.writeName("width");'); - cppOutput.push(' _json.writeValue(obj.getRegionWidth());'); - cppOutput.push(' _json.writeName("height");'); - cppOutput.push(' _json.writeValue(obj.getRegionHeight());'); - cppOutput.push(' _json.writeObjectEnd();'); - cppOutput.push(' }'); - cppOutput.push(''); + cppOutput.push(' void writeTextureRegion(const TextureRegion& obj) {'); + cppOutput.push(' _json.writeObjectStart();'); + cppOutput.push(' _json.writeName("u");'); + cppOutput.push(' _json.writeValue(obj.getU());'); + cppOutput.push(' _json.writeName("v");'); + cppOutput.push(' _json.writeValue(obj.getV());'); + cppOutput.push(' _json.writeName("u2");'); + cppOutput.push(' _json.writeValue(obj.getU2());'); + cppOutput.push(' _json.writeName("v2");'); + cppOutput.push(' _json.writeValue(obj.getV2());'); + cppOutput.push(' _json.writeName("width");'); + cppOutput.push(' _json.writeValue(obj.getRegionWidth());'); + cppOutput.push(' _json.writeName("height");'); + cppOutput.push(' _json.writeValue(obj.getRegionHeight());'); + cppOutput.push(' _json.writeObjectEnd();'); + cppOutput.push(' }'); + cppOutput.push(''); - cppOutput.push(' void writeIntArray(const Array& obj) {'); - cppOutput.push(' _json.writeArrayStart();'); - cppOutput.push(' for (size_t i = 0; i < obj.size(); i++) {'); - cppOutput.push(' _json.writeValue(obj[i]);'); - cppOutput.push(' }'); - cppOutput.push(' _json.writeArrayEnd();'); - cppOutput.push(' }'); - cppOutput.push(''); + cppOutput.push(' void writeIntArray(const Array& obj) {'); + cppOutput.push(' _json.writeArrayStart();'); + cppOutput.push(' for (size_t i = 0; i < obj.size(); i++) {'); + cppOutput.push(' _json.writeValue(obj[i]);'); + cppOutput.push(' }'); + cppOutput.push(' _json.writeArrayEnd();'); + cppOutput.push(' }'); + cppOutput.push(''); - cppOutput.push(' void writeFloatArray(const Array& obj) {'); - cppOutput.push(' _json.writeArrayStart();'); - cppOutput.push(' for (size_t i = 0; i < obj.size(); i++) {'); - cppOutput.push(' _json.writeValue(obj[i]);'); - cppOutput.push(' }'); - cppOutput.push(' _json.writeArrayEnd();'); - cppOutput.push(' }'); - cppOutput.push(''); + cppOutput.push(' void writeFloatArray(const Array& obj) {'); + cppOutput.push(' _json.writeArrayStart();'); + cppOutput.push(' for (size_t i = 0; i < obj.size(); i++) {'); + cppOutput.push(' _json.writeValue(obj[i]);'); + cppOutput.push(' }'); + cppOutput.push(' _json.writeArrayEnd();'); + cppOutput.push(' }'); + cppOutput.push(''); - // Add reference versions for write methods (excluding custom implementations) - cppOutput.push(' // Reference versions of write methods'); - const writeMethods = ir.writeMethods.filter(m => - !m.isAbstractType && - m.name !== 'writeSkin' && - m.name !== 'writeSkinEntry' - ); - for (const method of writeMethods) { - const cppType = transformType(method.paramType); - cppOutput.push(` void ${method.name}(const ${cppType}& obj) {`); - cppOutput.push(` ${method.name}(const_cast<${cppType}*>(&obj));`); - cppOutput.push(' }'); - cppOutput.push(''); - } + // Add reference versions for write methods (excluding custom implementations) + cppOutput.push(' // Reference versions of write methods'); + const writeMethods = ir.writeMethods.filter(m => + !m.isAbstractType && + m.name !== 'writeSkin' && + m.name !== 'writeSkinEntry' + ); + for (const method of writeMethods) { + const cppType = transformType(method.paramType); + cppOutput.push(` void ${method.name}(const ${cppType}& obj) {`); + cppOutput.push(` ${method.name}(const_cast<${cppType}*>(&obj));`); + cppOutput.push(' }'); + cppOutput.push(''); + } - // C++ footer - cppOutput.push('};'); - cppOutput.push(''); - cppOutput.push('} // namespace spine'); - cppOutput.push(''); - cppOutput.push('#endif'); + // C++ footer + cppOutput.push('};'); + cppOutput.push(''); + cppOutput.push('} // namespace spine'); + cppOutput.push(''); + cppOutput.push('#endif'); - return cppOutput.join('\n'); + return cppOutput.join('\n'); } -async function main() { - try { - // Read the IR file - const irFile = path.resolve(__dirname, '../output/serializer-ir.json'); - if (!fs.existsSync(irFile)) { - console.error('Serializer IR not found. Run generate-serializer-ir.ts first.'); - process.exit(1); - } +async function main () { + try { + // Read the IR file + const irFile = path.resolve(__dirname, '../output/serializer-ir.json'); + if (!fs.existsSync(irFile)) { + console.error('Serializer IR not found. Run generate-serializer-ir.ts first.'); + process.exit(1); + } - const ir: SerializerIR = JSON.parse(fs.readFileSync(irFile, 'utf8')); + const ir: SerializerIR = JSON.parse(fs.readFileSync(irFile, 'utf8')); - // Generate C++ serializer from IR - const cppCode = generateCppFromIR(ir); + // Generate C++ serializer from IR + const cppCode = generateCppFromIR(ir); - // Write the C++ file - const cppFile = path.resolve( - __dirname, - '../../spine-cpp/tests/SkeletonSerializer.h' - ); + // Write the C++ file + const cppFile = path.resolve( + __dirname, + '../../spine-cpp/tests/SkeletonSerializer.h' + ); - fs.mkdirSync(path.dirname(cppFile), { recursive: true }); - fs.writeFileSync(cppFile, cppCode); + fs.mkdirSync(path.dirname(cppFile), { recursive: true }); + fs.writeFileSync(cppFile, cppCode); - console.log(`Generated C++ serializer from IR: ${cppFile}`); - console.log(`- ${ir.publicMethods.length} public methods`); - console.log(`- ${ir.writeMethods.length} write methods`); - console.log(`- ${Object.keys(ir.enumMappings).length} enum mappings`); + console.log(`Generated C++ serializer from IR: ${cppFile}`); + console.log(`- ${ir.publicMethods.length} public methods`); + console.log(`- ${ir.writeMethods.length} write methods`); + console.log(`- ${Object.keys(ir.enumMappings).length} enum mappings`); - } catch (error: any) { - console.error('Error:', error.message); - console.error('Stack:', error.stack); - process.exit(1); - } + } catch (error: any) { + console.error('Error:', error.message); + console.error('Stack:', error.stack); + process.exit(1); + } } // Allow running as a script or importing the function if (import.meta.url === `file://${process.argv[1]}`) { - main(); + main(); } export { generateCppFromIR }; diff --git a/tests/src/generate-java-serializer.ts b/tests/src/generate-java-serializer.ts index 5b9ae67a7..cb0a5fe28 100644 --- a/tests/src/generate-java-serializer.ts +++ b/tests/src/generate-java-serializer.ts @@ -7,323 +7,323 @@ import type { Property, SerializerIR, WriteMethod } from './generate-serializer- const __dirname = path.dirname(fileURLToPath(import.meta.url)); -function generatePropertyCode(property: Property, indent: string, method?: WriteMethod): string[] { - const lines: string[] = []; - const accessor = `obj.${property.getter}`; +function generatePropertyCode (property: Property, indent: string, method?: WriteMethod): string[] { + const lines: string[] = []; + const accessor = `obj.${property.getter}`; - switch (property.kind) { - case "primitive": - lines.push(`${indent}json.writeValue(${accessor});`); - break; + switch (property.kind) { + case "primitive": + lines.push(`${indent}json.writeValue(${accessor});`); + break; - case "object": - if (property.isNullable) { - lines.push(`${indent}if (${accessor} == null) {`); - lines.push(`${indent} json.writeNull();`); - lines.push(`${indent}} else {`); - lines.push(`${indent} ${property.writeMethodCall}(${accessor});`); - lines.push(`${indent}}`); - } else { - lines.push(`${indent}${property.writeMethodCall}(${accessor});`); - } - break; + case "object": + if (property.isNullable) { + lines.push(`${indent}if (${accessor} == null) {`); + lines.push(`${indent} json.writeNull();`); + lines.push(`${indent}} else {`); + lines.push(`${indent} ${property.writeMethodCall}(${accessor});`); + lines.push(`${indent}}`); + } else { + lines.push(`${indent}${property.writeMethodCall}(${accessor});`); + } + break; - case "enum": - if (property.isNullable) { - lines.push(`${indent}if (${accessor} == null) {`); - lines.push(`${indent} json.writeNull();`); - lines.push(`${indent}} else {`); - lines.push(`${indent} json.writeValue(${accessor}.name());`); - lines.push(`${indent}}`); - } else { - lines.push(`${indent}json.writeValue(${accessor}.name());`); - } - break; + case "enum": + if (property.isNullable) { + lines.push(`${indent}if (${accessor} == null) {`); + lines.push(`${indent} json.writeNull();`); + lines.push(`${indent}} else {`); + lines.push(`${indent} json.writeValue(${accessor}.name());`); + lines.push(`${indent}}`); + } else { + lines.push(`${indent}json.writeValue(${accessor}.name());`); + } + break; - case "array": - // Special handling for Skin attachments - sort by slot index - const isSkinAttachments = method?.paramType === 'Skin' && property.name === 'attachments' && property.elementType === 'SkinEntry'; - const sortedAccessor = isSkinAttachments ? 'sortedAttachments' : accessor; + case "array": + // Special handling for Skin attachments - sort by slot index + const isSkinAttachments = method?.paramType === 'Skin' && property.name === 'attachments' && property.elementType === 'SkinEntry'; + const sortedAccessor = isSkinAttachments ? 'sortedAttachments' : accessor; - if (isSkinAttachments) { - lines.push(`${indent}Array<${property.elementType}> sortedAttachments = new Array<>(${accessor});`); - lines.push(`${indent}sortedAttachments.sort((a, b) -> Integer.compare(a.getSlotIndex(), b.getSlotIndex()));`); - } + if (isSkinAttachments) { + lines.push(`${indent}Array<${property.elementType}> sortedAttachments = new Array<>(${accessor});`); + lines.push(`${indent}sortedAttachments.sort((a, b) -> Integer.compare(a.getSlotIndex(), b.getSlotIndex()));`); + } - if (property.isNullable) { - lines.push(`${indent}if (${accessor} == null) {`); - lines.push(`${indent} json.writeNull();`); - lines.push(`${indent}} else {`); - lines.push(`${indent} json.writeArrayStart();`); - lines.push(`${indent} for (${property.elementType} item : ${sortedAccessor}) {`); - if (property.elementKind === "primitive") { - lines.push(`${indent} json.writeValue(item);`); - } else { - lines.push(`${indent} ${property.writeMethodCall}(item);`); - } - lines.push(`${indent} }`); - lines.push(`${indent} json.writeArrayEnd();`); - lines.push(`${indent}}`); - } else { - lines.push(`${indent}json.writeArrayStart();`); - lines.push(`${indent}for (${property.elementType} item : ${sortedAccessor}) {`); - if (property.elementKind === "primitive") { - lines.push(`${indent} json.writeValue(item);`); - } else { - lines.push(`${indent} ${property.writeMethodCall}(item);`); - } - lines.push(`${indent}}`); - lines.push(`${indent}json.writeArrayEnd();`); - } - break; + if (property.isNullable) { + lines.push(`${indent}if (${accessor} == null) {`); + lines.push(`${indent} json.writeNull();`); + lines.push(`${indent}} else {`); + lines.push(`${indent} json.writeArrayStart();`); + lines.push(`${indent} for (${property.elementType} item : ${sortedAccessor}) {`); + if (property.elementKind === "primitive") { + lines.push(`${indent} json.writeValue(item);`); + } else { + lines.push(`${indent} ${property.writeMethodCall}(item);`); + } + lines.push(`${indent} }`); + lines.push(`${indent} json.writeArrayEnd();`); + lines.push(`${indent}}`); + } else { + lines.push(`${indent}json.writeArrayStart();`); + lines.push(`${indent}for (${property.elementType} item : ${sortedAccessor}) {`); + if (property.elementKind === "primitive") { + lines.push(`${indent} json.writeValue(item);`); + } else { + lines.push(`${indent} ${property.writeMethodCall}(item);`); + } + lines.push(`${indent}}`); + lines.push(`${indent}json.writeArrayEnd();`); + } + break; - case "nestedArray": - if (property.isNullable) { - lines.push(`${indent}if (${accessor} == null) {`); - lines.push(`${indent} json.writeNull();`); - lines.push(`${indent}} else {`); - lines.push(`${indent} json.writeArrayStart();`); - lines.push(`${indent} for (${property.elementType}[] nestedArray : ${accessor}) {`); - lines.push(`${indent} if (nestedArray == null) {`); - lines.push(`${indent} json.writeNull();`); - lines.push(`${indent} } else {`); - lines.push(`${indent} json.writeArrayStart();`); - lines.push(`${indent} for (${property.elementType} elem : nestedArray) {`); - lines.push(`${indent} json.writeValue(elem);`); - lines.push(`${indent} }`); - lines.push(`${indent} json.writeArrayEnd();`); - lines.push(`${indent} }`); - lines.push(`${indent} }`); - lines.push(`${indent} json.writeArrayEnd();`); - lines.push(`${indent}}`); - } else { - lines.push(`${indent}json.writeArrayStart();`); - lines.push(`${indent}for (${property.elementType}[] nestedArray : ${accessor}) {`); - lines.push(`${indent} json.writeArrayStart();`); - lines.push(`${indent} for (${property.elementType} elem : nestedArray) {`); - lines.push(`${indent} json.writeValue(elem);`); - lines.push(`${indent} }`); - lines.push(`${indent} json.writeArrayEnd();`); - lines.push(`${indent}}`); - lines.push(`${indent}json.writeArrayEnd();`); - } - break; - } + case "nestedArray": + if (property.isNullable) { + lines.push(`${indent}if (${accessor} == null) {`); + lines.push(`${indent} json.writeNull();`); + lines.push(`${indent}} else {`); + lines.push(`${indent} json.writeArrayStart();`); + lines.push(`${indent} for (${property.elementType}[] nestedArray : ${accessor}) {`); + lines.push(`${indent} if (nestedArray == null) {`); + lines.push(`${indent} json.writeNull();`); + lines.push(`${indent} } else {`); + lines.push(`${indent} json.writeArrayStart();`); + lines.push(`${indent} for (${property.elementType} elem : nestedArray) {`); + lines.push(`${indent} json.writeValue(elem);`); + lines.push(`${indent} }`); + lines.push(`${indent} json.writeArrayEnd();`); + lines.push(`${indent} }`); + lines.push(`${indent} }`); + lines.push(`${indent} json.writeArrayEnd();`); + lines.push(`${indent}}`); + } else { + lines.push(`${indent}json.writeArrayStart();`); + lines.push(`${indent}for (${property.elementType}[] nestedArray : ${accessor}) {`); + lines.push(`${indent} json.writeArrayStart();`); + lines.push(`${indent} for (${property.elementType} elem : nestedArray) {`); + lines.push(`${indent} json.writeValue(elem);`); + lines.push(`${indent} }`); + lines.push(`${indent} json.writeArrayEnd();`); + lines.push(`${indent}}`); + lines.push(`${indent}json.writeArrayEnd();`); + } + break; + } - return lines; + return lines; } -function generateJavaFromIR(ir: SerializerIR): string { - const javaOutput: string[] = []; +function generateJavaFromIR (ir: SerializerIR): string { + const javaOutput: string[] = []; - // Generate Java file header - javaOutput.push('package com.esotericsoftware.spine.utils;'); - javaOutput.push(''); - javaOutput.push('import com.esotericsoftware.spine.*;'); - javaOutput.push('import com.esotericsoftware.spine.Animation.*;'); - javaOutput.push('import com.esotericsoftware.spine.AnimationState.*;'); - javaOutput.push('import com.esotericsoftware.spine.BoneData.Inherit;'); - javaOutput.push('import com.esotericsoftware.spine.Skin.SkinEntry;'); - javaOutput.push('import com.esotericsoftware.spine.PathConstraintData.*;'); - javaOutput.push('import com.esotericsoftware.spine.TransformConstraintData.*;'); - javaOutput.push('import com.esotericsoftware.spine.attachments.*;'); - javaOutput.push('import com.badlogic.gdx.graphics.Color;'); - javaOutput.push('import com.badlogic.gdx.graphics.g2d.TextureRegion;'); - javaOutput.push('import com.badlogic.gdx.utils.Array;'); - javaOutput.push('import com.badlogic.gdx.utils.IntArray;'); - javaOutput.push('import com.badlogic.gdx.utils.FloatArray;'); - javaOutput.push(''); - javaOutput.push('import java.util.Locale;'); - javaOutput.push('import java.util.Set;'); - javaOutput.push('import java.util.HashSet;'); - javaOutput.push(''); - javaOutput.push('public class SkeletonSerializer {'); - javaOutput.push(' private final Set visitedObjects = new HashSet<>();'); - javaOutput.push(' private JsonWriter json;'); - javaOutput.push(''); + // Generate Java file header + javaOutput.push('package com.esotericsoftware.spine.utils;'); + javaOutput.push(''); + javaOutput.push('import com.esotericsoftware.spine.*;'); + javaOutput.push('import com.esotericsoftware.spine.Animation.*;'); + javaOutput.push('import com.esotericsoftware.spine.AnimationState.*;'); + javaOutput.push('import com.esotericsoftware.spine.BoneData.Inherit;'); + javaOutput.push('import com.esotericsoftware.spine.Skin.SkinEntry;'); + javaOutput.push('import com.esotericsoftware.spine.PathConstraintData.*;'); + javaOutput.push('import com.esotericsoftware.spine.TransformConstraintData.*;'); + javaOutput.push('import com.esotericsoftware.spine.attachments.*;'); + javaOutput.push('import com.badlogic.gdx.graphics.Color;'); + javaOutput.push('import com.badlogic.gdx.graphics.g2d.TextureRegion;'); + javaOutput.push('import com.badlogic.gdx.utils.Array;'); + javaOutput.push('import com.badlogic.gdx.utils.IntArray;'); + javaOutput.push('import com.badlogic.gdx.utils.FloatArray;'); + javaOutput.push(''); + javaOutput.push('import java.util.Locale;'); + javaOutput.push('import java.util.Set;'); + javaOutput.push('import java.util.HashSet;'); + javaOutput.push(''); + javaOutput.push('public class SkeletonSerializer {'); + javaOutput.push(' private final Set visitedObjects = new HashSet<>();'); + javaOutput.push(' private JsonWriter json;'); + javaOutput.push(''); - // Generate public methods - for (const method of ir.publicMethods) { - javaOutput.push(` public String ${method.name}(${method.paramType} ${method.paramName}) {`); - javaOutput.push(' visitedObjects.clear();'); - javaOutput.push(' json = new JsonWriter();'); - javaOutput.push(` ${method.writeMethodCall}(${method.paramName});`); - javaOutput.push(' json.close();'); - javaOutput.push(' return json.getString();'); - javaOutput.push(' }'); - javaOutput.push(''); - } + // Generate public methods + for (const method of ir.publicMethods) { + javaOutput.push(` public String ${method.name}(${method.paramType} ${method.paramName}) {`); + javaOutput.push(' visitedObjects.clear();'); + javaOutput.push(' json = new JsonWriter();'); + javaOutput.push(` ${method.writeMethodCall}(${method.paramName});`); + javaOutput.push(' json.close();'); + javaOutput.push(' return json.getString();'); + javaOutput.push(' }'); + javaOutput.push(''); + } - // Generate write methods - for (const method of ir.writeMethods) { - const shortName = method.paramType.split('.').pop()!; - const className = method.paramType.includes('.') ? method.paramType : shortName; + // Generate write methods + for (const method of ir.writeMethods) { + const shortName = method.paramType.split('.').pop()!; + const className = method.paramType.includes('.') ? method.paramType : shortName; - javaOutput.push(` private void ${method.name}(${className} obj) {`); + javaOutput.push(` private void ${method.name}(${className} obj) {`); - if (method.isAbstractType) { - // Handle abstract types with instanceof chain - if (method.subtypeChecks && method.subtypeChecks.length > 0) { - let first = true; - for (const subtype of method.subtypeChecks) { - const subtypeShortName = subtype.typeName.split('.').pop()!; - const subtypeClassName = subtype.typeName.includes('.') ? subtype.typeName : subtypeShortName; + if (method.isAbstractType) { + // Handle abstract types with instanceof chain + if (method.subtypeChecks && method.subtypeChecks.length > 0) { + let first = true; + for (const subtype of method.subtypeChecks) { + const subtypeShortName = subtype.typeName.split('.').pop()!; + const subtypeClassName = subtype.typeName.includes('.') ? subtype.typeName : subtypeShortName; - if (first) { - javaOutput.push(` if (obj instanceof ${subtypeClassName}) {`); - first = false; - } else { - javaOutput.push(` } else if (obj instanceof ${subtypeClassName}) {`); - } - javaOutput.push(` ${subtype.writeMethodCall}((${subtypeClassName}) obj);`); - } - javaOutput.push(' } else {'); - javaOutput.push(` throw new RuntimeException("Unknown ${shortName} type: " + obj.getClass().getName());`); - javaOutput.push(' }'); - } else { - javaOutput.push(' json.writeNull(); // No concrete implementations after filtering exclusions'); - } - } else { - // Handle concrete types - // Add cycle detection - javaOutput.push(' if (visitedObjects.contains(obj)) {'); - javaOutput.push(' json.writeValue("");'); - javaOutput.push(' return;'); - javaOutput.push(' }'); - javaOutput.push(' visitedObjects.add(obj);'); - javaOutput.push(''); + if (first) { + javaOutput.push(` if (obj instanceof ${subtypeClassName}) {`); + first = false; + } else { + javaOutput.push(` } else if (obj instanceof ${subtypeClassName}) {`); + } + javaOutput.push(` ${subtype.writeMethodCall}((${subtypeClassName}) obj);`); + } + javaOutput.push(' } else {'); + javaOutput.push(` throw new RuntimeException("Unknown ${shortName} type: " + obj.getClass().getName());`); + javaOutput.push(' }'); + } else { + javaOutput.push(' json.writeNull(); // No concrete implementations after filtering exclusions'); + } + } else { + // Handle concrete types + // Add cycle detection + javaOutput.push(' if (visitedObjects.contains(obj)) {'); + javaOutput.push(' json.writeValue("");'); + javaOutput.push(' return;'); + javaOutput.push(' }'); + javaOutput.push(' visitedObjects.add(obj);'); + javaOutput.push(''); - javaOutput.push(' json.writeObjectStart();'); + javaOutput.push(' json.writeObjectStart();'); - // Write type field - javaOutput.push(' json.writeName("type");'); - javaOutput.push(` json.writeValue("${shortName}");`); + // Write type field + javaOutput.push(' json.writeName("type");'); + javaOutput.push(` json.writeValue("${shortName}");`); - // Write properties - for (const property of method.properties) { - javaOutput.push(''); - javaOutput.push(` json.writeName("${property.name}");`); - const propertyLines = generatePropertyCode(property, ' ', method); - javaOutput.push(...propertyLines); - } + // Write properties + for (const property of method.properties) { + javaOutput.push(''); + javaOutput.push(` json.writeName("${property.name}");`); + const propertyLines = generatePropertyCode(property, ' ', method); + javaOutput.push(...propertyLines); + } - javaOutput.push(''); - javaOutput.push(' json.writeObjectEnd();'); - } + javaOutput.push(''); + javaOutput.push(' json.writeObjectEnd();'); + } - javaOutput.push(' }'); - javaOutput.push(''); - } + javaOutput.push(' }'); + javaOutput.push(''); + } - // Add helper methods for special types - javaOutput.push(' private void writeColor(Color obj) {'); - javaOutput.push(' if (obj == null) {'); - javaOutput.push(' json.writeNull();'); - javaOutput.push(' } else {'); - javaOutput.push(' json.writeObjectStart();'); - javaOutput.push(' json.writeName("r");'); - javaOutput.push(' json.writeValue(obj.r);'); - javaOutput.push(' json.writeName("g");'); - javaOutput.push(' json.writeValue(obj.g);'); - javaOutput.push(' json.writeName("b");'); - javaOutput.push(' json.writeValue(obj.b);'); - javaOutput.push(' json.writeName("a");'); - javaOutput.push(' json.writeValue(obj.a);'); - javaOutput.push(' json.writeObjectEnd();'); - javaOutput.push(' }'); - javaOutput.push(' }'); - javaOutput.push(''); + // Add helper methods for special types + javaOutput.push(' private void writeColor(Color obj) {'); + javaOutput.push(' if (obj == null) {'); + javaOutput.push(' json.writeNull();'); + javaOutput.push(' } else {'); + javaOutput.push(' json.writeObjectStart();'); + javaOutput.push(' json.writeName("r");'); + javaOutput.push(' json.writeValue(obj.r);'); + javaOutput.push(' json.writeName("g");'); + javaOutput.push(' json.writeValue(obj.g);'); + javaOutput.push(' json.writeName("b");'); + javaOutput.push(' json.writeValue(obj.b);'); + javaOutput.push(' json.writeName("a");'); + javaOutput.push(' json.writeValue(obj.a);'); + javaOutput.push(' json.writeObjectEnd();'); + javaOutput.push(' }'); + javaOutput.push(' }'); + javaOutput.push(''); - javaOutput.push(' private void writeTextureRegion(TextureRegion obj) {'); - javaOutput.push(' if (obj == null) {'); - javaOutput.push(' json.writeNull();'); - javaOutput.push(' } else {'); - javaOutput.push(' json.writeObjectStart();'); - javaOutput.push(' json.writeName("u");'); - javaOutput.push(' json.writeValue(obj.getU());'); - javaOutput.push(' json.writeName("v");'); - javaOutput.push(' json.writeValue(obj.getV());'); - javaOutput.push(' json.writeName("u2");'); - javaOutput.push(' json.writeValue(obj.getU2());'); - javaOutput.push(' json.writeName("v2");'); - javaOutput.push(' json.writeValue(obj.getV2());'); - javaOutput.push(' json.writeName("width");'); - javaOutput.push(' json.writeValue(obj.getRegionWidth());'); - javaOutput.push(' json.writeName("height");'); - javaOutput.push(' json.writeValue(obj.getRegionHeight());'); - javaOutput.push(' json.writeObjectEnd();'); - javaOutput.push(' }'); - javaOutput.push(' }'); + javaOutput.push(' private void writeTextureRegion(TextureRegion obj) {'); + javaOutput.push(' if (obj == null) {'); + javaOutput.push(' json.writeNull();'); + javaOutput.push(' } else {'); + javaOutput.push(' json.writeObjectStart();'); + javaOutput.push(' json.writeName("u");'); + javaOutput.push(' json.writeValue(obj.getU());'); + javaOutput.push(' json.writeName("v");'); + javaOutput.push(' json.writeValue(obj.getV());'); + javaOutput.push(' json.writeName("u2");'); + javaOutput.push(' json.writeValue(obj.getU2());'); + javaOutput.push(' json.writeName("v2");'); + javaOutput.push(' json.writeValue(obj.getV2());'); + javaOutput.push(' json.writeName("width");'); + javaOutput.push(' json.writeValue(obj.getRegionWidth());'); + javaOutput.push(' json.writeName("height");'); + javaOutput.push(' json.writeValue(obj.getRegionHeight());'); + javaOutput.push(' json.writeObjectEnd();'); + javaOutput.push(' }'); + javaOutput.push(' }'); - // Add IntArray and FloatArray helper methods - javaOutput.push(''); - javaOutput.push(' private void writeIntArray(IntArray obj) {'); - javaOutput.push(' if (obj == null) {'); - javaOutput.push(' json.writeNull();'); - javaOutput.push(' } else {'); - javaOutput.push(' json.writeArrayStart();'); - javaOutput.push(' for (int i = 0; i < obj.size; i++) {'); - javaOutput.push(' json.writeValue(obj.get(i));'); - javaOutput.push(' }'); - javaOutput.push(' json.writeArrayEnd();'); - javaOutput.push(' }'); - javaOutput.push(' }'); - javaOutput.push(''); + // Add IntArray and FloatArray helper methods + javaOutput.push(''); + javaOutput.push(' private void writeIntArray(IntArray obj) {'); + javaOutput.push(' if (obj == null) {'); + javaOutput.push(' json.writeNull();'); + javaOutput.push(' } else {'); + javaOutput.push(' json.writeArrayStart();'); + javaOutput.push(' for (int i = 0; i < obj.size; i++) {'); + javaOutput.push(' json.writeValue(obj.get(i));'); + javaOutput.push(' }'); + javaOutput.push(' json.writeArrayEnd();'); + javaOutput.push(' }'); + javaOutput.push(' }'); + javaOutput.push(''); - javaOutput.push(' private void writeFloatArray(FloatArray obj) {'); - javaOutput.push(' if (obj == null) {'); - javaOutput.push(' json.writeNull();'); - javaOutput.push(' } else {'); - javaOutput.push(' json.writeArrayStart();'); - javaOutput.push(' for (int i = 0; i < obj.size; i++) {'); - javaOutput.push(' json.writeValue(obj.get(i));'); - javaOutput.push(' }'); - javaOutput.push(' json.writeArrayEnd();'); - javaOutput.push(' }'); - javaOutput.push(' }'); + javaOutput.push(' private void writeFloatArray(FloatArray obj) {'); + javaOutput.push(' if (obj == null) {'); + javaOutput.push(' json.writeNull();'); + javaOutput.push(' } else {'); + javaOutput.push(' json.writeArrayStart();'); + javaOutput.push(' for (int i = 0; i < obj.size; i++) {'); + javaOutput.push(' json.writeValue(obj.get(i));'); + javaOutput.push(' }'); + javaOutput.push(' json.writeArrayEnd();'); + javaOutput.push(' }'); + javaOutput.push(' }'); - javaOutput.push('}'); + javaOutput.push('}'); - return javaOutput.join('\n'); + return javaOutput.join('\n'); } -async function main() { - try { - // Read the IR file - const irFile = path.resolve(__dirname, '../output/serializer-ir.json'); - if (!fs.existsSync(irFile)) { - console.error('Serializer IR not found. Run generate-serializer-ir.ts first.'); - process.exit(1); - } +async function main () { + try { + // Read the IR file + const irFile = path.resolve(__dirname, '../output/serializer-ir.json'); + if (!fs.existsSync(irFile)) { + console.error('Serializer IR not found. Run generate-serializer-ir.ts first.'); + process.exit(1); + } - const ir: SerializerIR = JSON.parse(fs.readFileSync(irFile, 'utf8')); + const ir: SerializerIR = JSON.parse(fs.readFileSync(irFile, 'utf8')); - // Generate Java serializer from IR - const javaCode = generateJavaFromIR(ir); + // Generate Java serializer from IR + const javaCode = generateJavaFromIR(ir); - // Write the Java file - const javaFile = path.resolve( - __dirname, - '../../spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java' - ); + // Write the Java file + const javaFile = path.resolve( + __dirname, + '../../spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java' + ); - fs.mkdirSync(path.dirname(javaFile), { recursive: true }); - fs.writeFileSync(javaFile, javaCode); + fs.mkdirSync(path.dirname(javaFile), { recursive: true }); + fs.writeFileSync(javaFile, javaCode); - console.log(`Generated Java serializer from IR: ${javaFile}`); - console.log(`- ${ir.publicMethods.length} public methods`); - console.log(`- ${ir.writeMethods.length} write methods`); + console.log(`Generated Java serializer from IR: ${javaFile}`); + console.log(`- ${ir.publicMethods.length} public methods`); + console.log(`- ${ir.writeMethods.length} write methods`); - } catch (error: any) { - console.error('Error:', error.message); - console.error('Stack:', error.stack); - process.exit(1); - } + } catch (error: any) { + console.error('Error:', error.message); + console.error('Stack:', error.stack); + process.exit(1); + } } // Allow running as a script or importing the function if (import.meta.url === `file://${process.argv[1]}`) { - main(); + main(); } export { generateJavaFromIR }; diff --git a/tests/src/generate-serializer-ir.ts b/tests/src/generate-serializer-ir.ts index 52ed8134e..2fc3ada4e 100644 --- a/tests/src/generate-serializer-ir.ts +++ b/tests/src/generate-serializer-ir.ts @@ -9,567 +9,567 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); // IR Type Definitions interface SerializerIR { - publicMethods: PublicMethod[]; - writeMethods: WriteMethod[]; - enumMappings: { [enumName: string]: { [javaValue: string]: string } }; + publicMethods: PublicMethod[]; + writeMethods: WriteMethod[]; + enumMappings: { [enumName: string]: { [javaValue: string]: string } }; } interface PublicMethod { - name: string; - paramType: string; - paramName: string; - writeMethodCall: string; + name: string; + paramType: string; + paramName: string; + writeMethodCall: string; } interface WriteMethod { - name: string; - paramType: string; - properties: Property[]; - isAbstractType: boolean; - subtypeChecks?: SubtypeCheck[]; + name: string; + paramType: string; + properties: Property[]; + isAbstractType: boolean; + subtypeChecks?: SubtypeCheck[]; } interface SubtypeCheck { - typeName: string; - writeMethodCall: string; + typeName: string; + writeMethodCall: string; } type Property = Primitive | Object | Enum | Array | NestedArray; interface Primitive { - kind: "primitive"; - name: string; - getter: string; - valueType: string; - isNullable: boolean; + kind: "primitive"; + name: string; + getter: string; + valueType: string; + isNullable: boolean; } interface Object { - kind: "object"; - name: string; - getter: string; - valueType: string; - writeMethodCall: string; - isNullable: boolean; + kind: "object"; + name: string; + getter: string; + valueType: string; + writeMethodCall: string; + isNullable: boolean; } interface Enum { - kind: "enum"; - name: string; - getter: string; - enumName: string; - isNullable: boolean; + kind: "enum"; + name: string; + getter: string; + enumName: string; + isNullable: boolean; } interface Array { - kind: "array"; - name: string; - getter: string; - elementType: string; - elementKind: "primitive" | "object"; - writeMethodCall?: string; - isNullable: boolean; + kind: "array"; + name: string; + getter: string; + elementType: string; + elementKind: "primitive" | "object"; + writeMethodCall?: string; + isNullable: boolean; } interface NestedArray { - kind: "nestedArray"; - name: string; - getter: string; - elementType: string; - isNullable: boolean; + kind: "nestedArray"; + name: string; + getter: string; + elementType: string; + isNullable: boolean; } interface SerializedAnalysisResult { - classMap: [string, ClassInfo][]; - accessibleTypes: string[]; - abstractTypes: [string, string[]][]; - allTypesToGenerate: string[]; - typeProperties: [string, PropertyInfo[]][]; + classMap: [string, ClassInfo][]; + accessibleTypes: string[]; + abstractTypes: [string, string[]][]; + allTypesToGenerate: string[]; + typeProperties: [string, PropertyInfo[]][]; } -function loadExclusions(): { types: Set, methods: Map>, fields: Map> } { - const exclusionsPath = path.resolve(__dirname, '../java-exclusions.txt'); - const types = new Set(); - const methods = new Map>(); - const fields = new Map>(); +function loadExclusions (): { types: Set, methods: Map>, fields: Map> } { + const exclusionsPath = path.resolve(__dirname, '../java-exclusions.txt'); + const types = new Set(); + const methods = new Map>(); + const fields = new Map>(); - if (!fs.existsSync(exclusionsPath)) { - return { types, methods, fields }; - } + if (!fs.existsSync(exclusionsPath)) { + return { types, methods, fields }; + } - const content = fs.readFileSync(exclusionsPath, 'utf-8'); - const lines = content.split('\n'); + const content = fs.readFileSync(exclusionsPath, 'utf-8'); + const lines = content.split('\n'); - for (const line of lines) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith('#')) continue; + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; - const parts = trimmed.split(/\s+/); - if (parts.length < 2) continue; + const parts = trimmed.split(/\s+/); + if (parts.length < 2) continue; - const [type, className, property] = parts; + const [type, className, property] = parts; - switch (type) { - case 'type': - types.add(className); - break; - case 'method': - if (property) { - if (!methods.has(className)) { - methods.set(className, new Set()); - } - methods.get(className)!.add(property); - } - break; - case 'field': - if (property) { - if (!fields.has(className)) { - fields.set(className, new Set()); - } - fields.get(className)!.add(property); - } - break; - } - } + switch (type) { + case 'type': + types.add(className); + break; + case 'method': + if (property) { + if (!methods.has(className)) { + methods.set(className, new Set()); + } + methods.get(className)!.add(property); + } + break; + case 'field': + if (property) { + if (!fields.has(className)) { + fields.set(className, new Set()); + } + fields.get(className)!.add(property); + } + break; + } + } - return { types, methods, fields }; + return { types, methods, fields }; } -function analyzePropertyType(propType: string, classMap: Map): Property { - // Handle null annotations - const isNullable = propType.includes('@Null'); - propType = propType.replace(/@Null\s+/g, '').trim(); +function analyzePropertyType (propType: string, classMap: Map): Property { + // Handle null annotations + const isNullable = propType.includes('@Null'); + propType = propType.replace(/@Null\s+/g, '').trim(); - // Extract property name and getter from the type analysis - // This is a simplified version - in practice we'd get this from PropertyInfo - const name = "propertyName"; // placeholder - const getter = "getPropertyName"; // placeholder + // Extract property name and getter from the type analysis + // This is a simplified version - in practice we'd get this from PropertyInfo + const name = "propertyName"; // placeholder + const getter = "getPropertyName"; // placeholder - // Primitive types - if (['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(propType)) { - return { - kind: "primitive", - name, - getter, - valueType: propType, - isNullable - }; - } + // Primitive types + if (['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(propType)) { + return { + kind: "primitive", + name, + getter, + valueType: propType, + isNullable + }; + } - // Check if it's an enum - let classInfo = classMap.get(propType); - if (!classInfo && !propType.includes('.')) { - // Try to find by short name - for (const [fullName, info] of classMap) { - if (fullName.split('.').pop() === propType) { - classInfo = info; - break; - } - } - } + // Check if it's an enum + let classInfo = classMap.get(propType); + if (!classInfo && !propType.includes('.')) { + // Try to find by short name + for (const [fullName, info] of classMap) { + if (fullName.split('.').pop() === propType) { + classInfo = info; + break; + } + } + } - if (classInfo?.isEnum) { - return { - kind: "enum", - name, - getter, - enumName: propType.split('.').pop()!, - isNullable - }; - } + if (classInfo?.isEnum) { + return { + kind: "enum", + name, + getter, + enumName: propType.split('.').pop()!, + isNullable + }; + } - // Arrays - if (propType.startsWith('Array<')) { - const innerType = propType.match(/Array<(.+?)>/)![1].trim(); - const elementKind = ['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(innerType) ? "primitive" : "object"; + // Arrays + if (propType.startsWith('Array<')) { + const innerType = propType.match(/Array<(.+?)>/)![1].trim(); + const elementKind = ['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(innerType) ? "primitive" : "object"; - return { - kind: "array", - name, - getter, - elementType: innerType, - elementKind, - writeMethodCall: elementKind === "object" ? `write${innerType}` : undefined, - isNullable - }; - } + return { + kind: "array", + name, + getter, + elementType: innerType, + elementKind, + writeMethodCall: elementKind === "object" ? `write${innerType}` : undefined, + isNullable + }; + } - // Handle nested arrays (like float[][]) - if (propType.endsWith('[]')) { - const elemType = propType.slice(0, -2); - if (elemType.endsWith('[]')) { - const nestedType = elemType.slice(0, -2); - return { - kind: "nestedArray", - name, - getter, - elementType: nestedType, - isNullable - }; - } else { - const elementKind = ['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(elemType) ? "primitive" : "object"; - return { - kind: "array", - name, - getter, - elementType: elemType, - elementKind, - writeMethodCall: elementKind === "object" ? `write${elemType}` : undefined, - isNullable - }; - } - } + // Handle nested arrays (like float[][]) + if (propType.endsWith('[]')) { + const elemType = propType.slice(0, -2); + if (elemType.endsWith('[]')) { + const nestedType = elemType.slice(0, -2); + return { + kind: "nestedArray", + name, + getter, + elementType: nestedType, + isNullable + }; + } else { + const elementKind = ['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(elemType) ? "primitive" : "object"; + return { + kind: "array", + name, + getter, + elementType: elemType, + elementKind, + writeMethodCall: elementKind === "object" ? `write${elemType}` : undefined, + isNullable + }; + } + } - // Special libGDX types that get custom handling - if (['Color', 'TextureRegion', 'IntArray', 'FloatArray'].includes(propType)) { - return { - kind: "object", - name, - getter, - valueType: propType, - writeMethodCall: `write${propType}`, - isNullable - }; - } + // Special libGDX types that get custom handling + if (['Color', 'TextureRegion', 'IntArray', 'FloatArray'].includes(propType)) { + return { + kind: "object", + name, + getter, + valueType: propType, + writeMethodCall: `write${propType}`, + isNullable + }; + } - // Object types - const shortType = propType.split('.').pop()!; - return { - kind: "object", - name, - getter, - valueType: propType, - writeMethodCall: `write${shortType}`, - isNullable - }; + // Object types + const shortType = propType.split('.').pop()!; + return { + kind: "object", + name, + getter, + valueType: propType, + writeMethodCall: `write${shortType}`, + isNullable + }; } -function generateSerializerIR(analysisData: SerializedAnalysisResult): SerializerIR { - // Convert arrays back to Maps - const classMap = new Map(analysisData.classMap); - const abstractTypes = new Map(analysisData.abstractTypes); - const typeProperties = new Map(analysisData.typeProperties); - const exclusions = loadExclusions(); +function generateSerializerIR (analysisData: SerializedAnalysisResult): SerializerIR { + // Convert arrays back to Maps + const classMap = new Map(analysisData.classMap); + const abstractTypes = new Map(analysisData.abstractTypes); + const typeProperties = new Map(analysisData.typeProperties); + const exclusions = loadExclusions(); - // Generate enum mappings - const enumMappings: { [enumName: string]: { [javaValue: string]: string } } = {}; - for (const [className, classInfo] of classMap) { - if (classInfo.isEnum && classInfo.enumValues) { - const shortName = className.split('.').pop()!; - const valueMap: { [javaValue: string]: string } = {}; + // Generate enum mappings + const enumMappings: { [enumName: string]: { [javaValue: string]: string } } = {}; + for (const [className, classInfo] of classMap) { + if (classInfo.isEnum && classInfo.enumValues) { + const shortName = className.split('.').pop()!; + const valueMap: { [javaValue: string]: string } = {}; - for (const javaValue of classInfo.enumValues) { - // Convert Java enum value to C++ enum value - // e.g. "setup" -> "MixBlend_Setup", "first" -> "MixBlend_First" - const cppValue = `${shortName}_${javaValue.charAt(0).toUpperCase() + javaValue.slice(1)}`; - valueMap[javaValue] = cppValue; - } + for (const javaValue of classInfo.enumValues) { + // Convert Java enum value to C++ enum value + // e.g. "setup" -> "MixBlend_Setup", "first" -> "MixBlend_First" + const cppValue = `${shortName}_${javaValue.charAt(0).toUpperCase() + javaValue.slice(1)}`; + valueMap[javaValue] = cppValue; + } - enumMappings[shortName] = valueMap; - } - } + enumMappings[shortName] = valueMap; + } + } - // Generate public methods - const publicMethods: PublicMethod[] = [ - { - name: "serializeSkeletonData", - paramType: "SkeletonData", - paramName: "data", - writeMethodCall: "writeSkeletonData" - }, - { - name: "serializeSkeleton", - paramType: "Skeleton", - paramName: "skeleton", - writeMethodCall: "writeSkeleton" - }, - { - name: "serializeAnimationState", - paramType: "AnimationState", - paramName: "state", - writeMethodCall: "writeAnimationState" - } - ]; + // Generate public methods + const publicMethods: PublicMethod[] = [ + { + name: "serializeSkeletonData", + paramType: "SkeletonData", + paramName: "data", + writeMethodCall: "writeSkeletonData" + }, + { + name: "serializeSkeleton", + paramType: "Skeleton", + paramName: "skeleton", + writeMethodCall: "writeSkeleton" + }, + { + name: "serializeAnimationState", + paramType: "AnimationState", + paramName: "state", + writeMethodCall: "writeAnimationState" + } + ]; - // Collect all types that need write methods - const typesNeedingMethods = new Set(); + // Collect all types that need write methods + const typesNeedingMethods = new Set(); - // Add all types from allTypesToGenerate - for (const type of analysisData.allTypesToGenerate) { - typesNeedingMethods.add(type); - } + // Add all types from allTypesToGenerate + for (const type of analysisData.allTypesToGenerate) { + typesNeedingMethods.add(type); + } - // Add all abstract types that are referenced (but not excluded) - for (const [abstractType] of abstractTypes) { - if (!exclusions.types.has(abstractType)) { - typesNeedingMethods.add(abstractType); - } - } + // Add all abstract types that are referenced (but not excluded) + for (const [abstractType] of abstractTypes) { + if (!exclusions.types.has(abstractType)) { + typesNeedingMethods.add(abstractType); + } + } - // Add types referenced in properties - for (const [typeName, props] of typeProperties) { - if (!typesNeedingMethods.has(typeName)) continue; + // Add types referenced in properties + for (const [typeName, props] of typeProperties) { + if (!typesNeedingMethods.has(typeName)) continue; - for (const prop of props) { - let propType = prop.type.replace(/@Null\s+/g, '').trim(); + for (const prop of props) { + let propType = prop.type.replace(/@Null\s+/g, '').trim(); - // Extract type from Array - const arrayMatch = propType.match(/Array<(.+?)>/); - if (arrayMatch) { - propType = arrayMatch[1].trim(); - } + // Extract type from Array + const arrayMatch = propType.match(/Array<(.+?)>/); + if (arrayMatch) { + propType = arrayMatch[1].trim(); + } - // Extract type from Type[] - if (propType.endsWith('[]')) { - propType = propType.slice(0, -2); - } + // Extract type from Type[] + if (propType.endsWith('[]')) { + propType = propType.slice(0, -2); + } - // Skip primitives and special types - if (['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long', - 'Color', 'TextureRegion', 'IntArray', 'FloatArray'].includes(propType)) { - continue; - } + // Skip primitives and special types + if (['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long', + 'Color', 'TextureRegion', 'IntArray', 'FloatArray'].includes(propType)) { + continue; + } - // Add the type if it's a class (but not excluded) - if (propType.match(/^[A-Z]/)) { - if (!exclusions.types.has(propType)) { - typesNeedingMethods.add(propType); - } + // Add the type if it's a class (but not excluded) + if (propType.match(/^[A-Z]/)) { + if (!exclusions.types.has(propType)) { + typesNeedingMethods.add(propType); + } - // Also check if it's an abstract type in classMap - for (const [fullName, info] of classMap) { - if (fullName === propType || fullName.split('.').pop() === propType) { - if ((info.isAbstract || info.isInterface) && !exclusions.types.has(fullName)) { - typesNeedingMethods.add(fullName); - } - break; - } - } - } - } - } + // Also check if it's an abstract type in classMap + for (const [fullName, info] of classMap) { + if (fullName === propType || fullName.split('.').pop() === propType) { + if ((info.isAbstract || info.isInterface) && !exclusions.types.has(fullName)) { + typesNeedingMethods.add(fullName); + } + break; + } + } + } + } + } - // Generate write methods - const writeMethods: WriteMethod[] = []; - const generatedMethods = new Set(); + // Generate write methods + const writeMethods: WriteMethod[] = []; + const generatedMethods = new Set(); - for (const typeName of Array.from(typesNeedingMethods).sort()) { - const classInfo = classMap.get(typeName); - if (!classInfo) continue; + for (const typeName of Array.from(typesNeedingMethods).sort()) { + const classInfo = classMap.get(typeName); + if (!classInfo) continue; - // Skip enums - they are handled inline with .name() calls - if (classInfo.isEnum) continue; + // Skip enums - they are handled inline with .name() calls + if (classInfo.isEnum) continue; - const shortName = typeName.split('.').pop()!; + const shortName = typeName.split('.').pop()!; - // Skip if already generated (handle name collisions) - if (generatedMethods.has(shortName)) continue; - generatedMethods.add(shortName); + // Skip if already generated (handle name collisions) + if (generatedMethods.has(shortName)) continue; + generatedMethods.add(shortName); - const writeMethod: WriteMethod = { - name: `write${shortName}`, - paramType: typeName, - properties: [], - isAbstractType: classInfo.isAbstract || classInfo.isInterface || false - }; + const writeMethod: WriteMethod = { + name: `write${shortName}`, + paramType: typeName, + properties: [], + isAbstractType: classInfo.isAbstract || classInfo.isInterface || false + }; - if (classInfo.isAbstract || classInfo.isInterface) { - // Handle abstract types with instanceof chain - const implementations = classInfo.concreteImplementations || []; + if (classInfo.isAbstract || classInfo.isInterface) { + // Handle abstract types with instanceof chain + const implementations = classInfo.concreteImplementations || []; - // Filter out excluded types from implementations - const filteredImplementations = implementations.filter(impl => { - return !exclusions.types.has(impl); - }); + // Filter out excluded types from implementations + const filteredImplementations = implementations.filter(impl => { + return !exclusions.types.has(impl); + }); - if (filteredImplementations.length > 0) { - writeMethod.subtypeChecks = filteredImplementations.map(impl => { - const implShortName = impl.split('.').pop()!; - return { - typeName: impl, - writeMethodCall: `write${implShortName}` - }; - }); - } - } else { - // Handle concrete types - convert properties - const properties = typeProperties.get(typeName) || []; + if (filteredImplementations.length > 0) { + writeMethod.subtypeChecks = filteredImplementations.map(impl => { + const implShortName = impl.split('.').pop()!; + return { + typeName: impl, + writeMethodCall: `write${implShortName}` + }; + }); + } + } else { + // Handle concrete types - convert properties + const properties = typeProperties.get(typeName) || []; - for (const prop of properties) { - if (prop.excluded) { - continue; // Skip excluded properties - } + for (const prop of properties) { + if (prop.excluded) { + continue; // Skip excluded properties + } - const propName = prop.isGetter ? - prop.name.replace('get', '').replace('()', '').charAt(0).toLowerCase() + - prop.name.replace('get', '').replace('()', '').slice(1) : - prop.name; + const propName = prop.isGetter ? + prop.name.replace('get', '').replace('()', '').charAt(0).toLowerCase() + + prop.name.replace('get', '').replace('()', '').slice(1) : + prop.name; - // Handle getter vs field access - let getter: string; - if (prop.isGetter) { - // It's a method call - ensure it has parentheses - getter = prop.name.includes('()') ? prop.name : `${prop.name}()`; - } else { - // It's a field access - use the field name directly - getter = prop.name; - } + // Handle getter vs field access + let getter: string; + if (prop.isGetter) { + // It's a method call - ensure it has parentheses + getter = prop.name.includes('()') ? prop.name : `${prop.name}()`; + } else { + // It's a field access - use the field name directly + getter = prop.name; + } - // Analyze the property type to determine the correct Property variant - const irProperty = analyzePropertyWithDetails(prop, propName, getter, classMap); - writeMethod.properties.push(irProperty); - } - } + // Analyze the property type to determine the correct Property variant + const irProperty = analyzePropertyWithDetails(prop, propName, getter, classMap); + writeMethod.properties.push(irProperty); + } + } - writeMethods.push(writeMethod); - } + writeMethods.push(writeMethod); + } - return { - publicMethods, - writeMethods, - enumMappings - }; + return { + publicMethods, + writeMethods, + enumMappings + }; } -function analyzePropertyWithDetails(prop: PropertyInfo, propName: string, getter: string, classMap: Map): Property { - // Handle null annotations - const isNullable = prop.type.includes('@Null'); - let propType = prop.type.replace(/@Null\s+/g, '').trim(); +function analyzePropertyWithDetails (prop: PropertyInfo, propName: string, getter: string, classMap: Map): Property { + // Handle null annotations + const isNullable = prop.type.includes('@Null'); + let propType = prop.type.replace(/@Null\s+/g, '').trim(); - // Primitive types - if (['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(propType)) { - return { - kind: "primitive", - name: propName, - getter, - valueType: propType, - isNullable - }; - } + // Primitive types + if (['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(propType)) { + return { + kind: "primitive", + name: propName, + getter, + valueType: propType, + isNullable + }; + } - // Check if it's an enum - let classInfo = classMap.get(propType); - if (!classInfo && !propType.includes('.')) { - // Try to find by short name - for (const [fullName, info] of classMap) { - if (fullName.split('.').pop() === propType) { - classInfo = info; - propType = fullName; // Use full name - break; - } - } - } + // Check if it's an enum + let classInfo = classMap.get(propType); + if (!classInfo && !propType.includes('.')) { + // Try to find by short name + for (const [fullName, info] of classMap) { + if (fullName.split('.').pop() === propType) { + classInfo = info; + propType = fullName; // Use full name + break; + } + } + } - if (classInfo?.isEnum) { - return { - kind: "enum", - name: propName, - getter, - enumName: propType.split('.').pop()!, - isNullable - }; - } + if (classInfo?.isEnum) { + return { + kind: "enum", + name: propName, + getter, + enumName: propType.split('.').pop()!, + isNullable + }; + } - // Arrays - if (propType.startsWith('Array<')) { - const innerType = propType.match(/Array<(.+?)>/)![1].trim(); - const elementKind = ['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(innerType) ? "primitive" : "object"; + // Arrays + if (propType.startsWith('Array<')) { + const innerType = propType.match(/Array<(.+?)>/)![1].trim(); + const elementKind = ['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(innerType) ? "primitive" : "object"; - return { - kind: "array", - name: propName, - getter, - elementType: innerType, - elementKind, - writeMethodCall: elementKind === "object" ? `write${innerType.split('.').pop()}` : undefined, - isNullable - }; - } + return { + kind: "array", + name: propName, + getter, + elementType: innerType, + elementKind, + writeMethodCall: elementKind === "object" ? `write${innerType.split('.').pop()}` : undefined, + isNullable + }; + } - // Handle nested arrays (like float[][]) - if (propType.endsWith('[]')) { - const elemType = propType.slice(0, -2); - if (elemType.endsWith('[]')) { - const nestedType = elemType.slice(0, -2); - return { - kind: "nestedArray", - name: propName, - getter, - elementType: nestedType, - isNullable - }; - } else { - const elementKind = ['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(elemType) ? "primitive" : "object"; - return { - kind: "array", - name: propName, - getter, - elementType: elemType, - elementKind, - writeMethodCall: elementKind === "object" ? `write${elemType.split('.').pop()}` : undefined, - isNullable - }; - } - } + // Handle nested arrays (like float[][]) + if (propType.endsWith('[]')) { + const elemType = propType.slice(0, -2); + if (elemType.endsWith('[]')) { + const nestedType = elemType.slice(0, -2); + return { + kind: "nestedArray", + name: propName, + getter, + elementType: nestedType, + isNullable + }; + } else { + const elementKind = ['String', 'int', 'float', 'boolean', 'short', 'byte', 'double', 'long'].includes(elemType) ? "primitive" : "object"; + return { + kind: "array", + name: propName, + getter, + elementType: elemType, + elementKind, + writeMethodCall: elementKind === "object" ? `write${elemType.split('.').pop()}` : undefined, + isNullable + }; + } + } - // Special libGDX types that get custom handling - if (['Color', 'TextureRegion', 'IntArray', 'FloatArray'].includes(propType)) { - return { - kind: "object", - name: propName, - getter, - valueType: propType, - writeMethodCall: `write${propType}`, - isNullable - }; - } + // Special libGDX types that get custom handling + if (['Color', 'TextureRegion', 'IntArray', 'FloatArray'].includes(propType)) { + return { + kind: "object", + name: propName, + getter, + valueType: propType, + writeMethodCall: `write${propType}`, + isNullable + }; + } - // Object types - const shortType = propType.split('.').pop()!; - return { - kind: "object", - name: propName, - getter, - valueType: propType, - writeMethodCall: `write${shortType}`, - isNullable - }; + // Object types + const shortType = propType.split('.').pop()!; + return { + kind: "object", + name: propName, + getter, + valueType: propType, + writeMethodCall: `write${shortType}`, + isNullable + }; } -async function main() { - try { - // Read analysis result - const analysisFile = path.resolve(__dirname, '../output/analysis-result.json'); - if (!fs.existsSync(analysisFile)) { - console.error('Analysis result not found. Run analyze-java-api.ts first.'); - process.exit(1); - } +async function main () { + try { + // Read analysis result + const analysisFile = path.resolve(__dirname, '../output/analysis-result.json'); + if (!fs.existsSync(analysisFile)) { + console.error('Analysis result not found. Run analyze-java-api.ts first.'); + process.exit(1); + } - const analysisData: SerializedAnalysisResult = JSON.parse(fs.readFileSync(analysisFile, 'utf8')); + const analysisData: SerializedAnalysisResult = JSON.parse(fs.readFileSync(analysisFile, 'utf8')); - // Generate IR - const ir = generateSerializerIR(analysisData); + // Generate IR + const ir = generateSerializerIR(analysisData); - // Write the IR file - const irFile = path.resolve(__dirname, '../output/serializer-ir.json'); - fs.mkdirSync(path.dirname(irFile), { recursive: true }); - fs.writeFileSync(irFile, JSON.stringify(ir, null, 2)); + // Write the IR file + const irFile = path.resolve(__dirname, '../output/serializer-ir.json'); + fs.mkdirSync(path.dirname(irFile), { recursive: true }); + fs.writeFileSync(irFile, JSON.stringify(ir, null, 2)); - console.log(`Generated serializer IR: ${irFile}`); - console.log(`- ${ir.publicMethods.length} public methods`); - console.log(`- ${ir.writeMethods.length} write methods`); - console.log(`- ${Object.keys(ir.enumMappings).length} enum mappings`); + console.log(`Generated serializer IR: ${irFile}`); + console.log(`- ${ir.publicMethods.length} public methods`); + console.log(`- ${ir.writeMethods.length} write methods`); + console.log(`- ${Object.keys(ir.enumMappings).length} enum mappings`); - } catch (error: any) { - console.error('Error:', error.message); - console.error('Stack:', error.stack); - process.exit(1); - } + } catch (error: any) { + console.error('Error:', error.message); + console.error('Stack:', error.stack); + process.exit(1); + } } // Allow running as a script or importing the function if (import.meta.url === `file://${process.argv[1]}`) { - main(); + main(); } export { generateSerializerIR, type SerializerIR, type PublicMethod, type WriteMethod, type Property }; \ No newline at end of file diff --git a/tests/src/types.ts b/tests/src/types.ts index b34cdcb10..4004224f2 100644 --- a/tests/src/types.ts +++ b/tests/src/types.ts @@ -2,44 +2,44 @@ import { Supertype } from '@mariozechner/lsp-cli'; // Shared types for the Spine serializer generator export interface ClassInfo { - className: string; - superTypes: string[]; // Just the names for backward compatibility - superTypeDetails?: Supertype[]; // Full details with type arguments - getters: GetterInfo[]; - fields: FieldInfo[]; - file: string; - isAbstract: boolean; - isInterface: boolean; - isEnum: boolean; - typeParameters?: string[]; // The class's own type parameters - enumValues?: string[]; // For enums - concreteImplementations?: string[]; // For abstract classes/interfaces - only leaf concrete types - allImplementations?: string[]; // For abstract classes/interfaces - includes intermediate abstract types + className: string; + superTypes: string[]; // Just the names for backward compatibility + superTypeDetails?: Supertype[]; // Full details with type arguments + getters: GetterInfo[]; + fields: FieldInfo[]; + file: string; + isAbstract: boolean; + isInterface: boolean; + isEnum: boolean; + typeParameters?: string[]; // The class's own type parameters + enumValues?: string[]; // For enums + concreteImplementations?: string[]; // For abstract classes/interfaces - only leaf concrete types + allImplementations?: string[]; // For abstract classes/interfaces - includes intermediate abstract types } export interface GetterInfo { - methodName: string; - returnType: string; + methodName: string; + returnType: string; } export interface FieldInfo { - fieldName: string; - fieldType: string; - isFinal: boolean; + fieldName: string; + fieldType: string; + isFinal: boolean; } export interface PropertyInfo { - name: string; - type: string; - isGetter: boolean; - inheritedFrom?: string; // Which class this property was inherited from - excluded: boolean; // Whether this property should be excluded from serialization + name: string; + type: string; + isGetter: boolean; + inheritedFrom?: string; // Which class this property was inherited from + excluded: boolean; // Whether this property should be excluded from serialization } export interface AnalysisResult { - classMap: Map; - accessibleTypes: Set; - abstractTypes: Map; // abstract type -> concrete implementations - allTypesToGenerate: Set; // all types that need write methods - typeProperties: Map; // type -> all properties (including inherited) + classMap: Map; + accessibleTypes: Set; + abstractTypes: Map; // abstract type -> concrete implementations + allTypesToGenerate: Set; // all types that need write methods + typeProperties: Map; // type -> all properties (including inherited) } \ No newline at end of file