Fix exclusions.ts: discriminated union type, correct const method handling

This commit is contained in:
Mario Zechner 2025-07-08 23:38:43 +02:00
parent 4fd23d3abe
commit 0a33247f44
4 changed files with 51 additions and 28 deletions

View File

@ -1,17 +1,40 @@
import * as fs from 'fs'; import * as fs from 'fs';
import { Exclusion } from './types'; import { Exclusion } from './types';
/**
* Loads exclusions from a text file.
*
* File format:
* - Lines starting with # are comments
* - Empty lines are ignored
* - Type exclusions: "type: TypeName"
* - Method exclusions: "method: TypeName::methodName [const]"
*
* Examples:
* ```
* # Exclude entire types
* type: SkeletonClipping
* type: Triangulator
*
* # Exclude specific methods
* method: AnimationState::setListener
* method: AnimationState::addListener
*
* # Exclude only const version of a method
* method: BoneData::getSetupPose const
* ```
*/
export function loadExclusions(filePath: string): Exclusion[] { export function loadExclusions(filePath: string): Exclusion[] {
const content = fs.readFileSync(filePath, 'utf8'); const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n'); const lines = content.split('\n');
const exclusions: Exclusion[] = []; const exclusions: Exclusion[] = [];
for (const line of lines) { for (const line of lines) {
const trimmed = line.trim(); const trimmed = line.trim();
// Skip empty lines and comments // Skip empty lines and comments
if (!trimmed || trimmed.startsWith('#')) continue; if (!trimmed || trimmed.startsWith('#')) continue;
// Parse type exclusion // Parse type exclusion
const typeMatch = trimmed.match(/^type:\s*(.+)$/); const typeMatch = trimmed.match(/^type:\s*(.+)$/);
if (typeMatch) { if (typeMatch) {
@ -21,27 +44,24 @@ export function loadExclusions(filePath: string): Exclusion[] {
}); });
continue; continue;
} }
// Parse method exclusion with optional const specification // Parse method exclusion with optional const specification
// Format: method: Type::method or method: Type::method const // Format: method: Type::method or method: Type::method const
const methodMatch = trimmed.match(/^method:\s*(.+?)::(.+?)(\s+const)?$/); const methodMatch = trimmed.match(/^method:\s*(.+?)::(.+?)(\s+const)?$/);
if (methodMatch) { if (methodMatch) {
const methodName = methodMatch[2].trim(); const methodName = methodMatch[2].trim();
const isConst = !!methodMatch[3]; const isConst = !!methodMatch[3];
exclusions.push({ exclusions.push({
kind: 'method', kind: 'method',
typeName: methodMatch[1].trim(), typeName: methodMatch[1].trim(),
methodName: methodName, methodName: methodName,
isConst: isConst isConst: isConst || undefined
}); });
if (isConst) {
console.log(`Parsed const exclusion: ${methodMatch[1].trim()}::${methodName} const`);
}
} }
} }
return exclusions; return exclusions;
} }
@ -49,13 +69,12 @@ export function isTypeExcluded(typeName: string, exclusions: Exclusion[]): boole
return exclusions.some(ex => ex.kind === 'type' && ex.typeName === typeName); return exclusions.some(ex => ex.kind === 'type' && ex.typeName === typeName);
} }
export function isMethodExcluded(typeName: string, methodName: string, exclusions: Exclusion[], returnType?: string): boolean { export function isMethodExcluded(typeName: string, methodName: string, exclusions: Exclusion[], method?: { isConst?: boolean }): boolean {
// Determine if method is const by looking at return type const isConstMethod = method?.isConst || false;
const isConstMethod = returnType ? returnType.includes('const ') && returnType.includes('&') : false;
const result = exclusions.some(ex => { const result = exclusions.some(ex => {
if (ex.kind === 'method' && if (ex.kind === 'method' &&
ex.typeName === typeName && ex.typeName === typeName &&
ex.methodName === methodName) { ex.methodName === methodName) {
// If exclusion doesn't specify const, it matches all // If exclusion doesn't specify const, it matches all
if (ex.isConst === undefined) return true; if (ex.isConst === undefined) return true;
@ -64,6 +83,6 @@ export function isMethodExcluded(typeName: string, methodName: string, exclusion
} }
return false; return false;
}); });
return result; return result;
} }

View File

@ -14,7 +14,7 @@ export class MethodGenerator {
const methods = type.members.filter(m => const methods = type.members.filter(m =>
m.kind === 'method' && m.kind === 'method' &&
!m.isStatic && !m.isStatic &&
!isMethodExcluded(type.name, m.name, this.exclusions, m.returnType) !isMethodExcluded(type.name, m.name, this.exclusions, m)
); );
// Check for const/non-const method pairs // Check for const/non-const method pairs

View File

@ -40,10 +40,9 @@ function checkConstNonConstConflicts(classes: Type[], exclusions: Exclusion[]):
const methodGroups = new Map<string, Member[]>(); const methodGroups = new Map<string, Member[]>();
for (const method of allMethods) { for (const method of allMethods) {
// Skip if this specific const/non-const version is excluded // Skip if this specific const/non-const version is excluded
if (isMethodExcluded(type.name, method.name, exclusions, method.returnType)) { if (isMethodExcluded(type.name, method.name, exclusions, method)) {
if (method.name === 'getSetupPose') { if (method.name === 'getSetupPose') {
const isConstMethod = method.returnType && method.returnType.includes('const ') && method.returnType.includes('&'); console.log(`Skipping excluded method: ${type.name}::${method.name}${method.isConst ? ' const' : ''}`);
console.log(`Skipping excluded method: ${type.name}::${method.name}${isConstMethod ? ' const' : ''}`);
} }
continue; continue;
} }

View File

@ -41,12 +41,17 @@ export interface SpineTypes {
[header: string]: Type[]; [header: string]: Type[];
} }
export interface Exclusion { export type Exclusion =
kind: 'type' | 'method'; | {
typeName: string; kind: 'type';
methodName?: string; typeName: string;
isConst?: boolean; // For excluding specifically const or non-const versions }
} | {
kind: 'method';
typeName: string;
methodName: string;
isConst?: boolean; // Whether the method is const (e.g., void foo() const), NOT whether return type is const
};
export function toSnakeCase(name: string): string { export function toSnakeCase(name: string): string {
// Handle acronyms and consecutive capitals // Handle acronyms and consecutive capitals