Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/handlers/typebox/base-type-handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { Node, ts } from 'ts-morph'

export abstract class BaseTypeHandler {
abstract canHandle(node: Node): boolean
abstract handle(node: Node): ts.Expression
abstract handle(node: Node, context: TypeBoxContext): ts.Expression
}
Comment on lines +1 to 7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Abstract signature update looks correct; this is a breaking change—verify all subclasses.
Good use of a type-only import. Please ensure all handlers now implement (node, context).

Run to find handlers missing a context parameter:


🏁 Script executed:

#!/bin/bash
# Handlers extending BaseTypeHandler
rg -nP --type ts -C2 'class\s+\w+\s+extends\s+BaseTypeHandler\b' src

# handle() overrides that don't include a `context:` parameter
rg -nP --type ts 'handle\s*\((?![^)]*context\s*:)' src/handlers/typebox

Length of output: 7830


Ensure all handlers implement the updated handle(node, context) signature
The following subclasses of BaseTypeHandler still use the old handle(...) signature and must be updated to handle(node: X, context: TypeBoxContext): ts.Expression:

  • src/handlers/typebox/typeof-type-handler.ts (line 10)
  • src/handlers/typebox/type-query-handler.ts (line 10)
  • src/handlers/typebox/template-literal-type-handler.ts (line 11)
  • src/handlers/typebox/literal-type-handler.ts (line 10)
  • src/handlers/typebox/keyof-typeof-handler.ts (line 14)
  • src/handlers/typebox/simple-type-handler.ts (line 36)
  • src/handlers/typebox/date-type-handler.ts (line 12)
  • src/handlers/typebox/type-operator-handler.ts (line 11)
🤖 Prompt for AI Agents
In src/handlers/typebox/base-type-handler.ts lines 1-7 and the listed
subclasses, the abstract handle signature was changed to handle(node: Node,
context: TypeBoxContext): ts.Expression but several subclasses still use the old
signature; update each subclass file
(src/handlers/typebox/typeof-type-handler.ts line 10, type-query-handler.ts line
10, template-literal-type-handler.ts line 11, literal-type-handler.ts line 10,
keyof-typeof-handler.ts line 14, simple-type-handler.ts line 36,
date-type-handler.ts line 12, type-operator-handler.ts line 11) so their class
method signature is handle(node: <specific Node type>, context: TypeBoxContext):
ts.Expression, import TypeBoxContext where needed, adjust any usages inside the
methods to use the new context parameter, and ensure the return type is
ts.Expression; also update any callers to pass the context if they currently
omit it.

9 changes: 5 additions & 4 deletions src/handlers/typebox/collection/array-type-handler.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
import { ArrayTypeNode, Node, ts } from 'ts-morph'

export class ArrayTypeHandler extends CollectionBaseHandler {
canHandle(node: Node): boolean {
return Node.isArrayTypeNode(node)
}

handle(node: ArrayTypeNode): ts.Expression {
const typeboxType = getTypeBoxType(node.getElementTypeNode())
handle(node: ArrayTypeNode, context: TypeBoxContext): ts.Expression {
const typeboxType = getTypeBoxType(node.getElementTypeNode(), context)

return makeTypeCall('Array', [typeboxType])
return GenericTypeUtils.makeTypeCall('Array', [typeboxType])
}
}
13 changes: 9 additions & 4 deletions src/handlers/typebox/collection/collection-base-handler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
import { Node, ts } from 'ts-morph'

export abstract class CollectionBaseHandler extends BaseTypeHandler {
protected processTypeCollection(nodes: Node[], typeBoxFunction: string): ts.Expression {
const typeBoxTypes = nodes.map((node) => getTypeBoxType(node))
protected processTypeCollection(
nodes: Node[],
typeBoxFunction: string,
context: TypeBoxContext,
): ts.Expression {
const typeBoxTypes = nodes.map((node) => getTypeBoxType(node, context))
const arrayLiteral = ts.factory.createArrayLiteralExpression(typeBoxTypes)

return makeTypeCall(typeBoxFunction, [arrayLiteral])
return GenericTypeUtils.makeTypeCall(typeBoxFunction, [arrayLiteral])
}
Comment on lines +8 to 17
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Guard against empty collections (defensive).
If nodes is empty, Type.Union([])/Type.Intersect([]) may not be meaningful. Consider returning Type.Never/Type.Object({}) per your conventions, or assert upstream.


🏁 Script executed:

#!/bin/bash
# List all call sites to ensure `nodes.length > 0` is enforced upstream
rg -nP --type=ts -C2 '\bprocessTypeCollection\s*\(' src/handlers

Length of output: 1954


Guard empty collections in processTypeCollection. Add at the top of processTypeCollection (in collection-base-handler.ts) a check for nodes.length === 0 that returns your agreed fallback instead of calling, for example, Type.Union([]) or Type.Intersect([]). For instance, return Type.Never() for unions/intersections or Type.Object({}) for empty tuples per project conventions.

🤖 Prompt for AI Agents
In src/handlers/typebox/collection/collection-base-handler.ts around lines 8 to
17, add a guard at the top of processTypeCollection to return the project
fallback when nodes.length === 0 instead of calling the TypeBox collection
functions with an empty array; specifically, detect empty nodes and return
Type.Never() for union/intersect cases or Type.Object({}) for empty tuple cases
(use the existing GenericTypeUtils.makeTypeCall or appropriate helper to
construct these fallback TypeBox calls), otherwise continue to build the
arrayLiteral and call the original typeBoxFunction.

}
5 changes: 3 additions & 2 deletions src/handlers/typebox/collection/intersection-type-handler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { IntersectionTypeNode, Node, ts } from 'ts-morph'

export class IntersectionTypeHandler extends CollectionBaseHandler {
canHandle(node: Node): boolean {
return Node.isIntersectionTypeNode(node)
}

handle(node: IntersectionTypeNode): ts.Expression {
return this.processTypeCollection(node.getTypeNodes(), 'Intersect')
handle(node: IntersectionTypeNode, context: TypeBoxContext): ts.Expression {
return this.processTypeCollection(node.getTypeNodes(), 'Intersect', context)
}
}
6 changes: 4 additions & 2 deletions src/handlers/typebox/collection/tuple-type-handler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { Node, ts, TupleTypeNode } from 'ts-morph'

export class TupleTypeHandler extends CollectionBaseHandler {
canHandle(node: Node): boolean {
return Node.isTupleTypeNode(node)
}

handle(node: TupleTypeNode): ts.Expression {
return this.processTypeCollection(node.getElements(), 'Tuple')
handle(node: TupleTypeNode, context: TypeBoxContext): ts.Expression {
const elements = node.getElements()
return this.processTypeCollection(elements, 'Tuple', context)
Comment on lines +10 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Context-plumbed Tuple handler looks correct; verify all call sites pass context

The signature and forwarding into processTypeCollection are consistent with the new context-aware API.

Run to catch any remaining handlers/call-sites missing the context param:


🏁 Script executed:

#!/bin/bash
# 1) Handlers without a `context: TypeBoxContext` param
rg -nP --type=ts '\bhandle\s*\(\s*[^)]*\)' src/handlers/typebox | rg -nPv 'context\s*:\s*TypeBoxContext' || true

# 2) processTypeCollection calls lacking the context arg
rg -nP --type=ts 'processTypeCollection\([^,]+,\s*["'\''](Tuple|Array|Intersect|Union)["'\'']\s*\)' src/handlers/typebox || true

Length of output: 995


Add missing context parameter to handlers
The following handle() methods lack the required context: TypeBoxContext parameter and need to forward it to their processing calls:

  • src/handlers/typebox/typeof-type-handler.ts:10
  • src/handlers/typebox/type-query-handler.ts:10
  • src/handlers/typebox/template-literal-type-handler.ts:11
  • src/handlers/typebox/simple-type-handler.ts:36
  • src/handlers/typebox/literal-type-handler.ts:10
  • src/handlers/typebox/keyof-typeof-handler.ts:14
  • src/handlers/typebox/date-type-handler.ts:12
  • src/handlers/typebox/type-operator-handler.ts:11

Update each signature to handle(..., context: TypeBoxContext) and pass context into the underlying process* calls.

🤖 Prompt for AI Agents
In src/handlers/typebox/collection/tuple-type-handler.ts around lines 10 to 12,
the handle method is missing the required context parameter and does not forward
it to the processing call; change the signature to include context:
TypeBoxContext and pass that context into this.processTypeCollection(elements,
'Tuple', context) so the handler matches the other TypeBox handlers and forwards
context to downstream processing.

}
}
6 changes: 4 additions & 2 deletions src/handlers/typebox/collection/union-type-handler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler'
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { Node, ts, UnionTypeNode } from 'ts-morph'

export class UnionTypeHandler extends CollectionBaseHandler {
canHandle(node: Node): boolean {
return Node.isUnionTypeNode(node)
}

handle(node: UnionTypeNode): ts.Expression {
return this.processTypeCollection(node.getTypeNodes(), 'Union')
handle(node: UnionTypeNode, context: TypeBoxContext): ts.Expression {
const types = node.getTypeNodes()
return this.processTypeCollection(types, 'Union', context)
}
}
4 changes: 2 additions & 2 deletions src/handlers/typebox/date-type-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
import { Node, ts, TypeReferenceNode } from 'ts-morph'

export class DateTypeHandler extends BaseTypeHandler {
Expand All @@ -10,6 +10,6 @@ export class DateTypeHandler extends BaseTypeHandler {
}

handle(): ts.Expression {
return makeTypeCall('Date')
return GenericTypeUtils.makeTypeCall('Date')
}
Comment on lines 12 to 14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

handle signature must accept (node, context).
It currently takes no params and will not satisfy BaseTypeHandler.

Apply:

+import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
@@
-  handle(): ts.Expression {
+  handle(_node: TypeReferenceNode, _context: TypeBoxContext): ts.Expression {
     return GenericTypeUtils.makeTypeCall('Date')
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
handle(): ts.Expression {
return makeTypeCall('Date')
return GenericTypeUtils.makeTypeCall('Date')
}
// at the top of src/handlers/typebox/date-type-handler.ts
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
// …within your handler class…
handle(_node: TypeReferenceNode, _context: TypeBoxContext): ts.Expression {
return GenericTypeUtils.makeTypeCall('Date')
}
🤖 Prompt for AI Agents
In src/handlers/typebox/date-type-handler.ts around lines 12 to 14, the handle
method signature currently has no parameters but must match BaseTypeHandler by
accepting (node, context); change the method signature to handle(node, context)
and update any references inside to use the provided parameters (or keep them
unused but present), update imports/types if necessary so the types for node and
context are correct, and ensure the file compiles and the class implements the
BaseTypeHandler interface with the new signature.

}
13 changes: 7 additions & 6 deletions src/handlers/typebox/function-type-handler.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
import { FunctionTypeNode, Node, ts } from 'ts-morph'

export class FunctionTypeHandler extends BaseTypeHandler {
canHandle(node: Node): boolean {
return Node.isFunctionTypeNode(node)
}

handle(node: FunctionTypeNode): ts.Expression {
handle(node: FunctionTypeNode, context: TypeBoxContext): ts.Expression {
const parameters = node.getParameters()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Exclude this parameter from runtime schema generation

this parameters are type-only and must not appear in Type.Function’s parameter list.

Apply this diff:

-    const parameters = node.getParameters()
+    const parameters = node.getParameters().filter((p) => !p.isThisParameter())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const parameters = node.getParameters()
const parameters = node.getParameters().filter((p) => !p.isThisParameter())
🤖 Prompt for AI Agents
In src/handlers/typebox/function-type-handler.ts around line 13, the code
collects parameters with node.getParameters() but must exclude the type-only
`this` parameter from the runtime schema; filter the parameters list to remove
any parameter that is the `this` parameter (e.g., using
parameter.isThisParameter() or checking the parameter name === 'this') before
generating the Type.Function parameter list so `this` does not appear in the
emitted TypeBox schema.

const returnType = node.getReturnTypeNode()

// Convert parameters to TypeBox types
const parameterTypes = parameters.map((param) => {
const paramTypeNode = param.getTypeNode()
const paramType = getTypeBoxType(paramTypeNode)
const paramType = getTypeBoxType(paramTypeNode, context)

// Check if parameter is optional
if (param.hasQuestionToken()) {
return makeTypeCall('Optional', [paramType])
return GenericTypeUtils.makeTypeCall('Optional', [paramType])
}

return paramType
})

// Convert return type to TypeBox type
const returnTypeBox = getTypeBoxType(returnType)
const returnTypeBox = getTypeBoxType(returnType, context)

// Create TypeBox Function call with parameters array and return type
return makeTypeCall('Function', [
return GenericTypeUtils.makeTypeCall('Function', [
ts.factory.createArrayLiteralExpression(parameterTypes),
returnTypeBox,
])
Expand Down
28 changes: 17 additions & 11 deletions src/handlers/typebox/indexed-access-type-handler.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
import { IndexedAccessTypeNode, Node, ts, TypeNode } from 'ts-morph'

export class IndexedAccessTypeHandler extends BaseTypeHandler {
canHandle(node: Node): boolean {
return node.isKind(ts.SyntaxKind.IndexedAccessType)
}

handle(node: IndexedAccessTypeNode): ts.Expression {
handle(node: IndexedAccessTypeNode, context: TypeBoxContext): ts.Expression {
const objectType = node.getObjectTypeNode()
const indexType = node.getIndexTypeNode()

Expand All @@ -17,18 +18,19 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler {
objectType.isKind(ts.SyntaxKind.TypeQuery) &&
indexType.isKind(ts.SyntaxKind.NumberKeyword)
) {
return this.handleTypeofArrayAccess(objectType, node)
return this.handleTypeofArrayAccess(objectType, node, context)
}

const typeboxObjectType = getTypeBoxType(objectType)
const typeboxIndexType = getTypeBoxType(indexType)
const typeboxObjectType = getTypeBoxType(objectType, context)
const typeboxIndexType = getTypeBoxType(indexType, context)

return makeTypeCall('Index', [typeboxObjectType, typeboxIndexType])
return GenericTypeUtils.makeTypeCall('Index', [typeboxObjectType, typeboxIndexType])
}

private handleTypeofArrayAccess(
typeQuery: Node,
indexedAccessType: IndexedAccessTypeNode,
context: TypeBoxContext,
): ts.Expression {
const typeQueryNode = typeQuery.asKindOrThrow(ts.SyntaxKind.TypeQuery)
const exprName = typeQueryNode.getExprName()
Expand All @@ -54,10 +56,10 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler {
}

// Fallback to default Index behavior
const typeboxObjectType = getTypeBoxType(typeQuery)
const typeboxIndexType = getTypeBoxType(indexedAccessType.getIndexTypeNode())
const typeboxObjectType = getTypeBoxType(typeQuery, context)
const typeboxIndexType = getTypeBoxType(indexedAccessType.getIndexTypeNode(), context)

return makeTypeCall('Index', [typeboxObjectType, typeboxIndexType])
return GenericTypeUtils.makeTypeCall('Index', [typeboxObjectType, typeboxIndexType])
}

private extractTupleUnion(typeNode: TypeNode | undefined): ts.Expression | null {
Expand Down Expand Up @@ -86,14 +88,18 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler {
if (literal.isKind(ts.SyntaxKind.StringLiteral)) {
const stringLiteral = literal.asKindOrThrow(ts.SyntaxKind.StringLiteral)
const value = stringLiteral.getLiteralValue()
literalTypes.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(value)]))
literalTypes.push(
GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createStringLiteral(value)]),
)
}
}
}

// Return union of literal types if we found any
if (literalTypes.length > 0) {
return makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(literalTypes)])
return GenericTypeUtils.makeTypeCall('Union', [
ts.factory.createArrayLiteralExpression(literalTypes),
])
}
Comment on lines 98 to 103
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Avoid bypassing union chunking in typeof A[number] fast-path

Constructing Type.Union([...]) directly here will skip the new shouldChunkUnion/createChunkNodes flow, defeating the PR’s goal on large const tuples (e.g., >20 elements). Route this union construction through the same union/chunking pipeline (e.g., a small helper that consults shouldChunkUnion and registers chunk nodes via context.nodeGraph), or delegate to the union handler.

Add/adjust an integration test with a large readonly tuple (e.g., 100 string literals) accessed via typeof A[number] and assert chunk types are emitted rather than a monolithic union expression.


🏁 Script executed:

#!/bin/bash
set -e
# Search for chunking utilities
rg -n 'shouldChunkUnion' -C3 || true
rg -n 'createChunkNodes' -C3 || true

# Inspect GenericTypeUtils.makeTypeCall usage
rg -n 'GenericTypeUtils\.makeTypeCall' -C3 || true

# Locate the union handler implementation
rg -n 'handleUnion' -g 'src/handlers/typebox/*-handler.ts' -C3 || true

Length of output: 33962


Pipe union creation through the chunking pipeline in extractTupleUnion

  • Directly calling GenericTypeUtils.makeTypeCall('Union', [...]) here skips shouldChunkUnion/createChunkNodes, so large tuple-indexed unions won’t be chunked.
  • Replace this with a helper that checks shouldChunkUnion(typeNode) and registers chunk nodes via createChunkNodes(typeNode, typeName, context.nodeGraph), or delegate to the existing union handler.
  • Add an integration test with a large readonly tuple (e.g. 100+ string literals) accessed via typeof A[number] and assert that chunked unions are emitted instead of one monolithic union.
🤖 Prompt for AI Agents
In src/handlers/typebox/indexed-access-type-handler.ts around lines 98 to 103,
the code directly returns a GenericTypeUtils.makeTypeCall('Union', [...]) which
bypasses the union chunking pipeline; change this to route union creation
through the chunking logic by checking shouldChunkUnion(typeNode) and, if true,
calling createChunkNodes(typeNode, typeName, context.nodeGraph) (or delegating
to the existing union handler that already performs these checks) so chunk nodes
are registered instead of emitting one large union; update the code path to use
the chunk-aware helper/output from the union handler and add an integration test
that builds a readonly tuple with 100+ string literals accessed via typeof
A[number] asserting that chunked unions are produced rather than a single
monolithic union.

}

Expand Down
12 changes: 6 additions & 6 deletions src/handlers/typebox/keyof-typeof-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
import { Node, ts, TypeOperatorTypeNode, VariableDeclaration } from 'ts-morph'

export class KeyOfTypeofHandler extends BaseTypeHandler {
Expand All @@ -13,13 +13,13 @@ export class KeyOfTypeofHandler extends BaseTypeHandler {

handle(node: TypeOperatorTypeNode): ts.Expression {
const typeQuery = node.getTypeNode()
Comment on lines 14 to 15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handler signature missing context parameter — likely breaks getTypeBoxType caller contract.

getTypeBoxType invokes handlers as handle(node, context). This override lacks the second parameter, which can cause a type error or silent loss of context. Add the context arg (even if unused for now).

-  handle(node: TypeOperatorTypeNode): ts.Expression {
+  handle(node: TypeOperatorTypeNode, _context: unknown): ts.Expression {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
handle(node: TypeOperatorTypeNode): ts.Expression {
const typeQuery = node.getTypeNode()
handle(node: TypeOperatorTypeNode, _context: unknown): ts.Expression {
const typeQuery = node.getTypeNode()
// …rest of implementation…
🤖 Prompt for AI Agents
In src/handlers/typebox/keyof-typeof-handler.ts around lines 14 to 15, the
handle method is missing the second context parameter expected by getTypeBoxType
(which calls handlers as handle(node, context)); update the method signature to
include the context parameter (e.g., handle(node: TypeOperatorTypeNode, context:
HandlerContext)) and propagate or use it as needed (even if unused, keep the
parameter to satisfy the caller contract), and adjust any internal references or
imports so the file compiles with the new signature.

if (!Node.isTypeQuery(typeQuery)) return makeTypeCall('Any')
if (!Node.isTypeQuery(typeQuery)) return GenericTypeUtils.makeTypeCall('Any')

const exprName = typeQuery.getExprName()
if (!Node.isIdentifier(exprName)) return makeTypeCall('Any')
if (!Node.isIdentifier(exprName)) return GenericTypeUtils.makeTypeCall('Any')

const keys = this.getObjectKeys(exprName)
return keys.length > 0 ? this.createUnion(keys) : makeTypeCall('Any')
return keys.length > 0 ? this.createUnion(keys) : GenericTypeUtils.makeTypeCall('Any')
}

private getObjectKeys(node: Node): string[] {
Expand Down Expand Up @@ -80,11 +80,11 @@ export class KeyOfTypeofHandler extends BaseTypeHandler {
? ts.factory.createNumericLiteral(num)
: ts.factory.createStringLiteral(key)

return makeTypeCall('Literal', [literal])
return GenericTypeUtils.makeTypeCall('Literal', [literal])
})

return literals.length === 1
? literals[0]!
: makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(literals)])
: GenericTypeUtils.makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(literals)])
}
}
16 changes: 9 additions & 7 deletions src/handlers/typebox/literal-type-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler'
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
import { LiteralTypeNode, Node, SyntaxKind, ts } from 'ts-morph'

export class LiteralTypeHandler extends BaseTypeHandler {
Expand All @@ -12,19 +12,21 @@ export class LiteralTypeHandler extends BaseTypeHandler {

switch (literal.getKind()) {
case SyntaxKind.StringLiteral:
return makeTypeCall('Literal', [
return GenericTypeUtils.makeTypeCall('Literal', [
ts.factory.createStringLiteral(literal.getText().slice(1, -1)),
])
case SyntaxKind.NumericLiteral:
return makeTypeCall('Literal', [ts.factory.createNumericLiteral(literal.getText())])
return GenericTypeUtils.makeTypeCall('Literal', [
ts.factory.createNumericLiteral(literal.getText()),
])
case SyntaxKind.TrueKeyword:
return makeTypeCall('Literal', [ts.factory.createTrue()])
return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createTrue()])
case SyntaxKind.FalseKeyword:
return makeTypeCall('Literal', [ts.factory.createFalse()])
return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createFalse()])
case SyntaxKind.NullKeyword:
return makeTypeCall('Null')
return GenericTypeUtils.makeTypeCall('Null')
default:
return makeTypeCall('Any')
return GenericTypeUtils.makeTypeCall('Any')
}
Comment on lines 13 to 30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Support BigInt and negative numeric literal types

BigInt (123n) and negative numbers (-1, -123n) currently fall through to Any. Add explicit handling.

     switch (literal.getKind()) {
       case SyntaxKind.StringLiteral:
         return GenericTypeUtils.makeTypeCall('Literal', [
           ts.factory.createStringLiteral(literal.getText().slice(1, -1)),
         ])
       case SyntaxKind.NumericLiteral:
         return GenericTypeUtils.makeTypeCall('Literal', [
           ts.factory.createNumericLiteral(literal.getText()),
         ])
+      case SyntaxKind.BigIntLiteral:
+        return GenericTypeUtils.makeTypeCall('Literal', [
+          ts.factory.createBigIntLiteral(literal.getText()),
+        ])
+      case SyntaxKind.PrefixUnaryExpression: {
+        if (Node.isPrefixUnaryExpression(literal)) {
+          const operand = literal.getOperand()
+          const op = literal.getOperatorToken()
+          if (op === SyntaxKind.MinusToken && Node.isNumericLiteral(operand)) {
+            const expr = ts.factory.createPrefixUnaryExpression(
+              ts.SyntaxKind.MinusToken,
+              ts.factory.createNumericLiteral(operand.getText()),
+            )
+            return GenericTypeUtils.makeTypeCall('Literal', [expr])
+          }
+          if (op === SyntaxKind.MinusToken && Node.isBigIntLiteral(operand)) {
+            const expr = ts.factory.createPrefixUnaryExpression(
+              ts.SyntaxKind.MinusToken,
+              ts.factory.createBigIntLiteral(operand.getText()),
+            )
+            return GenericTypeUtils.makeTypeCall('Literal', [expr])
+          }
+        }
+        break
+      }
       case SyntaxKind.TrueKeyword:
         return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createTrue()])
       case SyntaxKind.FalseKeyword:
         return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createFalse()])
       case SyntaxKind.NullKeyword:
         return GenericTypeUtils.makeTypeCall('Null')
       default:
         return GenericTypeUtils.makeTypeCall('Any')
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
switch (literal.getKind()) {
case SyntaxKind.StringLiteral:
return makeTypeCall('Literal', [
return GenericTypeUtils.makeTypeCall('Literal', [
ts.factory.createStringLiteral(literal.getText().slice(1, -1)),
])
case SyntaxKind.NumericLiteral:
return makeTypeCall('Literal', [ts.factory.createNumericLiteral(literal.getText())])
return GenericTypeUtils.makeTypeCall('Literal', [
ts.factory.createNumericLiteral(literal.getText()),
])
case SyntaxKind.TrueKeyword:
return makeTypeCall('Literal', [ts.factory.createTrue()])
return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createTrue()])
case SyntaxKind.FalseKeyword:
return makeTypeCall('Literal', [ts.factory.createFalse()])
return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createFalse()])
case SyntaxKind.NullKeyword:
return makeTypeCall('Null')
return GenericTypeUtils.makeTypeCall('Null')
default:
return makeTypeCall('Any')
return GenericTypeUtils.makeTypeCall('Any')
}
switch (literal.getKind()) {
case SyntaxKind.StringLiteral:
return GenericTypeUtils.makeTypeCall('Literal', [
ts.factory.createStringLiteral(literal.getText().slice(1, -1)),
])
case SyntaxKind.NumericLiteral:
return GenericTypeUtils.makeTypeCall('Literal', [
ts.factory.createNumericLiteral(literal.getText()),
])
case SyntaxKind.BigIntLiteral:
return GenericTypeUtils.makeTypeCall('Literal', [
ts.factory.createBigIntLiteral(literal.getText()),
])
case SyntaxKind.PrefixUnaryExpression: {
if (Node.isPrefixUnaryExpression(literal)) {
const operand = literal.getOperand()
const op = literal.getOperatorToken()
if (op === SyntaxKind.MinusToken && Node.isNumericLiteral(operand)) {
const expr = ts.factory.createPrefixUnaryExpression(
ts.SyntaxKind.MinusToken,
ts.factory.createNumericLiteral(operand.getText()),
)
return GenericTypeUtils.makeTypeCall('Literal', [expr])
}
if (op === SyntaxKind.MinusToken && Node.isBigIntLiteral(operand)) {
const expr = ts.factory.createPrefixUnaryExpression(
ts.SyntaxKind.MinusToken,
ts.factory.createBigIntLiteral(operand.getText()),
)
return GenericTypeUtils.makeTypeCall('Literal', [expr])
}
}
break
}
case SyntaxKind.TrueKeyword:
return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createTrue()])
case SyntaxKind.FalseKeyword:
return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createFalse()])
case SyntaxKind.NullKeyword:
return GenericTypeUtils.makeTypeCall('Null')
default:
return GenericTypeUtils.makeTypeCall('Any')
}
🤖 Prompt for AI Agents
In src/handlers/typebox/literal-type-handler.ts around lines 13 to 30, add
explicit handling for BigIntLiteral and for negative numeric/BigInt literals
(which appear as PrefixUnaryExpression with a Minus operator) so they no longer
fall through to Any: add a case for SyntaxKind.BigIntLiteral that returns
GenericTypeUtils.makeTypeCall('Literal',
[ts.factory.createBigIntLiteral(literal.getText())]), and add a case for
SyntaxKind.PrefixUnaryExpression that checks if the operator is Minus and the
operand is a NumericLiteral or BigIntLiteral — for NumericLiteral return a
Literal with ts.factory.createNumericLiteral('-' + operand.getText()), and for
BigIntLiteral return a Literal with ts.factory.createBigIntLiteral('-' +
operand.getText()); leave other branches unchanged.

}
}
17 changes: 10 additions & 7 deletions src/handlers/typebox/object/interface-type-handler.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { ObjectLikeBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/object/object-like-base-handler'
import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils'
import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils'
import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call'
import { HeritageClause, InterfaceDeclaration, Node, ts } from 'ts-morph'

export class InterfaceTypeHandler extends ObjectLikeBaseHandler {
canHandle(node: Node): boolean {
return Node.isInterfaceDeclaration(node)
}

handle(node: InterfaceDeclaration): ts.Expression {
handle(node: InterfaceDeclaration, context: TypeBoxContext): ts.Expression {
const heritageClauses = node.getHeritageClauses()
const baseObjectType = this.createObjectType(this.processProperties(node.getProperties()))
const baseObjectType = this.createObjectType(
this.processProperties(node.getProperties(), context),
)

if (heritageClauses.length === 0) return baseObjectType

Expand All @@ -20,7 +23,7 @@ export class InterfaceTypeHandler extends ObjectLikeBaseHandler {
const allTypes = [...extendedTypes, baseObjectType]
const expression = ts.factory.createArrayLiteralExpression(allTypes, true)

return makeTypeCall('Composite', [expression])
return GenericTypeUtils.makeTypeCall('Composite', [expression])
}

private parseGenericTypeCall(typeText: string): ts.Expression | null {
Expand All @@ -42,11 +45,11 @@ export class InterfaceTypeHandler extends ObjectLikeBaseHandler {
// Convert common TypeScript types to TypeBox calls
switch (typeArg) {
case 'number':
return makeTypeCall('Number')
return GenericTypeUtils.makeTypeCall('Number')
case 'string':
return makeTypeCall('String')
return GenericTypeUtils.makeTypeCall('String')
case 'boolean':
return makeTypeCall('Boolean')
return GenericTypeUtils.makeTypeCall('Boolean')
default:
// For other types, assume it's a reference
return ts.factory.createIdentifier(typeArg)
Expand Down
Loading