From 1bc044910dbb00052c10575154654528f14efb34 Mon Sep 17 00:00:00 2001 From: rockerBOO Date: Mon, 21 Jul 2025 18:33:00 -0400 Subject: [PATCH 1/7] feat: add initial ReturnType parser --- factory/parser.ts | 2 + src/NodeParser/ReturnTypeNodeParser.ts | 104 +++++++++++++++++++ test/valid-data-type.test.ts | 5 + test/valid-data/type-return-type/main.ts | 5 + test/valid-data/type-return-type/schema.json | 18 ++++ 5 files changed, 134 insertions(+) create mode 100644 src/NodeParser/ReturnTypeNodeParser.ts create mode 100644 test/valid-data/type-return-type/main.ts create mode 100644 test/valid-data/type-return-type/schema.json diff --git a/factory/parser.ts b/factory/parser.ts index 6bc089000..7bdc5e09d 100644 --- a/factory/parser.ts +++ b/factory/parser.ts @@ -61,6 +61,7 @@ import { SatisfiesNodeParser } from "../src/NodeParser/SatisfiesNodeParser.js"; import { PromiseNodeParser } from "../src/NodeParser/PromiseNodeParser.js"; import { SpreadElementNodeParser } from "../src/NodeParser/SpreadElementNodeParser.js"; import { IdentifierNodeParser } from "../src/NodeParser/IdentifierNodeParser.js"; +import { ReturnTypeNodeParser } from "../src/NodeParser/ReturnTypeNodeParser.js"; export type ParserAugmentor = (parser: MutableParser) => void; @@ -130,6 +131,7 @@ export function createParser(program: ts.Program, config: CompletedConfig, augme .addNodeParser(new ParenthesizedNodeParser(chainNodeParser)) .addNodeParser(new PromiseNodeParser(typeChecker, chainNodeParser)) + .addNodeParser(new ReturnTypeNodeParser(chainNodeParser, typeChecker)) .addNodeParser(new TypeReferenceNodeParser(typeChecker, chainNodeParser)) .addNodeParser(new ExpressionWithTypeArgumentsNodeParser(typeChecker, chainNodeParser)) .addNodeParser(new IndexedAccessTypeNodeParser(typeChecker, chainNodeParser)) diff --git a/src/NodeParser/ReturnTypeNodeParser.ts b/src/NodeParser/ReturnTypeNodeParser.ts new file mode 100644 index 000000000..2d58f115e --- /dev/null +++ b/src/NodeParser/ReturnTypeNodeParser.ts @@ -0,0 +1,104 @@ +import ts from "typescript"; +import type { Context, NodeParser } from "../NodeParser.js"; +import type { SubNodeParser } from "../SubNodeParser.js"; +import type { BaseType } from "../Type/BaseType.js"; +import { UnknownNodeError } from "../Error/Errors.js"; +import { ObjectType } from "../Type/ObjectType.js"; + +export class ReturnTypeNodeParser implements SubNodeParser { + constructor( + private readonly childNodeParser: NodeParser, + private readonly checker: ts.TypeChecker, + ) {} + + supportsNode(node: ts.Node): boolean { + return ts.isTypeReferenceNode(node) && + node.typeName.getText() === 'ReturnType' && + node.typeArguments?.length === 1; + } + + createType(node: ts.TypeReferenceNode, context: Context): BaseType { + try { + if (!node.typeArguments || node.typeArguments.length !== 1) { + throw new UnknownNodeError(node); + } + + const typeArg = node.typeArguments[0]; + + // If the type argument is a typeof expression + if (ts.isTypeQueryNode(typeArg)) { + // Get the symbol for the identifier + const symbol = this.checker.getSymbolAtLocation(typeArg.exprName); + if (!symbol) { + throw new UnknownNodeError(node); + } + + // Get the declarations of the symbol + const declarations = symbol.getDeclarations() || []; + + // Try multiple methods to extract return type + for (const decl of declarations) { + let returnTypeNode: ts.TypeNode | undefined; + + // If declaration is a function/method with explicit return type + if ( + (ts.isFunctionDeclaration(decl) || ts.isMethodDeclaration(decl) || + ts.isArrowFunction(decl) || ts.isFunctionExpression(decl)) && + decl.type + ) { + returnTypeNode = decl.type; + } + // If declaration is a variable with function type annotation + else if ( + ts.isVariableDeclaration(decl) && + decl.type && + ts.isFunctionTypeNode(decl.type) + ) { + returnTypeNode = decl.type.type; + } + + // If we found a return type node, process it + if (returnTypeNode) { + const baseType = this.childNodeParser.createType(returnTypeNode, context); + return baseType; + } + } + + // Fallback to type checking method + const type = this.checker.getTypeOfSymbolAtLocation(symbol, typeArg); + const signatures = type.getCallSignatures(); + + if (signatures.length > 0) { + // Use getReturnType directly from the signature + const returnType = signatures[0].getReturnType(); + + const returnTypeNode = this.checker.typeToTypeNode( + returnType, + undefined, + ts.NodeBuilderFlags.NoTruncation + ); + + if (returnTypeNode) { + return this.childNodeParser.createType(returnTypeNode, context); + } + } + } + + // If the above methods fail, try to get type directly + const type = this.checker.getTypeAtLocation(typeArg); + const typeNode = this.checker.typeToTypeNode( + type, + undefined, + ts.NodeBuilderFlags.NoTruncation + ); + + if (typeNode) { + return this.childNodeParser.createType(typeNode, context); + } + + throw new UnknownNodeError(node); + } catch (error) { + throw error; + } + } +} diff --git a/test/valid-data-type.test.ts b/test/valid-data-type.test.ts index 519b48864..d43086bd0 100644 --- a/test/valid-data-type.test.ts +++ b/test/valid-data-type.test.ts @@ -156,4 +156,9 @@ describe("valid-data-type", () => { "export-star-prune-unreachable", assertValidSchema("export-star-prune-unreachable", "*", undefined, { mainTsOnly: true }), ); + + it( + "type-return-type", + assertValidSchema("type-return-type", "Greeting") + ); }); diff --git a/test/valid-data/type-return-type/main.ts b/test/valid-data/type-return-type/main.ts new file mode 100644 index 000000000..c38663716 --- /dev/null +++ b/test/valid-data/type-return-type/main.ts @@ -0,0 +1,5 @@ +export function greet(name: string): { message: string } { + return { message: `Hello, ${name}!` }; +} + +export type Greeting = ReturnType; \ No newline at end of file diff --git a/test/valid-data/type-return-type/schema.json b/test/valid-data/type-return-type/schema.json new file mode 100644 index 000000000..1948bf15f --- /dev/null +++ b/test/valid-data/type-return-type/schema.json @@ -0,0 +1,18 @@ +{ + "$ref": "#/definitions/Greeting", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Greeting": { + "additionalProperties": false, + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } +} \ No newline at end of file From f379eac17bea52990f8a949521de756474d9c832 Mon Sep 17 00:00:00 2001 From: rockerBOO Date: Mon, 21 Jul 2025 18:37:32 -0400 Subject: [PATCH 2/7] feat: add ReturnType support --- src/NodeParser/ReturnTypeNodeParser.ts | 62 ++++++++++++++----- test/valid-data-type.test.ts | 4 ++ .../type-return-type-complex/main.ts | 15 +++++ .../type-return-type-complex/schema.json | 25 ++++++++ 4 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 test/valid-data/type-return-type-complex/main.ts create mode 100644 test/valid-data/type-return-type-complex/schema.json diff --git a/src/NodeParser/ReturnTypeNodeParser.ts b/src/NodeParser/ReturnTypeNodeParser.ts index 2d58f115e..29ec055b4 100644 --- a/src/NodeParser/ReturnTypeNodeParser.ts +++ b/src/NodeParser/ReturnTypeNodeParser.ts @@ -12,9 +12,19 @@ export class ReturnTypeNodeParser implements SubNodeParser { ) {} supportsNode(node: ts.Node): boolean { - return ts.isTypeReferenceNode(node) && - node.typeName.getText() === 'ReturnType' && - node.typeArguments?.length === 1; + if (!ts.isTypeReferenceNode(node)) { + return false; + } + + // Check if it's a ReturnType reference + try { + const typeName = ts.isIdentifier(node.typeName) + ? node.typeName.text + : node.typeName.getText(); + return typeName === 'ReturnType' && node.typeArguments?.length === 1; + } catch { + return false; + } } createType(node: ts.TypeReferenceNode, context: Context): BaseType { @@ -25,8 +35,9 @@ export class ReturnTypeNodeParser implements SubNodeParser { const typeArg = node.typeArguments[0]; - // If the type argument is a typeof expression + // Handle different types of type arguments if (ts.isTypeQueryNode(typeArg)) { + // Case: ReturnType // Get the symbol for the identifier const symbol = this.checker.getSymbolAtLocation(typeArg.exprName); if (!symbol) { @@ -82,18 +93,37 @@ export class ReturnTypeNodeParser implements SubNodeParser { return this.childNodeParser.createType(returnTypeNode, context); } } - } - - // If the above methods fail, try to get type directly - const type = this.checker.getTypeAtLocation(typeArg); - const typeNode = this.checker.typeToTypeNode( - type, - undefined, - ts.NodeBuilderFlags.NoTruncation - ); - - if (typeNode) { - return this.childNodeParser.createType(typeNode, context); + } else { + // Case: ReturnType or other complex types + // Get the type directly from TypeScript's type system + const argType = this.checker.getTypeAtLocation(typeArg); + + // If it's a function type, get its return type + const signatures = argType.getCallSignatures(); + if (signatures.length > 0) { + const returnType = signatures[0].getReturnType(); + const returnTypeNode = this.checker.typeToTypeNode( + returnType, + undefined, + ts.NodeBuilderFlags.NoTruncation + ); + + if (returnTypeNode) { + return this.childNodeParser.createType(returnTypeNode, context); + } + } + + // Final fallback: try to get type directly + const type = this.checker.getTypeAtLocation(typeArg); + const typeNode = this.checker.typeToTypeNode( + type, + undefined, + ts.NodeBuilderFlags.NoTruncation + ); + + if (typeNode) { + return this.childNodeParser.createType(typeNode, context); + } } throw new UnknownNodeError(node); diff --git a/test/valid-data-type.test.ts b/test/valid-data-type.test.ts index d43086bd0..41ba20a7a 100644 --- a/test/valid-data-type.test.ts +++ b/test/valid-data-type.test.ts @@ -161,4 +161,8 @@ describe("valid-data-type", () => { "type-return-type", assertValidSchema("type-return-type", "Greeting") ); + it( + "type-return-type-complex", + assertValidSchema("type-return-type-complex", "TestAppState") + ); }); diff --git a/test/valid-data/type-return-type-complex/main.ts b/test/valid-data/type-return-type-complex/main.ts new file mode 100644 index 000000000..48923a624 --- /dev/null +++ b/test/valid-data/type-return-type-complex/main.ts @@ -0,0 +1,15 @@ +// Simulated Redux Toolkit scenario +export interface TestState { + counter: number; + name: string; +} + +export function createTestStore() { + return { + getState: () => ({ counter: 0, name: "test" } as TestState), + dispatch: (action: any) => {}, + }; +} + +export type TestAppStore = ReturnType; +export type TestAppState = ReturnType; \ No newline at end of file diff --git a/test/valid-data/type-return-type-complex/schema.json b/test/valid-data/type-return-type-complex/schema.json new file mode 100644 index 000000000..629ac0739 --- /dev/null +++ b/test/valid-data/type-return-type-complex/schema.json @@ -0,0 +1,25 @@ +{ + "$ref": "#/definitions/TestAppState", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "TestAppState": { + "$ref": "#/definitions/TestState" + }, + "TestState": { + "additionalProperties": false, + "properties": { + "counter": { + "type": "number" + }, + "name": { + "type": "string" + } + }, + "required": [ + "counter", + "name" + ], + "type": "object" + } + } +} \ No newline at end of file From af0482e1ff67536bf1a09a5cd07b088f7d4f897e Mon Sep 17 00:00:00 2001 From: rockerBOO Date: Tue, 22 Jul 2025 13:33:28 -0400 Subject: [PATCH 3/7] fix: add implicit return type test --- src/NodeParser/ReturnTypeNodeParser.ts | 176 ++++++++---------- test/valid-data-type.test.ts | 4 + .../type-return-type-implicit/main.ts | 5 + .../type-return-type-implicit/schema.json | 23 +++ 4 files changed, 114 insertions(+), 94 deletions(-) create mode 100644 test/valid-data/type-return-type-implicit/main.ts create mode 100644 test/valid-data/type-return-type-implicit/schema.json diff --git a/src/NodeParser/ReturnTypeNodeParser.ts b/src/NodeParser/ReturnTypeNodeParser.ts index 29ec055b4..93dbf27a8 100644 --- a/src/NodeParser/ReturnTypeNodeParser.ts +++ b/src/NodeParser/ReturnTypeNodeParser.ts @@ -15,120 +15,108 @@ export class ReturnTypeNodeParser implements SubNodeParser { if (!ts.isTypeReferenceNode(node)) { return false; } - + // Check if it's a ReturnType reference try { - const typeName = ts.isIdentifier(node.typeName) - ? node.typeName.text - : node.typeName.getText(); - return typeName === 'ReturnType' && node.typeArguments?.length === 1; + const typeName = ts.isIdentifier(node.typeName) ? node.typeName.text : node.typeName.getText(); + return typeName === "ReturnType" && node.typeArguments?.length === 1; } catch { return false; } } createType(node: ts.TypeReferenceNode, context: Context): BaseType { - try { - if (!node.typeArguments || node.typeArguments.length !== 1) { + if (!node.typeArguments || node.typeArguments.length !== 1) { + throw new UnknownNodeError(node); + } + + const typeArg = node.typeArguments[0]; + + // Handle different types of type arguments + if (ts.isTypeQueryNode(typeArg)) { + // Case: ReturnType + // Get the symbol for the identifier + const symbol = this.checker.getSymbolAtLocation(typeArg.exprName); + if (!symbol) { throw new UnknownNodeError(node); } - const typeArg = node.typeArguments[0]; - - // Handle different types of type arguments - if (ts.isTypeQueryNode(typeArg)) { - // Case: ReturnType - // Get the symbol for the identifier - const symbol = this.checker.getSymbolAtLocation(typeArg.exprName); - if (!symbol) { - throw new UnknownNodeError(node); - } + // Get the declarations of the symbol + const declarations = symbol.getDeclarations() || []; + + // Try multiple methods to extract return type + for (const decl of declarations) { + let returnTypeNode: ts.TypeNode | undefined; - // Get the declarations of the symbol - const declarations = symbol.getDeclarations() || []; - - // Try multiple methods to extract return type - for (const decl of declarations) { - let returnTypeNode: ts.TypeNode | undefined; - - // If declaration is a function/method with explicit return type - if ( - (ts.isFunctionDeclaration(decl) || ts.isMethodDeclaration(decl) || - ts.isArrowFunction(decl) || ts.isFunctionExpression(decl)) && - decl.type - ) { - returnTypeNode = decl.type; - } - // If declaration is a variable with function type annotation - else if ( - ts.isVariableDeclaration(decl) && - decl.type && - ts.isFunctionTypeNode(decl.type) - ) { - returnTypeNode = decl.type.type; - } - - // If we found a return type node, process it - if (returnTypeNode) { - const baseType = this.childNodeParser.createType(returnTypeNode, context); - return baseType; - } + // If declaration is a function/method with explicit return type + if ( + (ts.isFunctionDeclaration(decl) || + ts.isMethodDeclaration(decl) || + ts.isArrowFunction(decl) || + ts.isFunctionExpression(decl)) && + decl.type + ) { + returnTypeNode = decl.type; + } + // If declaration is a variable with function type annotation + else if (ts.isVariableDeclaration(decl) && decl.type && ts.isFunctionTypeNode(decl.type)) { + returnTypeNode = decl.type.type; } - // Fallback to type checking method - const type = this.checker.getTypeOfSymbolAtLocation(symbol, typeArg); - const signatures = type.getCallSignatures(); - - if (signatures.length > 0) { - // Use getReturnType directly from the signature - const returnType = signatures[0].getReturnType(); - - const returnTypeNode = this.checker.typeToTypeNode( - returnType, - undefined, - ts.NodeBuilderFlags.NoTruncation - ); - - if (returnTypeNode) { - return this.childNodeParser.createType(returnTypeNode, context); - } + // If we found a return type node, process it + if (returnTypeNode) { + const baseType = this.childNodeParser.createType(returnTypeNode, context); + return baseType; } - } else { - // Case: ReturnType or other complex types - // Get the type directly from TypeScript's type system - const argType = this.checker.getTypeAtLocation(typeArg); - - // If it's a function type, get its return type - const signatures = argType.getCallSignatures(); - if (signatures.length > 0) { - const returnType = signatures[0].getReturnType(); - const returnTypeNode = this.checker.typeToTypeNode( - returnType, - undefined, - ts.NodeBuilderFlags.NoTruncation - ); - - if (returnTypeNode) { - return this.childNodeParser.createType(returnTypeNode, context); - } + } + + // Fallback to type checking method + const type = this.checker.getTypeOfSymbolAtLocation(symbol, typeArg); + const signatures = type.getCallSignatures(); + + if (signatures.length > 0) { + // Use getReturnType directly from the signature + const returnType = signatures[0].getReturnType(); + + const returnTypeNode = this.checker.typeToTypeNode( + returnType, + undefined, + ts.NodeBuilderFlags.NoTruncation, + ); + + if (returnTypeNode) { + return this.childNodeParser.createType(returnTypeNode, context); } - - // Final fallback: try to get type directly - const type = this.checker.getTypeAtLocation(typeArg); - const typeNode = this.checker.typeToTypeNode( - type, - undefined, - ts.NodeBuilderFlags.NoTruncation + } + } else { + // Case: ReturnType or other complex types + // Get the type directly from TypeScript's type system + const argType = this.checker.getTypeAtLocation(typeArg); + + // If it's a function type, get its return type + const signatures = argType.getCallSignatures(); + if (signatures.length > 0) { + const returnType = signatures[0].getReturnType(); + const returnTypeNode = this.checker.typeToTypeNode( + returnType, + undefined, + ts.NodeBuilderFlags.NoTruncation, ); - - if (typeNode) { - return this.childNodeParser.createType(typeNode, context); + + if (returnTypeNode) { + return this.childNodeParser.createType(returnTypeNode, context); } } - throw new UnknownNodeError(node); - } catch (error) { - throw error; + // Final fallback: try to get type directly + const type = this.checker.getTypeAtLocation(typeArg); + const typeNode = this.checker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.NoTruncation); + + if (typeNode) { + return this.childNodeParser.createType(typeNode, context); + } } + + throw new UnknownNodeError(node); } } diff --git a/test/valid-data-type.test.ts b/test/valid-data-type.test.ts index 41ba20a7a..06592362c 100644 --- a/test/valid-data-type.test.ts +++ b/test/valid-data-type.test.ts @@ -165,4 +165,8 @@ describe("valid-data-type", () => { "type-return-type-complex", assertValidSchema("type-return-type-complex", "TestAppState") ); + it( + "type-return-type-implicit", + assertValidSchema("type-return-type-implicit", "ImplicitReturnType") + ); }); diff --git a/test/valid-data/type-return-type-implicit/main.ts b/test/valid-data/type-return-type-implicit/main.ts new file mode 100644 index 000000000..9fdcee57f --- /dev/null +++ b/test/valid-data/type-return-type-implicit/main.ts @@ -0,0 +1,5 @@ +export function implicitReturn() { + return { message: "Hello", count: 42 }; +} + +export type ImplicitReturnType = ReturnType; \ No newline at end of file diff --git a/test/valid-data/type-return-type-implicit/schema.json b/test/valid-data/type-return-type-implicit/schema.json new file mode 100644 index 000000000..79ce6eb27 --- /dev/null +++ b/test/valid-data/type-return-type-implicit/schema.json @@ -0,0 +1,23 @@ +{ + "$ref": "#/definitions/ImplicitReturnType", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ImplicitReturnType": { + "additionalProperties": false, + "properties": { + "count": { + "type": "number" + }, + "message": { + "type": "string" + } + }, + "required": [ + "message", + "count" + ], + "type": "object" + } + } +} + From 1008735808ff38171676978d344630017a58ad68 Mon Sep 17 00:00:00 2001 From: rockerBOO Date: Tue, 22 Jul 2025 18:46:00 -0400 Subject: [PATCH 4/7] feat: add more complex function/method parsing tests. abstract common signature extraction --- factory/parser.ts | 2 +- src/NodeParser/ReturnTypeNodeParser.ts | 54 ++++---- test/valid-data-type.test.ts | 18 +-- .../type-return-type-complex/main.ts | 14 +- .../type-return-type-function/main.ts | 44 +++++++ .../type-return-type-function/schema.json | 120 ++++++++++++++++++ .../type-return-type-implicit/main.ts | 2 +- .../type-return-type-method/main.ts | 33 +++++ .../type-return-type-method/schema.json | 94 ++++++++++++++ 9 files changed, 332 insertions(+), 49 deletions(-) create mode 100644 test/valid-data/type-return-type-function/main.ts create mode 100644 test/valid-data/type-return-type-function/schema.json create mode 100644 test/valid-data/type-return-type-method/main.ts create mode 100644 test/valid-data/type-return-type-method/schema.json diff --git a/factory/parser.ts b/factory/parser.ts index 7bdc5e09d..7cb66d528 100644 --- a/factory/parser.ts +++ b/factory/parser.ts @@ -131,7 +131,7 @@ export function createParser(program: ts.Program, config: CompletedConfig, augme .addNodeParser(new ParenthesizedNodeParser(chainNodeParser)) .addNodeParser(new PromiseNodeParser(typeChecker, chainNodeParser)) - .addNodeParser(new ReturnTypeNodeParser(chainNodeParser, typeChecker)) + .addNodeParser(new ReturnTypeNodeParser(chainNodeParser, typeChecker)) .addNodeParser(new TypeReferenceNodeParser(typeChecker, chainNodeParser)) .addNodeParser(new ExpressionWithTypeArgumentsNodeParser(typeChecker, chainNodeParser)) .addNodeParser(new IndexedAccessTypeNodeParser(typeChecker, chainNodeParser)) diff --git a/src/NodeParser/ReturnTypeNodeParser.ts b/src/NodeParser/ReturnTypeNodeParser.ts index 93dbf27a8..51d41e791 100644 --- a/src/NodeParser/ReturnTypeNodeParser.ts +++ b/src/NodeParser/ReturnTypeNodeParser.ts @@ -72,21 +72,9 @@ export class ReturnTypeNodeParser implements SubNodeParser { // Fallback to type checking method const type = this.checker.getTypeOfSymbolAtLocation(symbol, typeArg); - const signatures = type.getCallSignatures(); - - if (signatures.length > 0) { - // Use getReturnType directly from the signature - const returnType = signatures[0].getReturnType(); - - const returnTypeNode = this.checker.typeToTypeNode( - returnType, - undefined, - ts.NodeBuilderFlags.NoTruncation, - ); - - if (returnTypeNode) { - return this.childNodeParser.createType(returnTypeNode, context); - } + const result = extractReturnTypeFromSignatures(type, this.checker, this.childNodeParser, context); + if (result) { + return result; } } else { // Case: ReturnType or other complex types @@ -94,18 +82,9 @@ export class ReturnTypeNodeParser implements SubNodeParser { const argType = this.checker.getTypeAtLocation(typeArg); // If it's a function type, get its return type - const signatures = argType.getCallSignatures(); - if (signatures.length > 0) { - const returnType = signatures[0].getReturnType(); - const returnTypeNode = this.checker.typeToTypeNode( - returnType, - undefined, - ts.NodeBuilderFlags.NoTruncation, - ); - - if (returnTypeNode) { - return this.childNodeParser.createType(returnTypeNode, context); - } + const result = extractReturnTypeFromSignatures(argType, this.checker, this.childNodeParser, context); + if (result) { + return result; } // Final fallback: try to get type directly @@ -120,3 +99,24 @@ export class ReturnTypeNodeParser implements SubNodeParser { throw new UnknownNodeError(node); } } + +/** + * Helper function to extract return type from call signatures + */ +function extractReturnTypeFromSignatures( + type: ts.Type, + checker: ts.TypeChecker, + childNodeParser: NodeParser, + context: Context, +): BaseType | null { + const signatures = type.getCallSignatures(); + if (signatures.length > 0) { + const returnType = signatures[0].getReturnType(); + const returnTypeNode = checker.typeToTypeNode(returnType, undefined, ts.NodeBuilderFlags.NoTruncation); + + if (returnTypeNode) { + return childNodeParser.createType(returnTypeNode, context); + } + } + return null; +} diff --git a/test/valid-data-type.test.ts b/test/valid-data-type.test.ts index 06592362c..54ff588c0 100644 --- a/test/valid-data-type.test.ts +++ b/test/valid-data-type.test.ts @@ -156,17 +156,9 @@ describe("valid-data-type", () => { "export-star-prune-unreachable", assertValidSchema("export-star-prune-unreachable", "*", undefined, { mainTsOnly: true }), ); - - it( - "type-return-type", - assertValidSchema("type-return-type", "Greeting") - ); - it( - "type-return-type-complex", - assertValidSchema("type-return-type-complex", "TestAppState") - ); - it( - "type-return-type-implicit", - assertValidSchema("type-return-type-implicit", "ImplicitReturnType") - ); + it("type-return-type", assertValidSchema("type-return-type", "Greeting")); + it("type-return-type-complex", assertValidSchema("type-return-type-complex", "TestAppState")); + it("type-return-type-implicit", assertValidSchema("type-return-type-implicit", "ImplicitReturnType")); + it("type-return-type-function-parser", assertValidSchema("type-return-type-function", "FunctionReturnTypes")); + it("type-return-type-method", assertValidSchema("type-return-type-method", "MethodReturnTypes")); }); diff --git a/test/valid-data/type-return-type-complex/main.ts b/test/valid-data/type-return-type-complex/main.ts index 48923a624..976f1099c 100644 --- a/test/valid-data/type-return-type-complex/main.ts +++ b/test/valid-data/type-return-type-complex/main.ts @@ -1,15 +1,15 @@ // Simulated Redux Toolkit scenario export interface TestState { - counter: number; - name: string; + counter: number; + name: string; } export function createTestStore() { - return { - getState: () => ({ counter: 0, name: "test" } as TestState), - dispatch: (action: any) => {}, - }; + return { + getState: () => ({ counter: 0, name: "test" }) as TestState, + dispatch: (action: any) => {}, + }; } export type TestAppStore = ReturnType; -export type TestAppState = ReturnType; \ No newline at end of file +export type TestAppState = ReturnType; diff --git a/test/valid-data/type-return-type-function/main.ts b/test/valid-data/type-return-type-function/main.ts new file mode 100644 index 000000000..ed6d63677 --- /dev/null +++ b/test/valid-data/type-return-type-function/main.ts @@ -0,0 +1,44 @@ +// Test cases to demonstrate ReturnType parsing with various function types + +// Implicit return type +export function implicitReturn() { + return { message: "Hello", count: 42 }; +} + +// Arrow function with implicit return +export const arrowImplicitReturn = () => ({ + nested: { + value: "test", + count: 123, + }, +}); + +// Function expression with implicit return +export const functionExprImplicitReturn = function () { + return { + dynamic: true, + payload: { id: 456, name: "example" }, + }; +}; + +// Complex nested return type with explicit annotation +export function complexNestedReturn(): { + meta: { + version: number; + type: string; + }; + data: string[]; +} { + return { + meta: { version: 1, type: "test" }, + data: ["item1", "item2"], + }; +} + +// Combined type that tests all function return types +export type FunctionReturnTypes = { + implicit: ReturnType; + arrow: ReturnType; + functionExpr: ReturnType; + complex: ReturnType; +}; diff --git a/test/valid-data/type-return-type-function/schema.json b/test/valid-data/type-return-type-function/schema.json new file mode 100644 index 000000000..0d6e3c116 --- /dev/null +++ b/test/valid-data/type-return-type-function/schema.json @@ -0,0 +1,120 @@ +{ + "$ref": "#/definitions/FunctionReturnTypes", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "FunctionReturnTypes": { + "additionalProperties": false, + "properties": { + "arrow": { + "additionalProperties": false, + "properties": { + "nested": { + "additionalProperties": false, + "properties": { + "count": { + "type": "number" + }, + "value": { + "type": "string" + } + }, + "required": [ + "value", + "count" + ], + "type": "object" + } + }, + "required": [ + "nested" + ], + "type": "object" + }, + "complex": { + "additionalProperties": false, + "properties": { + "data": { + "items": { + "type": "string" + }, + "type": "array" + }, + "meta": { + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "version": { + "type": "number" + } + }, + "required": [ + "version", + "type" + ], + "type": "object" + } + }, + "required": [ + "meta", + "data" + ], + "type": "object" + }, + "functionExpr": { + "additionalProperties": false, + "properties": { + "dynamic": { + "type": "boolean" + }, + "payload": { + "additionalProperties": false, + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + } + }, + "required": [ + "dynamic", + "payload" + ], + "type": "object" + }, + "implicit": { + "additionalProperties": false, + "properties": { + "count": { + "type": "number" + }, + "message": { + "type": "string" + } + }, + "required": [ + "message", + "count" + ], + "type": "object" + } + }, + "required": [ + "implicit", + "arrow", + "functionExpr", + "complex" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/test/valid-data/type-return-type-implicit/main.ts b/test/valid-data/type-return-type-implicit/main.ts index 9fdcee57f..fa66b8f1d 100644 --- a/test/valid-data/type-return-type-implicit/main.ts +++ b/test/valid-data/type-return-type-implicit/main.ts @@ -2,4 +2,4 @@ export function implicitReturn() { return { message: "Hello", count: 42 }; } -export type ImplicitReturnType = ReturnType; \ No newline at end of file +export type ImplicitReturnType = ReturnType; diff --git a/test/valid-data/type-return-type-method/main.ts b/test/valid-data/type-return-type-method/main.ts new file mode 100644 index 000000000..a3cdf5250 --- /dev/null +++ b/test/valid-data/type-return-type-method/main.ts @@ -0,0 +1,33 @@ +export class MyClass { + // Method with explicit return type + getData(): { id: number; name: string } { + return { id: 1, name: "test" }; + } + + // Method with implicit return type + getStatus() { + return { active: true, count: 42 }; + } + + // Method with complex return type + getNestedData(): { + meta: { version: number; type: string }; + items: string[]; + } { + return { + meta: { version: 1, type: "test" }, + items: ["a", "b", "c"], + }; + } + + // Arrow method with implicit return + getSimple = () => ({ message: "hello" }); +} + +// Combined type that tests all method return types +export type MethodReturnTypes = { + explicit: ReturnType; + implicit: ReturnType; + complex: ReturnType; + arrow: ReturnType; +}; diff --git a/test/valid-data/type-return-type-method/schema.json b/test/valid-data/type-return-type-method/schema.json new file mode 100644 index 000000000..2522d72e3 --- /dev/null +++ b/test/valid-data/type-return-type-method/schema.json @@ -0,0 +1,94 @@ +{ + "$ref": "#/definitions/MethodReturnTypes", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MethodReturnTypes": { + "additionalProperties": false, + "properties": { + "arrow": { + "additionalProperties": false, + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "complex": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "type": "string" + }, + "type": "array" + }, + "meta": { + "additionalProperties": false, + "properties": { + "type": { + "type": "string" + }, + "version": { + "type": "number" + } + }, + "required": [ + "version", + "type" + ], + "type": "object" + } + }, + "required": [ + "meta", + "items" + ], + "type": "object" + }, + "explicit": { + "additionalProperties": false, + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "type": "object" + }, + "implicit": { + "additionalProperties": false, + "properties": { + "active": { + "type": "boolean" + }, + "count": { + "type": "number" + } + }, + "required": [ + "active", + "count" + ], + "type": "object" + } + }, + "required": [ + "explicit", + "implicit", + "complex", + "arrow" + ], + "type": "object" + } + } +} \ No newline at end of file From 7c0648975eb5a3d36edb90f4ba5723a9dd714e55 Mon Sep 17 00:00:00 2001 From: rockerBOO Date: Tue, 22 Jul 2025 19:57:47 -0400 Subject: [PATCH 5/7] chore: formatting --- factory/parser.ts | 2 +- test/valid-data/type-return-type/main.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/factory/parser.ts b/factory/parser.ts index 7cb66d528..7bdc5e09d 100644 --- a/factory/parser.ts +++ b/factory/parser.ts @@ -131,7 +131,7 @@ export function createParser(program: ts.Program, config: CompletedConfig, augme .addNodeParser(new ParenthesizedNodeParser(chainNodeParser)) .addNodeParser(new PromiseNodeParser(typeChecker, chainNodeParser)) - .addNodeParser(new ReturnTypeNodeParser(chainNodeParser, typeChecker)) + .addNodeParser(new ReturnTypeNodeParser(chainNodeParser, typeChecker)) .addNodeParser(new TypeReferenceNodeParser(typeChecker, chainNodeParser)) .addNodeParser(new ExpressionWithTypeArgumentsNodeParser(typeChecker, chainNodeParser)) .addNodeParser(new IndexedAccessTypeNodeParser(typeChecker, chainNodeParser)) diff --git a/test/valid-data/type-return-type/main.ts b/test/valid-data/type-return-type/main.ts index c38663716..97f13f196 100644 --- a/test/valid-data/type-return-type/main.ts +++ b/test/valid-data/type-return-type/main.ts @@ -2,4 +2,4 @@ export function greet(name: string): { message: string } { return { message: `Hello, ${name}!` }; } -export type Greeting = ReturnType; \ No newline at end of file +export type Greeting = ReturnType; From e188df586214af991ee9ca75e8a21db2ee23d174 Mon Sep 17 00:00:00 2001 From: rockerBOO Date: Thu, 24 Jul 2025 12:54:19 -0400 Subject: [PATCH 6/7] fix: removed extra try/catch --- src/NodeParser/ReturnTypeNodeParser.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/NodeParser/ReturnTypeNodeParser.ts b/src/NodeParser/ReturnTypeNodeParser.ts index 51d41e791..72249b0b7 100644 --- a/src/NodeParser/ReturnTypeNodeParser.ts +++ b/src/NodeParser/ReturnTypeNodeParser.ts @@ -16,13 +16,8 @@ export class ReturnTypeNodeParser implements SubNodeParser { return false; } - // Check if it's a ReturnType reference - try { - const typeName = ts.isIdentifier(node.typeName) ? node.typeName.text : node.typeName.getText(); - return typeName === "ReturnType" && node.typeArguments?.length === 1; - } catch { - return false; - } + const typeName = ts.isIdentifier(node.typeName) ? node.typeName.text : node.typeName.getText(); + return typeName === "ReturnType" && node.typeArguments?.length === 1; } createType(node: ts.TypeReferenceNode, context: Context): BaseType { From a26686708fe18c42e0b2146e7d34f909374117f3 Mon Sep 17 00:00:00 2001 From: rockerBOO Date: Fri, 25 Jul 2025 17:54:53 -0400 Subject: [PATCH 7/7] fix: remove ObjectType --- src/NodeParser/ReturnTypeNodeParser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NodeParser/ReturnTypeNodeParser.ts b/src/NodeParser/ReturnTypeNodeParser.ts index 72249b0b7..ad5e18579 100644 --- a/src/NodeParser/ReturnTypeNodeParser.ts +++ b/src/NodeParser/ReturnTypeNodeParser.ts @@ -3,7 +3,6 @@ import type { Context, NodeParser } from "../NodeParser.js"; import type { SubNodeParser } from "../SubNodeParser.js"; import type { BaseType } from "../Type/BaseType.js"; import { UnknownNodeError } from "../Error/Errors.js"; -import { ObjectType } from "../Type/ObjectType.js"; export class ReturnTypeNodeParser implements SubNodeParser { constructor(