From 0adb150c26ed710c6e9bad2c6eec22659a7bf3ea Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 28 Mar 2026 18:32:46 +0100 Subject: [PATCH 1/3] feat: enhance getTypeVariant to support multiple types and return detailed variant results --- src/extraction/getTypeVariant.ts | 114 ++++++++++++++++++++----------- test/getTypeVariant.test.ts | 66 ++++++++++-------- 2 files changed, 109 insertions(+), 71 deletions(-) diff --git a/src/extraction/getTypeVariant.ts b/src/extraction/getTypeVariant.ts index ccd2dca..45bc996 100644 --- a/src/extraction/getTypeVariant.ts +++ b/src/extraction/getTypeVariant.ts @@ -10,53 +10,85 @@ export enum DataTypeVariant { NODE } +export interface TypeVariantResult { + identifier: string; + variant: DataTypeVariant; +} + /** - * Determines the variant of a given TypeScript type string using the TS compiler. + * Determines the variant of a given TypeScript types string or DataType(s) using the TS compiler. + * - If types is a string: returns one result with the string as identifier + * - If types is a DataType: returns one result with DataType.identifier + * - If types is a DataType[]: returns results for each DataType with their identifiers */ export const getTypeVariant = ( - type?: string, + types?: string | DataType | DataType[], dataTypes?: DataType[] -): DataTypeVariant => { +): TypeVariantResult[] => { const typeDefs = getSharedTypeDeclarations(dataTypes); - // We declare a variable with the type to probe it - const sourceCode = ` - ${typeDefs} - type TargetType = ${type}; - const val: TargetType = {} as any; - `; - - const fileName = `index.ts`; - const host = createCompilerHost(fileName, sourceCode); - const sourceFile = host.getSourceFile(fileName)!; - const program = host.languageService.getProgram()!; - const checker = program.getTypeChecker(); - - let discoveredVariant: DataTypeVariant = DataTypeVariant.TYPE; - - const visit = (node: ts.Node) => { - if (ts.isVariableDeclaration(node) && node.name.getText() === "val") { - const type = checker.getTypeAtLocation(node); - - if (type.getCallSignatures().length > 0) { - discoveredVariant = DataTypeVariant.NODE; - } else if (checker.isArrayType(type)) { - discoveredVariant = DataTypeVariant.ARRAY; - } else if ( - type.isStringLiteral() || - type.isNumberLiteral() || - (type.getFlags() & (ts.TypeFlags.String | ts.TypeFlags.Number | ts.TypeFlags.Boolean | ts.TypeFlags.EnumLiteral | ts.TypeFlags.BigInt | ts.TypeFlags.ESSymbol)) !== 0 - ) { - discoveredVariant = DataTypeVariant.PRIMITIVE; - } else if (type.isClassOrInterface() || (type.getFlags() & ts.TypeFlags.Object) !== 0) { - discoveredVariant = DataTypeVariant.OBJECT; - } else { - discoveredVariant = DataTypeVariant.TYPE; + // Determine what we're analyzing + const isStringType = typeof types === "string"; + const isArrayType = Array.isArray(types); + const typesToAnalyze = isArrayType ? (types as DataType[]) : isStringType ? [{ identifier: types, type: types }] : [types]; + const identifiers = isArrayType + ? (types as DataType[]).map(t => t.identifier) + : isStringType + ? [types as string] + : [(types as DataType).identifier]; + + const results: TypeVariantResult[] = []; + + for (let i = 0; i < typesToAnalyze.length; i++) { + const currentType = typesToAnalyze[i]; + const currentIdentifier = identifiers[i]; + const typeString = isStringType ? (types as string) : (currentType as DataType).type; + + // We declare a variable with the types to probe it + const sourceCode = ` + ${typeDefs} + ${typeString ? `type TargetType = ${typeString};` : ""} + const val: TargetType = {} as any; + `; + + const fileName = `index.ts`; + const host = createCompilerHost(fileName, sourceCode); + const sourceFile = host.getSourceFile(fileName)!; + const program = host.languageService.getProgram()!; + const checker = program.getTypeChecker(); + + let discoveredVariant: DataTypeVariant = DataTypeVariant.TYPE; + + const visit = (node: ts.Node) => { + if (ts.isVariableDeclaration(node) && node.name.getText() === "val") { + const type = checker.getTypeAtLocation(node); + + if (type.getCallSignatures().length > 0) { + discoveredVariant = DataTypeVariant.NODE; + } else if (checker.isArrayType(type)) { + discoveredVariant = DataTypeVariant.ARRAY; + } else if ( + type.isStringLiteral() || + type.isNumberLiteral() || + (type.getFlags() & (ts.TypeFlags.String | ts.TypeFlags.Number | ts.TypeFlags.Boolean | ts.TypeFlags.EnumLiteral | ts.TypeFlags.BigInt | ts.TypeFlags.ESSymbol)) !== 0 + ) { + discoveredVariant = DataTypeVariant.PRIMITIVE; + } else if (type.isClassOrInterface() || (type.getFlags() & ts.TypeFlags.Object) !== 0) { + discoveredVariant = DataTypeVariant.OBJECT; + } else { + discoveredVariant = DataTypeVariant.TYPE; + } } - } - ts.forEachChild(node, visit); - }; + ts.forEachChild(node, visit); + }; + + visit(sourceFile); + + results.push({ + identifier: currentIdentifier!, + variant: discoveredVariant + }); + } - visit(sourceFile); - return discoveredVariant; + return results; }; diff --git a/test/getTypeVariant.test.ts b/test/getTypeVariant.test.ts index 3cff28f..8783c04 100644 --- a/test/getTypeVariant.test.ts +++ b/test/getTypeVariant.test.ts @@ -4,58 +4,64 @@ import {DATA_TYPES, FUNCTION_SIGNATURES} from "./data"; import {getTypesFromNode} from "../src"; describe('getTypeVariant', () => { - it('sollte PRIMITIVE für einfache Typen zurückgeben', () => { - expect(getTypeVariant("string", DATA_TYPES)).toBe(DataTypeVariant.PRIMITIVE); - expect(getTypeVariant("number", DATA_TYPES)).toBe(DataTypeVariant.PRIMITIVE); - expect(getTypeVariant("boolean", DATA_TYPES)).toBe(DataTypeVariant.PRIMITIVE); + it('identifies native TypeScript primitives (string, number, boolean) as PRIMITIVE variant', () => { + expect(getTypeVariant("string", DATA_TYPES)[0].variant).toBe(DataTypeVariant.PRIMITIVE); + expect(getTypeVariant("number", DATA_TYPES)[0].variant).toBe(DataTypeVariant.PRIMITIVE); + expect(getTypeVariant("boolean", DATA_TYPES)[0].variant).toBe(DataTypeVariant.PRIMITIVE); }); - it('sollte ARRAY für Array-Typen zurückgeben', () => { - expect(getTypeVariant("string[]", DATA_TYPES)).toBe(DataTypeVariant.ARRAY); - expect(getTypeVariant("Array", DATA_TYPES)).toBe(DataTypeVariant.ARRAY); + it('recognizes both bracket notation and generic syntax for array types as ARRAY variant', () => { + expect(getTypeVariant("string[]", DATA_TYPES)[0].variant).toBe(DataTypeVariant.ARRAY); + expect(getTypeVariant("Array", DATA_TYPES)[0].variant).toBe(DataTypeVariant.ARRAY); }); - it('sollte OBJECT für Interfaces oder Objekte mit Properties zurückgeben', () => { - expect(getTypeVariant("{ name: string }", DATA_TYPES)).toBe(DataTypeVariant.OBJECT); - expect(getTypeVariant("{}", DATA_TYPES)).toBe(DataTypeVariant.OBJECT); - expect(getTypeVariant("OBJECT", DATA_TYPES)).toBe(DataTypeVariant.OBJECT); + it('classifies object literals and interface-like structures as OBJECT variant', () => { + expect(getTypeVariant("{ name: string }", DATA_TYPES)[0].variant).toBe(DataTypeVariant.OBJECT); + expect(getTypeVariant("{}", DATA_TYPES)[0].variant).toBe(DataTypeVariant.OBJECT); + expect(getTypeVariant("OBJECT", DATA_TYPES)[0].variant).toBe(DataTypeVariant.OBJECT); }); - it('sollte TYPE für einfache Type-Aliase oder void zurückgeben', () => { - expect(getTypeVariant("void", DATA_TYPES)).toBe(DataTypeVariant.TYPE); - expect(getTypeVariant("any", DATA_TYPES)).toBe(DataTypeVariant.TYPE); + it('marks special types like void and any as TYPE variant', () => { + expect(getTypeVariant("void", DATA_TYPES)[0].variant).toBe(DataTypeVariant.TYPE); + expect(getTypeVariant("any", DATA_TYPES)[0].variant).toBe(DataTypeVariant.TYPE); }); - it('sollte LIST (NUMBER) als ARRAY erkennen (wenn in DATA_TYPES definiert)', () => { - // In data.ts ist LIST als T[] definiert - expect(getTypeVariant("LIST", DATA_TYPES)).toBe(DataTypeVariant.ARRAY); - expect(getTypeVariant("LIST", DATA_TYPES)).toBe(DataTypeVariant.ARRAY); - expect(getTypeVariant("LIST", DATA_TYPES)).toBe(DataTypeVariant.ARRAY); + it('resolves generic LIST type aliases to ARRAY variant regardless of type parameter', () => { + // LIST is defined as T[] in data.ts, so all LIST variants should be arrays + expect(getTypeVariant("LIST", DATA_TYPES)[0].variant).toBe(DataTypeVariant.ARRAY); + expect(getTypeVariant("LIST", DATA_TYPES)[0].variant).toBe(DataTypeVariant.ARRAY); + expect(getTypeVariant("LIST", DATA_TYPES)[0].variant).toBe(DataTypeVariant.ARRAY); }); - it('sollte NODE für Funktionstypen wie CONSUMER zurückgeben', () => { - // In data.ts ist CONSUMER als (item:R) => void definiert - expect(getTypeVariant("CONSUMER", DATA_TYPES)).toBe(DataTypeVariant.NODE); + it('identifies CONSUMER function types with generic parameters as NODE variant', () => { + // CONSUMER represents a callback function (T) => void + expect(getTypeVariant("CONSUMER", DATA_TYPES)[0].variant).toBe(DataTypeVariant.NODE); }); - it('sollte NODE für Funktionstypen wie RUNNABLE zurückgeben', () => { - // In data.ts ist CONSUMER als (item:R) => void definiert - expect(getTypeVariant("RUNNABLE", DATA_TYPES)).toBe(DataTypeVariant.NODE); + it('identifies parameterless function types like RUNNABLE as NODE variant', () => { + // RUNNABLE represents () => void with no parameters + expect(getTypeVariant("RUNNABLE", DATA_TYPES)[0].variant).toBe(DataTypeVariant.NODE); }); - it('sollte NODE für Funktionstypen wie PREDICATE zurückgeben', () => { - // In data.ts ist CONSUMER als (item:R) => void definiert - expect(getTypeVariant("PREDICATE", DATA_TYPES)).toBe(DataTypeVariant.NODE); + it('identifies PREDICATE function types with generic parameters as NODE variant', () => { + // PREDICATE represents a boolean-returning function (T) => boolean + expect(getTypeVariant("PREDICATE", DATA_TYPES)[0].variant).toBe(DataTypeVariant.NODE); }); + it('correctly identifies type variants when retrieved directly from DATA_TYPES registry', () => { + // Verify that types stored in DATA_TYPES are properly classified + expect(getTypeVariant(DATA_TYPES.find(dt => dt.identifier === "LIST"), DATA_TYPES)[0].variant).toBe(DataTypeVariant.ARRAY); + expect(getTypeVariant(DATA_TYPES.find(dt => dt.identifier === "HTTP_METHOD"), DATA_TYPES)[0].variant).toBe(DataTypeVariant.TYPE); + }); - it("Check if", () => { + it('recognizes callback parameter types in real function signatures as NODE variant', () => { + // When extracting types from std::list::for_each, the second parameter (consumer) should be NODE const types = getTypesFromNode({ functionDefinition: { identifier: "std::list::for_each" } }, FUNCTION_SIGNATURES, DATA_TYPES) - expect(getTypeVariant(types.parameters[1], DATA_TYPES)).toBe(DataTypeVariant.NODE); + expect(getTypeVariant(types.parameters[1], DATA_TYPES)[0].variant).toBe(DataTypeVariant.NODE); }); }); From e1dd64c9d33ac642a5b00b88bc000d046e1a47f1 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 28 Mar 2026 18:35:00 +0100 Subject: [PATCH 2/3] feat: refine isFunctionType check to access variant directly for improved type validation --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 96d7196..611a64f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -178,7 +178,7 @@ export function generateFlowSourceCode( if (!isForInference) { const expectedType = nodeTypes.parameters[index]; - const isFunctionType = expectedType ? getTypeVariant(expectedType, dataTypes) === DataTypeVariant.NODE : false; + const isFunctionType = expectedType ? getTypeVariant(expectedType, dataTypes)[0].variant === DataTypeVariant.NODE : false; if (isFunctionType) { const lambdaArgName = `p_${sanitizeId(nodeId)}_${index}`; From 126a9c3e3c815a8809f155c6d42c754ead920f2e Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 28 Mar 2026 18:36:19 +0100 Subject: [PATCH 3/3] feat: update getNodeSuggestions to directly access variant for improved type handling --- src/suggestion/getNodeSuggestions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/suggestion/getNodeSuggestions.ts b/src/suggestion/getNodeSuggestions.ts index 1211f92..40cffd0 100644 --- a/src/suggestion/getNodeSuggestions.ts +++ b/src/suggestion/getNodeSuggestions.ts @@ -14,7 +14,7 @@ export const getNodeSuggestions = ( let functionToSuggest = functions - const typeVariant = type ? getTypeVariant(type, dataTypes) : null; + const typeVariant = type ? getTypeVariant(type, dataTypes)[0].variant : null; if (type && functions && typeVariant !== DataTypeVariant.NODE) { function getGenericsCount(input: string): number {