ng-di-graph 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,252 @@
1
+ import { Project } from 'ts-morph';
2
+ import type { CliOptions, ParsedClass, StructuredWarnings } from '../types';
3
+ import { type Logger } from './logger';
4
+ export declare class AngularParser {
5
+ private _options;
6
+ private _logger?;
7
+ private _project?;
8
+ private _typeResolutionCache;
9
+ private _circularTypeRefs;
10
+ private _structuredWarnings;
11
+ constructor(_options: CliOptions, _logger?: Logger | undefined);
12
+ /**
13
+ * Reset global warning deduplication state (useful for testing)
14
+ */
15
+ static resetWarningState(): void;
16
+ /**
17
+ * Get structured warnings for analysis (Task 3.3)
18
+ * Returns a copy of the structured warnings collected during parsing
19
+ * @returns StructuredWarnings object with categorized warnings
20
+ */
21
+ getStructuredWarnings(): StructuredWarnings;
22
+ /**
23
+ * Add structured warning to collection (Task 3.3)
24
+ * Includes global deduplication for both structured warnings and console output
25
+ * @param category Warning category
26
+ * @param warning Warning object
27
+ */
28
+ private addStructuredWarning;
29
+ /**
30
+ * Load TypeScript project using ts-morph
31
+ * Implements FR-01 with error handling from PRD Section 13
32
+ */
33
+ loadProject(): void;
34
+ /**
35
+ * Get the loaded ts-morph Project instance
36
+ * @returns Project instance
37
+ * @throws Error if project not loaded
38
+ */
39
+ getProject(): Project;
40
+ /**
41
+ * Parse decorated classes from the loaded project
42
+ * Auto-loads project if not already loaded
43
+ * @returns Promise of parsed class information
44
+ */
45
+ parseClasses(): Promise<ParsedClass[]>;
46
+ /**
47
+ * Find all classes decorated with @Injectable, @Component, or @Directive
48
+ * Implements FR-02: Decorated Class Collection
49
+ * @returns Promise<ParsedClass[]> List of decorated classes
50
+ */
51
+ findDecoratedClasses(): Promise<ParsedClass[]>;
52
+ /**
53
+ * Parse a single class declaration for Angular decorators
54
+ * @param classDeclaration ts-morph ClassDeclaration
55
+ * @returns ParsedClass if decorated with Angular decorator, null otherwise
56
+ */
57
+ private parseClassDeclaration;
58
+ /**
59
+ * Find Angular decorator (@Injectable, @Component, @Directive) from list of decorators
60
+ * @param decorators Array of decorators from ts-morph
61
+ * @returns Angular decorator if found, null otherwise
62
+ */
63
+ private findAngularDecorator;
64
+ /**
65
+ * Extract decorator name from ts-morph Decorator
66
+ * Handles various import patterns and aliases
67
+ * @param decorator ts-morph Decorator
68
+ * @returns Decorator name string
69
+ */
70
+ private getDecoratorName;
71
+ /**
72
+ * Resolve decorator alias from import declarations with basic caching
73
+ * @param sourceFile Source file containing the decorator
74
+ * @param decoratorName Raw decorator name from AST
75
+ * @returns Original decorator name if alias found, null otherwise
76
+ */
77
+ private resolveDecoratorAlias;
78
+ /**
79
+ * Determine NodeKind from Angular decorator
80
+ * @param decorator Angular decorator
81
+ * @returns NodeKind mapping
82
+ */
83
+ private determineNodeKind;
84
+ /**
85
+ * Detect and warn about anonymous class expressions
86
+ * Handles patterns like: const X = Decorator()(class { ... })
87
+ * @param sourceFile Source file to analyze
88
+ */
89
+ private detectAnonymousClasses;
90
+ /**
91
+ * Extract constructor dependencies from a class declaration
92
+ * Implements FR-03: Constructor parameter analysis
93
+ * Implements TDD Cycle 2.1: inject() function detection
94
+ * @param classDeclaration ts-morph ClassDeclaration
95
+ * @returns Array of parsed dependencies
96
+ */
97
+ private extractConstructorDependencies;
98
+ /**
99
+ * Check if type reference is circular (Task 3.3)
100
+ * @param typeText Type text to check
101
+ * @param typeNode TypeNode for context
102
+ * @returns True if circular reference detected
103
+ */
104
+ private isCircularTypeReference;
105
+ /**
106
+ * Check if type is generic (Task 3.3)
107
+ * @param typeText Type text to check
108
+ * @returns True if generic type
109
+ */
110
+ private isGenericType;
111
+ /**
112
+ * Handle generic types (Task 3.3)
113
+ * @param typeText Generic type text
114
+ * @param filePath File path for context
115
+ * @param lineNumber Line number
116
+ * @param columnNumber Column number
117
+ * @returns Token string or null
118
+ */
119
+ private handleGenericType;
120
+ /**
121
+ * Check if type is union type (Task 3.3)
122
+ * @param typeText Type text to check
123
+ * @returns True if union type
124
+ */
125
+ private isUnionType;
126
+ /**
127
+ * Handle union types (Task 3.3)
128
+ * @param typeText Union type text
129
+ * @param filePath File path for context
130
+ * @param lineNumber Line number
131
+ * @param columnNumber Column number
132
+ * @returns Null (union types are skipped)
133
+ */
134
+ private handleUnionType;
135
+ /**
136
+ * Check if type can be resolved through imports (Task 3.3)
137
+ * Handles module-scoped types (e.g., MyModule.ScopedService)
138
+ * @param typeNode TypeNode to check
139
+ * @returns True if type can be resolved
140
+ */
141
+ private canResolveType;
142
+ /**
143
+ * Enhanced type token extraction with advanced analysis (Task 3.3)
144
+ * @param typeNode TypeNode to extract token from
145
+ * @param filePath File path for context
146
+ * @param lineNumber Line number
147
+ * @param columnNumber Column number
148
+ * @returns Token string or null
149
+ */
150
+ private extractTypeTokenEnhanced;
151
+ /**
152
+ * Resolve inferred types with enhanced validation (Task 3.3)
153
+ * @param type Type object from ts-morph
154
+ * @param typeText Type text representation
155
+ * @param param Parameter declaration for context
156
+ * @param filePath File path for warnings
157
+ * @param lineNumber Line number for warnings
158
+ * @param columnNumber Column number for warnings
159
+ * @returns Resolved token or null
160
+ */
161
+ private resolveInferredTypeEnhanced;
162
+ /**
163
+ * Parse a single constructor parameter to extract dependency token
164
+ * Implements FR-03 token resolution priority: @Inject > type annotation > inferred type
165
+ * Implements FR-04 parameter decorator handling
166
+ * @param param ts-morph ParameterDeclaration
167
+ * @returns ParsedDependency if valid dependency, null if should be skipped
168
+ */
169
+ private parseConstructorParameter;
170
+ /**
171
+ * Extract token from @Inject decorator
172
+ * @param decorator @Inject decorator
173
+ * @returns Token string or null
174
+ */
175
+ private extractInjectToken;
176
+ /**
177
+ * Check if type should be skipped (any/unknown types)
178
+ * Implements FR-09: Skip dependencies whose type resolves to any/unknown
179
+ * @param typeText Type text to check
180
+ * @returns True if should be skipped
181
+ */
182
+ private shouldSkipType;
183
+ /**
184
+ * Check if type is primitive and should be skipped
185
+ * @param typeText Type text to check
186
+ * @returns True if primitive type
187
+ */
188
+ private isPrimitiveType;
189
+ /**
190
+ * Extract parameter decorators from constructor parameter
191
+ * Implements FR-04: Parameter decorator handling (@Optional, @Self, @SkipSelf, @Host)
192
+ * Optimized for performance with early returns and minimal object allocation
193
+ * @param param ts-morph ParameterDeclaration
194
+ * @returns EdgeFlags object with detected decorators
195
+ */
196
+ private extractParameterDecorators;
197
+ /**
198
+ * Extract dependencies from inject() function calls in class properties
199
+ * Implements TDD Cycle 2.1: Modern Angular inject() pattern detection
200
+ * @param classDeclaration ts-morph ClassDeclaration
201
+ * @returns Array of parsed dependencies from inject() calls
202
+ */
203
+ private extractInjectFunctionDependencies;
204
+ /**
205
+ * Parse a property declaration for inject() function calls
206
+ * @param property ts-morph PropertyDeclaration
207
+ * @returns ParsedDependency if inject() call found, null otherwise
208
+ */
209
+ private parseInjectProperty;
210
+ /**
211
+ * Parse options object from inject() function call
212
+ * @param optionsArg Options argument from inject() call
213
+ * @returns EdgeFlags object with parsed options
214
+ */
215
+ private parseInjectOptions;
216
+ /**
217
+ * Analyze parameter decorators for TDD Cycle 1.1
218
+ * Legacy parameter decorator detection method for @Optional, @Self, @SkipSelf, @Host
219
+ * @param parameter ParameterDeclaration to analyze
220
+ * @param includeDecorators Whether to include decorators in analysis
221
+ * @returns EdgeFlags object with detected decorators
222
+ */
223
+ private analyzeParameterDecorators;
224
+ /**
225
+ * Check if inject() function is imported from @angular/core
226
+ * Prevents false positives from custom inject() functions
227
+ * @param sourceFile Source file to check imports
228
+ * @returns True if Angular inject is imported
229
+ */
230
+ private isAngularInjectImported;
231
+ /**
232
+ * Analyze inject() function call expression to extract token and options
233
+ * Implements TDD Cycle 2.1 - Modern Angular inject() pattern support
234
+ * @param expression Expression to analyze (should be a CallExpression)
235
+ * @returns ParameterAnalysisResult or null if not a valid inject() call
236
+ */
237
+ private analyzeInjectCall;
238
+ /**
239
+ * Collect verbose statistics for decorator analysis
240
+ * @param param Parameter declaration being analyzed
241
+ * @param dependency Parsed dependency result
242
+ * @param verboseStats Statistics object to update
243
+ */
244
+ private collectVerboseStats;
245
+ /**
246
+ * Output comprehensive verbose analysis summary
247
+ * @param dependencies All parsed dependencies
248
+ * @param verboseStats Collected statistics
249
+ * @param classDeclaration Class being analyzed
250
+ */
251
+ private outputVerboseAnalysis;
252
+ }
@@ -0,0 +1,24 @@
1
+ import { type Logger } from '../core/logger';
2
+ import type { Graph } from '../types';
3
+ /**
4
+ * JSON formatter for dependency graph output
5
+ * Produces pretty-printed JSON with 2-space indentation
6
+ */
7
+ export declare class JsonFormatter {
8
+ /**
9
+ * Logger instance for verbose output (optional)
10
+ * @private
11
+ */
12
+ private readonly _logger?;
13
+ /**
14
+ * Create a new JSON formatter
15
+ * @param logger Optional Logger instance for verbose mode
16
+ */
17
+ constructor(logger?: Logger);
18
+ /**
19
+ * Format a dependency graph as JSON
20
+ * @param graph The dependency graph to format
21
+ * @returns Pretty-printed JSON string
22
+ */
23
+ format(graph: Graph): string;
24
+ }
@@ -0,0 +1,31 @@
1
+ import { type Logger } from '../core/logger';
2
+ import type { Graph } from '../types';
3
+ /**
4
+ * Mermaid formatter for dependency graph output
5
+ * Produces flowchart LR syntax compatible with Mermaid Live Editor
6
+ */
7
+ export declare class MermaidFormatter {
8
+ /**
9
+ * Logger instance for verbose output (optional)
10
+ * @private
11
+ */
12
+ private readonly _logger?;
13
+ /**
14
+ * Create a new Mermaid formatter
15
+ * @param logger Optional Logger instance for verbose mode
16
+ */
17
+ constructor(logger?: Logger);
18
+ /**
19
+ * Format a dependency graph as Mermaid flowchart
20
+ * @param graph The dependency graph to format
21
+ * @returns Mermaid flowchart string
22
+ */
23
+ format(graph: Graph): string;
24
+ /**
25
+ * Sanitize node names for Mermaid compatibility
26
+ * Replaces special characters that break Mermaid syntax
27
+ * @param nodeName The original node name
28
+ * @returns Sanitized node name
29
+ */
30
+ private sanitizeNodeName;
31
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Common Test Utilities
3
+ * Provides reusable helper functions for test setup and mocking
4
+ */
5
+ import type { LogCategory, LogContext, Logger, LoggingStats } from '../../core/logger';
6
+ import { AngularParser } from '../../core/parser';
7
+ import type { CliOptions, Edge, Graph, Node } from '../../types';
8
+ /**
9
+ * Default test fixtures directory path
10
+ */
11
+ export declare const TEST_FIXTURES_DIR = "./src/tests/fixtures";
12
+ export declare const TEST_TSCONFIG = "./src/tests/fixtures/tsconfig.json";
13
+ /**
14
+ * Create default CliOptions for testing
15
+ * @param overrides - Partial options to override defaults
16
+ * @returns Complete CliOptions object
17
+ */
18
+ export declare function createTestCliOptions(overrides?: Partial<CliOptions>): CliOptions;
19
+ /**
20
+ * Create and initialize an AngularParser for testing
21
+ * @param optionsOverrides - Optional CliOptions overrides
22
+ * @param loadProject - Whether to automatically load the project (default: true)
23
+ * @returns Initialized AngularParser instance
24
+ */
25
+ export declare function createTestParser(optionsOverrides?: Partial<CliOptions>, loadProject?: boolean): AngularParser;
26
+ /**
27
+ * Create a Logger instance for testing
28
+ * @param verbose - Whether to enable verbose mode (default: false)
29
+ * @returns Logger instance or undefined if verbose is false
30
+ */
31
+ export declare function createTestLogger(verbose?: boolean): Logger | undefined;
32
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
33
+ /**
34
+ * Lightweight stub logger that captures log events without touching the console.
35
+ * Enables deterministic assertions in tests that need to observe instrumentation.
36
+ */
37
+ export declare class StubLogger implements Logger {
38
+ readonly logs: Array<{
39
+ level: LogLevel;
40
+ category: LogCategory;
41
+ message: string;
42
+ context?: LogContext;
43
+ }>;
44
+ private readonly _timers;
45
+ private readonly _completedTimers;
46
+ private readonly _stats;
47
+ debug(category: LogCategory, message: string, context?: LogContext): void;
48
+ info(category: LogCategory, message: string, context?: LogContext): void;
49
+ warn(category: LogCategory, message: string, context?: LogContext): void;
50
+ error(category: LogCategory, message: string, context?: LogContext): void;
51
+ time(label: string): void;
52
+ timeEnd(label: string): number;
53
+ getStats(): LoggingStats;
54
+ getCompletedTimerLabels(): string[];
55
+ getTimerDuration(label: string): number | undefined;
56
+ private _record;
57
+ }
58
+ /**
59
+ * Factory for stub logger instances so tests can avoid importing the class directly.
60
+ */
61
+ export declare function createStubLogger(): StubLogger;
62
+ /**
63
+ * Create a simple mock graph for testing
64
+ * @param options - Optional graph customization
65
+ * @returns Graph object
66
+ */
67
+ export declare function createMockGraph(options?: {
68
+ nodes?: Node[];
69
+ edges?: Edge[];
70
+ circularDependencies?: string[][];
71
+ }): Graph;
72
+ /**
73
+ * Create a minimal empty graph
74
+ * @returns Empty graph object
75
+ */
76
+ export declare function createEmptyGraph(): Graph;
77
+ /**
78
+ * Mock console methods for testing CLI output
79
+ * @returns Object with mocked console methods and restore function
80
+ */
81
+ export declare function mockConsole(): {
82
+ log: {
83
+ calls: () => string[];
84
+ mock: typeof console.log;
85
+ };
86
+ error: {
87
+ calls: () => string[];
88
+ mock: typeof console.error;
89
+ };
90
+ warn: {
91
+ calls: () => string[];
92
+ mock: typeof console.warn;
93
+ };
94
+ restore: () => void;
95
+ };
96
+ /**
97
+ * Wait for async operations to complete
98
+ * @param ms - Milliseconds to wait (default: 0)
99
+ * @returns Promise that resolves after specified time
100
+ */
101
+ export declare function wait(ms?: number): Promise<void>;
102
+ /**
103
+ * Reset warning state for parser tests
104
+ * Ensures clean test isolation
105
+ */
106
+ export declare function resetParserState(): void;
107
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Core TypeScript interfaces for ng-di-graph CLI tool
3
+ * Based on PRD requirements in @docs/prd/mvp-requirements.md
4
+ */
5
+ export type NodeKind = 'service' | 'component' | 'directive' | 'unknown';
6
+ export interface Node {
7
+ id: string;
8
+ kind: NodeKind;
9
+ }
10
+ export interface EdgeFlags {
11
+ optional?: boolean;
12
+ self?: boolean;
13
+ skipSelf?: boolean;
14
+ host?: boolean;
15
+ }
16
+ export interface Edge {
17
+ from: string;
18
+ to: string;
19
+ flags?: EdgeFlags;
20
+ isCircular?: boolean;
21
+ }
22
+ export interface Graph {
23
+ nodes: Node[];
24
+ edges: Edge[];
25
+ circularDependencies: string[][];
26
+ }
27
+ export interface CliOptions {
28
+ project: string;
29
+ format: 'json' | 'mermaid';
30
+ entry?: string[];
31
+ direction: 'upstream' | 'downstream' | 'both';
32
+ includeDecorators: boolean;
33
+ out?: string;
34
+ verbose: boolean;
35
+ }
36
+ export interface ParsedClass {
37
+ name: string;
38
+ kind: NodeKind;
39
+ filePath: string;
40
+ dependencies: ParsedDependency[];
41
+ }
42
+ export interface ParsedDependency {
43
+ token: string;
44
+ flags?: EdgeFlags;
45
+ parameterName: string;
46
+ }
47
+ export interface ParameterAnalysisResult {
48
+ token: string;
49
+ flags: EdgeFlags;
50
+ source: 'decorator' | 'inject' | 'type';
51
+ }
52
+ export interface ParserError extends Error {
53
+ code: 'TSCONFIG_NOT_FOUND' | 'TSCONFIG_INVALID' | 'PROJECT_LOAD_FAILED' | 'COMPILATION_ERROR';
54
+ filePath?: string;
55
+ }
56
+ export type { CliError, ErrorCode, ExitCodes } from '../core/error-handler';
57
+ export interface VerboseStats {
58
+ decoratorCounts: {
59
+ optional: number;
60
+ self: number;
61
+ skipSelf: number;
62
+ host: number;
63
+ };
64
+ skippedDecorators: Array<{
65
+ name: string;
66
+ reason: string;
67
+ }>;
68
+ parametersWithDecorators: number;
69
+ parametersWithoutDecorators: number;
70
+ legacyDecoratorsUsed: number;
71
+ injectPatternsUsed: number;
72
+ totalProcessingTime: number;
73
+ totalParameters: number;
74
+ }
75
+ /**
76
+ * Enhanced Type Validation - Task 3.3
77
+ * Structured warning system for comprehensive type analysis
78
+ */
79
+ export interface Warning {
80
+ type: string;
81
+ message: string;
82
+ file: string;
83
+ line?: number;
84
+ column?: number;
85
+ suggestion?: string;
86
+ severity: 'warning' | 'error' | 'info';
87
+ }
88
+ export interface StructuredWarnings {
89
+ categories: {
90
+ typeResolution: Warning[];
91
+ skippedTypes: Warning[];
92
+ unresolvedImports: Warning[];
93
+ circularReferences: Warning[];
94
+ performance: Warning[];
95
+ };
96
+ totalCount: number;
97
+ }
98
+ export interface TypeValidationResult {
99
+ isValid: boolean;
100
+ token: string | null;
101
+ warnings: Warning[];
102
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "ng-di-graph",
3
+ "version": "0.1.0",
4
+ "description": "Angular DI dependency graph CLI tool",
5
+ "main": "dist/cli/index.cjs",
6
+ "bin": {
7
+ "ng-di-graph": "dist/cli/index.cjs"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "dev": "tsx src/cli/index.ts",
14
+ "dev:node": "tsx src/cli/index.ts",
15
+ "build:bundle": "tsdown --config tsdown.config.ts",
16
+ "build:types": "tsc --emitDeclarationOnly --outDir dist",
17
+ "build": "npm run build:bundle && npm run build:types",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest watch",
20
+ "test:coverage": "vitest run --coverage",
21
+ "lint": "biome check src",
22
+ "lint:fix": "biome check --write src",
23
+ "format": "biome format src --write",
24
+ "format:check": "biome format src",
25
+ "check": "npm run lint && npm run typecheck",
26
+ "typecheck": "tsc --noEmit",
27
+ "clean": "rimraf dist",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "angular",
32
+ "dependency-injection",
33
+ "typescript",
34
+ "cli",
35
+ "graph"
36
+ ],
37
+ "author": "",
38
+ "license": "MIT",
39
+ "devDependencies": {
40
+ "@biomejs/biome": "^2.3.7",
41
+ "@types/node": "^24.10.1",
42
+ "@vitest/coverage-v8": "^4.0.13",
43
+ "rimraf": "^6.1.2",
44
+ "tsdown": "^0.16.6",
45
+ "tsx": "^4.8.1",
46
+ "typescript": "^5.9.3",
47
+ "vitest": "^4.0.10"
48
+ },
49
+ "dependencies": {
50
+ "commander": "^14.0.0",
51
+ "ts-morph": "^27.0.2"
52
+ },
53
+ "engines": {
54
+ "node": ">=20.19.0 <21.0.0 || >=22.12.0"
55
+ }
56
+ }