-
Notifications
You must be signed in to change notification settings - Fork 0
feat(dependency-management): Improve dependency filtering for better output #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| # Maincode Filtering | ||
|
|
||
| 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. | ||
|
|
||
| ## Overview | ||
|
|
||
| The code generator includes only the following nodes in the output: | ||
|
|
||
| 1. **Maincode nodes**: Types defined in the main source file (not imported) | ||
| 2. **Connected dependencies**: Types that are directly or indirectly referenced by maincode nodes | ||
|
|
||
| Imported types that are not referenced anywhere in the dependency chain are automatically filtered out. | ||
|
|
||
| ## Example | ||
|
|
||
| Consider the following file structure: | ||
|
|
||
| **external.ts** | ||
|
|
||
| ```typescript | ||
| export type UsedType = { | ||
| value: string | ||
| } | ||
|
|
||
| export type UnusedType = { | ||
| unused: boolean | ||
| } | ||
| ``` | ||
|
|
||
| **main.ts** | ||
|
|
||
| ```typescript | ||
| import { UsedType, UnusedType } from './external' | ||
|
|
||
| export interface MainInterface { | ||
| data: UsedType // UsedType is referenced and will be included | ||
| } | ||
|
|
||
| export type SimpleType = { | ||
| id: string | ||
| } | ||
|
|
||
| // UnusedType is imported but not referenced, so it will be filtered out | ||
| ``` | ||
|
|
||
| ### Generated Output | ||
|
|
||
| ```typescript | ||
| const result = generateCode({ | ||
| filePath: './main.ts', | ||
| }) | ||
| ``` | ||
|
|
||
| **Output automatically excludes unconnected imports:** | ||
|
|
||
| ```typescript | ||
| export const UsedType = Type.Object({ | ||
| value: Type.String(), | ||
| }) | ||
|
|
||
| export const MainInterface = Type.Object({ | ||
| data: UsedType, | ||
| }) | ||
|
|
||
| export const SimpleType = Type.Object({ | ||
| id: Type.String(), | ||
| }) | ||
|
|
||
| // UnusedType is not included because it's not connected to any maincode node | ||
| ``` | ||
|
|
||
| ## Dependency Chain Handling | ||
|
|
||
| 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: | ||
|
|
||
| **level1.ts** | ||
|
|
||
| ```typescript | ||
| export type Level1 = { | ||
| value: string | ||
| } | ||
| ``` | ||
|
|
||
| **level2.ts** | ||
|
|
||
| ```typescript | ||
| import { Level1 } from './level1' | ||
| export type Level2 = { | ||
| level1: Level1 | ||
| } | ||
| ``` | ||
|
|
||
| **main.ts** | ||
|
|
||
| ```typescript | ||
| import { Level2 } from './level2' | ||
|
|
||
| export interface MainType { | ||
| data: Level2 | ||
| } | ||
| ``` | ||
|
|
||
| The output automatically includes `Level1`, `Level2`, and `MainType` because they form a complete dependency chain starting from the maincode node `MainType`. | ||
|
|
||
| ## Implementation Details | ||
|
|
||
| The filtering algorithm: | ||
|
|
||
| 1. Identifies maincode nodes (nodes with `isImported: false`) and marks them as required | ||
| 2. Analyzes type references to identify which imported types are actually used | ||
| 3. Marks referenced types and their transitive dependencies as required | ||
| 4. Filters the final output to include only required nodes | ||
| 5. Returns the filtered nodes in topological order to maintain proper dependency ordering | ||
|
|
||
| This approach ensures that only types that are part of a connected dependency graph starting from maincode nodes are included in the output. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph' | ||
| import { TypeReferenceExtractor } from '@daxserver/validation-schema-codegen/traverse/type-reference-extractor' | ||
| import { | ||
| EnumDeclaration, | ||
| FunctionDeclaration, | ||
| InterfaceDeclaration, | ||
| Node, | ||
| TypeAliasDeclaration, | ||
| } from 'ts-morph' | ||
|
|
||
| export const extractDependencies = (nodeGraph: NodeGraph, requiredNodeIds: Set<string>): void => { | ||
| const processedNodes = new Set<string>() | ||
| const nodesToProcess = new Set(requiredNodeIds) | ||
| const typeReferenceExtractor = new TypeReferenceExtractor(nodeGraph) | ||
|
|
||
| // Process nodes iteratively until no new dependencies are found | ||
| while (nodesToProcess.size > 0) { | ||
| const currentNodeId = Array.from(nodesToProcess)[0] | ||
| if (!currentNodeId) break | ||
|
|
||
| nodesToProcess.delete(currentNodeId) | ||
|
|
||
| if (processedNodes.has(currentNodeId)) continue | ||
| processedNodes.add(currentNodeId) | ||
|
|
||
| const nodeData = nodeGraph.getNode(currentNodeId) | ||
| if (!nodeData) continue | ||
|
|
||
| let nodeToAnalyze: Node | undefined | ||
|
|
||
| if (nodeData.type === 'typeAlias') { | ||
| const typeAlias = nodeData.node as TypeAliasDeclaration | ||
| nodeToAnalyze = typeAlias.getTypeNode() | ||
| } else if (nodeData.type === 'interface') { | ||
| nodeToAnalyze = nodeData.node as InterfaceDeclaration | ||
| } else if (nodeData.type === 'enum') { | ||
| nodeToAnalyze = nodeData.node as EnumDeclaration | ||
| } else if (nodeData.type === 'function') { | ||
| nodeToAnalyze = nodeData.node as FunctionDeclaration | ||
| } | ||
|
|
||
| if (!nodeToAnalyze) continue | ||
|
|
||
| const typeReferences = typeReferenceExtractor.extractTypeReferences(nodeToAnalyze) | ||
|
|
||
| for (const referencedType of typeReferences) { | ||
| if (nodeGraph.hasNode(referencedType)) { | ||
| // Only add to required if not already processed | ||
| if (!requiredNodeIds.has(referencedType)) { | ||
| requiredNodeIds.add(referencedType) | ||
| nodesToProcess.add(referencedType) | ||
| } | ||
| nodeGraph.addDependency(referencedType, currentNodeId) | ||
| } | ||
|
Comment on lines
+44
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Name resolution is ambiguous across files with duplicate type names
Happy to sketch a small resolver that uses 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs/code mismatch: imported types are “referenced-only” in docs, but code includes all imports when the entry file has no local types
addLocalTypes currently marks every named import as required if the main file defines no local types, which contradicts this section (“Imported types: Only included if actually referenced”). Please align behavior or document the special-case explicitly.
Would you like to (A) change the code to only mark imported types as required when actually referenced, or (B) keep the current import-only seeding and document it as an exception?
🧰 Tools
🪛 LanguageTool
[grammar] ~43-~43: There might be a mistake here.
Context: ...: Always included (defined in main file) - Imported types: Only included if actua...
(QB_NEW_EN)
[grammar] ~44-~44: There might be a mistake here.
Context: ...ed if actually referenced by other types - Transitive dependencies: Automatically...
(QB_NEW_EN)
🤖 Prompt for AI Agents