diff --git a/.gitignore b/.gitignore
index 9cfafcf..babdf26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,4 +33,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Finder (MacOS) folder config
.DS_Store
-tests/integration/wikibase/output.ts
+samples
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 7845001..80e113a 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -163,15 +163,11 @@ const result = await generateCode({
## Utility Functions and Modules
-- ****: This module contains the core logic for converting TypeScript type nodes into TypeBox `Type` expressions. Its primary function, `getTypeBoxType`, takes a TypeScript `TypeNode` as input and returns a `ts.Node` representing the equivalent TypeBox schema. This is a crucial part of the transformation process, handling various TypeScript types like primitives, arrays, objects, and unions.
-
-- ****: This utility function is responsible for generating and adding the `export type [TypeName] = Static` declaration to the output source file. This declaration is essential for enabling TypeScript's static type inference from the dynamically generated TypeBox schemas, ensuring type safety at compile time.
-
-- ****: This file likely contains general utility functions that support the TypeBox code generation process, such as helper functions for string manipulation or AST node creation.
-
-- ****: This module is responsible for parsing TypeScript source code and extracting relevant Abstract Syntax Tree (AST) information. It might provide functions to navigate the AST and identify specific nodes like type aliases, interfaces, or enums.
-
-- ****: This file likely defines custom types or interfaces that represent the structured AST information extracted by `typescript-ast-parser.ts`, providing a consistent data model for further processing.
+- : Contains the core logic for converting TypeScript type nodes into TypeBox `Type` expressions. `getTypeBoxType` takes a `TypeNode` as input and returns a `ts.Node` representing the equivalent TypeBox schema.
+- : Generates and adds the `export type [TypeName] = Static` declaration to the output source file. This declaration is essential for enabling TypeScript's static type inference from the dynamically generated TypeBox schemas, ensuring type safety at compile time.
+- : Contains general utility functions that support the TypeBox code generation process, such as helper functions for string manipulation or AST node creation.
+- : Responsible for parsing TypeScript source code and extracting relevant Abstract Syntax Tree (AST) information. It provides functions to navigate the AST and identify specific nodes like type aliases, interfaces, or enums.
+- : Defines custom types and interfaces that represent the structured AST information extracted by `typescript-ast-parser.ts`, providing a consistent data model for further processing.
### Handlers Directory
@@ -189,13 +185,13 @@ This directory contains a collection of specialized handler modules, each respon
- : Handles TypeScript `Record` utility types.
- : Handles basic TypeScript types like `string`, `number`, `boolean`, `null`, `undefined`, `any`, `unknown`, `void`.
- : Handles TypeScript function types and function declarations, including parameter types, optional parameters, and return types.
-- : Handles TypeScript template literal types (e.g., `` `hello-${string}` ``).
+- : Handles TypeScript template literal types (e.g., `` `hello-${string}` ``). Parses template literals into components, handling literal text, embedded types (string, number, unions), and string/numeric literals.
- : Handles TypeScript `typeof` expressions for extracting types from values.
- : Handles TypeScript tuple types.
- : Handles TypeScript type operators like `keyof`, `typeof`.
- : Handles references to other types (e.g., `MyType`).
- : A generic handler for TypeBox types.
-- : This file likely orchestrates the use of the individual type handlers, acting as a dispatcher based on the type of AST node encountered.
+- : Orchestrates the use of the individual type handlers, acting as a dispatcher based on the type of AST node encountered.
- : Handles TypeScript union types (e.g., `string | number`).
### Parsers Directory
@@ -273,7 +269,7 @@ The optimizations maintain full backward compatibility and test reliability whil
### Performance Testing
-To ensure the dependency collection system performs efficiently under various scenarios, comprehensive performance tests have been implemented in . These tests specifically target potential bottlenecks in dependency collection and import processing.
+To ensure the dependency collection system performs efficiently under various scenarios, comprehensive performance tests have been implemented in . These tests specifically target potential bottlenecks in dependency collection and import processing.
## Process Overview
@@ -303,29 +299,27 @@ The project uses Bun as the test runner. Here are the key commands for running t
bun test
# Run a specific test file
-bun test tests/ts-morph/function-types.test.ts
+bun test tests/handlers/typebox/function-types.test.ts
# Run tests in a specific directory
-bun test tests/ts-morph/
+bun test tests/
```
### TDD Workflow for New Features
When implementing new type handlers or features:
-1. **Start with Tests**: Create test cases in the appropriate test file (e.g., `tests/ts-morph/function-types.test.ts` for function-related features)
-2. **Run Tests First**: Execute `bun test tests/ts-morph/[test-file]` to confirm tests fail as expected
+1. **Start with Tests**: Create test cases in the appropriate test file (e.g., `tests/handlers/typebox/function-types.test.ts` for function-related features)
+2. **Run Tests First**: Execute `bun test tests/handlers/typebox/[test-file]` to confirm tests fail as expected
3. **Implement Handler**: Create or modify the type handler to make tests pass
4. **Verify Implementation**: Run tests again to ensure they pass
5. **Integration Testing**: Run the full test suite with `bun test` to ensure no regressions
-6. **Manual Verification**: Test with integration examples like `tests/integration/wikibase/wikibase.ts`
### Test Organization
Tests are organized into several categories:
-- **Unit Tests** (`tests/ts-morph/`): Test individual type handlers and parsers
-- **Integration Tests** (`tests/integration/`): Test end-to-end functionality with real-world examples
+- **Unit Tests** (`tests/handlers/`): Test individual type handlers and parsers
- **Performance Tests**: Validate performance characteristics of complex operations
### Best Practices
@@ -337,8 +331,8 @@ Tests are organized into several categories:
- Include edge cases and error conditions
- Run specific tests frequently during development
- Run the full test suite before committing changes
-- Run any specific tests with path like `bun test tests/ts-morph/function-types.test.ts`
-- Run any specific test cases using command like `bun test tests/ts-morph/function-types.test.ts -t function types`
+- Run any specific tests with path like `bun test tests/handlers/typebox/function-types.test.ts`
+- Run any specific test cases using command like `bun test tests/handlers/typebox/function-types.test.ts -t function types`
- If tests keep failing, take help from tsc, lint commands to detect for any issues
## Documentation Guidelines
diff --git a/src/handlers/typebox/template-literal-type-handler.ts b/src/handlers/typebox/template-literal-type-handler.ts
index afb5187..bd327c9 100644
--- a/src/handlers/typebox/template-literal-type-handler.ts
+++ b/src/handlers/typebox/template-literal-type-handler.ts
@@ -16,12 +16,78 @@ export class TemplateLiteralTypeHandler extends BaseTypeHandler {
return makeTypeCall('Any')
}
- // For template literal types, we'll convert them to string patterns
- // This is a simplified approach - TypeBox supports template literals with T.TemplateLiteral
- const templateText = typeNode.getText()
+ const parts: ts.Expression[] = []
- // For simple cases like `Q${number}`, we can represent as a string pattern
- // In a more complete implementation, we might parse the template parts
- return makeTypeCall('TemplateLiteral', [ts.factory.createStringLiteral(templateText)])
+ // Add the head part (literal string before first substitution)
+ const head = typeNode.getHead()
+ const headCompilerNode = head.compilerNode as ts.TemplateHead
+ const headText = headCompilerNode.text
+ if (headText) {
+ parts.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(headText)]))
+ }
+
+ // Process template spans (substitutions + following literal parts)
+ const templateSpans = typeNode.getTemplateSpans()
+ for (const span of templateSpans) {
+ // Access the compiler node to get type and literal
+ const compilerNode = span.compilerNode as ts.TemplateLiteralTypeSpan
+
+ // Add the type from the substitution
+ if (compilerNode.type) {
+ // Handle common type cases directly
+ const typeKind = compilerNode.type.kind
+ if (typeKind === ts.SyntaxKind.StringKeyword) {
+ parts.push(makeTypeCall('String'))
+ } else if (typeKind === ts.SyntaxKind.NumberKeyword) {
+ parts.push(makeTypeCall('Number'))
+ } else if (typeKind === ts.SyntaxKind.LiteralType) {
+ // Handle literal types (e.g., 'A', 42, true)
+ const literalType = compilerNode.type as ts.LiteralTypeNode
+ if (ts.isStringLiteral(literalType.literal)) {
+ parts.push(
+ makeTypeCall('Literal', [ts.factory.createStringLiteral(literalType.literal.text)]),
+ )
+ } else if (ts.isNumericLiteral(literalType.literal)) {
+ parts.push(
+ makeTypeCall('Literal', [ts.factory.createNumericLiteral(literalType.literal.text)]),
+ )
+ } else {
+ parts.push(makeTypeCall('String')) // fallback for other literals
+ }
+ } else if (typeKind === ts.SyntaxKind.UnionType) {
+ // For union types, we need to handle each type in the union
+ const unionType = compilerNode.type as ts.UnionTypeNode
+ const unionParts = unionType.types.map((t) => {
+ if (t.kind === ts.SyntaxKind.LiteralType) {
+ const literalType = t as ts.LiteralTypeNode
+ if (ts.isStringLiteral(literalType.literal)) {
+ return makeTypeCall('Literal', [
+ ts.factory.createStringLiteral(literalType.literal.text),
+ ])
+ }
+ }
+ return makeTypeCall('String') // fallback
+ })
+ parts.push(makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(unionParts)]))
+ } else {
+ // Fallback for other types
+ parts.push(makeTypeCall('String'))
+ }
+ }
+
+ // Add the literal part after the substitution
+ const literalText = compilerNode.literal?.text
+ if (literalText) {
+ parts.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(literalText)]))
+ }
+ }
+
+ // If no parts were found, fallback to a simple string
+ if (parts.length === 0) {
+ return makeTypeCall('String')
+ }
+
+ // Return TemplateLiteral with array of parts
+ return makeTypeCall('TemplateLiteral', [ts.factory.createArrayLiteralExpression(parts)])
}
}
diff --git a/src/input-handler.ts b/src/input-handler.ts
index e92eb1f..1e90c0f 100644
--- a/src/input-handler.ts
+++ b/src/input-handler.ts
@@ -10,11 +10,9 @@ export interface InputOptions {
}
const hasRelativeImports = (sourceFile: SourceFile): boolean => {
- const hasRelativeImports = sourceFile
+ return sourceFile
.getImportDeclarations()
.some((importDeclaration) => importDeclaration.isModuleSpecifierRelative())
-
- return hasRelativeImports
}
const resolveFilePath = (input: string, callerFile?: string): string => {
@@ -42,9 +40,9 @@ const resolveFilePath = (input: string, callerFile?: string): string => {
if (existingPaths.length > 1) {
throw new Error(
- `Multiple resolutions found for path: ${input}. '` +
- `Found: ${existingPaths.join(', ')}. ' +
- 'Please provide a more specific path.`,
+ `Multiple resolutions found for path: ${input}. ` +
+ `Found: ${existingPaths.join(', ')}. ` +
+ 'Please provide a more specific path.',
)
}
@@ -73,7 +71,10 @@ export const createSourceFileFromInput = (options: InputOptions): SourceFile =>
// If callerFile is provided, it means this code came from an existing SourceFile
// and relative imports should be allowed
- const sourceFile = project.createSourceFile('temp.ts', sourceCode)
+ const virtualPath = callerFile ? resolve(dirname(callerFile), '__virtual__.ts') : 'temp.ts'
+ const sourceFile = project.createSourceFile(virtualPath, sourceCode, {
+ overwrite: true,
+ })
if (!callerFile && hasRelativeImports(sourceFile)) {
throw new Error(
@@ -83,9 +84,7 @@ export const createSourceFileFromInput = (options: InputOptions): SourceFile =>
)
}
- const virtualPath = callerFile ? resolve(dirname(callerFile), '__virtual__.ts') : 'temp.ts'
-
- return project.createSourceFile(virtualPath, sourceCode, { overwrite: true })
+ return sourceFile
}
if (filePath) {
diff --git a/src/ts-morph-codegen.ts b/src/ts-morph-codegen.ts
index 31bed13..0a9388a 100644
--- a/src/ts-morph-codegen.ts
+++ b/src/ts-morph-codegen.ts
@@ -6,10 +6,7 @@ import { EnumParser } from '@daxserver/validation-schema-codegen/parsers/parse-e
import { FunctionDeclarationParser } from '@daxserver/validation-schema-codegen/parsers/parse-function-declarations'
import { InterfaceParser } from '@daxserver/validation-schema-codegen/parsers/parse-interfaces'
import { TypeAliasParser } from '@daxserver/validation-schema-codegen/parsers/parse-type-aliases'
-import {
- DependencyCollector,
- type TypeDependency,
-} from '@daxserver/validation-schema-codegen/utils/dependency-collector'
+import { DependencyCollector } from '@daxserver/validation-schema-codegen/utils/dependency-collector'
import { Project, ts } from 'ts-morph'
export interface GenerateCodeOptions extends InputOptions {
@@ -61,36 +58,23 @@ export const generateCode = async ({
const importDeclarations = sourceFile.getImportDeclarations()
const localTypeAliases = sourceFile.getTypeAliases()
- let orderedDependencies: TypeDependency[]
- if (exportEverything) {
- // When exporting everything, maintain original order
- orderedDependencies = dependencyCollector.collectFromImports(
- importDeclarations,
- exportEverything,
- )
- dependencyCollector.addLocalTypes(localTypeAliases, sourceFile)
- } else {
- // When not exporting everything, add local types first so filtering can detect their dependencies
- dependencyCollector.addLocalTypes(localTypeAliases, sourceFile)
- orderedDependencies = dependencyCollector.collectFromImports(
- importDeclarations,
- exportEverything,
- )
- }
+ // Always add local types first so they can be included in topological sort
+ dependencyCollector.addLocalTypes(localTypeAliases, sourceFile)
- // Process all dependencies in topological order
+ const orderedDependencies = dependencyCollector.collectFromImports(
+ importDeclarations,
+ exportEverything,
+ )
+
+ // Process all dependencies (both imported and local) in topological order
for (const dependency of orderedDependencies) {
if (!processedTypes.has(dependency.typeAlias.getName())) {
typeAliasParser.parseWithImportFlag(dependency.typeAlias, dependency.isImported)
}
}
- // Process local types
+ // Process any remaining local types that weren't included in the dependency graph
if (exportEverything) {
- for (const typeAlias of localTypeAliases) {
- typeAliasParser.parseWithImportFlag(typeAlias, false)
- }
- } else {
for (const typeAlias of localTypeAliases) {
if (!processedTypes.has(typeAlias.getName())) {
typeAliasParser.parseWithImportFlag(typeAlias, false)
diff --git a/tests/ts-morph/dependency-collector.integration.test.ts b/tests/dependency-collector.integration.test.ts
similarity index 99%
rename from tests/ts-morph/dependency-collector.integration.test.ts
rename to tests/dependency-collector.integration.test.ts
index ddeb1ba..1a8bcab 100644
--- a/tests/ts-morph/dependency-collector.integration.test.ts
+++ b/tests/dependency-collector.integration.test.ts
@@ -1,5 +1,5 @@
import { DependencyCollector } from '@daxserver/validation-schema-codegen/utils/dependency-collector'
-import { createSourceFile } from '@test-fixtures/ts-morph/utils'
+import { createSourceFile } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/ts-morph/dependency-collector.performance.test.ts b/tests/dependency-collector.performance.test.ts
similarity index 99%
rename from tests/ts-morph/dependency-collector.performance.test.ts
rename to tests/dependency-collector.performance.test.ts
index d7dcd99..44e8099 100644
--- a/tests/ts-morph/dependency-collector.performance.test.ts
+++ b/tests/dependency-collector.performance.test.ts
@@ -1,5 +1,5 @@
import { DependencyCollector } from '@daxserver/validation-schema-codegen/utils/dependency-collector'
-import { createSourceFile } from '@test-fixtures/ts-morph/utils'
+import { createSourceFile } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/ts-morph/dependency-collector.unit.test.ts b/tests/dependency-collector.unit.test.ts
similarity index 100%
rename from tests/ts-morph/dependency-collector.unit.test.ts
rename to tests/dependency-collector.unit.test.ts
diff --git a/tests/dependency-ordering.test.ts b/tests/dependency-ordering.test.ts
new file mode 100644
index 0000000..7e31fec
--- /dev/null
+++ b/tests/dependency-ordering.test.ts
@@ -0,0 +1,92 @@
+import { formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
+import { beforeEach, describe, expect, test } from 'bun:test'
+import { Project } from 'ts-morph'
+
+describe('Dependency Ordering Bug', () => {
+ let project: Project
+
+ beforeEach(() => {
+ project = new Project()
+ })
+
+ test('should define StringSnakDataValue before using it', () => {
+ const sourceFile = project.createSourceFile(
+ 'test.ts',
+ `
+ export type CommonsMediaSnakDataValue = StringSnakDataValue;
+ export type StringSnakDataValue = {
+ value: string;
+ type: 'string';
+ };
+ `,
+ )
+
+ expect(generateFormattedCode(sourceFile, true)).resolves.toBe(
+ formatWithPrettier(`
+ export const StringSnakDataValue = Type.Object({
+ value: Type.String(),
+ type: Type.Literal('string'),
+ });
+
+ export type StringSnakDataValue = Static;
+
+ export const CommonsMediaSnakDataValue = StringSnakDataValue;
+
+ export type CommonsMediaSnakDataValue = Static;
+ `),
+ )
+ })
+
+ test('should handle complex dependency chains correctly', () => {
+ const sourceFile = project.createSourceFile(
+ 'test.ts',
+ `
+ export type CommonsMediaSnakDataValue = StringSnakDataValue;
+ export type ExternalIdSnakDataValue = StringSnakDataValue;
+ export type GeoShapeSnakDataValue = StringSnakDataValue;
+ export type StringSnakDataValue = {
+ value: string;
+ type: 'string';
+ };
+ export type DataValueByDataType = {
+ 'string': StringSnakDataValue;
+ 'commonsMedia': CommonsMediaSnakDataValue;
+ 'external-id': ExternalIdSnakDataValue;
+ 'geo-shape': GeoShapeSnakDataValue;
+ };
+ `,
+ )
+
+ expect(generateFormattedCode(sourceFile, true)).resolves.toBe(
+ formatWithPrettier(`
+ export const StringSnakDataValue = Type.Object({
+ value: Type.String(),
+ type: Type.Literal('string'),
+ });
+
+ export type StringSnakDataValue = Static;
+
+ export const CommonsMediaSnakDataValue = StringSnakDataValue;
+
+ export type CommonsMediaSnakDataValue = Static;
+
+ export const ExternalIdSnakDataValue = StringSnakDataValue;
+
+ export type ExternalIdSnakDataValue = Static;
+
+ export const GeoShapeSnakDataValue = StringSnakDataValue;
+
+ export type GeoShapeSnakDataValue = Static;
+
+ export const DataValueByDataType = Type.Object({
+ "'string'": StringSnakDataValue,
+ "'commonsMedia'": CommonsMediaSnakDataValue,
+ "'external-id'": ExternalIdSnakDataValue,
+ "'geo-shape'": GeoShapeSnakDataValue,
+ });
+
+ export type DataValueByDataType = Static;
+ `),
+ )
+ })
+})
diff --git a/tests/ts-morph/export-everything.test.ts b/tests/export-everything.test.ts
similarity index 99%
rename from tests/ts-morph/export-everything.test.ts
rename to tests/export-everything.test.ts
index bdd2fd0..37f66c9 100644
--- a/tests/ts-morph/export-everything.test.ts
+++ b/tests/export-everything.test.ts
@@ -1,4 +1,4 @@
-import { formatWithPrettier, generateFormattedCode } from '@test-fixtures/ts-morph/utils'
+import { formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
import { beforeEach, describe, expect, it } from 'bun:test'
import { Project } from 'ts-morph'
@@ -73,13 +73,13 @@ describe('exportEverything flag', () => {
expect(generateFormattedCode(sourceFile, true)).resolves.toBe(
formatWithPrettier(`
- export const UnusedImportedType = Type.Number();
-
- export type UnusedImportedType = Static;
-
export const MyType = Type.String();
export type MyType = Static;
+
+ export const UnusedImportedType = Type.Number();
+
+ export type UnusedImportedType = Static;
`),
)
})
diff --git a/tests/handlers/typebox/advanced-types.test.ts b/tests/handlers/typebox/advanced-types.test.ts
new file mode 100644
index 0000000..e558687
--- /dev/null
+++ b/tests/handlers/typebox/advanced-types.test.ts
@@ -0,0 +1,51 @@
+import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
+import { beforeEach, describe, expect, test } from 'bun:test'
+import { Project } from 'ts-morph'
+
+describe('Advanced types', () => {
+ let project: Project
+
+ beforeEach(() => {
+ project = new Project()
+ })
+
+ describe('Typeof expressions', () => {
+ test('typeof variable', () => {
+ const sourceFile = createSourceFile(
+ project,
+ `
+ const myVar = { x: 1, y: 'hello' }
+ type A = typeof myVar
+ `,
+ )
+
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(`
+ const A = myVar;
+
+ type A = Static;
+ `),
+ )
+ })
+
+ test('typeof with qualified name', () => {
+ const sourceFile = createSourceFile(
+ project,
+ `
+ namespace MyNamespace {
+ export const config = { port: 3000 }
+ }
+ type A = typeof MyNamespace.config
+ `,
+ )
+
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(`
+ const A = MyNamespace_config;
+
+ type A = Static;
+ `),
+ )
+ })
+ })
+})
diff --git a/tests/ts-morph/array-types.test.ts b/tests/handlers/typebox/array-types.test.ts
similarity index 97%
rename from tests/ts-morph/array-types.test.ts
rename to tests/handlers/typebox/array-types.test.ts
index da3b59f..f90d817 100644
--- a/tests/ts-morph/array-types.test.ts
+++ b/tests/handlers/typebox/array-types.test.ts
@@ -1,8 +1,4 @@
-import {
- createSourceFile,
- formatWithPrettier,
- generateFormattedCode,
-} from '@test-fixtures/ts-morph/utils'
+import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/ts-morph/enum-types.test.ts b/tests/handlers/typebox/enum-types.test.ts
similarity index 94%
rename from tests/ts-morph/enum-types.test.ts
rename to tests/handlers/typebox/enum-types.test.ts
index 48d6c12..15fe5a1 100644
--- a/tests/ts-morph/enum-types.test.ts
+++ b/tests/handlers/typebox/enum-types.test.ts
@@ -1,8 +1,4 @@
-import {
- createSourceFile,
- formatWithPrettier,
- generateFormattedCode,
-} from '@test-fixtures/ts-morph/utils'
+import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/ts-morph/function-types.test.ts b/tests/handlers/typebox/function-types.test.ts
similarity index 97%
rename from tests/ts-morph/function-types.test.ts
rename to tests/handlers/typebox/function-types.test.ts
index 260c0cf..d8068ca 100644
--- a/tests/ts-morph/function-types.test.ts
+++ b/tests/handlers/typebox/function-types.test.ts
@@ -1,8 +1,4 @@
-import {
- createSourceFile,
- formatWithPrettier,
- generateFormattedCode,
-} from '@test-fixtures/ts-morph/utils'
+import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/ts-morph/object-types.test.ts b/tests/handlers/typebox/object-types.test.ts
similarity index 95%
rename from tests/ts-morph/object-types.test.ts
rename to tests/handlers/typebox/object-types.test.ts
index 0524fdc..cc347bf 100644
--- a/tests/ts-morph/object-types.test.ts
+++ b/tests/handlers/typebox/object-types.test.ts
@@ -1,8 +1,4 @@
-import {
- createSourceFile,
- formatWithPrettier,
- generateFormattedCode,
-} from '@test-fixtures/ts-morph/utils'
+import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/ts-morph/primitive-types.test.ts b/tests/handlers/typebox/primitive-types.test.ts
similarity index 97%
rename from tests/ts-morph/primitive-types.test.ts
rename to tests/handlers/typebox/primitive-types.test.ts
index c772bf1..1103468 100644
--- a/tests/ts-morph/primitive-types.test.ts
+++ b/tests/handlers/typebox/primitive-types.test.ts
@@ -1,8 +1,4 @@
-import {
- createSourceFile,
- formatWithPrettier,
- generateFormattedCode,
-} from '@test-fixtures/ts-morph/utils'
+import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/handlers/typebox/template-literal-types.test.ts b/tests/handlers/typebox/template-literal-types.test.ts
new file mode 100644
index 0000000..1186f53
--- /dev/null
+++ b/tests/handlers/typebox/template-literal-types.test.ts
@@ -0,0 +1,95 @@
+import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
+import { beforeEach, describe, expect, test } from 'bun:test'
+import { Project } from 'ts-morph'
+
+describe('Template literals', () => {
+ let project: Project
+
+ beforeEach(() => {
+ project = new Project()
+ })
+
+ test('one string', () => {
+ const sourceFile = createSourceFile(project, "type T = `${'A'}`;")
+
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(`
+ const T = Type.TemplateLiteral([Type.Literal('A')]);
+
+ type T = Static;
+ `),
+ )
+ })
+
+ test('multiple strings', () => {
+ const sourceFile = createSourceFile(project, "type T = `${'A'|'B'}`;")
+
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(`
+ const T = Type.TemplateLiteral([
+ Type.Union([Type.Literal('A'), Type.Literal('B')])
+ ]);
+
+ type T = Static;
+ `),
+ )
+ })
+
+ test('concatenated with literal at start', () => {
+ const sourceFile = createSourceFile(project, "type T = `${'A'|'B'}prop`;")
+
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(`
+ const T = Type.TemplateLiteral([
+ Type.Union([Type.Literal('A'), Type.Literal('B')]),
+ Type.Literal('prop'),
+ ]);
+
+ type T = Static;
+ `),
+ )
+ })
+
+ test('concatenated with literal at end', () => {
+ const sourceFile = createSourceFile(project, "type T = `prop${'A'|'B'}`;")
+
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(`
+ const T = Type.TemplateLiteral([
+ Type.Literal('prop'),
+ Type.Union([Type.Literal('A'), Type.Literal('B')]),
+ ]);
+
+ type T = Static;
+ `),
+ )
+ })
+
+ test('concatenated with numeric type', () => {
+ const sourceFile = createSourceFile(project, 'type T = `prop${number}`;')
+
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(`
+ const T = Type.TemplateLiteral([Type.Literal('prop'), Type.Number()]);
+
+ type T = Static;
+ `),
+ )
+ })
+
+ test('concatenation before and after', () => {
+ const sourceFile = createSourceFile(project, 'type A = `prefix-${string}-suffix`')
+
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(`
+ const A = Type.TemplateLiteral([
+ Type.Literal("prefix-"),
+ Type.String(),
+ Type.Literal("-suffix"),
+ ]);
+
+ type A = Static;
+ `),
+ )
+ })
+})
diff --git a/tests/ts-morph/utility-types.test.ts b/tests/handlers/typebox/utility-types.test.ts
similarity index 98%
rename from tests/ts-morph/utility-types.test.ts
rename to tests/handlers/typebox/utility-types.test.ts
index 083bb00..9a7c516 100644
--- a/tests/ts-morph/utility-types.test.ts
+++ b/tests/handlers/typebox/utility-types.test.ts
@@ -1,8 +1,4 @@
-import {
- createSourceFile,
- formatWithPrettier,
- generateFormattedCode,
-} from '@test-fixtures/ts-morph/utils'
+import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/ts-morph/import-resolution.test.ts b/tests/import-resolution.test.ts
similarity index 99%
rename from tests/ts-morph/import-resolution.test.ts
rename to tests/import-resolution.test.ts
index 06dee6d..173e343 100644
--- a/tests/ts-morph/import-resolution.test.ts
+++ b/tests/import-resolution.test.ts
@@ -1,9 +1,5 @@
import { DependencyCollector } from '@daxserver/validation-schema-codegen/utils/dependency-collector'
-import {
- createSourceFile,
- formatWithPrettier,
- generateFormattedCode,
-} from '@test-fixtures/ts-morph/utils'
+import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/ts-morph/input-handler.test.ts b/tests/input-handler.test.ts
similarity index 100%
rename from tests/ts-morph/input-handler.test.ts
rename to tests/input-handler.test.ts
diff --git a/tests/integration/wikibase/wikibase.ts b/tests/integration/wikibase/wikibase.ts
deleted file mode 100644
index e52af05..0000000
--- a/tests/integration/wikibase/wikibase.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { generateCode } from '@daxserver/validation-schema-codegen/ts-morph-codegen'
-import { Project } from 'ts-morph'
-
-// Read the Wikibase SDK entity type definitions
-const project = new Project()
-
-// Read the Wikibase SDK entity type definitions
-const sourceFile = project.createSourceFile('wikibase.ts', 'import { Entity } from "wikibase-sdk"')
-
-// Generate TypeBox code
-const typeboxCode = await generateCode({
- sourceCode: sourceFile.getFullText(),
- exportEverything: true,
- callerFile: sourceFile.getFilePath(),
-})
-
-// Write the generated code to the output file
-const outputPath = `${__dirname}/output.ts`
-await Bun.write(outputPath, typeboxCode)
-console.log(`TypeBox schemas generated at ${outputPath}`)
diff --git a/tests/ts-morph/advanced-types.test.ts b/tests/ts-morph/advanced-types.test.ts
deleted file mode 100644
index a394dcb..0000000
--- a/tests/ts-morph/advanced-types.test.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import {
- createSourceFile,
- formatWithPrettier,
- generateFormattedCode,
-} from '@test-fixtures/ts-morph/utils'
-import { beforeEach, describe, expect, test } from 'bun:test'
-import { Project } from 'ts-morph'
-
-describe('Advanced types', () => {
- let project: Project
-
- beforeEach(() => {
- project = new Project()
- })
-
- describe('Template literal types', () => {
- test('simple template literal', () => {
- const sourceFile = createSourceFile(project, 'type A = `Q${number}`')
-
- expect(generateFormattedCode(sourceFile)).resolves.toBe(
- formatWithPrettier(`
- const A = Type.TemplateLiteral("\`Q\${number}\`");
-
- type A = Static;
- `),
- )
- })
-
- test('complex template literal', () => {
- const sourceFile = createSourceFile(project, 'type A = `prefix-${string}-suffix`')
-
- expect(generateFormattedCode(sourceFile)).resolves.toBe(
- formatWithPrettier(`
- const A = Type.TemplateLiteral("\`prefix-\${string}-suffix\`");
-
- type A = Static;
- `),
- )
- })
- })
-
- describe('Typeof expressions', () => {
- test('typeof variable', () => {
- const sourceFile = createSourceFile(
- project,
- `
- const myVar = { x: 1, y: 'hello' }
- type A = typeof myVar
- `,
- )
-
- expect(generateFormattedCode(sourceFile)).resolves.toBe(
- formatWithPrettier(`
- const A = myVar;
-
- type A = Static;
- `),
- )
- })
-
- test('typeof with qualified name', () => {
- const sourceFile = createSourceFile(
- project,
- `
- namespace MyNamespace {
- export const config = { port: 3000 }
- }
- type A = typeof MyNamespace.config
- `,
- )
-
- expect(generateFormattedCode(sourceFile)).resolves.toBe(
- formatWithPrettier(`
- const A = MyNamespace_config;
-
- type A = Static;
- `),
- )
- })
- })
-})
diff --git a/tests/ts-morph/utils.ts b/tests/utils.ts
similarity index 100%
rename from tests/ts-morph/utils.ts
rename to tests/utils.ts