Skip to content

Commit b363896

Browse files
authored
feat(dependency-management): Improve dependency filtering for better output (#20)
This commit introduces several improvements to the dependency management system: - Filters out unconnected dependencies to optimize output - Automatically marks local types as required - Marks imported types as required only if they are actually referenced by other types - Automatically includes transitive dependencies when their parent types are referenced The key changes are: - Added `maincodeNodeIds` and `requiredNodeIds` sets to track main code and required nodes - Updated `addLocalTypes` to mark local types as both main code and required - Adjusted `collectFromImports` to only mark imported types as required if they are referenced - Implemented filtering logic in `getNodesToPrint` to return only the required nodes These changes ensure the dependency analysis produces a more optimized output, only including the necessary types and their transitive dependencies.
1 parent 44a13e5 commit b363896

12 files changed

+1408
-383
lines changed

docs/dependency-management.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Dependency Management
22

3-
Graph-based dependency analysis for proper type processing order.
3+
Graph-based dependency analysis for proper type processing order with intelligent filtering of unconnected dependencies.
44

55
## Components
66

@@ -9,6 +9,7 @@ Graph-based dependency analysis for proper type processing order.
99
Main orchestrator in `src/traverse/dependency-traversal.ts`:
1010

1111
- Coordinates dependency collection
12+
- Filters out unconnected dependencies
1213
- Returns topologically sorted nodes
1314

1415
### FileGraph
@@ -28,11 +29,20 @@ Handles type-level dependencies in `src/traverse/node-graph.ts`:
2829

2930
## Process
3031

31-
1. Collect local types from main file
32-
2. Process import chains recursively
33-
3. Extract type dependencies
34-
4. Build dependency graph
35-
5. Topological sort for processing order
32+
1. Collect local types from main file (automatically marked as required)
33+
2. Process import chains recursively (adds types to graph)
34+
3. Extract type dependencies (marks referenced types as required)
35+
4. Filter to only connected dependencies
36+
5. Build dependency graph
37+
6. Topological sort for processing order
38+
39+
## Dependency Filtering
40+
41+
The system now filters out unconnected dependencies to optimize output:
42+
43+
- **Local types**: Always included (defined in main file)
44+
- **Imported types**: Only included if actually referenced by other types
45+
- **Transitive dependencies**: Automatically included when their parent types are referenced
3646

3747
## Circular Dependencies
3848

docs/maincode-filtering.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Maincode Filtering
2+
3+
The code generator automatically filters the output to include only maincode nodes and their connected dependencies. This feature reduces the generated output by excluding imported types that are not actually used by the main code.
4+
5+
## Overview
6+
7+
The code generator includes only the following nodes in the output:
8+
9+
1. **Maincode nodes**: Types defined in the main source file (not imported)
10+
2. **Connected dependencies**: Types that are directly or indirectly referenced by maincode nodes
11+
12+
Imported types that are not referenced anywhere in the dependency chain are automatically filtered out.
13+
14+
## Example
15+
16+
Consider the following file structure:
17+
18+
**external.ts**
19+
20+
```typescript
21+
export type UsedType = {
22+
value: string
23+
}
24+
25+
export type UnusedType = {
26+
unused: boolean
27+
}
28+
```
29+
30+
**main.ts**
31+
32+
```typescript
33+
import { UsedType, UnusedType } from './external'
34+
35+
export interface MainInterface {
36+
data: UsedType // UsedType is referenced and will be included
37+
}
38+
39+
export type SimpleType = {
40+
id: string
41+
}
42+
43+
// UnusedType is imported but not referenced, so it will be filtered out
44+
```
45+
46+
### Generated Output
47+
48+
```typescript
49+
const result = generateCode({
50+
filePath: './main.ts',
51+
})
52+
```
53+
54+
**Output automatically excludes unconnected imports:**
55+
56+
```typescript
57+
export const UsedType = Type.Object({
58+
value: Type.String(),
59+
})
60+
61+
export const MainInterface = Type.Object({
62+
data: UsedType,
63+
})
64+
65+
export const SimpleType = Type.Object({
66+
id: Type.String(),
67+
})
68+
69+
// UnusedType is not included because it's not connected to any maincode node
70+
```
71+
72+
## Dependency Chain Handling
73+
74+
The filtering algorithm correctly handles deep dependency chains. If a maincode node references a type that has its own dependencies, all dependencies in the chain are included:
75+
76+
**level1.ts**
77+
78+
```typescript
79+
export type Level1 = {
80+
value: string
81+
}
82+
```
83+
84+
**level2.ts**
85+
86+
```typescript
87+
import { Level1 } from './level1'
88+
export type Level2 = {
89+
level1: Level1
90+
}
91+
```
92+
93+
**main.ts**
94+
95+
```typescript
96+
import { Level2 } from './level2'
97+
98+
export interface MainType {
99+
data: Level2
100+
}
101+
```
102+
103+
The output automatically includes `Level1`, `Level2`, and `MainType` because they form a complete dependency chain starting from the maincode node `MainType`.
104+
105+
## Implementation Details
106+
107+
The filtering algorithm:
108+
109+
1. Identifies maincode nodes (nodes with `isImported: false`) and marks them as required
110+
2. Analyzes type references to identify which imported types are actually used
111+
3. Marks referenced types and their transitive dependencies as required
112+
4. Filters the final output to include only required nodes
113+
5. Returns the filtered nodes in topological order to maintain proper dependency ordering
114+
115+
This approach ensures that only types that are part of a connected dependency graph starting from maincode nodes are included in the output.

docs/overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,5 @@ export type User = Static<typeof User>
4848
- [handler-system.md](./handler-system.md) - Type conversion
4949
- [compiler-configuration.md](./compiler-configuration.md) - Compiler options and script targets
5050
- [dependency-management.md](./dependency-management.md) - Dependency analysis
51+
- [maincode-filtering.md](./maincode-filtering.md) - Filtering output to maincode and dependencies
5152
- [testing.md](./testing.md) - Testing
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph'
2+
import { TypeReferenceExtractor } from '@daxserver/validation-schema-codegen/traverse/type-reference-extractor'
3+
import {
4+
EnumDeclaration,
5+
FunctionDeclaration,
6+
InterfaceDeclaration,
7+
Node,
8+
TypeAliasDeclaration,
9+
} from 'ts-morph'
10+
11+
export const extractDependencies = (nodeGraph: NodeGraph, requiredNodeIds: Set<string>): void => {
12+
const processedNodes = new Set<string>()
13+
const nodesToProcess = new Set(requiredNodeIds)
14+
const typeReferenceExtractor = new TypeReferenceExtractor(nodeGraph)
15+
16+
// Process nodes iteratively until no new dependencies are found
17+
while (nodesToProcess.size > 0) {
18+
const currentNodeId = Array.from(nodesToProcess)[0]
19+
if (!currentNodeId) break
20+
21+
nodesToProcess.delete(currentNodeId)
22+
23+
if (processedNodes.has(currentNodeId)) continue
24+
processedNodes.add(currentNodeId)
25+
26+
const nodeData = nodeGraph.getNode(currentNodeId)
27+
if (!nodeData) continue
28+
29+
let nodeToAnalyze: Node | undefined
30+
31+
if (nodeData.type === 'typeAlias') {
32+
const typeAlias = nodeData.node as TypeAliasDeclaration
33+
nodeToAnalyze = typeAlias.getTypeNode()
34+
} else if (nodeData.type === 'interface') {
35+
nodeToAnalyze = nodeData.node as InterfaceDeclaration
36+
} else if (nodeData.type === 'enum') {
37+
nodeToAnalyze = nodeData.node as EnumDeclaration
38+
} else if (nodeData.type === 'function') {
39+
nodeToAnalyze = nodeData.node as FunctionDeclaration
40+
}
41+
42+
if (!nodeToAnalyze) continue
43+
44+
const typeReferences = typeReferenceExtractor.extractTypeReferences(nodeToAnalyze)
45+
46+
for (const referencedType of typeReferences) {
47+
if (nodeGraph.hasNode(referencedType)) {
48+
// Only add to required if not already processed
49+
if (!requiredNodeIds.has(referencedType)) {
50+
requiredNodeIds.add(referencedType)
51+
nodesToProcess.add(referencedType)
52+
}
53+
nodeGraph.addDependency(referencedType, currentNodeId)
54+
}
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)