From 4d45abbcf385a4c8f7c6feebf4cd128c5dd3031d Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Tue, 15 Jul 2025 16:22:45 +0200 Subject: [PATCH] [tests] Fix up output paths, lsp-cli invocation --- tests/generate-serializers.sh | 9 ++++--- tests/package-lock.json | 10 ++++---- tests/package.json | 2 +- tests/src/analyze-java-api.ts | 25 +++++++++++-------- tests/src/generate-cpp-serializer.ts | 4 +-- tests/src/generate-java-serializer.ts | 4 +-- tests/src/generate-serializer-ir.ts | 36 +++++++++++++-------------- tests/src/types.ts | 2 +- todos/todos.md | 1 - 9 files changed, 48 insertions(+), 45 deletions(-) diff --git a/tests/generate-serializers.sh b/tests/generate-serializers.sh index ab69fd522..ee9bf7c63 100755 --- a/tests/generate-serializers.sh +++ b/tests/generate-serializers.sh @@ -1,21 +1,22 @@ #!/bin/bash set -e +set -o pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" pushd "$SCRIPT_DIR" > /dev/null echo "Analyzing Java API..." -npx tsx src/analyze-java-api.ts +npx tsx src/analyze-java-api.ts || { echo "Failed to analyze Java API"; exit 1; } echo "Generating serializer IR..." -npx tsx src/generate-serializer-ir.ts +npx tsx src/generate-serializer-ir.ts || { echo "Failed to generate IR"; exit 1; } echo "Generating Java SkeletonSerializer..." -npx tsx src/generate-java-serializer.ts +npx tsx src/generate-java-serializer.ts || { echo "Failed to generate Java serializer"; exit 1; } echo "Generating C++ SkeletonSerializer..." -npx tsx src/generate-cpp-serializer.ts +npx tsx src/generate-cpp-serializer.ts || { echo "Failed to generate C++ serializer"; exit 1; } echo "Done." popd > /dev/null \ No newline at end of file diff --git a/tests/package-lock.json b/tests/package-lock.json index 3e467242c..c5ef1c939 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "spine-tests", "dependencies": { - "@mariozechner/lsp-cli": "^0.1.1" + "@mariozechner/lsp-cli": "^0.1.3" }, "devDependencies": { "@types/node": "^20.0.0", @@ -456,9 +456,9 @@ } }, "node_modules/@mariozechner/lsp-cli": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@mariozechner/lsp-cli/-/lsp-cli-0.1.1.tgz", - "integrity": "sha512-/5HF/PoYhQKFMhXLQiH1ZHTBJfs7rB8xcXa4YXSq6aTR5G6tWOrE6fwFbK7pwtkry6ZFeCTE2HI4BRWv5La9Qg==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@mariozechner/lsp-cli/-/lsp-cli-0.1.3.tgz", + "integrity": "sha512-5kyYiI4k7bf48ineGLdHluHx9ZjYLIEknsgi3vpxBjUCf6y6pDmXsqDezlvZsWkittz9PMUJzCJD9cze6/d/Ug==", "dependencies": { "chalk": "^5.4.1", "commander": "^11.0.0", @@ -468,7 +468,7 @@ "vscode-languageserver-protocol": "^3.17.0" }, "bin": { - "lsp-cli": "dist/index.js" + "lsp-cli": "dist/cli.js" } }, "node_modules/@types/node": { diff --git a/tests/package.json b/tests/package.json index ed0e927c6..a22d15c52 100644 --- a/tests/package.json +++ b/tests/package.json @@ -10,6 +10,6 @@ "tsx": "^4.0.0" }, "dependencies": { - "@mariozechner/lsp-cli": "^0.1.1" + "@mariozechner/lsp-cli": "^0.1.3" } } diff --git a/tests/src/analyze-java-api.ts b/tests/src/analyze-java-api.ts index 2495e7326..429a235af 100755 --- a/tests/src/analyze-java-api.ts +++ b/tests/src/analyze-java-api.ts @@ -4,12 +4,13 @@ import { execSync } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; -import type { Symbol, LspOutput, ClassInfo, PropertyInfo, AnalysisResult } from './types'; +import type { ClassInfo, PropertyInfo, AnalysisResult } from './types'; +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'); + const outputDir = path.resolve(__dirname, '../output'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } @@ -18,7 +19,7 @@ function ensureOutputDir(): string { function generateLspData(outputDir: string): string { const outputFile = path.join(outputDir, 'spine-libgdx-symbols.json'); - const projectDir = '/Users/badlogic/workspaces/spine-runtimes/spine-libgdx'; + const projectDir = path.resolve(__dirname, '../../spine-libgdx'); const srcDir = path.join(projectDir, 'spine-libgdx/src'); // Check if we need to regenerate @@ -42,9 +43,8 @@ function generateLspData(outputDir: string): string { if (needsRegeneration) { console.error('Generating LSP data for spine-libgdx...'); try { - execSync(`npx lsp-cli "${projectDir}" java "${outputFile}"`, { - encoding: 'utf8', - stdio: ['ignore', 'ignore', 'pipe'] // Hide stdout but show stderr + 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) { @@ -58,11 +58,11 @@ function generateLspData(outputDir: string): string { return outputFile; } -function analyzeClasses(symbols: Symbol[]): Map { +function analyzeClasses(symbols: SymbolInfo[]): Map { const classMap = new Map(); - const srcPath = '/Users/badlogic/workspaces/spine-runtimes/spine-libgdx/spine-libgdx/src/'; + const srcPath = path.resolve(__dirname, '../../spine-libgdx/spine-libgdx/src/'); - function processSymbol(symbol: Symbol, parentName?: string) { + 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 @@ -256,6 +256,9 @@ function findAccessibleTypes( 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 @@ -279,7 +282,7 @@ function findAccessibleTypes( } function loadExclusions(): { types: Set, methods: Map>, fields: Map> } { - const exclusionsPath = path.resolve(__dirname, 'java-exclusions.txt'); + const exclusionsPath = path.resolve(__dirname, '../java-exclusions.txt'); const types = new Set(); const methods = new Map>(); const fields = new Map>(); @@ -688,7 +691,7 @@ async function main() { // Read and parse the JSON const jsonContent = fs.readFileSync(jsonFile, 'utf8'); - const lspData: LspOutput = JSON.parse(jsonContent); + const lspData: LspCliResult = JSON.parse(jsonContent); console.error(`Analyzing ${lspData.symbols.length} symbols...`); diff --git a/tests/src/generate-cpp-serializer.ts b/tests/src/generate-cpp-serializer.ts index 32318de43..c9daa7bd2 100644 --- a/tests/src/generate-cpp-serializer.ts +++ b/tests/src/generate-cpp-serializer.ts @@ -420,7 +420,7 @@ function generateCppFromIR(ir: SerializerIR): string { async function main() { try { // Read the IR file - const irFile = path.resolve(__dirname, '../../output/serializer-ir.json'); + 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); @@ -434,7 +434,7 @@ async function main() { // Write the C++ file const cppFile = path.resolve( __dirname, - '../../../spine-cpp/tests/SkeletonSerializer.h' + '../../spine-cpp/tests/SkeletonSerializer.h' ); fs.mkdirSync(path.dirname(cppFile), { recursive: true }); diff --git a/tests/src/generate-java-serializer.ts b/tests/src/generate-java-serializer.ts index bb6c67754..5b9ae67a7 100644 --- a/tests/src/generate-java-serializer.ts +++ b/tests/src/generate-java-serializer.ts @@ -290,7 +290,7 @@ function generateJavaFromIR(ir: SerializerIR): string { async function main() { try { // Read the IR file - const irFile = path.resolve(__dirname, '../../output/serializer-ir.json'); + 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); @@ -304,7 +304,7 @@ async function main() { // Write the Java file const javaFile = path.resolve( __dirname, - '../../../spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java' + '../../spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/utils/SkeletonSerializer.java' ); fs.mkdirSync(path.dirname(javaFile), { recursive: true }); diff --git a/tests/src/generate-serializer-ir.ts b/tests/src/generate-serializer-ir.ts index b114b1b39..52ed8134e 100644 --- a/tests/src/generate-serializer-ir.ts +++ b/tests/src/generate-serializer-ir.ts @@ -88,27 +88,27 @@ interface SerializedAnalysisResult { } function loadExclusions(): { types: Set, methods: Map>, fields: Map> } { - const exclusionsPath = path.resolve(__dirname, 'java-exclusions.txt'); + 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 }; } - + 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; - + const parts = trimmed.split(/\s+/); if (parts.length < 2) continue; - + const [type, className, property] = parts; - + switch (type) { case 'type': types.add(className); @@ -131,7 +131,7 @@ function loadExclusions(): { types: Set, methods: Map) 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, @@ -257,14 +257,14 @@ function generateSerializerIR(analysisData: SerializedAnalysisResult): Serialize 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; } - + enumMappings[shortName] = valueMap; } } @@ -356,7 +356,7 @@ function generateSerializerIR(analysisData: SerializedAnalysisResult): Serialize 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; @@ -376,12 +376,12 @@ function generateSerializerIR(analysisData: SerializedAnalysisResult): Serialize 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); }); - + if (filteredImplementations.length > 0) { writeMethod.subtypeChecks = filteredImplementations.map(impl => { const implShortName = impl.split('.').pop()!; @@ -399,7 +399,7 @@ function generateSerializerIR(analysisData: SerializedAnalysisResult): Serialize 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) : @@ -474,7 +474,7 @@ function analyzePropertyWithDetails(prop: PropertyInfo, propName: string, getter 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, @@ -539,7 +539,7 @@ function analyzePropertyWithDetails(prop: PropertyInfo, propName: string, getter async function main() { try { // Read analysis result - const analysisFile = path.resolve(__dirname, '../../output/analysis-result.json'); + 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); @@ -551,7 +551,7 @@ async function main() { const ir = generateSerializerIR(analysisData); // Write the IR file - const irFile = path.resolve(__dirname, '../../output/serializer-ir.json'); + const irFile = path.resolve(__dirname, '../output/serializer-ir.json'); fs.mkdirSync(path.dirname(irFile), { recursive: true }); fs.writeFileSync(irFile, JSON.stringify(ir, null, 2)); diff --git a/tests/src/types.ts b/tests/src/types.ts index 3b7e71338..b34cdcb10 100644 --- a/tests/src/types.ts +++ b/tests/src/types.ts @@ -1,4 +1,4 @@ -import { Symbol, Supertype, LspOutput } from '@mariozechner/lsp-cli'; +import { Supertype } from '@mariozechner/lsp-cli'; // Shared types for the Spine serializer generator export interface ClassInfo { diff --git a/todos/todos.md b/todos/todos.md index 25ae9d2e5..d86d77e6e 100644 --- a/todos/todos.md +++ b/todos/todos.md @@ -1,4 +1,3 @@ -- lsp-cli should export its types, so we can pull them in as a dependency instead of redefining them ourselves in types.ts - clean up logging in spine-c/codegen, use chalk to do colored warnings/errors and make logging look very nice and informative (no emojis) - spine-c/codegen type extractor should also report typedefs like typedef long long PropertyId; so primitive type to some name, and we need to handle that in the codegen - Generate language bindings in spine-c/codegen